From f87be695e1be58e06030b9cdf0f47dfeb2c1f3ed Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Sat, 13 Jun 2026 13:14:49 +0200 Subject: [PATCH] doc(backmp11): back adapter --- .../ROOT/attachments/backmp11/BackAdapter.cpp | 248 ++++++++++++++++++ doc/modules/ROOT/pages/backmp11-back-end.adoc | 11 +- .../pages/backmp11-back-end/examples.adoc | 6 + include/boost/msm/backmp11/common_types.hpp | 12 + include/boost/msm/backmp11/detail/common.hpp | 7 - .../msm/backmp11/serialization/boost_json.hpp | 6 +- include/boost/msm/backmp11/state_machine.hpp | 11 +- test/Backmp11.hpp | 2 +- test/Backmp11Adapter.hpp | 225 +--------------- 9 files changed, 281 insertions(+), 247 deletions(-) create mode 100644 doc/modules/ROOT/attachments/backmp11/BackAdapter.cpp diff --git a/doc/modules/ROOT/attachments/backmp11/BackAdapter.cpp b/doc/modules/ROOT/attachments/backmp11/BackAdapter.cpp new file mode 100644 index 00000000..4dfbc55e --- /dev/null +++ b/doc/modules/ROOT/attachments/backmp11/BackAdapter.cpp @@ -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 +#include + +namespace +{ + +// Serializer for Boost.Serialization. +// Replicates the serialization implementation of `back::state_machine`. +template +class back_serializer +{ + public: + back_serializer(Archive& archive) : m_archive(archive) {} + + template + void visit_front_end(FrontEnd&& front_end) + { + if constexpr (has_do_serialize>::value) + { + m_archive & front_end; + } + } + + template + void visit_front_end(FrontEnd&& /*front_end*/, Reflect&& reflect) + { + reflect(); + } + + template + void visit_member(const char* /*key*/, Member&& member) + { + m_archive & member; + } + + template + void visit_state(size_t /*state_id*/, State&& state) + { + if constexpr (has_do_serialize>::value) + { + m_archive & state; + } + } + + template + 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 +struct back_config_adapter : boost::msm::backmp11::state_machine_config +{ + using compile_policy = boost::mp11::mp_if_c< + std::is_same_v, + boost::msm::backmp11::favor_runtime_speed, + boost::msm::backmp11::favor_compile_time>; + + template + using event_pool_container = + typename QueueContainerPolicy::template In::type; +}; + +template +struct get_back_config_adapter_impl +{ + template + using parameters = boost::parameter::parameters; + template + using optional = boost::parameter::optional; + + using config_signature = boost::parameter::parameters< + boost::parameter::optional< + boost::parameter::deduced, + has_history_policy>, + boost::parameter::optional< + boost::parameter::deduced, + has_compile_policy>, + boost::parameter::optional< + boost::parameter::deduced, + has_fsm_check>, + boost::parameter::optional< + boost::parameter::deduced< + boost::msm::back::tag::queue_container_policy>, + has_queue_container_policy>>; + + using config_args = typename config_signature::bind::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; +}; +template +using get_back_config_adapter = + typename get_back_config_adapter_impl::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 back_adapter : public boost::msm::backmp11::state_machine< + A0, get_back_config_adapter, + back_adapter> +{ + using Base = boost::msm::backmp11::state_machine< + A0, get_back_config_adapter, + back_adapter>; + + 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 + 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 + void serialize(Archive& ar, const unsigned int /*version*/) + { + boost::msm::backmp11::reflect(*this, back_serializer{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 diff --git a/doc/modules/ROOT/pages/backmp11-back-end.adoc b/doc/modules/ROOT/pages/backmp11-back-end.adoc index 6f1a3b6a..a4a93414 100644 --- a/doc/modules/ROOT/pages/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/backmp11-back-end.adoc @@ -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 { @@ -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 @@ -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 back::HandledEnum process_event(const Event& event) @@ -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 diff --git a/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc b/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc index 5b8d0029..d5a27e39 100644 --- a/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc +++ b/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc @@ -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. diff --git a/include/boost/msm/backmp11/common_types.hpp b/include/boost/msm/backmp11/common_types.hpp index 43e86d8d..704e26cb 100644 --- a/include/boost/msm/backmp11/common_types.hpp +++ b/include/boost/msm/backmp11/common_types.hpp @@ -12,6 +12,7 @@ #ifndef BOOST_MSM_BACKMP11_COMMON_TYPES_HPP #define BOOST_MSM_BACKMP11_COMMON_TYPES_HPP +#include #include #include @@ -19,6 +20,17 @@ 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; diff --git a/include/boost/msm/backmp11/detail/common.hpp b/include/boost/msm/backmp11/detail/common.hpp index 4ee1a23e..53e4cc96 100644 --- a/include/boost/msm/backmp11/detail/common.hpp +++ b/include/boost/msm/backmp11/detail/common.hpp @@ -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: diff --git a/include/boost/msm/backmp11/serialization/boost_json.hpp b/include/boost/msm/backmp11/serialization/boost_json.hpp index b9733c63..92574d76 100644 --- a/include/boost/msm/backmp11/serialization/boost_json.hpp +++ b/include/boost/msm/backmp11/serialization/boost_json.hpp @@ -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, @@ -195,7 +195,7 @@ inline machine_state tag_invoke(const boost::json::value_to_tag&, } template >> + typename = std::enable_if_t>> void tag_invoke(const boost::json::value_from_tag&, boost::json::value& json, const StateMachine& sm) { @@ -203,7 +203,7 @@ void tag_invoke(const boost::json::value_from_tag&, boost::json::value& json, } template >> + typename = std::enable_if_t>> StateMachine tag_invoke(const boost::json::value_to_tag&, const boost::json::value& json) { diff --git a/include/boost/msm/backmp11/state_machine.hpp b/include/boost/msm/backmp11/state_machine.hpp index 3c9b3796..cfa5e7c5 100644 --- a/include/boost/msm/backmp11/state_machine.hpp +++ b/include/boost/msm/backmp11/state_machine.hpp @@ -310,7 +310,7 @@ class state_machine BOOST_ASSERT_MSG(&(this->get_root_sm()), "Root sm must be passed as Derived and configured as root_sm"); } - if (this->m_machine_state == detail::machine_state::stopped) + if (this->m_machine_state == machine_state::stopped) { on_entry(initial_event, get_fsm_argument()); } @@ -334,7 +334,7 @@ class state_machine template void stop(Event const& final_event) { - if (this->m_machine_state != detail::machine_state::stopped) + if (this->m_machine_state != machine_state::stopped) { on_exit(final_event, get_fsm_argument()); } @@ -379,8 +379,7 @@ class state_machine void defer_event(Event const& event) { do_defer_event(compile_policy_impl::normalize_event(event), - this->m_machine_state == - detail::machine_state::processing); + this->m_machine_state == machine_state::processing); } /// Returns the active state ids of the machine. @@ -546,7 +545,7 @@ class state_machine template process_result process_event_impl(Event const& event, detail::process_info info) { - if (this->m_machine_state != detail::machine_state::idle) + if (this->m_machine_state != machine_state::idle) { return process_result::HANDLED_FALSE; } @@ -882,7 +881,7 @@ class state_machine // ... then call our own exit. (static_cast(this))->on_exit(event, fsm); } - this->m_machine_state = detail::machine_state::stopped; + this->m_machine_state = machine_state::stopped; } derived_t& self() diff --git a/test/Backmp11.hpp b/test/Backmp11.hpp index dbf90f88..a2505dfb 100644 --- a/test/Backmp11.hpp +++ b/test/Backmp11.hpp @@ -10,7 +10,7 @@ // http://www.boost.org/LICENSE_1_0.txt) #include "boost/msm/backmp11/state_machine.hpp" -#include "boost/msm/backmp11/favor_compile_time.hpp" +#include "boost/msm/backmp11/favor_compile_time.hpp" // IWYU pragma: keep namespace boost::msm::backmp11 { diff --git a/test/Backmp11Adapter.hpp b/test/Backmp11Adapter.hpp index e01e4c01..c6b527db 100644 --- a/test/Backmp11Adapter.hpp +++ b/test/Backmp11Adapter.hpp @@ -9,14 +9,7 @@ // 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_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" -#define BOOST_PARAMETER_CAN_USE_MP11 -#include -#include +#include "attachments/backmp11/BackAdapter.cpp" #include "Backmp11.hpp" namespace boost::msm::backmp11 @@ -28,225 +21,11 @@ struct is_backmp11_state_machine : std::false_type {}; template struct is_backmp11_state_machine> : std::true_type {}; -template -class serializer -{ - public: - serializer(Archive& archive) : m_archive(archive) {} - - template - void visit_front_end(FrontEnd&& front_end) - { - if constexpr (has_serialize_member::value) - { - m_archive & front_end; - } - } - - template - void visit_front_end(FrontEnd&& /*front_end*/, Reflect&& reflect) - { - reflect(); - } - - template - void visit_member(const char* /*key*/, Member&& member) - { - m_archive & member; - } - - template - void visit_state(size_t /*state_id*/, State&& state) - { - if constexpr (has_serialize_member::value) - { - m_archive & state; - } - } - - template - void visit_state(size_t /*state_id*/, State&& /*state*/, Reflect&& reflect) - { - reflect(); - } - - private: - template - struct has_serialize_member : std::false_type {}; - template - struct has_serialize_member< - State, - std::void_t().serialize( - std::declval(), 0u))>> - : std::true_type {}; - - Archive& m_archive; -}; - -using back::queue_container_deque; - -template -struct state_machine_config_adapter : state_machine_config -{ - using compile_policy = mp11::mp_if_c< - std::is_same_v, - favor_runtime_speed, - favor_compile_time>; - - template - using event_pool_container = typename QueueContainerPolicy::template In::type; -}; - -template -struct get_state_machine_config_adapter_impl -{ - using config_signature = parameter::parameters< - parameter::optional, - has_history_policy>, - parameter::optional, - has_compile_policy>, - parameter::optional, - has_fsm_check>, - parameter::optional, - has_queue_container_policy>>; - - using config_args = typename config_signature::bind::type; - - using CompilePolicy = - typename parameter::binding::type; - - using QueueContainerPolicy = - typename parameter::binding::type; - - using type = - state_machine_config_adapter; -}; -template -using get_state_machine_config_adapter = - typename get_state_machine_config_adapter_impl::type; - template -class state_machine_adapter - : public state_machine, - state_machine_adapter> -{ - using Base = - state_machine, - state_machine_adapter>; - - public: - using Base::Base; - - void start() - { - try - { - Base::start(); - } - catch (std::exception& e) - { - this->exception_caught(starting{}, *this, e); - } - } - - void stop() - { - try - { - Base::stop(); - } - catch (std::exception& e) - { - this->exception_caught(stopping{}, *this, e); - } - } - - template - back::HandledEnum process_event(const Event& event) - { - if (this->get_machine_state() == detail::machine_state::processing) - { - this->enqueue_event(event); - return back::HANDLED_DEFERRED; - } - else - { - try - { - return Base::process_event(event); - } - catch (std::exception& e) - { - this->exception_caught(event, *this, e); - return 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 - void serialize(Archive& ar, - const unsigned int /*version*/) - { - backmp11::reflect(*this, serializer{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) {...} -}; +using state_machine_adapter = back_adapter; } // boost::msm::backmp11