diff --git a/doc/modules/ROOT/attachments/backmp11/Logger.cpp b/doc/modules/ROOT/attachments/backmp11/Logger.cpp new file mode 100644 index 00000000..a8683361 --- /dev/null +++ b/doc/modules/ROOT/attachments/backmp11/Logger.cpp @@ -0,0 +1,226 @@ +// Copyright 2026 Christian Granzin +// Copyright 2010 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) + +#include +#include + +#include + +#include +#include +#include +#include + +namespace back = boost::msm::backmp11; +namespace front = boost::msm::front; +using front::none; +using front::Row; +using front::Internal; +namespace mp11 = boost::mp11; + +namespace +{ + +// Logger for state machine activities. +// Logs the beginning of event processing and the result of each processed transition. +class Logger : public back::default_observer +{ + public: + template + void pre_process_event(const StateMachine& sm, const Event& event) + { + log(to_string(sm) + " processing event " + to_string(event)); + } + + template + void post_process_transition(const StateMachine& sm, size_t /*region_id*/, + process_result result) + { + log(to_string(sm) + " processed transition \"" + to_string() + + " + " + to_string() + guard_to_string() + + action_to_string() + target_to_string() + "\" (" + + to_string(result) + ")"); + } + + template + void post_process_transition(const StateMachine& sm, process_result result) + { + log(to_string(sm) + " processed transition \"" + to_string(sm) + " + " + + to_string() + guard_to_string() + + action_to_string() + "\" (" + to_string(result) + ")"); + } + + protected: + virtual void log(std::string_view msg) + { + std::cout << msg << std::endl; + } + + private: + static std::string to_string(const std::type_info& type) + { + auto full_name = boost::core::demangled_name(type); + return full_name.substr(full_name.rfind(':') + 1); + } + + template + static std::string to_string() + { + return to_string(typeid(T)); + } + + template + static std::string to_string(const back::state_machine& /*sm*/) + { + return to_string(); + } + + template + static std::string to_string(const T& /*event*/) + { + return to_string(); + } + + static std::string to_string(const std::any& event) + { + return to_string(event.type()); + } + + template + static std::string target_to_string() + { + if constexpr (!std::is_same_v) + { + return std::string{" -> "} + to_string(); + } + return {}; + } + + template + static std::string action_to_string() + { + if constexpr (!std::is_same_v) + { + return std::string{" / "} + to_string(); + } + return {}; + } + + template + static std::string guard_to_string() + { + if constexpr (!std::is_same_v) + { + return std::string{" [ "} + to_string() + " ]"; + } + return {}; + } + + static std::string to_string(process_result result) + { + using Enum = boost::msm::back::HandledEnum; + switch (result) + { + case Enum::HANDLED_FALSE: + return "discarded"; + case Enum::HANDLED_TRUE: + return "handled"; + case Enum::HANDLED_GUARD_REJECT: + return "rejected"; + case Enum::HANDLED_DEFERRED: + return "deferred"; + default: + return {}; + } + } +}; + +// Events. +struct TransitionEvent {}; +struct InternalTransitionEvent {}; +struct SmInternalTransitionEvent {}; + +// Actions +struct MyAction +{ + template + void operator()(Fsm&) + { + } +}; + +// Guards +struct MyGuard +{ + template + bool operator()(Fsm&) const + { + return true; + } +}; + +struct NotMyGuard +{ + template + bool operator()(Fsm&) const + { + return false; + } +}; + +// States. +struct MyState : front::state<> {}; +struct MyOtherState : front::state<> {}; + +// State machine. +// We leave out the trailing underscore for prettier logs. +// You could also implement `to_string()` methods to explicitly set names and +// use these in the logger implementation. +struct MyStateMachine : front::state_machine_def +{ + using initial_state = MyState; + using transition_table = mp11::mp_list< + Row, + Row, + Row, + Row + >; + using internal_transition_table = mp11::mp_list< + Internal + >; +}; + +struct MyConfig : back::state_machine_config +{ + using observer = Logger; +}; + +using MyStateMachineBe = back::state_machine; + +[[maybe_unused]] void logger_example() +{ + MyStateMachineBe test_machine; + + std::cout << "Starting state machine..." << std::endl; + test_machine.start(); + + test_machine.process_event(TransitionEvent{}); + test_machine.process_event(InternalTransitionEvent{}); + test_machine.process_event(TransitionEvent{}); + test_machine.process_event(SmInternalTransitionEvent{}); + + std::cout << "Stopping state machine..." << std::endl; + test_machine.stop(); +} + +} diff --git a/doc/modules/ROOT/pages/backmp11-back-end.adoc b/doc/modules/ROOT/pages/backmp11-back-end.adoc index 8c597825..82ac0fc1 100644 --- a/doc/modules/ROOT/pages/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/backmp11-back-end.adoc @@ -16,8 +16,8 @@ It offers a significant improvement in runtime and memory usage, as can be seen | back_favor_compile_time | 12 | 821 | 241 | 1.0 | back11 | 26 | 2675 | 92 | 0.7 | backmp11 | 2 | 236 | 18 | 0.2 -| backmp11_favor_compile_time | 2 | 225 | 44 | 2.2 -| sml | 3 | 242 | 57 | 0.1 +| backmp11_favor_compile_time | 2 | 226 | 44 | 2.2 +| sml | 4 | 254 | 64 | 0.1 |======================================================================================================= @@ -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 | 32 | 2160 | 252 | 3.7 | back_favor_compile_time | 37 | 1747 | 974 | 263 -| backmp11 | 5 | 360 | 55 | 0.9 -| backmp11_favor_compile_time | 3 | 263 | 96 | 7.5 -| sml | 12 | 567 | 436 | 2.5 +| backmp11 | 5 | 362 | 55 | 0.9 +| backmp11_favor_compile_time | 3 | 266 | 96 | 7.5 +| sml | 13 | 612 | 460 | 2.8 |======================================================================================================= @@ -68,6 +68,8 @@ struct default_state_machine_config using event_pool_container = std::deque; // Type of the Fsm parameter passed in actions and guards. using fsm_parameter = local_transition_owner; + // Sets up an observer for monitoring state machine activities. + using observer = no_observer; // Identifies the upper‐most machine in hierarchical state machines. using root_sm = no_root_sm; }; @@ -88,6 +90,21 @@ struct MyStateMachineConfig : state_machine_config The state machine configuration is designed to be shared in hierarchical state machines. + +=== Root state machine + +The setting `root_sm` defines the type of the root state machine in hierarchical state machines. The root sm is the uppermost state machine. + +If `using root_sm = MyRootSm;` is defined in the config, the following API becomes available to access it from any (sub-)machine: + +```cpp +MyRootSm& state_machine::get_root_sm(); +``` + +It is highly recommended to always configure the `root_sm` in hierarchical state machines, even if access to it is not required. +This reduces compilation time and memory footprint, because it enables the back-end to instantiate the full set of methods and members only for the root and it can omit them for submachines. + + === Context The setting `context` sets up a context member in the state machine for dependency injection. @@ -98,20 +115,28 @@ If `using context = MyContext;` is defined in the config, the following API beco MyContext& state_machine::get_context(); ``` -If a context is configured, a reference to a context instance must be passed as the first argument to the state_machine constructor. +If a context is configured, a reference to an instance of it must be passed as the first argument to the state_machine constructor. +In hierarchical state machines the root state machine has to be set as well. -=== Root state machine -The setting `root_sm` defines the type of the root state machine of hierarchical state machines. The root sm is the uppermost state machine. +=== Observer -If `using root_sm = MyRootSm;` is defined in the config, the following API becomes available to access it from any (sub-)machine: +The setting `observer` sets up an xref:reference:boost/msm/backmp11/default_observer.adoc[`observer member`] in the state machine for monitoring state machine activities. +An observer is particularly useful for logging and capturing metrics. You can find a demo logger implementation in the xref:backmp11-back-end/examples.adoc#_logger[`Logger example`]. + +If `using observer = MyObserver;` is defined in the config, the following API becomes available to access it in the state machine: ```cpp -MyRootSm& state_machine::get_root_sm(); +MyObserver& state_machine::get_observer(); ``` -It is highly recommended to always configure the `root_sm` in hierarchical state machines, even if access to it is not required. -This reduces compilation time and memory footprint, because it enables the back-end to instantiate the full set of methods and members only for the root and it can omit them for submachines. +This creates an observer instance whose lifetime is managed within the state machine. If your instance's lifetime is managed externally, wrap your observer into an `observer_ref`: + +```cpp +using observer = observer_ref; +``` + +If an observer ref is configured, a reference to an instance of it must be passed as the first argument (second argument if a context is also configured) to the `state_machine` constructor. In hierarchical state machines the root state machine has to be set as well. === `Fsm` parameter of actions and guards @@ -294,11 +319,6 @@ You can also defer events in transitions by using the `front::Defer` action. Whi The state machine also contains a xref:reference:boost/msm/backmp11/state_machine/defer_event.adoc[`void state_machine::defer_event(const Event&)`] method, which defers the processing to the next time `process_event(...)` or `process_event_pool(...)` is called. -== Handling exceptions - -The back-end is exception-neutral; exceptions thrown during processing are propagated to the caller. -It provides a _basic exception guarantee_: The processing aborts mid-execution, but the state machine remains in a valid state. If the processing step involves a state switch, the active state switches to the target state as soon as the source state's exit action has completed. If you need to know exact details about the aborted state machine activities during event processing, see the xref:#_run_to_completion[run-to-completion algorithm]. - == Check for active states @@ -363,6 +383,11 @@ my_state_machine.visit ``` +== Exceptions + +The back-end is exception-neutral; exceptions thrown during processing are propagated to the caller. +It provides a _basic exception guarantee_: The processing aborts mid-execution, but the state machine remains in a valid state. If the processing step involves a state switch, the active state switches to the target state as soon as the source state's exit action has completed. If you need to know exact details about the aborted state machine activities during event processing, see the xref:#_run_to_completion[run-to-completion algorithm]. + == Reflection and serialization @@ -593,7 +618,7 @@ Futhermore, the back-end forwards Kleene events without converting them to `std: There were some caveats with one constructor that was used for different use cases: On the one hand, some arguments were immediately forwarded to the front-end's constructor, on the other hand, the stream operator was used to identify other arguments in the constructor as states, to copy them into the state machine. Besides the syntax of the latter being rather unusual, when doing both at once, the syntax becomes too difficult to understand; even more so if states within hierarchical submachines are initialized in this fashion. -To keep the API of the constructor simpler and less ambiguous, it only supports forwarding arguments to the front-end. +To keep the API of the constructor simpler and less ambiguous, it only supports arguments related to the `state_machine` configuration. The state machine's front-end must be default-constructible. The `set_states(...)` API has also been removed. If setting a state is required, this can still be done (in a more verbose, but also more direct and explicit way) by getting a reference to the desired state via `get_state()` and then assigning a new state object to it. diff --git a/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc b/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc new file mode 100644 index 00000000..552c3f9b --- /dev/null +++ b/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc @@ -0,0 +1,23 @@ += Examples + +== xref:attachment$backmp11/Logger.cpp[Logger] + +This example contains an observer implementation for logging state machine activities. +The implementation logs the start of event processing and every processed transition. + +Running the example produces the following output: + +```bash +Starting state machine... +MyStateMachine processed transition "none + starting -> MyState" (handled) +MyStateMachine processing event TransitionEvent +MyStateMachine processed transition "MyState + TransitionEvent [ NotMyGuard ] / MyAction -> MyOtherState" (rejected) +MyStateMachine processed transition "MyState + TransitionEvent [ MyGuard ] / MyAction -> MyOtherState" (handled) +MyStateMachine processing event TransitionEvent +MyStateMachine processed transition "MyOtherState + TransitionEvent -> MyState" (handled) +MyStateMachine processing event InternalTransitionEvent +MyStateMachine processed transition "MyState + InternalTransitionEvent / MyAction" (handled) +MyStateMachine processing event SmInternalTransitionEvent +MyStateMachine processed transition "MyStateMachine + SmInternalTransitionEvent / MyAction" (handled) +Stopping state machine... +``` diff --git a/doc/modules/ROOT/pages/version-history.adoc b/doc/modules/ROOT/pages/version-history.adoc index 3628bdcd..79021124 100644 --- a/doc/modules/ROOT/pages/version-history.adoc +++ b/doc/modules/ROOT/pages/version-history.adoc @@ -4,16 +4,18 @@ == Boost 1.92 -* **Breaking changes (`backmp11`)**: -** Make enqueuing events explicit (https://github.com/boostorg/msm/issues/178[#178]) -** The APIs `process_queued_events()` and `process_single_queued_event()` are removed, use `process_event_pool(...)` instead -** The default active state switch policy is set to "after source exit" in compliance to the UML specification. The other options are not UML-compliant and will be removed in version 1.93 (https://github.com/boostorg/msm/issues/222[#222]) * New features (`backmp11`): +** Provide an observer API for logging support (https://github.com/boostorg/msm/issues/220[#220]) ** Forward Kleene events to actions and guards without conversion (https://github.com/boostorg/msm/issues/229[#229]) ** Ensure basic exception guarantee and propagate exceptions to the caller (https://github.com/boostorg/msm/issues/221[#221]) ** Provide a reflection API and serialization support for Boost.Serialization, Boost.JSON and nlohmann/json (https://github.com/boostorg/msm/issues/197[#197]) * Bug fixes (`backmp11`): ** State machine processes events although it is not running (https://github.com/boostorg/msm/issues/198[#198]) +* **Breaking changes (`backmp11`)**: +** Events in `process_event(...)` are not enqueued automatically anymore to reduce memory footprint, use `enqueue_event(...)` in actions (https://github.com/boostorg/msm/issues/178[#178]) +** The back-end does not forward constructor arguments to the front-end anymore, it must be default-constructible. To use custom constructors, define them in the back-end (https://github.com/boostorg/msm/issues/232[#232]) +** The APIs `process_queued_events()` and `process_single_queued_event()` are removed, use `process_event_pool(...)` instead +** The default active state switch policy is set to "after source exit" in compliance to the UML specification. The other options are not UML-compliant and will be removed in version 1.93 (https://github.com/boostorg/msm/issues/222[#222]) * Documentation: ** API reference is now generated from source with MrDocs diff --git a/doc/modules/ROOT/partials/toc.adoc b/doc/modules/ROOT/partials/toc.adoc index ae310ac1..0c909155 100644 --- a/doc/modules/ROOT/partials/toc.adoc +++ b/doc/modules/ROOT/partials/toc.adoc @@ -11,6 +11,7 @@ *** xref:back-back-end/examples.adoc[Examples] *** xref:back-back-end/performance-compilers.adoc[Performance / Compilers] ** xref:backmp11-back-end.adoc[`backmp11` back-end (C++17, experimental)] +*** xref:backmp11-back-end/examples.adoc[Examples] ** xref:internals.adoc[Internals] ** xref:acknowledgements.adoc[Acknowledgements] ** xref:version-history.adoc[Version history] diff --git a/doc/mrdocs.cpp b/doc/mrdocs.cpp index 80fe8067..353055b9 100644 --- a/doc/mrdocs.cpp +++ b/doc/mrdocs.cpp @@ -1 +1,2 @@ #include +#include diff --git a/include/boost/msm/backmp11/detail/event_pool_processor.hpp b/include/boost/msm/backmp11/detail/event_pool_processor.hpp index 8c0fcb4e..ebd4af27 100644 --- a/include/boost/msm/backmp11/detail/event_pool_processor.hpp +++ b/include/boost/msm/backmp11/detail/event_pool_processor.hpp @@ -94,7 +94,7 @@ class deferred_event : public event_occurrence return std::nullopt; } mark_for_deletion(); - return sm.process_event_internal(m_event, process_info::event_pool); + return sm.process_event_observed(m_event, process_info::event_pool); } private: diff --git a/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp b/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp index 2ccff1da..8d9171e8 100644 --- a/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp +++ b/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp @@ -262,7 +262,6 @@ struct compile_policy_impl< struct forward_transition { using current_state_type = Submachine; - using next_state_type = Submachine; using transition_event = Event; static process_result process(StateMachine& sm, @@ -275,7 +274,7 @@ struct compile_policy_impl< process_info::submachine_call; process_result result = sm.template get_state() - .process_event_internal(event, info); + .process_event_observed(event, info); return result; } }; diff --git a/include/boost/msm/backmp11/detail/state_machine_base.hpp b/include/boost/msm/backmp11/detail/state_machine_base.hpp index fd06b777..974e328a 100644 --- a/include/boost/msm/backmp11/detail/state_machine_base.hpp +++ b/include/boost/msm/backmp11/detail/state_machine_base.hpp @@ -38,6 +38,10 @@ class non_propagating { } + non_propagating(const non_propagating&) + { + } + non_propagating& operator=(const non_propagating&) { return *this; @@ -107,6 +111,8 @@ class context_member protected: static constexpr bool has_context_member = true; + context_member(Context& context) noexcept : m_context(&context) {} + private: template friend class backmp11::state_machine; @@ -137,16 +143,60 @@ class context_member static constexpr bool has_context_member = false; }; +template +class observer_member +{ + protected: + static constexpr bool has_observer_member = true; + + observer_member() = default; + + template >> + observer_member(T&& arg) : m_observer(std::forward(arg)) {} + + private: + template + friend class backmp11::state_machine; + template + friend class state_machine_base; + + Observer m_observer; +}; + +template +class observer_member +{ + protected: + static constexpr bool has_observer_member = false; +}; + +template <> +class observer_member +{ + protected: + static constexpr bool has_observer_member = false; +}; + +template <> +class observer_member +{ + protected: + static constexpr bool has_observer_member = false; +}; + template class state_machine_base : public event_pool_processor>, - public context_member + public context_member, + public observer_member { public: using config_t = Config; using root_sm_t = typename Config::root_sm; using context_t = typename Config::context; + using observer_t = typename Config::observer; /// Gets the context of the state machine. /// See @ref state_machine_config::context. @@ -173,6 +223,31 @@ class state_machine_base } } + /// Get the observer of the state machine. + /// See @ref state_machine_config::observer. + template , + typename = std::enable_if_t> + observer_t& get_observer() + { + return const_cast(std::as_const(*this).get_observer()); + } + + /// Gets the observer of the state machine. + /// See @ref state_machine_config::observer. + template , + typename = std::enable_if_t> + const observer_t& get_observer() const + { + if constexpr (NestingRole == nesting_role::root) + { + return this->m_observer; + } + else + { + return (*m_root_sm)->m_observer; + } + } + /** * @brief Processes up to `max_events` from the event pool. * @@ -215,6 +290,26 @@ class state_machine_base } protected: + using context_member = + detail::context_member; + using observer_member = + detail::observer_member; + + using context_member::context_member; + + using observer_member::observer_member; + + state_machine_base() = default; + + template >> + state_machine_base(context_t& context, T&& arg) + : context_member(context), observer_member(std::forward(arg)) + { + } + machine_state get_machine_state() const { return m_machine_state; diff --git a/include/boost/msm/backmp11/detail/transition_table.hpp b/include/boost/msm/backmp11/detail/transition_table.hpp index 0738d17a..c426416f 100644 --- a/include/boost/msm/backmp11/detail/transition_table.hpp +++ b/include/boost/msm/backmp11/detail/transition_table.hpp @@ -247,6 +247,33 @@ struct transition_table_impl static process_result process(StateMachine& sm, uint8_t region_id, const Event& event) + { + if constexpr (!std::is_same_v) + { + sm.get_observer() + .template pre_process_transition< + typename Row::Source, typename Row::Evt, + typename Row::Target, typename Row::Action, + typename Row::Guard>(sm, region_id); + } + const auto result = process_impl(sm, region_id, event); + if constexpr (!std::is_same_v) + { + sm.get_observer() + .template post_process_transition< + typename Row::Source, typename Row::Evt, + typename Row::Target, typename Row::Action, + typename Row::Guard>(sm, region_id, result); + } + return result; + } + + template + static process_result process_impl(StateMachine& sm, + uint8_t region_id, + const Event& event) { auto& state_id = sm.m_active_state_ids[region_id]; [[maybe_unused]] constexpr auto current_state_id = @@ -309,12 +336,38 @@ struct transition_table_impl { using transition_event = typename Row::Evt; using current_state_type = State; - using next_state_type = current_state_type; template static process_result process(StateMachine& sm, uint8_t region_id, const Event& event) + { + if constexpr (!std::is_same_v) + { + sm.get_observer() + .template pre_process_transition< + typename Row::Source, typename Row::Evt, + typename front::none, typename Row::Action, + typename Row::Guard>(sm, region_id); + } + const auto result = process_impl(sm, region_id, event); + if constexpr (!std::is_same_v) + { + sm.get_observer() + .template post_process_transition< + typename Row::Source, typename Row::Evt, + typename front::none, typename Row::Action, + typename Row::Guard>(sm, region_id, result); + } + return result; + } + + template + static process_result process_impl(StateMachine& sm, + uint8_t region_id, + const Event& event) { [[maybe_unused]] const auto state_id = sm.m_active_state_ids[region_id]; BOOST_ASSERT( @@ -342,6 +395,32 @@ struct transition_table_impl template static process_result process(StateMachine& sm, const Event& event) + { + if constexpr (!std::is_same_v) + { + sm.get_observer() + .template pre_process_transition( + sm); + } + const auto result = process_impl(sm, event); + if constexpr (!std::is_same_v) + { + sm.get_observer() + .template post_process_transition( + sm, result); + } + return result; + } + + template + static process_result process_impl(StateMachine& sm, + const Event& event) { auto& source = sm; auto& target = source; diff --git a/include/boost/msm/backmp11/favor_compile_time.hpp b/include/boost/msm/backmp11/favor_compile_time.hpp index d054ccc8..28f2486b 100644 --- a/include/boost/msm/backmp11/favor_compile_time.hpp +++ b/include/boost/msm/backmp11/favor_compile_time.hpp @@ -392,7 +392,7 @@ struct compile_policy_impl static process_result call_process_event(StateMachine& sm, const any_event& event) { return sm.template get_state() - .process_event_internal(event, process_info::submachine_call); + .process_event_observed(event, process_info::submachine_call); } std::unordered_map m_transition_chains; diff --git a/include/boost/msm/backmp11/observer.hpp b/include/boost/msm/backmp11/observer.hpp new file mode 100644 index 00000000..8f8d3e1a --- /dev/null +++ b/include/boost/msm/backmp11/observer.hpp @@ -0,0 +1,153 @@ +// Copyright 2026 Christian Granzin +// Copyright 2010 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_INTERCEPTOR_HPP +#define BOOST_MSM_BACKMP11_INTERCEPTOR_HPP + +#include + +#include + +namespace boost::msm::backmp11 +{ + +/// Default observer for state machine activities. +/// Defines no-op hooks for all observable activities. +class default_observer +{ + public: + using process_result = backmp11::process_result; + + /// Hook called before a (sub-)machine processes an event. + template + constexpr void pre_process_event(const StateMachine&, const Event&) const + { + } + + /// Hook called after a (sub-)machine processes an event. + template + constexpr void post_process_event(const StateMachine&, const Event&, + process_result) const + { + } + + /// Hook called before a (sub-)machine processes a transition. + template + constexpr void pre_process_transition(const StateMachine&, + size_t /*region_id*/) const + { + } + + /// Hook called after a (sub-)machine processes a transition. + template + constexpr void post_process_transition(const StateMachine&, + size_t /*region_id*/, + process_result) const + { + } + + /// Hook called before a (sub-)machine processes a sm-internal transition. + template + constexpr void pre_process_transition(const StateMachine&) const + { + } + + /// Hook called after a (sub-)machine processes a sm-internal transition. + template + constexpr void post_process_transition(const StateMachine&, + process_result) const + { + } +}; + +/// Wrapper for an observer reference whose lifetime is managed externally. +template +class observer_ref +{ + public: + observer_ref(Observer& observer) noexcept : m_observer(&observer) {} + + Observer& get() const noexcept + { + return *m_observer; + } + + operator Observer&() const noexcept + { + return *m_observer; + } + + /// @copydoc observer::pre_process_event + template + void pre_process_event(StateMachine& sm, const Event& event) const + { + m_observer->pre_process_event(sm, event); + } + + /// @copydoc observer::post_process_event + template + void post_process_event(StateMachine& sm, const Event& event, + process_result result) const + { + m_observer->post_process_event(sm, event, result); + } + + /// @copydoc observer::pre_process_transition + template + void pre_process_transition(StateMachine& sm, + size_t region_id) const + { + m_observer->template pre_process_transition(sm, + region_id); + } + + /// @copydoc observer::post_process_transition + template + void post_process_transition(StateMachine& sm, size_t region_id, + process_result result) const + { + m_observer->template post_process_transition( + sm, region_id, result); + } + + /// @copydoc observer::pre_process_transition + template + void pre_process_transition(StateMachine& sm) const + { + m_observer->template pre_process_transition(sm); + } + + /// @copydoc observer::post_process_transition + template + void post_process_transition(StateMachine& sm, + process_result result) const + { + m_observer->template post_process_transition( + sm, result); + } + + private: + Observer* m_observer; +}; + +} // namespace boost::msm::backmp11 + +#endif // BOOST_MSM_BACKMP11_INTERCEPTOR_HPP diff --git a/include/boost/msm/backmp11/state_machine.hpp b/include/boost/msm/backmp11/state_machine.hpp index 828e0ff0..72fdf5fe 100644 --- a/include/boost/msm/backmp11/state_machine.hpp +++ b/include/boost/msm/backmp11/state_machine.hpp @@ -80,14 +80,16 @@ class state_machine "FrontEnd must be a composite state"); public: + /// Type of the front-end (same as FrontEnd). + using front_end_t = FrontEnd; /// Type of the configuration (same as Config, see @ref state_machine_config). using config_t = typename state_machine_base::config_t; - /// Type of the root machine (see @ref state_machine_config::root_sm). - using root_sm_t = typename state_machine_base::root_sm_t; /// Type of the context (see @ref state_machine_config::context). using context_t = typename state_machine_base::context_t; - /// Type of the front-end (same as FrontEnd). - using front_end_t = FrontEnd; + /// Type of the context (see @ref state_machine_config::observer). + using observer_t = typename state_machine_base::observer_t; + /// Type of the root machine (see @ref state_machine_config::root_sm). + using root_sm_t = typename state_machine_base::root_sm_t; /// Type of the derived machine (corresponds to Derived). using derived_t = mp11::mp_if_c, state_machine, Derived>; @@ -225,13 +227,8 @@ class state_machine using history_impl = detail::history_impl; - struct front_end_init_tag {}; - - template - state_machine(front_end_init_tag, Args&&... args) - : front_end_t(std::forward(args)...) + void init() { - // Same logic as default constructor static_assert( std::is_base_of_v, "Derived must inherit from state_machine"); @@ -242,70 +239,47 @@ class state_machine using visitor_t = detail::init_state_visitor; visitor_t visitor{self()}; detail::visit_if(self(), visitor); + visitor_t::template predicate>(self(), visitor); } } public: - state_machine() : state_machine(front_end_init_tag{}) - { - } - /** - * @brief Constructs and forwards further constructor arguments to the - * front-end. + * @brief Constructs and forwards constructor arguments to the back-end. * - * @param arg Constructor argument for the front-end. - * @param args Constructor arguments for the front-end. + * Requires constructor arguments as configured: + * - Context& (if context = Context is set) + * - Observer& (if observer = observer_ref is set) */ - template , - std::decay_t>::value>, - typename... Args> - state_machine(Arg&& arg, Args&&... args) - : state_machine(front_end_init_tag{}, std::forward(arg), - std::forward(args)...) + template + state_machine(Args&&... args) + : state_machine_base(std::forward(args)...) { + init(); } - /** - * @brief Constructs with a context and forwards further constructor - * arguments to the front-end. - * - * @param context The context. - * @param args Constructor arguments for the front-end. - */ - template , - typename... Args> - state_machine(context_t& context, Args&&... args) - : state_machine(front_end_init_tag{}, std::forward(args)...) + // Copy constructor. + state_machine(state_machine const& rhs) + : state_machine_base(rhs), m_states(rhs.m_states), + m_active_state_ids(rhs.m_active_state_ids) { - if constexpr (!std::is_same_v) - { - static_assert( - std::is_constructible_v, - "Derived must inherit the base class constructors"); - } - this->m_context = &context; + init(); } - // Copy constructor. - state_machine(state_machine const& rhs) : state_machine() + state_machine(state_machine& rhs) + : state_machine(static_cast(rhs)) { - *this = rhs; } // Copy assignment operator. state_machine& operator=(state_machine const& rhs) = default; // Move constructor. - state_machine(state_machine&& rhs) : state_machine() + state_machine(state_machine&& rhs) + : state_machine_base(std::move(rhs)), m_states(std::move(rhs.m_states)), + m_active_state_ids(rhs.m_active_state_ids) { - *this = std::move(rhs); + init(); } // Move assignment operator. @@ -370,7 +344,7 @@ class state_machine template process_result process_event(Event const& event) { - return process_event_internal( + return process_event_observed( compile_policy_impl::normalize_event(event), detail::process_info::direct_call); } @@ -554,7 +528,23 @@ class state_machine private: // Main function used internally to process events. template - process_result process_event_internal(Event const& event, detail::process_info info) + process_result process_event_observed(Event const& event, + detail::process_info info) + { + if constexpr (!std::is_same_v) + { + this->get_observer().pre_process_event(self(), event); + } + const auto result = process_event_impl(event, info); + if constexpr (!std::is_same_v) + { + this->get_observer().post_process_event(self(), event, result); + } + return result; + } + + template + process_result process_event_impl(Event const& event, detail::process_info info) { if (this->m_machine_state != detail::machine_state::idle) { @@ -753,8 +743,22 @@ class state_machine template void operator()(State& state) { + if constexpr (!std::is_same_v) + { + m_self.get_observer() + .template pre_process_transition( + m_self, m_region_id); + } state.on_entry(m_event, m_self.get_fsm_argument()); m_self.template on_state_entry_completed(m_region_id++); + if constexpr (!std::is_same_v) + { + m_self.get_observer() + .template post_process_transition( + m_self, m_region_id, process_result::HANDLED_TRUE); + } } private: diff --git a/include/boost/msm/backmp11/state_machine_config.hpp b/include/boost/msm/backmp11/state_machine_config.hpp index 95478261..4ad0f4ea 100644 --- a/include/boost/msm/backmp11/state_machine_config.hpp +++ b/include/boost/msm/backmp11/state_machine_config.hpp @@ -91,6 +91,9 @@ struct local_transition_owner {}; template struct no_event_pool_container {}; +/// No observer is configured (see @ref state_machine_config::observer). +struct no_observer {}; + /// Default state machine configuration. struct default_state_machine_config { @@ -119,6 +122,8 @@ struct default_state_machine_config /// Type of the Fsm parameter passed in actions and guards. /// Defaults to @ref local_transition_owner. using fsm_parameter = local_transition_owner; + /// Sets up an observer for monitoring state machine activities. + using observer = no_observer; /// Identifies the upper-most machine in hierarchical state machines. /// Defaults to @ref no_root_sm. using root_sm = no_root_sm; diff --git a/test/Backmp11Constructor.cpp b/test/Backmp11Constructor.cpp index b2ca555d..ff78f15d 100644 --- a/test/Backmp11Constructor.cpp +++ b/test/Backmp11Constructor.cpp @@ -9,18 +9,19 @@ // file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -// back-end -#include "BackCommon.hpp" -//front-end -#include #ifndef BOOST_MSM_NONSTANDALONE_TEST #define BOOST_TEST_MODULE test_constructor #endif #include + +// back-end +#include "BackCommon.hpp" +#include +//front-end +#include "FrontCommon.hpp" #include namespace msm = boost::msm; -namespace mpl = boost::mpl; using namespace msm::front; using namespace msm::backmp11; @@ -28,272 +29,60 @@ using namespace msm::backmp11; namespace { - struct SomeExternalContext - { - SomeExternalContext(int b):bla(b){} - int bla; - }; - // events - struct play {}; - struct end_pause {}; - struct stop {}; - struct pause {}; - struct open_close {}; - struct NextSong {}; - struct PreviousSong {}; - - // A "complicated" event type that carries some data. - enum DiskTypeEnum - { - DISK_CD=0, - DISK_DVD=1 - }; - struct cd_detected - { - cd_detected(std::string name, DiskTypeEnum diskType) - : name(name), - disc_type(diskType) - {} - - std::string name; - DiskTypeEnum disc_type; - }; - - template