CPP-TOOLBOX
Loading...
Searching...
No Matches
state_machine.hpp
Go to the documentation of this file.
1#ifndef STATE_MACHINE_HPP
2#define STATE_MACHINE_HPP
3
4#include <functional>
5#include <unordered_map>
6#include <vector>
7#include <string>
8#include <optional>
9
22template <typename StateType> class PureStateMachine {
23 public:
24 using Condition = std::function<bool()>;
25
29 struct Transition {
30 StateType to;
32 };
33
43
47 struct Event {
49 StateType state;
50 StateType next_state;
51 };
52
56 enum class Mode {
59 };
60
67 explicit PureStateMachine(StateType initial_state, Mode mode = Mode::MULTI_EVENT)
68 : current_state(initial_state), mode(mode) {}
69
75 void add_state(StateType state) { states[state] = {}; }
76
84 void add_transition(StateType from, StateType to, Condition condition) { states[from].push_back({to, condition}); }
85
95 [[nodiscard]] std::vector<Event> update_pure() {
96 switch (mode) {
98 return update_multi_event();
100 return update_single_event();
101 default:
102 return {};
103 }
104 }
105
110 [[nodiscard]] StateType get_state() const { return current_state; }
111
116 void set_mode(Mode new_mode) { mode = new_mode; }
117
122 [[nodiscard]] Mode get_mode() const { return mode; }
123
124 private:
135 [[nodiscard]] std::vector<Event> update_multi_event() {
136 std::vector<Event> events;
137
138 bool state_changed = current_state != previous_state;
139 if (state_changed) {
140 if (there_is_a_previous_state)
141 events.push_back({EventType::EXIT_STATE, previous_state, current_state});
142
143 events.push_back({EventType::ENTER_STATE, current_state, current_state});
144 previous_state = current_state;
145 there_is_a_previous_state = true;
146 }
147
148 events.push_back({EventType::UPDATE_STATE, current_state, current_state});
149
150 for (auto &t : states[current_state]) {
151 if (t.condition && t.condition()) {
152 events.push_back({EventType::TRANSITION, current_state, t.to});
153 current_state = t.to;
154 break;
155 }
156 }
157
158 return events;
159 }
160
172 [[nodiscard]] std::vector<Event> update_single_event() {
173 bool state_changed = current_state != previous_state;
174
175 if (state_changed) {
176 if (there_is_a_previous_state) {
177 previous_state = current_state;
178 there_is_a_previous_state = true;
179 return {{EventType::EXIT_STATE, previous_state, current_state}};
180 } else {
181 previous_state = current_state;
182 there_is_a_previous_state = true;
183 return {{EventType::ENTER_STATE, current_state, current_state}};
184 }
185 }
186
187 // Evaluate transitions first
188 for (auto &t : states[current_state]) {
189 if (t.condition && t.condition()) {
190 current_state = t.to;
191 return {{EventType::TRANSITION, previous_state, current_state}};
192 }
193 }
194
195 // Default: update event
196 return {{EventType::UPDATE_STATE, current_state, current_state}};
197 }
198
199 private:
200 StateType current_state;
201 StateType previous_state;
202 bool there_is_a_previous_state = false;
203 Mode mode;
204
205 std::unordered_map<StateType, std::vector<Transition>> states;
206};
207
217template <typename StateType> class StateMachine {
218 public:
219 using Action = std::function<void()>;
220 using Condition = std::function<bool()>;
222
223 struct State {
224 std::optional<Action> on_enter;
225 std::optional<Action> on_update;
226 std::optional<Action> on_exit;
227 };
228
229 explicit StateMachine(StateType initial_state) : pure_fsm(initial_state) {}
230
231 void add_state(StateType state, std::optional<Action> on_enter = std::nullopt,
232 std::optional<Action> on_update = std::nullopt, std::optional<Action> on_exit = std::nullopt) {
233 pure_fsm.add_state(state);
234 states[state] = {on_enter, on_update, on_exit};
235 }
236
237 void add_transition(StateType from, StateType to, Condition condition) {
238 pure_fsm.add_transition(from, to, condition);
239 }
240
246 void update() {
247 auto events = pure_fsm.update_pure();
248
249 for (auto &event : events) {
250
251 // NOTE: the order in this switch defines that exit is run first, then enter, and then update
252 // which is how we would want it to be in terms of linear time
253 switch (event.type) {
254
256 if (states[event.state].on_exit)
257 (*states[event.state].on_exit)();
258 break;
259
261 if (states[event.state].on_enter)
262 (*states[event.state].on_enter)();
263 break;
264
266 if (states[event.state].on_update)
267 (*states[event.state].on_update)();
268 break;
269
270 default:
271 break;
272 }
273 }
274 }
275
276 [[nodiscard]] StateType get_state() const { return pure_fsm.get_state(); }
277
278 [[nodiscard]] const Pure &get_pure_fsm() const { return pure_fsm; }
279
280 private:
281 Pure pure_fsm;
282 std::unordered_map<StateType, State> states;
283};
284
285#endif // STATE_MACHINE_HPP
A minimal and flexible finite state machine (FSM) implementation.
Definition state_machine.hpp:22
std::vector< Event > update_pure()
Updates the state machine and emits events describing what happened.
Definition state_machine.hpp:95
std::function< bool()> Condition
Definition state_machine.hpp:24
StateType get_state() const
Gets the current state.
Definition state_machine.hpp:110
void add_state(StateType state)
Adds a new state to the machine.
Definition state_machine.hpp:75
PureStateMachine(StateType initial_state, Mode mode=Mode::MULTI_EVENT)
Constructs a new PureStateMachine with an initial state and mode.
Definition state_machine.hpp:67
EventType
The type of event emitted by the state machine.
Definition state_machine.hpp:37
@ UPDATE_STATE
Indicates the current state is being updated.
Definition state_machine.hpp:39
@ TRANSITION
Indicates a transition between two states.
Definition state_machine.hpp:41
@ ENTER_STATE
Indicates entry into a new state.
Definition state_machine.hpp:38
@ EXIT_STATE
Indicates exit from a previous state.
Definition state_machine.hpp:40
void set_mode(Mode new_mode)
Sets the current operational mode of the FSM.
Definition state_machine.hpp:116
Mode
Determines how many events are emitted per update tick.
Definition state_machine.hpp:56
@ MULTI_EVENT
Default mode: may emit multiple events per tick.
Definition state_machine.hpp:57
@ SINGLE_EVENT
Alternate mode: only one event per tick.
Definition state_machine.hpp:58
Mode get_mode() const
Gets the current operational mode.
Definition state_machine.hpp:122
void add_transition(StateType from, StateType to, Condition condition)
Adds a conditional transition between two states.
Definition state_machine.hpp:84
std::function< void()> Action
Definition state_machine.hpp:219
PureStateMachine< StateType > Pure
Definition state_machine.hpp:221
const Pure & get_pure_fsm() const
Definition state_machine.hpp:278
void add_transition(StateType from, StateType to, Condition condition)
Definition state_machine.hpp:237
StateMachine(StateType initial_state)
Definition state_machine.hpp:229
void add_state(StateType state, std::optional< Action > on_enter=std::nullopt, std::optional< Action > on_update=std::nullopt, std::optional< Action > on_exit=std::nullopt)
Definition state_machine.hpp:231
StateType get_state() const
Definition state_machine.hpp:276
void update()
Performs one update tick:
Definition state_machine.hpp:246
std::function< bool()> Condition
Definition state_machine.hpp:220
@ t
Definition input_state.hpp:46
Represents a state-related event emitted during an update.
Definition state_machine.hpp:47
StateType next_state
The next state (may be the same as the current for non-transition events).
Definition state_machine.hpp:50
StateType state
The current state.
Definition state_machine.hpp:49
EventType type
The type of event.
Definition state_machine.hpp:48
Represents a possible transition from one state to another.
Definition state_machine.hpp:29
StateType to
The target state to transition to.
Definition state_machine.hpp:30
Condition condition
A callable that returns true when the transition should occur.
Definition state_machine.hpp:31
Definition state_machine.hpp:223
std::optional< Action > on_enter
Definition state_machine.hpp:224
std::optional< Action > on_update
Definition state_machine.hpp:225
std::optional< Action > on_exit
Definition state_machine.hpp:226