1 2 module state_machine.base; 3 4 /// State machine using an integer or string state variable. 5 mixin template StateMachine(alias variable, states...) 6 if(((is(typeof(variable) : int) || is(typeof(variable) : string)) && states.length > 0) || 7 (is(typeof(variable) == enum) && states.length == 0)) 8 { 9 import state_machine.util; 10 11 import std.algorithm; 12 import std.meta; 13 import std.traits; 14 15 private 16 { 17 static if(is(typeof(variable()))) 18 { 19 // For properties, .stringof ends with parentheses. 20 enum __name__ = variable.stringof[0 .. $ - 2]; 21 } 22 else 23 { 24 enum __name__ = variable.stringof; 25 } 26 27 static if(is(typeof(variable) == enum)) 28 { 29 // States on enum types are derived from their members. 30 enum __states__ = __traits(allMembers, typeof(variable)); 31 } 32 else 33 { 34 enum __states__ = states; 35 } 36 37 struct BeforeTransition 38 { 39 string state; 40 } 41 42 struct AfterTransition 43 { 44 string state; 45 } 46 47 typeof(variable) __prevState__; 48 } 49 50 @property 51 static string[] opDispatch(string op : __name__ ~ "Names")() 52 { 53 return [ __states__ ]; 54 } 55 56 @property 57 static typeof(variable)[string] opDispatch(string op : __name__ ~ "Values")() 58 { 59 typeof(variable)[string] values; 60 61 static if(is(typeof(variable) == enum)) 62 { 63 foreach(string state; __states__) 64 { 65 values[state] = __traits(getMember, typeof(variable), state); 66 } 67 } 68 else static if(is(typeof(variable) : int)) 69 { 70 foreach(typeof(variable) index, string state; __states__) 71 { 72 values[state] = index; 73 } 74 } 75 else 76 { 77 foreach(string state; __states__) 78 { 79 // The most useful. 80 values[state] = state; 81 } 82 } 83 84 return values; 85 } 86 87 @property 88 bool opDispatch(string state)() 89 if([ __states__ ].countUntil(state) != -1) 90 { 91 // Compare state variable. 92 static if(is(typeof(variable) == enum)) 93 { 94 return variable == __traits(getMember, typeof(variable), state); 95 } 96 else static if(is(typeof(variable) : int)) 97 { 98 // Ensure countUntil happens at compile-time. 99 enum index = [ __states__ ].countUntil(state); 100 return variable == index; 101 } 102 else 103 { 104 return variable == state; 105 } 106 } 107 108 typeof(variable) opDispatch(string op : "prev" ~ __name__)() 109 { 110 return __prevState__; 111 } 112 113 void opDispatch(string op : "revert" ~ __name__)() 114 { 115 variable = __prevState__; 116 } 117 118 bool opDispatch(string op)() 119 if(op.length > 2 && op[0 .. 2] == "to" && 120 [ __states__ ].map!toTitle.countUntil(op[2 .. $]) != -1) 121 { 122 enum index = [ __states__ ].map!toTitle.countUntil(op[2 .. $]); 123 124 // Fire and check any BeforeTransition callbacks. 125 foreach(name; __traits(allMembers, typeof(this))) 126 { 127 alias member = Alias!(__traits(getMember, typeof(this), name)); 128 129 static if(is(typeof(member) == function)) 130 { 131 static if(arity!member <= 1) 132 { 133 foreach(attribute; __traits(getAttributes, member)) 134 { 135 static if(is(attribute == BeforeTransition) || 136 (is(typeof(attribute) == BeforeTransition) && 137 attribute.state == __states__[index])) 138 { 139 static if(is(typeof(member()) : bool)) 140 { 141 static if(arity!member == 1) 142 { 143 // Callback can accept destination state. 144 bool result = member(__states__[index]); 145 } 146 else 147 { 148 bool result = member(); 149 } 150 151 if(!result) 152 { 153 return false; 154 } 155 } 156 else 157 { 158 static if(arity!member == 1) 159 { 160 member(__states__[index]); 161 } 162 else 163 { 164 member(); 165 } 166 } 167 } 168 } 169 } 170 } 171 } 172 173 // Save previous state. 174 __prevState__ = variable; 175 176 // Update state variable. 177 static if(is(typeof(variable) == enum)) 178 { 179 enum string constant = __states__[index]; 180 variable = __traits(getMember, typeof(variable), constant); 181 } 182 else static if(is(typeof(variable) : int)) 183 { 184 variable = index; 185 } 186 else 187 { 188 variable = __states__[index]; 189 } 190 191 // Fire any AfterTransition callbacks. 192 foreach(name; __traits(allMembers, typeof(this))) 193 { 194 alias member = Alias!(__traits(getMember, typeof(this), name)); 195 196 static if(is(typeof(member) == function)) 197 { 198 static if(arity!member <= 1) 199 { 200 foreach(attribute; __traits(getAttributes, member)) 201 { 202 static if(is(attribute == AfterTransition) || 203 (is(typeof(attribute) == AfterTransition) && 204 attribute.state == __states__[index])) 205 { 206 static if(arity!member == 1) 207 { 208 // Callback can accept destination state. 209 member(__states__[index]); 210 } 211 else 212 { 213 member(); 214 } 215 } 216 } 217 } 218 } 219 } 220 221 return true; 222 } 223 }