From 751d7a57371912a055cda583d4b6e4907a6ea47a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Betto?= <niccolo.betto@skywarder.eu> Date: Sun, 19 Jan 2025 22:01:19 +0100 Subject: [PATCH] [FSM] Introduce async continuation support This commit introduces 'ASYNC_' macros that allow async-like syntax to be used for waiting on delayed events, allowing a linear implementation of the code as if it was synchronous. --- src/shared/events/Event.h | 11 ++--- src/shared/events/FSM.h | 90 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/src/shared/events/Event.h b/src/shared/events/Event.h index c38ec3ef8..62a2ab6a8 100644 --- a/src/shared/events/Event.h +++ b/src/shared/events/Event.h @@ -31,11 +31,12 @@ typedef uint8_t Event; enum BasicEvent : Event { - EV_ENTRY = 0, - EV_EXIT = 1, - EV_EMPTY = 2, - EV_INIT = 3, - EV_FIRST_CUSTOM = 4 + EV_ENTRY = 0, + EV_EXIT = 1, + EV_EMPTY = 2, + EV_INIT = 3, + EV_ASYNC_CONTINUE = 4, // Async continuation support + EV_FIRST_CUSTOM = 5 }; /** diff --git a/src/shared/events/FSM.h b/src/shared/events/FSM.h index f4ed57064..35a3626be 100644 --- a/src/shared/events/FSM.h +++ b/src/shared/events/FSM.h @@ -56,6 +56,11 @@ protected: private: void (T::*state)(const Event&); Event specialEvent; + +protected: + // Async continuation support + uint16_t asyncDelayedEventId; + uint32_t asyncContinuation = 0; }; template <class T> @@ -93,4 +98,89 @@ void FSM<T>::handleEvent(const Event& e) (static_cast<T*>(this)->*state)(e); } +/** + * @brief Asynchronous continuation support. + * + * The following macros provide support for asynchronous continuation inside a + * single state handler. This allows to write code that will be executed + * asynchronously just like synchronous code, without the need to split the + * logic into multiple states or switch-case branches. + * + * This enables stateful algorithms to be written in a more linear and readable + * way. + * + * @example + * void MyFSM::state_myState(const Event& event) { + * ASYNC_BEGIN(TOPIC_MY_FSM); + * ...some operation... + * ASYNC_WAIT_FOR(1000); + * + * // Code executed after 1 second + * ...some other operation... + * ASYNC_WAIT_FOR(2000); + * + * // Code executed after 2 seconds + * ...some other operation... + * ASYNC_END(); + * } + */ + +/** + * @brief Begins an asynchronous continuation context. + * + * This macro defines the beginning of a region of code that will be executed + * asynchronously. When a wait point is reached, the FSM will return from the + * state handler and resume from the waiting point at a later time. + * + * @note This macro must be followed by an ASYNC_END() macro. + * @note Local variables are not preserved between wait points, so they must be + * declared as class members. + * @note All macros assume that the name of the event parameter is "event". + * + * @param _topic Topic to use for the async events. Must be unique for each FSM. + */ +#define ASYNC_BEGIN(_topic) \ + constexpr auto _ASYNC_TOPIC = _topic; \ + /* Reset async continuation if entering the state */ \ + if (event == EV_ENTRY) \ + asyncContinuation = 0; \ + /* Remove any pending async event if exiting the state */ \ + if (event == EV_EXIT) \ + EventBroker::getInstance().removeDelayed(asyncDelayedEventId); \ + \ + if (event == EV_ASYNC_CONTINUE || event == EV_ENTRY) \ + { \ + switch (asyncContinuation) \ + { \ + case 0: + +/** + * @brief Ends an asynchronous continuation context. + * + * This macro defines the end of an asynchronous continuation context. + */ +#define ASYNC_END() \ + } /* switch */ \ + return; \ + } /* if */ + +/** + * @brief Waits for the specified amount of time before continuing execution. + * + * This macro defines a wait point in an asynchronous continuation context. + * When this point is reached, the FSM will post a delayed EV_ASYNC_CONTINUE + * event of the specified time and return from the state handler. When the + * delayed event is finally received, the FSM will resume execution from the + * waiting point. + */ +#define ASYNC_WAIT_FOR(t) \ + case __LINE__: \ + if (asyncContinuation != __LINE__) \ + { \ + asyncContinuation = __LINE__; \ + asyncDelayedEventId = EventBroker::getInstance().postDelayed( \ + EV_ASYNC_CONTINUE, _ASYNC_TOPIC, t); \ + return; \ + } + } // namespace Boardcore -- GitLab