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
155 changes: 155 additions & 0 deletions doc/modules/ROOT/attachments/backmp11/Visitor.cpp
Original file line number Diff line number Diff line change
@@ -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 <iostream>
#include <random>

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

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 <const char* Name>
struct Song : front::state<>
{
template <typename Fsm>
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 <const char* Name>
struct IsSong
{
template <typename Fsm>
bool operator()(const Play& play, Fsm&)
{
return std::string_view{Name} == std::string_view{play.song};
}
};

// State machine.
struct Playing_ : front::state_machine_def<Playing_>
{
template <typename Fsm>
void on_entry(const Play& play, Fsm& fsm)
{
fsm.enqueue_event(play);
}

using initial_state = Idle;
using transition_table = mp11::mp_list<
Row<Idle, Play, Song<hey_jude> , none, IsSong<hey_jude>>,
Row<Idle, Play, Song<all_you_need_is_love>, none, IsSong<all_you_need_is_love>>,
Row<Idle, Play, Song<paint_it_black> , none, IsSong<paint_it_black>>
>;
};

using Playing = back::state_machine<Playing_>;

struct Jukebox_ : front::state_machine_def<Jukebox_>
{
using initial_state = Idle;
using transition_table = mp11::mp_list<
Row<Idle , Play, Playing>,
Row<Playing, Stop, Idle>
>;
};

using Jukebox = back::state_machine<Jukebox_>;

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 <typename T>
struct is_song : std::false_type {};
template <const char* Name>
struct is_song<Song<Name>> : std::true_type {};

// Functor with a call overload for a song state.
struct check_times_played
{
template <const char* Name>
void operator()(const Song<Name>& song)
{
std::cout << "Song " << song.name << " played " << song.times_played
<< " times" << std::endl;
}

template <typename State>
void operator()(const State&)
{
}
};

[[maybe_unused]] void visitor_example()
{
std::mt19937 rng{std::random_device{}()};
std::uniform_int_distribution<size_t> 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<std::decay_t<decltype(maybe_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<back::visit_mode::all_recursive>(check_times_played{});
}

} // namespace
44 changes: 5 additions & 39 deletions doc/modules/ROOT/pages/backmp11-back-end.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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(<sm_type>)`

You can find an example for this in the https://github.com/boostorg/msm/blob/develop/test/Backmp11Visitor.cpp[visitor test].


== Extension

Expand Down Expand Up @@ -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 <typename Visitor>
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.

Expand All @@ -370,40 +368,8 @@ template <typename State>
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
<visit_mode::all_recursive>
([](auto &state) {/*...*/});
// ... or assemble a mode
my_state_machine.visit
<visit_mode::all_states | visit_mode::recursive>
([](auto &state) {/*...*/});

```

== Exceptions

Expand Down
7 changes: 7 additions & 0 deletions doc/modules/ROOT/pages/backmp11-back-end/examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
26 changes: 26 additions & 0 deletions test/Backmp11Visitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
#endif
#include <boost/test/unit_test.hpp>

#include "attachments/backmp11/Visitor.cpp"

// back-end
// Generate the favor_compile_time SM manually.
#define BOOST_MSM_BACKMP11_MANUAL_GENERATION
#include "Backmp11.hpp"
// front-end
#include "FrontCommon.hpp"

#include "Utils.hpp"

namespace msm = boost::msm;
namespace mp11 = boost::mp11;

Expand Down Expand Up @@ -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<Song<songs[0]>>::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<Playing>();
ASSERT_ONE_AND_RESET(playing.get_state<Song<songs[0]>>().times_played);
ASSERT_ONE_AND_RESET(playing.get_state<Song<songs[1]>>().times_played);
ASSERT_ONE_AND_RESET(playing.get_state<Song<songs[2]>>().times_played);

jukebox.process_event(Play{songs[0]});
BOOST_REQUIRE(jukebox.is_state_active<Song<songs[0]>>());
ASSERT_ONE_AND_RESET(playing.get_state<Song<songs[0]>>().times_played);
}

} // namespace

using TestMachine = hierarchical_state_machine<favor_compile_time_config>;
Expand Down
Loading