statemachine_rs/machine/
mod.rs

1use std::{cell::RefCell, marker::PhantomData};
2
3pub mod builder;
4pub mod error;
5
6/// The trait is representing the basic operation for the state machine.
7/// It includes getting its current state, transition to the next state,
8/// resetting its current state to initial state and setting particular state forcibly.
9/// [`BasicStateMachine`] is a good example to implement it.
10/// Of course, you can build your own state machine by using this trait.
11pub trait StateMachine<State, Input> {
12    /// Returns the current state of the state machine.
13    ///
14    /// # Example
15    /// ```
16    /// use statemachine_rs::machine::{
17    ///     builder::BasicStateMachineBuilder, builder::StateMachineBuilder, StateMachine,
18    /// };
19    ///
20    /// #[derive(Clone, Debug, PartialEq)]
21    /// enum ButtonState {
22    ///     On,
23    ///     Off,
24    /// }
25    ///
26    /// #[allow(dead_code)]
27    /// enum Input {
28    ///     Press,
29    /// }
30    ///
31    /// let sm = BasicStateMachineBuilder::start()
32    ///     .initial_state(ButtonState::Off)
33    ///     .transition(|state, input| match (state, input) {
34    ///         (ButtonState::On, Input::Press) => ButtonState::Off,
35    ///         (ButtonState::Off, Input::Press) => ButtonState::On,
36    ///     })
37    ///     .build()
38    ///     .unwrap();
39    ///
40    /// assert_eq!(ButtonState::Off, sm.current_state());
41    /// ```
42    fn current_state(&self) -> State;
43    /// Returns the result of state transition according to `input` and
44    /// the definition of transition function.
45    ///
46    /// # Example
47    /// ```
48    /// use statemachine_rs::machine::{
49    ///     builder::BasicStateMachineBuilder, builder::StateMachineBuilder, StateMachine,
50    /// };
51    ///
52    /// #[derive(Clone, Debug, PartialEq)]
53    /// enum ButtonState {
54    ///     On,
55    ///     Off,
56    /// }
57    ///
58    /// enum Input {
59    ///     Press,
60    /// }
61    ///
62    /// let sm = BasicStateMachineBuilder::start()
63    ///     .initial_state(ButtonState::Off)
64    ///     .transition(|state, input| match (state, input) {
65    ///         (ButtonState::On, Input::Press) => ButtonState::Off,
66    ///         (ButtonState::Off, Input::Press) => ButtonState::On,
67    ///     })
68    ///     .build()
69    ///     .unwrap();
70    ///
71    /// assert_eq!(ButtonState::Off, sm.current_state());
72    /// assert_eq!(ButtonState::On, sm.consume(Input::Press));
73    /// ```
74    fn consume(&self, input: Input) -> State;
75    /// Returns the next state from the current state but the state machine
76    /// retains in its current state.
77    ///
78    /// # Example
79    /// ```
80    /// use statemachine_rs::machine::{
81    ///     builder::BasicStateMachineBuilder, builder::StateMachineBuilder, StateMachine,
82    /// };
83    ///
84    /// #[derive(Clone, Debug, PartialEq)]
85    /// enum ButtonState {
86    ///     On,
87    ///     Off,
88    /// }
89    ///
90    /// enum Input {
91    ///     Press,
92    /// }
93    ///
94    /// let sm = BasicStateMachineBuilder::start()
95    ///     .initial_state(ButtonState::Off)
96    ///     .transition(|state, input| match (state, input) {
97    ///         (ButtonState::On, Input::Press) => ButtonState::Off,
98    ///         (ButtonState::Off, Input::Press) => ButtonState::On,
99    ///     })
100    ///     .build()
101    ///     .unwrap();
102    ///
103    /// assert_eq!(ButtonState::Off, sm.current_state());
104    /// assert_eq!(ButtonState::On, sm.peek(Input::Press));
105    /// assert_eq!(ButtonState::Off, sm.current_state());
106    /// ```
107    fn peek(&self, input: Input) -> State;
108    /// Resets the current state to the initial state.
109    ///
110    /// # Example
111    /// ```
112    /// use statemachine_rs::machine::{
113    ///     builder::BasicStateMachineBuilder, builder::StateMachineBuilder, StateMachine,
114    /// };
115    ///
116    /// #[derive(Clone, Debug, PartialEq)]
117    /// enum ButtonState {
118    ///     On,
119    ///     Off,
120    /// }
121    ///
122    /// enum Input {
123    ///     Press,
124    /// }
125    ///
126    /// let sm = BasicStateMachineBuilder::start()
127    ///     .initial_state(ButtonState::Off)
128    ///     .transition(|state, input| match (state, input) {
129    ///         (ButtonState::On, Input::Press) => ButtonState::Off,
130    ///         (ButtonState::Off, Input::Press) => ButtonState::On,
131    ///     })
132    ///     .build()
133    ///     .unwrap();
134    ///
135    /// assert_eq!(ButtonState::Off, sm.current_state());
136    /// assert_eq!(ButtonState::On, sm.consume(Input::Press));
137    /// assert_eq!(ButtonState::Off, sm.reset());
138    /// ```
139    fn reset(&self) -> State;
140    /// Set a new state forcibly to the current state.
141    ///
142    /// # Example
143    /// ```
144    /// use statemachine_rs::machine::{
145    ///     builder::BasicStateMachineBuilder, builder::StateMachineBuilder, StateMachine,
146    /// };
147    ///
148    /// #[derive(Clone, Debug, PartialEq)]
149    /// enum ButtonState {
150    ///     On,
151    ///     Off,
152    ///     Disable,
153    /// }
154    ///
155    /// enum Input {
156    ///     Press,
157    /// }
158    ///
159    /// let sm = BasicStateMachineBuilder::start()
160    ///     .initial_state(ButtonState::Off)
161    ///     .transition(|state, input| match (state, input) {
162    ///         (ButtonState::On, Input::Press) => ButtonState::Off,
163    ///         (ButtonState::Off, Input::Press) => ButtonState::On,
164    ///         (ButtonState::Disable, Input::Press) => ButtonState::Disable,
165    ///     })
166    ///     .build()
167    ///     .unwrap();
168    ///
169    /// assert_eq!(ButtonState::Off, sm.current_state());
170    /// sm.set(ButtonState::Disable);
171    /// assert_eq!(ButtonState::Disable, sm.consume(Input::Press));
172    /// ```
173    fn set(&self, new_state: State);
174}
175
176/// [`StateWrapper`] is a struct for interior mutability.
177/// It enables to acquire the control of switching mutable/imutable
178/// with [`std::cell::RefCell`].
179pub(crate) struct StateWrapper<State: Clone>(State);
180
181impl<State> StateWrapper<State>
182where
183    State: Clone,
184{
185    pub fn new(state: State) -> Self {
186        StateWrapper(state)
187    }
188
189    pub fn get(&self) -> State {
190        self.0.clone()
191    }
192
193    pub fn set(&mut self, state: State) {
194        self.0 = state;
195    }
196}
197
198/// The basic state machine implementation.
199/// It holds `initial_state`, `current_state`, `transition` function.
200pub struct BasicStateMachine<State, Input, Transition>
201where
202    Transition: Fn(&State, Input) -> State,
203    State: Clone,
204{
205    /// `initial_state` is literally an initial state of the state machine.
206    /// The field isn't updated the whole life of its state machine.
207    /// That is, it always returns its initial state of its machine.
208    initial_state: State,
209    /// `current_state` is the current state of the state machine.
210    /// It transit to the next state via `transition`.
211    current_state: RefCell<StateWrapper<State>>,
212    /// `transition` is the definition of state transition.
213    /// See an example of [`StateMachine::consume()`], you can grasp how
214    /// to define the transition.
215    transition: Transition,
216    _maker: PhantomData<Input>,
217}
218
219impl<State, Input, Transition> StateMachine<State, Input>
220    for BasicStateMachine<State, Input, Transition>
221where
222    Transition: Fn(&State, Input) -> State,
223    State: Clone,
224{
225    fn current_state(&self) -> State {
226        self.current_state.borrow().get()
227    }
228
229    fn consume(&self, input: Input) -> State {
230        let new_state = (self.transition)(&self.current_state.borrow().0, input);
231        self.current_state.borrow_mut().set(new_state);
232        self.current_state()
233    }
234
235    fn peek(&self, input: Input) -> State {
236        (self.transition)(&self.current_state.borrow().0, input)
237    }
238
239    fn reset(&self) -> State {
240        self.current_state
241            .borrow_mut()
242            .set(self.initial_state.clone());
243        self.current_state()
244    }
245
246    fn set(&self, new_state: State) {
247        self.current_state.borrow_mut().set(new_state)
248    }
249}
250
251#[cfg(test)]
252mod test {
253    use std::{cell::RefCell, marker::PhantomData};
254
255    use super::StateMachine;
256    use super::{BasicStateMachine, StateWrapper};
257
258    #[derive(Copy, Clone, Debug, PartialEq)]
259    enum Stations {
260        Shibuya,
261        IkejiriOhashi,
262        Sangendyaya,
263        KomazawaDaigaku,
264        Sakurashinmachi,
265        Yoga,
266        FutakoTamagawa,
267    }
268
269    enum Train {
270        Local,
271        Express,
272    }
273
274    #[test]
275    fn test_current_state() {
276        let sm = BasicStateMachine {
277            initial_state: Stations::Shibuya,
278            current_state: RefCell::new(StateWrapper::new(Stations::Shibuya)),
279            transition: |station, train| match (station, train) {
280                (Stations::Shibuya, Train::Local) => Stations::IkejiriOhashi,
281                _ => unreachable!(),
282            },
283            _maker: PhantomData::<Train>::default(),
284        };
285
286        assert_eq!(Stations::Shibuya, sm.current_state());
287    }
288
289    #[test]
290    fn test_consume() {
291        let sm = BasicStateMachine {
292            initial_state: Stations::Shibuya,
293            current_state: RefCell::new(StateWrapper::new(Stations::Shibuya)),
294            transition: |station, train| match (station, train) {
295                (Stations::Shibuya, Train::Local) => Stations::IkejiriOhashi,
296                (Stations::Shibuya, Train::Express) => Stations::Sangendyaya,
297                (Stations::IkejiriOhashi, Train::Local) => Stations::Sangendyaya,
298                (Stations::Sangendyaya, Train::Local) => Stations::KomazawaDaigaku,
299                (Stations::Sangendyaya, Train::Express) => Stations::FutakoTamagawa,
300                (Stations::KomazawaDaigaku, Train::Local) => Stations::Sakurashinmachi,
301                (Stations::Sakurashinmachi, Train::Local) => Stations::Yoga,
302                _ => unreachable!(),
303            },
304            _maker: PhantomData::<Train>::default(),
305        };
306
307        assert_eq!(Stations::IkejiriOhashi, sm.consume(Train::Local));
308    }
309
310    #[test]
311    fn test_peek() {
312        let sm = BasicStateMachine {
313            initial_state: Stations::Sangendyaya,
314            current_state: RefCell::new(StateWrapper::new(Stations::Sangendyaya)),
315            transition: |station, train| match (station, train) {
316                (Stations::Shibuya, Train::Local) => Stations::IkejiriOhashi,
317                (Stations::Shibuya, Train::Express) => Stations::Sangendyaya,
318                (Stations::IkejiriOhashi, Train::Local) => Stations::Sangendyaya,
319                (Stations::Sangendyaya, Train::Local) => Stations::KomazawaDaigaku,
320                (Stations::Sangendyaya, Train::Express) => Stations::FutakoTamagawa,
321                (Stations::KomazawaDaigaku, Train::Local) => Stations::Sakurashinmachi,
322                (Stations::Sakurashinmachi, Train::Local) => Stations::Yoga,
323                _ => unreachable!(),
324            },
325            _maker: PhantomData::<Train>::default(),
326        };
327
328        assert_eq!(Stations::FutakoTamagawa, sm.peek(Train::Express));
329        assert_eq!(Stations::Sangendyaya, sm.current_state());
330    }
331
332    #[test]
333    fn test_reset() {
334        let sm = BasicStateMachine {
335            initial_state: Stations::Shibuya,
336            current_state: RefCell::new(StateWrapper::new(Stations::Sangendyaya)),
337            transition: |station, train| match (station, train) {
338                (Stations::Shibuya, Train::Local) => Stations::IkejiriOhashi,
339                (Stations::Shibuya, Train::Express) => Stations::Sangendyaya,
340                (Stations::IkejiriOhashi, Train::Local) => Stations::Sangendyaya,
341                (Stations::Sangendyaya, Train::Local) => Stations::KomazawaDaigaku,
342                (Stations::Sangendyaya, Train::Express) => Stations::FutakoTamagawa,
343                (Stations::KomazawaDaigaku, Train::Local) => Stations::Sakurashinmachi,
344                (Stations::Sakurashinmachi, Train::Local) => Stations::Yoga,
345                _ => unreachable!(),
346            },
347            _maker: PhantomData::<Train>::default(),
348        };
349
350        assert_eq!(Stations::FutakoTamagawa, sm.consume(Train::Express));
351        assert_eq!(Stations::Shibuya, sm.reset());
352    }
353
354    #[test]
355    fn test_set() {
356        let sm = BasicStateMachine {
357            initial_state: Stations::Shibuya,
358            current_state: RefCell::new(StateWrapper::new(Stations::Shibuya)),
359            transition: |station, train| match (station, train) {
360                (Stations::Shibuya, Train::Local) => Stations::IkejiriOhashi,
361                _ => unreachable!(),
362            },
363            _maker: PhantomData::<Train>::default(),
364        };
365
366        assert_eq!(Stations::Shibuya, sm.current_state());
367        sm.set(Stations::Yoga);
368        assert_eq!(Stations::Yoga, sm.current_state())
369    }
370}