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
89 changes: 89 additions & 0 deletions doc/modules/ROOT/attachments/backmp11/HeaplessStateMachine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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/container/static_vector.hpp>

#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::none;
using front::Row;
namespace mp11 = boost::mp11;

namespace
{

// 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{};
};

// Actions.
struct PrintMessage
{
template <typename Fsm>
void operator()(const Greet& greet, Fsm&)
{
std::cout << greet.message.data();
}
};

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

// State machine.
struct MyStateMachine_ : front::state_machine_def<MyStateMachine_>
{
template <typename Fsm>
void on_entry(const back::starting&, Fsm& fsm)
{
fsm.enqueue_event(Greet{"Hello"});
fsm.enqueue_event(Greet{" heapless"});
fsm.enqueue_event(Greet{" world\n"});
}

using initial_state = MyState;
using transition_table = mp11::mp_list<
Row<MyState, Greet, none, PrintMessage>
>;
};

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()
{
MyStateMachine state_machine;

// Prints "Hello heapless world",
// triggered by multiple events processed from the event pool.
state_machine.start();
}

}
20 changes: 12 additions & 8 deletions doc/modules/ROOT/pages/backmp11-back-end.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -174,20 +174,23 @@ 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 deferred events, enqueued events, and completion transitions. The default event pool container is a `std::deque<T>`.
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>`.

You can explicitly request a state machine to process events from its event pool with xref:reference:boost/msm/backmp11/state_machine/process_event_pool.adoc[`size_t process_even_pool(size_t max_events = SIZE_MAX)`].
The method returns the number of events it successfully processed and removed from the event pool.
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:

You can configure a custom event pool container. It has to support push at both ends as well as removal from the front without iterator invalidation, specifically the following API calls:

- `push_back(const T&)`
- `push_front(const T&)`
- `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.
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>;`.


Expand Down Expand Up @@ -290,7 +293,8 @@ Calling `start(...)` on an active or `stop(...)` on an inactive state machine ha

Use xref:reference:boost/msm/backmp11/state_machine/process_event.adoc[`process_result state_machine::process_event(const Event&)`] to start processing an event while the state machine is idle.

You can enqueue an event for subsequent processing in an action with xref:reference:boost/msm/backmp11/state_machine/enqueue_event.adoc[`void state_machine::enqueue_event(const Event&)`]. The enqueued event will be processed immediately after the current event has finished processing.
You can enqueue an event to the event pool for subsequent processing in an action with xref:reference:boost/msm/backmp11/state_machine/enqueue_event.adoc[`void state_machine::enqueue_event(const Event&)`]. The enqueued event will be processed immediately after the current event has finished processing.
If an event is enqueued while the state machine is idle, you can explicitly request the state machine to process events from its event pool with xref:reference:boost/msm/backmp11/state_machine/process_event_pool.adoc[`size_t process_event_pool(size_t max_events = SIZE_MAX)`].

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.

Expand Down
7 changes: 7 additions & 0 deletions doc/modules/ROOT/pages/backmp11-back-end/examples.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
= Examples

== xref:attachment$backmp11/HeaplessStateMachine.cpp[Heapless state machine]

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.


== xref:attachment$backmp11/Logger.cpp[Logger]

This example contains an observer implementation for logging state machine activities.
Expand Down
24 changes: 9 additions & 15 deletions include/boost/msm/backmp11/detail/event_pool_processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,21 @@ class event_occurrence
// were not given and the event has not been dispatched.
std::optional<process_result> try_process(void* processor, uint16_t seq_cnt)
{
return m_process_fn(*this, processor, seq_cnt);
}

void mark_for_deletion()
{
m_marked_for_deletion = true;
const auto result = m_process_fn(*this, processor, seq_cnt);
if (result)
{
m_process_fn = nullptr;
}
return result;
}

bool marked_for_deletion() const
bool is_processed() const
{
return m_marked_for_deletion;
return m_process_fn == nullptr;
}

private:
process_fn_t m_process_fn{};
// Flag set when this event has been processed and can be erased.
// Deletion is deferred to allow the use of std::deque,
// which provides better cache locality and lower per-element overhead.
bool m_marked_for_deletion{};
};

template <typename Event>
Expand Down Expand Up @@ -93,7 +89,6 @@ class deferred_event : public event_occurrence
{
return std::nullopt;
}
mark_for_deletion();
return sm.process_event_observed(m_event, process_info::event_pool);
}

Expand Down Expand Up @@ -138,8 +133,7 @@ class event_pool_processor
while (it != m_event_pool.events.end())
{
event_occurrence& event = **it;
// The event was already processed.
if (event.marked_for_deletion())
if (event.is_processed())
{
it = m_event_pool.events.erase(it);
continue;
Expand Down
1 change: 0 additions & 1 deletion include/boost/msm/backmp11/state_machine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,6 @@ class state_machine
private:
std::optional<process_result> try_process_impl(derived_t& sm)
{
mark_for_deletion();
return sm.template process_completion_transition<
completion_transition>(m_region_id);
}
Expand Down
99 changes: 99 additions & 0 deletions test/Backmp11Heapless.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2025 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_NONSTANDALONE_TEST
#define BOOST_TEST_MODULE test_heapless_state_machine
#endif
#include <boost/test/unit_test.hpp>

// back-end
#include "BackCommon.hpp"
//front-end
#include "FrontCommon.hpp"

#include "Utils.hpp"
#include "attachments/backmp11/HeaplessStateMachine.cpp"

namespace msm = boost::msm;

using namespace msm::front;
using namespace msm::backmp11;

static bool g_heap_forbidden = false;

void* operator new(std::size_t size)
{
if (g_heap_forbidden)
throw std::bad_alloc{};
void* p = std::malloc(size);
if (!p)
throw std::bad_alloc{};
return p;
}

namespace
{

struct CountEvent
{
template <typename Fsm>
void operator()(const Greet&, Fsm& fsm)
{
fsm.events++;
}
};

// State machine.
struct TestMachine_ : front::state_machine_def<MyStateMachine_>
{
template <typename Fsm>
void on_entry(const back::starting&, Fsm& fsm)
{
fsm.enqueue_event(Greet{});
fsm.enqueue_event(Greet{});
fsm.enqueue_event(Greet{});
}

using initial_state = MyState;
using transition_table = mp11::mp_list<
Row<MyState, Greet, none, CountEvent>
>;

size_t events{};
};

using TestMachine = state_machine<TestMachine_, MyConfig>;

BOOST_AUTO_TEST_CASE(heapless_state_machine)
{
using polymorphic_t = msm::backmp11::detail::basic_polymorphic<
msm::backmp11::detail::event_occurrence>;

TestMachine* sm_ptr{nullptr};
using deferred_t = msm::backmp11::detail::deferred_event<Greet>;
static_assert(std::is_nothrow_move_constructible_v<deferred_t>);
const auto deferred_event =
deferred_t{*sm_ptr, Greet{}, 0};
const auto event_occurrence = polymorphic_t::make(deferred_event);
BOOST_REQUIRE_MESSAGE(event_occurrence.is_inline(),
"Event must fit in inline storage");

g_heap_forbidden = true;

TestMachine sm;

sm.start();
ASSERT_AND_RESET(sm.events, 3);

g_heap_forbidden = false;
}

} // namespace
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ add_executable(boost_msm_cxx17_tests
Backmp11EntryExit.cpp
Backmp11Exceptions.cpp
Backmp11FunctorApi.cpp
Backmp11Heapless.cpp
Backmp11History.cpp
Backmp11ManyDeferTransitions.cpp
Backmp11Observer.cpp
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ test-suite msm-unit-tests-cxxstd17
[ run Backmp11EntryExit.cpp ]
[ run Backmp11Exceptions.cpp ]
[ run Backmp11FunctorApi.cpp ]
[ run Backmp11Heapless.cpp ]
[ run Backmp11History.cpp ]
[ run Backmp11ManyDeferTransitions.cpp ]
[ run Backmp11Observer.cpp ]
Expand Down
Loading