edfsm/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3
4pub use edfsm_macros::impl_fsm;
5
6/// A type of input - commands or events.
7#[derive(Debug, Clone)]
8pub enum Input<C, E> {
9    Command(C),
10    Event(E),
11}
12
13/// Describes a type of state change that `on_event` can perform.
14#[derive(Debug, Clone, Copy)]
15pub enum Change {
16    Transitioned,
17    Updated,
18}
19
20/// Runs the state machine for a command or event, optionally performing effects,
21/// events, or receive events. These types of FSM can be broadly described as "Mealy" and "Moore" machines
22/// respectively. Along the way, effects can be performed given the receipt of a command or the application
23/// of an event. State can be reconsituted by replaying events.
24///
25/// Note that effects are represented by a separate structure so that they can be consolidated,
26/// and also to help structure the code. Further, it is possible to have multiple implementations
27/// of effects e.g. different ones when testing.
28///
29/// Effects are also synchronous. If an effect handler must communicate, say, with a task in a
30/// non-blocking fashion, then the state machine should represent this intermediate state. For
31/// example, a channel could be used to communicate with such a task with `try_send` being used
32/// and then causing a state transition in relation to that result. While this approach adds
33/// steps to a state machine, it does allow them to remain responsive to receiving more
34/// commands.
35pub trait Fsm {
36    /// The state managed by the FSM
37    type S;
38    /// The command(s) that are able to be processed by the FSM
39    type C;
40    /// The event emitted having performed a command
41    type E;
42    /// The side effect handler
43    type SE;
44
45    /// Given a state and command, optionally emit an event if it applies. Can perform side
46    /// effects. This function is generally only called from the
47    /// `step` function.
48    fn for_command(s: &Self::S, c: Self::C, se: &mut Self::SE) -> Option<Self::E>;
49
50    /// Given a state and event, modify state, which could indicate transition to
51    /// the next state. No side effects are to be performed. Can be used to replay
52    /// events to attain a new state i.e. the major function of event sourcing.
53    /// Returns some enumeration of the `Change` type if there is a state transition.
54    fn on_event(s: &mut Self::S, e: &Self::E) -> Option<Change>;
55
56    /// Given a state and event having been applied then handle any potential change
57    /// and optionally perform side effects.
58    /// This function is generally only called from the `step` function.
59    fn on_change(s: &Self::S, e: &Self::E, se: &mut Self::SE, change: Change);
60
61    /// This is the common entry point to the event driven FSM.
62    /// Runs the state machine for a command input, optionally performing effects,
63    /// possibly producing an event and possibly transitioning to a new state. Also
64    /// applies any "Entry/" processing when arriving at a new state, and a change
65    /// handler if there is a state change.
66    fn step(s: &mut Self::S, i: Input<Self::C, Self::E>, se: &mut Self::SE) -> Option<Self::E> {
67        let e = match i {
68            Input::Command(c) => Self::for_command(s, c, se),
69            Input::Event(e) => Some(e),
70        };
71        if let Some(e) = e {
72            let r = Self::on_event(s, &e);
73            if let Some(c) = r {
74                Self::on_change(s, &e, se, c);
75                Some(e)
76            } else {
77                None
78            }
79        } else {
80            None
81        }
82    }
83}
84
85// The following traits can be used with `Fsm` but are optional.
86
87/// The ability to perform initial effects given a starting state.
88///
89/// This trait can be implemented for `Fsm::SE`.  A state machine driver
90/// that requires this will call `init` with the initial state, possibly
91/// recovered from storage or by rehydration, before accepting any
92/// commands or live events.
93pub trait Init<S> {
94    /// Perform any initial effects given an initial state.
95    fn init(&mut self, state: &S);
96}
97
98/// A trait to mark the terminating event of a state machine.
99///
100/// This can be implemented by `Fsm::E`.
101/// If `event.terminating()` then no more events are expected
102/// for the receiving state machine and the state machine driver
103/// might drop it or take other lifecycle steps.
104pub trait Terminating {
105    /// This event is the final event.
106    fn terminating(&self) -> bool;
107}
108
109/// The ability to extract output messages from a state machine.
110///
111/// This trait can be implement for `Fsm::SE`. A state machine driver
112/// that requires this will call `drain_all` after each `step` and
113/// forward the returned output messages.
114pub trait Drain {
115    /// Messages generated during a state machine `step`.
116    type Item;
117
118    /// remove and return accumulated messages.
119    fn drain_all(&mut self) -> impl Iterator<Item = Self::Item> + Send;
120}
121
122#[cfg(test)]
123mod test {
124    use super::{Change, Fsm, Input};
125
126    #[test]
127    fn test_step() {
128        // Declare our state, commands and events
129
130        struct Idle;
131        struct Running;
132        enum State {
133            Idle(Idle),
134            Running(Running),
135        }
136
137        struct Start;
138        struct Stop;
139        enum Command {
140            Start(Start),
141            Stop(Stop),
142        }
143
144        struct Started;
145        struct Stopped;
146        enum Event {
147            Started(Started),
148            Stopped(Stopped),
149        }
150
151        // Declare an object to handle effects as we step through the FSM
152
153        struct EffectHandlers {
154            started: u32,
155            stopped: u32,
156            transitioned_stopped_to_started: u32,
157        }
158
159        impl EffectHandlers {
160            pub fn start_something(&mut self) {
161                self.started += 1;
162            }
163
164            pub fn stop_something(&mut self) {
165                self.stopped += 1;
166            }
167
168            pub fn enter_running(&mut self) {
169                self.transitioned_stopped_to_started += 1;
170            }
171        }
172
173        // Declare the FSM itself
174
175        struct MyFsm;
176
177        impl Fsm for MyFsm {
178            type S = State;
179            type C = Command;
180            type E = Event;
181            type SE = EffectHandlers;
182
183            fn for_command(s: &State, c: Command, se: &mut EffectHandlers) -> Option<Event> {
184                match (s, c) {
185                    (State::Running(s), Command::Stop(c)) => {
186                        Self::for_running_stop(s, c, se).map(Event::Stopped)
187                    }
188                    (State::Idle(s), Command::Start(c)) => {
189                        Self::for_idle_start(s, c, se).map(Event::Started)
190                    }
191                    _ => None,
192                }
193            }
194
195            fn on_event(mut s: &mut State, e: &Event) -> Option<Change> {
196                let r = match (&mut s, e) {
197                    (State::Running(s), Event::Stopped(e)) => Self::on_running_stopped(s, e)
198                        .map(|new_s| (Change::Transitioned, Some(State::Idle(new_s)))),
199                    (State::Idle(s), Event::Started(e)) => Self::on_idle_started(s, e)
200                        .map(|new_s| (Change::Transitioned, Some(State::Running(new_s)))),
201                    _ => None,
202                };
203                if let Some((c, new_s)) = r {
204                    if let Some(new_s) = new_s {
205                        *s = new_s;
206                    }
207                    Some(c)
208                } else {
209                    None
210                }
211            }
212
213            fn on_change(s: &State, e: &Event, se: &mut EffectHandlers, change: Change) {
214                if let Change::Transitioned = change {
215                    // Let's implement this optional function to show how entry/exit
216                    // processing can be achieved, and also confirm that our FSM is
217                    // calling it.
218                    if let State::Running(s) = s {
219                        Self::on_entry_running(s, e, se)
220                    }
221                }
222                match (s, e) {
223                    (State::Idle(s), Event::Stopped(e)) => Self::on_idle_stopped(s, e, se),
224                    (State::Running(s), Event::Started(e)) => Self::on_running_started(s, e, se),
225                    _ => (),
226                }
227            }
228        }
229
230        impl MyFsm {
231            fn on_entry_running(_to_s: &Running, _e: &Event, se: &mut EffectHandlers) {
232                se.enter_running()
233            }
234
235            fn for_running_stop(
236                _s: &Running,
237                _c: Stop,
238                _se: &mut EffectHandlers,
239            ) -> Option<Stopped> {
240                Some(Stopped)
241            }
242
243            fn on_running_started(_s: &Running, _e: &Started, se: &mut EffectHandlers) {
244                se.start_something();
245            }
246
247            fn on_running_stopped(_s: &Running, _e: &Stopped) -> Option<Idle> {
248                Some(Idle)
249            }
250
251            fn for_idle_start(_s: &Idle, _c: Start, _se: &mut EffectHandlers) -> Option<Started> {
252                Some(Started)
253            }
254
255            fn on_idle_started(_s: &Idle, _e: &Started) -> Option<Running> {
256                Some(Running)
257            }
258
259            fn on_idle_stopped(_s: &Idle, _e: &Stopped, se: &mut EffectHandlers) {
260                se.stop_something();
261            }
262        }
263
264        // Initialize our effect handlers
265
266        let mut se = EffectHandlers {
267            started: 0,
268            stopped: 0,
269            transitioned_stopped_to_started: 0,
270        };
271
272        // First, test the FSM by stepping through various states given commands
273
274        let e = MyFsm::step(
275            &mut State::Idle(Idle),
276            Input::Command(Command::Start(Start)),
277            &mut se,
278        );
279        assert!(matches!(e, Some(Event::Started(Started))));
280        assert_eq!(se.started, 1);
281        assert_eq!(se.stopped, 0);
282        assert_eq!(se.transitioned_stopped_to_started, 1);
283
284        let e = MyFsm::step(
285            &mut State::Running(Running),
286            Input::Command(Command::Start(Start)),
287            &mut se,
288        );
289        assert!(e.is_none());
290        assert_eq!(se.started, 1);
291        assert_eq!(se.stopped, 0);
292        assert_eq!(se.transitioned_stopped_to_started, 1);
293
294        let e = MyFsm::step(
295            &mut State::Running(Running),
296            Input::Command(Command::Stop(Stop)),
297            &mut se,
298        );
299        assert!(matches!(e, Some(Event::Stopped(Stopped))));
300        assert_eq!(se.started, 1);
301        assert_eq!(se.stopped, 1);
302        assert_eq!(se.transitioned_stopped_to_started, 1);
303
304        let e = MyFsm::step(
305            &mut State::Idle(Idle),
306            Input::Command(Command::Stop(Stop)),
307            &mut se,
308        );
309        assert!(e.is_none());
310        assert_eq!(se.started, 1);
311        assert_eq!(se.stopped, 1);
312        assert_eq!(se.transitioned_stopped_to_started, 1);
313
314        // Reset our effect handlers
315
316        let mut se = EffectHandlers {
317            started: 0,
318            stopped: 0,
319            transitioned_stopped_to_started: 0,
320        };
321
322        // Now, test the FSM by stepping through various states given events
323
324        let e = MyFsm::step(
325            &mut State::Idle(Idle),
326            Input::Event(Event::Started(Started)),
327            &mut se,
328        );
329        assert!(matches!(e, Some(Event::Started(Started))));
330        assert_eq!(se.started, 1);
331        assert_eq!(se.stopped, 0);
332        assert_eq!(se.transitioned_stopped_to_started, 1);
333
334        let e = MyFsm::step(
335            &mut State::Running(Running),
336            Input::Event(Event::Started(Started)),
337            &mut se,
338        );
339        assert!(e.is_none());
340        assert_eq!(se.started, 1);
341        assert_eq!(se.stopped, 0);
342        assert_eq!(se.transitioned_stopped_to_started, 1);
343
344        let e = MyFsm::step(
345            &mut State::Running(Running),
346            Input::Event(Event::Stopped(Stopped)),
347            &mut se,
348        );
349        assert!(matches!(e, Some(Event::Stopped(Stopped))));
350        assert_eq!(se.started, 1);
351        assert_eq!(se.stopped, 1);
352        assert_eq!(se.transitioned_stopped_to_started, 1);
353
354        let e = MyFsm::step(
355            &mut State::Idle(Idle),
356            Input::Event(Event::Stopped(Stopped)),
357            &mut se,
358        );
359        assert!(e.is_none());
360        assert_eq!(se.started, 1);
361        assert_eq!(se.stopped, 1);
362        assert_eq!(se.transitioned_stopped_to_started, 1);
363    }
364}