game_state_machine/
lib.rs

1//! A generic stack-based state machine.
2//! This state machine contains a stack of states and handles transitions between them.
3//! StateTransition happen based on the return value of the currently running state's functions.
4//! Only one state can run at once.
5#![deny(missing_docs)]
6
7/// A transition from one state to the other.
8/// ## Generics
9/// - S: State data, the data that is sent to states for them to do their operations.
10pub enum StateTransition<S> {
11    /// Stay in the current state.
12    None,
13    /// End the current state and go to the previous state on the stack, if any.
14    /// If we Pop the last state, the state machine exits.
15    Pop,
16    /// Push a new state on the stack.
17    Push(Box<dyn State<S>>),
18    /// Pop all states on the stack and insert this one.
19    Switch(Box<dyn State<S>>),
20    /// Pop all states and exit the state machine.
21    Quit,
22}
23
24/// Trait that states must implement.
25///
26/// ## Generics
27/// - S: State data, the data that is sent to states for them to do their operations.
28pub trait State<S> {
29    /// Called when the state is first inserted on the stack.
30    fn on_start(&mut self, _state_data: &mut S) {}
31    /// Called when the state is popped from the stack.
32    fn on_stop(&mut self, _state_data: &mut S) {}
33    /// Called when a state is pushed over this one in the stack.
34    fn on_pause(&mut self, _state_data: &mut S) {}
35    /// Called when the state just on top of this one in the stack is popped.
36    fn on_resume(&mut self, _state_data: &mut S) {}
37    /// Executed on every frame immediately, as fast as the engine will allow.
38    /// If you need to execute logic at a predictable interval (for example, a physics engine)
39    /// it is suggested to use the state data information to determine when to run such fixed timed
40    /// logic.
41    fn update(&mut self, _state_data: &mut S) -> StateTransition<S> {
42        StateTransition::None
43    }
44}
45
46/// A state machine that holds the stack of states and performs transitions between states.
47/// It can be created using
48/// ```rust,ignore
49/// StateMachine::<()>::default()
50/// ```
51/// ## Generics
52/// - S: State data, the data that is sent to states for them to do their operations.
53pub struct StateMachine<S> {
54    state_stack: Vec<Box<dyn State<S>>>,
55}
56
57impl<S> Default for StateMachine<S> {
58    fn default() -> Self {
59        Self {
60            state_stack: Vec::default(),
61        }
62    }
63}
64
65impl<S> StateMachine<S> {
66    /// Returns if the state machine still has states in its stack.
67    pub fn is_running(&self) -> bool {
68        !self.state_stack.is_empty()
69    }
70
71    /// Updates the state at the top of the stack with the provided data.
72    /// If the states returns a transition, perform it.
73    pub fn update(&mut self, state_data: &mut S) {
74        let trans = match self.state_stack.last_mut() {
75            Some(state) => state.update(state_data),
76            None => StateTransition::None,
77        };
78
79        self.transition(trans, state_data);
80    }
81
82    fn transition(&mut self, request: StateTransition<S>, state_data: &mut S) {
83        match request {
84            StateTransition::None => (),
85            StateTransition::Pop => self.pop(state_data),
86            StateTransition::Push(state) => self.push(state, state_data),
87            StateTransition::Switch(state) => self.switch(state, state_data),
88            StateTransition::Quit => self.stop(state_data),
89        }
90    }
91
92    fn switch(&mut self, mut state: Box<dyn State<S>>, state_data: &mut S) {
93        if let Some(mut state) = self.state_stack.pop() {
94            state.on_stop(state_data)
95        }
96
97        state.on_start(state_data);
98        self.state_stack.push(state);
99    }
100
101    /// Push a state on the stack and start it.
102    /// Pauses any previously active state.
103    pub fn push(&mut self, mut state: Box<dyn State<S>>, state_data: &mut S) {
104        if let Some(state) = self.state_stack.last_mut() {
105            state.on_pause(state_data);
106        }
107
108        state.on_start(state_data);
109        self.state_stack.push(state);
110    }
111
112    fn pop(&mut self, state_data: &mut S) {
113        if let Some(mut state) = self.state_stack.pop() {
114            state.on_stop(state_data);
115        }
116
117        if let Some(state) = self.state_stack.last_mut() {
118            state.on_resume(state_data);
119        }
120    }
121
122    /// Removes all currently running states from the stack.
123    pub fn stop(&mut self, state_data: &mut S) {
124        while let Some(mut state) = self.state_stack.pop() {
125            state.on_stop(state_data);
126        }
127    }
128}
129#[cfg(test)]
130mod tests {
131    use crate::*;
132
133    type StateData = (isize, isize);
134
135    pub struct Test;
136
137    impl State<StateData> for Test {
138        fn on_start(&mut self, data: &mut StateData) {
139            data.0 += data.1;
140        }
141
142        fn on_resume(&mut self, data: &mut StateData) {
143            self.on_start(data);
144        }
145
146        fn update(&mut self, _data: &mut StateData) -> StateTransition<StateData> {
147            StateTransition::Push(Box::new(Test))
148        }
149    }
150
151    #[test]
152    fn sm_test() {
153        let mut sm = StateMachine::<StateData>::default();
154
155        let mut state_data = (0, 10);
156
157        sm.push(Box::new(Test), &mut state_data);
158        assert!(state_data.0 == 10);
159
160        sm.update(&mut state_data);
161        assert!(state_data.0 == 20);
162
163        sm.stop(&mut state_data);
164        assert!(state_data.0 == 20);
165        assert!(!sm.is_running())
166    }
167}