mirror of
https://github.com/boostorg/msm.git
synced 2026-07-21 13:23:45 +00:00
doc(backmp11): heapless state machine
This commit is contained in:
committed by
Christian Granzin
parent
2ff2b5caea
commit
0720d98501
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>;`.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
const auto result = m_process_fn(*this, processor, seq_cnt);
|
||||
if (result)
|
||||
{
|
||||
m_process_fn = nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void mark_for_deletion()
|
||||
bool is_processed() const
|
||||
{
|
||||
m_marked_for_deletion = true;
|
||||
}
|
||||
|
||||
bool marked_for_deletion() 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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -86,6 +86,7 @@ add_executable(boost_msm_cxx17_tests
|
||||
Backmp11EntryExit.cpp
|
||||
Backmp11Exceptions.cpp
|
||||
Backmp11FunctorApi.cpp
|
||||
Backmp11Heapless.cpp
|
||||
Backmp11History.cpp
|
||||
Backmp11ManyDeferTransitions.cpp
|
||||
Backmp11Observer.cpp
|
||||
|
||||
@@ -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 ]
|
||||
|
||||
Reference in New Issue
Block a user