#pragma once #include #include namespace mtl { /** * \brief FSM Event * * Base class each FSM event should be derived from. Derived classes are passed * to fsm.dispatch(). Empty unless members are added in the derived class. */ class fsm_event { private: public: }; /** * \brief FSM State * * Base class each FSM state interface should be derived from. A FSM's state * interface should provide an empty virtual overload of `react` for each event that is supported. * Then, each FSM state derives from this state interface and overrides the * event overloads as needed. * * FSMType = The FSM context type */ template class fsm_state { public: /** * Set in `fsm` constructor. We're not able to use a constructor with * signature `fsm_state(FSMType*)` because std::tuple does not support * passing to constructors (as far as I'm aware). */ FSMType* m_fsm = nullptr; fsm_state() {} virtual void react(const mtl::fsm_event& event) = 0; virtual void entry() {} virtual void exit() {} /* * \brief Changes the current state * * `exit` is called on the current state, the state is switched to the * desired state, and `entry` is called on the new state. * * Should not be used inside `entry` or `exit` because `entry` and `exit` * are called on the entered/exited states. */ template void change_state() { m_fsm->template change_state(); } }; // We need the state base class type for overriding of overloaded `react` /** * \brief FSM Context * * FSMType = CRTP derived class * StateType = state interface type * FSMStates = list of 1 or more valid states * * All memory is allocated on the stack where the FSM is instantiated. * * The FSM state is initialized to the first state in FSMStates. */ template class fsm { private: friend fsm_state; StateType* m_cur_state = nullptr; /* * TODO: Possibly use different tuple implementation? */ std::tuple m_states; /* * \brief Changes the current state * * NOTE: SHOULD NOT BE CALLED DIRECTLY. CALL FROM * `fsm_state::change_state` INSTEAD. * * `exit` is called on the current state, the state is switched to the * desired state, and `entry` is called on the new state. * * Should not be used inside `entry` or `exit` because `entry` and `exit` * are called on the entered/exited states. */ template void change_state() { m_cur_state->exit(); m_cur_state = &std::get(m_states); m_cur_state->entry(); } public: fsm() { // Set the FSM instance of each state using lambda+pack expansion std::apply([this](auto&... states){ // std::apply calls the lambda // Pack expansion sets m_fsm for each state ((states.m_fsm = static_cast(this)), ...); }, m_states); // Set the initial state to the first type in FSMStates m_cur_state = &std::get<0>(m_states); } template void dispatch(const T& event = T()) { // Use default arg to allow dispatch() m_cur_state->react(event); // with default constructor } }; } // namespace mtl