FSMgine
High-performance finite state machine library for C++17 with single-threaded and multi-threaded variants
Loading...
Searching...
No Matches
FSM.hpp
Go to the documentation of this file.
1
4
5#pragma once
6
7#include <string_view>
8#include <string>
9#include <unordered_map>
10#include <vector>
11#include <stdexcept>
12#include <variant> // For std::monostate
15
16#ifdef FSMGINE_MULTI_THREADED
17#include <mutex>
18#endif
19
22
23namespace fsmgine {
24
25// Forward declaration
26template<typename TEvent>
27class FSMBuilder;
28
29template<typename TEvent>
31
34class FSMStateNotFoundError : public std::runtime_error {
35public:
38 explicit FSMStateNotFoundError(const std::string& state)
39 : std::runtime_error("FSM state not found: " + state) {}
40};
41
44class FSMNotInitializedError : public std::runtime_error {
45public:
48 : std::runtime_error("FSM has not been initialized with a state") {}
49};
50
53class FSMInvalidStateError : public std::invalid_argument {
54public:
57 explicit FSMInvalidStateError(const std::string& message)
58 : std::invalid_argument(message) {}
59};
60
93template<typename TEvent = std::monostate>
94class FSM {
95public:
98 using Predicate = std::function<bool(const TEvent&)>;
99
102 using Action = std::function<void(const TEvent&)>;
103
104private:
105 // Internal state data structure
106 struct StateData {
107 std::vector<Action> on_enter_actions;
108 std::vector<Action> on_exit_actions;
109 std::vector<Transition<TEvent>> transitions;
110
111 StateData() = default;
112 StateData(const StateData&) = delete;
113 StateData& operator=(const StateData&) = delete;
114 StateData(StateData&&) = default;
115 StateData& operator=(StateData&&) = default;
116 };
117
118public:
120 FSM() = default;
121
122 // Copy operations are deleted
123 FSM(const FSM&) = delete;
124 FSM& operator=(const FSM&) = delete;
125
128 FSM(FSM&& other) noexcept {
129#ifdef FSMGINE_MULTI_THREADED
130 std::unique_lock<std::mutex> lock(other.mutex_);
131#endif
132 states_ = std::move(other.states_);
133 current_state_ = other.current_state_;
134 has_initial_state_ = other.has_initial_state_;
135 }
136
140 FSM& operator=(FSM&& other) noexcept {
141 if (this != &other) {
142#ifdef FSMGINE_MULTI_THREADED
143 std::unique_lock<std::mutex> lock(mutex_);
144 std::unique_lock<std::mutex> other_lock(other.mutex_);
145#endif
146 states_ = std::move(other.states_);
147 current_state_ = other.current_state_;
148 has_initial_state_ = other.has_initial_state_;
149 }
150 return *this;
151 }
152
162
167 void setInitialState(std::string_view state);
168
173 void setCurrentState(std::string_view state);
174
178 std::string_view getCurrentState() const;
179
186 bool process(const TEvent& event);
187
191 bool process() {
192 static_assert(std::is_same_v<TEvent, std::monostate>, "process() can only be used with event-less FSMs (FSM<> or FSM<std::monostate>).");
193 return process(std::monostate{});
194 }
195
196private:
197 // Friend declarations for builder access
198 friend class FSMBuilder<TEvent>;
199 friend class TransitionBuilder<TEvent>;
200
201 // Adds a transition from a state (internal use by builder)
202 void addTransition(std::string_view from_state, Transition<TEvent> transition);
203
204 // Adds an on-enter action to a state (internal use by builder)
205 void addOnEnterAction(std::string_view state, Action action);
206
207 // Adds an on-exit action to a state (internal use by builder)
208 void addOnExitAction(std::string_view state, Action action);
209
210 std::unordered_map<std::string_view, StateData> states_;
211 std::string_view current_state_;
212 bool has_initial_state_ = false;
213
214#ifdef FSMGINE_MULTI_THREADED
215 mutable std::mutex mutex_;
216#endif
217
218 // Helper methods
219 StateData& getOrCreateState(std::string_view state);
220 void executeOnExitActions(std::string_view state, const TEvent& event) const;
221 void executeOnEnterActions(std::string_view state, const TEvent& event) const;
222};
223
224// --- Implementation ---
225
226template<typename TEvent>
230
231template<typename TEvent>
232void FSM<TEvent>::setInitialState(std::string_view state) {
233#ifdef FSMGINE_MULTI_THREADED
234 std::unique_lock<std::mutex> lock(mutex_);
235#endif
236
237 // Optimization 1: Cache StringInterner reference
238 auto& interner = StringInterner::instance();
239 auto interned_state = interner.intern(state);
240
241 // Optimization 2: Single map lookup instead of redundant find
242 auto it = states_.find(interned_state);
243 if (it == states_.end()) {
244 // Optimization 3: Optimized exception string construction
245 std::string error_msg;
246 error_msg.reserve(50 + state.size());
247 error_msg.append("Cannot set initial state to undefined state: ");
248 error_msg.append(state);
249 throw FSMInvalidStateError(error_msg);
250 }
251
252 current_state_ = interned_state;
253 has_initial_state_ = true;
254
255 // Optimization 4: Static dummy event to avoid repeated object construction
256 static const TEvent dummy_event{};
257 executeOnEnterActions(current_state_, dummy_event);
258}
259
260template<typename TEvent>
261void FSM<TEvent>::setCurrentState(std::string_view state) {
262#ifdef FSMGINE_MULTI_THREADED
263 std::unique_lock<std::mutex> lock(mutex_);
264#endif
265
266 // Optimization 1: Cache StringInterner reference
267 auto& interner = StringInterner::instance();
268 auto interned_state = interner.intern(state);
269
270 // Optimization 2: Single map lookup instead of redundant find
271 auto it = states_.find(interned_state);
272 if (it == states_.end()) {
273 // Optimization 3: Optimized exception string construction
274 std::string error_msg;
275 error_msg.reserve(50 + state.size());
276 error_msg.append("Cannot set current state to undefined state: ");
277 error_msg.append(state);
278 throw FSMInvalidStateError(error_msg);
279 }
280
281 // Optimization 4: Static dummy event to avoid repeated object construction
282 static const TEvent dummy_event{};
283 if (has_initial_state_ && current_state_ != interned_state) {
284 executeOnExitActions(current_state_, dummy_event);
285 }
286
287 current_state_ = interned_state;
288 has_initial_state_ = true;
289
290 executeOnEnterActions(current_state_, dummy_event);
291}
292
293template<typename TEvent>
294bool FSM<TEvent>::process(const TEvent& event) {
295#ifdef FSMGINE_MULTI_THREADED
296 std::unique_lock<std::mutex> lock(mutex_);
297#endif
298
299 if (!has_initial_state_) {
301 }
302
303 auto it = states_.find(current_state_);
304 if (it == states_.end()) {
305 throw FSMStateNotFoundError(std::string(current_state_));
306 }
307
308 const auto& state_data = it->second;
309
310 for (const auto& transition : state_data.transitions) {
311 if (transition.predicatesPass(event)) {
312 auto target_state = transition.getTargetState();
313
314 if (target_state.empty()) {
315 throw FSMInvalidStateError("Transition has no target state");
316 }
317
318 // Optimization 1: Combine target state validation with lookup needed later
319 auto target_it = states_.find(target_state);
320 if (target_it == states_.end()) {
321 throw FSMStateNotFoundError(std::string(target_state));
322 }
323
324 transition.executeActions(event);
325
326 if (current_state_ != target_state) {
327 executeOnExitActions(current_state_, event);
328 current_state_ = target_state;
329 executeOnEnterActions(current_state_, event);
330 }
331
332 return true;
333 }
334 }
335
336 return false;
337}
338
339template<typename TEvent>
340std::string_view FSM<TEvent>::getCurrentState() const {
341#ifdef FSMGINE_MULTI_THREADED
342 std::unique_lock<std::mutex> lock(mutex_);
343#endif
344
345 if (!has_initial_state_) {
347 }
348
349 return current_state_;
350}
351
352template<typename TEvent>
353void FSM<TEvent>::addTransition(std::string_view from_state, Transition<TEvent> transition) {
354#ifdef FSMGINE_MULTI_THREADED
355 std::unique_lock<std::mutex> lock(mutex_);
356#endif
357
358 // Optimization 1: Cache StringInterner reference to avoid repeated singleton calls
359 auto& interner = StringInterner::instance();
360 auto interned_from_state = interner.intern(from_state);
361 auto& state_data = getOrCreateState(interned_from_state);
362
363 auto target_state = transition.getTargetState();
364 if (!target_state.empty()) {
365 auto interned_target_state = interner.intern(target_state);
366 getOrCreateState(interned_target_state);
367 }
368
369 state_data.transitions.push_back(std::move(transition));
370}
371
372template<typename TEvent>
373void FSM<TEvent>::addOnEnterAction(std::string_view state, Action action) {
374#ifdef FSMGINE_MULTI_THREADED
375 std::unique_lock<std::mutex> lock(mutex_);
376#endif
377
378 // Optimization 1: Cache StringInterner reference
379 auto& interner = StringInterner::instance();
380 auto interned_state = interner.intern(state);
381 auto& state_data = getOrCreateState(interned_state);
382
383 if (action) {
384 state_data.on_enter_actions.push_back(std::move(action));
385 }
386}
387
388template<typename TEvent>
389void FSM<TEvent>::addOnExitAction(std::string_view state, Action action) {
390#ifdef FSMGINE_MULTI_THREADED
391 std::unique_lock<std::mutex> lock(mutex_);
392#endif
393
394 // Optimization 1: Cache StringInterner reference
395 auto& interner = StringInterner::instance();
396 auto interned_state = interner.intern(state);
397 auto& state_data = getOrCreateState(interned_state);
398
399 if (action) {
400 state_data.on_exit_actions.push_back(std::move(action));
401 }
402}
403
404template<typename TEvent>
405typename FSM<TEvent>::StateData& FSM<TEvent>::getOrCreateState(std::string_view state) {
406 auto it = states_.find(state);
407 if (it == states_.end()) {
408 auto [inserted_it, success] = states_.emplace(state, StateData{});
409 return inserted_it->second;
410 }
411 return it->second;
412}
413
414template<typename TEvent>
415void FSM<TEvent>::executeOnExitActions(std::string_view state, const TEvent& event) const {
416 auto it = states_.find(state);
417 if (it != states_.end()) {
418 for (const auto& action : it->second.on_exit_actions) {
419 action(event);
420 }
421 }
422}
423
424template<typename TEvent>
425void FSM<TEvent>::executeOnEnterActions(std::string_view state, const TEvent& event) const {
426 auto it = states_.find(state);
427 if (it != states_.end()) {
428 for (const auto& action : it->second.on_enter_actions) {
429 action(event);
430 }
431 }
432}
433
434} // namespace fsmgine
String interning utility for memory-efficient state name storage.
State transition representation with guards and actions.
Main builder class for constructing FSMs with a fluent interface.
Definition FSMBuilder.hpp:112
Exception thrown for invalid state operations.
Definition FSM.hpp:53
FSMInvalidStateError(const std::string &message)
Constructs an exception for invalid state operations.
Definition FSM.hpp:57
Exception thrown when FSM operations are attempted before initialization.
Definition FSM.hpp:44
FSMNotInitializedError()
Constructs an exception for uninitialized FSM.
Definition FSM.hpp:47
Exception thrown when attempting to access a state that doesn't exist.
Definition FSM.hpp:34
FSMStateNotFoundError(const std::string &state)
Constructs an exception for a missing state.
Definition FSM.hpp:38
A high-performance finite state machine implementation.
Definition FSM.hpp:94
bool process(const TEvent &event)
Processes an event and potentially transitions to a new state.
Definition FSM.hpp:294
FSM()=default
Default constructor.
FSM & operator=(FSM &&other) noexcept
Move assignment operator.
Definition FSM.hpp:140
FSMBuilder< TEvent > get_builder()
Creates a builder for fluent FSM construction.
Definition FSM.hpp:227
std::string_view getCurrentState() const
Gets the name of the current state.
Definition FSM.hpp:340
std::function< void(const TEvent &)> Action
Type alias for transition actions.
Definition FSM.hpp:102
FSM(FSM &&other) noexcept
Move constructor.
Definition FSM.hpp:128
bool process()
Processes a transition for event-less FSMs.
Definition FSM.hpp:191
std::function< bool(const TEvent &)> Predicate
Type alias for transition predicates.
Definition FSM.hpp:98
void setCurrentState(std::string_view state)
Changes the current state of the FSM.
Definition FSM.hpp:261
void setInitialState(std::string_view state)
Sets the initial state of the FSM.
Definition FSM.hpp:232
static StringInterner & instance()
Gets the singleton instance of StringInterner.
Builder for creating transitions with a fluent interface.
Definition FSMBuilder.hpp:37
Represents a transition between states in a finite state machine.
Definition Transition.hpp:41
std::string_view getTargetState() const
Gets the target state for this transition.
Definition Transition.hpp:144
Main namespace for the FSMgine library.
Definition FSM.hpp:23