From 5734df5735693f291f7f7ceab10b5c178579bb59 Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Mon, 15 Jun 2026 18:17:34 +0200 Subject: [PATCH] doc(backmp11): state machine interface --- .../backmp11/StateMachineInterface.cpp | 172 ++++++++++++++++++ doc/modules/ROOT/pages/backmp11-back-end.adoc | 6 +- .../pages/backmp11-back-end/examples.adoc | 7 + .../boost/msm/backmp11/favor_compile_time.hpp | 6 +- test/Backmp11StateMachineInterface.cpp | 75 ++++++++ test/CMakeLists.txt | 1 + test/Jamfile.v2 | 1 + 7 files changed, 265 insertions(+), 3 deletions(-) create mode 100644 doc/modules/ROOT/attachments/backmp11/StateMachineInterface.cpp create mode 100644 test/Backmp11StateMachineInterface.cpp diff --git a/doc/modules/ROOT/attachments/backmp11/StateMachineInterface.cpp b/doc/modules/ROOT/attachments/backmp11/StateMachineInterface.cpp new file mode 100644 index 00000000..6eb1a76e --- /dev/null +++ b/doc/modules/ROOT/attachments/backmp11/StateMachineInterface.cpp @@ -0,0 +1,172 @@ +// Copyright 2026 Christian Granzin +// Copyright 2010 Christophe Henry +// henry UNDERSCORE christophe AT hotmail DOT com +// This is an extended version of the state machine available in the boost::mpl library +// Distributed under the same license as the original. +// Copyright for the original version: +// Copyright 2005 David Abrahams and Aleksey Gurtovoy. Distributed +// under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +#include +#include +#include +#include +#include + +namespace back = boost::msm::backmp11; +namespace front = boost::msm::front; +using front::none; +using front::Row; +namespace mp11 = boost::mp11; + +namespace +{ + +// State machine interface. +class state_machine +{ + public: + virtual ~state_machine() = default; + + // Starts the state machine. + virtual void start() = 0; + + // Stops the state machine. + virtual void stop() = 0; + + // Checks whether a state is active. + // Uses a string for state identification to + // remove the state's type dependency. + virtual bool is_state_active(std::string_view name) = 0; + + // Processes an event. + virtual back::process_result process_event( + const back::any_event& event) = 0; +}; + +// Implementation of the interface. +struct MyConfig : back::state_machine_config +{ + // Use favor compile time, we need type erasure on events + // for a state machine interface. + using compile_policy = back::favor_compile_time; +}; + +template +class state_machine_impl + : public state_machine, + public back::state_machine> +{ + using base = back::state_machine>; + + public: + void start() override + { + base::start(); + } + + void stop() override + { + base::stop(); + } + + bool is_state_active(std::string_view name) override + { + bool result{false}; + this->visit( + [name, &result](const auto& state) + { + if (state.to_string() == name) + { + result = true; + } + }); + return result; + } + + back::process_result process_event(const back::any_event& event) override + { + return base::process_event(event); + } +}; + +// Events. +struct Greet {}; + +// Actions. +struct PrintMessage +{ + template + void operator()(const Greet&, Fsm&, Source& source, Target&) + { + if (talk) + { + std::cout << "Hello from " << source.to_string() << std::endl; + } + } + + [[maybe_unused]] static inline bool talk{true}; +}; + +// States. +template +struct StateBase : front::state<> +{ + static std::string to_string() + { + auto full_name = boost::core::demangled_name(typeid(Derived)); + return full_name.substr(full_name.rfind(':') + 1); + } +}; + +struct MyState : StateBase {}; + +struct MyOtherState : StateBase {}; + +// State machines. + +struct MyStateMachine_ : front::state_machine_def +{ + using initial_state = MyState; + using transition_table = mp11::mp_list< + Row + >; +}; + +using MyStateMachine = state_machine_impl; + +struct MyOtherStateMachine_ : front::state_machine_def +{ + using initial_state = MyOtherState; + using transition_table = mp11::mp_list< + Row + >; +}; + +using MyOtherStateMachine = state_machine_impl; + +[[maybe_unused]] void state_machine_interface_example() +{ + std::unique_ptr state_machine; + + state_machine = std::make_unique(); + state_machine->start(); + state_machine->process_event(Greet{}); + std::cout << "Is MyState active: " + << state_machine->is_state_active("MyState") << std::endl; + + state_machine = std::make_unique(); + state_machine->start(); + state_machine->process_event(Greet{}); + std::cout << "Is MyOtherState active: " + << state_machine->is_state_active("MyOtherState") << std::endl; +} + +} // namespace diff --git a/doc/modules/ROOT/pages/backmp11-back-end.adoc b/doc/modules/ROOT/pages/backmp11-back-end.adoc index a4a93414..6475a7e4 100644 --- a/doc/modules/ROOT/pages/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/backmp11-back-end.adoc @@ -237,11 +237,13 @@ Like its counterpart in `back`, it does not support event hierarchies and Kleene Events are wrapped into a `std::any` when they enter event processing. The dispatch table is stored in a hash map, with the type index of each event as the key and an array of function pointers to the matching transitions as the value. -With this policy, you can compile a state machine across multiple translation units (TUs) with the help of a preprocessor macro. +With this policy, you can hide a state machine implementation behind an interface. See the xref:backmp11-back-end/examples.adoc#_state_machine_interface[`state machine interface example`]. + +You can also compile a state machine across multiple translation units with the help of a preprocessor macro. Since the back-end should compile very quickly for most state machines, this is an opt-in feature: - define `BOOST_MSM_BACKMP11_MANUAL_GENERATION` before including `msm/backmp11/favor_compile_time.hpp` -- then generate your state machine back-end(s) with the macro `BOOST_MSM_BACKMP11_GENERATE_STATE_MACHINE()` +- then generate the state machine back-end(s) with the macro `BOOST_MSM_BACKMP11_GENERATE_STATE_MACHINE()` You can find an example for this in the https://github.com/boostorg/msm/blob/develop/test/Backmp11Visitor.cpp[visitor test]. diff --git a/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc b/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc index d5a27e39..6eb94136 100644 --- a/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc +++ b/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc @@ -34,3 +34,10 @@ MyStateMachine processing event SmInternalTransitionEvent MyStateMachine processed transition "MyStateMachine + SmInternalTransitionEvent / MyAction" (handled) Stopping state machine... ``` + + +== xref:attachment$backmp11/StateMachineInterface.cpp[State machine interface] + +This example demonstrates how to establish a generic state machine interface with the `favor_compile_time` policy. + +Hiding a state machine behind an interface enables full encapsulation of its implementation — useful for improving compilation times, unit testing, and concealing proprietary logic when distributing pre-compiled libraries. diff --git a/include/boost/msm/backmp11/favor_compile_time.hpp b/include/boost/msm/backmp11/favor_compile_time.hpp index 28f2486b..7c67415a 100644 --- a/include/boost/msm/backmp11/favor_compile_time.hpp +++ b/include/boost/msm/backmp11/favor_compile_time.hpp @@ -37,11 +37,14 @@ } \ } // boost::msm::backmp11::detail -namespace boost:: msm::backmp11::detail +namespace boost:: msm::backmp11 { using any_event = std::any; +namespace detail +{ + template <> struct compile_policy_impl { @@ -539,6 +542,7 @@ compile_policy_impl::dispatch_table #endif +} // namespace detail } // namespace boost::msm::backmp11::detail #endif //BOOST_MSM_BACKMP11_FAVOR_COMPILE_TIME_HPP diff --git a/test/Backmp11StateMachineInterface.cpp b/test/Backmp11StateMachineInterface.cpp new file mode 100644 index 00000000..fc9d6922 --- /dev/null +++ b/test/Backmp11StateMachineInterface.cpp @@ -0,0 +1,75 @@ +// Copyright 2025 Christian Granzin +// Copyright 2010 Christophe Henry +// henry UNDERSCORE christophe AT hotmail DOT com +// This is an extended version of the state machine available in the boost::mpl library +// Distributed under the same license as the original. +// Copyright for the original version: +// Copyright 2005 David Abrahams and Aleksey Gurtovoy. Distributed +// under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_MSM_NONSTANDALONE_TEST +#define BOOST_TEST_MODULE test_heapless_state_machine +#endif +#include + +// back-end +#include "BackCommon.hpp" +#include +//front-end +#include "FrontCommon.hpp" + +#include "Utils.hpp" +#include "attachments/backmp11/StateMachineInterface.cpp" + +namespace msm = boost::msm; + +using namespace msm::front; +using namespace msm::backmp11; + +namespace +{ + +struct EventObserver : default_observer +{ + template + void pre_process_event(const StateMachine&, const std::any&) + { + processed_greet_events++; + } + + size_t processed_greet_events{}; +}; + +struct TestConfig : MyConfig +{ + using observer = EventObserver; +}; + +using TestMachine = state_machine_impl; +using OtherTestMachine = state_machine_impl; + +BOOST_AUTO_TEST_CASE(state_machine_interface) +{ + PrintMessage::talk = false; + + std::unique_ptr state_machine; + + state_machine = std::make_unique(); + EventObserver* observer = + &dynamic_cast(*state_machine).get_observer(); + state_machine->start(); + BOOST_REQUIRE(state_machine->is_state_active("MyState")); + state_machine->process_event(Greet{}); + BOOST_REQUIRE(observer->processed_greet_events == 1); + + state_machine = std::make_unique(); + observer = &dynamic_cast(*state_machine).get_observer(); + state_machine->start(); + BOOST_REQUIRE(state_machine->is_state_active("MyOtherState")); + state_machine->process_event(Greet{}); + BOOST_REQUIRE(observer->processed_greet_events == 1); +} + +} // namespace diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cb1b365f..c93c3b44 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -92,6 +92,7 @@ add_executable(boost_msm_cxx17_tests Backmp11Observer.cpp Backmp11RootSm.cpp Backmp11Serialization.cpp + Backmp11StateMachineInterface.cpp Backmp11Transitions.cpp Backmp11Visitor.cpp main.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 2d593bde..cb858681 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -92,6 +92,7 @@ test-suite msm-unit-tests-cxxstd17 [ run Backmp11Observer.cpp ] [ run Backmp11RootSm.cpp ] [ run Backmp11Serialization.cpp : : : BOOST_MSM_TEST_NLOHMANN_JSON ] + [ run Backmp11StateMachineInterface.cpp ] [ run Backmp11Transitions.cpp ] [ run Backmp11Visitor.cpp ] :