Skip to main content

edfsm_machine/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#[cfg(feature = "alloc")]
4extern crate alloc;
5#[cfg(feature = "std")]
6extern crate std;
7
8pub mod adapter;
9pub mod error;
10
11#[cfg(feature = "alloc")]
12pub mod output;
13
14#[cfg(feature = "tokio")]
15use tokio::sync::mpsc::{channel, Receiver, Sender};
16
17use crate::{
18    adapter::{Adapter, Feed, Placeholder},
19    error::Result,
20};
21use core::future::Future;
22use edfsm::{Drain, Fsm, Init, Input, Terminating};
23
24/// The event type of an Fsm
25pub type Event<M> = <M as Fsm>::E;
26
27/// The command type of an Fsm
28pub type Command<M> = <M as Fsm>::C;
29
30/// The input type of an Fsm
31pub type In<M> = Input<<M as Fsm>::C, <M as Fsm>::E>;
32
33/// The output message type of an Fsm for the purpose of this module.
34pub type Out<M> = <<M as Fsm>::SE as Drain>::Item;
35
36/// The effector/effects type of an Fsm
37pub type Effects<M> = <M as Fsm>::SE;
38
39/// The state type of an Fsm
40pub type State<M> = <M as Fsm>::S;
41
42/// A `Machine` is a state machine (implementing `Fsm`) that will run in a rust `task`.
43///
44/// Each `Machine` has an input channel, and adapters for output and event log.
45/// The type of the input messages, events and output messages are part of
46/// the state machine specification, ie the `Fsm` implementation.
47/// Conversely, the wiring or inputs and outputs is independent of the underlying state machine
48/// and involves channels and adapters.
49///
50/// A `Machine` also has a data structure used to perform side effects, including generating output messages.
51/// The type of this is also part of the state machine specification (the `SE` associated type).  
52/// Note: side effects must be synchronous. If they may block they should be bracketed with
53/// tokio's `block_in_place` or equivalent.
54///
55/// A machine is created by functions `machine` or `machine_with_effects`.
56/// It is wired to other machines or channels by functions `input`, `with_output`, `merge_output` and
57/// `with_event_log`.
58///
59/// The machine is made runnable by function `task`.  This is a future intended to be spawned onto
60/// the tokio (or other) runtime.
61///
62/// Once running, a `Machine`
63/// - initialises state, which may involve replaying messages from the event log
64/// - performs initial effects
65/// - enters the main loop, which is dirven by messages received on the input channel
66/// - each message may cause the state to evolve and/or generate side effects
67/// - an event is logged if the state changed
68/// - any output messages are dispatched
69///
70pub trait Machine<M>
71where
72    M: Fsm,
73    Effects<M>: Drain,
74{
75    /// Return a new `Sender` for the input channel.
76    /// Any number can be created , enabling fan-in of messages.
77    ///
78    /// The sender accepts the Fsm `Input` values, representing either
79    /// a command or an event.   It implements `Adapter` so the type can be adjusted.
80    /// For example, to accept events only use:
81    ///
82    /// `machine.input().adapt_map(Input::Event)`
83    ///
84    fn input(&self) -> Sender<In<M>>;
85
86    /// Connect a channel `Sender` or an adapter for output messages.
87    ///
88    /// This method replaces any existing adapter for output messages.
89    /// Note that if the channel or adapter stalls this will stall the state machine.
90    fn with_output(self, output: impl Adapter<Item = Out<M>> + 'static) -> impl Machine<M>;
91
92    /// Connect an additional channel or adapter for output messages.
93    ///
94    /// Any number of channels or adapters can be connected, enabling fan-out of messages.
95    /// Each will receive all output messages, however if an adapter stalls this will stall the state machine.
96    fn merge_output(self, output: impl Adapter<Item = Out<M>> + 'static) -> impl Machine<M>
97    where
98        Out<M>: Clone + Send;
99
100    /// Connect an event log that provides intialisation from historical events and records live events.
101    ///
102    /// Each event received by the machine and each event produced by a command will be notified.
103    /// This method replaces any existing event log.
104    fn with_event_log(
105        self,
106        log: impl Adapter<Item = Event<M>> + Feed<Item = Event<M>> + 'static,
107    ) -> impl Machine<M>;
108
109    /// Connect an additional channel or adapter for events.
110    ///
111    /// Each event received by the machine and each event produced by a command will be notified.
112    /// Any number of channels or adapters can be connected, enabling fan-out of events.
113    /// Each will receive all output messages, however if an adapter stalls this will stall the state machine.
114    fn merge_event_log(self, output: impl Adapter<Item = Event<M>> + 'static) -> impl Machine<M>;
115
116    /// Convert this machine into a future that will run as a task
117    fn task(self) -> impl Future<Output = Result<()>> + Send + 'static
118    where
119        Self: Sized,
120        Out<M>: Send,
121        Event<M>: Send + Terminating,
122        Effects<M>: Init<State<M>> + Send,
123        Command<M>: Send,
124        State<M>: Default + Send;
125}
126
127/// A concrete `Machine`
128struct Template<M, N, O, P>
129where
130    M: Fsm,
131{
132    sender: Option<Sender<In<M>>>,
133    receiver: Receiver<In<M>>,
134    effects: Effects<M>,
135    log: N,
136    output: O,
137    events: P,
138}
139
140impl<M, N, O, P> Machine<M> for Template<M, N, O, P>
141where
142    M: Fsm + 'static,
143    Effects<M>: Drain,
144    N: Adapter<Item = Event<M>> + Feed<Item = Event<M>> + 'static,
145    O: Adapter<Item = Out<M>> + 'static,
146    P: Adapter<Item = Event<M>> + 'static,
147    Event<M>: Clone + Send,
148{
149    fn input(&self) -> Sender<In<M>> {
150        self.sender.as_ref().unwrap().clone()
151    }
152
153    fn with_output(self, output: impl Adapter<Item = Out<M>> + 'static) -> impl Machine<M> {
154        Template {
155            sender: self.sender,
156            receiver: self.receiver,
157            effects: self.effects,
158            log: self.log,
159            output,
160            events: self.events,
161        }
162    }
163
164    fn merge_output(self, output: impl Adapter<Item = Out<M>> + 'static) -> impl Machine<M>
165    where
166        Out<M>: Clone + Send,
167    {
168        Template {
169            sender: self.sender,
170            receiver: self.receiver,
171            effects: self.effects,
172            log: self.log,
173            output: self.output.merge(output),
174            events: self.events,
175        }
176    }
177
178    fn with_event_log(
179        self,
180        log: impl Adapter<Item = Event<M>> + Feed<Item = Event<M>> + 'static,
181    ) -> impl Machine<M> {
182        Template {
183            sender: self.sender,
184            receiver: self.receiver,
185            effects: self.effects,
186            log,
187            output: self.output,
188            events: self.events,
189        }
190    }
191
192    fn merge_event_log(self, events: impl Adapter<Item = Event<M>> + 'static) -> impl Machine<M> {
193        Template {
194            sender: self.sender,
195            receiver: self.receiver,
196            effects: self.effects,
197            log: self.log,
198            output: self.output,
199            events: self.events.merge(events),
200        }
201    }
202
203    async fn task(mut self) -> Result<()>
204    where
205        Effects<M>: Init<State<M>>,
206        State<M>: Default,
207        Event<M>: Send + Terminating,
208        State<M>: Send,
209    {
210        // close the local sender side of the input channel
211        // this ensures the task will exit when all other senders are closed
212        self.sender = None;
213
214        // Construct the initial state and rehydrate it from the log.
215        let mut state: State<M> = Default::default();
216        let mut hydra = Hydrator::<M> { state: &mut state };
217        self.log.feed(&mut hydra).await?;
218
219        // Initialise the effector with the rehydrated, state.
220        self.effects.init(&state);
221
222        // Flush output messages generated in initialisation
223        for item in self.effects.drain_all() {
224            self.output.notify(item).await
225        }
226
227        // Read events and commands
228        while let Some(input) = self.receiver.recv().await {
229            // Indicates a terminating event is seen
230            let mut terminating = false;
231
232            // Run Fsm and log any event
233            if let Some(e) = M::step(&mut state, input, &mut self.effects) {
234                terminating = e.terminating();
235                self.log.clone_notify(&e).await;
236                self.events.notify(e).await;
237            }
238
239            // Flush output messages generated during the `step`, if any.
240            for item in self.effects.drain_all() {
241                self.output.notify(item).await
242            }
243
244            if terminating {
245                break;
246            }
247        }
248        Ok(())
249    }
250}
251
252/// Default machine input backlog limit
253pub const DEFAULT_BUFFER: usize = 10;
254
255/// Create new machine for an `Fsm` of type `M`
256pub fn machine<M>() -> impl Machine<M>
257where
258    M: Fsm + 'static,
259    Effects<M>: Drain + Default,
260    Out<M>: Send + Clone,
261    Event<M>: Send + Sync + Clone,
262{
263    machine_with_effects(Default::default(), DEFAULT_BUFFER)
264}
265
266/// Create a new machine for an `Fsm` of type `M` with explicit effects and backlog
267pub fn machine_with_effects<M>(effects: Effects<M>, buffer: usize) -> impl Machine<M>
268where
269    M: Fsm + 'static,
270    Effects<M>: Drain,
271    Out<M>: Send + Clone,
272    Event<M>: Send + Sync + Clone,
273{
274    let (sender, receiver) = channel(buffer);
275    Template {
276        sender: Some(sender),
277        receiver,
278        effects,
279        log: Placeholder::default(),
280        output: Placeholder::default(),
281        events: Placeholder::default(),
282    }
283}
284
285/// A `Hydrator` is an event `Adapter` that accepts
286/// a stream of initialisation events for an `Fsm`.
287///
288/// It will apply these to the state bringing it up
289/// to date without causing side effects.
290struct Hydrator<'a, M>
291where
292    M: Fsm,
293{
294    state: &'a mut State<M>,
295}
296
297impl<M> Adapter for Hydrator<'_, M>
298where
299    M: Fsm,
300    Event<M>: Send,
301    State<M>: Send,
302{
303    type Item = Event<M>;
304
305    async fn notify(&mut self, a: Self::Item)
306    where
307        Self::Item: Send + 'static,
308    {
309        M::on_event(self.state, &a);
310    }
311}