1 module flow; 2 3 4 import std.range, std.algorithm, std.array, std.format; 5 import cushion; 6 7 8 /*************************************************************** 9 * Interface defines the method of the state 10 * 11 * This interface has an update method, that does anything 12 * and returns the next state. 13 */ 14 interface TestFlow 15 { 16 /// 17 TestFlow update() @safe; 18 } 19 20 private struct StateTransitorPolicy(MyState, MyEvent) 21 { 22 alias State = MyState; 23 alias Event = MyEvent; 24 enum consumeMode = ConsumeMode.separate; 25 } 26 private alias StateTransitor(State, Event) 27 = cushion.StateTransitor!(StateTransitorPolicy!(State,Event)); 28 29 /*************************************************************** 30 * Base class of state pattern 31 * 32 * This class is a class that inherits from the TestFlow interface, 33 * which is the base class for state patterns. 34 * TestFlow has a primitive implementation by passing through State. 35 * This provides an implementation for the `update` declared in 36 * the interface to return the next state specified in `setNext`. 37 * And some handlers [`onEnter`, `onExit`, `onEnterChild`, `onExitChild`] 38 * are available. 39 */ 40 abstract class BaseTestFlow: State!TestFlow 41 { 42 /// 43 abstract string name() const @property; 44 } 45 46 /*************************************************************** 47 * Concrete class of state pattern named `Child1Stm` 48 * 49 * This class is a concrete class of state pattern. 50 * The implementation of the behavior that should be done by 51 * the `update` is complete, and STM is driven internally. 52 */ 53 class Child1Stm: BaseTestFlow 54 { 55 private: 56 mixin(loadStmFromCsv!"flow-child1"("#>")); 57 Event[][] _stepData; 58 Event[] _step; 59 public: 60 /// 61 StateTransitor!(State, Event) _stm; 62 /// ditto 63 alias _stm this; 64 /// 65 this() @safe 66 { 67 _stm = makeStm(); 68 with (Event) 69 _stepData = [[b],[b]]; 70 } 71 /// 72 final void initialize() @safe 73 { 74 _step = _stepData.front; 75 _stepData.popFront(); 76 } 77 /// 78 override TestFlow update() @safe 79 { 80 if (emptyEvents) 81 { 82 _stm.put(_step.front); 83 _step.popFront(); 84 } 85 consume(); 86 return super.update(); 87 } 88 /// 89 override string name() const @property 90 { 91 return _stm.matrixName; 92 } 93 } 94 95 /*************************************************************** 96 * Concrete class of state pattern named `Child2Stm` 97 * 98 * Implement a different state from `Child1Stm` 99 */ 100 class Child2Stm: BaseTestFlow 101 { 102 private: 103 mixin(loadStmFromCsv!"flow-child2"("#>")); 104 Event[][] _stepData; 105 Event[] _step; 106 public: 107 /// 108 StateTransitor!(State, Event) _stm; 109 /// ditto 110 alias _stm this; 111 /// 112 this() @safe 113 { 114 _stm = makeStm(); 115 with (Event) 116 _stepData = [[b],[a]]; 117 } 118 /// 119 final void initialize() @safe 120 { 121 _step = _stepData.front; 122 _stepData.popFront(); 123 } 124 /// 125 override TestFlow update() @safe 126 { 127 if (emptyEvents) 128 { 129 _stm.put(_step.front); 130 _step.popFront(); 131 } 132 consume(); 133 return super.update(); 134 } 135 /// 136 override string name() const @property 137 { 138 return _stm.matrixName; 139 } 140 } 141 142 /*************************************************************** 143 * Concrete class of state pattern named `MainStm` 144 * 145 * Implement a different state from `Child1Stm` / `Child2Stm`. 146 * This class acts as a parent to `Child1Stm` and `Child2Stm`. 147 * The `update` method of this class may return the child class 148 * `Child1Stm` or `Child2Stm` as the next state. 149 * This class is also "previous state", which backs when 150 * `Child1Stm` or `Child2Stm` ends. 151 * Also, in this class, there is a gimmick that can record the 152 * transition of state using each handler and output the result. 153 */ 154 class MainStm: BaseTestFlow 155 { 156 private: 157 Child1Stm _child1; 158 Child2Stm _child2; 159 160 mixin(loadStmFromCsv!"flow-main"("#>")); 161 Event[] _step; 162 public: 163 /// 164 StateTransitor!(State, Event) _stm; 165 /// ditto 166 alias _stm this; 167 /// 168 void delegate() onError; 169 /// 170 Appender!string message; 171 /// 172 this() @safe 173 { 174 _stm = makeStm(); 175 _child1 = new Child1Stm; 176 _child2 = new Child2Stm; 177 178 void setMsgEv(Stm, Ev)(Stm stm, Ev e) 179 { 180 message.formattedWrite( 181 "[%s-%s]onEvent:%s(%s)\n", 182 stm.name, stm.stateNames[stm.currentState], stm.eventNames[e], e); 183 } 184 void setMsgSt(Stm, St)(Stm stm, St oldSt, St newSt) 185 { 186 message.formattedWrite( 187 "[%s-%s]onStateChanged:%s(%s)->%s(%s)\n", 188 stm.name, stm.stateNames[stm.currentState], stm.stateNames[oldSt], oldSt, stm.stateNames[newSt], newSt); 189 } 190 void setMsgEntCh(StmP, StmC)(StmP p, StmC c) 191 { 192 message.formattedWrite("[%s-%s]onEnterChild:>>>%s\n", p.name, p.stateNames[p.currentState], c.name); 193 } 194 void setMsgExitCh(StmP, StmC)(StmP p, StmC c) 195 { 196 message.formattedWrite("[%s-%s]onExitChild:<<<%s\n", p.name, p.stateNames[p.currentState], c.name); 197 } 198 199 addEventHandler( (Event e) { setMsgEv(this, e); } ); 200 addStateChangedHandler( (State oldSt, State newSt) { setMsgSt(this, oldSt, newSt); } ); 201 onEnterChild ~= (TestFlow child) { setMsgEntCh(this, cast(BaseTestFlow)child); }; 202 onExitChild ~= (TestFlow child) { setMsgExitCh(this, cast(BaseTestFlow)child); }; 203 204 _child1.addEventHandler( (Child1Stm.Event e) { setMsgEv(_child1, e); }); 205 _child1.addStateChangedHandler((Child1Stm.State oldSt, Child1Stm.State newSt) { setMsgSt(_child1, oldSt, newSt); }); 206 207 _child2.addEventHandler( (Child2Stm.Event e) {setMsgEv(_child2, e); }); 208 _child2.addStateChangedHandler((Child2Stm.State oldSt, Child2Stm.State newSt) { setMsgSt(_child2, oldSt, newSt); }); 209 210 onError = () 211 { 212 put(_stm, Event.test); 213 // end 214 setNext(null); 215 }; 216 _child1.onExit ~= () { _stm.put(Event.stm1exit); }; 217 _child2.onExit ~= () 218 { 219 if (_child2.currentState == _child2.State.init) 220 { 221 _stm.put(Event.stm2exit); 222 } 223 else 224 { 225 _stm.put(Event.stm2err); 226 } 227 }; 228 229 initialize(); 230 } 231 /// 232 final void initialize() @safe 233 { 234 with (Event) 235 _step = [test, test]; 236 } 237 /// 238 override TestFlow update() @safe 239 { 240 if (emptyEvents) 241 { 242 _stm.put(_step.front); 243 _step.popFront(); 244 } 245 consume(); 246 return super.update(); 247 } 248 /// 249 override string name() const @property 250 { 251 return _stm.matrixName; 252 } 253 /// 254 override string toString() const @safe 255 { 256 return message.data; 257 } 258 } 259 260 /// 261 @safe unittest 262 { 263 // Create a MainStm 264 auto stm = new MainStm; 265 266 // Create a context class of TestFlow 267 auto stFlow = new Flow!TestFlow(stm); 268 269 // Iterate until `stFlow.current` is null. 270 // This process advances the state transition in `stFlow`. 271 while (stFlow.current) 272 { 273 stFlow.update(); 274 } 275 276 // Finally check the result 277 assert(stm.toString() == import("flow-result.txt")); 278 }