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}