Skip to main content

fluent_fsm/machine/
passive.rs

1// MIT License
2//
3// Copyright (c) 2024 Wes Kelly
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23use std::collections::HashMap;
24use std::hash::Hash;
25
26pub struct PassiveStateMachine<TState, TModel = (), TEvent = ()>
27where
28    TState: Eq + Hash + Copy + Clone,
29    TEvent: Eq + Hash + Copy + Clone,
30{
31    running: bool,
32    current_state: TState,
33    model: TModel,
34
35    on_event: HashMap<(TState, TEvent), Vec<Box<dyn Fn(&mut TModel) + 'static + Sync + Send>>>,
36    on_enter: HashMap<TState, Vec<Box<dyn Fn(&mut TModel) + 'static + Sync + Send>>>,
37    on_leave: HashMap<TState, Vec<Box<dyn Fn(&mut TModel) + 'static + Sync + Send>>>,
38
39    transitions: HashMap<(TState, TEvent), TState>,
40}
41
42impl<TState, TModel, TEvent> PassiveStateMachine<TState, TModel, TEvent>
43where
44    TState: Eq + Hash + Copy,
45    TEvent: Eq + Hash + Copy,
46{
47    pub(crate) fn new(initial_state: TState, model: TModel) -> Self {
48        Self {
49            running: false,
50            current_state: initial_state,
51            model,
52            on_event: HashMap::new(),
53            on_enter: HashMap::new(),
54            on_leave: HashMap::new(),
55            transitions: HashMap::new(),
56        }
57    }
58
59    pub(crate) fn add_event_handler(
60        &mut self,
61        state: TState,
62        event: TEvent,
63        func: impl Fn(&mut TModel) + 'static + Sync + Send,
64    ) {
65        let key = (state, event);
66        match self.on_event.get_mut(&key) {
67            Some(vec) => {
68                vec.push(Box::new(func));
69            }
70            None => {
71                self.on_event.insert(key, vec![Box::new(func)]);
72            }
73        }
74    }
75
76    pub(crate) fn add_enter_handler(
77        &mut self,
78        state: TState,
79        func: impl Fn(&mut TModel) + 'static + Sync + Send,
80    ) {
81        match self.on_enter.get_mut(&state) {
82            Some(vec) => {
83                vec.push(Box::new(func));
84            }
85            None => {
86                self.on_enter.insert(state, vec![Box::new(func)]);
87            }
88        }
89    }
90
91    pub(crate) fn add_leave_handler(
92        &mut self,
93        state: TState,
94        func: impl Fn(&mut TModel) + 'static + Sync + Send,
95    ) {
96        match self.on_leave.get_mut(&state) {
97            Some(vec) => {
98                vec.push(Box::new(func));
99            }
100            None => {
101                self.on_leave.insert(state, vec![Box::new(func)]);
102            }
103        }
104    }
105
106    pub(crate) fn add_transition(&mut self, on: TEvent, from: TState, to: TState) {
107        self.transitions.insert((from, on), to);
108    }
109
110    pub fn current_state(&self) -> &TState {
111        &self.current_state
112    }
113
114    pub fn model(&self) -> &TModel {
115        &self.model
116    }
117
118    pub fn model_mut(&mut self) -> &mut TModel {
119        &mut self.model
120    }
121
122    pub fn start(&mut self) {
123        if self.running {
124            return;
125        }
126
127        self.running = true;
128
129        if let Some(actions) = self.on_enter.get(&(self.current_state)) {
130            for action in actions.iter() {
131                action(&mut self.model);
132            }
133        }
134    }
135
136    pub fn fire(&mut self, event: TEvent) {
137        if !self.running {
138            panic!("State machine is not running");
139        }
140
141        // Handle event and update state
142        if let Some(handlers) = self.on_event.get(&(self.current_state, event)) {
143            for handler in handlers.iter() {
144                handler(&mut self.model);
145            }
146        }
147
148        // If a transition happens, handle on-leave and on-enter
149        if let Some(state) = self.transitions.get(&(self.current_state, event)) {
150            self.goto(*state);
151        }
152    }
153
154    pub(crate) fn goto(&mut self, state: TState) {
155        if let Some(actions) = self.on_leave.get(&(self.current_state)) {
156            for action in actions.iter() {
157                action(&mut self.model);
158            }
159        }
160
161        self.current_state = state;
162
163        if let Some(actions) = self.on_enter.get(&(self.current_state)) {
164            for action in actions.iter() {
165                action(&mut self.model);
166            }
167        }
168    }
169}