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}