From 428cbad053bf76a9490f360d9552cb69f6d58eb9 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Thu, 19 Feb 2026 13:27:44 -0500 Subject: [PATCH 1/3] feat(backmp11): simplified functor signatures --- .../pages/tutorial/backmp11-back-end.adoc | 12 +- .../pages/tutorial/functor-front-end.adoc | 89 +++++- doc/modules/ROOT/pages/version-history.adoc | 1 + .../msm/backmp11/detail/transition_table.hpp | 117 +++++++- include/boost/msm/front/functor_row.hpp | 7 + test/Backmp11FunctorApi.cpp | 282 ++++++++++++++++++ test/CMakeLists.txt | 1 + test/Jamfile.v2 | 1 + test/OrthogonalDeferred3.cpp | 2 + 9 files changed, 502 insertions(+), 10 deletions(-) create mode 100644 test/Backmp11FunctorApi.cpp diff --git a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc index a54e1349..6b388ec0 100644 --- a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc @@ -15,8 +15,8 @@ It offers a significant improvement in runtime and memory usage, as can be seen | back | 14 | 815 | 68 | 2.8 | back_favor_compile_time | 17 | 775 | 226 | 3.5 | back11 | 37 | 2682 | 84 | 2.8 -| backmp11 | 3 | 209 | 29 | 0.7 -| backmp11_favor_compile_time | 3 | 193 | 43 | 6.0 +| backmp11 | 3 | 211 | 29 | 0.7 +| backmp11_favor_compile_time | 3 | 195 | 43 | 6.0 | sml | 5 | 234 | 57 | 0.3 |======================================================================================================= @@ -28,8 +28,8 @@ It offers a significant improvement in runtime and memory usage, as can be seen | | Compile time / sec | Compile RAM / MB | Binary size / kB | Runtime / sec | back | 49 | 2165 | 230 | 13.2 | back_favor_compile_time | 55 | 1704 | 911 | > 300 -| backmp11 | 8 | 351 | 85 | 3.3 -| backmp11_favor_compile_time | 5 | 256 | 100 | 20.4 +| backmp11 | 8 | 354 | 85 | 3.4 +| backmp11_favor_compile_time | 5 | 262 | 100 | 20.4 | backmp11_favor_compile_time_multi_cu | 5 | ~863 | 100 | 20.8 | sml | 18 | 543 | 422 | 5.4 |================================================================================================================ @@ -170,6 +170,10 @@ If the type of the state appears multiple times in a hierarchical state machine, // - if `start()` is called for a running state machine, the call is ignored // - if `stop()` is called on a stopped (not running) state machine, the call is ignored +=== Support for simplified functor signatures + +Further described in xref:./functor-front-end.adoc#simplified_functor_signatures[the functor front-end documentation]. + === Simplified state machine signature diff --git a/doc/modules/ROOT/pages/tutorial/functor-front-end.adoc b/doc/modules/ROOT/pages/tutorial/functor-front-end.adoc index 4d87a55b..54e80675 100644 --- a/doc/modules/ROOT/pages/tutorial/functor-front-end.adoc +++ b/doc/modules/ROOT/pages/tutorial/functor-front-end.adoc @@ -53,7 +53,6 @@ Row < Paused , stop , Stopped , stop_playback , none Row < Paused , open_close , Open , stop_and_open , none > // +---------+------------+-----------+---------------------------+----------------------------+ > {}; - ---- Transitions are now of type "Row" with exactly 5 template arguments: @@ -66,12 +65,12 @@ detected event, the state machine, source and target state: ---- struct store_cd_info { - template - void operator()(Evt const&, Fsm& fsm, SourceState&,TargetState& ) + template + void operator()(Event const&, Fsm& fsm, SourceState&, TargetState&) { cout << "player::store_cd_info" << endl; fsm.process_event(play()); - } + } }; ---- @@ -119,6 +118,88 @@ It even starts looking like functional programming. MSM ships with functors for operators, state machine usage, STL algorithms or container methods. + +== Using lambdas as functors ({cpp}20) + +If {cpp}20 is available you can shorten the functor syntax by using lambdas as functors. +The `store_cd_info` functor can be rewritten with a lambda as follows: + +[source,cpp] +---- +using store_cd_info = msm::front::Lambda< + [](auto const& /*event*/, auto& fsm, auto& /*source*/, auto& /*target*/) + { + cout << "player::store_cd_info" << endl; + fsm.process_event(play()); + }>; +---- + +[[simplified_functor_signatures]] +== Simplified functor signatures (`backmp11` only) + +The `backmp11` back-end allows you to use shorter functor signatures, reducing the boilerplate of action and guard functor definitions. It automatically detects how many parameters your functors have and invokes them with the appropriate arguments. + +The following three signatures are supported: + +**Full signature** + +[source,cpp] +---- +struct store_cd_info +{ + template + void operator()(Event const&, Fsm& fsm, SourceState&, TargetState&) + { + cout << "player::store_cd_info" << endl; + fsm.process_event(play()); + } +}; +---- + +**Event and FSM only** + +[source,cpp] +---- +struct store_cd_info +{ + template + void operator()(Event const&, Fsm& fsm) + { + cout << "player::store_cd_info" << endl; + fsm.process_event(play()); + } +}; +---- + +**FSM only** + +[source,cpp] +---- +struct store_cd_info +{ + template + void operator()(Fsm& fsm) + { + cout << "player::store_cd_info" << endl; + fsm.process_event(play()); + } +}; +---- + +Simplified signatures are also supported with lambdas, providing a way to define +functors with minimal boilerplate: + +[source,cpp] +---- +using store_cd_info = msm::front::Lambda< + [](auto& fsm) + { + cout << "player::store_cd_info" << endl; + fsm.process_event(play()); + }>; +---- + + [[defining-states-with-entryexit-actions]] == Defining states with entry/exit actions diff --git a/doc/modules/ROOT/pages/version-history.adoc b/doc/modules/ROOT/pages/version-history.adoc index 2e010849..093eeca9 100644 --- a/doc/modules/ROOT/pages/version-history.adoc +++ b/doc/modules/ROOT/pages/version-history.adoc @@ -12,6 +12,7 @@ * feat(backmp11): Small Object Optimization for events in the event pool (https://github.com/boostorg/msm/issues/172[#172]) * feat(backmp11): Improve support for the `deferred_events` property in hierarchical state machines (https://github.com/boostorg/msm/issues/173[#173]) * feat(backmp11): Improve runtime performance with a `flat_fold` dispatch strategy (https://github.com/boostorg/msm/issues/180[#180]) +* feat(backmp11): Simplified functor signatures (https://github.com/boostorg/msm/issues/175[#175]) == Boost 1.90 diff --git a/include/boost/msm/backmp11/detail/transition_table.hpp b/include/boost/msm/backmp11/detail/transition_table.hpp index 68bb3647..93b24a03 100644 --- a/include/boost/msm/backmp11/detail/transition_table.hpp +++ b/include/boost/msm/backmp11/detail/transition_table.hpp @@ -18,9 +18,111 @@ #include #include "boost/msm/backmp11/state_machine_config.hpp" +namespace boost::msm::front +{ + struct Defer; +}; + namespace boost::msm::backmp11::detail { +// Chain of priority tags for SFINAE handling: +// priority_tag_0 +// ↓ (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_1 : priority_tag_2 {}; +struct priority_tag_0 : priority_tag_1 {}; + +template +auto invoke_functor(priority_tag_0, Functor&&, const Event& event, Fsm& fsm, + Source& source, Target& target) + -> decltype(Functor{}(event, fsm, source, target)) +{ + return Functor{}(event, fsm, source, target); +} +template +auto invoke_functor(priority_tag_1, Functor&&, const Event& event, Fsm& fsm, Source&, + Target&) -> decltype(Functor{}(event, fsm)) +{ + return Functor{}(event, fsm); +} +template +auto invoke_functor(priority_tag_2, Functor&&, const Event&, Fsm& fsm, Source&, + Target&) -> decltype(Functor{}(fsm)) +{ + return Functor{}(fsm); +} + +template +using get_Guard = typename Row::Guard; +template +struct has_Guard : mp11::mp_valid {}; + +template +struct invoke_guard_functor +{ + template + static bool execute(const Event& event, Fsm& fsm, Source& source, + Target& target) + { + return invoke_functor(priority_tag_0{}, Functor{}, event, fsm, + source, target); + } +}; +template <> +struct invoke_guard_functor +{ + template + static bool execute(const Event&, Fsm&, Source&, Target&) + { + return true; + } +}; + +template +using get_Action = typename Row::Action; +template +struct has_Action : mp11::mp_valid {}; + +template +struct invoke_action_functor +{ + template + static process_result execute(const Event& event, Fsm& fsm, Source& source, + Target& target) + { + invoke_functor(priority_tag_0{}, Functor{}, event, fsm, source, + target); + return process_result::HANDLED_TRUE; + } +}; +template <> +struct invoke_action_functor +{ + template + static process_result execute(const Event&, Fsm&, Source&, Target&) + { + return process_result::HANDLED_TRUE; + } +}; +template <> +struct invoke_action_functor +{ + template + static process_result execute(const Event& event, Fsm& fsm, Source&, + Target&) + { + fsm.defer_event(event); + return process_result::HANDLED_DEFERRED; + } +}; + template struct transition_table_impl { @@ -39,7 +141,12 @@ struct transition_table_impl static bool call_guard_or_true(StateMachine& sm, const Event& event, Source& source, Target& target) { - if constexpr (HasGuard) + if constexpr (has_Guard::value) + { + return invoke_guard_functor::execute( + event, sm.get_fsm_argument(), source, target); + } + else if constexpr (HasGuard) { return Row::guard_call( sm.get_fsm_argument(), event, source, target, sm.m_states); @@ -49,13 +156,19 @@ struct transition_table_impl return true; } } + template static process_result call_action_or_true(StateMachine& sm, const Event& event, Source& source, Target& target) { - if constexpr (HasAction) + if constexpr (has_Action::value) + { + return invoke_action_functor::execute( + event, sm.get_fsm_argument(), source, target); + } + else if constexpr (HasAction) { return Row::action_call( sm.get_fsm_argument(), event, source, target, sm.m_states); diff --git a/include/boost/msm/front/functor_row.hpp b/include/boost/msm/front/functor_row.hpp index 59c2815a..4f8e52f5 100644 --- a/include/boost/msm/front/functor_row.hpp +++ b/include/boost/msm/front/functor_row.hpp @@ -361,5 +361,12 @@ namespace boost { namespace msm { namespace front fsm.defer_event(evt); } }; + +#if __cplusplus >= 202002L + // Wrapper to make a functor from a lambda. + template + struct Lambda : decltype(T) {}; +#endif + }}} #endif //BOOST_MSM_FRONT_FUNCTOR_ROW_H diff --git a/test/Backmp11FunctorApi.cpp b/test/Backmp11FunctorApi.cpp new file mode 100644 index 00000000..487855d5 --- /dev/null +++ b/test/Backmp11FunctorApi.cpp @@ -0,0 +1,282 @@ +// Copyright 2026 Christian Granzin +// Copyright 2024 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_NONSTANDALONE_TEST +#define BOOST_TEST_MODULE backmp11_completion +#endif +#include + +// back-end +#include "Backmp11.hpp" +// #include "BackCommon.hpp" +//front-end +#include "FrontCommon.hpp" +#include "Utils.hpp" + +using namespace boost::msm::front; +using namespace boost::msm::backmp11; +namespace mp11 = boost::mp11; + +namespace +{ + +// Events +struct TriggerOnlyFsmActionLambda {}; +struct TriggerDefaultAction {}; +struct TriggerOnlyEventAndFsmAction {}; +struct TriggerOnlyFsmAction {}; +struct TriggerActionWithMultipleOverloads {}; +struct TriggerSpecificAction {}; + +// States +struct State1 : test::StateBase +{ +}; +struct State2 : test::StateBase +{ +}; + +struct Machine_; + +// Actions +struct DefaultAction +{ + template + void operator()(const Event&, Fsm&, Source&, Target&) + { + static_assert(std::is_same_v); + static_assert(std::is_base_of_v); + calls += 1; + } + + [[maybe_unused]] static inline size_t calls{}; +}; +struct OnlyEventAndFsmAction +{ + template + void operator()(const Event&, Fsm&) + { + static_assert(std::is_same_v); + static_assert(std::is_base_of_v); + calls += 1; + } + + [[maybe_unused]] static inline size_t calls{}; +}; +struct OnlyFsmAction +{ + template + void operator()(Fsm&) + { + static_assert(std::is_base_of_v); + calls += 1; + } + + [[maybe_unused]] static inline size_t calls{}; +}; +struct ActionWithMultipleOverloads : DefaultAction +{ + template + void operator()(const Event&, Fsm&, Source&, Target&) + { + static_assert(std::is_same_v); + static_assert(std::is_base_of_v); + default_calls += 1; + } + + [[maybe_unused]] static inline size_t default_calls{}; + + // Overload constraint because caused by priority tags in this edge case: + // A specific overload must use the highest used arity of the generic overloads. + template + void operator()(const TriggerSpecificAction&, Fsm&, Source&, Target&) + { + static_assert(std::is_base_of_v); + specific_calls += 1; + } + + [[maybe_unused]] static inline size_t specific_calls{}; + + template + void operator()(const Event&, Fsm&) + { + static_assert(std::is_same_v); + static_assert(std::is_base_of_v); + only_event_and_fsm_calls += 1; + } + + [[maybe_unused]] static inline size_t only_event_and_fsm_calls{}; + + template + void operator()(Fsm&) + { + static_assert(std::is_base_of_v); + only_fsm_calls += 1; + } + + [[maybe_unused]] static inline size_t only_fsm_calls{}; +}; + +// Lambda support for shorter syntax (requires C++20). +#if __cplusplus >= 202002L +using OnlyFsmActionLambda = Lambda<[](auto& fsm) { + using Fsm = std::decay_t; + static_assert(std::is_base_of_v); +}>; +#endif + +// Guards +struct DefaultGuard +{ + template + bool operator()(const Event&, Fsm&, Source&, Target&) + { + static_assert(std::is_same_v); + static_assert(std::is_base_of_v); + calls += 1; + return true; + } + + [[maybe_unused]] static inline size_t calls{}; +}; +struct OnlyEventAndFsmGuard +{ + template + bool operator()(const Event&, Fsm&) + { + static_assert(std::is_same_v); + static_assert(std::is_base_of_v); + calls += 1; + return true; + } + + [[maybe_unused]] static inline size_t calls{}; +}; +struct OnlyFsmGuard +{ + template + bool operator()(Fsm&) + { + static_assert(std::is_base_of_v); + calls += 1; + return true; + } + + [[maybe_unused]] static inline size_t calls{}; +}; +struct GuardWithMultipleOverloads +{ + template + bool operator()(const Event&, Fsm&, Source&, Target&) + { + static_assert(std::is_same_v); + static_assert(std::is_base_of_v); + default_calls += 1; + return true; + } + + [[maybe_unused]] static inline size_t default_calls{}; + + // Overload constraint because caused by priority tags in this edge case: + // A specific overload must use the highest used arity of the generic overloads. + template + bool operator()(const TriggerSpecificAction&, Fsm&, Source&, Target&) + { + static_assert(std::is_base_of_v); + specific_calls += 1; + return true; + } + + [[maybe_unused]] static inline size_t specific_calls{}; + + template + bool operator()(const Event&, Fsm&) + { + static_assert(std::is_same_v); + static_assert(std::is_base_of_v); + only_event_and_fsm_calls += 1; + return true; + } + + [[maybe_unused]] static inline size_t only_event_and_fsm_calls{}; + + template + bool operator()(Fsm&) + { + static_assert(std::is_base_of_v); + only_fsm_calls += 1; + return true; + } + + [[maybe_unused]] static inline size_t only_fsm_calls{}; +}; +// Lambda support for shorter syntax (requires C++20). +#if __cplusplus >= 202002L +using OnlyFsmGuardLambda = Lambda<[](auto& fsm) { + using Fsm = std::decay_t; + static_assert(std::is_base_of_v); + return true; +}>; +#endif + +struct Machine_ : test::StateMachineBase_ +{ + using initial_state = State1; + + using transition_table = mp11::mp_list< + // Start Event Next Action Guard +#if __cplusplus >= 202002L + Row < State1, TriggerOnlyFsmActionLambda , none, OnlyFsmActionLambda , OnlyFsmGuardLambda >, +#endif + Row < State1, TriggerDefaultAction , none, DefaultAction , DefaultGuard >, + Row < State1, TriggerOnlyEventAndFsmAction , none, OnlyEventAndFsmAction , OnlyEventAndFsmGuard >, + Row < State1, TriggerOnlyFsmAction , none, OnlyFsmAction , OnlyFsmGuard >, + Row < State1, TriggerActionWithMultipleOverloads, none, ActionWithMultipleOverloads, GuardWithMultipleOverloads >, + Row < State1, TriggerSpecificAction , none, ActionWithMultipleOverloads, GuardWithMultipleOverloads > + >; +}; + +// Pick a back-end +using TestMachines = mp11::mp_list< +#ifndef BOOST_MSM_TEST_SKIP_BACKMP11 + state_machine, + state_machine +#endif // BOOST_MSM_TEST_SKIP_BACKMP11 + >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(test, TestMachine, TestMachines) +{ + TestMachine state_machine; + + state_machine.start(); + + state_machine.process_event(TriggerDefaultAction()); + ASSERT_ONE_AND_RESET(DefaultAction::calls); + ASSERT_ONE_AND_RESET(DefaultGuard::calls); + + state_machine.process_event(TriggerOnlyEventAndFsmAction()); + ASSERT_ONE_AND_RESET(OnlyEventAndFsmAction::calls); + ASSERT_ONE_AND_RESET(OnlyEventAndFsmGuard::calls); + + state_machine.process_event(TriggerOnlyFsmAction()); + ASSERT_ONE_AND_RESET(OnlyFsmAction::calls); + ASSERT_ONE_AND_RESET(OnlyFsmGuard::calls); + + state_machine.process_event(TriggerActionWithMultipleOverloads()); + ASSERT_ONE_AND_RESET(ActionWithMultipleOverloads::default_calls); + ASSERT_ONE_AND_RESET(GuardWithMultipleOverloads::default_calls); + +#if __cplusplus >= 202002L + state_machine.process_event(TriggerOnlyFsmActionLambda()); +#endif +} + +} // namespace diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0afc0816..2bb7f32e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -78,6 +78,7 @@ add_executable(boost_msm_cxx17_tests Backmp11Context.cpp Backmp11Deferred.cpp Backmp11EntryExit.cpp + Backmp11FunctorApi.cpp Backmp11ManyDeferTransitions.cpp Backmp11RootSm.cpp Backmp11Transitions.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index d1dccdce..d45363f5 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -81,6 +81,7 @@ test-suite msm-unit-tests-cxxstd17 [ run Backmp11Constructor.cpp ] [ run Backmp11Context.cpp ] [ run Backmp11Deferred.cpp ] + [ run Backmp11FunctorApi.cpp ] [ run Backmp11EntryExit.cpp ] [ run Backmp11ManyDeferTransitions.cpp ] [ run Backmp11RootSm.cpp ] diff --git a/test/OrthogonalDeferred3.cpp b/test/OrthogonalDeferred3.cpp index 1a37fe0c..823f7848 100644 --- a/test/OrthogonalDeferred3.cpp +++ b/test/OrthogonalDeferred3.cpp @@ -9,6 +9,8 @@ // http://www.boost.org/LICENSE_1_0.txt) // back-end +// Backmp11 does not support custom Defer actions. +#define BOOST_MSM_TEST_SKIP_BACKMP11 #include "BackCommon.hpp" //front-end #include From 79067cf6e0566d08489fd37eb43f9ff943f068d9 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Mon, 23 Feb 2026 14:21:29 -0500 Subject: [PATCH 2/3] refactor(backmp11): various topics --- .../pages/tutorial/backmp11-back-end.adoc | 8 +- include/boost/msm/backmp11/common_types.hpp | 14 +- .../msm/backmp11/detail/dispatch_table.hpp | 61 - .../backmp11/detail/favor_runtime_speed.hpp | 101 +- .../msm/backmp11/detail/history_impl.hpp | 14 +- .../msm/backmp11/detail/metafunctions.hpp | 15 +- .../backmp11/detail/state_machine_base.hpp | 1149 ++++++++++++++++ .../msm/backmp11/detail/state_visitor.hpp | 134 +- .../msm/backmp11/detail/transition_table.hpp | 2 +- include/boost/msm/backmp11/event_traits.hpp | 6 +- .../boost/msm/backmp11/favor_compile_time.hpp | 68 +- include/boost/msm/backmp11/state_machine.hpp | 1152 +---------------- .../msm/backmp11/state_machine_config.hpp | 6 +- meta/libraries.json | 3 +- 14 files changed, 1272 insertions(+), 1461 deletions(-) delete mode 100644 include/boost/msm/backmp11/detail/dispatch_table.hpp create mode 100644 include/boost/msm/backmp11/detail/state_machine_base.hpp diff --git a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc index 6b388ec0..7e229f83 100644 --- a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc @@ -15,7 +15,7 @@ It offers a significant improvement in runtime and memory usage, as can be seen | back | 14 | 815 | 68 | 2.8 | back_favor_compile_time | 17 | 775 | 226 | 3.5 | back11 | 37 | 2682 | 84 | 2.8 -| backmp11 | 3 | 211 | 29 | 0.7 +| backmp11 | 3 | 209 | 29 | 0.7 | backmp11_favor_compile_time | 3 | 195 | 43 | 6.0 | sml | 5 | 234 | 57 | 0.3 |======================================================================================================= @@ -28,9 +28,9 @@ It offers a significant improvement in runtime and memory usage, as can be seen | | Compile time / sec | Compile RAM / MB | Binary size / kB | Runtime / sec | back | 49 | 2165 | 230 | 13.2 | back_favor_compile_time | 55 | 1704 | 911 | > 300 -| backmp11 | 8 | 354 | 85 | 3.4 -| backmp11_favor_compile_time | 5 | 262 | 100 | 20.4 -| backmp11_favor_compile_time_multi_cu | 5 | ~863 | 100 | 20.8 +| backmp11 | 8 | 348 | 83 | 3.4 +| backmp11_favor_compile_time | 5 | 261 | 97 | 20.6 +| backmp11_favor_compile_time_multi_cu | 4 | ~863 | 97 | 21.4 | sml | 18 | 543 | 422 | 5.4 |================================================================================================================ diff --git a/include/boost/msm/backmp11/common_types.hpp b/include/boost/msm/backmp11/common_types.hpp index 2c89a6b8..a52e034d 100644 --- a/include/boost/msm/backmp11/common_types.hpp +++ b/include/boost/msm/backmp11/common_types.hpp @@ -9,8 +9,8 @@ // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_MSM_BACKMP11_COMMON_TYPES_H -#define BOOST_MSM_BACKMP11_COMMON_TYPES_H +#ifndef BOOST_MSM_BACKMP11_COMMON_TYPES_HPP +#define BOOST_MSM_BACKMP11_COMMON_TYPES_HPP #include #include @@ -111,11 +111,11 @@ static constexpr process_result handled_true_or_deferred = // Event occurrences are placed in an event pool for later processing. class event_occurrence { - using process_fnc_t = std::optional (*)( + using process_fn_t = std::optional (*)( event_occurrence&, void* /*sm*/, uint16_t /*seq_cnt*/); public: - event_occurrence(process_fnc_t process_fnc) : m_process_fnc(process_fnc) + event_occurrence(process_fn_t process_fn) : m_process_fn(process_fn) { } @@ -125,7 +125,7 @@ class event_occurrence template std::optional try_process(StateMachine& sm, uint16_t seq_cnt) { - return m_process_fnc(*this, static_cast(&sm), seq_cnt); + return m_process_fn(*this, static_cast(&sm), seq_cnt); } void mark_for_deletion() @@ -139,7 +139,7 @@ class event_occurrence } private: - process_fnc_t m_process_fnc{}; + process_fn_t m_process_fn{}; // Flag set when this event has been processed and can be erased. // Deletion is deferred to allow the use of std::deque, // which provides better cache locality and lower per-element overhead. @@ -188,4 +188,4 @@ struct compile_policy_impl; } // namespace detail } // namespace boost::msm::backmp11 -#endif // BOOST_MSM_BACKMP11_COMMON_TYPES_H +#endif // BOOST_MSM_BACKMP11_COMMON_TYPES_HPP diff --git a/include/boost/msm/backmp11/detail/dispatch_table.hpp b/include/boost/msm/backmp11/detail/dispatch_table.hpp deleted file mode 100644 index 5366cbc6..00000000 --- a/include/boost/msm/backmp11/detail/dispatch_table.hpp +++ /dev/null @@ -1,61 +0,0 @@ -// 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_DISPATCH_TABLE_H -#define BOOST_MSM_BACKMP11_DISPATCH_TABLE_H - -#include - -#include -#include - -namespace boost { namespace msm { namespace backmp11 -{ - -namespace detail -{ - -// Value used to initialize a cell of the dispatch table -template -struct init_cell_value -{ - size_t index; - Cell cell; -}; -template -struct init_cell_constant -{ - using value_type = init_cell_value; - static constexpr value_type value = {index, cell}; -}; - -// Type-punned init cell value to suppress redundant template instantiations. -using generic_cell = void(*)(); -using generic_init_cell_value = init_cell_value; - -// Class that handles the initialization of dispatch table entries. -struct dispatch_table_initializer -{ - static void execute(generic_cell* cells, const generic_init_cell_value* values, size_t values_size) - { - for (size_t i = 0; i < values_size; i++) - { - const auto& item = values[i]; - cells[item.index] = item.cell; - } - } -}; - -} // detail - -}}} // boost::msm::backmp11 - -#endif //BOOST_MSM_BACKMP11_DISPATCH_TABLE_H diff --git a/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp b/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp index dbd57bb9..d5da04e4 100644 --- a/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp +++ b/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp @@ -9,16 +9,15 @@ // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_MSM_BACKMP11_DETAIL_FAVOR_RUNTIME_SPEED_H -#define BOOST_MSM_BACKMP11_DETAIL_FAVOR_RUNTIME_SPEED_H +#ifndef BOOST_MSM_BACKMP11_DETAIL_FAVOR_RUNTIME_SPEED_HPP +#define BOOST_MSM_BACKMP11_DETAIL_FAVOR_RUNTIME_SPEED_HPP #include -#include #include #include #include -namespace boost { namespace msm { namespace backmp11 +namespace boost::msm::backmp11 { struct favor_runtime_speed @@ -172,8 +171,6 @@ struct compile_policy_impl< } private: - using cell_t = process_result (*)(StateMachine&, int /*region_id*/, Event const&); - // All dispatch tables are friend with each other to check recursively // whether forward transitions are required. template @@ -360,78 +357,39 @@ struct compile_policy_impl< { using base = dispatch_base; public: - template - struct mp_indexed_dispatch_impl - { - using list = mp11::mp_list; - static constexpr std::size_t size = sizeof...(Ts); - - template - static constexpr process_result invoke(int state_id, F&& func) - { - return dispatch(state_id, func, - std::make_index_sequence{}); - } - - private: - // For position I in the list, get the corresponding state_id - // at compile time. - template - static constexpr int state_id_at = - StateMachine::template get_state_id< - typename mp11::mp_at_c::current_state_type>(); - - // Single case handler: invoke func with the I-th transition - template - static constexpr process_result handle_case(F& func) - { - return func(mp11::mp_at_c{}); - } - - // Generate branches comparing state_id against compile-time - // state_id_at for each position I in the list. - template - static constexpr process_result dispatch(int state_id, F& func, - std::index_sequence) - { - process_result result = process_result::HANDLED_FALSE; - // Each branch compares the runtime state_id against the - // compile-time state_id of the I-th transition. - // State IDs may be non-contiguous (e.g., 0, 2, 5). - (void)((state_id == state_id_at && - (result = handle_case(func), true)) || ...); - return result; - } - }; - - template - static constexpr process_result mp_indexed_dispatch(int state_id, F&& func) - { - return mp11::mp_apply::invoke( - state_id, std::forward(func)); - } - static inline process_result dispatch(StateMachine& sm, int region_id, const Event& event) { const int state_id = sm.m_active_state_ids[region_id]; - return mp_indexed_dispatch(state_id, - [&sm, region_id, &event](auto transition) -> process_result + process_result result = process_result::HANDLED_FALSE; + mp11::mp_for_each( + [&sm, region_id, &event, state_id, &result](auto transition) { using Transition = decltype(transition); using TransitionEvent = typename Transition::transition_event; - if constexpr (!is_kleene_event::value) - { - return Transition::execute(sm, region_id, event); - } - else + using SourceState = + typename Transition::current_state_type; + constexpr int source_state_id = + StateMachine::template get_state_id(); + if (state_id == source_state_id) { - return base::template - convert_event_and_execute( - sm, region_id, event); + 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); + } } - }); + } + ); + return result; } }; @@ -440,6 +398,9 @@ struct compile_policy_impl< : public dispatch_base { using base = dispatch_base; + using cell_t = process_result (*)(StateMachine&, int /*region_id*/, + Event const&); + public: static process_result dispatch( StateMachine& sm, int region_id, const Event& event) @@ -557,7 +518,7 @@ struct compile_policy_impl< }; } // detail -}}} // boost::msm::backmp11 +} // boost::msm::backmp11 -#endif // BOOST_MSM_BACKMP11_DETAIL_FAVOR_RUNTIME_SPEED_H +#endif // BOOST_MSM_BACKMP11_DETAIL_FAVOR_RUNTIME_SPEED_HPP diff --git a/include/boost/msm/backmp11/detail/history_impl.hpp b/include/boost/msm/backmp11/detail/history_impl.hpp index 615de98b..2794fc1b 100644 --- a/include/boost/msm/backmp11/detail/history_impl.hpp +++ b/include/boost/msm/backmp11/detail/history_impl.hpp @@ -9,18 +9,14 @@ // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_MSM_BACKMP11_HISTORY_IMPL_H -#define BOOST_MSM_BACKMP11_HISTORY_IMPL_H +#ifndef BOOST_MSM_BACKMP11_DETAIL_HISTORY_IMPL_HPP +#define BOOST_MSM_BACKMP11_DETAIL_HISTORY_IMPL_HPP #include #include -namespace boost::msm::backmp11 +namespace boost::msm::backmp11::detail { -namespace detail -{ - -// Implementations for history policies. template class history_impl; @@ -138,8 +134,6 @@ class history_impl, NumberOfRegions> std::array m_last_active_state_ids; }; -} // detail - } // boost::msm::backmp11 -#endif // BOOST_MSM_BACKMP11_HISTORY_IMPL_H +#endif // BOOST_MSM_BACKMP11_DETAIL_HISTORY_IMPL_HPP diff --git a/include/boost/msm/backmp11/detail/metafunctions.hpp b/include/boost/msm/backmp11/detail/metafunctions.hpp index d29dff45..5f62f046 100644 --- a/include/boost/msm/backmp11/detail/metafunctions.hpp +++ b/include/boost/msm/backmp11/detail/metafunctions.hpp @@ -9,8 +9,8 @@ // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_MSM_BACKMP11_METAFUNCTIONS_H -#define BOOST_MSM_BACKMP11_METAFUNCTIONS_H +#ifndef BOOST_MSM_BACKMP11_DETAIL_METAFUNCTIONS_HPP +#define BOOST_MSM_BACKMP11_DETAIL_METAFUNCTIONS_HPP #include #include @@ -36,10 +36,7 @@ namespace boost::mpl struct is_sequence; } -namespace boost { namespace msm { namespace backmp11 -{ - -namespace detail +namespace boost::msm::backmp11::detail { // Call a functor on all elements of List, until the functor returns true. @@ -336,8 +333,6 @@ struct is_state_blocking_impl template using is_state_blocking = typename is_state_blocking_impl::type; -} // detail - -}}} // boost::msm::backmp11 +} // boost::msm::backmp11::detail -#endif // BOOST_MSM_BACKMP11_METAFUNCTIONS_H +#endif // BOOST_MSM_BACKMP11_DETAIL_METAFUNCTIONS_HPP diff --git a/include/boost/msm/backmp11/detail/state_machine_base.hpp b/include/boost/msm/backmp11/detail/state_machine_base.hpp new file mode 100644 index 00000000..9f8631c4 --- /dev/null +++ b/include/boost/msm/backmp11/detail/state_machine_base.hpp @@ -0,0 +1,1149 @@ +// 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_DETAIL_STATE_MACHINE_BASE_HPP +#define BOOST_MSM_BACKMP11_DETAIL_STATE_MACHINE_BASE_HPP + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost::msm::backmp11::detail +{ + +template +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; + + // 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 {}; + + // 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; + }; + using state = ExitPseudostate; + using owner = derived_t; + using event = typename ExitPseudostate::event; + using forward_fn_t = void (*)(void* /*root_sm*/, const void* /*event*/); + + template + void init() + { + m_forward_fn = &call_enqueue_event; + } + + // forward event to the root sm. + template + void forward_event(void* root_sm, const ForwardEvent& forward_event) + { + 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_fn) + { + m_forward_fn(root_sm, &forward_event); + } + } + + private: + template + static void call_enqueue_event(void* root_sm, const void* event) + { + static_cast(root_sm)->enqueue_event( + *static_cast(event)); + } + + forward_fn_t m_forward_fn{}; + }; + + // 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; + }; + + using state = EntryPseudostate; + using owner = derived_t; + }; + + // 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; + }; + using state = State; + using owner = derived_t; + }; + + struct internal + { + using tag = state_machine_tag; + + using initial_states = to_mp_list_t; + static constexpr int nr_regions = mp11::mp_size::value; + + using state_set = generate_state_set; + using submachines = mp11::mp_copy_if; + }; + + using states_t = mp11::mp_rename; + + protected: + using processable_event = basic_polymorphic_value; + template + using event_container = typename config_t::template event_container; + using event_container_t = event_container; + + struct event_pool_t + { + event_container_t events; + uint16_t cur_seq_cnt{}; + }; + + using event_pool_member = optional_instance< + event_pool_t, + !std::is_same_v, no_event_container>>; + + template > + event_pool_t& get_event_pool() + { + return m_optional_members.template get(); + } + + template > + const event_pool_t& get_event_pool() const + { + return m_optional_members.template get(); + } + + 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_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 = + mp11::mp_eval_or; + + template + friend class state_machine_base; + + template + friend struct transition_table_impl; + + template + friend struct detail::compile_policy_impl; + + template typename...> + friend class state_visitor_impl; + template typename...> + friend class state_visitor_base_impl; + template typename...> + friend class event_deferral_visitor; + template + friend class init_state_visitor; + + template + friend class deferred_event; + + // 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 = + mp11::mp_eval_or; + + template + using get_final_event = typename T::final_event; + using fsm_final_event = + mp11::mp_eval_or; + + using state_map = generate_state_map; + using history_impl = detail::history_impl; + + using context_member = + optional_instance && + (std::is_same_v || + std::is_same_v)>; + + // Visit states with a compile-time filter (reduces template instantiations). + // Kept private for now, because the API of this method is not stable yet + // and this optimization is likely not needed to be available in the public API. + template typename... Predicates, typename Visitor> + void visit_if(Visitor&& visitor) + { + using state_visitor = + state_visitor; + state_visitor::visit(self(), visitor); + } + template typename... Predicates, typename Visitor> + void visit_if(Visitor&& visitor) const + { + using state_visitor = + state_visitor; + state_visitor::visit(self(), visitor); + } + + public: + // Construct and forward constructor arguments to the front-end. + template + state_machine_base(Args&&... args) + : front_end_t(std::forward(args)...) + { + static_assert( + std::is_base_of_v, + "Derived must inherit from state_machine"); + if constexpr (!std::is_same_v) + { + static_assert( + std::is_constructible_v, + "Derived must inherit the base class constructors"); + } + if constexpr (std::is_same_v || + std::is_same_v) + { + m_root_sm = this; + using visitor_t = init_state_visitor; + visitor_t visitor{self()}; + visit_if(visitor); + } + reset_active_state_ids(); + } + + // Construct with a context and forward further constructor arguments to the front-end. + template , + typename... Args> + state_machine_base(context_t& context, Args&&... args) + : state_machine_base(std::forward(args)...) + { + m_optional_members.template get() = &context; + if constexpr (std::is_same_v) + { + visit_if( + [&context](auto &state_machine) + { + state_machine.m_optional_members.template get() = &context; + }); + } + } + + // Copy constructor. + state_machine_base(state_machine_base const& rhs) + : front_end_t(rhs) + { + if constexpr (std::is_same_v || + std::is_same_v) + { + m_root_sm = this; + using visitor_t = init_state_visitor; + visitor_t visitor{self()}; + visit_if(visitor); + } + // Copy all members except the root sm pointer. + m_active_state_ids = rhs.m_active_state_ids; + m_optional_members = rhs.m_optional_members; + m_history = rhs.m_history; + m_event_processing = rhs.m_event_processing; + m_states = rhs.m_states; + m_running = rhs.m_running; + } + + // Copy assignment operator. + state_machine_base& operator= (state_machine_base const& rhs) + { + if (this != &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; + m_history = rhs.m_history; + m_event_processing = rhs.m_event_processing; + m_states = rhs.m_states; + m_running = rhs.m_running; + } + return *this; + } + + // Start the state machine (calls entry of the initial state(s)). + void start() + { + // Assert for a case where root sm was not set up correctly + // after construction. + if constexpr (!std::is_same_v) + { + BOOST_ASSERT_MSG(&(this->get_root_sm()), + "Root sm must be passed as Derived and configured as root_sm"); + } + start(fsm_initial_event{}); + } + + // Start the state machine + // (calls entry of the initial state(s) with initial_event). + template + void start(Event const& initial_event) + { + if (!m_running) + { + on_entry(initial_event, get_fsm_argument()); + } + } + + // Stop the state machine (calls exit of the current state(s)). + void stop() + { + stop(fsm_final_event{}); + } + + // Stop the state machine + // (calls exit of the current state(s) with final_event). + template + void stop(Event const& final_event) + { + if (m_running) + { + on_exit(final_event, get_fsm_argument()); + m_running = false; + } + } + + // Main function to process events. + template + process_result process_event(Event const& event) + { + return process_event_internal( + compile_policy_impl::normalize_event(event), + process_info::direct_call); + } + + // Try to process pending event occurrences in the event pool, + // with an optional limit for the max no. of events that shall be processed. + // Returns the no. of processed events. + template > + inline size_t process_event_pool(size_t max_events = SIZE_MAX) + { + if (get_event_pool().events.empty()) + { + return 0; + } + return do_process_event_pool(max_events); + } + + // Enqueues an event in the event pool for later processing. + // If the state machine is already processing, the event will be processed + // after the current event completes. + template > + void enqueue_event(Event const& event) + { + compile_policy_impl::defer_event( + *this, compile_policy_impl::normalize_event(event), false); + } + + // Process all queued events. + template > + [[deprecated ("Use process_event_pool() instead")]] + void process_queued_events() + { + process_event_pool(); + } + + // Process a single queued event. + template > + [[deprecated ("Use process_event_pool(1) instead")]] + void process_single_queued_event() + { + process_event_pool(1); + } + + // Get the context of the state machine. + template , + typename = std::enable_if_t> + context_t& get_context() + { + if constexpr (context_member::value) + { + return *m_optional_members.template get(); + } + else + { + return get_root_sm().get_context(); + } + } + + // Get the context of the state machine. + template , + typename = std::enable_if_t> + const context_t& get_context() const + { + if constexpr (context_member::value) + { + return *m_optional_members.template get(); + } + else + { + return get_root_sm().get_context(); + } + } + + // Getter that returns the currently active state ids of the FSM. + const active_state_ids_t& get_active_state_ids() const + { + return m_active_state_ids; + } + + // Get the root sm. + template >> + root_sm_t& get_root_sm() + { + return *static_cast(m_root_sm); + } + // Get the root sm. + template >> + const root_sm_t& get_root_sm() const + { + return *static_cast(m_root_sm); + } + + // Return the id of a state in the sm. + template + static constexpr int get_state_id(const State&) + { + static_assert( + mp11::mp_map_contains::value, + "The state must be contained in the state machine"); + return detail::get_state_id::value; + } + // Return the id of a state in the sm. + template + static constexpr int get_state_id() + { + static_assert( + mp11::mp_map_contains::value, + "The state must be contained in the state machine"); + return detail::get_state_id::value; + } + + // True if the sm is used in another sm. + bool is_contained() const + { + return (static_cast(this) != m_root_sm); + } + + // Get a state. + template + State& get_state() + { + return std::get>(m_states); + } + // Get a state. + template + const State& get_state() const + { + return std::get>(m_states); + } + + // Visit the states (only active states, recursive). + template + void visit(Visitor&& visitor) + { + visit(std::forward(visitor)); + } + + // Visit the states (only active states, recursive). + template + void visit(Visitor&& visitor) const + { + visit(std::forward(visitor)); + } + + // Visit the states. + // How to traverse is selected with visit_mode. + template + void visit(Visitor&& visitor) + { + visit_if(std::forward(visitor)); + } + + // Visit the states. + // How to traverse is selected with visit_mode. + template + void visit(Visitor&& visitor) const + { + visit_if(std::forward(visitor)); + } + + // Check whether a state is currently active. + template + bool is_state_active() const + { + using visitor_t = is_state_active_visitor; + visitor_t visitor; + visit_if(visitor); + return visitor.result(); + } + + // Check if a flag is active, using the BinaryOp as folding function. + template + bool is_flag_active() const + { + using visitor_t = is_flag_active_visitor; + visitor_t visitor; + visit_if(visitor); + return visitor.result(); + } + + // Puts the event into the event pool for later processing. + // If the deferral takes place while the state machine is processing, + // the event will be evaluated for dispatch from the next processing cycle. + template < + class Event, + bool C = event_pool_member::value, + typename = std::enable_if_t> + void defer_event(Event const& event) + { + compile_policy_impl::defer_event( + *this, compile_policy_impl::normalize_event(event), m_event_processing); + } + + protected: + static_assert(std::is_same_v || + (std::is_same_v && + !std::is_same_v), + "fsm_parameter must be local_transition_owner or root_sm" + ); + using fsm_parameter_t = mp11::mp_if_c< + std::is_same_v, + derived_t, + typename config_t::root_sm>; + + const fsm_parameter_t& get_fsm_argument() const + { + if constexpr (std::is_same_v) + { + return self(); + } + else + { + return get_root_sm(); + } + } + + fsm_parameter_t& get_fsm_argument() + { + return const_cast + (static_cast(*this).get_fsm_argument()); + } + + template + bool is_event_deferred(const Event& event) const + { + return compile_policy_impl::is_event_deferred(self(), event); + } + + // Repetition of the front-end's method definition + // required due to above signature. + template + bool is_event_deferred(const Event& event, Fsm& fsm) const + { + return static_cast(this)->is_event_deferred(event, + fsm); + } + + // Checks if an event is an end interrupt event. + template + bool is_end_interrupt_event(const Event& event) const + { + return compile_policy_impl::is_end_interrupt_event(*this, event); + } + + // Helpers used to reset the state machine. + void reset_active_state_ids() + { + size_t index = 0; + mp11::mp_for_each( + [this, &index](auto state_identity) + { + using State = typename decltype(state_identity)::type; + m_active_state_ids[index++] = get_state_id(); + }); + m_history.reset_active_state_ids(m_active_state_ids); + } + + // Main function used internally to process events. + // Explicitly not inline, because code size can significantly increase if + // this method is inlined in all existing process_info variants. + template + BOOST_NOINLINE process_result process_event_internal(Event const& event, + process_info info) + { + // If the state machine has terminate or interrupt flags, check them. + if constexpr (mp11::mp_any_of::value) + { + // If the state machine is terminated, do not handle any event. + if (is_flag_active()) + { + return process_result::HANDLED_TRUE; + } + + // If the state machine is interrupted, do not handle any event + // unless the event is the end interrupt event. + if (is_flag_active() && + !is_end_interrupt_event(event)) + { + return process_result::HANDLED_TRUE; + } + } + + if constexpr (event_pool_member::value) + { + if (info != process_info::event_pool) + { + // If we are already processing or the event is deferred in the + // active state configuration, process it later. + // Skip the deferral check in submachine calls, since the + // parent has already checked and dispatched the event. + if (m_event_processing || + (info != process_info::submachine_call && + compile_policy_impl::is_event_deferred(self(), event))) + { + compile_policy_impl::defer_event(self(), event, false); + return process_result::HANDLED_DEFERRED; + } + + // Ensure we consider an event + // that was action-deferred in the last sequence. + get_event_pool().cur_seq_cnt += 1; + } + } + else + { + BOOST_ASSERT_MSG(!m_event_processing, + "An event pool must be available to call " + "process_event while processing an event"); + } + + // Process the event. + m_event_processing = true; + process_result result; +#ifndef BOOST_NO_EXCEPTIONS + if constexpr (has_no_exception_thrown::value) + { + result = do_process_event(event, info); + } + else + { + try + { + result = do_process_event(event, info); + } + catch (std::exception& e) + { + // give a chance to the concrete state machine to handle + this->exception_caught(event, get_fsm_argument(), e); + result = process_result::HANDLED_FALSE; + } + } +#else + result = do_process_event(event, info); +#endif + m_event_processing = false; + + // After handling, look if we have more to process in the event pool + // (but only if we're not already processing from it). + if constexpr (event_pool_member::value) + { + if (info != process_info::event_pool) + { + process_event_pool(); + } + } + + return result; + } + + private: + // Core logic for event processing without exceptions, queues, etc. + template + process_result do_process_event(Event const& event, process_info info) + { + using dispatch_table = + typename compile_policy_impl::template dispatch_table; + process_result result = process_result::HANDLED_FALSE; + // Dispatch the event to every region. + for (int region_id = 0; region_id < nr_regions; region_id++) + { + result |= dispatch_table::dispatch(self(), region_id, event); + } + // Dispatch the event to the SM-internal table if it hasn't been consumed yet. + if (!(result & handled_true_or_deferred)) + { + result |= dispatch_table::internal_dispatch(self(), event); + } + + // If the event has not been handled and we have orthogonal zones, then + // generate an error on every active state. + // For events coming from upper machines, do not handle + // but let the upper sm handle the error. + if (!result && !(info == process_info::submachine_call)) + { + for (const auto state_id: m_active_state_ids) + { + this->no_transition(event, get_fsm_argument(), state_id); + } + } + return result; + } + + // MSCV Bug: + // Compile error if this class is named completion_event. + template + class completion_event_occurrence : public event_occurrence + { + // Merge each list of transitions into a chain if needed. + template + struct merge_transitions_impl; + template + struct merge_transitions_impl> + { + using type = Transition; + }; + template + struct merge_transitions_impl> + { + using list = mp11::mp_list; + using completion_event = + typename mp11::mp_first::transition_event; + using type = + transition_chain; + }; + template + using merge_transitions = + typename merge_transitions_impl::type; + using completion_transitions = + detail::completion_transitions; + using completion_transition = merge_transitions; + + public: + completion_event_occurrence(int region_id) + : event_occurrence(&try_process), m_region_id(region_id) + { + } + + static std::optional try_process(event_occurrence& self, void* sm, uint16_t /*seq_cnt*/) + { + return static_cast(&self) + ->try_process_impl(*reinterpret_cast(sm)); + } + + private: + std::optional try_process_impl(derived_t& sm) + { + mark_for_deletion(); + return sm.template + process_completion_transition(m_region_id); + } + + int m_region_id; + }; + + template + process_result process_completion_transition(int region_id) + { + // If the state machine has terminate or interrupt flags, check them. + if constexpr (mp11::mp_any_of::value) + { + // If the state machine is interrupted or terminated, do not handle any event. + if (is_flag_active() || is_flag_active()) + { + return process_result::HANDLED_TRUE; + } + } + + // Process the event. + using completion_event = typename Transition::transition_event; + completion_event event{}; + m_event_processing = true; + process_result result; +#ifndef BOOST_NO_EXCEPTIONS + if constexpr (has_no_exception_thrown::value) + { + result = Transition::execute(self(), region_id, event); + } + else + { + try + { + result = Transition::execute(self(), region_id, event); + } + catch (std::exception& e) + { + // give a chance to the concrete state machine to handle + this->exception_caught(event, get_fsm_argument(), e); + } + } +#else + result = Transition::execute(self(), state_id, event); +#endif + m_event_processing = false; + return result; + } + + // Core logic for event pool processing, + // there must be at least one event in the pool. + // Explicitly not inline, because code size can significantly increase if + // this method's content is inlined in all entries and process_event calls. + template > + BOOST_NOINLINE size_t do_process_event_pool(size_t max_events = SIZE_MAX) + { + event_pool_t& event_pool = get_event_pool(); + auto it = event_pool.events.begin(); + size_t processed_events = 0; + do + { + event_occurrence& event = **it; + // The event was already processed. + if (event.marked_for_deletion()) + { + it = event_pool.events.erase(it); + continue; + } + + std::optional result = + event.try_process(self(), event_pool.cur_seq_cnt); + // The event has not been dispatched. + if (!result.has_value()) + { + it++; + continue; + } + + // Consider anything except "only deferred" to be a processed event. + if (*result != process_result::HANDLED_DEFERRED) + { + processed_events++; + if (processed_events == max_events) + { + break; + } + } + + // Start from the beginning, we might be able to process + // events that were deferred before. + it = event_pool.events.begin(); + // Consider newly deferred events only if + // the event was not deferred at the same time + // (required to prevent infinitely processing the same event, + // if it was handled and at the same time action-deferred + // in orthogonal regions). + if (!(*result & process_result::HANDLED_DEFERRED)) + { + event_pool.cur_seq_cnt += 1; + } + } while (it != event_pool.events.end()); + return processed_events; + } + + template + void do_defer_event(const Event& event, bool next_rtc_seq) + { + auto& event_pool = get_event_pool(); + const uint16_t seq_cnt = next_rtc_seq ? event_pool.cur_seq_cnt + : event_pool.cur_seq_cnt - 1; + event_pool.events.push_back(processable_event::make( + deferred_event{self(), event, seq_cnt})); + } + + 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); + } + + void postprocess_entry() + { + m_event_processing = false; + + // After handling, look if we have more to process in the event pool. + if constexpr (event_pool_member::value) + { + process_event_pool(); + } + } + + template + class state_entry_visitor + { + public: + state_entry_visitor(derived_t& self, const Event& event) + : m_self(self), m_event(event) + { + } + + template + void operator()(State& state) + { + state.on_entry(m_event, m_self.get_fsm_argument()); + m_self.template on_state_entry_completed(m_region_id++); + } + + private: + derived_t& m_self; + const Event& m_event; + int m_region_id{}; + }; + + + template + void on_entry(Event const& event, Fsm& fsm) + { + preprocess_entry(event, fsm); + + // First set all active state ids... + m_active_state_ids = m_history.on_entry(event); + // ... then execute each state entry. + state_entry_visitor visitor{self(), event}; + if constexpr (std::is_same_v) + { + mp11::mp_for_each( + [this, &visitor](auto state_identity) + { + using State = typename decltype(state_identity)::type; + auto& state = this->get_state(); + visitor(state); + }); + } + else + { + visit(visitor); + } + + 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. + state_entry_visitor visitor{self(), event}; + if constexpr (all_regions_defined) + { + mp11::mp_for_each( + [this, &visitor](auto state_identity) + { + using State = typename decltype(state_identity)::type; + auto& state = this->get_state(); + visitor(state); + }); + } + else + { + visit(visitor); + } + + 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_state_entry_completed(int region_id) + { + // Exclude composite states from completion transitions, + // these should fire when all their regions reach a final state + // (and final states do not exist yet). + if constexpr( + !is_composite::value && + has_completion_transitions::value) + { + auto& event_pool = get_event_pool(); + // Process completion transitions BEFORE any other event in the + // pool (UML Standard 2.3 15.3.14). + event_pool.events.push_front( + processable_event::make( + completion_event_occurrence{region_id})); + } + } + + template + void on_exit(Event const& event, Fsm& fsm) + { + // First exit the substates. + visit( + [this, &event](auto& state) + { + state.on_exit(event, get_fsm_argument()); + } + ); + // Then call our own exit. + (static_cast(this))->on_exit(event, fsm); + // Give the history a chance to handle this (or not). + m_history.on_exit(this->m_active_state_ids); + // History decides what happens with the event pool. + if (m_history.clear_event_pool(event)) + { + if constexpr (event_pool_member::value) + { + get_event_pool().events.clear(); + } + } + } + + derived_t& self() + { + return *static_cast(this); + } + + const derived_t& self() const + { + return *static_cast(this); + } + + struct optional_members : + event_pool_member, + context_member + { + template + typename T::type& get() + { + return static_cast(this)->instance; + } + template + const typename T::type& get() const + { + return static_cast(this)->instance; + } + }; + + active_state_ids_t m_active_state_ids; + optional_members m_optional_members; + history_impl m_history{}; + bool m_event_processing{false}; + void* m_root_sm{nullptr}; + states_t m_states{}; + bool m_running{false}; +}; + +} // boost::msm::backmp11::detail + +#endif // BOOST_MSM_BACKMP11_DETAIL_STATE_MACHINE_BASE_HPP diff --git a/include/boost/msm/backmp11/detail/state_visitor.hpp b/include/boost/msm/backmp11/detail/state_visitor.hpp index be0bb81b..c910dc6a 100644 --- a/include/boost/msm/backmp11/detail/state_visitor.hpp +++ b/include/boost/msm/backmp11/detail/state_visitor.hpp @@ -12,7 +12,6 @@ #ifndef BOOST_MSM_BACKMP11_DETAIL_STATE_VISITOR_HPP #define BOOST_MSM_BACKMP11_DETAIL_STATE_VISITOR_HPP -#include #include #include @@ -197,61 +196,27 @@ class state_visitor_impl< { if (sm.m_running) { - const state_visitor_impl& self = instance(); - for (const int state_id : sm.m_active_state_ids) + using state_identities = mp11::mp_transform< + mp11::mp_identity, + typename base::states_to_traverse>; + for (const int active_state_id : sm.m_active_state_ids) { - self.dispatch(sm, state_id, visitor); + mp11::mp_for_each( + [&sm, &visitor, active_state_id](auto state_identity) + { + using State = + typename decltype(state_identity)::type; + constexpr int state_id = + StateMachine::template get_state_id(); + if (active_state_id == state_id) + { + base::template accept(sm, visitor); + } + }); } } } } - - // Bug: Clang 17 complains about private member if this method is private. - template - static void accept(StateMachine& sm, Visitor& visitor) - { - base::template accept(sm, visitor); - } - - private: - using state_set = typename StateMachine::internal::state_set; - using cell_t = void (*)(StateMachine&, Visitor&); - template - using init_cell_constant = init_cell_constant; - - template - using get_init_cell_constant = init_cell_constant< - StateMachine::template get_state_id(), - &accept>; - - state_visitor_impl() - { - using init_cell_constants = mp11::mp_transform< - get_init_cell_constant, - typename base::states_to_traverse>; - dispatch_table_initializer::execute( - reinterpret_cast(m_cells), - reinterpret_cast( - value_array), - mp11::mp_size::value); - } - - void dispatch(StateMachine& sm, int state_id, Visitor& visitor) const - { - const cell_t& cell = m_cells[state_id]; - if (cell) - { - (*cell)(sm, visitor); - } - } - - static const state_visitor_impl& instance() - { - static const state_visitor_impl instance; - return instance; - } - - cell_t m_cells[mp11::mp_size::value]{}; }; template < @@ -325,15 +290,27 @@ class event_deferral_visitor "The visitor must have at least one state to visit"); if (sm.m_running) { - const event_deferral_visitor& self = instance(); - for (const int state_id : sm.m_active_state_ids) + using state_identities = mp11::mp_transform< + mp11::mp_identity, + typename visit_set::states_to_traverse>; + for (const int active_state_id : sm.m_active_state_ids) { - self.dispatch(sm, state_id, visitor); + mp11::mp_for_each( + [&sm, &visitor, active_state_id](auto state_identity) + { + using State = typename decltype(state_identity)::type; + constexpr int state_id = + StateMachine::template get_state_id(); + if (active_state_id == state_id) + { + accept(sm, visitor); + } + }); } } } - // Bug: Clang 17 complains about private member if this method is private. + private: template static void accept(StateMachine& sm, Visitor& visitor) { @@ -353,46 +330,6 @@ class event_deferral_visitor submachine_visitor::visit(state, visitor); } } - - private: - using state_set = typename StateMachine::internal::state_set; - using cell_t = void (*)(StateMachine&, Visitor&); - template - using init_cell_constant = init_cell_constant; - - template - using get_init_cell_constant = init_cell_constant< - StateMachine::template get_state_id(), - &accept>; - - event_deferral_visitor() - { - using init_cell_constants = mp11::mp_transform< - get_init_cell_constant, - typename visit_set::states_to_traverse>; - dispatch_table_initializer::execute( - reinterpret_cast(m_cells), - reinterpret_cast( - value_array), - mp11::mp_size::value); - } - - void dispatch(StateMachine& sm, int state_id, Visitor& visitor) const - { - const cell_t& cell = m_cells[state_id]; - if (cell) - { - (*cell)(sm, visitor); - } - } - - static const event_deferral_visitor& instance() - { - static const event_deferral_visitor instance; - return instance; - } - - cell_t m_cells[mp11::mp_size::value]{}; }; // Predefined visitor functors used in backmp11. @@ -499,12 +436,7 @@ class init_state_visitor { if constexpr (has_exit_pseudostate_be_tag::value) { - state.set_forward_fct( - [&root_sm = m_root_sm](typename State::event const& event) - { - return root_sm.enqueue_event(event); - } - ); + state.template init(); } if constexpr (has_state_machine_tag::value) diff --git a/include/boost/msm/backmp11/detail/transition_table.hpp b/include/boost/msm/backmp11/detail/transition_table.hpp index 93b24a03..41775a7f 100644 --- a/include/boost/msm/backmp11/detail/transition_table.hpp +++ b/include/boost/msm/backmp11/detail/transition_table.hpp @@ -213,7 +213,7 @@ struct transition_table_impl if constexpr (has_exit_pseudostate_be_tag::value) { // Execute the second part of the compound transition. - target.forward_event(event); + target.forward_event(sm.m_root_sm, event); } } } diff --git a/include/boost/msm/backmp11/event_traits.hpp b/include/boost/msm/backmp11/event_traits.hpp index 4e552611..e4a24ebf 100644 --- a/include/boost/msm/backmp11/event_traits.hpp +++ b/include/boost/msm/backmp11/event_traits.hpp @@ -9,8 +9,8 @@ // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_MSM_BACKMP11_EVENT_TRAITS_H -#define BOOST_MSM_BACKMP11_EVENT_TRAITS_H +#ifndef BOOST_MSM_BACKMP11_EVENT_TRAITS_HPP +#define BOOST_MSM_BACKMP11_EVENT_TRAITS_HPP #include #include @@ -35,4 +35,4 @@ using std::any_cast; } // boost::msm::backmp11 -#endif //BOOST_MSM_EVENT_TRAITS_H +#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 379c2723..342ad35c 100644 --- a/include/boost/msm/backmp11/favor_compile_time.hpp +++ b/include/boost/msm/backmp11/favor_compile_time.hpp @@ -9,17 +9,15 @@ // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_MSM_BACKMP11_FAVOR_COMPILE_TIME_H -#define BOOST_MSM_BACKMP11_FAVOR_COMPILE_TIME_H +#ifndef BOOST_MSM_BACKMP11_FAVOR_COMPILE_TIME_HPP +#define BOOST_MSM_BACKMP11_FAVOR_COMPILE_TIME_HPP -#include #include #include #include #include #include -#include #include #include #include @@ -39,7 +37,7 @@ } \ } // boost::msm::backmp11::detail -namespace boost { namespace msm { namespace backmp11 +namespace boost:: msm::backmp11 { struct favor_compile_time @@ -54,6 +52,9 @@ namespace detail template <> struct compile_policy_impl { + // Type-punned init cell value to suppress redundant template instantiations. + using generic_cell = void(*)(); + // GCC cannot recognize the parameter pack in the array's initializer. #if !defined(__GNUC__) || defined(__clang__) // Convert a list with integral constants of the same value_type to a value array @@ -80,11 +81,23 @@ struct compile_policy_impl return event; } - template - static bool is_end_interrupt_event(Statemachine& sm, const any_event& event) + template + static bool is_end_interrupt_event(const StateMachine& sm, const any_event& event) { - static end_interrupt_event_helper helper{sm}; - return helper.is_end_interrupt_event(event); + using event_set = generate_event_set< + typename StateMachine::front_end_t::transition_table>; + bool result{false}; + mp11::mp_for_each>( + [&sm, &event, &result](auto event_identity) + { + using Event = typename decltype(event_identity)::type; + using Flag = EndInterruptFlag; + if (event.type() == typeid(Event)) + { + result = sm.template is_flag_active(); + } + }); + return result; } // Dispatch table for event deferral checks. @@ -185,39 +198,6 @@ struct compile_policy_impl return std::type_index{typeid(Event)}; } - // Helper class to manage end interrupt events. - class end_interrupt_event_helper - { - public: - template - end_interrupt_event_helper(const StateMachine& sm) - { - using event_set = generate_event_set< - typename StateMachine::front_end_t::transition_table>; - mp11::mp_for_each>( - [this, &sm](auto event_identity) - { - using Event = typename decltype(event_identity)::type; - using Flag = EndInterruptFlag; - m_is_flag_active_functions[to_type_index()] = - [&sm](){return sm.template is_flag_active();}; - }); - } - - bool is_end_interrupt_event(const any_event& event) const - { - auto it = m_is_flag_active_functions.find(event.type()); - if (it != m_is_flag_active_functions.end()) - { - return (it->second)(); - } - return false; - } - - private: - using map = std::unordered_map>; - map m_is_flag_active_functions; - }; // Class used to build a chain of transitions for a given event and state. // Allows transition conflicts. @@ -553,6 +533,6 @@ compile_policy_impl::dispatch_table #endif } // detail -}}} // boost::msm::backmp11 +} // boost::msm::backmp11 -#endif //BOOST_MSM_BACKMP11_FAVOR_COMPILE_TIME_H +#endif //BOOST_MSM_BACKMP11_FAVOR_COMPILE_TIME_HPP diff --git a/include/boost/msm/backmp11/state_machine.hpp b/include/boost/msm/backmp11/state_machine.hpp index 4629c0e0..b50395b4 100644 --- a/include/boost/msm/backmp11/state_machine.hpp +++ b/include/boost/msm/backmp11/state_machine.hpp @@ -9,1153 +9,13 @@ // 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 +#ifndef BOOST_MSM_BACKMP11_STATE_MACHINE_HPP +#define BOOST_MSM_BACKMP11_STATE_MACHINE_HPP -#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 -{ - -template < - class FrontEnd, - class Config, - class Derived -> -class state_machine_base : public FrontEnd +namespace boost::msm::backmp11 { - 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; - - // 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 {}; - - // 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; - }; - 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) - { - 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() = 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&) - { - return *this; - } - - private: - forward_function m_forward; - }; - - // 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; - }; - - using state = EntryPseudostate; - using owner = derived_t; - }; - - // 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; - }; - using state = State; - using owner = derived_t; - }; - - struct internal - { - using tag = state_machine_tag; - - using initial_states = to_mp_list_t; - static constexpr int nr_regions = mp11::mp_size::value; - - using state_set = generate_state_set; - using submachines = mp11::mp_copy_if; - }; - - using states_t = mp11::mp_rename; - - protected: - using processable_event = basic_polymorphic_value; - template - using event_container = typename config_t::template event_container; - using event_container_t = event_container; - - struct event_pool_t - { - event_container_t events; - uint16_t cur_seq_cnt{}; - }; - - using event_pool_member = optional_instance< - event_pool_t, - !std::is_same_v, no_event_container>>; - - template > - event_pool_t& get_event_pool() - { - return m_optional_members.template get(); - } - - template > - const event_pool_t& get_event_pool() const - { - return m_optional_members.template get(); - } - - 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_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 = - mp11::mp_eval_or; - - template - friend class state_machine_base; - - template - friend struct transition_table_impl; - - template - friend struct detail::compile_policy_impl; - - template typename...> - friend class state_visitor_impl; - template typename...> - friend class state_visitor_base_impl; - template typename...> - friend class event_deferral_visitor; - template - friend class init_state_visitor; - - template - friend class deferred_event; - - // 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 = - mp11::mp_eval_or; - - template - using get_final_event = typename T::final_event; - using fsm_final_event = - mp11::mp_eval_or; - - using state_map = generate_state_map; - using history_impl = detail::history_impl; - - using context_member = - optional_instance && - (std::is_same_v || - std::is_same_v)>; - - // Visit states with a compile-time filter (reduces template instantiations). - // Kept private for now, because the API of this method is not stable yet - // and this optimization is likely not needed to be available in the public API. - template typename... Predicates, typename Visitor> - void visit_if(Visitor&& visitor) - { - using state_visitor = - state_visitor; - state_visitor::visit(self(), visitor); - } - template typename... Predicates, typename Visitor> - void visit_if(Visitor&& visitor) const - { - using state_visitor = - state_visitor; - state_visitor::visit(self(), visitor); - } - - public: - // Construct and forward constructor arguments to the front-end. - template - state_machine_base(Args&&... args) - : front_end_t(std::forward(args)...) - { - static_assert( - std::is_base_of_v, - "Derived must inherit from state_machine"); - if constexpr (!std::is_same_v) - { - static_assert( - std::is_constructible_v, - "Derived must inherit the base class constructors"); - } - if constexpr (std::is_same_v || - std::is_same_v) - { - m_root_sm = this; - using visitor_t = init_state_visitor; - visitor_t visitor{self()}; - visit_if(visitor); - } - reset_active_state_ids(); - } - - // Construct with a context and forward further constructor arguments to the front-end. - template , - typename... Args> - state_machine_base(context_t& context, Args&&... args) - : state_machine_base(std::forward(args)...) - { - m_optional_members.template get() = &context; - if constexpr (std::is_same_v) - { - visit_if( - [&context](auto &state_machine) - { - state_machine.m_optional_members.template get() = &context; - }); - } - } - - // Copy constructor. - state_machine_base(state_machine_base const& rhs) - : front_end_t(rhs) - { - if constexpr (std::is_same_v || - std::is_same_v) - { - m_root_sm = this; - using visitor_t = init_state_visitor; - visitor_t visitor{self()}; - visit_if(visitor); - } - // Copy all members except the root sm pointer. - m_active_state_ids = rhs.m_active_state_ids; - m_optional_members = rhs.m_optional_members; - m_history = rhs.m_history; - m_event_processing = rhs.m_event_processing; - m_states = rhs.m_states; - m_running = rhs.m_running; - } - - // Copy assignment operator. - state_machine_base& operator= (state_machine_base const& rhs) - { - if (this != &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; - m_history = rhs.m_history; - m_event_processing = rhs.m_event_processing; - m_states = rhs.m_states; - m_running = rhs.m_running; - } - return *this; - } - - // Start the state machine (calls entry of the initial state(s)). - void start() - { - // Assert for a case where root sm was not set up correctly - // after construction. - if constexpr (!std::is_same_v) - { - BOOST_ASSERT_MSG(&(this->get_root_sm()), - "Root sm must be passed as Derived and configured as root_sm"); - } - start(fsm_initial_event{}); - } - - // Start the state machine - // (calls entry of the initial state(s) with initial_event). - template - void start(Event const& initial_event) - { - if (!m_running) - { - on_entry(initial_event, get_fsm_argument()); - } - } - - // Stop the state machine (calls exit of the current state(s)). - void stop() - { - stop(fsm_final_event{}); - } - - // Stop the state machine - // (calls exit of the current state(s) with final_event). - template - void stop(Event const& final_event) - { - if (m_running) - { - on_exit(final_event, get_fsm_argument()); - m_running = false; - } - } - - // Main function to process events. - template - process_result process_event(Event const& event) - { - return process_event_internal( - compile_policy_impl::normalize_event(event), - process_info::direct_call); - } - - // Try to process pending event occurrences in the event pool, - // with an optional limit for the max no. of events that shall be processed. - // Returns the no. of processed events. - template > - inline size_t process_event_pool(size_t max_events = SIZE_MAX) - { - if (get_event_pool().events.empty()) - { - return 0; - } - return do_process_event_pool(max_events); - } - - // Enqueues an event in the event pool for later processing. - // If the state machine is already processing, the event will be processed - // after the current event completes. - template > - void enqueue_event(Event const& event) - { - compile_policy_impl::defer_event( - *this, compile_policy_impl::normalize_event(event), false); - } - - // Process all queued events. - template > - [[deprecated ("Use process_event_pool() instead")]] - void process_queued_events() - { - process_event_pool(); - } - - // Process a single queued event. - template > - [[deprecated ("Use process_event_pool(1) instead")]] - void process_single_queued_event() - { - process_event_pool(1); - } - - // Get the context of the state machine. - template , - typename = std::enable_if_t> - context_t& get_context() - { - if constexpr (context_member::value) - { - return *m_optional_members.template get(); - } - else - { - return get_root_sm().get_context(); - } - } - - // Get the context of the state machine. - template , - typename = std::enable_if_t> - const context_t& get_context() const - { - if constexpr (context_member::value) - { - return *m_optional_members.template get(); - } - else - { - return get_root_sm().get_context(); - } - } - - // Getter that returns the currently active state ids of the FSM. - const active_state_ids_t& get_active_state_ids() const - { - return m_active_state_ids; - } - - // Get the root sm. - template >> - root_sm_t& get_root_sm() - { - return *static_cast(m_root_sm); - } - // Get the root sm. - template >> - const root_sm_t& get_root_sm() const - { - return *static_cast(m_root_sm); - } - - // Return the id of a state in the sm. - template - static constexpr int get_state_id(const State&) - { - static_assert( - mp11::mp_map_contains::value, - "The state must be contained in the state machine"); - return detail::get_state_id::value; - } - // Return the id of a state in the sm. - template - static constexpr int get_state_id() - { - static_assert( - mp11::mp_map_contains::value, - "The state must be contained in the state machine"); - return detail::get_state_id::value; - } - - // True if the sm is used in another sm. - bool is_contained() const - { - return (static_cast(this) != m_root_sm); - } - - // Get a state. - template - State& get_state() - { - return std::get>(m_states); - } - // Get a state. - template - const State& get_state() const - { - return std::get>(m_states); - } - - // Visit the states (only active states, recursive). - template - void visit(Visitor&& visitor) - { - visit(std::forward(visitor)); - } - - // Visit the states (only active states, recursive). - template - void visit(Visitor&& visitor) const - { - visit(std::forward(visitor)); - } - - // Visit the states. - // How to traverse is selected with visit_mode. - template - void visit(Visitor&& visitor) - { - visit_if(std::forward(visitor)); - } - - // Visit the states. - // How to traverse is selected with visit_mode. - template - void visit(Visitor&& visitor) const - { - visit_if(std::forward(visitor)); - } - - // Check whether a state is currently active. - template - bool is_state_active() const - { - using visitor_t = is_state_active_visitor; - visitor_t visitor; - visit_if(visitor); - return visitor.result(); - } - - // Check if a flag is active, using the BinaryOp as folding function. - template - bool is_flag_active() const - { - using visitor_t = is_flag_active_visitor; - visitor_t visitor; - visit_if(visitor); - return visitor.result(); - } - - // Puts the event into the event pool for later processing. - // If the deferral takes place while the state machine is processing, - // the event will be evaluated for dispatch from the next processing cycle. - template < - class Event, - bool C = event_pool_member::value, - typename = std::enable_if_t> - void defer_event(Event const& event) - { - compile_policy_impl::defer_event( - *this, compile_policy_impl::normalize_event(event), m_event_processing); - } - - protected: - static_assert(std::is_same_v || - (std::is_same_v && - !std::is_same_v), - "fsm_parameter must be local_transition_owner or root_sm" - ); - using fsm_parameter_t = mp11::mp_if_c< - std::is_same_v, - derived_t, - typename config_t::root_sm>; - - const fsm_parameter_t& get_fsm_argument() const - { - if constexpr (std::is_same_v) - { - return self(); - } - else - { - return get_root_sm(); - } - } - - fsm_parameter_t& get_fsm_argument() - { - return const_cast - (static_cast(*this).get_fsm_argument()); - } - - template - bool is_event_deferred(const Event& event) const - { - return compile_policy_impl::is_event_deferred(self(), event); - } - - // Repetition of the front-end's method definition - // required due to above signature. - template - bool is_event_deferred(const Event& event, Fsm& fsm) const - { - return static_cast(this)->is_event_deferred(event, - fsm); - } - - // Checks if an event is an end interrupt event. - template - bool is_end_interrupt_event(const Event& event) const - { - return compile_policy_impl::is_end_interrupt_event(*this, event); - } - - // Helpers used to reset the state machine. - void reset_active_state_ids() - { - size_t index = 0; - mp11::mp_for_each( - [this, &index](auto state_identity) - { - using State = typename decltype(state_identity)::type; - m_active_state_ids[index++] = get_state_id(); - }); - m_history.reset_active_state_ids(m_active_state_ids); - } - - // Main function used internally to process events. - // Explicitly not inline, because code size can significantly increase if - // this method is inlined in all existing process_info variants. - template - BOOST_NOINLINE process_result process_event_internal(Event const& event, - process_info info) - { - // If the state machine has terminate or interrupt flags, check them. - if constexpr (mp11::mp_any_of::value) - { - // If the state machine is terminated, do not handle any event. - if (is_flag_active()) - { - return process_result::HANDLED_TRUE; - } - - // If the state machine is interrupted, do not handle any event - // unless the event is the end interrupt event. - if (is_flag_active() && - !is_end_interrupt_event(event)) - { - return process_result::HANDLED_TRUE; - } - } - - if constexpr (event_pool_member::value) - { - if (info != process_info::event_pool) - { - // If we are already processing or the event is deferred in the - // active state configuration, process it later. - // Skip the deferral check in submachine calls, since the - // parent has already checked and dispatched the event. - if (m_event_processing || - (info != process_info::submachine_call && - compile_policy_impl::is_event_deferred(self(), event))) - { - compile_policy_impl::defer_event(self(), event, false); - return process_result::HANDLED_DEFERRED; - } - - // Ensure we consider an event - // that was action-deferred in the last sequence. - get_event_pool().cur_seq_cnt += 1; - } - } - else - { - BOOST_ASSERT_MSG(!m_event_processing, - "An event pool must be available to call " - "process_event while processing an event"); - } - - // Process the event. - m_event_processing = true; - process_result result; -#ifndef BOOST_NO_EXCEPTIONS - if constexpr (has_no_exception_thrown::value) - { - result = do_process_event(event, info); - } - else - { - try - { - result = do_process_event(event, info); - } - catch (std::exception& e) - { - // give a chance to the concrete state machine to handle - this->exception_caught(event, get_fsm_argument(), e); - result = process_result::HANDLED_FALSE; - } - } -#else - result = do_process_event(event, info); -#endif - m_event_processing = false; - - // After handling, look if we have more to process in the event pool - // (but only if we're not already processing from it). - if constexpr (event_pool_member::value) - { - if (info != process_info::event_pool) - { - process_event_pool(); - } - } - - return result; - } - - private: - // Core logic for event processing without exceptions, queues, etc. - template - process_result do_process_event(Event const& event, process_info info) - { - using dispatch_table = - typename compile_policy_impl::template dispatch_table; - process_result result = process_result::HANDLED_FALSE; - // Dispatch the event to every region. - for (int region_id = 0; region_id < nr_regions; region_id++) - { - result |= dispatch_table::dispatch(self(), region_id, event); - } - // Dispatch the event to the SM-internal table if it hasn't been consumed yet. - if (!(result & handled_true_or_deferred)) - { - result |= dispatch_table::internal_dispatch(self(), event); - } - - // If the event has not been handled and we have orthogonal zones, then - // generate an error on every active state. - // For events coming from upper machines, do not handle - // but let the upper sm handle the error. - if (!result && !(info == process_info::submachine_call)) - { - for (const auto state_id: m_active_state_ids) - { - this->no_transition(event, get_fsm_argument(), state_id); - } - } - return result; - } - - // MSCV Bug: - // Compile error if this class is named completion_event. - template - class completion_event_occurrence : public event_occurrence - { - // Merge each list of transitions into a chain if needed. - template - struct merge_transitions_impl; - template - struct merge_transitions_impl> - { - using type = Transition; - }; - template - struct merge_transitions_impl> - { - using list = mp11::mp_list; - using completion_event = - typename mp11::mp_first::transition_event; - using type = - transition_chain; - }; - template - using merge_transitions = - typename merge_transitions_impl::type; - using completion_transitions = - detail::completion_transitions; - using completion_transition = merge_transitions; - - public: - completion_event_occurrence(int region_id) - : event_occurrence(&try_process), m_region_id(region_id) - { - } - - static std::optional try_process(event_occurrence& self, void* sm, uint16_t /*seq_cnt*/) - { - return static_cast(&self) - ->try_process_impl(*reinterpret_cast(sm)); - } - - private: - std::optional try_process_impl(derived_t& sm) - { - mark_for_deletion(); - return sm.template - process_completion_transition(m_region_id); - } - - int m_region_id; - }; - - template - process_result process_completion_transition(int region_id) - { - // If the state machine has terminate or interrupt flags, check them. - if constexpr (mp11::mp_any_of::value) - { - // If the state machine is interrupted or terminated, do not handle any event. - if (is_flag_active() || is_flag_active()) - { - return process_result::HANDLED_TRUE; - } - } - - // Process the event. - using completion_event = typename Transition::transition_event; - completion_event event{}; - m_event_processing = true; - process_result result; -#ifndef BOOST_NO_EXCEPTIONS - if constexpr (has_no_exception_thrown::value) - { - result = Transition::execute(self(), region_id, event); - } - else - { - try - { - result = Transition::execute(self(), region_id, event); - } - catch (std::exception& e) - { - // give a chance to the concrete state machine to handle - this->exception_caught(event, get_fsm_argument(), e); - } - } -#else - result = Transition::execute(self(), state_id, event); -#endif - m_event_processing = false; - return result; - } - - // Core logic for event pool processing, - // there must be at least one event in the pool. - // Explicitly not inline, because code size can significantly increase if - // this method's content is inlined in all entries and process_event calls. - template > - BOOST_NOINLINE size_t do_process_event_pool(size_t max_events = SIZE_MAX) - { - event_pool_t& event_pool = get_event_pool(); - auto it = event_pool.events.begin(); - size_t processed_events = 0; - do - { - event_occurrence& event = **it; - // The event was already processed. - if (event.marked_for_deletion()) - { - it = event_pool.events.erase(it); - continue; - } - - std::optional result = - event.try_process(self(), event_pool.cur_seq_cnt); - // The event has not been dispatched. - if (!result.has_value()) - { - it++; - continue; - } - - // Consider anything except "only deferred" to be a processed event. - if (*result != process_result::HANDLED_DEFERRED) - { - processed_events++; - if (processed_events == max_events) - { - break; - } - } - - // Start from the beginning, we might be able to process - // events that were deferred before. - it = event_pool.events.begin(); - // Consider newly deferred events only if - // the event was not deferred at the same time - // (required to prevent infinitely processing the same event, - // if it was handled and at the same time action-deferred - // in orthogonal regions). - if (!(*result & process_result::HANDLED_DEFERRED)) - { - event_pool.cur_seq_cnt += 1; - } - } while (it != event_pool.events.end()); - return processed_events; - } - - template - void do_defer_event(const Event& event, bool next_rtc_seq) - { - auto& event_pool = get_event_pool(); - const uint16_t seq_cnt = next_rtc_seq ? event_pool.cur_seq_cnt - : event_pool.cur_seq_cnt - 1; - event_pool.events.push_back(processable_event::make( - deferred_event{self(), event, seq_cnt})); - } - - 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); - } - - void postprocess_entry() - { - m_event_processing = false; - - // After handling, look if we have more to process in the event pool. - if constexpr (event_pool_member::value) - { - process_event_pool(); - } - } - - template - class state_entry_visitor - { - public: - state_entry_visitor(derived_t& self, const Event& event) - : m_self(self), m_event(event) - { - } - - template - void operator()(State& state) - { - state.on_entry(m_event, m_self.get_fsm_argument()); - m_self.template on_state_entry_completed(m_region_id++); - } - - private: - derived_t& m_self; - const Event& m_event; - int m_region_id{}; - }; - - - template - void on_entry(Event const& event, Fsm& fsm) - { - preprocess_entry(event, fsm); - - // First set all active state ids... - m_active_state_ids = m_history.on_entry(event); - // ... then execute each state entry. - state_entry_visitor visitor{self(), event}; - if constexpr (std::is_same_v) - { - mp11::mp_for_each( - [this, &visitor](auto state_identity) - { - using State = typename decltype(state_identity)::type; - auto& state = this->get_state(); - visitor(state); - }); - } - else - { - visit(visitor); - } - - 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. - state_entry_visitor visitor{self(), event}; - if constexpr (all_regions_defined) - { - mp11::mp_for_each( - [this, &visitor](auto state_identity) - { - using State = typename decltype(state_identity)::type; - auto& state = this->get_state(); - visitor(state); - }); - } - else - { - visit(visitor); - } - - 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_state_entry_completed(int region_id) - { - // Exclude composite states from completion transitions, - // these should fire when all their regions reach a final state - // (and final states do not exist yet). - if constexpr( - !is_composite::value && - has_completion_transitions::value) - { - auto& event_pool = get_event_pool(); - // Process completion transitions BEFORE any other event in the - // pool (UML Standard 2.3 15.3.14). - event_pool.events.push_front( - processable_event::make( - completion_event_occurrence{region_id})); - } - } - - template - void on_exit(Event const& event, Fsm& fsm) - { - // First exit the substates. - visit( - [this, &event](auto& state) - { - state.on_exit(event, get_fsm_argument()); - } - ); - // Then call our own exit. - (static_cast(this))->on_exit(event, fsm); - // Give the history a chance to handle this (or not). - m_history.on_exit(this->m_active_state_ids); - // History decides what happens with the event pool. - if (m_history.clear_event_pool(event)) - { - if constexpr (event_pool_member::value) - { - get_event_pool().events.clear(); - } - } - } - - derived_t& self() - { - return *static_cast(this); - } - - const derived_t& self() const - { - return *static_cast(this); - } - - struct optional_members : - event_pool_member, - context_member - { - template - typename T::type& get() - { - return static_cast(this)->instance; - } - template - const typename T::type& get() const - { - return static_cast(this)->instance; - } - }; - - // data members - active_state_ids_t m_active_state_ids; - optional_members m_optional_members; - history_impl m_history{}; - bool m_event_processing{false}; - void* m_root_sm{nullptr}; - states_t m_states{}; - bool m_running{false}; -}; - -} // detail /** * @brief Back-end for state machines. @@ -1196,6 +56,6 @@ class state_machine using Base::Base; }; -}}} // boost::msm::backmp11 +} // boost::msm::backmp11 -#endif //BOOST_MSM_BACKMP11_STATE_MACHINE_H +#endif // BOOST_MSM_BACKMP11_STATE_MACHINE_HPP diff --git a/include/boost/msm/backmp11/state_machine_config.hpp b/include/boost/msm/backmp11/state_machine_config.hpp index 7acfe5f6..7fe3a11f 100644 --- a/include/boost/msm/backmp11/state_machine_config.hpp +++ b/include/boost/msm/backmp11/state_machine_config.hpp @@ -9,8 +9,8 @@ // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_MSM_BACKMP11_STATE_MACHINE_CONFIG_H -#define BOOST_MSM_BACKMP11_STATE_MACHINE_CONFIG_H +#ifndef BOOST_MSM_BACKMP11_STATE_MACHINE_CONFIG_HPP +#define BOOST_MSM_BACKMP11_STATE_MACHINE_CONFIG_HPP #include @@ -102,4 +102,4 @@ struct function_pointer_array {}; } // namespace boost::msm::backmp11 -#endif // BOOST_MSM_BACKMP11_STATE_MACHINE_CONFIG_H +#endif // BOOST_MSM_BACKMP11_STATE_MACHINE_CONFIG_HPP diff --git a/meta/libraries.json b/meta/libraries.json index 31330825..17b25bc7 100644 --- a/meta/libraries.json +++ b/meta/libraries.json @@ -9,7 +9,8 @@ "State" ], "maintainers": [ - "Christophe Henry " + "Christophe Henry ", + "Christian Granzin" ], "cxxstd": "03" } From 1756026554703118b7cf0ff2d01243c81a786e4e Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Tue, 24 Feb 2026 19:51:31 -0500 Subject: [PATCH 3/3] refactor(backmp11): basic_polymorphic, version history Cleaned up basic_polymorphic and aligned version history with the Boost relase notes. --- .../pages/tutorial/backmp11-back-end.adoc | 4 +- doc/modules/ROOT/pages/version-history.adoc | 22 ++- include/boost/msm/backmp11/common_types.hpp | 3 - ...orphic_value.hpp => basic_polymorphic.hpp} | 186 +++++++++--------- .../backmp11/detail/state_machine_base.hpp | 8 +- .../boost/msm/backmp11/favor_compile_time.hpp | 2 + ...Value.cpp => Backmp11BasicPolymorphic.cpp} | 16 +- test/CMakeLists.txt | 6 +- test/Jamfile.v2 | 2 +- 9 files changed, 131 insertions(+), 118 deletions(-) rename include/boost/msm/backmp11/detail/{basic_polymorphic_value.hpp => basic_polymorphic.hpp} (63%) rename test/{Backmp11BasicPolymorphicValue.cpp => Backmp11BasicPolymorphic.cpp} (91%) diff --git a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc index 7e229f83..007193eb 100644 --- a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc @@ -15,7 +15,7 @@ It offers a significant improvement in runtime and memory usage, as can be seen | back | 14 | 815 | 68 | 2.8 | back_favor_compile_time | 17 | 775 | 226 | 3.5 | back11 | 37 | 2682 | 84 | 2.8 -| backmp11 | 3 | 209 | 29 | 0.7 +| backmp11 | 3 | 209 | 28 | 0.7 | backmp11_favor_compile_time | 3 | 195 | 43 | 6.0 | sml | 5 | 234 | 57 | 0.3 |======================================================================================================= @@ -28,7 +28,7 @@ It offers a significant improvement in runtime and memory usage, as can be seen | | Compile time / sec | Compile RAM / MB | Binary size / kB | Runtime / sec | back | 49 | 2165 | 230 | 13.2 | back_favor_compile_time | 55 | 1704 | 911 | > 300 -| backmp11 | 8 | 348 | 83 | 3.4 +| backmp11 | 8 | 348 | 79 | 3.3 | backmp11_favor_compile_time | 5 | 261 | 97 | 20.6 | backmp11_favor_compile_time_multi_cu | 4 | ~863 | 97 | 21.4 | sml | 18 | 543 | 422 | 5.4 diff --git a/doc/modules/ROOT/pages/version-history.adoc b/doc/modules/ROOT/pages/version-history.adoc index 093eeca9..e32339ef 100644 --- a/doc/modules/ROOT/pages/version-history.adoc +++ b/doc/modules/ROOT/pages/version-history.adoc @@ -4,15 +4,19 @@ == Boost 1.91 -* feat(backmp11): Further optimized compilation, with up to 25% faster compile times and less RAM consumption than the 1.90 version -* fix(backmp11): Incorrect FSM type in call on_entry() & on_exit() (https://github.com/boostorg/msm/issues/167[#167]) -* feat(backmp11): Merge queued & deferred events into a single event pool (https://github.com/boostorg/msm/issues/168[#168]) -* feat(backmp11): Support conditional deferral with the `deferred_events` property (https://github.com/boostorg/msm/issues/155[#155]) -* fix(backmp11): Completion events fire too often (https://github.com/boostorg/msm/issues/166[#166]) -* feat(backmp11): Small Object Optimization for events in the event pool (https://github.com/boostorg/msm/issues/172[#172]) -* feat(backmp11): Improve support for the `deferred_events` property in hierarchical state machines (https://github.com/boostorg/msm/issues/173[#173]) -* feat(backmp11): Improve runtime performance with a `flat_fold` dispatch strategy (https://github.com/boostorg/msm/issues/180[#180]) -* feat(backmp11): Simplified functor signatures (https://github.com/boostorg/msm/issues/175[#175]) +* New features (`backmp11`): +** Improve support for the `deferred_events` property in hierarchical state machines (https://github.com/boostorg/msm/issues/173[#173]) +** Support conditional deferral with the `deferred_events` property (https://github.com/boostorg/msm/issues/155[#155]) +** Simplified functor signatures (https://github.com/boostorg/msm/issues/175[#175]) +** Merge queued and deferred events into a single event pool (https://github.com/boostorg/msm/issues/168[#168]) +** Small object optimization for events in the event pool (https://github.com/boostorg/msm/issues/172[#172]) +** Improve runtime performance with a `flat_fold` dispatch strategy (https://github.com/boostorg/msm/issues/180[#180]) +** Further optimize compilation, with up to 25% faster compile times and lower RAM consumption compared to version 1.90 +* Bug fixes (`backmp11`): +** Incorrect `FSM` type in calls to `on_entry(...)` and `on_exit(...)` (https://github.com/boostorg/msm/issues/167[#167]) +** Completion events fire too often (https://github.com/boostorg/msm/issues/166[#166]) +* **Breaking change (`backmp11`)**: Direct access to the event pool is changed from `public` to `protected`, +because manipulating it outside of the library code can lead to undefined behavior. == Boost 1.90 diff --git a/include/boost/msm/backmp11/common_types.hpp b/include/boost/msm/backmp11/common_types.hpp index a52e034d..f95cb1f0 100644 --- a/include/boost/msm/backmp11/common_types.hpp +++ b/include/boost/msm/backmp11/common_types.hpp @@ -12,7 +12,6 @@ #ifndef BOOST_MSM_BACKMP11_COMMON_TYPES_HPP #define BOOST_MSM_BACKMP11_COMMON_TYPES_HPP -#include #include #include @@ -61,8 +60,6 @@ namespace boost::msm::backmp11 using process_result = back::HandledEnum; -using any_event = std::any; - // flag handling struct flag_or {}; struct flag_and {}; diff --git a/include/boost/msm/backmp11/detail/basic_polymorphic_value.hpp b/include/boost/msm/backmp11/detail/basic_polymorphic.hpp similarity index 63% rename from include/boost/msm/backmp11/detail/basic_polymorphic_value.hpp rename to include/boost/msm/backmp11/detail/basic_polymorphic.hpp index 215e256e..7bf156d1 100644 --- a/include/boost/msm/backmp11/detail/basic_polymorphic_value.hpp +++ b/include/boost/msm/backmp11/detail/basic_polymorphic.hpp @@ -21,8 +21,8 @@ namespace boost::msm::backmp11::detail { -// Basic polymorphic value with small buffer optimization. -// Similar to standard proposal P0201 (polymorphic_value). +// Basic polymorphic value with small object optimization. +// Similar to std::polymorphic (C++26). struct control_block { @@ -84,7 +84,9 @@ struct create_control_block }; template -struct inline_control_bock +struct inline_control_bock; +template +struct inline_control_bock { inline static const control_block instance = []() constexpr { control_block self{}; @@ -126,8 +128,9 @@ const control_block& control_block_v = struct inline_tag {}; -template -class basic_polymorphic_value_base +template +class basic_polymorphic_base { static_assert(BufferSize <= 255, "BufferSize must not be bigger than 255 Bytes"); @@ -138,59 +141,100 @@ class basic_polymorphic_value_base return m_control_block->is_inline; } - protected: - explicit basic_polymorphic_value_base(const control_block& class_data) - : m_control_block(&class_data) + void* get() const noexcept { + if (m_control_block->is_inline) + { + return const_cast(reinterpret_cast(&m_buffer)); + } + else + { + return m_ptr; + } } - basic_polymorphic_value_base(const void* src, const control_block& class_data, - inline_tag) - : m_control_block(&class_data) - { - std::memcpy(&this->m_buffer, src, m_control_block->size); - } + protected: + template + using IsInline = std::bool_constant< + sizeof(U) <= BufferSize && + alignof(U) <= BufferAlignment>; + + template + static constexpr bool is_nothrow_constructible_v = + std::is_trivially_copyable_v && IsInline::value; - basic_polymorphic_value_base(void* ptr, control_block& class_data) - : m_control_block(&class_data) + template + explicit basic_polymorphic_base(const U& obj) noexcept( + is_nothrow_constructible_v) + : m_control_block(&control_block_v::value>) { - m_ptr = ptr; + if constexpr (IsInline::value) + { + if constexpr (std::is_trivially_copyable_v) + { + std::memcpy(&m_buffer, &obj, sizeof(U)); + } + else + { + new (&m_buffer) U(obj); + } + } + else + { + m_ptr = new U(obj); + } } - ~basic_polymorphic_value_base() + template + explicit basic_polymorphic_base( + std::in_place_type_t, + Args&&... args) noexcept(is_nothrow_constructible_v) + : m_control_block(&control_block_v::value>) { - m_control_block->destroy(get()); + if constexpr (IsInline::value) + { + new (&m_buffer) U(std::forward(args)...); + } + else + { + m_ptr = new U(std::forward(args)...); + } } - basic_polymorphic_value_base(const basic_polymorphic_value_base&) = delete; - basic_polymorphic_value_base& operator=(const basic_polymorphic_value_base&) = delete; + // Moveable is enough for our needs. + // Removing the copy operators ensures we do not accidentally copy the value. + basic_polymorphic_base(const basic_polymorphic_base& rhs) = delete; + basic_polymorphic_base& operator=(const basic_polymorphic_base& rhs) = delete; - basic_polymorphic_value_base(basic_polymorphic_value_base&& other) noexcept + basic_polymorphic_base(basic_polymorphic_base&& other) noexcept : m_control_block(other.m_control_block) { m_control_block->move(m_ptr, other.m_ptr); } - basic_polymorphic_value_base& operator=(basic_polymorphic_value_base&& other) noexcept + basic_polymorphic_base& operator=(basic_polymorphic_base&& other) noexcept { if (this != &other) { - m_control_block->destroy(get()); + destroy(); m_control_block = other.m_control_block; m_control_block->move(m_ptr, other.m_ptr); } return *this; } - void* get() const noexcept + ~basic_polymorphic_base() { - if (m_control_block->is_inline) - { - return const_cast(reinterpret_cast(&m_buffer)); - } - else + destroy(); + } + + private: + void destroy() + { + m_control_block->destroy(get());; + if (!m_control_block->is_inline) { - return m_ptr; + m_ptr = nullptr; } } @@ -204,76 +248,41 @@ class basic_polymorphic_value_base template -class basic_polymorphic_value - : public basic_polymorphic_value_base +class basic_polymorphic + : public basic_polymorphic_base { - private: - using destroy_fn_t = void(*)(T* ptr) noexcept; - using base = basic_polymorphic_value_base; - - template - using IsInline = std::bool_constant< - sizeof(U) <= BufferSize && - alignof(U) <= BufferAlignment>; + using base = basic_polymorphic_base; public: - basic_polymorphic_value(basic_polymorphic_value&& other) noexcept = default; - basic_polymorphic_value& operator=(basic_polymorphic_value&& other) noexcept = default; + basic_polymorphic(basic_polymorphic&& other) noexcept = default; + basic_polymorphic& operator=(basic_polymorphic&& other) noexcept = default; - template ::value> - struct make_impl; - template - struct make_impl + template >> + explicit basic_polymorphic(const U& obj) noexcept(base::template is_nothrow_constructible_v) + : base(obj) { - static_assert(std::is_base_of_v, "U must be derived from T"); - - static basic_polymorphic_value make(const U& obj) - { - if constexpr (std::is_trivially_copyable_v) - { - return basic_polymorphic_value{&obj, control_block_v, - inline_tag{}}; - } - else - { - basic_polymorphic_value self{control_block_v}; - new (&self.m_buffer) U(obj); - return self; - } - } + } - template - static basic_polymorphic_value make(Args&&... args) - { - basic_polymorphic_value self{control_block_v}; - new (&self.m_buffer) U(std::forward(args)...); - return self; - } - }; - template - struct make_impl + template >> + explicit basic_polymorphic(std::in_place_type_t in_place, + Args&&... args) noexcept(base::template is_nothrow_constructible_v) + : base(in_place, std::forward(args)...) { - static_assert(std::is_base_of_v, "U must be derived from T"); - - template - static basic_polymorphic_value make(Args&&... args) - { - basic_polymorphic_value self{control_block_v}; - self.m_ptr = new U(std::forward(args)...); - return self; - } - }; + } template - static basic_polymorphic_value make(Args&&... args) + static basic_polymorphic make(Args&&... args) { - return make_impl::make(std::forward(args)...); + return basic_polymorphic{std::in_place_type, + std::forward(args)...}; }; template - static constexpr basic_polymorphic_value make(const U& obj) + static basic_polymorphic make(const U& obj) { - return make_impl::make(obj); + return basic_polymorphic{obj}; }; T* get() const noexcept @@ -290,9 +299,6 @@ class basic_polymorphic_value { return get(); } - - protected: - using base::base; }; } // namespace boost::msm::backmp11::detail diff --git a/include/boost/msm/backmp11/detail/state_machine_base.hpp b/include/boost/msm/backmp11/detail/state_machine_base.hpp index 9f8631c4..452d8884 100644 --- a/include/boost/msm/backmp11/detail/state_machine_base.hpp +++ b/include/boost/msm/backmp11/detail/state_machine_base.hpp @@ -23,7 +23,7 @@ #include #include -#include +#include #include #include #include @@ -148,7 +148,7 @@ class state_machine_base : public FrontEnd using states_t = mp11::mp_rename; protected: - using processable_event = basic_polymorphic_value; + using processable_event = basic_polymorphic; template using event_container = typename config_t::template event_container; using event_container_t = event_container; @@ -399,7 +399,7 @@ class state_machine_base : public FrontEnd typename = std::enable_if_t> inline size_t process_event_pool(size_t max_events = SIZE_MAX) { - if (get_event_pool().events.empty()) + if (get_event_pool().events.empty() || m_event_processing) { return 0; } @@ -869,7 +869,7 @@ class state_machine_base : public FrontEnd } } #else - result = Transition::execute(self(), state_id, event); + result = Transition::execute(self(), region_id, event); #endif m_event_processing = false; return result; diff --git a/include/boost/msm/backmp11/favor_compile_time.hpp b/include/boost/msm/backmp11/favor_compile_time.hpp index 342ad35c..82b6d6ef 100644 --- a/include/boost/msm/backmp11/favor_compile_time.hpp +++ b/include/boost/msm/backmp11/favor_compile_time.hpp @@ -49,6 +49,8 @@ struct favor_compile_time namespace detail { +using any_event = std::any; + template <> struct compile_policy_impl { diff --git a/test/Backmp11BasicPolymorphicValue.cpp b/test/Backmp11BasicPolymorphic.cpp similarity index 91% rename from test/Backmp11BasicPolymorphicValue.cpp rename to test/Backmp11BasicPolymorphic.cpp index 1fd19fd2..319f4c66 100644 --- a/test/Backmp11BasicPolymorphicValue.cpp +++ b/test/Backmp11BasicPolymorphic.cpp @@ -10,14 +10,14 @@ // http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_MSM_NONSTANDALONE_TEST -#define BOOST_TEST_MODULE backmp11_basic_polymorphic_value +#define BOOST_TEST_MODULE backmp11_basic_polymorphic #endif #include -#include +#include #include "Utils.hpp" -using boost::msm::backmp11::detail::basic_polymorphic_value; +using boost::msm::backmp11::detail::basic_polymorphic; namespace { @@ -29,7 +29,7 @@ struct trivial_class BOOST_AUTO_TEST_CASE(trivial_class_test) { - using ptr_t = basic_polymorphic_value; + using ptr_t = basic_polymorphic; static_assert(sizeof(ptr_t) == 64); ptr_t ptr = ptr_t::make(trivial_class{42}); @@ -52,7 +52,7 @@ struct class_with_destructor size_t class_with_destructor::destructor_calls{}; BOOST_AUTO_TEST_CASE(class_with_destructor_test) { - using ptr_t = basic_polymorphic_value; + using ptr_t = basic_polymorphic; [[maybe_unused]] size_t& destructor_calls = class_with_destructor::destructor_calls; @@ -104,7 +104,7 @@ struct big_class_with_destructor size_t big_class_with_destructor::destructor_calls{}; BOOST_AUTO_TEST_CASE(big_class_with_destructor_test) { - using ptr_t = basic_polymorphic_value; + using ptr_t = basic_polymorphic; { ptr_t ptr = ptr_t::make(42); BOOST_REQUIRE(!ptr.is_inline()); @@ -133,7 +133,7 @@ struct sub_class__with_destructor : class_with_destructor size_t sub_class__with_destructor::destructor_calls{}; BOOST_AUTO_TEST_CASE(two_destructors_test) { - using ptr_t = basic_polymorphic_value; + using ptr_t = basic_polymorphic; [[maybe_unused]] size_t& destructor_calls_0 = class_with_destructor::destructor_calls; [[maybe_unused]] size_t& destructor_calls_1 = sub_class__with_destructor::destructor_calls; @@ -191,7 +191,7 @@ struct other_virtual_class : virtual_class size_t other_virtual_class::method_calls{}; BOOST_AUTO_TEST_CASE(virtual_class_test) { - using ptr_t = basic_polymorphic_value; + using ptr_t = basic_polymorphic; ptr_t ptr = ptr_t::make(); BOOST_REQUIRE(ptr.is_inline()); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2bb7f32e..2f385d60 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,10 @@ endif() if(BOOST_MSM_TEST_STRICT) add_compile_options(-Wall -Wextra -Werror -Wno-language-extension-token) endif() +if(BOOST_MSM_TEST_ENABLE_SANITIZERS) + add_compile_options(-fsanitize=address -fno-omit-frame-pointer) + add_link_options(-fsanitize=address -fno-omit-frame-pointer) +endif() add_executable(boost_msm_tests @@ -72,7 +76,7 @@ add_dependencies(tests boost_msm_euml_tests) add_executable(boost_msm_cxx17_tests EXCLUDE_FROM_ALL - Backmp11BasicPolymorphicValue.cpp + Backmp11BasicPolymorphic.cpp Backmp11Completion.cpp Backmp11Constructor.cpp Backmp11Context.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index d45363f5..dc938ded 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -76,7 +76,7 @@ test-suite msm-unit-tests test-suite msm-unit-tests-cxxstd17 : - [ run Backmp11BasicPolymorphicValue.cpp ] + [ run Backmp11BasicPolymorphic.cpp ] [ run Backmp11Completion.cpp ] [ run Backmp11Constructor.cpp ] [ run Backmp11Context.cpp ]