async_hsm/
lib.rs

1//! The asynchronous hierarchical state machine (HSM)
2//!
3//! This state machine  is an programmatic approach. It uses features of async-await
4//! functionality, and combines it with usage of the process stack and scoping.
5//!
6//! Instead of storing state as variable, the state is represented by an async function.
7//! The transition from one state to new state is a sequence of async function invocations, either
8//! within a single 'Composite' or lifting back to the parent 'Composite', and so on.
9//!
10//! The following state machine diagram is implemented by the following code, entering the menu and
11//! couting the amount of ping-pongs.
12//!```text
13//! @startuml res/hierarchy
14//! [*] --> App
15//! state App {
16//! [*] --> Menu
17//! state Menu {
18//! }
19//!
20//! state Play {
21//!   [*] --> Ping
22//!
23//!   Ping --> Ping : ping
24//!   Ping --> Pong : ping
25//!   Pong --> Pong : pong
26//!   Pong --> Ping : ping
27//! }
28//! Menu --> Play: play
29//! Play --> Menu: menu
30//! }
31//! App --> [*]: terminate
32//! @enduml
33//!```
34//!
35//!<div class="column"></div>
36//!<img src="https://raw.githubusercontent.com/frehberg/async-hsm/main/res/hierarchy.svg"
37//!alt="[ping pong state diagram https://github.com/frehberg/async-hsm/blob/main/res/hierarchy.svg]" />
38//!</div>
39//!
40//!See here the [ping pong state diagram](https://raw.githubusercontent.com/frehberg/async-hsm/main/res/hierarchy.svg)
41//!
42//!```rust
43//!     use async_std::prelude::*;
44//!     use async_std::stream;
45//!     use async_std::task;
46//!     use async_hsm::{Composite, Transit, Builder, BuilderPair};
47//!     use std::rc::Rc;
48//!     use std::cell::RefCell;
49//!
50//!     type Score = u32;
51//!     type EnterStateScore = u32;
52//!     type AppComposite = Composite<AppData>;
53//!     type PlayComposite = Composite<AppData>;
54//!
55//!
56//!     type AppTransit<'s> = Transit<'s, AppComposite, Score, AppError>;
57//!     type PlayTransit<'s> = Transit<'s, PlayComposite, BuilderPair<AppComposite, EnterStateScore, Score, AppError>, AppError>;
58//!
59//!     type AppBuilder = Builder<AppComposite, EnterStateScore, Score, AppError>;
60//!     type AppBuilderPair = BuilderPair<AppComposite, EnterStateScore, Score, AppError>;
61//!
62//!     #[derive(Debug, Clone, PartialEq)]
63//!     enum AppError { Failure }
64//!
65//!     static TO_MENU: AppBuilder = || |comp, score| Box::pin(menu(comp, score));
66//!     static TO_PLAY: AppBuilder = || |comp, score| Box::pin(play(comp, score));
67//!     static TERMINATE: AppBuilder = || |comp, score| Box::pin(terminate(comp, score));
68//!
69//!     #[derive(Debug, Clone, PartialEq)]
70//!     enum IoEvent { Ping, Pong, Terminate, Menu, Play }
71//!
72//!     #[derive(Debug, Clone)]
73//!     struct AppData {
74//!         event: Rc<RefCell<stream::FromIter<std::vec::IntoIter<IoEvent>>>>,
75//!     }
76//!
77//!     async fn pong<'s>(comp: &'s mut PlayComposite, score: Score) -> Result<PlayTransit<'s>, AppError> {
78//!         let mut score = score + 1;
79//!         let event = comp.data.event.clone();
80//!         while let Some(event) = (*event).borrow_mut().next().await {
81//!             match event {
82//!                 IoEvent::Ping => return Ok(Transit::To(Box::pin(ping(comp, score)))),
83//!                 IoEvent::Terminate => return Ok(Transit::Lift((TERMINATE, score))),
84//!                 _ => score += 1,
85//!             }
86//!         }
87//!         Ok(Transit::Lift((TERMINATE, score)))
88//!     }
89//!
90//!     async fn ping<'s>(comp: &'s mut PlayComposite, score: Score) -> Result<PlayTransit<'s>, AppError> {
91//!         let mut score = score + 1;
92//!         let event = comp.data.event.clone();
93//!         while let Some(event) = (*event).borrow_mut().next().await {
94//!             match event {
95//!                 IoEvent::Pong => return Ok(Transit::To(Box::pin(pong(comp, score)))),
96//!                 IoEvent::Terminate => return Ok(Transit::Lift((TERMINATE, score))),
97//!                 _ => score += 1,
98//!             }
99//!         }
100//!         Ok(Transit::Lift((TERMINATE, score)))
101//!     }
102//!
103//!     async fn terminate<'s>(_comp: &'s mut AppComposite, score: Score) -> Result<AppTransit<'s>, AppError> {
104//!         Ok(Transit::Lift(score))
105//!     }
106//!
107//!     async fn play<'s>(comp: &'s mut AppComposite, score: Score) -> Result<AppTransit<'s>, AppError> {
108//!         let event = comp.data.event.clone();
109//!         let mut play = PlayComposite::new(AppData { event: event });
110//!         let (builder, build_arg): AppBuilderPair = play.init(ping, score).await?;
111//!         builder()(comp, build_arg).await
112//!     }
113//!
114//!     async fn menu<'s>(comp: &'s mut AppComposite, score: Score) -> Result<AppTransit<'s>, AppError> {
115//!         let score = score;
116//!         let event = comp.data.event.clone();
117//!         while let Some(event) = (*event).borrow_mut().next().await {
118//!             match event {
119//!                 IoEvent::Play => return Ok(Transit::To(Box::pin(play(comp, score)))),
120//!                 IoEvent::Terminate => return Ok(Transit::Lift(score)),
121//!                 _ => continue,
122//!             }
123//!         }
124//!         Ok(Transit::Lift(score))
125//!     }
126//!
127//!     #[test]
128//!     fn test_game() {
129//!         let sequence = vec![IoEvent::Play, IoEvent::Ping, IoEvent::Pong,
130//!                             IoEvent::Ping, IoEvent::Pong, IoEvent::Terminate];
131//!         let event = Rc::new(RefCell::new(stream::from_iter(sequence)));
132//!         let start_score = 0;
133//!         let mut app = AppComposite::new(AppData { event: event });
134//!         let result: Result<Score, AppError> = task::block_on(app.init(menu, start_score));
135//!         assert_eq!(Ok(5), result);
136//!     }
137//! ```
138use std::pin::Pin;
139use std::future::{Future};
140
141/// Abstract builder, a function constructing an async state function
142///
143/// This function are returned in case a composite ist terminated, In the outer scope/composite
144/// thus function will generate the successing state.
145///
146/// Instances of this function must be decalred as static functions.
147pub type Builder<Composite, BuildArg, Out, Err> = fn()
148    -> for<'c> fn(&'c mut Composite, data: BuildArg)
149        -> Pin<Box<dyn Future<Output=Result<Transit<'c, Composite,  Out, Err>, Err>> + 'c>>;
150
151/// Pair of a factory funtion of type Builder<..>  and tha e factory argument
152///
153/// First element is the builder-function, the second element is used as input for the builder function.
154pub type BuilderPair<Composite, BuildArg, Out, Err> = (Builder<Composite, BuildArg, Out, Err>, BuildArg);
155
156/// Abstract handle of the successing state
157///
158/// This type must be returned by all async functions forming the HSM.
159pub type Handle<'s, Composite, Out, Err> = Pin<Box<dyn Future<Output=Result<Transit<'s, Composite, Out, Err>, Err>> + 's>>;
160
161/// Strucuture refering to next state within Composite or Lifter that will create instance in parent Composite
162pub enum Transit<'s, Composite,  Out, Err>
163    where Out: Sized + Copy
164{
165    /// Refering to the next state within the Composite
166    To(Handle<'s, Composite, Out,  Err>),
167    /// From current composite lift to outer composite and enter the state formed by "Out"
168    Lift(Out),
169}
170
171/// The structure may be used to share data between states within the same Composite
172pub struct Composite<Data> {
173    pub data: Data,
174}
175
176/// Implementing Composite methods
177impl<Data> Composite<Data> {
178    /// Create a new Composite instance, sharing the data between all states within the Composite
179    pub fn new(data: Data) -> Self {
180        Composite { data: data }
181    }
182
183    /// Composition of states, only one sub-state at a time. The function f is initializing the  first sub state.
184    ///
185    /// #Examples
186    /// ```
187    ///     use async_std::prelude::*;
188    ///     use async_std::stream;
189    ///     use async_std::task;
190    ///     use async_hsm::{Composite, Transit, Builder, BuilderPair};
191    ///     use std::rc::Rc;
192    ///     use std::cell::RefCell;
193    ///
194    ///     type Score = u32;
195    ///     type EnterStateScore = u32;
196    ///     type AppComposite = Composite<AppData>;
197    ///     type AppTransit<'s> = Transit<'s, AppComposite, Score, AppError>;
198    ///     type AppBuilder = Builder<AppComposite, EnterStateScore, Score, AppError>;
199    ///     type AppBuilderPair = BuilderPair<AppComposite, EnterStateScore, Score, AppError>;
200    ///
201    ///     #[derive(Debug, Clone, PartialEq)]
202    ///     enum AppError { Failure }
203    ///
204    ///     #[derive(Debug, Clone, PartialEq)]
205    ///     enum IoEvent { Ping, Pong, Terminate, Menu, Play }
206    ///
207    ///     #[derive(Debug, Clone)]
208    ///     struct AppData { event: Rc<RefCell<stream::FromIter<std::vec::IntoIter<IoEvent>>>> }
209    ///
210    ///     async fn ping<'s>(comp: &'s mut AppComposite, score: Score) -> Result<AppTransit<'s>, AppError> {
211    ///         let mut score = score + 1;
212    ///         let event = comp.data.event.clone();
213    ///         while let Some(event) = (*event).borrow_mut().next().await {
214    ///             match event {
215    ///                 IoEvent::Pong => return Ok(Transit::To(Box::pin(pong(comp, score)))),
216    ///                 _ => score += 1,
217    ///             }
218    ///         }
219    ///         Ok(Transit::Lift(score))
220    ///     }
221    ///
222    ///     async fn pong<'s>(comp: &'s mut AppComposite, score: Score) -> Result<AppTransit<'s>, AppError> {
223    ///         let mut score = score + 1;
224    ///         let event = comp.data.event.clone();
225    ///         while let Some(event) = (*event).borrow_mut().next().await {
226    ///             match event {
227    ///                 IoEvent::Ping => return Ok(Transit::To(Box::pin(ping(comp, score)))),
228    ///                 _ => score += 1,
229    ///             }
230    ///         }
231    ///         Ok(Transit::Lift(score))
232    ///     }
233    ///
234    ///     #[test]
235    ///     fn test_game() {
236    ///         let sequence = vec![ IoEvent::Ping, IoEvent::Pong, IoEvent::Ping, IoEvent::Pong];
237    ///         let event = Rc::new(RefCell::new(stream::from_iter(sequence)));
238    ///         let start_score = 0;
239    ///         let mut app = AppComposite::new(AppData { event: event });
240    ///         let result: Result<Score, AppError> = task::block_on(app.init(ping, start_score));
241    ///         assert_eq!(Ok(5), result);
242    ///     }
243    /// ```
244    pub async fn init<'s, Factory, FactoryArg, Out, Err,  Fut>(&'s mut self, f: Factory, arg: FactoryArg)
245                                                              -> Result<Out, Err>
246
247        where Factory: FnOnce(&'s mut Self, FactoryArg) -> Fut,
248              Fut: Future<Output=Result<Transit<'s, Self, Out, Err>, Err>>,
249            Out: Sized + Copy
250    {
251        let mut trans = f(self, arg).await?;
252
253        loop {
254            trans = match trans {
255                Transit::To(h) => h.await?,
256                Transit::Lift(lift) => return Ok(lift)
257            }
258        }
259    }
260}