fluent_state_machine/
lib.rs

1mod errors;
2use errors::StateMachineError;
3
4pub struct Transition<Event, State, Store> {
5    event: Event,
6    from_state: State,
7    to_state: State,
8    before_event: fn(&mut Store),
9    after_event: fn(&mut Store),
10    condition: fn(&Store) -> bool,
11}
12
13pub struct StateMachine<Event, State, Store> {
14    global_function_after_transition: fn(&mut Store, &State, &Event),
15    transitions: Vec<Transition<Event, State, Store>>,
16    pub state: State,
17    pub store: Store,
18}
19
20/// `StateMachineBuilder` is used to build a `StateMachine`.
21///
22/// # Example
23///
24/// ```
25/// use fluent_state_machine::StateMachineBuilder;
26/// 
27/// let mut turnstyle = StateMachineBuilder::new((), "Locked")
28///     .state("Locked")
29///         .on("Coin").go_to("Unlocked")
30///     .state("Unlocked")
31///         .on("Push").go_to("Locked")
32///     .build().unwrap();
33///
34/// turnstyle.trigger("Coin");
35/// assert_eq!(turnstyle.state, "Unlocked");
36///
37/// turnstyle.trigger("Push");
38/// assert_eq!(turnstyle.state, "Locked");
39/// ```
40pub struct StateMachineBuilder<Event, State, Store> {
41    state_machine: StateMachine<Event, State, Store>,
42    last_added_state: State,
43    errors: Vec<StateMachineError>,
44}
45
46impl<Event, State, Store> StateMachine<Event, State, Store>
47where
48    State: Copy + PartialEq,
49    Event: PartialEq,
50{
51    // Trigger an event, this will result in the state machine changing state if the condition is met. By default the condition is always true.
52    #[allow(clippy::needless_pass_by_value)]
53    pub fn trigger(&mut self, event: Event) {
54        for transition in &mut self.transitions {
55            // Filter out transitions that do not match the trigger or the current state
56            if transition.event != event || self.state != transition.from_state {
57                continue;
58            }
59
60            // Call the before_event function
61            (transition.before_event)(&mut self.store);
62
63            // If condition is met call the after trigger and change internal state
64            if (transition.condition)(&self.store) {
65                (transition.after_event)(&mut self.store);
66                self.state = transition.to_state;
67                (self.global_function_after_transition)(&mut self.store, &self.state, &event);
68                break;
69            }
70        }
71    }
72}
73
74impl<Event, State, Store> StateMachineBuilder<Event, State, Store>
75where
76    State: Copy + PartialEq,
77    Event: PartialEq,
78{
79    pub fn new(data_store: Store, initial_state: State) -> Self {
80        Self {
81            state_machine: StateMachine {
82                transitions: Vec::new(),
83                state: initial_state,
84                store: data_store,
85                global_function_after_transition: |_,_,_| {},
86            },
87            last_added_state: initial_state,
88            errors: Vec::new(),
89        }
90    }
91
92    #[must_use]
93    pub fn set_global_action(mut self, global_action: fn(&mut Store, &State, &Event)) -> Self {
94        self.state_machine.global_function_after_transition = global_action;
95        self
96    }
97
98    #[must_use]
99    pub const fn state(mut self, state: State) -> Self {
100        self.last_added_state = state;
101        self
102    }
103
104    #[must_use]
105    pub fn on(mut self, event: Event) -> Self {
106        self.state_machine.transitions.push(Transition {
107            event,
108            from_state: self.last_added_state,
109            to_state: self.last_added_state,
110            condition: |_| true,
111            before_event: |_| {},
112            after_event: |_| {},
113        });
114        self
115    }
116
117    #[must_use]
118    pub fn go_to(mut self, target: State) -> Self {
119        self.update_last_transition(|transition| transition.to_state = target);
120        self
121    }
122
123    #[must_use]
124    pub fn update(mut self, before_event: fn(&mut Store)) -> Self {
125        self.update_last_transition(|transition| transition.before_event = before_event);
126        self
127    }
128
129    #[must_use]
130    pub fn only_if(mut self, condition: fn(&Store) -> bool) -> Self {
131        self.update_last_transition(|transition| transition.condition = condition);
132        self
133    }
134
135    #[must_use]
136    pub fn then(mut self, after_event: fn(&mut Store)) -> Self {
137        self.update_last_transition(|transition| transition.after_event = after_event);
138        self
139    }
140
141    fn update_last_transition<F>(&mut self, mut update: F)
142    where
143        F: FnMut(&mut Transition<Event, State, Store>),
144    {
145        match self.state_machine.transitions.last_mut() {
146            None => self.errors.push(StateMachineError::MissingState),
147            Some(transition) => update(transition),
148        }
149    }
150
151    // Build the state machine and return the result. If there are any duplicate transitions an error will be returned.
152    pub fn build(mut self) -> Result<StateMachine<Event, State, Store>, Vec<StateMachineError>> {
153        let transitions = &self.state_machine.transitions;
154
155        for i in 0..transitions.len() {
156            for j in i + 1..transitions.len() {
157                if transitions[i].event == transitions[j].event
158                    && transitions[i].from_state == transitions[j].from_state
159                    && transitions[i].to_state == transitions[j].to_state
160                {
161                    self.errors.push(StateMachineError::DuplicateTransition);
162                }
163            }
164        }
165
166        if !self.errors.is_empty() {
167            return Err(self.errors);
168        }
169
170        Ok(self.state_machine)
171    }
172}