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}