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
1 change: 1 addition & 0 deletions doc/modules/ROOT/attachments/backmp11/BackAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ struct back_config_adapter : boost::msm::backmp11::state_machine_config
template <typename T>
using event_pool_container =
typename QueueContainerPolicy::template In<T>::type;
using event_pool = boost::msm::backmp11::event_pool<event_pool_container>;
};

template <class A1, class A2, class A3, class A4>
Expand Down
29 changes: 16 additions & 13 deletions doc/modules/ROOT/attachments/backmp11/HeaplessStateMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,26 @@ namespace mp11 = boost::mp11;
namespace
{

static constexpr size_t max_event_size = 32;

struct MyConfig : back::state_machine_config
{
// Use a static vector as event pool container.
// This container is sufficient for enqueueing and deferring events,
// but it does not support completion transitions.
// If needed, use a heapless deque implementation (e.g. etl::deque).
template <typename T>
using static_vector = boost::container::static_vector<T, 10>;
using event_pool =
back::event_pool</*Container=*/static_vector,
/*InlineCapacity=*/max_event_size>;
};

// Events.
struct Greet
{
// A heapless event must be copy constructible and nothrow move constructible.
// The maximum size depends on alignment requirements and the target system,
// 32 bytes should always work.
alignas(void*) std::array<char, 32> message{};
std::array<char, max_event_size> message{};
};

// Actions.
Expand Down Expand Up @@ -65,16 +78,6 @@ struct MyStateMachine_ : front::state_machine_def<MyStateMachine_>
>;
};

struct MyConfig : back::state_machine_config
{
// Use a static vector as event pool container.
// This container is sufficient for enqueueing and deferring events,
// but it does not support completion transitions.
// If needed, use a heapless deque implementation (e.g. etl::deque).
template <typename T>
using event_pool_container = boost::container::static_vector<T, 10>;
};

using MyStateMachine = back::state_machine<MyStateMachine_, MyConfig>;

[[maybe_unused]] void heapless_state_machine_example()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ std::string to_boost_json_string(const DimSwitch& dim_switch)

// 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}
std::cout << to_boost_json_string(dim_switch) << std::endl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ std::string to_nlohmann_json_string(const DimSwitch& dim_switch)

// Turn On (state id 1) and set brightness to 75.
dim_switch.process_event(TurnOn{});
dim_switch.process_event(Dim{75});
// Prints:
// {
// "active_state_ids": [
Expand Down
34 changes: 25 additions & 9 deletions doc/modules/ROOT/pages/backmp11-back-end.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ struct default_state_machine_config
using context = no_context;
// Optimizes for runtime speed or compile time.
using compile_policy = favor_runtime_speed;
// Configures the container type of the event pool.
template <typename T>
using event_pool_container = std::deque<T>;
// Configures the event pool.
using event_pool = backmp11::event_pool<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.
Expand Down Expand Up @@ -174,24 +173,41 @@ When a transition defined in SM1 causes SM2 and SM3 to exit:

=== Event pool

The setting `event_pool_container` defines the container type of the state machine's event pool. It requires an event pool to handle event enqueueing, deferral, and completion transitions. The default event pool container is a `std::deque<T>`.
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 event pool container can be customized with `using event_pool_container = MyEventPoolContainer<T>;`. It has to support push without iterator invalidation, specifically the following API calls:
```cpp
template <template <typename> typename Container,
size_t InlineCapacity = 32,
size_t InlineAlign = alignof(std::max_align_t)>
struct event_pool;
```

The default configuration uses `std::deque`. Each pooled event occupies a type-erased slot in the container.
To avoid a heap allocation per slot, the back-end uses a small-object optimization: events that fit within `InlineCapacity` bytes and `InlineAlign` alignment are stored directly in the slot. Events exceeding either limit fall back to heap allocation.

- `push_back(const T&)` (for event enqueueing and deferral)
- `push_front(const T&)` (for completion transitions)
The container must not invalidate existing iterators when `push_back` or `push_front` is called. The required interface is:

- `push_back(const T&)` — for event enqueueing and deferral
- `push_front(const T&)` — for completion transitions
- `begin()`
- `end()`
- `erase(iterator)`
- `clear()`

[NOTE]
====
By replacing the default `std::deque` with a fixed-capacity container, the state machine can operate entirely without heap allocation.
By replacing `std::deque` with a fixed-capacity container and setting `InlineCapacity` and `InlineAlign` to cover all event types, the state machine can operate entirely without heap allocation.
See the xref:backmp11-back-end/examples.adoc#_heapless_state_machine[heapless state machine example] for a demo using `boost::container::static_vector`.
====

You can deactivate the event pool with `using event_pool_container = no_event_pool_container<T>;`.
Use `no_event_pool` to disable the event pool if the state machine does not require event enqueueing, deferral, and completion transitions:

```cpp
struct MyConfig : state_machine_config
{
using event_pool = no_event_pool;
};
```


=== Compile policy
Expand Down
2 changes: 1 addition & 1 deletion doc/modules/ROOT/pages/backmp11-back-end/examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ It is helpful for utilizing `backmp11` in a legacy code base.

This example demonstrates a state machine configured for heapless execution.

It uses `boost::container::static_vector` as the event pool container — a fixed-capacity replacement for the default `std::deque` container. Note that this container type does not support completion events; use a heapless deque (e.g. `etl::deque`) if those are required.
It uses `boost::container::static_vector` as the event pool container — a fixed-capacity replacement for the default `std::deque` container. Note that this container type does not support completion transitions; use a heapless deque (e.g. `etl::deque`) if those are required.


== xref:attachment$backmp11/Logger.cpp[Logger]
Expand Down
3 changes: 2 additions & 1 deletion doc/modules/ROOT/pages/version-history.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
** 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])
** Configurable event pool and inline storage (https://github.com/boostorg/msm/issues/239[#239])
* 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 deprecated 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
Expand Down
26 changes: 16 additions & 10 deletions include/boost/msm/backmp11/detail/event_pool_processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <boost/config.hpp>

#include <boost/msm/backmp11/detail/basic_polymorphic.hpp>
#include <boost/msm/backmp11/detail/common.hpp>
#include <boost/msm/backmp11/state_machine_config.hpp>

Expand Down Expand Up @@ -97,15 +98,23 @@ class deferred_event : public event_occurrence
Event m_event;
};

template <template <typename> typename EventPoolContainer,
typename ProcessableEvent,
template <typename Config,
typename = void>
class event_pool_processor
{
struct alignas(Config::inline_align) dummy_event
{
char data[Config::inline_capacity];
};
using deferred_dummy = deferred_event<dummy_event>;

protected:
static constexpr bool has_event_pool = true;
using processable_event = ProcessableEvent;
using event_pool_container_t = EventPoolContainer<processable_event>;
using processable_event =
basic_polymorphic<event_occurrence, sizeof(deferred_dummy),
alignof(deferred_dummy)>;
using event_pool_container_t =
typename Config::template container_t<processable_event>;

struct event_pool_t
{
Expand Down Expand Up @@ -178,13 +187,10 @@ class event_pool_processor
event_pool_t m_event_pool;
};

template <template <typename> typename EventPoolContainer,
typename ProcessableEvent>
template <typename Config>
class event_pool_processor<
EventPoolContainer,
ProcessableEvent,
std::enable_if_t<std::is_same_v<EventPoolContainer<ProcessableEvent>,
no_event_pool_container<ProcessableEvent>>>>
Config,
std::enable_if_t<std::is_same_v<Config, no_event_pool>>>
{
protected:
static constexpr bool has_event_pool = false;
Expand Down
6 changes: 2 additions & 4 deletions include/boost/msm/backmp11/detail/state_machine_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,7 @@ class observer_member<no_observer, nesting_role::unknown>

template <typename Config, nesting_role NestingRole>
class state_machine_base
: public event_pool_processor<Config::template event_pool_container,
basic_polymorphic<event_occurrence>>,
: public event_pool_processor<typename Config::event_pool>,
public context_member<typename Config::context, NestingRole>,
public observer_member<typename Config::observer, NestingRole>
{
Expand Down Expand Up @@ -331,8 +330,7 @@ class state_machine_base
friend struct transition_table_impl;

using event_pool_processor =
detail::event_pool_processor<Config::template event_pool_container,
basic_polymorphic<event_occurrence>>;
detail::event_pool_processor<typename Config::event_pool>;
using root_sm_base =
state_machine_base<Config, get_root_nesting_role(NestingRole)>;

Expand Down
33 changes: 25 additions & 8 deletions include/boost/msm/backmp11/state_machine_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,27 @@ struct no_root_sm {};
/// (see @ref state_machine_config::fsm_parameter).
struct local_transition_owner {};

/// Deactivates the event pool (see @ref state_machine_config::event_pool_container).
template <typename T>
struct no_event_pool_container {};
/**
* @brief Defines the container type and inline storage for the event pool (see
* @ref state_machine_config::event_pool).
*
* @tparam Container Type of the container to use.
* @tparam InlineCapacity Max inline storage per event.
* @tparam InlineAlign Inline storage alignment.
*/
template <template <typename...> typename Container,
size_t InlineCapacity = 32,
size_t InlineAlign = alignof(std::max_align_t)>
struct event_pool
{
template <typename T>
using container_t = Container<T>;
static constexpr size_t inline_capacity = InlineCapacity;
static constexpr size_t inline_align = InlineAlign;
};

/// Deactivates the event pool (see @ref state_machine_config::event_pool).
struct no_event_pool {};

/// No observer is configured (see @ref state_machine_config::observer).
struct no_observer {};
Expand All @@ -111,14 +129,13 @@ struct default_state_machine_config
*/
using compile_policy = favor_runtime_speed;
/**
* @brief Configures the container type of the event pool. Defaults to
* `std::deque`.
* @brief Configures the event pool.
* Defaults to `std::deque` and 32 bytes inline storage capacity per event.
*
* The event pool is required to handle deferred events, enqueued events,
* The event pool is required to handle enqueued events, deferred events,
* and completion transitions.
*/
template <typename T>
using event_pool_container = std::deque<T>;
using event_pool = backmp11::event_pool<std::deque>;
/// Type of the Fsm parameter passed in actions and guards.
/// Defaults to @ref local_transition_owner.
using fsm_parameter = local_transition_owner;
Expand Down
3 changes: 1 addition & 2 deletions test/Backmp11Transitions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ struct StateMachine_;
struct default_config : state_machine_config
{
// using root_sm = StateMachine;
template <typename T>
using event_pool_container = no_event_pool_container<T>;
using event_pool = no_event_pool;
};
struct favor_compile_time_config : default_config
{
Expand Down
Loading