generic_state_machine/
primitives.rs

1use crate::error::{ContexError, Error, Result};
2use derivative::*;
3use std::collections::HashMap;
4use std::fmt::Debug;
5use std::hash::Hash;
6use std::sync::Mutex;
7
8pub type TransitionFunction<S, E> = fn(&StateMachine<S, E>, E) -> S;
9
10/// The primitive state machine type that holds all context
11///  * List of available states
12///  * Maps of input events per state. An event not in the list is ignored by default
13///  * Maps of transition functions per input event
14/// It also holds the live status of the state machine:
15///  * Queue with incoming input events to be processed
16///  * Current state
17///
18/// **Important Note:**
19/// This type is used to implement the async (and later the sync) versions of the generic state machine
20/// You should not use this type for most of use cases.
21/// Use the async (or sync) implementations instead!
22#[derive(Debug)]
23pub struct StateMachine<S, E>
24where
25    S: Clone + std::hash::Hash + Eq + Debug,
26    E: Hash + Eq + Debug,
27{
28    states: Vec<S>,
29    current: Mutex<Option<S>>,
30    pub transitions: HashMap<S, StateMachineTransitions<S, E>>,
31}
32
33/// Helper structure to bypass Debug trait limitations on function items
34#[derive(Derivative)]
35#[derivative(Debug)]
36pub struct StateMachineTransitions<S, E>
37where
38    S: Clone + Hash + Eq + Debug,
39    E: Hash + Eq + Debug,
40{
41    #[derivative(Debug(format_with = "my_fmt_fn"))]
42    map: HashMap<E, TransitionFunction<S, E>>,
43}
44
45/// Helper function to bypass Debug trait limitations on function items
46fn my_fmt_fn<K: std::fmt::Debug, V>(
47    t: &HashMap<K, V>,
48    f: &mut std::fmt::Formatter,
49) -> std::result::Result<(), std::fmt::Error> {
50    f.debug_list().entries(t.keys()).finish()
51}
52
53impl<S, E> Default for StateMachine<S, E>
54where
55    S: Clone + Hash + Eq + Debug,
56    E: Hash + Eq + Debug,
57{
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63impl<S, E> StateMachine<S, E>
64where
65    S: Clone + Hash + Eq + Debug,
66    E: Hash + Eq + Debug,
67{
68    /// Create a new empty state machine
69    ///  The state machine needs to be confifures properly using:
70    ///   * [add_state()](StateMachine::add_states)
71    ///   * [add_transition()](StateMachine::add_transition)
72    ///   * [set_state()](StateMachine::set_state)
73    ///  The above methods can be chained in a single command, like so:
74    ///  ```rust
75    ///     use generic_state_machine::primitives::StateMachine;
76    ///
77    ///     let mut fsm = StateMachine::<String, String>::new();
78    ///     fsm.add_states(&mut vec![
79    ///         "alpha".to_string(),
80    ///         "beta".to_string(),
81    ///         "gamma".to_string(),
82    ///         ])
83    ///         .add_transition("alpha".to_string(), "beta".to_string(), |_, _| {
84    ///             "beta".to_string()
85    ///         })
86    ///         .initial_state("alpha".to_string());
87    ///
88    ///     println!("{:?}", fsm);
89    pub fn new() -> Self {
90        StateMachine {
91            states: vec![],
92            current: Mutex::new(None),
93            transitions: std::collections::HashMap::new(),
94        }
95    }
96
97    /// Get the current state
98    pub fn current_state(&self) -> Result<S> {
99        match &self.current.lock().unwrap().clone() {
100            Some(s) => Ok(s.clone()),
101            None => Err(Error::NoCurrentState),
102        }
103    }
104
105    pub fn initial_state(&mut self, state: S) -> Result<&mut Self> {
106        if self.current.lock()?.is_some() {
107            Err(Error::InitialStateDoubleSet)
108        } else {
109            *self.current.lock().unwrap() = Some(state);
110            Ok(self)
111        }
112    }
113
114    /// Force the current state to the state provided. Used to set the initial state
115    pub(crate) fn set_state(&mut self, state: S) -> &mut Self {
116        // let mut test = Some(1);
117
118        // test = match test {
119        //     Some(_) => unreachable!(),
120        //     None => Some(2),
121        // };
122
123        *self.current.lock().unwrap() = Some(state);
124        self
125    }
126
127    /// Add the provided states to the list of available states
128    pub fn add_states(&mut self, states: &[S]) -> &mut Self {
129        self.states.extend_from_slice(states);
130        self
131    }
132
133    /// Add the provided function as the transition function to be executed if the machine
134    /// is in the provided state and receives the provided input event
135    pub fn add_transition(
136        &mut self,
137        state: S,
138        event: E,
139        function: TransitionFunction<S, E>,
140    ) -> &mut Self {
141        if let Some(transitions) = self.transitions.get_mut(&state) {
142            transitions.map.insert(event, function);
143        } else {
144            self.transitions.insert(state, {
145                let mut transitions = StateMachineTransitions {
146                    map: std::collections::HashMap::new(),
147                };
148                transitions.map.insert(event, function);
149                transitions
150            });
151        }
152        self
153    }
154
155    /// Executes the received event
156    /// Checks if a transition function is provided for the current state and for the incoming event
157    /// It calls the function and sets the state to the output of the transition function.
158    /// If no transition function is available, the incoming event is dropped.
159    pub fn execute(&mut self, event: E) -> Result<S> {
160        if let Some(events) = self.transitions.get(&self.current_state()?) {
161            if let Some(&function) = events.map.get(&event) {
162                let res = function(self, event);
163                self.set_state(res);
164            } else {
165                return Err(ContexError::EventNotMachingState(self.current_state()?, event).into());
166            }
167        } else {
168            unreachable!();
169        }
170
171        let current_state = self.current_state()?;
172        Ok(current_state)
173    }
174}