From f152198f40d87e6cca6fe3eaf7c9a383a600b404 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Thu, 4 Jun 2026 12:31:10 +0200 Subject: [PATCH 1/3] feat(backmp11): invokable static_assert for Action/Guard --- .../msm/backmp11/detail/transition_table.hpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/include/boost/msm/backmp11/detail/transition_table.hpp b/include/boost/msm/backmp11/detail/transition_table.hpp index 121bc219..8dc4adbd 100644 --- a/include/boost/msm/backmp11/detail/transition_table.hpp +++ b/include/boost/msm/backmp11/detail/transition_table.hpp @@ -33,8 +33,9 @@ namespace boost::msm::backmp11::detail // ↓ (SFINAE fails?) // priority_tag_1 (base of priority_tag_0) // ↓ (SFINAE fails?) -// priority_tag_2 (base of priority_tag_1) -struct priority_tag_2 {}; +// ... +struct priority_tag_3 {}; +struct priority_tag_2 : priority_tag_3 {}; struct priority_tag_1 : priority_tag_2 {}; struct priority_tag_0 : priority_tag_1 {}; @@ -60,6 +61,18 @@ auto invoke_functor(priority_tag_2, Functor&&, const Event&, Fsm& fsm, Source&, { return Functor{}(fsm); } +template +inline constexpr bool invokable = false; +template +auto invoke_functor(priority_tag_3, Functor&&, const Event&, Fsm&, Source&, + Target&) +{ + static_assert( + invokable, + "Action/Guard must be invokable with one of these signatures: " + "(event, fsm, source, target), (event, fsm), or (fsm)"); +} template using get_Guard = typename Row::Guard; From 03e1efe0a482d42b2adb9e28f81c916dfb9963d6 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Thu, 4 Jun 2026 16:07:50 +0200 Subject: [PATCH 2/3] doc: search bar for local playbook --- doc/local-playbook.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/doc/local-playbook.yml b/doc/local-playbook.yml index fe7f092d..0e658e26 100644 --- a/doc/local-playbook.yml +++ b/doc/local-playbook.yml @@ -40,19 +40,12 @@ site: antora: extensions: - - require: '@cppalliance/antora-playbook-macros-extension' - macros: - # Default values for macros - # These values can be overridden with environment variables, - # asciidoc.attributes, or --attribute command line option. - page-boost-branch: develop - page-boost-ui-branch: develop - page-commit-id: '000000' + - require: '@antora/lunr-extension' + index_latest_only: true - require: '@cppalliance/antora-cpp-tagfiles-extension' cpp-tagfiles: using-namespaces: - 'boost::' - - require: '@cppalliance/antora-downloads-extension' - require: '@cppalliance/antora-cpp-reference-extension' dependencies: - name: 'boost' @@ -60,6 +53,7 @@ antora: tag: 'develop' variable: 'BOOST_SRC_DIR' system-env: 'BOOST_SRC_DIR' + - require: '@cppalliance/antora-downloads-extension' asciidoc: attributes: From 9cf4a219a24188f83cec843384f9518fa2bf0f11 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Thu, 4 Jun 2026 16:19:16 +0200 Subject: [PATCH 3/3] feat(backmp11): forward Kleene events without conversion --- .../pages/tutorial/backmp11-back-end.adoc | 2 + doc/modules/ROOT/pages/version-history.adoc | 1 + .../backmp11/detail/favor_runtime_speed.hpp | 96 ++----------------- .../msm/backmp11/detail/transition_table.hpp | 27 +++--- include/boost/msm/backmp11/event_traits.hpp | 12 +-- .../boost/msm/backmp11/favor_compile_time.hpp | 36 +++---- include/boost/msm/backmp11/state_machine.hpp | 11 +-- test/Backmp11Transitions.cpp | 7 ++ test/FrontCommon.hpp | 2 +- test/KleeneDeferred.cpp | 4 +- 10 files changed, 58 insertions(+), 140 deletions(-) diff --git a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc index 56d6719f..36c34f44 100644 --- a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc @@ -584,6 +584,8 @@ It can be copied and adapted if needed, though this class is internal to the tes To reduce the number of required header inclusions, `backmp11` uses `std::any` for defining Kleene events instead of `boost::any`. You can opt in to use `boost::any` support by including `boost/msm/event_traits.h`. +Futhermore, the back-end forwards Kleene events without converting them to `std::any` or `boost:any`. Actions and guards receive the original event. + === Removed features diff --git a/doc/modules/ROOT/pages/version-history.adoc b/doc/modules/ROOT/pages/version-history.adoc index a66280ee..3628bdcd 100644 --- a/doc/modules/ROOT/pages/version-history.adoc +++ b/doc/modules/ROOT/pages/version-history.adoc @@ -9,6 +9,7 @@ ** The APIs `process_queued_events()` and `process_single_queued_event()` are removed, use `process_event_pool(...)` instead ** The default active state switch policy is set to "after source exit" in compliance to the UML specification. The other options are not UML-compliant and will be removed in version 1.93 (https://github.com/boostorg/msm/issues/222[#222]) * New features (`backmp11`): +** Forward Kleene events to actions and guards without conversion (https://github.com/boostorg/msm/issues/229[#229]) ** Ensure basic exception guarantee and propagate exceptions to the caller (https://github.com/boostorg/msm/issues/221[#221]) ** Provide a reflection API and serialization support for Boost.Serialization, Boost.JSON and nlohmann/json (https://github.com/boostorg/msm/issues/197[#197]) * Bug fixes (`backmp11`): diff --git a/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp b/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp index 57f52499..2ccff1da 100644 --- a/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp +++ b/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp @@ -112,49 +112,13 @@ struct compile_policy_impl< { if (is_event_deferred(sm, event)) { - defer_event(sm, event, false); + sm.defer_event(event); return true; } } return false; } - template - static void defer_event(StateMachine& sm, Event const& event, - bool next_rtc_seq) - { - if constexpr (is_kleene_event::value) - { - using event_set = generate_event_set< - typename StateMachine::front_end_t::transition_table>; - bool found = - mp_for_each_until>( - [&sm, &event, next_rtc_seq](auto event_identity) - { - using KnownEvent = typename decltype(event_identity)::type; - if (event.type() == typeid(KnownEvent)) - { - sm.do_defer_event(*any_cast(&event), - next_rtc_seq); - return true; - } - return false; - } - ); - if (!found) - { - for (const auto state_id : sm.get_active_state_ids()) - { - sm.no_transition(event, sm.get_fsm_argument(), state_id); - } - } - } - else - { - sm.do_defer_event(event, next_rtc_seq); - } - } - // Generates a singleton runtime lookup table that maps current state // to a function that makes the SM take its transition on the given // Event type. @@ -181,7 +145,7 @@ struct compile_policy_impl< { if constexpr (has_internal_transitions::value) { - return internal_dispatch_impl::transition::execute(sm, event); + return internal_dispatch_impl::transition::process(sm, event); } return process_result::HANDLED_FALSE; } @@ -301,7 +265,7 @@ struct compile_policy_impl< using next_state_type = Submachine; using transition_event = Event; - static process_result execute(StateMachine& sm, + static process_result process(StateMachine& sm, uint8_t region_id, Event const& event) { @@ -352,15 +316,6 @@ struct compile_policy_impl< using merged_transitions = mp11::mp_transform; - - template - static process_result convert_event_and_execute(StateMachine& sm, - uint8_t region_id, - Event const& evt) - { - typename Transition::transition_event kleene_event{evt}; - return Transition::execute(sm, region_id, kleene_event); - } }; template @@ -381,29 +336,15 @@ struct compile_policy_impl< [&sm, region_id, &event, state_id, &result](auto transition) { using Transition = decltype(transition); - using TransitionEvent = - typename Transition::transition_event; using SourceState = typename Transition::current_state_type; constexpr auto source_state_id = StateMachine::template get_state_id(); if (state_id == source_state_id) { - if constexpr (!is_kleene_event< - TransitionEvent>::value) - { - result = - Transition::execute(sm, region_id, event); - } - else - { - result = - base::template convert_event_and_execute< - Transition>(sm, region_id, event); - } + result = Transition::process(sm, region_id, event); } - } - ); + }); return result; } }; @@ -417,7 +358,7 @@ struct compile_policy_impl< Event const&); public: - static process_result dispatch( + static inline process_result dispatch( StateMachine& sm, uint8_t region_id, const Event& event) { const auto state_id = sm.m_active_state_ids[region_id]; @@ -431,25 +372,6 @@ struct compile_policy_impl< } private: - // Convert a transition to its function pointer. - template ::value> - struct get_cell; - template - struct get_cell - { - static constexpr cell_t value = &Transition::execute; - }; - template - struct get_cell - { - static constexpr cell_t value = - &base::template convert_event_and_execute; - }; - template - static constexpr cell_t cell_v = get_cell::value; - struct cell_table { cell_t data[max_state]{}; @@ -466,7 +388,7 @@ struct compile_policy_impl< ((table.data[ StateMachine::template get_state_id< typename Transitions::current_state_type>()] = - get_cell::value), ...); + &Transitions::process), ...); return table; } @@ -487,14 +409,14 @@ struct compile_policy_impl< { using transition_event = Event; - static process_result execute(StateMachine& sm, Event const& evt) + static process_result process(StateMachine& sm, Event const& evt) { process_result result = process_result::HANDLED_FALSE; mp_for_each_until( [&result, &sm, &evt](auto transition) { using Transition = decltype(transition); - result |= Transition::execute(sm, evt); + result |= Transition::process(sm, evt); if (result & handled_true_or_deferred) { // If a guard rejected previously, diff --git a/include/boost/msm/backmp11/detail/transition_table.hpp b/include/boost/msm/backmp11/detail/transition_table.hpp index 8dc4adbd..0738d17a 100644 --- a/include/boost/msm/backmp11/detail/transition_table.hpp +++ b/include/boost/msm/backmp11/detail/transition_table.hpp @@ -243,10 +243,10 @@ struct transition_table_impl using next_state_type = convert_target_state; - // Take the transition action and return the next state. - static process_result execute(StateMachine& sm, + template + static process_result process(StateMachine& sm, uint8_t region_id, - transition_event const& event) + const Event& event) { auto& state_id = sm.m_active_state_ids[region_id]; [[maybe_unused]] constexpr auto current_state_id = @@ -311,10 +311,10 @@ struct transition_table_impl using current_state_type = State; using next_state_type = current_state_type; - // Take the transition action and return the next state. - static process_result execute(StateMachine& sm, + template + static process_result process(StateMachine& sm, uint8_t region_id, - transition_event const& event) + const Event& event) { [[maybe_unused]] const auto state_id = sm.m_active_state_ids[region_id]; BOOST_ASSERT( @@ -339,9 +339,9 @@ struct transition_table_impl { using transition_event = typename Row::Evt; - // Take the transition action and return the next state. - static process_result execute(StateMachine& sm, - transition_event const& event) + template + static process_result process(StateMachine& sm, + const Event& event) { auto& source = sm; auto& target = source; @@ -457,7 +457,8 @@ struct transition_table_impl // Completion transitions are handled separately per state. template - using has_completion_event = has_completion_event; + using has_completion_event = + has_completion_event; using completion_transition_table = mp11::mp_copy_if; static_assert(mp11::mp_empty::value || @@ -504,14 +505,16 @@ struct transition_chain using current_state_type = State; using transition_event = Event; - static process_result execute(StateMachine& sm, uint8_t region_id, Event const& evt) + static process_result process(StateMachine& sm, + uint8_t region_id, + const Event& evt) { process_result result = process_result::HANDLED_FALSE; mp_for_each_until( [&result, &sm, region_id, &evt](auto transition) { using Transition = decltype(transition); - result |= Transition::execute(sm, region_id, evt); + result |= Transition::process(sm, region_id, evt); if (result & handled_true_or_deferred) { // If a guard rejected previously, ensure this bit is not present. diff --git a/include/boost/msm/backmp11/event_traits.hpp b/include/boost/msm/backmp11/event_traits.hpp index aad42ffd..22b8fffc 100644 --- a/include/boost/msm/backmp11/event_traits.hpp +++ b/include/boost/msm/backmp11/event_traits.hpp @@ -23,16 +23,6 @@ namespace boost::msm template <> struct is_kleene_event : std::true_type {}; -} // boost::msm - -namespace boost::msm::backmp11::detail -{ - -// Import std::any_cast for overload resolution: -// In case boost::any is used as Kleene event, -// overload resolution will pick boost::any_cast for us. -using std::any_cast; - -} // boost::msm::backmp11 +} // namespace boost::msm #endif //BOOST_MSM_EVENT_TRAITS_HPP diff --git a/include/boost/msm/backmp11/favor_compile_time.hpp b/include/boost/msm/backmp11/favor_compile_time.hpp index bfb710c3..d054ccc8 100644 --- a/include/boost/msm/backmp11/favor_compile_time.hpp +++ b/include/boost/msm/backmp11/favor_compile_time.hpp @@ -134,7 +134,7 @@ struct compile_policy_impl static bool convert_and_execute(const State& state, const any_event& event, const Fsm& fsm) { - return state.is_event_deferred(*any_cast(&event), fsm); + return state.is_event_deferred(*std::any_cast(&event), fsm); } std::unordered_map m_cells; @@ -189,20 +189,13 @@ struct compile_policy_impl { if (is_event_deferred(sm, event)) { - defer_event(sm, event, false); + sm.defer_event(event); return true; } } return false; } - template - static void defer_event(StateMachine& sm, any_event const& event, - bool next_rtc_seq) - { - sm.do_defer_event(event, next_rtc_seq); - } - // Convert an event to a type index. template static std::type_index to_type_index() @@ -217,7 +210,7 @@ struct compile_policy_impl { public: template - process_result execute(StateMachine& sm, uint8_t region_id, + process_result process(StateMachine& sm, uint8_t region_id, any_event const& event, process_result result) const { @@ -254,7 +247,7 @@ struct compile_policy_impl { public: template - process_result execute(StateMachine& sm, any_event const& event) const + process_result process(StateMachine& sm, any_event const& event) const { using cell_t = process_result (*)(StateMachine&, any_event const&); process_result result = process_result::HANDLED_FALSE; @@ -333,10 +326,10 @@ struct compile_policy_impl }; template - static process_result convert_event_and_execute( + static process_result convert_event_and_process( StateMachine& sm, uint8_t region_id, const any_event& event) { - return Transition::execute(sm, region_id, *any_cast(&event)); + return Transition::process(sm, region_id, *std::any_cast(&event)); } template @@ -345,7 +338,7 @@ struct compile_policy_impl using type = init_cell_constant< typename Transition::transition_event, StateMachine::template get_state_id(), - convert_event_and_execute + convert_event_and_process >; }; template @@ -389,7 +382,7 @@ struct compile_policy_impl auto it = m_transition_chains.find(event.type()); if (it != m_transition_chains.end()) { - result = (it->second.execute)(sm, region_id, event, result); + result = (it->second.process)(sm, region_id, event, result); } return result; } @@ -422,10 +415,10 @@ struct compile_policy_impl }; template - static process_result convert_event_and_execute_internal( + static process_result convert_event_and_process( StateMachine& sm, const any_event& event) { - return Transition::execute(sm, *any_cast(&event)); + return Transition::process(sm, *std::any_cast(&event)); } template @@ -433,8 +426,8 @@ struct compile_policy_impl { using type = init_internal_cell_constant< typename Transition::transition_event, - convert_event_and_execute_internal - >; + convert_event_and_process>; }; template using get_internal_init_cell_constant = @@ -459,7 +452,7 @@ struct compile_policy_impl auto it = m_transition_chains.find(event.type()); if (it != m_transition_chains.end()) { - result = (it->second.execute)(sm, event); + result = (it->second.process)(sm, event); } return result; } @@ -479,7 +472,8 @@ struct compile_policy_impl using Submachine = typename decltype(state_identity)::type; static constexpr auto state_id = StateMachine::template get_state_id(); - m_state_dispatch_tables[state_id].template init_composite_state(); + m_state_dispatch_tables[state_id] + .template init_composite_state(); }); if constexpr (has_transitions::value) { diff --git a/include/boost/msm/backmp11/state_machine.hpp b/include/boost/msm/backmp11/state_machine.hpp index e5ab51b0..828e0ff0 100644 --- a/include/boost/msm/backmp11/state_machine.hpp +++ b/include/boost/msm/backmp11/state_machine.hpp @@ -388,8 +388,7 @@ class state_machine typename = std::enable_if_t> void enqueue_event(Event const& event) { - compile_policy_impl::defer_event( - *this, compile_policy_impl::normalize_event(event), false); + do_defer_event(compile_policy_impl::normalize_event(event), false); } /** @@ -405,9 +404,9 @@ class state_machine typename = std::enable_if_t> void defer_event(Event const& event) { - compile_policy_impl::defer_event( - *this, compile_policy_impl::normalize_event(event), - this->m_machine_state == detail::machine_state::processing); + do_defer_event(compile_policy_impl::normalize_event(event), + this->m_machine_state == + detail::machine_state::processing); } /// Returns the active state ids of the machine. @@ -728,7 +727,7 @@ class state_machine using completion_event = typename Transition::transition_event; { detail::process_guard guard{this->m_machine_state}; - return Transition::execute(self(), region_id, completion_event{}); + return Transition::process(self(), region_id, completion_event{}); } } diff --git a/test/Backmp11Transitions.cpp b/test/Backmp11Transitions.cpp index db21c17b..22c0d0bd 100644 --- a/test/Backmp11Transitions.cpp +++ b/test/Backmp11Transitions.cpp @@ -50,6 +50,13 @@ struct MyAction void operator()(const Event&, Fsm& fsm, Source& source, Target&) { source.action_counter++; + if constexpr (std::is_same_v) + { + // An action in favor_runtime_speed shall not receive an event + // converted to Kleene, the Kleene event type is only used as a + // placeholder in the front-end. + static_assert(!std::is_same_v); + } // Attempting to process events while the state machine is processing // shall discard the event. BOOST_REQUIRE(fsm.process_event(TriggerNoTransition{}) == diff --git a/test/FrontCommon.hpp b/test/FrontCommon.hpp index 82d44fba..bbeca9c9 100644 --- a/test/FrontCommon.hpp +++ b/test/FrontCommon.hpp @@ -58,7 +58,7 @@ struct StateMachineBase_ : state_machine_def template void no_transition(Event const&, FSM&, int) { - BOOST_TEST_FAIL("no_transition called!"); + throw std::logic_error{"no_transition called"}; } size_t entry_counter{}; diff --git a/test/KleeneDeferred.cpp b/test/KleeneDeferred.cpp index f858a4a4..999731cd 100644 --- a/test/KleeneDeferred.cpp +++ b/test/KleeneDeferred.cpp @@ -69,8 +69,8 @@ namespace struct is_event1 { - template - bool operator()(EVT const& evt, FSM&, SourceState&, TargetState&) + template + bool operator()(boost::any const& evt, FSM&, SourceState&, TargetState&) { bool is_deferred = boost::any_cast(&evt) != 0; return is_deferred;