# The asynchronous hierarchical state machine (HSM) ## Concept "Let the compiler do the optimization" "Each state is an async function" "State-Transitions are sequences of async functions" "States of the same composite share state using an instance of 'Composite'" "Composites may be nested, forming an Hierarchical State Machine (HSM)" "Within a composite its states may be cyclic." "Async-await functions are managed within the context of the composite." "Exiting an async function will drop all its resources, incl other async-awaits." "Transiting to a new state is performed by a sequence of async-await functions" "The total code size is a few lines of code" "Parallel states are formed by multiple composites, bound by an async-await composition." ## Features * Hierarchical State Compositions * Asynchronous State Handler * Performant * Parallel State (example to be done) * Synchronization points between parallel composites (intended as next feature) ## License This work is dual-licensed under Apache 2.0 OR MIT. You can choose between one of them if you use this work. SPDX-License-Identifier: Apache-2.0 OR MIT ## Integration Put this in your Cargo.toml: ```toml ## Cargo.toml file [dependencies] async-hsm = "^0.2" ``` ## Example The following state diagram (PlantUml syntax) can be encoded by the following Rust-code ```puml @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 ``` ![](res/hierarchy.svg) See here the [ping pong state diagram](https://raw.githubusercontent.com/frehberg/async-hsm/main/res/hierarchy.svg) ```edition2018 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; type PlayComposite = Composite; type AppTransit<'s> = Transit<'s, AppComposite, Score, AppError>; type PlayTransit<'s> = Transit<'s, PlayComposite, BuilderPair, AppError>; type AppBuilder = Builder; type AppBuilderPair = BuilderPair; #[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>>>, } async fn pong<'s>(comp: &'s mut PlayComposite, score: Score) -> Result, 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, 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, AppError> { Ok(Transit::Lift(score)) } async fn play<'s>(comp: &'s mut AppComposite, score: Score) -> Result, 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, 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 = task::block_on(app.init(menu, start_score)); assert_eq!(Ok(5), result); } ```