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

#include "boost/msm/back/state_machine.hpp"
#include "boost/msm/backmp11/state_machine.hpp"
#define BOOST_PARAMETER_CAN_USE_MP11
#include <boost/parameter.hpp>
#include <boost/serialization/array.hpp>

namespace
{

// Serializer for Boost.Serialization.
// Replicates the serialization implementation of `back::state_machine`.
template <typename Archive>
class back_serializer
{
public:
back_serializer(Archive& archive) : m_archive(archive) {}

template <typename FrontEnd>
void visit_front_end(FrontEnd&& front_end)
{
if constexpr (has_do_serialize<std::decay_t<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_do_serialize<std::decay_t<State>>::value)
{
m_archive & state;
}
}

template <typename State, typename Reflect>
void visit_state(size_t /*state_id*/, State&& /*state*/, Reflect&& reflect)
{
reflect();
}

private:
Archive& m_archive;
};

// Config adapter for `back::state_machine`.
// The adapter only contains the compile policy and the queue container policy,
// the other template arguments are not supported.
template <typename CompilePolicy, typename QueueContainerPolicy>
struct back_config_adapter : boost::msm::backmp11::state_machine_config
{
using compile_policy = boost::mp11::mp_if_c<
std::is_same_v<CompilePolicy,
boost::msm::backmp11::favor_runtime_speed>,
boost::msm::backmp11::favor_runtime_speed,
boost::msm::backmp11::favor_compile_time>;

template <typename T>
using event_pool_container =
typename QueueContainerPolicy::template In<T>::type;
};

template <class A1, class A2, class A3, class A4>
struct get_back_config_adapter_impl
{
template <typename... Spec>
using parameters = boost::parameter::parameters<Spec...>;
template <typename Tag,
typename Predicate = ::boost::parameter::aux::use_default>
using optional = boost::parameter::optional<Tag, Predicate>;

using config_signature = boost::parameter::parameters<
boost::parameter::optional<
boost::parameter::deduced<boost::msm::back::tag::history_policy>,
has_history_policy<boost::mpl::_>>,
boost::parameter::optional<
boost::parameter::deduced<boost::msm::back::tag::compile_policy>,
has_compile_policy<boost::mpl::_>>,
boost::parameter::optional<
boost::parameter::deduced<boost::msm::back::tag::fsm_check_policy>,
has_fsm_check<boost::mpl::_>>,
boost::parameter::optional<
boost::parameter::deduced<
boost::msm::back::tag::queue_container_policy>,
has_queue_container_policy<boost::mpl::_>>>;

using config_args = typename config_signature::bind<A1, A2, A3, A4>::type;

using CompilePolicy = typename boost::parameter::binding<
config_args, boost::msm::back::tag::compile_policy,
boost::msm::backmp11::favor_runtime_speed>::type;

using QueueContainerPolicy = typename boost::parameter::binding<
config_args, boost::msm::back::tag::queue_container_policy,
boost::msm::back::queue_container_deque>::type;

using type =
back_config_adapter<CompilePolicy, QueueContainerPolicy>;
};
template <class A1, class A2, class A3, class A4>
using get_back_config_adapter =
typename get_back_config_adapter_impl<A1, A2, A3, A4>::type;

// State machine adapter for a `back::state_machine`.
// Provides a `backmp11::state_machine` implementation adapted to
// replicate the API of `back::state_machine`.
template <class A0,
class A1 = boost::parameter::void_,
class A2 = boost::parameter::void_,
class A3 = boost::parameter::void_,
class A4 = boost::parameter::void_>
class back_adapter : public boost::msm::backmp11::state_machine<
A0, get_back_config_adapter<A1, A2, A3, A4>,
back_adapter<A0, A1, A2, A3, A4>>
{
using Base = boost::msm::backmp11::state_machine<
A0, get_back_config_adapter<A1, A2, A3, A4>,
back_adapter<A0, A1, A2, A3, A4>>;

public:
using Base::Base;

void start()
{
try
{
Base::start();
}
catch (std::exception& e)
{
this->exception_caught(boost::msm::backmp11::starting{}, *this, e);
}
}

void stop()
{
try
{
Base::stop();
}
catch (std::exception& e)
{
this->exception_caught(boost::msm::backmp11::stopping{}, *this, e);
}
}

template <typename Event>
boost::msm::back::HandledEnum process_event(const Event& event)
{
if (this->get_machine_state() ==
boost::msm::backmp11::machine_state::processing)
{
this->enqueue_event(event);
return boost::msm::back::HANDLED_DEFERRED;
}
else
{
try
{
return Base::process_event(event);
}
catch (std::exception& e)
{
this->exception_caught(event, *this, e);
return boost::msm::back::HANDLED_FALSE;
}
}
}

// The new API returns a const std::array<...>&.
const uint16_t* current_state() const
{
return &this->get_active_state_ids()[0];
}

auto& get_message_queue()
{
return this->get_event_pool().events;
}

size_t get_message_queue_size() const
{
return this->get_event_pool().events.size();
}

void execute_queued_events()
{
this->process_event_pool();
}

void execute_single_queued_event()
{
this->process_event_pool(1);
}

auto& get_deferred_queue()
{
return this->get_event_pool().events;
}

void clear_deferred_queue()
{
this->get_event_pool().events.clear();
}

template <class Archive>
void serialize(Archive& ar, const unsigned int /*version*/)
{
boost::msm::backmp11::reflect(*this, back_serializer<Archive>{ar});
}

// No adapter.
// Superseded by the visitor API.
// void visit_current_states(...) {...}

// No adapter.
// States can be set with `get_state<...>() = ...` or the visitor API.
// void set_states(...) {...}

// No adapter.
// Could be implemented with the visitor API.
// auto get_state_by_id(int id) {...}
};

} // namespace
11 changes: 4 additions & 7 deletions doc/modules/ROOT/pages/backmp11-back-end.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ 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/Backmp11Adapter.hpp[backmp11 serialization test]:
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
{
Expand All @@ -481,7 +481,7 @@ Serializing a state machine to JSON provides a neat way to inspect it in a human

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/Backmp11Adapter.hpp[backmp11 serialization test] demonstrates how to use the 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.


== Run to completion
Expand Down Expand Up @@ -527,10 +527,10 @@ The following sections provide further details about the differences between `ba

=== Public API of `state_machine`

The following adapter pseudo-code illustrates the differences from the `state_machine` API of `back`.
The following adapter pseudo-code illustrates the differences from the `back::state_machine` API (see xref:backmp11-back-end/examples.adoc#_back_adapter[`full code example`]).

```cpp
class state_machine_adapter
class back_adapter
{
template <typename Event>
back::HandledEnum process_event(const Event& event)
Expand Down Expand Up @@ -604,9 +604,6 @@ 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.


=== Kleene events

Expand Down
6 changes: 6 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,11 @@
= Examples

== xref:attachment$backmp11/BackAdapter.cpp[`back` adapter]

This example contains a state machine implementation with an API adapter that adheres to the `back::state_machine` interface.
It is helpful for utilizing `backmp11` in a legacy code base.


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

This example demonstrates a state machine configured for heapless execution.
Expand Down
12 changes: 12 additions & 0 deletions include/boost/msm/backmp11/common_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,25 @@
#ifndef BOOST_MSM_BACKMP11_COMMON_TYPES_HPP
#define BOOST_MSM_BACKMP11_COMMON_TYPES_HPP

#include <cstdint>
#include <type_traits>

#include <boost/msm/back/common_types.hpp>

namespace boost::msm::backmp11
{

/// Describes the state of the state machine.
enum class machine_state : uint8_t
{
/// Stopped / not started.
stopped = 0,
/// Ready to process an event.
idle,
/// Processing an event.
processing
};

/// Return type of @ref state_machine::process_event calls.
using process_result = back::HandledEnum;

Expand Down
7 changes: 0 additions & 7 deletions include/boost/msm/backmp11/detail/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,6 @@ constexpr HandledEnum& operator&=(HandledEnum& lhs, HandledEnum rhs)
namespace boost::msm::backmp11::detail
{

enum class machine_state : uint8_t
{
stopped = 0,
idle,
processing
};

class process_guard
{
public:
Expand Down
6 changes: 3 additions & 3 deletions include/boost/msm/backmp11/serialization/boost_json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ class boost_json_deserializer

} // namespace boost::msm::backmp11::serialization

namespace boost::msm::backmp11::detail
namespace boost::msm::backmp11
{

inline void tag_invoke(const boost::json::value_from_tag&, boost::json::value& json,
Expand All @@ -195,15 +195,15 @@ inline machine_state tag_invoke(const boost::json::value_to_tag<machine_state>&,
}

template <typename StateMachine,
typename = std::enable_if_t<is_state_machine_v<StateMachine>>>
typename = std::enable_if_t<detail::is_state_machine_v<StateMachine>>>
void tag_invoke(const boost::json::value_from_tag&, boost::json::value& json,
const StateMachine& sm)
{
reflect(sm, serialization::boost_json_serializer{json});
}

template <typename StateMachine,
typename = std::enable_if_t<is_state_machine_v<StateMachine>>>
typename = std::enable_if_t<detail::is_state_machine_v<StateMachine>>>
StateMachine tag_invoke(const boost::json::value_to_tag<StateMachine>&,
const boost::json::value& json)
{
Expand Down
Loading
Loading