From 108de8bb8564b5fffc012acd181ba6fc4511dae0 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Tue, 27 Jan 2026 16:02:27 -0500 Subject: [PATCH] refactor(backmp11): entry/exit logic --- .../pages/tutorial/backmp11-back-end.adoc | 4 +- doc/modules/ROOT/pages/version-history.adoc | 8 +- .../msm/backmp11/detail/metafunctions.hpp | 13 - .../msm/backmp11/detail/transition_table.hpp | 112 ++++-- .../boost/msm/backmp11/favor_compile_time.hpp | 16 +- include/boost/msm/backmp11/state_machine.hpp | 339 ++++++++---------- 6 files changed, 264 insertions(+), 228 deletions(-) diff --git a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc index d8c4349..2777680 100644 --- a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc @@ -34,8 +34,8 @@ It offers a significant reduction in compilation time and RAM usage, as can be s | | Compile / sec | RAM / MB | Runtime / sec | back | 68 | 2849 | 23 | back_favor_compile_time | 80 | 2551 | 261 -| backmp11 | 8 | 357 | 11 -| backmp11_favor_compile_time | 6 | 274 | 26 +| backmp11 | 8 | 350 | 11 +| backmp11_favor_compile_time | 6 | 266 | 26 | backmp11_favor_compile_time_multi_cu | 5 | ~915 | 26 | sml | 40 | 1056 | 11 |================================================================================ diff --git a/doc/modules/ROOT/pages/version-history.adoc b/doc/modules/ROOT/pages/version-history.adoc index 31d709b..02a9a7e 100644 --- a/doc/modules/ROOT/pages/version-history.adoc +++ b/doc/modules/ROOT/pages/version-history.adoc @@ -2,10 +2,14 @@ = Version history +== Boost 1.91 + +* feat(backmp11): Further optimized compilation, with up to 25% faster compile times and less RAM consumption than the 1.90 version + == Boost 1.90 -* feat/backmp11: New back-end backmp11, requires C++ 17 -* fix/back: boost::any stopped working as Kleene event (https://github.com/boostorg/msm/issues/87[#87]) +* feat(backmp11): New back-end backmp11, requires C++ 17 +* fix(back): boost::any stopped working as Kleene event (https://github.com/boostorg/msm/issues/87[#87]) * feat: The documentation has been refurbished, it is now built with Antora [[boost-185]] diff --git a/include/boost/msm/backmp11/detail/metafunctions.hpp b/include/boost/msm/backmp11/detail/metafunctions.hpp index 0d36a3b..c7a48ed 100644 --- a/include/boost/msm/backmp11/detail/metafunctions.hpp +++ b/include/boost/msm/backmp11/detail/metafunctions.hpp @@ -347,19 +347,6 @@ struct has_deferred_event_impl, Event> template using has_deferred_event = typename has_deferred_event_impl::type; -// event used internally for wrapping a direct entry -template -struct direct_entry_event -{ - public: - typedef int direct_entry; - typedef State active_state; - typedef Event contained_event; - - direct_entry_event(Event const& event):m_event(event){} - Event const& m_event; -}; - // Builds flags from flag_list + internal_flag_list, // internal_flag_list is used for terminate/interrupt states. template diff --git a/include/boost/msm/backmp11/detail/transition_table.hpp b/include/boost/msm/backmp11/detail/transition_table.hpp index 03238cc..011472c 100644 --- a/include/boost/msm/backmp11/detail/transition_table.hpp +++ b/include/boost/msm/backmp11/detail/transition_table.hpp @@ -33,24 +33,31 @@ struct transition_table_impl boost::mp11::mp_eval_or; - template - static bool call_guard_or_true(StateMachine& sm, const Event& event, Source& source, Target& target) + template + static bool call_guard_or_true(StateMachine& 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); + 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(StateMachine& sm, const Event& event, Source& source, Target& target) + template + static process_result call_action_or_true(StateMachine& 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); + return Row::action_call( + sm.get_fsm_argument(), event, source, target, sm.m_states); } else { @@ -58,21 +65,64 @@ struct transition_table_impl } } + template + using get_state = typename DirectWrapper::state; + + template + using is_explicit_entry_point = mp11::mp_eval_if< + mpl::is_sequence, + mp11::mp_true, + has_explicit_entry_be_tag, + FeTarget>; + + template + static void call_entry(StateMachine& sm, const Event& event, Target& target) + { + using FeTarget = typename Row::Target; + auto& fsm = sm.get_fsm_argument(); + + if constexpr (is_explicit_entry_point::value) + { + using targets = to_mp_list_t; + using states = mp11::mp_transform; + target.template on_explicit_entry(event, fsm); + } + else if constexpr (has_entry_pseudostate_be_tag::value) + { + using targets = to_mp_list_t; + using states = mp11::mp_transform; + target.template on_pseudo_entry(event, fsm); + } + else + { + target.on_entry(event, fsm); + if constexpr (has_exit_pseudostate_be_tag::value) + { + // Execute the second part of the compound transition. + target.forward_event(event); + } + } + } + // Template used to create transitions from rows in the transition table. - template + template struct transition { using transition_event = typename Row::Evt; - using current_state_type = convert_source_state; - using next_state_type = convert_target_state; + using current_state_type = + convert_source_state; + using next_state_type = + convert_target_state; // Take the transition action and return the next state. static process_result execute(StateMachine& sm, int& state_id, transition_event const& event) { - static constexpr int current_state_id = StateMachine::template get_state_id(); - static constexpr int next_state_id = StateMachine::template get_state_id(); + static constexpr int current_state_id = + StateMachine::template get_state_id(); + static constexpr int next_state_id = + StateMachine::template get_state_id(); BOOST_ASSERT(state_id == current_state_id); auto& source = sm.template get_state(); @@ -83,26 +133,33 @@ struct transition_table_impl // guard rejected the event, we stay in the current one return process_result::HANDLED_GUARD_REJECT; } - state_id = active_state_switching::after_guard(current_state_id,next_state_id); + state_id = active_state_switching::after_guard(current_state_id, + next_state_id); // first call the exit method of the current state source.on_exit(event, sm.get_fsm_argument()); - state_id = active_state_switching::after_exit(current_state_id,next_state_id); + state_id = active_state_switching::after_exit(current_state_id, + next_state_id); // then call the action method - process_result res = call_action_or_true(sm, event, source, target); - state_id = active_state_switching::after_action(current_state_id,next_state_id); + process_result res = + call_action_or_true(sm, event, source, target); + state_id = active_state_switching::after_action(current_state_id, + next_state_id); - // and finally the entry method of the new current state - StateMachine::template convert_event_and_execute_entry(target,event,sm); - state_id = active_state_switching::after_entry(current_state_id,next_state_id); + // and finally the entry method of the new state + call_entry(sm, event, target); + state_id = active_state_switching::after_entry(current_state_id, + next_state_id); return res; } }; - // Template used to create internal transitions from rows in the transition table. - template + // Template used to create internal transitions + // from rows in the transition table. + template struct internal_transition { using transition_event = typename Row::Evt; @@ -114,8 +171,10 @@ struct transition_table_impl [[maybe_unused]] int& state_id, transition_event const& event) { - BOOST_ASSERT(state_id == StateMachine::template get_state_id()); - + BOOST_ASSERT( + state_id == + StateMachine::template get_state_id()); + auto& source = sm.template get_state(); auto& target = source; @@ -127,7 +186,8 @@ struct transition_table_impl } }; - // Template used to create sm-internal transitions from rows in the transition table. + // Template used to create sm-internal transitions + // from rows in the transition table. template struct internal_transition { @@ -250,9 +310,11 @@ struct transition_table_impl append_internal_transition_table>>; }; template -using transition_table = typename transition_table_impl::transition_table; +using transition_table = + typename transition_table_impl::transition_table; template -using internal_transition_table = typename transition_table_impl::internal_transition_table; +using internal_transition_table = + typename transition_table_impl::internal_transition_table; } diff --git a/include/boost/msm/backmp11/favor_compile_time.hpp b/include/boost/msm/backmp11/favor_compile_time.hpp index a723279..1bdd211 100644 --- a/include/boost/msm/backmp11/favor_compile_time.hpp +++ b/include/boost/msm/backmp11/favor_compile_time.hpp @@ -302,12 +302,17 @@ struct compile_policy_impl // Dispatch an event to the SM's internal table. static process_result internal_dispatch(StateMachine& sm, const any_event& event) { - const dispatch_table& self = instance(); - return self.m_internal_dispatch_table.dispatch(sm, event); + if constexpr (has_internal_transitions::value) + { + const dispatch_table& self = instance(); + return self.m_internal_dispatch_table.dispatch(sm, event); + } + return process_result::HANDLED_FALSE; } private: using state_set = typename StateMachine::internal::state_set; + using submachines = mp11::mp_copy_if; // Value used to initialize a cell of the dispatch table. using cell_t = process_result (*)(StateMachine&, int&, any_event const&); @@ -343,6 +348,9 @@ struct compile_policy_impl template using get_init_cell_constant = typename get_init_cell_constant_impl::type; + using has_internal_transitions = mp11::mp_not>>; + using has_transitions = mp11::mp_not>>; + // Dispatch table for one state. class state_dispatch_table { @@ -467,7 +475,7 @@ struct compile_policy_impl StateMachine::template get_state_id(); m_state_dispatch_tables[state_id].template init_composite_state(); }); - if constexpr (!mp11::mp_empty>::value) + if constexpr (has_transitions::value) { using init_cell_constants = mp11::mp_transform< get_init_cell_constant, @@ -488,7 +496,7 @@ struct compile_policy_impl } // Initialize the sm-internal dispatch table. - if constexpr (!mp11::mp_empty>::value) + if constexpr (has_internal_transitions::value) { using init_internal_cell_constants = mp11::mp_transform< get_internal_init_cell_constant, diff --git a/include/boost/msm/backmp11/state_machine.hpp b/include/boost/msm/backmp11/state_machine.hpp index 009f964..ba50abd 100644 --- a/include/boost/msm/backmp11/state_machine.hpp +++ b/include/boost/msm/backmp11/state_machine.hpp @@ -18,17 +18,9 @@ #include #include -#include -#include - -#include - #include -#include -#include -#include -#include -#include +#include +#include #include #include @@ -141,92 +133,79 @@ class state_machine_base : public FrontEnd // Used when the front-end does not define a final_event. struct stopping {}; - template - struct exit_pt : public ExitPoint + // Wrapper for an exit pseudostate, + // which upper SMs can use to connect to it. + template + struct exit_pt : public ExitPseudostate { // tags struct internal { using tag = exit_pseudostate_be_tag; }; - typedef ExitPoint wrapped_exit; - typedef derived_t owner; - typedef int no_automatic_create; - typedef typename - ExitPoint::event Event; - typedef std::function - forward_function; + using state = ExitPseudostate; + using owner = derived_t; + using event = typename ExitPseudostate::event; + using forward_function = std::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); + static_assert(std::is_convertible_v, + "ForwardEvent must be convertible to exit pseudostate's event"); + // Call if handler set. If not, this state is simply a terminate state. + if (m_forward) + { + m_forward(incomingEvent); + } } void set_forward_fct(forward_function fct) { m_forward = fct; } - exit_pt():m_forward(){} + exit_pt() = default; // 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& ) + exit_pt(RHS&) : m_forward() + { + } + exit_pt& operator=(const exit_pt&) { return *this; } - private: + 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 + // Wrapper for an entry pseudostate, + // which upper SMs can use to connect to it. + template + struct entry_pt : public EntryPseudostate { // tags struct internal { using tag = entry_pseudostate_be_tag; }; - typedef EntryPoint wrapped_entry; - typedef derived_t owner; - typedef int no_automatic_create; + + using state = EntryPseudostate; + using owner = derived_t; }; - template - struct direct : public EntryPoint + + // Wrapper for a direct entry, + // which upper SMs can use to connect to it. + template + struct direct : public State { // tags struct internal { using tag = explicit_entry_be_tag; }; - typedef EntryPoint wrapped_entry; - typedef derived_t owner; - typedef int no_automatic_create; + using state = State; + using owner = derived_t; }; struct internal @@ -240,21 +219,21 @@ class state_machine_base : public FrontEnd using submachines = mp11::mp_copy_if; }; - typedef mp11::mp_rename states_t; + using states_t = mp11::mp_rename; private: 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 initial_state_identities = mp11::mp_transform; using compile_policy = typename config_t::compile_policy; using compile_policy_impl = detail::compile_policy_impl; template using get_active_state_switch_policy = typename T::active_state_switch_policy; using active_state_switching = - boost::mp11::mp_eval_or; + mp11::mp_eval_or; template friend class state_machine_base; @@ -278,12 +257,12 @@ class state_machine_base : public FrontEnd template using get_initial_event = typename T::initial_event; using fsm_initial_event = - boost::mp11::mp_eval_or; + mp11::mp_eval_or; template using get_final_event = typename T::final_event; using fsm_final_event = - boost::mp11::mp_eval_or; + mp11::mp_eval_or; using state_map = generate_state_map; using history_impl = detail::history_impl; @@ -407,7 +386,7 @@ class state_machine_base : public FrontEnd { if (this != &rhs) { - front_end_t::operator=(rhs); + front_end_t::operator=(rhs); // Copy all members except the root sm pointer. m_active_state_ids = rhs.m_active_state_ids; m_optional_members = rhs.m_optional_members; @@ -438,7 +417,7 @@ class state_machine_base : public FrontEnd { if (!m_running) { - internal_start(initial_event, get_fsm_argument()); + on_entry(initial_event, get_fsm_argument()); } } @@ -749,7 +728,7 @@ class state_machine_base : public FrontEnd void reset_active_state_ids() { size_t index = 0; - mp11::mp_for_each( + mp11::mp_for_each( [this, &index](auto state_identity) { using State = typename decltype(state_identity)::type; @@ -996,102 +975,142 @@ class state_machine_base : public FrontEnd } private: - template - void internal_start(Event const& event, Fsm& fsm) + template + void preprocess_entry(Event const& event, Fsm& fsm) { m_running = true; - + m_event_processing = true; + // Call on_entry on this SM first. static_cast(this)->on_entry(event, fsm); - - // Then call on_entry on the states. - if constexpr (InitialStart) - { - mp11::mp_for_each( - [this, &event, &fsm](auto state_identity) - { - using State = typename decltype(state_identity)::type; - execute_entry(this->get_state(), event, fsm); - }); - } - else - { - // Visit active states non-recursively. - visit( - [&event, &fsm](auto& state) - { - // TODO: - // Add filter to rule out impossible entry states. - execute_entry(state, event, fsm); - }); - } - - // give a chance to handle an anonymous (eventless) transition - try_process_completion_event(EventSource::EVENT_SOURCE_DEFAULT, true); } - // entry for states machines which are themselves embedded in other state machines (composites) + void postprocess_entry() + { + m_event_processing = false; + + // Give a chance to handle a completion transition first. + try_process_completion_event(EventSource::EVENT_SOURCE_DEFAULT, true); + + // Default: + // Handle deferred events queue with higher prio than events queue. + if constexpr (!has_event_queue_before_deferred_queue::value) + { + try_process_deferred_events(); + try_process_queued_events(); + } + // Non-default: + // Handle events queue with higher prio than deferred events queue. + else + { + try_process_queued_events(); + try_process_deferred_events(); + } + } + template void on_entry(Event const& event, Fsm& fsm) { - // block immediate handling of events - m_event_processing = true; - // by default we activate the history/init states, can be overwritten by direct events. + preprocess_entry(event, fsm); + + // First set all active state ids... m_active_state_ids = m_history.on_entry(event); - // this variant is for the standard case, entry due to activation of the containing FSM - if constexpr (!has_direct_entry::value) + // ... then execute each state entry. + if constexpr (std::is_same_v) { - internal_start(event, fsm); - } - // this variant is for the direct entry case - else if constexpr (has_direct_entry::value) - { - // Set the new active state(s) first, this includes - // a normal direct entry (or entries) and a pseudo entry. - using entry_states = to_mp_list_t; - mp11::mp_for_each>( - [this](auto state_identity) + mp11::mp_for_each( + [this, &event, &fsm](auto state_identity) { - using State = typename decltype(state_identity)::type::wrapped_entry; - static constexpr int region_index = State::zone_index; - static_assert(region_index >= 0 && region_index < nr_regions); - m_active_state_ids[region_index] = get_state_id(); - } - ); - internal_start(event.m_event, fsm); - // in case of a pseudo entry process the transition in the zone of the newly active state - // (entry pseudo states are, according to UML, a state connecting 1 transition outside to 1 inside - if constexpr (has_pseudo_entry::value) - { - static_assert(!mpl::is_sequence::value); - process_event(event.m_event); - } + using State = typename decltype(state_identity)::type; + auto& state = this->get_state(); + state.on_entry(event, fsm); + }); } - // handle messages which were generated and blocked in the init calls - m_event_processing = false; - // look for deferred events waiting - try_process_deferred_events(); - try_process_queued_events(); + else + { + visit( + [&event, &fsm](auto& state) + { + state.on_entry(event, fsm); + }); + } + + postprocess_entry(); } + + template + void on_explicit_entry(Event const& event, Fsm& fsm) + { + preprocess_entry(event, fsm); + + using state_identities = + mp11::mp_transform; + static constexpr bool all_regions_defined = + mp11::mp_size::value == nr_regions; + + // First set all active state ids... + if constexpr (!all_regions_defined) + { + m_active_state_ids = m_history.on_entry(event); + } + mp11::mp_for_each( + [this](auto state_identity) + { + using State = typename decltype(state_identity)::type; + static constexpr int region_id = State::zone_index; + static_assert(region_id >= 0 && region_id < nr_regions); + m_active_state_ids[region_id] = get_state_id(); + } + ); + // ... then execute each state entry. + if constexpr (all_regions_defined) + { + mp11::mp_for_each( + [this, &event, &fsm](auto state_identity) + { + using State = typename decltype(state_identity)::type; + auto& state = this->get_state(); + state.on_entry(event, fsm); + }); + } + else + { + visit( + [&event, &fsm](auto& state) + { + state.on_entry(event, fsm); + }); + } + + postprocess_entry(); + } + + template + void on_pseudo_entry(Event const& event, Fsm& fsm) + { + on_explicit_entry(event, fsm); + + // Execute the second part of the compound transition. + process_event(event); + } + template void on_exit(Event const& event, Fsm& fsm) { - // first recursively exit the sub machines - // forward the event for handling by sub state machines - visit( + // First exit the substates. + visit( [&event, &fsm](auto& state) { - // TODO: - // Filter out impossible exit states. state.on_exit(event, fsm); } ); - // then call our own exit + // Then call our own exit. (static_cast(this))->on_exit(event,fsm); - // give the history a chance to handle this (or not). + // Give the history a chance to handle this (or not). m_history.on_exit(this->m_active_state_ids); - // history decides what happens with deferred events + // History decides what happens with deferred events. if (!m_history.process_deferred_events(event)) { if constexpr (deferred_events_member::value) @@ -1101,50 +1120,6 @@ private: } } - // calls entry or on_entry depending on the state type - template - static void execute_entry(State& state, Event const& event, Fsm& fsm) - { - // calls on_entry on the fsm then handles direct entries, fork, entry pseudo state - if constexpr (has_state_machine_tag::value) - { - state.on_entry(event,fsm); - } - else if constexpr (has_exit_pseudostate_be_tag::value) - { - // calls on_entry on the state then forward the event to the transition which should be defined inside the - // contained fsm - state.on_entry(event,fsm); - state.forward_event(event); - } - else if constexpr (has_direct_entry::value) - { - state.on_entry(event.m_event, fsm); - } - else - { - state.on_entry(event, fsm); - } - - } - - // helper allowing special handling of direct entries / fork - template - static void convert_event_and_execute_entry(State& state,Event const& event, state_machine_base& sm) - { - auto& fsm = sm.get_fsm_argument(); - if constexpr (has_explicit_entry_state::value || mpl::is_sequence::value) - { - // for the direct entry, pack the event in a wrapper so that we handle it differently during fsm entry - execute_entry(state,direct_entry_event(event),fsm); - } - else - { - // if the target is a normal state, do the standard entry handling - execute_entry(state,event,fsm); - } - } - template using state_requires_init = mp11::mp_or, has_state_machine_tag>;