| 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. |
| 12 | namespace utility // NOLINT(modernize-concat-nested-namespaces) |
| 13 | { |
| 14 | //! @brief Finite-state-machine-related functions in the utility module. |
| 15 | namespace fsm |
| 16 | { |
| 17 | //! @brief Brief function description. |
| 18 | //! @return function description (module_function) |
| 19 | inline static const char* description() noexcept |
| 20 | { |
| 21 | return "UTIL_FSM" ; |
| 22 | } |
| 23 | extern 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 |
| 28 | template <typename Func, typename... Args> |
| 29 | using 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 |
| 36 | template <typename Func, typename... Args> |
| 37 | constexpr 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 |
| 50 | template <typename Ret, typename T1, typename T2, typename... Args> |
| 51 | constexpr 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 |
| 64 | template < |
| 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>> |
| 72 | struct 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 |
| 77 | template <typename Func, typename Arg1, typename Arg2> |
| 78 | struct 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 |
| 94 | template <typename Func, typename Arg1, typename Arg2> |
| 95 | struct 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 |
| 112 | template <typename Func, typename Arg1, typename Arg2> |
| 113 | struct 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 |
| 130 | template <typename Func, typename Arg1, typename Arg2> |
| 131 | struct 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 |
| 150 | template <typename Func, typename Arg1, typename Arg2> |
| 151 | using 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 |
| 160 | template <typename Func, typename Arg1, typename Arg2> |
| 161 | constexpr 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 |
| 169 | template <typename... Bs> |
| 170 | struct List; |
| 171 | |
| 172 | //! @brief Associate behaviors. |
| 173 | //! @tparam Bs - type of behaviors |
| 174 | template <typename... Bs> |
| 175 | struct Concat; |
| 176 | //! @brief Associate events with behaviors. |
| 177 | //! @tparam B0 - type of current behavior |
| 178 | //! @tparam Bs - type of behaviors |
| 179 | template <typename B0, typename... Bs> |
| 180 | struct Concat<B0, List<Bs...>> |
| 181 | { |
| 182 | //! @brief Alias for the list. |
| 183 | using Type = List<B0, Bs...>; |
| 184 | }; |
| 185 | //! @brief Associate. |
| 186 | template <> |
| 187 | struct 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 |
| 196 | template <template <typename> class Predicate, typename... Bs> |
| 197 | struct 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 |
| 202 | template <template <typename> class Predicate, typename B0, typename... Bs> |
| 203 | struct 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 |
| 213 | template <template <typename> class Predicate> |
| 214 | struct 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 |
| 223 | template <typename Derived, typename State = int> |
| 224 | class FSM |
| 225 | { |
| 226 | public: |
| 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 | |
| 242 | private: |
| 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 | |
| 366 | protected: |
| 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 | |
| 477 | template <typename Derived, typename State> |
| 478 | template <typename Event> |
| 479 | void 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 | |
| 488 | template <typename Derived, typename State> |
| 489 | State FSM<Derived, State>::currentState() const |
| 490 | { |
| 491 | const std::lock_guard<std::recursive_mutex> lock(mtx); |
| 492 | return state; |
| 493 | } |
| 494 | |
| 495 | template <typename Derived, typename State> |
| 496 | template <typename Event> |
| 497 | State 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 | |