1//! @file fsm.hpp
2//! @author ryftchen
3//! @brief The declarations (fsm) in the utility module.
4//! @version 0.1.0
5//! @copyright Copyright (c) 2022-2025 ryftchen. All rights reserved.
6
7#pragma once
8
9#include <mutex>
10
11//! @brief The utility module.
12namespace utility // NOLINT(modernize-concat-nested-namespaces)
13{
14//! @brief Finite-state-machine-related functions in the utility module.
15namespace fsm
16{
17//! @brief Brief function description.
18//! @return function description (module_function)
19inline static const char* description() noexcept
20{
21 return "UTIL_FSM";
22}
23extern const char* version() noexcept;
24
25//! @brief Alias for the invoke result type.
26//! @tparam Func - type of callable function
27//! @tparam Args - type of function arguments
28template <typename Func, typename... Args>
29using InvokeResult = std::invoke_result_t<Func, Args...>;
30//! @brief Invoke callable.
31//! @tparam Func - type of callable function
32//! @tparam Args - type of function arguments
33//! @param func - callable function
34//! @param args - function arguments
35//! @return result from calls
36template <typename Func, typename... Args>
37constexpr InvokeResult<Func, Args...> invokeCallable(Func&& func, Args&&... args)
38{
39 return std::forward<Func>(func)(std::forward<Args>(args)...);
40}
41//! @brief Invoke callable. Multiple objects.
42//! @tparam Ret - type of return value
43//! @tparam T1 - type of class to which the function belongs
44//! @tparam T2 - type of object to which the function belongs
45//! @tparam Args - type of function arguments
46//! @param func - callable function
47//! @param obj - object to which the function belongs
48//! @param args - function arguments
49//! @return result from calls
50template <typename Ret, typename T1, typename T2, typename... Args>
51constexpr InvokeResult<Ret T1::*, T2, Args...> invokeCallable(Ret T1::* func, T2&& obj, Args&&... args)
52{
53 return (std::forward<T2>(obj).*func)(std::forward<Args>(args)...);
54}
55
56//! @brief Flexible invoke helper.
57//! @tparam Func - type of callable function
58//! @tparam Arg1 - type of function arguments
59//! @tparam Arg2 - type of function arguments
60//! @tparam isInvocable0 - flag to indicate the value when exclude both Arg1 and Arg2
61//! @tparam isInvocable1 - flag to indicate the value when include only Arg1
62//! @tparam isInvocable2 - flag to indicate the value when include only Arg2
63//! @tparam isInvocable12 - flag to indicate the value when include both Arg1 and Arg2
64template <
65 typename Func,
66 typename Arg1,
67 typename Arg2,
68 bool isInvocable0 = std::is_invocable_v<Func>,
69 bool isInvocable1 = std::is_invocable_v<Func, Arg1>,
70 bool isInvocable2 = std::is_invocable_v<Func, Arg2>,
71 bool isInvocable12 = std::is_invocable_v<Func, Arg1, Arg2>>
72struct FlexInvokeHelper;
73//! @brief Flexible invoke helper. Exclude both Arg1 and Arg2.
74//! @tparam Func - type of callable function
75//! @tparam Arg1 - type of function arguments
76//! @tparam Arg2 - type of function arguments
77template <typename Func, typename Arg1, typename Arg2>
78struct FlexInvokeHelper<Func, Arg1, Arg2, true, false, false, false>
79{
80 //! @brief Alias for the return type.
81 using Ret = InvokeResult<Func>;
82 //! @brief Invoke operation.
83 //! @param func - callable function
84 //! @return invoke result
85 static constexpr Ret invoke(Func&& func, [[maybe_unused]] Arg1&& /*arg1*/, [[maybe_unused]] Arg2&& /*arg2*/)
86 {
87 return invokeCallable(std::move(func));
88 }
89};
90//! @brief Flexible invoke helper. Include only Arg1.
91//! @tparam Func - type of callable function
92//! @tparam Arg1 - type of function arguments
93//! @tparam Arg2 - type of function arguments
94template <typename Func, typename Arg1, typename Arg2>
95struct FlexInvokeHelper<Func, Arg1, Arg2, false, true, false, false>
96{
97 //! @brief Alias for the return type.
98 using Ret = InvokeResult<Func, Arg1>;
99 //! @brief Invoke operation.
100 //! @param func - callable function
101 //! @param arg1 - function argument
102 //! @return invoke result
103 static constexpr Ret invoke(Func&& func, Arg1&& arg1, [[maybe_unused]] Arg2&& /*arg2*/)
104 {
105 return invokeCallable(std::move(func), std::move(arg1));
106 }
107};
108//! @brief Flexible invoke helper. Include only Arg2.
109//! @tparam Func - type of callable function
110//! @tparam Arg1 - type of function arguments
111//! @tparam Arg2 - type of function arguments
112template <typename Func, typename Arg1, typename Arg2>
113struct FlexInvokeHelper<Func, Arg1, Arg2, false, false, true, false>
114{
115 //! @brief Alias for the return type.
116 using Ret = InvokeResult<Func, Arg2>;
117 //! @brief Invoke operation.
118 //! @param func - callable function
119 //! @param arg2 - function arguments
120 //! @return invoke result
121 static constexpr Ret invoke(Func&& func, [[maybe_unused]] Arg1&& /*arg1*/, Arg2&& arg2)
122 {
123 return invokeCallable(std::move(func), std::move(arg2));
124 }
125};
126//! @brief Flexible invoke helper. Include both Arg1 and Arg2.
127//! @tparam Func - type of callable function
128//! @tparam Arg1 - type of function arguments
129//! @tparam Arg2 - type of function arguments
130template <typename Func, typename Arg1, typename Arg2>
131struct FlexInvokeHelper<Func, Arg1, Arg2, false, false, false, true>
132{
133 //! @brief Alias for the return type.
134 using Ret = InvokeResult<Func, Arg1, Arg2>;
135 //! @brief Invoke operation.
136 //! @param func - callable function
137 //! @param arg1 - function arguments
138 //! @param arg2 - function arguments
139 //! @return invoke result
140 static constexpr Ret invoke(Func&& func, Arg1&& arg1, Arg2&& arg2)
141 {
142 return invokeCallable(std::move(func), std::move(arg1), std::move(arg2));
143 }
144};
145
146//! @brief Alias for the adaptive invoke result type.
147//! @tparam Func - type of callable function
148//! @tparam Arg1 - type of function arguments
149//! @tparam Arg2 - type of function arguments
150template <typename Func, typename Arg1, typename Arg2>
151using AdaptInvokeResult = typename FlexInvokeHelper<Func, Arg1, Arg2>::Ret;
152//! @brief Adaptive invoke.
153//! @tparam Func - type of callable function
154//! @tparam Arg1 - type of function arguments
155//! @tparam Arg2 - type of function arguments
156//! @param func - callable function
157//! @param arg1 - function arguments
158//! @param arg2 - function arguments
159//! @return result from calls
160template <typename Func, typename Arg1, typename Arg2>
161constexpr AdaptInvokeResult<Func, Arg1, Arg2> adaptiveInvoke(Func&& func, Arg1&& arg1, Arg2&& arg2)
162{
163 return FlexInvokeHelper<Func, Arg1, Arg2>::invoke(
164 std::forward<Func>(func), std::forward<Arg1>(arg1), std::forward<Arg2>(arg2));
165}
166
167//! @brief The list of behaviors.
168//! @tparam Bs - type of behaviors
169template <typename... Bs>
170struct List;
171
172//! @brief Associate behaviors.
173//! @tparam Bs - type of behaviors
174template <typename... Bs>
175struct Concat;
176//! @brief Associate events with behaviors.
177//! @tparam B0 - type of current behavior
178//! @tparam Bs - type of behaviors
179template <typename B0, typename... Bs>
180struct Concat<B0, List<Bs...>>
181{
182 //! @brief Alias for the list.
183 using Type = List<B0, Bs...>;
184};
185//! @brief Associate.
186template <>
187struct Concat<>
188{
189 //! @brief Alias for the list.
190 using Type = List<>;
191};
192
193//! @brief The filter of events and behaviors.
194//! @tparam Predicate - type of predicate
195//! @tparam Bs - type of behaviors
196template <template <typename> class Predicate, typename... Bs>
197struct Filter;
198//! @brief The filter of events and behaviors. Based on conditions.
199//! @tparam Predicate - type of predicate
200//! @tparam B0 - type of current behavior
201//! @tparam Bs - type of behaviors
202template <template <typename> class Predicate, typename B0, typename... Bs>
203struct Filter<Predicate, B0, Bs...>
204{
205 //! @brief Alias for the concat or filter.
206 using Type = std::conditional_t<
207 Predicate<B0>::value,
208 typename Concat<B0, typename Filter<Predicate, Bs...>::Type>::Type,
209 typename Filter<Predicate, Bs...>::Type>;
210};
211//! @brief The filter of behaviors.
212//! @tparam Predicate - type of predicate
213template <template <typename> class Predicate>
214struct Filter<Predicate>
215{
216 //! @brief Alias for the list.
217 using Type = List<>;
218};
219
220//! @brief Finite state machine.
221//! @tparam Derived - type of derived class
222//! @tparam State - type of state
223template <typename Derived, typename State = int>
224class FSM
225{
226public:
227 //! @brief Alias for the state.
228 using StateType = State;
229 //! @brief Construct a new FSM object.
230 //! @param initState - initialization value of state
231 explicit FSM(const State initState = {}) : state{initState} {}
232
233 //! @brief Process the specific event.
234 //! @tparam Event - type of triggered event
235 //! @param event - event to be processed
236 template <typename Event>
237 void processEvent(const Event& event);
238 //! @brief Get current state.
239 //! @return State current state
240 State currentState() const;
241
242private:
243 //! @brief The base row of the transition table.
244 //! @tparam Source - source state
245 //! @tparam Event - type of triggered event
246 //! @tparam Target - target state
247 template <State Source, typename Event, State Target>
248 class RowBase
249 {
250 public:
251 //! @brief Alias for the state.
252 using StateType = State;
253 //! @brief Alias for the event.
254 using EventType = Event;
255 //! @brief Get source state.
256 //! @return source state
257 static constexpr StateType sourceState() { return Source; }
258 //! @brief Get target state.
259 //! @return target state
260 static constexpr StateType targetState() { return Target; }
261
262 protected:
263 //! @brief Process the specific event.
264 //! @tparam Action - type of action function
265 //! @param action - action function
266 //! @param self - derived object
267 //! @param event - event to be processed
268 template <typename Action>
269 static constexpr void processEvent(Action&& action, Derived& self, const Event& event)
270 {
271 adaptiveInvoke(std::forward<Action>(action), self, event);
272 }
273 //! @brief Process the specific event by default.
274 static constexpr void processEvent(const std::nullptr_t /*null*/, Derived& /*self*/, const Event& /*event*/) {}
275
276 //! @brief Check guard condition.
277 //! @tparam Guard - type of guard condition
278 //! @param guard - guard condition
279 //! @param self - derived object
280 //! @param event - event to be processed
281 //! @return pass or not
282 template <typename Guard>
283 static constexpr bool checkGuard(Guard&& guard, const Derived& self, const Event& event)
284 {
285 return adaptiveInvoke(std::forward<Guard>(guard), self, event);
286 }
287 //! @brief Check guard condition by default.
288 //! @return pass or not
289 static constexpr bool checkGuard(const std::nullptr_t /*null*/, const Derived& /*self*/, const Event& /*event*/)
290 {
291 return true;
292 }
293 };
294
295 //! @brief Classification by event type.
296 //! @tparam Event - type of triggered event
297 //! @tparam Bs - type of behaviors
298 template <typename Event, typename... Bs>
299 struct ByEvent;
300 //! @brief Classification by event type. Include both event and behaviors.
301 //! @tparam Event - type of triggered event
302 //! @tparam Bs - type of behaviors
303 template <typename Event, typename... Bs>
304 struct ByEvent<Event, List<Bs...>>
305 {
306 //! @brief Alias for the predicate.
307 //! @tparam Beh - type of behavior
308 template <typename Beh>
309 using Pred = std::is_same<typename Beh::EventType, Event>;
310 //! @brief Alias for the filter type.
311 using Type = typename Filter<Pred, Bs...>::Type;
312 };
313 //! @brief Classification by event type. Include only event.
314 //! @tparam Event - type of triggered event
315 template <typename Event>
316 struct ByEvent<Event, List<>>
317 {
318 //! @brief Alias for the list.
319 using Type = List<>;
320 };
321
322 //! @brief Handle the specific event.
323 //! @tparam Event - type of triggered event
324 //! @tparam Bs - type of behaviors
325 template <typename Event, typename... Bs>
326 struct EventHandler;
327 //! @brief Handle the specific event. Include both event and behaviors.
328 //! @tparam Event - type of triggered event
329 //! @tparam B0 - type of current behavior
330 //! @tparam Bs - type of behaviors
331 template <typename Event, typename B0, typename... Bs>
332 struct EventHandler<Event, List<B0, Bs...>>
333 {
334 //! @brief Execute handling.
335 //! @param self - derived object
336 //! @param event - event to be processed
337 //! @param state - source state
338 //! @return state after execute
339 static constexpr State execute(Derived& self, const Event& event, const State state)
340 {
341 return ((B0::sourceState() == state) && B0::checkGuard(self, event))
342 ? (B0::processEvent(self, event), B0::targetState())
343 : EventHandler<Event, List<Bs...>>::execute(self, event, state);
344 }
345 };
346 //! @brief Handle the specific event. Include only event.
347 //! @tparam Event - type of triggered event
348 template <typename Event>
349 struct EventHandler<Event, List<>>
350 {
351 //! @brief Execute handling. No transition.
352 //! @param self - derived object
353 //! @param event - event to be processed
354 //! @return state after execute
355 static constexpr State execute(Derived& self, const Event& event, const State /*state*/)
356 {
357 return self.noTransition(event);
358 }
359 };
360
361 //! @brief FSM state.
362 State state{};
363 //! @brief Mutex for controlling state.
364 mutable std::recursive_mutex mtx;
365
366protected:
367 //! @brief Alias for the transition table.
368 //! @tparam Rows - type of row-based
369 template <typename... Rows>
370 using Table = List<Rows...>;
371 //! @brief No transition can be found for the given event in its current state.
372 //! @tparam Event - type of triggered event
373 //! @param event - event to be processed
374 //! @return current state
375 template <typename Event>
376 State noTransition(const Event& event);
377
378 //! @brief Default row of the transition table.
379 //! @tparam Source - source state
380 //! @tparam Event - type of triggered event
381 //! @tparam Target - target state
382 //! @tparam Action - type of action function
383 //! @tparam action - action function
384 //! @tparam Guard - type of guard condition
385 //! @tparam guard - guard condition
386 template <
387 State Source,
388 typename Event,
389 State Target,
390 typename Action = std::nullptr_t,
391 Action action = nullptr,
392 typename Guard = std::nullptr_t,
393 Guard guard = nullptr>
394 class DefRow : public RowBase<Source, Event, Target>
395 {
396 public:
397 //! @brief Process the specific event.
398 //! @param self - derived object
399 //! @param event - event to be processed
400 static constexpr void processEvent(Derived& self, const Event& event)
401 {
402 RowBase<Source, Event, Target>::processEvent(action, self, event);
403 }
404 //! @brief Check guard condition.
405 //! @param self - derived object
406 //! @param event - event to be processed
407 //! @return pass or not
408 static constexpr bool checkGuard(const Derived& self, const Event& event)
409 {
410 return RowBase<Source, Event, Target>::checkGuard(guard, self, event);
411 }
412 };
413
414 //! @brief Member function row of the transition table.
415 //! @tparam Source - source state
416 //! @tparam Event - type of triggered event
417 //! @tparam Target - target state
418 //! @tparam action - action function
419 //! @tparam guard - guard condition
420 template <
421 State Source,
422 typename Event,
423 State Target,
424 void (Derived::*action)(const Event&) = nullptr,
425 bool (Derived::*guard)(const Event&) const = nullptr>
426 class MemFuncRow : public RowBase<Source, Event, Target>
427 {
428 public:
429 //! @brief Process the specific event.
430 //! @param self - derived object
431 //! @param event - event to be processed
432 static constexpr void processEvent(Derived& self, const Event& event)
433 {
434 if (action)
435 {
436 RowBase<Source, Event, Target>::processEvent(action, self, event);
437 }
438 }
439 //! @brief Check guard condition.
440 //! @param self - derived object
441 //! @param event - event to be processed
442 //! @return pass or not
443 static constexpr bool checkGuard(const Derived& self, const Event& event)
444 {
445 return !guard || RowBase<Source, Event, Target>::checkGuard(guard, self, event);
446 }
447 };
448
449 //! @brief The generic row of the transition table.
450 //! @tparam Source - source state
451 //! @tparam Event - type of triggered event
452 //! @tparam Target - target state
453 //! @tparam action - action function
454 //! @tparam guard - guard condition
455 template <State Source, typename Event, State Target, auto action = nullptr, auto guard = nullptr>
456 class Row : public RowBase<Source, Event, Target>
457 {
458 public:
459 //! @brief Process the specific event.
460 //! @param self - derived object
461 //! @param event - event to be processed
462 static constexpr void processEvent(Derived& self, const Event& event)
463 {
464 RowBase<Source, Event, Target>::processEvent(action, self, event);
465 }
466 //! @brief Check guard condition.
467 //! @param self - derived object
468 //! @param event - event to be processed
469 //! @return pass or not
470 static constexpr bool checkGuard(const Derived& self, const Event& event)
471 {
472 return RowBase<Source, Event, Target>::checkGuard(guard, self, event);
473 }
474 };
475};
476
477template <typename Derived, typename State>
478template <typename Event>
479void FSM<Derived, State>::processEvent(const Event& event)
480{
481 const std::lock_guard<std::recursive_mutex> lock(mtx);
482 using Rows = typename ByEvent<Event, typename Derived::TransitionTable>::Type;
483 static_assert(std::is_base_of_v<FSM, Derived>);
484 auto& self = static_cast<Derived&>(*this);
485 state = EventHandler<Event, Rows>::execute(self, event, state);
486}
487
488template <typename Derived, typename State>
489State FSM<Derived, State>::currentState() const
490{
491 const std::lock_guard<std::recursive_mutex> lock(mtx);
492 return state;
493}
494
495template <typename Derived, typename State>
496template <typename Event>
497State FSM<Derived, State>::noTransition(const Event& /*event*/)
498{
499 const std::lock_guard<std::recursive_mutex> lock(mtx);
500 return state;
501}
502} // namespace fsm
503} // namespace utility
504