feat(backmp11): reflection API and serialization support

This commit is contained in:
Christian Granzin
2026-05-03 08:54:06 -04:00
committed by Christian Granzin
parent 87c1f9fb83
commit 8c2495b091
18 changed files with 1194 additions and 190 deletions
+16
View File
@@ -166,6 +166,11 @@ jobs:
run: |
echo "using ${{matrix.toolset}} : : ${{matrix.compiler}} ;" > ~/user-config.jam
- name: Download nlohmann/json
run: |
cd ../boost-root/libs/$LIBRARY
python3 scripts/download_nlohmann_json.py
- name: Run tests
run: |
cd ../boost-root
@@ -207,6 +212,11 @@ jobs:
git submodule update --init tools/boostdep
python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY
- name: Download nlohmann/json
run: |
cd ../boost-root/libs/$LIBRARY
python3 scripts/download_nlohmann_json.py
- name: Configure
run: |
cd ../boost-root
@@ -272,6 +282,12 @@ jobs:
cmd /c bootstrap
b2 -d0 headers
- name: Download nlohmann/json
shell: cmd
run: |
cd ../boost-root/libs/%LIBRARY%
python3 scripts/download_nlohmann_json.py
- name: Run tests
shell: cmd
run: |
@@ -15,9 +15,9 @@ It offers a significant improvement in runtime and memory usage, as can be seen
| back | 14 | 815 | 68 | 2.8
| back_favor_compile_time | 17 | 775 | 226 | 3.5
| back11 | 37 | 2682 | 84 | 2.8
| backmp11 | 3 | 209 | 28 | 0.7
| backmp11_favor_compile_time | 3 | 195 | 43 | 6.0
| sml | 5 | 234 | 57 | 0.3
| backmp11 | 3 | 209 | 29 | 0.7
| backmp11_favor_compile_time | 3 | 195 | 41 | 6.2
| sml | 5 | 235 | 57 | 0.3
|=======================================================================================================
@@ -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 | 49 | 2165 | 230 | 13.2
| back_favor_compile_time | 55 | 1704 | 911 | > 300
| backmp11 | 8 | 348 | 79 | 3.3
| backmp11_favor_compile_time | 5 | 261 | 97 | 20.6
| backmp11_favor_compile_time_multi_cu | 4 | ~863 | 97 | 21.4
| backmp11 | 8 | 351 | 82 | 3.4
| backmp11_favor_compile_time | 5 | 263 | 89 | 20.4
| backmp11_favor_compile_time_multi_cu | 4 | ~863 | 89 | 20.2
| sml | 18 | 543 | 422 | 5.4
|================================================================================================================
@@ -301,23 +301,76 @@ If the `fsm_parameter` is set to `root_sm`, then also the `root_sm` must be set.
====
=== Generic support for serializers
=== Reflection API and serialization support
The `state_machine` allows access to its private members for serialization purposes with a friend:
The `state_machine` provides access to all its members recursively with a `reflect` free function and a visitor pattern:
```cpp
// Class boost::msm::backmp11::state_machine
template<typename T, typename A0, typename A1, typename A2>
friend void serialize(T&, state_machine<A0, A1, A2>&);
```
// namespace boost::msm::backmp11
A similar friend declaration is available in the `history_impl` classes.
// Reflect on a state_machine's members with a visitor.
// The visitor has to implement the methods:
// - visit_front_end(auto&& front_end)
// - visit_front_end(auto&& front_end, auto&& reflect)
// - visit_member(const char* key, auto&& member)
// - visit_state(size_t state_id, auto&& state)
// - visit_state(size_t state_id, auto&& state, auto&& reflect)
template <typename FrontEnd, typename Config, typename Derived,
typename Visitor>
void reflect(detail::state_machine_base<FrontEnd, Config, Derived>& sm,
Visitor&& visitor);
```
[IMPORTANT]
====
This design allows you to provide any serializer implementation, but due to the need to access private members there is no guarantee that your implementation breaks in a new version of the back-end.
Do not rely on assumptions about call orders and argument details of the visit methods.
The reflection API exposes internal members of the state machine, which are subject to changes.
====
The visit methods of the front-end and states contain two overloads. The first one gets called in case the object to be visited does not have reflection, the second one provides a `reflect` functor argument that triggers the reflection deeper into the object. You can set up reflection for a front-end or state by implementing a `reflect` member function or alternatively a free function (with MSVC you have to use a member function due to ADL limitations).
```cpp
struct MyState : boost::msm::front::state<>
{
// Reflect with a member function.
template <typename Visitor>
void reflect(Visitor&& visitor)
{
visitor.visit_member("my_member", my_member);
}
template <typename Visitor>
void reflect(Visitor&& visitor) const
{
visitor.visit_member("my_member", my_member);
}
uint32_t my_member{};
};
// Or reflect with a free function.
template <typename Visitor>
void reflect(MyState& my_state, Visitor&& visitor)
{
visitor.visit_member("my_member", my_state.my_member);
}
template <typename Visitor>
void reflect(const MyState& my_state, Visitor&& visitor)
{
visitor.visit_member("my_member", my_state.my_member);
}
```
You can use the reflection API for introspection use cases, the most prominent one being serialization of a state machine. `backmp11` supports 3 serialization libraries out-of-the-box:
- Boost.Serialization
- Boost.JSON
- nlohmann/json
For each serialization library you can find a corresponding header with serializer code under `boost/msm/backmp11/serialization`. The serializer expects all objects with non-static members to be serializable, which can be achieved by implementing either reflection or library-specific serialization methods. It is recommended to implement `backmp11`'s reflection API, because this mechanism is generic and supports all serialization libraries.
For serialization with Boost.JSON and nlohmann/json you only need to include the corresponding header, for Boost.Serialization you additionally need to provide a `serialize` method for the (root) state machine. The https://github.com/boostorg/msm/blob/develop/test/Backmp11Adapter.hpp[backmp11 serialization test] demonstrates how to use the serialization libraries.
=== Unified event pool for queued and deferred events
The containers for queued and deferred events have been merged into a single event pool.
@@ -389,11 +442,6 @@ In `back` the event is evaluted by all regions independently. This leads to the
C{plus}{plus}11 brings the strongly needed variadic template support for MSM, but later C{plus}{plus} versions provide other important features - for example C{plus}{plus}17's `if constexpr (...)`.
=== The signature of the state machine is changed
Please refer to the simplified state machine signature above for more information.
=== The history policy of a state machine is defined in the front-end instead of the back-end
The definition of the history policy is closer related to the front-end, and defining it there ensures that state machine configs can be shared between back-ends.
@@ -471,6 +519,13 @@ class state_machine_adapter
this->get_event_pool().events.clear();
}
template <class Archive>
void serialize(Archive& ar,
const unsigned int /*version*/)
{
backmp11::reflect(*this, serializer<Archive>{ar});
}
// No adapter.
// Superseded by the visitor API.
// void visit_current_states(...) {...}
@@ -488,57 +543,51 @@ class state_machine_adapter
A working code example of such an adapter is available in https://github.com/boostorg/msm/blob/develop/test/Backmp11Adapter.hpp[the tests].
It can be copied and adapted if needed, though this class is internal to the tests and not planned to be supported officially.
Further details about the applied API changes:
==== The dependency to `boost::serialization` is removed
The back-end aims to support serialization in general, but without providing a concrete implementation for a specific serialization library.
If you want to use `boost::serialization` for your state machine, you can look into the https://github.com/boostorg/msm/blob/develop/test/Backmp11Adapter.hpp[state machine adapter] from the tests for an example how to set it up.
==== The back-end's constructor does not allow initialization of states and `set_states` is removed
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 later being rather unusual, when doing both at once the syntax becomes too difficult to understand; even more so if states within hierarchical sub state machines were initialized in this fashion.
In order to keep the API of the constructor simpler and less ambiguous, it only supports forwarding arguments to the front-end and no more.
Also the `set_states` API is removed. If setting a state is required, this can still be done (in a little more verbose, but also more direct & explicit fashion) by getting a reference to the desired state via `get_state` and then assigning the desired new state to it.
==== The method `get_state_by_id` is removed
If you really need to get a state by id, please use the universal visitor API to implement the function on your own.
The `backmp11` `state_machine` has a new method to support getting the id of a state in the visitor:
```cpp
template<typename State>
static constexpr int state_machine::get_state_id(const State&);
```
==== The pointer overload of `get_state` is removed
Similarly to the STL's `std::get` of a tuple, the only sensible template parameter for `get_state` is `T` returning a `T&`.
The overload for a `T*` is removed and the `T&` is discouraged, although still supported.
If you need to get a state by its address, use the address operator after you have received the state by reference.
=== `boost::any` as Kleene event is replaced by `std::any`
To reduce the amount of necessary header inclusions `backmp11` uses `std::any` for defining Kleene events instead of `boost::any`.
You can still opt in to use `boost::any` by explicitly including `boost/msm/event_traits.h`.
=== Removed features
=== The eUML front-end support is removed
==== 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 later being rather unusual, when doing both at once the syntax becomes too difficult to understand; even more so if states within hierarchical sub state machines were initialized in this fashion.
In order to keep the API of the constructor simpler and less ambiguous, it only supports forwarding arguments to the front-end.
Also the `set_states` API is removed. If setting a state is required, this can still be done (in a little more verbose, but also more direct & explicit fashion) by getting a reference to the desired state via `get_state` and then assigning a new state object to it.
==== The `get_state_by_id` method
If you really need to get a state by id, please use the universal visitor API to implement the function on your own.
The `backmp11` `state_machine` has a new method to support getting the id of a state in the visitor:
```cpp
template<typename State>
static constexpr uint16_t state_machine::get_state_id(const State&);
```
==== The pointer overload of `get_state`
Similarly to the STL's `std::get` of a tuple, the only sensible template parameter for `get_state` is `T` returning a `T&`.
The overload for a `T*` is removed and the `T&` is discouraged, although still supported.
If you need to get a state by its address, use the address operator after you have received the state by reference.
==== The eUML front-end support
The support for EUML induces longer compilation times by the need to include the Boost proto headers and applying C{plus}{plus}03 variadic template emulation. If you want to use a UML-like syntax, please try out the new PUML front-end.
=== The fsm check and find region support is removed
==== The fsm check and find region support
The implementation of these two features depends on mpl_graph, which induces high compilation times.
=== `sm_ptr` support is removed
==== `sm_ptr` support
Not needed with the functor front-end and was already deprecated, thus removed in `backmp11`.
@@ -550,7 +599,7 @@ Like `back`, this back-end supports 2 compile policies. In case of hierarchical
=== `favor_runtime_speed`
This policy favors runtime speed over compile time, it evaluates all transitions and generates the dispatch table at compile time.
The dispatch strategy can be tuned by inheriting from `favor_runtime_speed` and adapting the using directive:
The dispatch strategy can be tuned by inheriting from `favor_runtime_speed` and adapting the `using dispatch_strategy` directive:
```cpp
struct favor_runtime_speed
@@ -585,7 +634,7 @@ Usually results in worse runtime performance and larger executable size, but sli
This policy favors compile time over runtime speed. It evaluates transitions lazily and generates the dispatch table at runtime. Like its counterpart in `back`, it does not support Kleene events.
Events are wrapped into a `std::any` when they enter event processing to reduce the number of necessary templates instances required to generate the state machine.
Events are wrapped into a `std::any` when they enter event processing to reduce the number of necessary template instances required to generate the state machine.
The policy utilizes a hash map for dispatch, with the type index of each event as the key and an array of function pointers to the matching transitions as the value.
This policy allows compiling a state machine across multiple translation units (TUs) with the help of a preprocessor macro.
@@ -2,6 +2,11 @@
= Version history
== Boost 1.92
* 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])
== Boost 1.91
* New features (`backmp11`):
@@ -54,10 +54,15 @@ class history_impl<front::no_history, InitialStateIds>
{
}
private:
// Allow access to private members for serialization.
template<typename T, typename U>
friend void serialize(T&, history_impl<front::no_history, U>&);
template <typename F>
void reflect(F&&)
{
}
template <typename F>
void reflect(F&&) const
{
}
};
template <typename InitialStateIds>
@@ -85,17 +90,26 @@ public:
m_last_active_state_ids = sm.m_active_state_ids;
}
private:
// Allow access to private members for serialization.
template<typename T, typename U>
friend void serialize(T&, history_impl<front::always_shallow_history, U>&);
template <typename F>
void reflect(F&& f)
{
f.visit_member("last_active_state_ids", m_last_active_state_ids);
}
template <typename F>
void reflect(F&& f) const
{
f.visit_member("last_active_state_ids", m_last_active_state_ids);
}
protected:
std::array<uint16_t, mp11::mp_size<InitialStateIds>::value>
m_last_active_state_ids{value_array<InitialStateIds>};
};
template <typename... Events, typename InitialStateIds>
class history_impl<front::shallow_history<Events...>, InitialStateIds>
: public history_impl<front::always_shallow_history, InitialStateIds>
{
using events = mp11::mp_list<Events...>;
@@ -105,7 +119,7 @@ public:
{
if constexpr (mp11::mp_contains<events, Event>::value)
{
sm.m_active_state_ids = m_last_active_state_ids;
sm.m_active_state_ids = this->m_last_active_state_ids;
}
else
{
@@ -125,20 +139,6 @@ public:
// ... then execute each state entry.
sm.template visit<visit_mode::active_non_recursive>(visitor);
}
template <typename StateMachine>
void on_exit(StateMachine& sm)
{
m_last_active_state_ids = sm.m_active_state_ids;
}
private:
// Allow access to private members for serialization.
template<typename T, typename... Es, typename U>
friend void serialize(T&, history_impl<front::shallow_history<Es...>, U>&);
std::array<uint16_t, mp11::mp_size<InitialStateIds>::value>
m_last_active_state_ids{value_array<InitialStateIds>};
};
} // boost::msm::backmp11
@@ -72,6 +72,17 @@ class non_propagating
T m_value;
};
// Wrapper to invoke a reflect free function
// (required for ADL).
struct invoke_reflect_free
{
template <typename State, typename Visitor>
void operator()(State&& state, Visitor&& visitor)
{
reflect(std::forward<State>(state), std::forward<Visitor>(visitor));
}
};
template <class FrontEnd, class Config, class Derived>
class state_machine_base : public FrontEnd
{
@@ -262,12 +273,10 @@ class state_machine_base : public FrontEnd
template <typename Event>
friend class deferred_event;
// Allow access to private members for serialization.
// WARNING:
// No guarantee is given on the private member layout.
// Future changes may break existing serializer implementations.
template<typename T, typename A0, typename A1, typename A2>
friend void serialize(T&, state_machine_base<A0, A1, A2>&);
template<typename T0, typename T1, typename T2, typename F>
friend void reflect(state_machine_base<T0, T1, T2>&, F&&);
template<typename T0, typename T1, typename T2, typename F>
friend void reflect(const state_machine_base<T0, T1, T2>&, F&&);
template <typename T>
using get_initial_event = typename T::initial_event;
@@ -773,7 +782,7 @@ class state_machine_base : public FrontEnd
Event>;
process_result result = process_result::HANDLED_FALSE;
// Dispatch the event to every region.
for (size_t region_id = 0; region_id < nr_regions; region_id++)
for (uint8_t region_id = 0; region_id < nr_regions; region_id++)
{
result |= dispatch_table::dispatch(self(), region_id, event);
}
@@ -1110,6 +1119,87 @@ class state_machine_base : public FrontEnd
return *static_cast<const derived_t*>(this);
}
template <typename State, typename F, typename = void>
struct has_reflect_member : std::false_type {};
template <typename State, typename Visitor>
struct has_reflect_member<State, Visitor,
std::void_t<decltype(std::declval<State&>().reflect(std::declval<Visitor&&>()))>>
: std::true_type {};
template <typename State, typename Visitor, typename = void>
struct has_reflect_free : std::false_type {};
template <typename State, typename Visitor>
struct has_reflect_free<State, Visitor,
std::void_t<decltype(reflect(std::declval<State&>(), std::declval<Visitor&&>()))>>
: std::true_type {};
template <typename Self, typename Visitor>
static void reflect_impl(Self& self, Visitor&& visitor)
{
using FrontEndRef = mp11::mp_if_c<
std::is_const_v<Self>,
const FrontEnd&,
FrontEnd&>;
auto& front_end = static_cast<FrontEndRef>(self);
if constexpr (has_reflect_member<FrontEnd, Visitor>::value)
{
visitor.visit_front_end(front_end, [&front_end, &visitor]() {
front_end.reflect(std::forward<Visitor>(visitor));
});
}
else if constexpr (has_reflect_free<FrontEnd, Visitor>::value)
{
visitor.visit_front_end(front_end, [&front_end, &visitor]() {
invoke_reflect_free{}(front_end, std::forward<Visitor>(visitor));
});
}
else
{
visitor.visit_front_end(front_end);
}
visitor.visit_member("active_state_ids", self.m_active_state_ids);
// event pool and context cannot be serialized.
self.m_history.reflect(std::forward<Visitor>(visitor));
visitor.visit_member("event_processing", self.m_event_processing);
mp11::tuple_for_each(self.m_states,
[&visitor](auto& state)
{
using State = std::decay_t<decltype(state)>;
if constexpr (has_reflect_member<State, Visitor>::value ||
has_state_machine_tag<State>::value)
{
visitor.visit_state(get_state_id<State>(), state, [&state, &visitor]() {
state.reflect(std::forward<Visitor>(visitor));
});
}
else if constexpr (has_reflect_free<State, Visitor>::value)
{
visitor.visit_state(get_state_id<State>(), state, [&state, &visitor]() {
invoke_reflect_free{}(state, std::forward<Visitor>(visitor));
});
}
else
{
visitor.visit_state(get_state_id<State>(), state);
}
});
visitor.visit_member("running", self.m_running);
}
template <typename Visitor>
void reflect(Visitor&& visitor)
{
reflect_impl(*this, std::forward<Visitor>(visitor));
}
template <typename Visitor>
void reflect(Visitor&& visitor) const
{
reflect_impl(*this, std::forward<Visitor>(visitor));
}
struct optional_members :
event_pool_member,
context_member
@@ -1135,6 +1225,31 @@ class state_machine_base : public FrontEnd
bool m_running{false};
};
std::false_type is_state_machine(...);
template <typename A0, typename A1, typename A2>
std::true_type is_state_machine(state_machine_base<A0, A1, A2>*);
template <typename T>
constexpr bool is_state_machine_v =
decltype(is_state_machine(std::declval<T*>()))::value;
template <typename FrontEnd, typename Config, typename Derived,
typename Visitor>
void reflect(state_machine_base<FrontEnd, Config, Derived>& sm,
Visitor&& visitor)
{
sm.reflect(std::forward<Visitor>(visitor));
}
template <typename FrontEnd, typename Config, typename Derived,
typename Visitor>
void reflect(const state_machine_base<FrontEnd, Config, Derived>& sm,
Visitor&& visitor)
{
sm.reflect(std::forward<Visitor>(visitor));
}
} // boost::msm::backmp11::detail
#endif // BOOST_MSM_BACKMP11_DETAIL_STATE_MACHINE_BASE_HPP
@@ -248,24 +248,36 @@ struct transition_table_impl
// guard rejected the event, we stay in the current one
return process_result::HANDLED_GUARD_REJECT;
}
state_id = active_state_switching::after_guard(current_state_id,
next_state_id);
if constexpr (std::is_same_v<active_state_switching,
active_state_switch_before_transition>)
{
state_id = next_state_id;
}
// first call the exit method of the current state
source.on_exit(event, sm.get_fsm_argument());
state_id = active_state_switching::after_exit(current_state_id,
next_state_id);
if constexpr (std::is_same_v<active_state_switching,
active_state_switch_after_exit>)
{
state_id = next_state_id;
}
// then call the action method
process_result res =
call_action_or_true<Row, HasAction>(sm, event, source, target);
state_id = active_state_switching::after_action(current_state_id,
next_state_id);
if constexpr (std::is_same_v<active_state_switching,
active_state_switch_after_transition_action>)
{
state_id = next_state_id;
}
// and finally the entry method of the new state
call_entry<Row>(sm, event, target);
state_id = active_state_switching::after_entry(current_state_id,
next_state_id);
if constexpr (std::is_same_v<active_state_switching,
active_state_switch_after_entry>)
{
state_id = next_state_id;
}
// Give a chance to handle completion transitions.
sm.template on_state_entry_completed<next_state_type>(region_id);
@@ -0,0 +1,206 @@
// Copyright 2026 Christian Granzin
// Copyright 2008 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_BACKMP11_SERIALIZATION_BOOST_JSON_HPP
#define BOOST_MSM_BACKMP11_SERIALIZATION_BOOST_JSON_HPP
#include <stack>
#include <boost/json.hpp>
#include <boost/msm/backmp11/detail/state_machine_base.hpp>
namespace boost::msm::backmp11::serialization
{
// Serializer for Boost.JSON.
class boost_json_serializer
{
using json = boost::json::object;
public:
boost_json_serializer(boost::json::value& json)
: m_json({&json.emplace_object()})
{
}
template <typename FrontEnd>
void visit_front_end(FrontEnd&& front_end)
{
if constexpr (!std::is_empty_v<std::decay_t<FrontEnd>>)
{
top()["front_end"] = boost::json::value_from(front_end);
}
}
template <typename FrontEnd, typename Reflect>
void visit_front_end(FrontEnd&& /*front_end*/, Reflect&& reflect)
{
auto& json_front_end = top()["front_end"].emplace_object();
m_json.push(&json_front_end);
reflect();
m_json.pop();
}
template <typename Member>
void visit_member(const char* key, Member&& member)
{
top()[key] = member;
}
template <typename T, std::size_t N>
void visit_member(const char* key, const std::array<T, N>& member)
{
boost::json::array& arr = top()[key].emplace_array();
for (const auto& elem : member)
{
arr.push_back(elem);
}
}
template <typename State>
void visit_state(size_t state_id, State&& state)
{
if constexpr (!std::is_empty_v<std::decay_t<State>>)
{
if (!top().contains("states"))
{
top()["states"].emplace_object();
}
top()["states"].as_object()[std::to_string(state_id)] =
boost::json::value_from(state);
}
}
template <typename State, typename Reflect>
void visit_state(size_t state_id, State&& /*state*/, Reflect&& reflect)
{
if (!top().contains("states"))
{
top()["states"].emplace_object();
}
auto& json_state = top()["states"]
.as_object()[std::to_string(state_id)]
.emplace_object();
m_json.push(&json_state);
reflect();
m_json.pop();
}
private:
json& top()
{
return *m_json.top();
}
std::stack<json*> m_json;
};
// Deserializer for Boost.JSON.
class boost_json_deserializer
{
using json = boost::json::object;
public:
boost_json_deserializer(const boost::json::value& json)
: m_json({&json.as_object()})
{
}
template <typename FrontEnd>
void visit_front_end(FrontEnd&& front_end)
{
if constexpr (!std::is_empty_v<std::decay_t<FrontEnd>>)
{
front_end = boost::json::value_to<std::decay_t<FrontEnd>>(
top().at("front_end"));
}
}
template <typename FrontEnd, typename Reflect>
void visit_front_end(FrontEnd&& /*front_end*/, Reflect&& reflect)
{
auto& json_state = top().at("front_end").as_object();
m_json.push(&json_state);
reflect();
m_json.pop();
}
template <typename Member>
void visit_member(const char* key, Member&& member)
{
member = boost::json::value_to<std::decay_t<Member>>(top().at(key));
}
template <typename T, std::size_t N>
void visit_member(const char* key, std::array<T, N>& member)
{
const auto& arr = top().at(key).as_array();
for (std::size_t i = 0; i < N; ++i)
member[i] = boost::json::value_to<T>(arr[i]);
}
template <typename State>
void visit_state(size_t state_id, State&& state)
{
if constexpr (!std::is_empty_v<std::decay_t<State>>)
{
state = boost::json::value_to<std::decay_t<State>>(
top().at("states").at(std::to_string(state_id)));
}
}
template <typename State, typename Reflect>
void visit_state(size_t state_id, State& /*state*/, Reflect&& reflect)
{
auto& json_state =
top().at("states").at(std::to_string(state_id)).as_object();
m_json.push(&json_state);
reflect();
m_json.pop();
}
private:
const json& top()
{
return *m_json.top();
}
std::stack<const json*> m_json;
};
} // namespace boost::msm::backmp11::serialization
namespace boost::msm::backmp11::detail
{
template <typename StateMachine,
typename = std::enable_if_t<is_state_machine_v<StateMachine>>>
void tag_invoke(const boost::json::value_from_tag&, boost::json::value& json,
const StateMachine& sm)
{
detail::reflect(sm, serialization::boost_json_serializer{json});
}
template <typename StateMachine,
typename = std::enable_if_t<is_state_machine_v<StateMachine>>>
StateMachine tag_invoke(const boost::json::value_to_tag<StateMachine>&,
const boost::json::value& json)
{
StateMachine state_machine;
detail::reflect(state_machine,
serialization::boost_json_deserializer{json});
return state_machine;
}
} // namespace boost::msm::backmp11::detail
#endif // BOOST_MSM_BACKMP11_SERIALIZATION_BOOST_JSON_HPP
@@ -0,0 +1,71 @@
// Copyright 2026 Christian Granzin
// Copyright 2008 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_BACKMP11_SERIALIZATION_BOOST_SERIALIZATION_HPP
#define BOOST_MSM_BACKMP11_SERIALIZATION_BOOST_SERIALIZATION_HPP
#include <boost/serialization/array.hpp>
#include <boost/msm/backmp11/detail/state_machine_base.hpp>
namespace boost::msm::backmp11::serialization
{
// Serializer for Boost.Serialization.
template <typename Archive>
class boost_serialization_serializer
{
public:
boost_serialization_serializer(Archive& archive) : m_archive(archive) {}
template <typename FrontEnd>
void visit_front_end(FrontEnd&& front_end)
{
if constexpr (!std::is_empty_v<std::decay_t<FrontEnd>>)
{
m_archive & front_end;
}
}
template <typename FrontEnd, typename Reflect>
void visit_front_end(FrontEnd&& /*front_end*/, Reflect&& reflect)
{
reflect();
}
template <typename Member>
void visit_member(const char* /*key*/, Member&& member)
{
m_archive & member;
}
template <typename State>
void visit_state(size_t /*state_id*/, State&& state)
{
if constexpr (!std::is_empty_v<std::decay_t<State>>)
{
m_archive & state;
}
}
template <typename State, typename Reflect>
void visit_state(size_t /*state_id*/, State&& /*state*/, Reflect&& reflect)
{
reflect();
}
private:
Archive& m_archive;
};
} // namespace boost::msm::backmp11::serialization
#endif // BOOST_MSM_BACKMP11_SERIALIZATION_BOOST_SERIALIZATION_HPP
@@ -0,0 +1,167 @@
// Copyright 2026 Christian Granzin
// Copyright 2008 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_BACKMP11_SERIALIZATION_NLOHMANN_JSON_HPP
#define BOOST_MSM_BACKMP11_SERIALIZATION_NLOHMANN_JSON_HPP
#include <stack>
#include <nlohmann/json.hpp>
#include <boost/msm/backmp11/detail/state_machine_base.hpp>
namespace boost::msm::backmp11::serialization
{
// Serializer for nlohmann json.
class nlohmann_json_serializer
{
using json = nlohmann::json;
public:
nlohmann_json_serializer(json& json) : m_json({&json}) {}
template <typename FrontEnd>
void visit_front_end(FrontEnd&& front_end)
{
if constexpr (!std::is_empty_v<std::decay_t<FrontEnd>>)
{
top()["front_end"] = front_end;
}
}
template <typename FrontEnd, typename Reflect>
void visit_front_end(FrontEnd&& /*front_end*/, Reflect&& reflect)
{
auto& json_front_end = top()["front_end"];
m_json.push(&json_front_end);
reflect();
m_json.pop();
}
template <typename Member>
void visit_member(const char* key, Member&& member)
{
top()[key] = member;
}
template <typename State>
void visit_state(size_t state_id, State&& state)
{
if constexpr (!std::is_empty_v<std::decay_t<State>>)
{
top()["states"][std::to_string(state_id)] = state;
}
}
template <typename State, typename Reflect>
void visit_state(size_t state_id, State&& /*state*/, Reflect&& reflect)
{
auto& json_state = top()["states"][std::to_string(state_id)];
m_json.push(&json_state);
reflect();
m_json.pop();
}
private:
json& top()
{
return *m_json.top();
}
std::stack<json*> m_json;
};
// Deserializer for nlohmann json.
class nlohmann_json_deserializer
{
using json = nlohmann::json;
public:
nlohmann_json_deserializer(const json& json) : m_json({&json}) {}
template <typename FrontEnd>
void visit_front_end(FrontEnd&& front_end)
{
if constexpr (!std::is_empty_v<std::decay_t<FrontEnd>>)
{
front_end = top()["front_end"];
}
}
template <typename FrontEnd, typename Reflect>
void visit_front_end(FrontEnd&& /*front_end*/, Reflect&& reflect)
{
auto& json_state = top().at("front_end");
m_json.push(&json_state);
reflect();
m_json.pop();
}
template <typename Member>
void visit_member(const char* key, Member&& member)
{
member = top().at(key);
}
template <typename State>
void visit_state(size_t state_id, State&& state)
{
if constexpr (!std::is_empty_v<std::decay_t<State>>)
{
state = top()["states"][std::to_string(state_id)];
}
}
template <typename State, typename Reflect>
void visit_state(size_t state_id, State& /*state*/, Reflect&& reflect)
{
auto& json_state = top().at("states").at(std::to_string(state_id));
m_json.push(&json_state);
reflect();
m_json.pop();
}
private:
const json& top()
{
return *m_json.top();
}
std::stack<const json*> m_json;
};
} // namespace boost::msm::backmp11::serialization
namespace boost::msm::backmp11::detail
{
template <typename StateMachine,
typename = std::enable_if_t<
backmp11::detail::is_state_machine_v<StateMachine>>>
void to_json(nlohmann::json& json, const StateMachine& state_machine)
{
detail::reflect(state_machine,
serialization::nlohmann_json_serializer{json});
}
template <typename StateMachine,
typename = std::enable_if_t<
backmp11::detail::is_state_machine_v<StateMachine>>>
void from_json(const nlohmann::json& json, StateMachine& state_machine)
{
detail::reflect(state_machine,
serialization::nlohmann_json_deserializer{json});
}
} // namespace boost::msm::backmp11::detail
#endif // BOOST_MSM_BACKMP11_SERIALIZATION_NLOHMANN_JSON_HPP
@@ -56,6 +56,36 @@ class state_machine<FrontEnd>
using Base::Base;
};
// Reflect on a state_machine's members with a visitor.
// The visitor has to implement the methods:
// - visit_front_end(auto&& front_end)
// - visit_front_end(auto&& front_end, auto&& reflect)
// - visit_member(const char* key, auto&& member)
// - visit_state(size_t state_id, auto&& state)
// - visit_state(size_t state_id, auto&& state, auto&& reflect)
template <typename FrontEnd, typename Config, typename Derived,
typename Visitor>
void reflect(detail::state_machine_base<FrontEnd, Config, Derived>& sm,
Visitor&& visitor)
{
detail::reflect(sm, std::forward<Visitor>(visitor));
}
// Reflect on a state_machine's members with a visitor.
// The visitor has to implement the methods:
// - visit_front_end(auto&& front_end)
// - visit_front_end(auto&& front_end, auto&& reflect)
// - visit_member(const char* key, auto&& member)
// - visit_state(size_t state_id, auto&& state)
// - visit_state(size_t state_id, auto&& state, auto&& reflect)
template <typename FrontEnd, typename Config, typename Derived,
typename Visitor>
void reflect(const detail::state_machine_base<FrontEnd, Config, Derived>& sm,
Visitor&& visitor)
{
detail::reflect(sm, std::forward<Visitor>(visitor));
}
} // boost::msm::backmp11
#endif // BOOST_MSM_BACKMP11_STATE_MACHINE_HPP
@@ -26,7 +26,6 @@ namespace boost { namespace msm { namespace front
// default base: non-polymorphic, not visitable
struct default_base_state
{
~default_base_state(){}
};
// default polymorphic base state. Derive all states from it to get polymorphic behavior
struct polymorphic_state
@@ -24,7 +24,7 @@
namespace boost { namespace msm { namespace front {namespace detail
{
template <class Attributes= ::boost::fusion::map<> >
template <class Attributes = ::boost::fusion::map<>>
struct inherit_attributes
{
inherit_attributes():m_attributes(){}
@@ -57,8 +57,11 @@ private:
Attributes m_attributes;
};
template <>
struct inherit_attributes<void> {};
// the interface for all states. Defines entry and exit functions. Overwrite to implement for any state needing it.
template<class USERBASE,class Attributes= ::boost::fusion::map<> >
template <class USERBASE, class Attributes = void>
struct state_base : public inherit_attributes<Attributes>, USERBASE
{
typedef USERBASE user_state_base;
+33
View File
@@ -0,0 +1,33 @@
"""
Copyright 2018-2025 Emil Dotchevski and Reverge Studios, Inc.
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)
This program downloads the nlohmann/json single header distribution.
Usage:
python3 download_nlohmann_json.py
"""
import urllib.request
import os
url = "https://github.com/nlohmann/json/releases/download/v3.11.3/json.hpp"
output_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "test", "nlohmann")
output_file = os.path.join(output_dir, "json.hpp")
def _main():
if os.path.exists(output_file):
print(f"{output_file} already exists, skipping download")
return
os.makedirs(output_dir, exist_ok=True)
print(f"Downloading {url}...")
urllib.request.urlretrieve(url, output_file)
print(f"Saved to {output_file}")
if __name__ == "__main__":
_main()
+63 -90
View File
@@ -13,16 +13,70 @@
#include "boost/msm/backmp11/state_machine_config.hpp"
#include "boost/msm/backmp11/favor_compile_time.hpp"
#include "boost/msm/backmp11/state_machine.hpp"
#include <boost/msm/back/queue_container_deque.hpp>
#include "boost/msm/back/queue_container_deque.hpp"
#define BOOST_PARAMETER_CAN_USE_MP11
#include <boost/parameter.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/array.hpp>
#include "Backmp11.hpp"
namespace boost::msm::backmp11
{
template <typename Archive>
class serializer
{
public:
serializer(Archive& archive) : m_archive(archive) {}
template <typename FrontEnd>
void visit_front_end(FrontEnd&& front_end)
{
if constexpr (has_serialize_member<FrontEnd>::value)
{
m_archive & front_end;
}
}
template <typename FrontEnd, typename Reflect>
void visit_front_end(FrontEnd&& /*front_end*/, Reflect&& reflect)
{
reflect();
}
template <typename Member>
void visit_member(const char* /*key*/, Member&& member)
{
m_archive & member;
}
template <typename State>
void visit_state(size_t /*state_id*/, State&& state)
{
if constexpr (has_serialize_member<State>::value)
{
m_archive & state;
}
}
template <typename State, typename Reflect>
void visit_state(size_t /*state_id*/, State&& /*state*/, Reflect&& reflect)
{
reflect();
}
private:
template <typename State, typename = void>
struct has_serialize_member : std::false_type {};
template <typename State>
struct has_serialize_member<
State,
std::void_t<decltype(std::declval<State&>().serialize(
std::declval<Archive&>(), 0u))>>
: std::true_type {};
Archive& m_archive;
};
using back::queue_container_deque;
template <
@@ -123,6 +177,13 @@ class state_machine_adapter
this->get_event_pool().events.clear();
}
template <class Archive>
void serialize(Archive& ar,
const unsigned int /*version*/)
{
backmp11::reflect(*this, serializer<Archive>{ar});
}
// No adapter.
// Superseded by the visitor API.
// void visit_current_states(...) {...}
@@ -136,92 +197,4 @@ class state_machine_adapter
// auto get_state_by_id(int id) {...}
};
template <class Archive>
struct serialize_state
{
serialize_state(Archive& ar):ar_(ar){}
template<typename T>
typename ::boost::enable_if<
typename ::boost::mpl::or_<
typename has_do_serialize<T>::type,
typename detail::has_state_machine_tag<typename T::internal>::type
>::type
,void
>::type
operator()(T& t) const
{
ar_ & t;
}
template<typename T>
typename ::boost::disable_if<
typename ::boost::mpl::or_<
typename has_do_serialize<T>::type,
typename detail::has_state_machine_tag<typename T::internal>::type
>::type
,void
>::type
operator()(T&) const
{
// no state to serialize
}
Archive& ar_;
};
namespace detail
{
template <class Archive, class FrontEnd, class Config, class Derived>
void serialize(Archive& ar,
state_machine_base<FrontEnd, Config, Derived>& sm)
{
(serialize_state<Archive>(ar))(boost::serialization::base_object<FrontEnd>(sm));
ar & sm.m_active_state_ids;
ar & sm.m_history;
ar & sm.m_event_processing;
mp11::tuple_for_each(sm.m_states, serialize_state<Archive>(ar));
}
template<typename T, typename InitialStateIds>
void serialize(T&, detail::history_impl<front::no_history, InitialStateIds>&)
{
}
template<typename T, typename... Es, typename InitialStateIds>
void serialize(T& ar, detail::history_impl<front::shallow_history<Es...>, InitialStateIds>& history)
{
ar & history.m_last_active_state_ids;
}
}
} // boost::msm::backmp11
namespace boost::serialization
{
template <class Archive, class A0, class A1, class A2, class A3, class A4>
void serialize(Archive& ar,
msm::backmp11::state_machine_adapter<A0, A1, A2, A3, A4>& sm,
const unsigned int /*version*/)
{
msm::backmp11::detail::serialize(ar, sm);
}
template<typename T, typename InitialStateIds>
void serialize(T& ar,
msm::backmp11::detail::history_impl<msm::front::no_history, InitialStateIds>& history,
const unsigned int /*version*/)
{
msm::backmp11::detail::serialize(ar, history);
}
template<typename T, typename... Es, typename InitialStateIds>
void serialize(T& ar,
msm::backmp11::detail::history_impl<msm::front::shallow_history<Es...>, InitialStateIds>& history,
const unsigned int /*version*/)
{
msm::backmp11::detail::serialize(ar, history);
}
} // boost::serialization
+1 -1
View File
@@ -152,7 +152,7 @@ BOOST_AUTO_TEST_CASE(class_with_throwing_move_constructor_test)
{
using polymorphic_t = basic_polymorphic<class_with_throwing_move_constructor>;
{
polymorphic_t obj = polymorphic_t::make<class_with_throwing_move_constructor>(42);
polymorphic_t obj = polymorphic_t::make<class_with_throwing_move_constructor>(static_cast<uint8_t>(42));
BOOST_REQUIRE(!obj.is_inline());
BOOST_REQUIRE(obj->value == 42);
+319
View File
@@ -0,0 +1,319 @@
// Copyright 2026 Christian Granzin
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_MSM_NONSTANDALONE_TEST
#define BOOST_TEST_MODULE backmp11_serialization_test
#endif
#include <boost/test/unit_test.hpp>
// back-end
#include <boost/msm/backmp11/state_machine.hpp>
// front-end
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>
// Boost.Serialization.
// Include headers for a simple text archive format.
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/msm/backmp11/serialization/boost_serialization.hpp>
// Boost.JSON.
#define BOOST_JSON_NO_LIB
#include <boost/json/src.hpp>
#include <boost/msm/backmp11/serialization/boost_json.hpp>
// nlohmann/json.
#include <boost/msm/backmp11/serialization/nlohmann_json.hpp>
using namespace boost::msm;
namespace mp11 = boost::mp11;
namespace
{
// States.
// An empty state (doesn't need reflection).
struct Off : front::state<> {};
// A state with a reflect free function.
struct On : front::state<>
{
template <typename Event, typename Fsm>
void on_entry(const Event&, Fsm&)
{
times_pressed += 1;
}
// ADL with MSVC does not work correctly.
#ifdef BOOST_MSVC
template <typename Visitor>
void reflect(Visitor&& visitor)
{
visitor.visit_member("times_pressed", times_pressed);
}
template <typename Visitor>
void reflect(Visitor&& visitor) const
{
visitor.visit_member("times_pressed", times_pressed);
}
#endif
uint32_t times_pressed{};
};
template <typename Visitor>
void reflect(On& on, Visitor&& visitor)
{
visitor.visit_member("times_pressed", on.times_pressed);
}
template <typename Visitor>
void reflect(const On& on, Visitor&& visitor)
{
visitor.visit_member("times_pressed", on.times_pressed);
}
// Events.
struct TurnOn {};
struct TurnOff {};
struct Dim
{
uint8_t brightness;
};
// State machine front-end with a reflect member function.
struct DimSwitch_ : front::state_machine_def<DimSwitch_>
{
// Actions.
struct SetDimValue
{
void operator()(const Dim& event, DimSwitch_& self)
{
self.brightness = event.brightness;
}
};
using initial_state = Off;
using transition_table = mp11::mp_list<
front::Row<Off, TurnOn , On >,
front::Row<On , TurnOff, Off>
>;
using internal_transition_table = mp11::mp_list<
front::Internal<Dim, SetDimValue>
>;
template <typename Visitor>
void reflect(Visitor&& visitor)
{
visitor.visit_member("brightness", this->brightness);
}
template <typename Visitor>
void reflect(Visitor&& visitor) const
{
visitor.visit_member("brightness", brightness);
}
uint8_t brightness;
};
using DimSwitch = backmp11::state_machine<DimSwitch_>;
} // namespace
// Add a serialize free function to support Boost.Serialization for DimSwitch.
// We can integrate Boost.Serialization with this mechanism or by
// adding a serialize member function to the state machine.
namespace boost::serialization {
template <typename Archive>
void serialize(Archive& archive, DimSwitch& state_machine,
const unsigned int /*version*/)
{
backmp11::reflect(
state_machine,
backmp11::serialization::boost_serialization_serializer<Archive>{
archive});
}
} // boost::serialization
namespace
{
BOOST_AUTO_TEST_CASE(boost_serialization)
{
DimSwitch dim_switch;
// The initial state is Off.
dim_switch.start();
BOOST_REQUIRE(dim_switch.is_state_active<Off>());
// Turn On and set brightness to 75.
dim_switch.process_event(TurnOn{});
BOOST_REQUIRE(dim_switch.is_state_active<On>());
BOOST_REQUIRE(dim_switch.get_state<On>().times_pressed == 1);
dim_switch.process_event(Dim{75});
BOOST_REQUIRE(dim_switch.brightness = 75);
// Serialize the state machine.
std::ostringstream ostream;
boost::archive::text_oarchive oarchive{ostream};
oarchive << dim_switch;
// Deserialize the archive into a new state machine.
std::istringstream istream{ostream.str()};
boost::archive::text_iarchive iarchive{istream};
DimSwitch dim_switch_2;
iarchive >> dim_switch_2;
// We have the same state as before.
BOOST_REQUIRE(dim_switch_2.is_state_active<On>());
BOOST_REQUIRE(dim_switch.get_state<On>().times_pressed == 1);
BOOST_REQUIRE(dim_switch_2.brightness = 75);
}
// Helper for convenience:
// Convert all state ids to a human-readable JSON array
// to understand which states the ids refer to.
boost::json::array state_names_to_boost_json(const DimSwitch& sm)
{
boost::json::array json;
sm.template visit<backmp11::visit_mode::all_states>(
[&json](auto& state)
{
using State = std::decay_t<decltype(state)>;
const auto demangled = boost::core::demangled_name(typeid(State));
const auto short_name = demangled.substr(demangled.rfind(':') + 1);
json.push_back(boost::json::string{short_name});
});
return json;
}
BOOST_AUTO_TEST_CASE(boost_json)
{
DimSwitch dim_switch;
// Convert all state ids to a human-readable JSON array
// to understand which states the ids refer to.
auto state_names_json = state_names_to_boost_json(dim_switch);
BOOST_REQUIRE(boost::json::serialize(state_names_json) ==
R"(["Off","On"])");
// The initial state is Off (state id 0).
dim_switch.start();
BOOST_REQUIRE(dim_switch.is_state_active<Off>());
boost::json::value json = boost::json::value_from(dim_switch);
BOOST_REQUIRE(boost::json::serialize(json) == \
R"({"front_end":{"brightness":0},"active_state_ids":[0],"event_processing":false,"states":{"1":{"times_pressed":0}},"running":true})");
// Turn On (state id 1) and set brightness to 75.
dim_switch.process_event(TurnOn{});
BOOST_REQUIRE(dim_switch.is_state_active<On>());
BOOST_REQUIRE(dim_switch.get_state<On>().times_pressed == 1);
dim_switch.process_event(Dim{75});
BOOST_REQUIRE(dim_switch.brightness = 75);
json = boost::json::value_from(dim_switch);
BOOST_REQUIRE(boost::json::serialize(json) == \
R"({"front_end":{"brightness":75},"active_state_ids":[1],"event_processing":false,"states":{"1":{"times_pressed":1}},"running":true})");
// Deserialize the json into a new state machine.
auto dim_switch_2 = boost::json::value_to<DimSwitch>(json);
// We have the same state as before.
BOOST_REQUIRE(dim_switch_2.is_state_active<On>());
BOOST_REQUIRE(dim_switch.get_state<On>().times_pressed == 1);
BOOST_REQUIRE(dim_switch_2.brightness = 75);
}
// Helper for convenience:
// Convert all state ids to a human-readable JSON array
// to understand which states the ids refer to.
nlohmann::json state_names_to_nlohmann_json(const DimSwitch& sm)
{
nlohmann::json json;
sm.template visit<backmp11::visit_mode::all_states>(
[&sm, &json](auto& state)
{
using State = std::decay_t<decltype(state)>;
const auto demangled = boost::core::demangled_name(typeid(State));
const auto short_name = demangled.substr(demangled.rfind(':') + 1);
json[sm.template get_state_id<State>()] = short_name;
});
return json;
}
BOOST_AUTO_TEST_CASE(nlohmann_json)
{
DimSwitch dim_switch;
auto state_names_json = state_names_to_nlohmann_json(dim_switch);
BOOST_REQUIRE(state_names_json.dump(4) == \
R"([
"Off",
"On"
])");
// The initial state is Off (state id 0).
dim_switch.start();
BOOST_REQUIRE(dim_switch.is_state_active<Off>());
nlohmann::json json = dim_switch;
BOOST_REQUIRE(json.dump(4) == \
R"({
"active_state_ids": [
0
],
"event_processing": false,
"front_end": {
"brightness": 0
},
"running": true,
"states": {
"1": {
"times_pressed": 0
}
}
})");
// Turn On (state id 1) and set brightness to 75.
dim_switch.process_event(TurnOn{});
BOOST_REQUIRE(dim_switch.is_state_active<On>());
BOOST_REQUIRE(dim_switch.get_state<On>().times_pressed == 1);
dim_switch.process_event(Dim{75});
BOOST_REQUIRE(dim_switch.brightness = 75);
json = dim_switch;
BOOST_REQUIRE(json.dump(4) == \
R"({
"active_state_ids": [
1
],
"event_processing": false,
"front_end": {
"brightness": 75
},
"running": true,
"states": {
"1": {
"times_pressed": 1
}
}
})");
// Deserialize the json into a new state machine.
auto dim_switch_2 = json.get<DimSwitch>();
// We have the same state as before.
BOOST_REQUIRE(dim_switch_2.is_state_active<On>());
BOOST_REQUIRE(dim_switch.get_state<On>().times_pressed == 1);
BOOST_REQUIRE(dim_switch_2.brightness = 75);
}
} // namespace
+6 -1
View File
@@ -7,12 +7,13 @@ if(NOT TARGET tests)
endif()
link_libraries(Boost::msm Boost::unit_test_framework)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_compile_definitions("BOOST_MSM_NONSTANDALONE_TEST")
if(BOOST_MSM_TEST_ONLY_BACKMP11)
add_compile_definitions("BOOST_MSM_TEST_ONLY_BACKMP11")
endif()
if(BOOST_MSM_TEST_STRICT)
add_compile_options(-Wall -Wpedantic -Wextra -Werror -Wno-language-extension-token)
add_compile_options(-Wall -Wpedantic -Wextra -Wconversion -Werror -Wno-language-extension-token)
endif()
if(BOOST_MSM_TEST_ENABLE_SANITIZERS)
add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
@@ -87,10 +88,14 @@ add_executable(boost_msm_cxx17_tests
Backmp11History.cpp
Backmp11ManyDeferTransitions.cpp
Backmp11RootSm.cpp
Backmp11Serialization.cpp
Backmp11Transitions.cpp
Backmp11Visitor.cpp
main.cpp
)
if(NOT BOOST_MSM_IS_ROOT)
target_link_libraries(boost_msm_cxx17_tests Boost::json)
endif()
target_compile_features(boost_msm_cxx17_tests PRIVATE cxx_std_17)
if(((DEFINED CMAKE_CXX_STANDARD) AND (CMAKE_CXX_STANDARD GREATER_EQUAL 17)) OR
((NOT DEFINED CMAKE_CXX_STANDARD) AND ("cxx_std_17" IN_LIST CMAKE_CXX_COMPILE_FEATURES)))
+1
View File
@@ -87,6 +87,7 @@ test-suite msm-unit-tests-cxxstd17
[ run Backmp11History.cpp ]
[ run Backmp11ManyDeferTransitions.cpp ]
[ run Backmp11RootSm.cpp ]
[ run Backmp11Serialization.cpp ]
[ run Backmp11Transitions.cpp ]
[ run Backmp11Visitor.cpp ]
: