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
71 changes: 71 additions & 0 deletions doc/modules/ROOT/attachments/backmp11/serializer/BoostJson.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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)

#include "DimSwitch.hpp"

#include <iostream>

#include <boost/core/typeinfo.hpp>

#define BOOST_JSON_NO_LIB
#include <boost/json/src.hpp>
#include <boost/msm/backmp11/serialization/boost_json.hpp>

namespace
{

// Helper for convenience:
// Convert all state ids to a human-readable JSON array
// to understand which states the ids refer to.
std::string state_names_to_boost_json_string(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 boost::json::serialize(json);
}

std::string to_boost_json_string(const DimSwitch& dim_switch)
{
const auto json = boost::json::value_from(dim_switch);
return boost::json::serialize(json);
}

[[maybe_unused]] void boost_json_example()
{
DimSwitch dim_switch;

// Prints:
// ["Off","On"]
std::cout << state_names_to_boost_json_string(dim_switch) << std::endl;

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

// 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}
std::cout << to_boost_json_string(dim_switch_2) << std::endl;
}

} // namespace
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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)

#include <iostream>

#include "DimSwitch.hpp"

// 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>

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

} // namespace boost::serialization

namespace
{

[[maybe_unused]] void boost_serialization_example()
{
DimSwitch dim_switch;

// Do something with the state machine.
dim_switch.start();
dim_switch.process_event(TurnOn{});
dim_switch.process_event(Dim{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 machine as before.
std::cout << "dim_switch_2.brightness == 75 : "
<< (dim_switch_2.brightness == 75) << std::endl;
}

} // namespace
115 changes: 115 additions & 0 deletions doc/modules/ROOT/attachments/backmp11/serializer/DimSwitch.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// 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)

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

#ifndef BOOST_MSM_EXAMPLE_DIM_SWITCH_HPP
#define BOOST_MSM_EXAMPLE_DIM_SWITCH_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;
};

// Actions.
struct SetDimValue
{
template <typename Fsm>
void operator()(const Dim& event, Fsm& fsm)
{
fsm.brightness = event.brightness;
}
};

// State machine front-end with a reflect member function.
struct DimSwitch_ : front::state_machine_def<DimSwitch_>
{
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

#endif // BOOST_MSM_EXAMPLE_DIM_SWITCH_HPP
112 changes: 112 additions & 0 deletions doc/modules/ROOT/attachments/backmp11/serializer/NlohmannJson.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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)

#include "DimSwitch.hpp"

#include <iostream>

#include <boost/core/typeinfo.hpp>

// nlohmann/json.
#include <boost/msm/backmp11/serialization/nlohmann_json.hpp>

namespace
{

// Helper for convenience:
// Convert all state ids to a human-readable JSON array
// to understand which states the ids refer to.
std::string state_names_to_nlohmann_json_string(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.dump(4);
}

std::string to_nlohmann_json_string(const DimSwitch& dim_switch)
{
const nlohmann::json json = dim_switch;
return json.dump(4);
}

[[maybe_unused]] void nlohmann_json_example()
{
DimSwitch dim_switch;

// Prints:
// [
// "Off",
// "On"
// ]
std::cout << state_names_to_nlohmann_json_string(dim_switch) << std::endl;

// The initial state is Off (state id 0).
dim_switch.start();
// Prints:
// {
// "active_state_ids": [
// 0
// ],
// "front_end": {
// "brightness": 0
// },
// "states": {
// "1": {
// "times_pressed": 0
// }
// },
// "stopped": false
// }
std::cout << to_nlohmann_json_string(dim_switch) << std::endl;

// Turn On (state id 1) and set brightness to 75.
dim_switch.process_event(TurnOn{});
// Prints:
// {
// "active_state_ids": [
// 1
// ],
// "front_end": {
// "brightness": 75
// },
// "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.
const nlohmann::json json =
nlohmann::json::parse(to_nlohmann_json_string(dim_switch));
auto dim_switch_2 = json.get<DimSwitch>();

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

} // namespace
22 changes: 1 addition & 21 deletions doc/modules/ROOT/pages/backmp11-back-end.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -461,29 +461,9 @@ You can use the reflection API for introspection use cases; the most prominent i
- Boost.JSON
- nlohmann/json

Serializing a state machine to JSON provides a neat way to inspect it in a human-readable format, as demonstrated with the `DimSwitch` state machine in the https://github.com/boostorg/msm/blob/develop/test/Backmp11Serialization.hpp[backmp11 serialization test]:

```json
{
"active_state_ids": [
1 // On
],
"front_end": {
"brightness": 75
},
"machine_state": 1, // idle
"states": {
"1": {
"times_pressed": 1
}
}
}
```


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 backmp11's `reflect(...)` API or library-specific serialization methods. It is recommended to implement `backmp11` back-end'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/Backmp11Serialization.hpp[backmp11 serialization test] demonstrates how to use the serialization libraries.
Serializing a state machine to JSON provides a neat way to inspect it in a human-readable format. 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 free `serialize(...)` method for the (root) state machine or implement a `serialize(...)` member in the back-end. Usage of all libraries is demonstrated in the xref:backmp11-back-end/examples.adoc#_serialization[`serialization examples`].


== Run to completion
Expand Down
Loading
Loading