pure_hfsm/
builder.rs

1//! Builder describing multiple interacting state machines
2//!
3//! You must define a [`StateMachines`] and use [`StateMachines::build`] to get a
4//! runnable [`crate::StateMachines`].
5//!
6//! In order for [`crate::Transition`] impls to be able to return
7//! [`crate::Target`], that cannot be constructed due to the arguments of
8//! [`crate::Target::Goto`] and [`crate::Target::Enter`] being non-constructible,
9//! you need to first define a [`IntoTransition`] impl. [`IntoTransition::into_with`]
10//! takes a [`NameMapping`] argument that let you construct [`crate::Target`]
11//! that you will be able to use in the impl of [`crate::Transition`].
12//!
13//! Once your obtain a [`crate::StateMachines`] through
14//! the [`StateMachines::build`] method, you can use it with
15//! [`label::NestedMachine`](crate::label::NestedMachine) to manage a state
16//! machine.
17use ahash::AHashMap;
18use serde::Deserialize;
19use smallvec::SmallVec;
20
21use crate::{SHandle, SHandleInner, SmHandle, SmHandleInner};
22
23/// Convert `Self` into something that implements [`crate::Transition`]
24///
25/// You will need [`NameMapping`] to be able to instantiate the [`crate::Target`]
26/// necessary for transitions to work. The [`NameMapping`] contains the
27/// [`SHandle`] and [`SmHandle`] necessary for implementing
28/// [`crate::Transition`]. The names correspond to the ones you provided in
29/// [`State`] and [`StateMachine`] `name` fields.
30pub trait IntoTransition<T> {
31    /// Convert `Self` into `T`
32    fn into_with(self, mapping: &NameMapping) -> T;
33}
34
35/// Obtain a [`Target`](crate::Target) based on serialized state and machine names
36///
37/// Use the [`NameMapping::target`], [`NameMapping::goto`] and [`NameMapping::enter`]
38/// methods to convert a [`Target`] with a [`String`] state/machine name into a
39/// [`Target`](crate::Target) with [`SHandle`] and [`SmHandle`] state names,
40/// usable with the [`crate::StateMachines`] compact and efficient struct.
41///
42/// The names correspond to the ones you provided in [`State`] and [`StateMachine`]
43/// `name` fields.
44pub struct NameMapping {
45    state_names: AHashMap<String, SHandleInner>,
46    machine_names: AHashMap<String, SmHandleInner>,
47}
48impl NameMapping {
49    fn new() -> Self {
50        NameMapping {
51            state_names: AHashMap::new(),
52            machine_names: AHashMap::new(),
53        }
54    }
55    /// Get [`crate::Target`] corresponding to this [`Target`]
56    pub fn target(&self, target: &Target) -> Option<crate::Target> {
57        match target {
58            Target::Goto(name) => self.goto(name),
59            Target::Enter(name) => self.enter(name),
60            Target::End => Some(crate::Target::Complete),
61        }
62    }
63    /// Get a [`crate::Target::Goto`] pointing to `State` named `name`
64    pub fn goto(&self, name: &str) -> Option<crate::Target> {
65        let target = self.state_names.get(name)?;
66        Some(crate::Target::Goto(SHandle(*target)))
67    }
68    /// Get a [`crate::Target::Enter`] pointing to `StateMachine` named `name`
69    pub fn enter(&self, name: &str) -> Option<crate::Target> {
70        let target = self.machine_names.get(name)?;
71        Some(crate::Target::Enter(SmHandle(*target)))
72    }
73}
74
75/// Convenience enum for serialized state machines
76///
77/// Pass this enum to the [`NameMapping::target`] method to get the corresponding
78/// [`crate::Target`] needed to implement the [`crate::Transition`] trait.
79#[derive(Deserialize)]
80pub enum Target {
81    Goto(String),
82    Enter(String),
83    End,
84}
85
86pub struct State<B, T> {
87    pub name: String,
88    pub behavior: B,
89    pub transitions: Vec<T>,
90}
91
92/// A single state machine which states can refer to each other by [`String`] name
93pub struct StateMachine<B, T> {
94    pub name: String,
95    pub states: Vec<State<B, T>>,
96}
97
98/// Multiple state machines that may refer each other by [`String`] name
99///
100/// Use [`StateMachines::build`] to get a [`crate::StateMachines`] usable with
101/// [`label::NestedMachine`](crate::label::NestedMachine) for an efficient
102/// state machine. `T` must implement [`IntoTransition`].
103#[derive(Deserialize)]
104#[serde(transparent)]
105pub struct StateMachines<B, T>(pub Vec<StateMachine<B, T>>);
106
107impl<B, T> StateMachines<B, T> {
108    /// Convert `Self` into a [`crate::StateMachines`]
109    ///
110    /// See [`NameMapping`] and [`IntoTransition`] for details on why this is
111    /// necessary.
112    pub fn build<Trs>(self) -> crate::StateMachines<B, Trs>
113    where
114        T: IntoTransition<Trs>,
115    {
116        let mut mapping = NameMapping::new();
117        let mut ret = crate::StateMachines {
118            machines: SmallVec::with_capacity(self.0.len()),
119            machine_names: Vec::with_capacity(self.0.len()),
120            state_names: Vec::with_capacity(self.0.len()),
121        };
122        // First: iterate through the builder to collect all state and machine names
123        for (mi, StateMachine { name, states }) in self.0.iter().enumerate() {
124            ret.machine_names.push(name.clone());
125            mapping
126                .machine_names
127                .insert(name.clone(), mi as SmHandleInner);
128
129            ret.state_names.push(Vec::with_capacity(states.len()));
130            let state_names = ret.state_names.last_mut().unwrap();
131            for (si, State { name, .. }) in states.iter().enumerate() {
132                state_names.push(name.clone());
133                mapping.state_names.insert(name.clone(), si as SHandleInner);
134            }
135        }
136        // Then, we can finally build the REAL crate::StateMachines now that we
137        // know the String->index mapping
138        for StateMachine { states, .. } in self.0.into_iter() {
139            let mut machine = Vec::with_capacity(states.len());
140            for State {
141                transitions,
142                behavior,
143                ..
144            } in states.into_iter()
145            {
146                machine.push(crate::State {
147                    transitions: transitions
148                        .into_iter()
149                        .map(|t| t.into_with(&mapping))
150                        .collect(),
151                    behavior,
152                });
153            }
154            ret.machines.push(crate::StateMachine {
155                states: machine.into(),
156            });
157        }
158        ret
159    }
160}