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
172 changes: 172 additions & 0 deletions doc/modules/ROOT/attachments/backmp11/StateMachineInterface.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/core/typeinfo.hpp>
#include <iostream>

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

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 <typename FrontEnd, typename Config = MyConfig>
class state_machine_impl
: public state_machine,
public back::state_machine<FrontEnd, Config,
state_machine_impl<FrontEnd, Config>>
{
using base = back::state_machine<FrontEnd, Config,
state_machine_impl<FrontEnd, Config>>;

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 <typename Fsm, typename Source, typename Target>
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 <typename Derived>
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<MyState> {};

struct MyOtherState : StateBase<MyOtherState> {};

// State machines.

struct MyStateMachine_ : front::state_machine_def<MyStateMachine_>
{
using initial_state = MyState;
using transition_table = mp11::mp_list<
Row<MyState, Greet, none, PrintMessage>
>;
};

using MyStateMachine = state_machine_impl<MyStateMachine_>;

struct MyOtherStateMachine_ : front::state_machine_def<MyOtherStateMachine_>
{
using initial_state = MyOtherState;
using transition_table = mp11::mp_list<
Row<MyOtherState, Greet, none, PrintMessage>
>;
};

using MyOtherStateMachine = state_machine_impl<MyOtherStateMachine_>;

[[maybe_unused]] void state_machine_interface_example()
{
std::unique_ptr<state_machine> state_machine;

state_machine = std::make_unique<MyStateMachine>();
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<MyOtherStateMachine>();
state_machine->start();
state_machine->process_event(Greet{});
std::cout << "Is MyOtherState active: "
<< state_machine->is_state_active("MyOtherState") << std::endl;
}

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

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 @@ -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.
6 changes: 5 additions & 1 deletion include/boost/msm/backmp11/favor_compile_time.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<favor_compile_time>
{
Expand Down Expand Up @@ -539,6 +542,7 @@ compile_policy_impl<favor_compile_time>::dispatch_table<StateMachine, any_event>

#endif

} // namespace detail
} // namespace boost::msm::backmp11::detail

#endif //BOOST_MSM_BACKMP11_FAVOR_COMPILE_TIME_HPP
75 changes: 75 additions & 0 deletions test/Backmp11StateMachineInterface.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/test/unit_test.hpp>

// back-end
#include "BackCommon.hpp"
#include <boost/msm/backmp11/observer.hpp>
//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 <typename StateMachine>
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<MyStateMachine_, TestConfig>;
using OtherTestMachine = state_machine_impl<MyOtherStateMachine_, TestConfig>;

BOOST_AUTO_TEST_CASE(state_machine_interface)
{
PrintMessage::talk = false;

std::unique_ptr<state_machine> state_machine;

state_machine = std::make_unique<TestMachine>();
EventObserver* observer =
&dynamic_cast<TestMachine&>(*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<OtherTestMachine>();
observer = &dynamic_cast<OtherTestMachine&>(*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
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ add_executable(boost_msm_cxx17_tests
Backmp11Observer.cpp
Backmp11RootSm.cpp
Backmp11Serialization.cpp
Backmp11StateMachineInterface.cpp
Backmp11Transitions.cpp
Backmp11Visitor.cpp
main.cpp
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ test-suite msm-unit-tests-cxxstd17
[ run Backmp11Observer.cpp ]
[ run Backmp11RootSm.cpp ]
[ run Backmp11Serialization.cpp : : : <define>BOOST_MSM_TEST_NLOHMANN_JSON ]
[ run Backmp11StateMachineInterface.cpp ]
[ run Backmp11Transitions.cpp ]
[ run Backmp11Visitor.cpp ]
:
Expand Down
Loading