From d40ebe07fbfc91c9cb9454ae7f0f6d5c350e6fed Mon Sep 17 00:00:00 2001 From: Christian Granzin Date: Tue, 16 Jun 2026 21:12:18 +0200 Subject: [PATCH] doc(backmp11): visitor example --- .../ROOT/attachments/backmp11/Visitor.cpp | 155 ++++++++++++++++++ doc/modules/ROOT/pages/backmp11-back-end.adoc | 44 +---- .../pages/backmp11-back-end/examples.adoc | 7 + test/Backmp11Visitor.cpp | 26 +++ 4 files changed, 193 insertions(+), 39 deletions(-) create mode 100644 doc/modules/ROOT/attachments/backmp11/Visitor.cpp diff --git a/doc/modules/ROOT/attachments/backmp11/Visitor.cpp b/doc/modules/ROOT/attachments/backmp11/Visitor.cpp new file mode 100644 index 00000000..39d5fbbd --- /dev/null +++ b/doc/modules/ROOT/attachments/backmp11/Visitor.cpp @@ -0,0 +1,155 @@ +// 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 + +namespace back = boost::msm::backmp11; +namespace front = boost::msm::front; +using front::none; +using front::Row; +namespace mp11 = boost::mp11; + +namespace +{ + +// Events. +struct Play +{ + std::string_view song; +}; + +struct Stop {}; + +// States. +struct Idle : front::state<> {}; + +template +struct Song : front::state<> +{ + template + void on_entry(const Play&, Fsm&) + { + times_played += 1; + } + + static constexpr const char* name = Name; + size_t times_played{}; +}; + +constexpr const char hey_jude[] = "Hey Jude"; +constexpr const char all_you_need_is_love[] = "All You Need Is Love"; +constexpr const char paint_it_black[] = "Paint It Black"; + +// Guards. +template +struct IsSong +{ + template + bool operator()(const Play& play, Fsm&) + { + return std::string_view{Name} == std::string_view{play.song}; + } +}; + +// State machine. +struct Playing_ : front::state_machine_def +{ + template + void on_entry(const Play& play, Fsm& fsm) + { + fsm.enqueue_event(play); + } + + using initial_state = Idle; + using transition_table = mp11::mp_list< + Row , none, IsSong>, + Row, none, IsSong>, + Row , none, IsSong> + >; +}; + +using Playing = back::state_machine; + +struct Jukebox_ : front::state_machine_def +{ + using initial_state = Idle; + using transition_table = mp11::mp_list< + Row, + Row + >; +}; + +using Jukebox = back::state_machine; + +constexpr const char* songs[] = { + hey_jude, + all_you_need_is_love, + paint_it_black, +}; + +// Type trait to identify a song state in a lambda. +template +struct is_song : std::false_type {}; +template +struct is_song> : std::true_type {}; + +// Functor with a call overload for a song state. +struct check_times_played +{ + template + void operator()(const Song& song) + { + std::cout << "Song " << song.name << " played " << song.times_played + << " times" << std::endl; + } + + template + void operator()(const State&) + { + } +}; + +[[maybe_unused]] void visitor_example() +{ + std::mt19937 rng{std::random_device{}()}; + std::uniform_int_distribution dist{0, 2}; + Jukebox jukebox; + jukebox.start(); + + // Play 100 + 1 songs + for (size_t i = 0; i < 100; i++) + { + const size_t n = dist(rng); + jukebox.process_event(Play{songs[n]}); + jukebox.process_event(Stop{}); + } + jukebox.process_event(Play{songs[dist(rng)]}); + + // Check the active song - using a lambda. + jukebox.visit( + [](const auto& maybe_song) + { + if constexpr (is_song>::value) + { + std::cout << "Currently playing: " << maybe_song.name << std::endl; + } + }); + + // Check how many times each song has been played - using a functor for more precise overloads. + jukebox.visit(check_times_played{}); +} + +} // namespace diff --git a/doc/modules/ROOT/pages/backmp11-back-end.adoc b/doc/modules/ROOT/pages/backmp11-back-end.adoc index 41259976..b2c0f7ab 100644 --- a/doc/modules/ROOT/pages/backmp11-back-end.adoc +++ b/doc/modules/ROOT/pages/backmp11-back-end.adoc @@ -261,8 +261,6 @@ Since the back-end should compile very quickly for most state machines, this is - define `BOOST_MSM_BACKMP11_MANUAL_GENERATION` before including `msm/backmp11/favor_compile_time.hpp` - 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]. - == Extension @@ -358,10 +356,10 @@ If the type of the state appears multiple times in a hierarchical state machine, Use xref:reference:boost/msm/backmp11/state_machine/visit-0b.adoc[`void state_machine::visit(Visitor&&)`] to visit all currently active states with a visitor functor. In hierarchical state machines, submachines are visited recursively. -```cpp -template -void state_machine::visit(Visitor&& visitor); -``` +The visit function can be customized with a xref:reference:boost/msm/backmp11/visit_mode.adoc[`visit_mode`] template parameter: + +- only the active states or all states +- non-recursive or recursive The visitor functor must support being called with all existing state types of the state machine. @@ -370,40 +368,8 @@ template void operator()(State& state); ``` -A state machine can be visited in multiple modes: +The usage of the visitor is demonstrated in the xref:backmp11-back-end/examples.adoc#_visitor[`visitor example`]. -- only the active states or all states -- non-recursive or recursive - -The visit function can be customized with a xref:reference:boost/msm/backmp11/visit_mode.adoc[`visit_mode`] template parameter. - -```cpp -enum class visit_mode -{ - /// Visit only active states (mutually exclusive with all_states). - active_states = 0b001, - /// Visit all states (mutually exclusive with active_states). - all_states = 0b010, - - /// Traversal mode (not set == non-recursive). - recursive = 0b100, - - active_non_recursive = active_states, - active_recursive = active_states | recursive, - all_non_recursive = all_states, - all_recursive = all_states | recursive -}; - -// Use the pre-defined constants... -my_state_machine.visit - - ([](auto &state) {/*...*/}); -// ... or assemble a mode -my_state_machine.visit - - ([](auto &state) {/*...*/}); - -``` == Exceptions diff --git a/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc b/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc index ef1b9b7f..3efddafb 100644 --- a/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc +++ b/doc/modules/ROOT/pages/backmp11-back-end/examples.adoc @@ -73,3 +73,10 @@ A serialization to JSON is particularly useful for inspecting a state machine's 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. + + +== xref:attachment$backmp11/Visitor.cpp[Visitor] + +This example demonstrates the usage of a visitor. + +A jukebox can play 3 different types of songs. An active state visitor reports which song is currently playing. An all states visitor reports how many times each song has been played. diff --git a/test/Backmp11Visitor.cpp b/test/Backmp11Visitor.cpp index 5f0a70f2..d83fd650 100644 --- a/test/Backmp11Visitor.cpp +++ b/test/Backmp11Visitor.cpp @@ -14,6 +14,8 @@ #endif #include +#include "attachments/backmp11/Visitor.cpp" + // back-end // Generate the favor_compile_time SM manually. #define BOOST_MSM_BACKMP11_MANUAL_GENERATION @@ -21,6 +23,8 @@ // front-end #include "FrontCommon.hpp" +#include "Utils.hpp" + namespace msm = boost::msm; namespace mp11 = boost::mp11; @@ -319,6 +323,28 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(no_copy_no_move, TestMachine, TestMachines) }); } +BOOST_AUTO_TEST_CASE(visitor_example_test) +{ + static_assert(is_song>::value); + + Jukebox jukebox; + jukebox.start(); + + for (size_t i = 0; i < 3; i++) + { + jukebox.process_event(Play{songs[i]}); + jukebox.process_event(Stop{}); + } + Playing& playing = jukebox.get_state(); + ASSERT_ONE_AND_RESET(playing.get_state>().times_played); + ASSERT_ONE_AND_RESET(playing.get_state>().times_played); + ASSERT_ONE_AND_RESET(playing.get_state>().times_played); + + jukebox.process_event(Play{songs[0]}); + BOOST_REQUIRE(jukebox.is_state_active>()); + ASSERT_ONE_AND_RESET(playing.get_state>().times_played); +} + } // namespace using TestMachine = hierarchical_state_machine;