Skip to main content

fluent_fsm/machine/
builder.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 crate::active::ActiveStateMachine;
24use crate::machine::passive::PassiveStateMachine;
25use std::hash::Hash;
26
27pub struct StateMachineBuilder<TState: Eq + Hash + Copy, TModel = (), TEvent: Eq + Hash + Copy = ()>
28{
29    working_on_state: TState,
30    working_on_event: Option<TEvent>,
31    current_state_machine: PassiveStateMachine<TState, TModel, TEvent>,
32}
33
34impl<TState, TModel, TEvent> StateMachineBuilder<TState, TModel, TEvent>
35where
36    TState: Eq + Hash + Copy + Sync + Send + 'static,
37    TModel: Sync + Send + 'static,
38    TEvent: Eq + Hash + Copy + Sync + Send + 'static,
39{
40    /// Create a state machine builder that starts in the given state
41    pub fn create(initial_state: TState, initial_model: TModel) -> Self {
42        Self {
43            working_on_state: initial_state,
44            working_on_event: None,
45            current_state_machine: PassiveStateMachine::new(initial_state, initial_model),
46        }
47    }
48
49    /// Change the builder context to operate on the given state
50    pub fn in_state(self, state: TState) -> Self {
51        Self {
52            working_on_state: state,
53            working_on_event: None,
54            ..self
55        }
56    }
57
58    pub fn on_enter(self, func: impl Fn() + 'static + Sync + Send) -> Self {
59        let mut builder = self;
60        let machine = &mut builder.current_state_machine;
61        machine.add_enter_handler(builder.working_on_state, move |_: &mut TModel| func());
62        builder
63    }
64
65    /// Run the given function when the state specified by `in_state` is entered
66    pub fn on_enter_mut(self, func: impl Fn(&mut TModel) + 'static + Sync + Send) -> Self {
67        let mut builder = self;
68
69        let machine = &mut builder.current_state_machine;
70
71        machine.add_enter_handler(builder.working_on_state, func);
72
73        builder
74    }
75
76    pub fn on_leave(self, func: impl Fn() + 'static + Sync + Send) -> Self {
77        let wrapper = move |_: &mut TModel| func();
78        self.on_leave_mut(wrapper)
79    }
80
81    /// Run the given function when the state specified by `in_state` is left
82    pub fn on_leave_mut(self, func: impl Fn(&mut TModel) + 'static + Sync + Send) -> Self {
83        let mut builder = self;
84
85        let machine = &mut builder.current_state_machine;
86
87        machine.add_leave_handler(builder.working_on_state, func);
88        builder
89    }
90
91    pub fn on(self, event: TEvent, func: impl Fn() + 'static + Sync + Send) -> Self {
92        let wrapper = move |_: &mut TModel| func();
93        self.on_mut(event, wrapper)
94    }
95
96    /// Run the given function when the event is fired in the state specified by `in_state`
97    pub fn on_mut(self, event: TEvent, func: impl Fn(&mut TModel) + 'static + Sync + Send) -> Self {
98        let mut builder = self;
99        builder.working_on_event = Some(event);
100
101        let machine = &mut builder.current_state_machine;
102
103        machine.add_event_handler(builder.working_on_state, event, func);
104
105        builder
106    }
107
108    /// Transition from the state specified by `in_state` to the given state when the event
109    /// specified by `on` is fired.
110    pub fn goto(self, state: TState) -> Self {
111        let mut builder = self;
112
113        match builder.working_on_event {
114            Some(e) => {
115                builder
116                    .current_state_machine
117                    .add_transition(e, builder.working_on_state, state);
118                builder.working_on_event = None;
119            }
120            None => {
121                panic!("Can't add a transition before an event is in scope with on()")
122            }
123        }
124
125        builder
126    }
127
128    /// Create a passive state machine, finalizing the builder
129    pub fn build_passive(self) -> PassiveStateMachine<TState, TModel, TEvent> {
130        self.current_state_machine
131    }
132
133    /// Create an active state machine, finalizing the builder
134    pub fn build_active(
135        self,
136        tick: impl Fn(&TState, &TModel) -> Option<TState> + Send + Sync + 'static,
137    ) -> ActiveStateMachine<TState, TModel, TEvent> {
138        ActiveStateMachine::create(tick, self.current_state_machine)
139    }
140}