#pragma once /* MOUNT CONTROL COMPONENTS LIBRARY */ /* FINITE-STATE MACHINE IMPLEMENTATION */ #include #include #include #include #include #include #include #include #include #include "mcc_traits.h" namespace mcc::fsm { /* error codes enum definition */ enum class MccFiniteStateMachineErrorCode : int { ERROR_OK, ERROR_UNREGISTERED_EVENT_TYPE, ERROR_UNHANDLED_TRANSITION }; } // namespace mcc::fsm namespace std { template <> class is_error_code_enum : public true_type { }; } // namespace std namespace mcc::fsm { /* error category definition */ // error category struct MccFiniteStateMachineCategory : public std::error_category { MccFiniteStateMachineCategory() : std::error_category() {} const char* name() const noexcept { return "MCC-FSM-ERROR-CATEGORY"; } std::string message(int ec) const { MccFiniteStateMachineErrorCode err = static_cast(ec); switch (err) { case MccFiniteStateMachineErrorCode::ERROR_OK: return "OK"; case MccFiniteStateMachineErrorCode::ERROR_UNREGISTERED_EVENT_TYPE: return "unregistered event type"; case MccFiniteStateMachineErrorCode::ERROR_UNHANDLED_TRANSITION: return "unhandled transition"; default: return "UNKNOWN"; } } static const MccFiniteStateMachineCategory& get() { static const MccFiniteStateMachineCategory constInst; return constInst; } }; inline std::error_code make_error_code(MccFiniteStateMachineErrorCode ec) { return std::error_code(static_cast(ec), MccFiniteStateMachineCategory::get()); } namespace traits { /* The only requirement to Event-class is public-accepted static constant 'ID' */ template concept fsm_event_c = requires { requires std::same_as; }; /* The only requirements to State-class is public-accepted static constant 'ID' and definition of type transition_t */ template concept fsm_state_c = std::is_default_constructible_v && requires { requires std::same_as; typename T::transition_t; }; // concept for std::pair template concept fsm_pair_of_types_c = requires { [](std::type_identity>) {}(std::type_identity()); }; template concept fsm_tuple_of_pairs_c = requires { [](std::type_identity>) {}(std::type_identity()); }; template concept fsm_tuple_of_events_c = requires { [](std::type_identity>) {}(std::type_identity()); }; } // namespace traits /* * Event-to-State transition table definition * (I do not use here concepts from the above traits to avoid possible recursive concept problem) */ template struct fsm_transition_table_t; template struct fsm_transition_table_t { using events_t = std::tuple; using event_state_pair_t = std::tuple; using unique_states_t = std::tuple; template using find_state_by_event_t = std::conditional_t, typename PT::second_type, void>; }; template struct fsm_transition_table_t { private: static constexpr bool non_unique_event = (std::same_as || ...); public: using events_t = std::conditional_t::events_t, decltype(std::tuple_cat(std::declval>(), std::declval::events_t>()))>; using event_state_pair_t = std::conditional_t::event_state_pair_t, decltype(std::tuple_cat( std::declval>(), std::declval::event_state_pair_t>()))>; using unique_states_t = std::conditional_t< non_unique_event, typename fsm_transition_table_t::unique_states_t, std::conditional_t<(std::same_as || ...), typename fsm_transition_table_t::unique_states_t, decltype(std::tuple_cat( std::declval>(), std::declval::unique_states_t>()))>>; private: template struct find_state_by_event; template struct find_state_by_event>> { using state_t = std::conditional_t, StT, void>; }; template struct find_state_by_event, PairTs...>> { using state_t = std::conditional_t, StT, typename find_state_by_event>::state_t>; }; public: // template template using find_state_by_event_t = typename find_state_by_event::state_t; }; /* * Finite-state machine definition * (an idea is from https://codeberg.org/cmargiotta/compile-time-fsm) */ class MccFiniteStateMachine { protected: /* helper types definition */ // merge N std::tuple types with filtering dublicates // (NOTE: the first std::tuple must contain unique types!!!) template struct merge_tuples; template struct merge_tuples { using result_t = TplT; }; template struct merge_tuples { using result_t = TplT1; }; template struct merge_tuples, std::tuple> : std::conditional_t<(std::same_as || ...), merge_tuples, std::tuple>, merge_tuples, std::tuple>> { }; template struct merge_tuples : merge_tuples::result_t, TplT3, TplTs...> { }; template using merge_tuples_t = typename merge_tuples::result_t; // deduce all unique states from the initial one template struct deduce_states; template struct deduce_states { using states_t = ResTplT; }; template struct deduce_states, InTplTs...> { using curr_collection_t = merge_tuples_t>; // using curr_collection_t = // merge_tuples_t, typename StTs::transition_t::unique_states_t...>; using states_t = typename deduce_states == std::tuple_size_v, curr_collection_t, merge_tuples_t, // typename StTs::transition_t::unique_states_t..., InTplTs...>::states_t; }; template using deduce_states_t = typename deduce_states, std::tuple>::states_t; // deduce all unique events from the initial state transition table template struct deduce_events; template struct deduce_events { using events_t = ResTplT; }; template struct deduce_events> { using curr_collection_t = merge_tuples_t; using events_t = typename deduce_events == std::tuple_size_v, curr_collection_t, merge_tuples_t>::events_t; }; template using deduce_events_t = typename deduce_events, std::tuple>::events_t; template struct variant_from_tuple; template struct variant_from_tuple> { using variant_t = std::variant; }; template using variant_from_tuple_t = typename variant_from_tuple::variant_t; // check if given event-type is in std::tuple of event-types template struct in_tuple; template struct in_tuple> : std::disjunction...> { }; template static constexpr bool in_tuple_v = in_tuple::value; template inline static std::unordered_map> _dispatchEventFunc{}; std::vector> _moveFunc{}; std::vector> _copyFunc{}; std::vector> _destroyFunc{}; std::string_view _currentStateID; std::vector _stateID{}; std::vector _eventID{}; std::recursive_mutex _transitionMutex{}; std::future _currentStateThreadFuture; static MccFiniteStateMachine& copyInstance(const MccFiniteStateMachine* from, MccFiniteStateMachine* to) { if (from != to) { for (auto& func : from->_copyFunc) { func(from, to); } to->_currentStateID = from->_currentStateID; to->_moveFunc = from->_moveFunc; to->_copyFunc = from->_copyFunc; to->_destroyFunc = from->_destroyFunc; to->_stateID = from->_stateID; to->_eventID = from->_eventID; to->_currentStateID = from->_currentStateID; } return *to; } static MccFiniteStateMachine& moveInstance(MccFiniteStateMachine* from, MccFiniteStateMachine* to) { if (from != to) { for (auto& func : from->_moveFunc) { func(from, to); } to->_currentStateID = std::move(from->_currentStateID); to->_moveFunc = std::move(from->_moveFunc); to->_copyFunc = std::move(from->_copyFunc); to->_destroyFunc = std::move(from->_destroyFunc); to->_stateID = std::move(from->_stateID); to->_eventID = std::move(from->_eventID); to->_currentStateID = std::move(from->_currentStateID); } return *to; } public: template constexpr MccFiniteStateMachine(InitStateT) : _currentStateID(InitStateT::ID) { using states_t = deduce_states_t; auto states = std::make_shared(); auto currentState = std::make_shared>(); *currentState = &std::get(*states); _stateID = [](std::tuple&) { return std::vector({STs::ID...}); }(*states); // setup dispatch event functions using all_events_t = deduce_events_t; [states, currentState, this](std::index_sequence) { ((_dispatchEventFunc>[this] = [states, currentState, this](EvT& event) { // to avoid effects of possible compiler optimizations // (here one needs to be sure that inside the lambda 'event' is used by reference) const auto p_event = &event; std::visit( [p_event, states, currentState, this](curr_state_t*) { using to_state_t = curr_state_t::transition_t::template find_state_by_event_t; if constexpr (!std::is_void_v) { std::lock_guard lock(_transitionMutex); // exit from current if constexpr (requires(curr_state_t inst) { { inst.exit(std::declval()) }; }) { std::get(*states).exit(*p_event); } else if constexpr (requires(curr_state_t inst) { { inst.exit() }; }) { std::get(*states).exit(); } // transit ... if constexpr (requires(EvT inst) { { inst.onTransit() }; }) { p_event->onTransit(); } *currentState = &std::get(*states); _currentStateID = to_state_t::ID; // enter to new if constexpr (requires(to_state_t inst) { { inst.enter(std::declval()) }; }) { std::get(*states).enter(*p_event); } else if constexpr (requires(to_state_t inst) { { inst.enter() }; }) { std::get(*states).enter(); } } else { throw std::system_error(MccFiniteStateMachineErrorCode::ERROR_UNHANDLED_TRANSITION); } }, *currentState); }), ...); (_moveFunc.emplace_back([](MccFiniteStateMachine* from, MccFiniteStateMachine* to) { _dispatchEventFunc>[to] = std::move(_dispatchEventFunc>[from]); }), ...); (_copyFunc.emplace_back([](const MccFiniteStateMachine* from, MccFiniteStateMachine* to) { _dispatchEventFunc>[to] = _dispatchEventFunc>[from]; }), ...); (_destroyFunc.emplace_back([](const MccFiniteStateMachine* inst) { _dispatchEventFunc>.erase(inst); }), ...); (_eventID.emplace_back(std::tuple_element_t::ID), ...); }(std::make_index_sequence>()); // call enter() method (if it exists) of the initial state std::visit( [](curr_state_t* cstate) { if constexpr (requires(curr_state_t inst) { { inst.enter() }; }) { cstate->enter(); } }, *currentState); } MccFiniteStateMachine(const MccFiniteStateMachine& other) { copyInstance(&other, this); } MccFiniteStateMachine(MccFiniteStateMachine&& other) { moveInstance(&other, this); } MccFiniteStateMachine& operator=(const MccFiniteStateMachine& other) { return copyInstance(&other, this); } MccFiniteStateMachine& operator=(MccFiniteStateMachine&& other) { return moveInstance(&other, this); } virtual ~MccFiniteStateMachine() { for (auto& func : _destroyFunc) { func(this); } } template auto dispatchEvent(EvT& event) { if (!_dispatchEventFunc[this]) { throw std::system_error(MccFiniteStateMachineErrorCode::ERROR_UNREGISTERED_EVENT_TYPE); } _dispatchEventFunc[this](event); } template auto dispatchEvent(EvT&& event) { return dispatchEvent(event); } template auto dispatchEvent() requires std::default_initializable { return dispatchEvent(EvT{}); } std::string_view currentStateID() const { return _currentStateID; } // returns IDs of all deduced unique states template R stateIDs() const { R r; for (auto& el : _stateID) { std::back_inserter(r) = {el.begin(), el.end()}; } return r; } std::vector stateIDs() const { return stateIDs>(); } // returns IDs of all deduced events template R eventIDs() const { R r; for (auto& el : _eventID) { std::back_inserter(r) = {el.begin(), el.end()}; } return r; } std::vector eventIDs() const { return eventIDs>(); } }; } // namespace mcc::fsm