[][src]Crate async_hsm

The asynchronous hierarchical state machine (HSM)

This state machine is an programmatic approach. It uses features of async-await functionality, and combines it with usage of the process stack and scoping.

Instead of storing state as variable, the state is represented by an async function. The transition from one state to new state is a sequence of async function invocations, either within a single 'Composite' or lifting back to the parent 'Composite', and so on.

The following state machine diagram is implemented by the following code, entering the menu and couting the amount of ping-pongs.

 @startuml res/hierarchy
 [*] --> App
 state App {
 [*] --> Menu
 state Menu {
 }

 state Play {
   [*] --> Ping

   Ping --> Ping : ping
   Ping --> Pong : ping
   Pong --> Pong : pong
   Pong --> Ping : ping
 }
 Menu --> Play: play
 Play --> Menu: menu
 }
 App --> [*]: terminate
 @enduml
[ping pong state diagram https://github.com/frehberg/async-hsm/blob/main/res/hierarchy.svg]

See here the ping pong state diagram

     use async_std::prelude::*;
     use async_std::stream;
     use async_std::task;
     use async_hsm::{Composite, Transit, Builder, BuilderPair};
     use std::rc::Rc;
     use std::cell::RefCell;

     type Score = u32;
     type EnterStateScore = u32;
     type AppComposite = Composite<AppData>;
     type PlayComposite = Composite<AppData>;


     type AppTransit<'s> = Transit<'s, AppComposite, Score, AppError>;
     type PlayTransit<'s> = Transit<'s, PlayComposite, BuilderPair<AppComposite, EnterStateScore, Score, AppError>, AppError>;

     type AppBuilder = Builder<AppComposite, EnterStateScore, Score, AppError>;
     type AppBuilderPair = BuilderPair<AppComposite, EnterStateScore, Score, AppError>;

     #[derive(Debug, Clone, PartialEq)]
     enum AppError { Failure }

     static TO_MENU: AppBuilder = || |comp, score| Box::pin(menu(comp, score));
     static TO_PLAY: AppBuilder = || |comp, score| Box::pin(play(comp, score));
     static TERMINATE: AppBuilder = || |comp, score| Box::pin(terminate(comp, score));

     #[derive(Debug, Clone, PartialEq)]
     enum IoEvent { Ping, Pong, Terminate, Menu, Play }

     #[derive(Debug, Clone)]
     struct AppData {
         event: Rc<RefCell<stream::FromIter<std::vec::IntoIter<IoEvent>>>>,
     }

     async fn pong<'s>(comp: &'s mut PlayComposite, score: Score) -> Result<PlayTransit<'s>, AppError> {
         let mut score = score + 1;
         let event = comp.data.event.clone();
         while let Some(event) = (*event).borrow_mut().next().await {
             match event {
                 IoEvent::Ping => return Ok(Transit::To(Box::pin(ping(comp, score)))),
                 IoEvent::Terminate => return Ok(Transit::Lift((TERMINATE, score))),
                 _ => score += 1,
             }
         }
         Ok(Transit::Lift((TERMINATE, score)))
     }

     async fn ping<'s>(comp: &'s mut PlayComposite, score: Score) -> Result<PlayTransit<'s>, AppError> {
         let mut score = score + 1;
         let event = comp.data.event.clone();
         while let Some(event) = (*event).borrow_mut().next().await {
             match event {
                 IoEvent::Pong => return Ok(Transit::To(Box::pin(pong(comp, score)))),
                 IoEvent::Terminate => return Ok(Transit::Lift((TERMINATE, score))),
                 _ => score += 1,
             }
         }
         Ok(Transit::Lift((TERMINATE, score)))
     }

     async fn terminate<'s>(_comp: &'s mut AppComposite, score: Score) -> Result<AppTransit<'s>, AppError> {
         Ok(Transit::Lift(score))
     }

     async fn play<'s>(comp: &'s mut AppComposite, score: Score) -> Result<AppTransit<'s>, AppError> {
         let event = comp.data.event.clone();
         let mut play = PlayComposite::new(AppData { event: event });
         let (builder, build_arg): AppBuilderPair = play.init(ping, score).await?;
         builder()(comp, build_arg).await
     }

     async fn menu<'s>(comp: &'s mut AppComposite, score: Score) -> Result<AppTransit<'s>, AppError> {
         let score = score;
         let event = comp.data.event.clone();
         while let Some(event) = (*event).borrow_mut().next().await {
             match event {
                 IoEvent::Play => return Ok(Transit::To(Box::pin(play(comp, score)))),
                 IoEvent::Terminate => return Ok(Transit::Lift(score)),
                 _ => continue,
             }
         }
         Ok(Transit::Lift(score))
     }

     #[test]
     fn test_game() {
         let sequence = vec![IoEvent::Play, IoEvent::Ping, IoEvent::Pong,
                             IoEvent::Ping, IoEvent::Pong, IoEvent::Terminate];
         let event = Rc::new(RefCell::new(stream::from_iter(sequence)));
         let start_score = 0;
         let mut app = AppComposite::new(AppData { event: event });
         let result: Result<Score, AppError> = task::block_on(app.init(menu, start_score));
         assert_eq!(Ok(5), result);
     }

Structs

Composite

The structure may be used to share data between states within the same Composite

Enums

Transit

Strucuture refering to next state within Composite or Lifter that will create instance in parent Composite

Type Definitions

Builder

Abstract builder, a function constructing an async state function

BuilderPair

Pair of a factory funtion of type Builder<..> and tha e factory argument

Handle

Abstract handle of the successing state