// Copyright 2025 Christian Granzin // Copyright 2008 Christophe Henry // henry UNDERSCORE christophe AT hotmail DOT com // This is an extended version of the state machine available in the boost::mpl library // Distributed under the same license as the original. // Copyright for the original version: // Copyright 2005 David Abrahams and Aleksey Gurtovoy. Distributed // under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_MSM_BACKMP11_STATE_MACHINE_H #define BOOST_MSM_BACKMP11_STATE_MACHINE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace msm { namespace backmp11 { // Check whether a state is a composite state. using detail::is_composite; namespace detail { constexpr bool is_valid(visit_mode mode) { constexpr uint8_t state_mask = 0b011; const uint8_t state_bits = static_cast(mode) & state_mask; return state_bits == 0b001 || state_bits == 0b010; } template < class FrontEnd, class Config, class Derived > class state_machine_base : public FrontEnd { static_assert( is_composite::value, "FrontEnd must be a composite state"); static_assert( is_config::value, "Config must be an instance of state machine config"); public: using config_t = Config; using root_sm_t = typename config_t::root_sm; using context_t = typename config_t::context; using front_end_t = FrontEnd; using derived_t = Derived; using events_queue_t = typename config_t::template queue_container>; // Event that describes the SM is starting. // Used when the front-end does not define an initial_event. struct starting {}; // Event that describes the SM is stopping. // Used when the front-end does not define a final_event. struct stopping {}; template struct exit_pt : public ExitPoint { // tags typedef ExitPoint wrapped_exit; typedef int pseudo_exit; typedef derived_t owner; typedef int no_automatic_create; typedef typename ExitPoint::event Event; typedef std::function forward_function; // forward event to the higher-level FSM template void forward_event(ForwardEvent const& incomingEvent) { // use helper to forward or not ForwardHelper< ::boost::is_convertible::value>::helper(incomingEvent,m_forward); } void set_forward_fct(forward_function fct) { m_forward = fct; } exit_pt():m_forward(){} // by assignments, we keep our forwarding functor unchanged as our containing SM did not change template exit_pt(RHS&):m_forward(){} exit_pt& operator= (const exit_pt& ) { return *this; } private: forward_function m_forward; // using partial specialization instead of enable_if because of VC8 bug template struct ForwardHelper { template static void helper(ForwardEvent const& ,forward_function& ) { // Not our event, assert BOOST_ASSERT(false); } }; template struct ForwardHelper { template static void helper(ForwardEvent const& incomingEvent,forward_function& forward_fct) { // call if handler set, if not, this state is simply a terminate state if (forward_fct) forward_fct(incomingEvent); } }; }; template struct entry_pt : public EntryPoint { // tags typedef EntryPoint wrapped_entry; typedef int pseudo_entry; typedef derived_t owner; typedef int no_automatic_create; }; template struct direct : public EntryPoint { // tags typedef EntryPoint wrapped_entry; typedef int explicit_entry_state; typedef derived_t owner; typedef int no_automatic_create; }; struct internal { using tag = back_end_tag; using initial_states = to_mp_list_t; static constexpr int nr_regions = mp11::mp_size::value; template struct make_entry { using type = State; }; template struct make_entry::value>> { using type = entry_pt; }; template struct make_entry::value>> { using type = direct; }; template struct make_exit { using type = State; }; template struct make_exit::value>> { using type = exit_pt; }; template static bool call_guard_or_true(state_machine_base& sm, const Event& event, Source& source, Target& target) { if constexpr (HasGuard) { return Row::guard_call(sm.get_fsm_argument(), event, source, target, sm.m_states); } else { return true; } } template static process_result call_action_or_true(state_machine_base& sm, const Event& event, Source& source, Target& target) { if constexpr (HasAction) { return Row::action_call(sm.get_fsm_argument(), event, source, target, sm.m_states); } else { return process_result::HANDLED_TRUE; } } // Template used to create transitions from rows in the transition table // (normal transitions). template struct Transition { typedef typename Row::Evt transition_event; typedef typename make_entry::type T1; // if the source is an exit pseudo state, then // current_state_type becomes the result of get_owner // meaning the containing SM from which the exit occurs typedef typename ::boost::mpl::eval_if< typename has_pseudo_exit::type, get_owner, ::boost::mpl::identity >::type current_state_type; typedef typename make_exit::type T2; // if Target is a sequence, then we have a fork and expect a sequence of explicit_entry // else if Target is an explicit_entry, next_state_type becomes the result of get_owner // meaning the containing SM if the row is "outside" the containing SM or else the explicit_entry state itself typedef typename ::boost::mpl::eval_if< typename ::boost::mpl::is_sequence::type, get_fork_owner, ::boost::mpl::eval_if< typename has_no_automatic_create::type, get_owner, ::boost::mpl::identity > >::type next_state_type; // Take the transition action and return the next state. static process_result execute(state_machine_base& sm, int region_index, int state, transition_event const& event) { BOOST_STATIC_CONSTANT(int, current_state = (get_state_id())); BOOST_STATIC_CONSTANT(int, next_state = (get_state_id())); boost::ignore_unused(state); // Avoid warnings if BOOST_ASSERT expands to nothing. BOOST_ASSERT(state == (current_state)); // if T1 is an exit pseudo state, then take the transition only if the pseudo exit state is active if (has_pseudo_exit::type::value && !sm.is_exit_state_active>()) { return process_result::HANDLED_FALSE; } auto& source = sm.get_state(); auto& target = sm.get_state(); if (!call_guard_or_true(sm, event, source, target)) { // guard rejected the event, we stay in the current one return process_result::HANDLED_GUARD_REJECT; } sm.m_active_state_ids[region_index] = active_state_switching::after_guard(current_state,next_state); // first call the exit method of the current state source.on_exit(event, sm.get_fsm_argument()); sm.m_active_state_ids[region_index] = active_state_switching::after_exit(current_state,next_state); // then call the action method process_result res = call_action_or_true(sm, event, source, target); sm.m_active_state_ids[region_index] = active_state_switching::after_action(current_state,next_state); // and finally the entry method of the new current state convert_event_and_execute_entry(target,event,sm); sm.m_active_state_ids[region_index] = active_state_switching::after_entry(current_state,next_state); return res; } }; // Template used to create transitions from rows in the transition table // (internal transitions). template struct InternalTransition { typedef typename Row::Evt transition_event; typedef State current_state_type; typedef current_state_type next_state_type; // Take the transition action and return the next state. static process_result execute(state_machine_base& sm, int , int state, transition_event const& event) { BOOST_STATIC_CONSTANT(int, current_state = (get_state_id())); boost::ignore_unused(state, current_state); // Avoid warnings if BOOST_ASSERT expands to nothing. BOOST_ASSERT(state == (current_state)); auto& source = sm.get_state(); auto& target = source; if (!call_guard_or_true(sm, event, source, target)) { // guard rejected the event, we stay in the current one return process_result::HANDLED_GUARD_REJECT; } // call the action method return call_action_or_true(sm, event, source, target); } }; template struct create_backend_stt; template struct create_backend_stt { using type = Transition; }; template struct create_backend_stt { using type = Transition; }; template struct create_backend_stt<_row_tag,Row,State> { using type = Transition; }; template struct create_backend_stt { using type = Transition; }; template struct create_backend_stt { using type = InternalTransition; }; template struct create_backend_stt { using type = InternalTransition; }; template struct create_backend_stt { using type = InternalTransition; }; template struct create_backend_stt<_irow_tag,Row,State> { using type = InternalTransition; }; template struct create_backend_stt { using type = InternalTransition; }; template struct create_backend_stt { using type = InternalTransition; }; template struct create_backend_stt { using type = InternalTransition; }; template struct create_backend_stt { using type = InternalTransition; }; template struct transition_cell { using type = typename create_backend_stt::type; }; // add to the stt the initial states which could be missing (if not being involved in a transition) template struct create_real_stt { template using get_transition_cell = typename transition_cell::type; typedef typename boost::mp11::mp_transform< get_transition_cell, to_mp_list_t > type; }; using stt = typename get_transition_table::type; using state_set = typename generate_state_set::state_set; }; typedef mp11::mp_rename states_t; private: using stt = typename internal::stt; using state_set = typename internal::state_set; static constexpr int nr_regions = internal::nr_regions; using active_state_ids_t = std::array; using initial_states_identity = mp11::mp_transform; using compile_policy = typename config_t::compile_policy; using compile_policy_impl = detail::compile_policy_impl; template using transition_cell = typename internal::template transition_cell; template using create_real_stt = typename internal::template create_real_stt; template using get_active_state_switch_policy = typename T::active_state_switch_policy; using active_state_switching = boost::mp11::mp_eval_or; typedef bool (*flag_handler)(state_machine_base const &); // all state machines are friend with each other to allow embedding any of them in another fsm template friend class state_machine_base; template friend struct detail::compile_policy_impl; // Allow access to private members for serialization. // WARNING: // No guarantee is given on the private member layout. // Future changes may break existing serializer implementations. template friend void serialize(T&, state_machine_base&); template using get_initial_event = typename T::initial_event; using fsm_initial_event = boost::mp11::mp_eval_or; template using get_final_event = typename T::final_event; using fsm_final_event = boost::mp11::mp_eval_or; // Template used to form forwarding rows in the transition table for every row of a composite SM template struct frow { typedef T1 current_state_type; typedef T1 next_state_type; typedef Evt transition_event; // tag to find out if a row is a forwarding row typedef int is_frow; // Take the transition action and return the next state. static process_result execute(state_machine_base& sm, int region_index, int , transition_event const& event) { // false as second parameter because this event is forwarded from outer fsm process_result res = (sm.get_state()).process_event_internal(event); sm.m_active_state_ids[region_index]=get_state_id(); return res; } // helper metafunctions used by dispatch table and give the frow a new event // (used to avoid double entries in a table because of base events) template struct replace_event { typedef frow type; }; }; template struct add_forwarding_row_helper { typedef typename generate_event_set::event_set_mp11 all_events; template using frow_state_type = frow; typedef mp11::mp_append< to_mp_list_t, mp11::mp_transform > type; }; // gets the transition table from a composite and make from it a forwarding row template struct get_internal_transition_table { // first get the table of a composite typedef typename recursive_get_transition_table::type original_table; // we now look for the events the composite has in its internal transitions // the internal ones are searched recursively in sub-sub... states // we go recursively because our states can also have internal tables or substates etc. typedef typename recursive_get_internal_transition_table::type recursive_istt; template using get_transition_cell = typename transition_cell::type; typedef boost::mp11::mp_transform< get_transition_cell, to_mp_list_t > recursive_istt_with_tag; typedef boost::mp11::mp_append table_with_all_events; // and add for every event a forwarding row typedef typename ::boost::mpl::eval_if< typename compile_policy_impl::add_forwarding_rows, add_forwarding_row_helper,State>, ::boost::mpl::identity< mp11::mp_list<> > >::type type; }; template struct get_internal_transition_table { typedef typename create_real_stt::type type; }; typedef typename generate_state_map::type state_map_mp11; typedef typename generate_event_set::event_set_mp11 event_set_mp11; typedef history_impl concrete_history; typedef typename generate_event_set< typename create_real_stt::type >::event_set_mp11 processable_events_internal_table; // extends the transition table with rows from composite states template struct extend_table { // add the init states //typedef typename get_transition_table::type stt; typedef typename Composite::stt Stt; // add the internal events defined in the internal_transition_table // Note: these are added first because they must have a lesser prio // than the deeper transitions in the sub regions // table made of a stt + internal transitions of composite template using get_transition_cell = typename transition_cell::type; typedef typename boost::mp11::mp_transform< get_transition_cell, to_mp_list_t > internal_stt; typedef boost::mp11::mp_append< to_mp_list_t, internal_stt > stt_plus_internal; // for every state, add its transition table (if any) // transformed as frow template using F = boost::mp11::mp_append< V, typename get_internal_transition_table::value>::type >; typedef boost::mp11::mp_fold< state_set, stt_plus_internal, F > type; }; // extend the table with tables from composite states typedef typename extend_table::type complete_table; // define the dispatch table used for event dispatch using sm_dispatch_table = typename compile_policy_impl::template dispatch_table; struct deferred_event_t { std::function process_event; std::function is_event_deferred; // Deferred events are added with a correlation sequence that helps to // identify when an event was added. // Newly deferred events will not be considered for procesing // within the same sequence. size_t seq_cnt; }; using deferred_events_queue_t = std::list; struct deferred_events_t { deferred_events_queue_t queue; size_t cur_seq_cnt; }; using has_any_deferred_event = mp11::mp_any_of; using deferred_events_member = optional_instance::value>; using events_queue_member = optional_instance::value>; using context_member = optional_instance && (std::is_same_v || std::is_same_v)>; template > deferred_events_t& get_deferred_events() { return m_optional_members.template get(); } template > const deferred_events_t& get_deferred_events() const { return m_optional_members.template get(); } template bool is_event_deferred(const Event& event) const { return compile_policy_impl::is_event_deferred( *const_cast(this), event); } // Visit states with a compile-time filter (reduces template instantiations). template