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}