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}