diff --git a/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc b/doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc index febb855a..fd4801c2 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 | 10 | 844 | 73 | 0.7 | back_favor_compile_time | 12 | 821 | 241 | 1.0 | back11 | 26 | 2675 | 92 | 0.7 -| backmp11 | 2 | 239 | 32 | 0.4 +| backmp11 | 2 | 237 | 21 | 0.4 | backmp11_favor_compile_time | 2 | 225 | 45 | 2.2 | sml | 3 | 242 | 57 | 0.1 |======================================================================================================= @@ -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 | 32 | 2160 | 252 | 3.7 | back_favor_compile_time | 37 | 1747 | 974 | 263 -| backmp11 | 5 | 371 | 91 | 2.0 -| backmp11_favor_compile_time | 3 | 264 | 98 | 7.6 +| backmp11 | 5 | 362 | 52 | 1.8 +| backmp11_favor_compile_time | 3 | 263 | 98 | 7.6 | backmp11_favor_compile_time_multi_cu | 3 | ~934 | 99 | 8.6 | sml | 12 | 567 | 436 | 2.5 |================================================================================================================ @@ -269,14 +269,13 @@ Calling `start()` on an active or `stop()` on an inactive state machine has no e == Handling events -Use `process_result process_event(const Event&)` to start processing an event while the state machine is idle or enqueue an event while the state machine is processing. +Use `process_result process_event(const Event&)` to start processing an event while the state machine is idle. You can enqueue an event for consecutive processing in an action with `void enqueue_event(const Event&)`. The event will be processed immediately after the current event is done processing. -The back-end supports event deferral with the front-end's `deferred_events` state property. Deferred events are evaluated in the same order they have been deferred, ensuring FIFO processing semantics. They are stored in the event pool of the state machine that was requested to process the event. In hierarchical state machines this is usually the root state machine, in which case all submachines are able to receive the event upon dispatch. Event deferral in orthogonal regions behaves as described in the UML standard: As long as one active region decides to defer an event, it remains deferred for all regions. Events stay in the event pool +The back-end supports event deferral with the front-end's `deferred_events` state property. Deferred events are evaluated in the same order they have been deferred, ensuring FIFO processing semantics. They are stored in the event pool of the state machine that was requested to process the event. In hierarchical state machines this is usually the root state machine, in which case all submachines are able to receive the event upon dispatch. Event deferral in orthogonal regions behaves as described in the UML standard: As long as one active region decides to defer an event, it remains deferred for all regions. -_Conditional deferral_ is a `backmp11`-exclusive extension of the `deferred_events` property in states. -Deferral can be made conditional by defining a `bool is_event_deferred(...)` method in the state: +_Conditional deferral_ is a `backmp11`-exclusive extension of the `deferred_events` property in states. Deferral can be made conditional by defining a `bool is_event_deferred(...)` method in the state: ```cpp struct MyState : boost::msm::front::state<> @@ -293,8 +292,7 @@ struct MyState : boost::msm::front::state<> }; ``` -You can also defer events in transitions by using the `front::Defer` action. While this mechanism offers additional flexibility for event deferral, -it has a couple of limitations: +You can also defer events in transitions by using the `front::Defer` action. While this mechanism offers additional flexibility for event deferral, it has a couple of limitations: - It uses the event pool of the state machine passed by the `Fsm` parameter - if this is not the root machine in hierarchical state machines, state machines further up the hierarchy cannot receive the event. - Action-deferred events get dispatched for evaluation and then put back into the event pool. This requires additional runtime and prevents deferred events from being processed in FIFO order. @@ -460,6 +458,20 @@ class state_machine_adapter { using Flag_AND = backmp11::flag_and; + template + back::HandledEnum process_event(const Event& event) + { + if (this->get_machine_state() == detail::machine_state::processing) + { + this->enqueue_event(event); + return back::HANDLED_DEFERRED; + } + else + { + return Base::process_event(event); + } + } + // The new API returns a const std::array<...>&. const uint16_t* current_state() const { diff --git a/doc/modules/ROOT/pages/version-history.adoc b/doc/modules/ROOT/pages/version-history.adoc index 283f5381..305bfac0 100644 --- a/doc/modules/ROOT/pages/version-history.adoc +++ b/doc/modules/ROOT/pages/version-history.adoc @@ -6,6 +6,10 @@ * New features (`backmp11`): ** Provide a reflection API and serialization support for Boost.Serialization, Boost.JSON and nlohmann/json (https://github.com/boostorg/msm/issues/197[#197]) +** **Breaking change**: Make enqueuing events explicit (https://github.com/boostorg/msm/issues/178[#178]) +* Bug fixes (`backmp11`): +** State machine processes events although it is not running (https://github.com/boostorg/msm/issues/198[#198]) +* **Breaking change (`backmp11`)**: The APIs `process_queued_events` and `process_single_queued_event` are removed, use `process_event_pool` instead == Boost 1.91 diff --git a/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp b/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp index 8de2c505..83fe7d88 100644 --- a/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp +++ b/include/boost/msm/backmp11/detail/favor_runtime_speed.hpp @@ -71,7 +71,7 @@ struct compile_policy_impl< }; template - static bool is_event_deferred(const StateMachine& sm, const Event& event) + static constexpr bool needs_event_deferral_check() { // Instantiate the templates for checking lazily, // optimize for the no deferred events case. @@ -91,13 +91,38 @@ struct compile_policy_impl< // We have deferring states that defer this event. if constexpr (minimal_visit_set::needs_traversal::value) { - using state_visitor = - event_deferral_visitor; - visitor_t visitor{event}; - state_visitor::visit(sm, visitor); - return visitor.result(); + return true; + } + } + return false; + } + + template + static bool is_event_deferred(const StateMachine& sm, const Event& event) + { + if constexpr (needs_event_deferral_check()) + { + using visitor_t = is_event_deferred_visitor; + using state_visitor = + event_deferral_visitor; + visitor_t visitor{event}; + state_visitor::visit(sm, visitor); + return visitor.result(); + } + return false; + } + + template + static bool try_defer_event(StateMachine& sm, const Event& event) + { + if constexpr (needs_event_deferral_check()) + { + if (is_event_deferred(sm, event)) + { + defer_event(sm, event, false); + return true; } } return false; diff --git a/include/boost/msm/backmp11/detail/state_machine_base.hpp b/include/boost/msm/backmp11/detail/state_machine_base.hpp index f4e217fe..d6eb20b1 100644 --- a/include/boost/msm/backmp11/detail/state_machine_base.hpp +++ b/include/boost/msm/backmp11/detail/state_machine_base.hpp @@ -697,15 +697,13 @@ class state_machine_base : public FrontEnd { if (info != process_info::event_pool) { - // If we are already processing or the event is deferred in the + // If 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_machine_state == machine_state::processing || - (info != process_info::submachine_call && - compile_policy_impl::is_event_deferred(self(), event))) + if (info != process_info::submachine_call && + compile_policy_impl::try_defer_event(self(), event)) { - compile_policy_impl::defer_event(self(), event, false); return process_result::HANDLED_DEFERRED; } @@ -716,9 +714,10 @@ class state_machine_base : public FrontEnd } else { - BOOST_ASSERT_MSG(m_machine_state != machine_state::processing, - "An event pool must be available to call " - "process_event while processing an event"); + if (m_machine_state == machine_state::processing) + { + return process_result::HANDLED_FALSE; + } } // Process the event. diff --git a/include/boost/msm/backmp11/favor_compile_time.hpp b/include/boost/msm/backmp11/favor_compile_time.hpp index afe749d4..6ae10500 100644 --- a/include/boost/msm/backmp11/favor_compile_time.hpp +++ b/include/boost/msm/backmp11/favor_compile_time.hpp @@ -175,8 +175,8 @@ struct compile_policy_impl using visitor_t = is_event_deferred_visitor; using state_visitor = event_deferral_visitor; + visitor_t, + visitor_t::template predicate>; if constexpr (state_visitor::needs_traversal::value) { visitor_t visitor{event}; @@ -186,6 +186,25 @@ struct compile_policy_impl return false; } + template + static bool try_defer_event(StateMachine& sm, const any_event& event) + { + using visitor_t = is_event_deferred_visitor; + using state_visitor = + event_deferral_visitor; + if constexpr (state_visitor::needs_traversal::value) + { + if (is_event_deferred(sm, event)) + { + defer_event(sm, event, false); + return true; + } + } + return false; + } + template static void defer_event(StateMachine& sm, any_event const& event, bool next_rtc_seq) diff --git a/test/Backmp11Adapter.hpp b/test/Backmp11Adapter.hpp index 83b9405d..7ab48821 100644 --- a/test/Backmp11Adapter.hpp +++ b/test/Backmp11Adapter.hpp @@ -134,6 +134,20 @@ class state_machine_adapter public: using Base::Base; + template + back::HandledEnum process_event(const Event& event) + { + if (this->get_machine_state() == detail::machine_state::processing) + { + this->enqueue_event(event); + return back::HANDLED_DEFERRED; + } + else + { + return Base::process_event(event); + } + } + // The new API returns a const std::array<...>&. const uint16_t* current_state() const { diff --git a/test/Backmp11Transitions.cpp b/test/Backmp11Transitions.cpp index 9bde6d0d..db21c17b 100644 --- a/test/Backmp11Transitions.cpp +++ b/test/Backmp11Transitions.cpp @@ -41,14 +41,19 @@ struct TriggerInternalTransitionWithGuard }; struct TriggerSmInternalTransition{}; struct TriggerAnyTransition{}; +struct TriggerNoTransition{}; // Actions struct MyAction { template - void operator()(const Event&, Fsm&, Source& source, Target&) + void operator()(const Event&, Fsm& fsm, Source& source, Target&) { source.action_counter++; + // Attempting to process events while the state machine is processing + // shall discard the event. + BOOST_REQUIRE(fsm.process_event(TriggerNoTransition{}) == + process_result::HANDLED_FALSE); } };