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