diff --git a/src/shared/events/Event.h b/src/shared/events/Event.h index c38ec3ef83a50bbbe86428bbb8f7d9e89a363427..62a2ab6a86907ce8b5d1d1f97ae2b0bc29e0a542 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 f4ed570643450d16616eafcd84c575b02b6de859..35a3626beabe70990d4d6978b1e096c6a55ddc28 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