1 /** 2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 */ 6 module my.fsm; 7 8 import std.format : format; 9 10 /** A state machine derived from the types it is based on. 11 * 12 * Each state have its unique data that it works on. 13 * 14 * The state transitions are calculated by `next` and the actions are performed 15 * by `act`. 16 */ 17 struct Fsm(StateTT...) { 18 static import sumtype; 19 20 alias StateT = sumtype.SumType!StateTT; 21 22 /// The states and state specific data. 23 StateT state; 24 25 /// Log messages of the last state transition (next). 26 /// Only updated in debug build. 27 string logNext; 28 29 /// Helper function to convert the return type to `StateT`. 30 static StateT opCall(T)(auto ref T a) { 31 return StateT(a); 32 } 33 } 34 35 /// Transition to the next state. 36 template next(handlers...) { 37 void next(Self)(auto ref Self self) if (is(Self : Fsm!StateT, StateT...)) { 38 static import sumtype; 39 40 auto nextSt = sumtype.match!handlers(self.state); 41 debug self.logNext = format!"%s -> %s"(self.state, nextSt); 42 43 self.state = nextSt; 44 } 45 } 46 47 /// Act on the current state. Use `(ref S)` to modify the states data. 48 template act(handlers...) { 49 void act(Self)(auto ref Self self) if (is(Self : Fsm!StateT, StateT...)) { 50 static import sumtype; 51 52 sumtype.match!handlers(self.state); 53 } 54 } 55 56 @("shall transition the fsm from A to B|C") 57 unittest { 58 struct Global { 59 int x; 60 } 61 62 struct A { 63 } 64 65 struct B { 66 int x; 67 } 68 69 struct C { 70 bool x; 71 } 72 73 Global global; 74 Fsm!(A, B, C) fsm; 75 bool running = true; 76 77 while (running) { 78 fsm.next!((A a) { global.x++; return fsm(B(0)); }, (B a) { 79 running = false; 80 if (a.x > 3) 81 return fsm(C(true)); 82 return fsm(a); 83 }, (C a) { running = false; return fsm(a); }); 84 85 fsm.act!((A a) {}, (ref B a) { a.x++; }, (C a) {}); 86 } 87 88 assert(global.x == 1); 89 } 90 91 /** Hold a mapping between a Type and data. 92 * 93 * The get function is used to get the corresponding data. 94 * 95 * This is useful when e.g. combined with a state machine to retrieve the state 96 * local data if a state is represented as a type. 97 * 98 * Params: 99 * RawDataT = type holding the data, retrieved via opIndex 100 * Ts = the types mapping to RawDataT by their position 101 */ 102 struct TypeDataMap(RawDataT, Ts...) 103 if (is(RawDataT : DataT!Args, alias DataT, Args...)) { 104 alias SrcT = Ts; 105 RawDataT data; 106 107 this(RawDataT a) { 108 this.data = a; 109 } 110 111 void opAssign(RawDataT a) { 112 this.data = a; 113 } 114 115 static if (is(RawDataT : DataT!Args, alias DataT, Args...)) 116 static assert(Ts.length == Args.length, 117 "Mismatch between Tuple and TypeMap template arguments"); 118 } 119 120 auto ref get(T, TMap)(auto ref TMap tmap) 121 if (is(TMap : TypeDataMap!(W, SrcT), W, SrcT...)) { 122 template Index(size_t Idx, T, Ts...) { 123 static if (Ts.length == 0) { 124 static assert(0, "Type " ~ T.stringof ~ " not found in the TypeMap"); 125 } else static if (is(T == Ts[0])) { 126 enum Index = Idx; 127 } else { 128 enum Index = Index!(Idx + 1, T, Ts[1 .. $]); 129 } 130 } 131 132 return tmap.data[Index!(0, T, TMap.SrcT)]; 133 } 134 135 @("shall retrieve the data for the type") 136 unittest { 137 import std.typecons : Tuple; 138 139 TypeDataMap!(Tuple!(int, bool), bool, int) a; 140 static assert(is(typeof(a.get!bool) == int), "wrong type"); 141 a.data[1] = true; 142 assert(a.get!int == true); 143 }