Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions doc/modules/ROOT/pages/tutorial/backmp11-back-end.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
|=======================================================================================================
Expand All @@ -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
|================================================================================================================
Expand Down Expand Up @@ -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<>
Expand All @@ -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.
Expand Down Expand Up @@ -460,6 +458,20 @@ class state_machine_adapter
{
using Flag_AND = backmp11::flag_and;

template <typename Event>
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
{
Expand Down
4 changes: 4 additions & 0 deletions doc/modules/ROOT/pages/version-history.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
41 changes: 33 additions & 8 deletions include/boost/msm/backmp11/detail/favor_runtime_speed.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ struct compile_policy_impl<
};

template <typename StateMachine, typename Event>
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.
Expand All @@ -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<const StateMachine, visitor_t,
visitor_t::template predicate,
visitor_t::template predicate2>;
visitor_t visitor{event};
state_visitor::visit(sm, visitor);
return visitor.result();
return true;
}
}
return false;
}

template <typename StateMachine, typename Event>
static bool is_event_deferred(const StateMachine& sm, const Event& event)
{
if constexpr (needs_event_deferral_check<StateMachine, Event>())
{
using visitor_t = is_event_deferred_visitor<Event>;
using state_visitor =
event_deferral_visitor<const StateMachine, visitor_t,
visitor_t::template predicate,
visitor_t::template predicate2>;
visitor_t visitor{event};
state_visitor::visit(sm, visitor);
return visitor.result();
}
return false;
}

template <typename StateMachine, typename Event>
static bool try_defer_event(StateMachine& sm, const Event& event)
{
if constexpr (needs_event_deferral_check<StateMachine, Event>())
{
if (is_event_deferred(sm, event))
{
defer_event(sm, event, false);
return true;
}
}
return false;
Expand Down
15 changes: 7 additions & 8 deletions include/boost/msm/backmp11/detail/state_machine_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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.
Expand Down
23 changes: 21 additions & 2 deletions include/boost/msm/backmp11/favor_compile_time.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ struct compile_policy_impl<favor_compile_time>
using visitor_t = is_event_deferred_visitor;
using state_visitor =
event_deferral_visitor<const StateMachine,
visitor_t,
visitor_t::template predicate>;
visitor_t,
visitor_t::template predicate>;
if constexpr (state_visitor::needs_traversal::value)
{
visitor_t visitor{event};
Expand All @@ -186,6 +186,25 @@ struct compile_policy_impl<favor_compile_time>
return false;
}

template <typename StateMachine>
static bool try_defer_event(StateMachine& sm, const any_event& event)
{
using visitor_t = is_event_deferred_visitor;
using state_visitor =
event_deferral_visitor<const StateMachine,
visitor_t,
visitor_t::template predicate>;
if constexpr (state_visitor::needs_traversal::value)
{
if (is_event_deferred(sm, event))
{
defer_event(sm, event, false);
return true;
}
}
return false;
}

template <typename StateMachine>
static void defer_event(StateMachine& sm, any_event const& event,
bool next_rtc_seq)
Expand Down
14 changes: 14 additions & 0 deletions test/Backmp11Adapter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ class state_machine_adapter
public:
using Base::Base;

template <typename Event>
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
{
Expand Down
7 changes: 6 additions & 1 deletion test/Backmp11Transitions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,19 @@ struct TriggerInternalTransitionWithGuard
};
struct TriggerSmInternalTransition{};
struct TriggerAnyTransition{};
struct TriggerNoTransition{};

// Actions
struct MyAction
{
template<typename Event, typename Fsm, typename Source, typename Target>
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);
}
};

Expand Down
Loading