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
6 changes: 6 additions & 0 deletions doc/modules/ROOT/attachments/backmp11/BackAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ class back_adapter : public boost::msm::backmp11::state_machine<
template <typename Event>
boost::msm::back::HandledEnum process_event(const Event& event)
{
if (this->template is_flag_active<boost::msm::InterruptedFlag>() &&
!this->is_end_interrupt_event(event))
{
return boost::msm::back::HANDLED_TRUE;
}

if (this->get_machine_state() ==
boost::msm::backmp11::machine_state::processing)
{
Expand Down
134 changes: 134 additions & 0 deletions doc/modules/ROOT/attachments/backmp11/InterruptibleStateMachine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// 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 <iostream>

#include <boost/msm/backmp11/state_machine.hpp>
#include <boost/msm/front/functor_row.hpp>
#include <boost/msm/front/state_machine_def.hpp>

namespace back = boost::msm::backmp11;
namespace front = boost::msm::front;
using front::Row;
using front::Internal;
namespace mp11 = boost::mp11;

namespace
{

// Events.
struct InterruptEvent {};
struct SmInternalEvent {};

// Actions.
struct Interrupt
{
template <typename Fsm>
void operator()(Fsm& fsm)
{
fsm.interrupt();
}
};

struct PrintMessage
{
template <typename Fsm>
void operator()(const SmInternalEvent&, Fsm&)
{
if (talk)
{
std::cout << "Processed SmInternalEvent" << std::endl;
}
}

[[maybe_unused]] static inline bool talk{true};
};

// States.
struct MyState : front::state<> {};

struct MyOtherState : front::state<> {};

// State machine.
struct InterruptibleStateMachine_
: front::state_machine_def<InterruptibleStateMachine_>
{
using initial_state = MyState;
using transition_table = mp11::mp_list<
Row<MyState, InterruptEvent, front::none, Interrupt>
>;
using internal_transition_table = mp11::mp_list<
Internal<SmInternalEvent, PrintMessage>
>;
};

template <typename Config = back::state_machine_config>
class InterruptibleStateMachine
: public back::state_machine<InterruptibleStateMachine_, Config,
InterruptibleStateMachine<Config>>
{
using Base = back::state_machine<InterruptibleStateMachine_, Config,
InterruptibleStateMachine<Config>>;

public:
using Base::Base;

void interrupt()
{
m_interrupted = true;
}

void resume()
{
m_interrupted = false;
this->process_event_pool();
}

template <typename Event>
back::process_result process_event(const Event& event)
{
if (m_interrupted)
{
// Enqueue all events for later processing
// as long as the state machine is interrupted.
this->enqueue_event(event);
return back::process_result::deferred;
}
else
{
return Base::process_event(event);
}
}

private:
bool m_interrupted{};
};


[[maybe_unused]] void interruptible_state_machine_example()
{
InterruptibleStateMachine<> state_machine;

state_machine.start();

// Interrupt the state machine.
state_machine.process_event(InterruptEvent{});

// The event is not yet processed.
state_machine.process_event(SmInternalEvent{});

// Resuming the state machine processes the event and prints:
// "Processed SmInternalEvent"
state_machine.resume();
}

} // namespace

Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,22 @@ std::string to_boost_json_string(const DimSwitch& dim_switch)
// The initial state is Off (state id 0).
dim_switch.start();
// Prints:
// {"front_end":{"brightness":0},"states":{"1":{"times_pressed":0}},"active_state_ids":[0],"stopped":false}
// {"front_end":{"brightness":0},"states":{"1":{"times_pressed":0}},"active_state_ids":[0],"machine_state":1}
std::cout << to_boost_json_string(dim_switch) << std::endl;

// Turn On (state id 1) and set brightness to 75.
dim_switch.process_event(TurnOn{});
dim_switch.process_event(Dim{75});
// Prints:
// {"front_end":{"brightness":75},"states":{"1":{"times_pressed":1}},"active_state_ids":[1],"stopped":false}
// {"front_end":{"brightness":75},"states":{"1":{"times_pressed":1}},"active_state_ids":[1],"machine_state":1}
std::cout << to_boost_json_string(dim_switch) << std::endl;

// Deserialize the json into a new state machine.
const auto json = boost::json::parse(to_boost_json_string(dim_switch));
const auto dim_switch_2 = boost::json::value_to<DimSwitch>(json);

// Prints:
// {"front_end":{"brightness":75},"states":{"1":{"times_pressed":1}},"active_state_ids":[1],"stopped":false}
// {"front_end":{"brightness":75},"states":{"1":{"times_pressed":1}},"active_state_ids":[1],"machine_state":1}
std::cout << to_boost_json_string(dim_switch_2) << std::endl;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ std::string to_nlohmann_json_string(const DimSwitch& dim_switch)
// "front_end": {
// "brightness": 0
// },
// "machine_state": 1,
// "states": {
// "1": {
// "times_pressed": 0
// }
// },
// "stopped": false
// }
// }
std::cout << to_nlohmann_json_string(dim_switch) << std::endl;

Expand All @@ -79,12 +79,12 @@ std::string to_nlohmann_json_string(const DimSwitch& dim_switch)
// "front_end": {
// "brightness": 75
// },
// "machine_state": 1,
// "states": {
// "1": {
// "times_pressed": 1
// }
// },
// "stopped": false
// }
// }
std::cout << to_nlohmann_json_string(dim_switch) << std::endl;
// Deserialize the json into a new state machine.
Expand All @@ -100,12 +100,12 @@ std::string to_nlohmann_json_string(const DimSwitch& dim_switch)
// "front_end": {
// "brightness": 75
// },
// "machine_state": 1,
// "states": {
// "1": {
// "times_pressed": 1
// }
// },
// "stopped": false
// }
// }
std::cout << to_nlohmann_json_string(dim_switch_2) << std::endl;
}
Expand Down
67 changes: 28 additions & 39 deletions doc/modules/ROOT/pages/backmp11-back-end.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ 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 | 236 | 18 | 0.2
| backmp11_favor_compile_time | 2 | 226 | 44 | 2.2
| backmp11 | 2 | 236 | 19 | 0.2
| backmp11_favor_compile_time | 2 | 225 | 44 | 2.2
| sml | 4 | 254 | 64 | 0.1
|=======================================================================================================

Expand All @@ -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 | 362 | 55 | 0.9
| backmp11_favor_compile_time | 3 | 266 | 96 | 7.5
| sml | 13 | 612 | 460 | 2.8
| backmp11 | 5 | 360 | 57 | 0.9
| backmp11_favor_compile_time | 3 | 265 | 96 | 7.6
| sml | 13 | 612 | 431 | 2.8
|=======================================================================================================


Expand Down Expand Up @@ -173,7 +173,7 @@ When a transition defined in SM1 causes SM2 and SM3 to exit:

=== Event pool

The setting `event_pool` configures the container and inline storage the back-end uses to hold work that cannot be processed immediately: enqueued events, deferred events, and completion transitions.
The setting `event_pool` configures the container and inline storage the back-end uses to hold work that cannot be processed immediately: enqueued events, deferred events, completion transitions, and state machine termination.

```cpp
template <template <typename> typename Container,
Expand All @@ -200,7 +200,7 @@ By replacing `std::deque` with a fixed-capacity container and setting `InlineCap
See the xref:backmp11-back-end/examples.adoc#_heapless_state_machine[heapless state machine example] for a demo using `boost::container::static_vector`.
====

Use `no_event_pool` to disable the event pool if the state machine does not require event enqueueing, deferral, and completion transitions:
Use `no_event_pool` to deactivate the event pool if you don't need it. This setting disables all related functionality, improves runtime performance, and reduces the state machine's memory footprint.

```cpp
struct MyConfig : state_machine_config
Expand Down Expand Up @@ -298,11 +298,13 @@ struct MyStateMachine_ : public msm::front::state_machine_def<MyStateMachine_>

== Start and stop

A newly constructed state machine is inactive and does not process events as long as it is not yet started. The xref:reference:boost/msm/backmp11/state_machine/start-04.adoc[`void state_machine::start()`] method activates the state machine by first calling the state machine front-end's entry action and then the initial state's. By default, the event passed to the entry actions is an empty struct xref:reference:boost/msm/backmp11/starting.adoc[`starting`]. You can pass a custom event with the overload xref:reference:boost/msm/backmp11/state_machine/start-0a.adoc[`void state_machine::start(const auto& initial_event)`].
A newly constructed state machine does not accept events as long as it is not yet started. Its own state is represented in the xref:reference:boost/msm/backmp11/machine_state.adoc[`machine_state`] enum, which you can query with xref:reference:boost/msm/backmp11/state_machine/get_machine_state.adoc[`machine_state state_machine::get_machine_state()`].

The xref:reference:boost/msm/backmp11/state_machine/stop-03.adoc[`void state_machine::stop()`] method deactivates the state machine by first calling the active state's exit action and then the state machine front-end's. The default event in this case is an empty struct xref:reference:boost/msm/backmp11/stopping.adoc[`stopping`], and a similar overload xref:reference:boost/msm/backmp11/state_machine/stop-05.adoc[`void state_machine::stop(const auto& final_event)`] exists to customize it.
The xref:reference:boost/msm/backmp11/state_machine/start-04.adoc[`void state_machine::start()`] method activates the state machine with a xref:reference:boost/msm/backmp11/starting.adoc[`starting`] event by first calling the state machine front-end's entry action and then the initial state's. You can pass a custom start event with the overload xref:reference:boost/msm/backmp11/state_machine/start-0a.adoc[`void state_machine::start(const auto& initial_event)`].

Calling `start(...)` on an active or `stop(...)` on an inactive state machine has no effect.
The xref:reference:boost/msm/backmp11/state_machine/stop-03.adoc[`void state_machine::stop()`] method deactivates the state machine with a xref:reference:boost/msm/backmp11/stopping.adoc[`stopping`] event by first calling the active state's exit action and then the state machine front-end's. A similar overload xref:reference:boost/msm/backmp11/state_machine/stop-05.adoc[`void state_machine::stop(const auto& final_event)`] exists to pass a custom stop event.

Calling `start(...)` on an active or `stop(...)` on an inactive state machine has no effect. Note that a state machine must be ready to process events in order to stop it.


== Handling events
Expand Down Expand Up @@ -452,20 +454,17 @@ Serializing a state machine to JSON provides a neat way to inspect it in a human

The back-end uses the following run-to-completion algorithm for processing events:

* Return if the state machine is not ready to process the event. It is ready if:
** it is started and
** it is not already processing an event and
** either no terminate state is active, or an interrupt state is active and the event ends the interrupt state
* Return if the state machine is not ready to process the event. It is ready if it is started and not already processing
* If an active state defers the event, push it to the end of the event pool and return
* Dispatch the event to the transition table of every region, in the order defined by the front-end's `initial_state`
* For each transition matching the active state and the event (in _reverse_ order of the front-end's transition table):
* _For each transition_ matching the active state and the event (in _reverse_ order of the front-end's transition table):
** Execute the transition if there is no guard or it returns true, otherwise skip it
** If the transition has a target state, call the source state's exit action and then switch the active state to the target state
** If the transition has a transition action, call it
** If the transition has a target state, call the target state's entry action
** If a completion transition exists for the target state, push its execution to the beginning of the event pool
** Consider the event processed and finish the transition execution loop
* If the event is not yet processed and the state machine has an internal transition table, dispatch it to the internal transition table as well. For each transition matching the event (in _reverse_ order of the front-end's internal transition table):
* If the event is not yet processed and the state machine has an internal transition table, dispatch it to the internal transition table as well. _For each internal transition_ matching the event (in _reverse_ order of the front-end's internal transition table):
** Execute the transition if there is no guard or it returns true, otherwise skip it
** If the transition has a transition action, call it
** Consider the event processed and finish the transition execution loop
Expand All @@ -491,39 +490,34 @@ The following sections provide further details about the differences between `ba

=== Public API of `state_machine`

The following adapter pseudo-code illustrates the differences from the `back::state_machine` API (see xref:backmp11-back-end/examples.adoc#_back_adapter[`full code example`]).
The following adapter pseudo-code illustrates the main differences from the `back::state_machine` API (see xref:backmp11-back-end/examples.adoc#_back_adapter[`full code example`]).

```cpp
class back_adapter
{
template <typename Event>
back::HandledEnum process_event(const Event& event)
{
if (this->get_machine_state() == detail::machine_state::processing)
if (get_machine_state() == machine_state::processing)
{
this->enqueue_event(event);
enqueue_event(event);
return back::HANDLED_DEFERRED;
}
else
{
try
{
return Base::process_event(event);
return static_cast<back::HandledEnum>(
Base::process_event(event));
}
catch (std::exception& e)
{
this->exception_caught(event, *this, e);
exception_caught(event, *this, e);
return back::HANDLED_FALSE;
}
}
}

// The new API returns a const std::array<...>&.
const uint16_t* current_state() const
{
return &this->get_active_state_ids()[0];
}

auto& get_message_queue()
{
return this->get_event_pool().events;
Expand Down Expand Up @@ -553,18 +547,6 @@ class back_adapter
{
this->get_event_pool().events.clear();
}

// No adapter.
// Superseded by the visitor API.
// void visit_current_states(...) {...}

// No adapter.
// States can be set with `get_state<...>() = ...` or the visitor API.
// void set_states(...) {...}

// No adapter.
// Could be implemented with the visitor API.
// auto get_state_by_id(int id) {...}
};
```

Expand All @@ -579,6 +561,13 @@ Futhermore, the back-end forwards Kleene events without converting them to `std:

=== Removed features

==== Interrupt states

Not supported, because the interrupt state evaluation requires a visitor to determine whether an interrupt state is active. This significantly increases the state machine's compilation time and runtime as soon as an interrupt state is defined. If needed, the interrupt state feature can be resembled with a state machine extension (see xref:backmp11-back-end/examples.adoc#_back_adapter[`back` adapter example]).

In `backmp11`, you can extend the state machine API to implement your own interrupt mechanism and tailor it to your needs. See the xref:backmp11-back-end/examples.adoc#_interruptible_state_machine[`interruptible state machine example`], which allows interruptions from any state and enqueues events for later processing instead of discarding them.


==== Initialization of states in the constructor and the `set_states(...)` method

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.
Expand Down
Loading
Loading