mirror of
https://github.com/boostorg/msm.git
synced 2026-07-21 13:23:45 +00:00
feat(backmp11): observer API
This commit is contained in:
committed by
Christian Granzin
parent
cd3fba62a6
commit
2ff2b5caea
@@ -0,0 +1,226 @@
|
||||
// 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 <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/core/typeinfo.hpp>
|
||||
|
||||
#include <boost/msm/backmp11/state_machine.hpp>
|
||||
#include <boost/msm/backmp11/observer.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;
|
||||
using front::Internal;
|
||||
namespace mp11 = boost::mp11;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Logger for state machine activities.
|
||||
// Logs the beginning of event processing and the result of each processed transition.
|
||||
class Logger : public back::default_observer
|
||||
{
|
||||
public:
|
||||
template <typename StateMachine, typename Event>
|
||||
void pre_process_event(const StateMachine& sm, const Event& event)
|
||||
{
|
||||
log(to_string(sm) + " processing event " + to_string(event));
|
||||
}
|
||||
|
||||
template <typename Source, typename Event, typename Target, typename Action,
|
||||
typename Guard, typename StateMachine>
|
||||
void post_process_transition(const StateMachine& sm, size_t /*region_id*/,
|
||||
process_result result)
|
||||
{
|
||||
log(to_string(sm) + " processed transition \"" + to_string<Source>() +
|
||||
" + " + to_string<Event>() + guard_to_string<Guard>() +
|
||||
action_to_string<Action>() + target_to_string<Target>() + "\" (" +
|
||||
to_string(result) + ")");
|
||||
}
|
||||
|
||||
template <typename Event, typename Action, typename Guard,
|
||||
typename StateMachine>
|
||||
void post_process_transition(const StateMachine& sm, process_result result)
|
||||
{
|
||||
log(to_string(sm) + " processed transition \"" + to_string(sm) + " + " +
|
||||
to_string<Event>() + guard_to_string<Guard>() +
|
||||
action_to_string<Action>() + "\" (" + to_string(result) + ")");
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void log(std::string_view msg)
|
||||
{
|
||||
std::cout << msg << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string to_string(const std::type_info& type)
|
||||
{
|
||||
auto full_name = boost::core::demangled_name(type);
|
||||
return full_name.substr(full_name.rfind(':') + 1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::string to_string()
|
||||
{
|
||||
return to_string(typeid(T));
|
||||
}
|
||||
|
||||
template <typename FrontEnd, typename Config, typename Derived>
|
||||
static std::string to_string(const back::state_machine<FrontEnd, Config, Derived>& /*sm*/)
|
||||
{
|
||||
return to_string<FrontEnd>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::string to_string(const T& /*event*/)
|
||||
{
|
||||
return to_string<T>();
|
||||
}
|
||||
|
||||
static std::string to_string(const std::any& event)
|
||||
{
|
||||
return to_string(event.type());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::string target_to_string()
|
||||
{
|
||||
if constexpr (!std::is_same_v<T, front::none>)
|
||||
{
|
||||
return std::string{" -> "} + to_string<T>();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::string action_to_string()
|
||||
{
|
||||
if constexpr (!std::is_same_v<T, front::none>)
|
||||
{
|
||||
return std::string{" / "} + to_string<T>();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::string guard_to_string()
|
||||
{
|
||||
if constexpr (!std::is_same_v<T, front::none>)
|
||||
{
|
||||
return std::string{" [ "} + to_string<T>() + " ]";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::string to_string(process_result result)
|
||||
{
|
||||
using Enum = boost::msm::back::HandledEnum;
|
||||
switch (result)
|
||||
{
|
||||
case Enum::HANDLED_FALSE:
|
||||
return "discarded";
|
||||
case Enum::HANDLED_TRUE:
|
||||
return "handled";
|
||||
case Enum::HANDLED_GUARD_REJECT:
|
||||
return "rejected";
|
||||
case Enum::HANDLED_DEFERRED:
|
||||
return "deferred";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Events.
|
||||
struct TransitionEvent {};
|
||||
struct InternalTransitionEvent {};
|
||||
struct SmInternalTransitionEvent {};
|
||||
|
||||
// Actions
|
||||
struct MyAction
|
||||
{
|
||||
template<typename Fsm>
|
||||
void operator()(Fsm&)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Guards
|
||||
struct MyGuard
|
||||
{
|
||||
template<typename Fsm>
|
||||
bool operator()(Fsm&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct NotMyGuard
|
||||
{
|
||||
template<typename Fsm>
|
||||
bool operator()(Fsm&) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// States.
|
||||
struct MyState : front::state<> {};
|
||||
struct MyOtherState : front::state<> {};
|
||||
|
||||
// State machine.
|
||||
// We leave out the trailing underscore for prettier logs.
|
||||
// You could also implement `to_string()` methods to explicitly set names and
|
||||
// use these in the logger implementation.
|
||||
struct MyStateMachine : front::state_machine_def<MyStateMachine>
|
||||
{
|
||||
using initial_state = MyState;
|
||||
using transition_table = mp11::mp_list<
|
||||
Row<MyState , InternalTransitionEvent, none , MyAction, none>,
|
||||
Row<MyState , TransitionEvent , MyOtherState, MyAction, MyGuard>,
|
||||
Row<MyState , TransitionEvent , MyOtherState, MyAction, NotMyGuard>,
|
||||
Row<MyOtherState, TransitionEvent , MyState , none , none>
|
||||
>;
|
||||
using internal_transition_table = mp11::mp_list<
|
||||
Internal<SmInternalTransitionEvent, MyAction, none>
|
||||
>;
|
||||
};
|
||||
|
||||
struct MyConfig : back::state_machine_config
|
||||
{
|
||||
using observer = Logger;
|
||||
};
|
||||
|
||||
using MyStateMachineBe = back::state_machine<MyStateMachine, MyConfig>;
|
||||
|
||||
[[maybe_unused]] void logger_example()
|
||||
{
|
||||
MyStateMachineBe test_machine;
|
||||
|
||||
std::cout << "Starting state machine..." << std::endl;
|
||||
test_machine.start();
|
||||
|
||||
test_machine.process_event(TransitionEvent{});
|
||||
test_machine.process_event(InternalTransitionEvent{});
|
||||
test_machine.process_event(TransitionEvent{});
|
||||
test_machine.process_event(SmInternalTransitionEvent{});
|
||||
|
||||
std::cout << "Stopping state machine..." << std::endl;
|
||||
test_machine.stop();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,8 +16,8 @@ It offers a significant improvement in runtime and memory usage, as can be seen
|
||||
| back_favor_compile_time | 12 | 821 | 241 | 1.0
|
||||
| back11 | 26 | 2675 | 92 | 0.7
|
||||
| backmp11 | 2 | 236 | 18 | 0.2
|
||||
| backmp11_favor_compile_time | 2 | 225 | 44 | 2.2
|
||||
| sml | 3 | 242 | 57 | 0.1
|
||||
| backmp11_favor_compile_time | 2 | 226 | 44 | 2.2
|
||||
| sml | 4 | 254 | 64 | 0.1
|
||||
|=======================================================================================================
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ It offers a significant improvement in runtime and memory usage, as can be seen
|
||||
| | Compile time / sec | Compile RAM / MB | Binary size / kB | Runtime / sec
|
||||
| back | 32 | 2160 | 252 | 3.7
|
||||
| back_favor_compile_time | 37 | 1747 | 974 | 263
|
||||
| backmp11 | 5 | 360 | 55 | 0.9
|
||||
| backmp11_favor_compile_time | 3 | 263 | 96 | 7.5
|
||||
| sml | 12 | 567 | 436 | 2.5
|
||||
| backmp11 | 5 | 362 | 55 | 0.9
|
||||
| backmp11_favor_compile_time | 3 | 266 | 96 | 7.5
|
||||
| sml | 13 | 612 | 460 | 2.8
|
||||
|=======================================================================================================
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ struct default_state_machine_config
|
||||
using event_pool_container = std::deque<T>;
|
||||
// Type of the Fsm parameter passed in actions and guards.
|
||||
using fsm_parameter = local_transition_owner;
|
||||
// Sets up an observer for monitoring state machine activities.
|
||||
using observer = no_observer;
|
||||
// Identifies the upper‐most machine in hierarchical state machines.
|
||||
using root_sm = no_root_sm;
|
||||
};
|
||||
@@ -117,6 +119,26 @@ If a context is configured, a reference to an instance of it must be passed as t
|
||||
In hierarchical state machines the root state machine has to be set as well.
|
||||
|
||||
|
||||
=== Observer
|
||||
|
||||
The setting `observer` sets up an xref:reference:boost/msm/backmp11/default_observer.adoc[`observer member`] in the state machine for monitoring state machine activities.
|
||||
An observer is particularly useful for logging and capturing metrics. You can find a demo logger implementation in the xref:backmp11-back-end/examples.adoc#_logger[`Logger example`].
|
||||
|
||||
If `using observer = MyObserver;` is defined in the config, the following API becomes available to access it in the state machine:
|
||||
|
||||
```cpp
|
||||
MyObserver& state_machine::get_observer();
|
||||
```
|
||||
|
||||
This creates an observer instance whose lifetime is managed within the state machine. If your instance's lifetime is managed externally, wrap your observer into an `observer_ref`:
|
||||
|
||||
```cpp
|
||||
using observer = observer_ref<MyObserver>;
|
||||
```
|
||||
|
||||
If an observer ref is configured, a reference to an instance of it must be passed as the first argument (second argument if a context is also configured) to the `state_machine` constructor. In hierarchical state machines the root state machine has to be set as well.
|
||||
|
||||
|
||||
=== `Fsm` parameter of actions and guards
|
||||
|
||||
The setting `fsm_parameter` defines the instance of the `Fsm& fsm` parameter that is passed to actions and guards in hierarchical state machines.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
= Examples
|
||||
|
||||
== xref:attachment$backmp11/Logger.cpp[Logger]
|
||||
|
||||
This example contains an observer implementation for logging state machine activities.
|
||||
The implementation logs the start of event processing and every processed transition.
|
||||
|
||||
Running the example produces the following output:
|
||||
|
||||
```bash
|
||||
Starting state machine...
|
||||
MyStateMachine processed transition "none + starting -> MyState" (handled)
|
||||
MyStateMachine processing event TransitionEvent
|
||||
MyStateMachine processed transition "MyState + TransitionEvent [ NotMyGuard ] / MyAction -> MyOtherState" (rejected)
|
||||
MyStateMachine processed transition "MyState + TransitionEvent [ MyGuard ] / MyAction -> MyOtherState" (handled)
|
||||
MyStateMachine processing event TransitionEvent
|
||||
MyStateMachine processed transition "MyOtherState + TransitionEvent -> MyState" (handled)
|
||||
MyStateMachine processing event InternalTransitionEvent
|
||||
MyStateMachine processed transition "MyState + InternalTransitionEvent / MyAction" (handled)
|
||||
MyStateMachine processing event SmInternalTransitionEvent
|
||||
MyStateMachine processed transition "MyStateMachine + SmInternalTransitionEvent / MyAction" (handled)
|
||||
Stopping state machine...
|
||||
```
|
||||
@@ -5,6 +5,7 @@
|
||||
== Boost 1.92
|
||||
|
||||
* New features (`backmp11`):
|
||||
** Provide an observer API for logging support (https://github.com/boostorg/msm/issues/220[#220])
|
||||
** Forward Kleene events to actions and guards without conversion (https://github.com/boostorg/msm/issues/229[#229])
|
||||
** Ensure basic exception guarantee and propagate exceptions to the caller (https://github.com/boostorg/msm/issues/221[#221])
|
||||
** Provide a reflection API and serialization support for Boost.Serialization, Boost.JSON and nlohmann/json (https://github.com/boostorg/msm/issues/197[#197])
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
*** xref:back-back-end/examples.adoc[Examples]
|
||||
*** xref:back-back-end/performance-compilers.adoc[Performance / Compilers]
|
||||
** xref:backmp11-back-end.adoc[`backmp11` back-end (C++17, experimental)]
|
||||
*** xref:backmp11-back-end/examples.adoc[Examples]
|
||||
** xref:internals.adoc[Internals]
|
||||
** xref:acknowledgements.adoc[Acknowledgements]
|
||||
** xref:version-history.adoc[Version history]
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include <boost/msm/backmp11/state_machine.hpp>
|
||||
#include <boost/msm/backmp11/observer.hpp>
|
||||
|
||||
@@ -94,7 +94,7 @@ class deferred_event : public event_occurrence
|
||||
return std::nullopt;
|
||||
}
|
||||
mark_for_deletion();
|
||||
return sm.process_event_internal(m_event, process_info::event_pool);
|
||||
return sm.process_event_observed(m_event, process_info::event_pool);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -262,7 +262,6 @@ struct compile_policy_impl<
|
||||
struct forward_transition
|
||||
{
|
||||
using current_state_type = Submachine;
|
||||
using next_state_type = Submachine;
|
||||
using transition_event = Event;
|
||||
|
||||
static process_result process(StateMachine& sm,
|
||||
@@ -275,7 +274,7 @@ struct compile_policy_impl<
|
||||
process_info::submachine_call;
|
||||
process_result result =
|
||||
sm.template get_state<Submachine>()
|
||||
.process_event_internal(event, info);
|
||||
.process_event_observed(event, info);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,14 +113,6 @@ class context_member
|
||||
|
||||
context_member(Context& context) noexcept : m_context(&context) {}
|
||||
|
||||
context_member(const context_member&) noexcept = default;
|
||||
|
||||
context_member& operator=(const context_member&) noexcept = default;
|
||||
|
||||
context_member(context_member&&) noexcept = default;
|
||||
|
||||
context_member& operator=(context_member&&) noexcept = default;
|
||||
|
||||
private:
|
||||
template <typename, typename, typename>
|
||||
friend class backmp11::state_machine;
|
||||
@@ -151,16 +143,60 @@ class context_member<no_context, nesting_role::unknown>
|
||||
static constexpr bool has_context_member = false;
|
||||
};
|
||||
|
||||
template <typename Observer, nesting_role NestingRole>
|
||||
class observer_member
|
||||
{
|
||||
protected:
|
||||
static constexpr bool has_observer_member = true;
|
||||
|
||||
observer_member() = default;
|
||||
|
||||
template <typename T,
|
||||
typename = std::enable_if_t<std::is_constructible_v<Observer, T>>>
|
||||
observer_member(T&& arg) : m_observer(std::forward<T>(arg)) {}
|
||||
|
||||
private:
|
||||
template <typename, typename, typename>
|
||||
friend class backmp11::state_machine;
|
||||
template <typename, nesting_role>
|
||||
friend class state_machine_base;
|
||||
|
||||
Observer m_observer;
|
||||
};
|
||||
|
||||
template <typename Observer>
|
||||
class observer_member<Observer, nesting_role::nested>
|
||||
{
|
||||
protected:
|
||||
static constexpr bool has_observer_member = false;
|
||||
};
|
||||
|
||||
template <>
|
||||
class observer_member<no_observer, nesting_role::root>
|
||||
{
|
||||
protected:
|
||||
static constexpr bool has_observer_member = false;
|
||||
};
|
||||
|
||||
template <>
|
||||
class observer_member<no_observer, nesting_role::unknown>
|
||||
{
|
||||
protected:
|
||||
static constexpr bool has_observer_member = false;
|
||||
};
|
||||
|
||||
template <typename Config, nesting_role NestingRole>
|
||||
class state_machine_base
|
||||
: public event_pool_processor<Config::template event_pool_container,
|
||||
basic_polymorphic<event_occurrence>>,
|
||||
public context_member<typename Config::context, NestingRole>
|
||||
public context_member<typename Config::context, NestingRole>,
|
||||
public observer_member<typename Config::observer, NestingRole>
|
||||
{
|
||||
public:
|
||||
using config_t = Config;
|
||||
using root_sm_t = typename Config::root_sm;
|
||||
using context_t = typename Config::context;
|
||||
using observer_t = typename Config::observer;
|
||||
|
||||
/// Gets the context of the state machine.
|
||||
/// See @ref state_machine_config::context.
|
||||
@@ -187,6 +223,31 @@ class state_machine_base
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the observer of the state machine.
|
||||
/// See @ref state_machine_config::observer.
|
||||
template <bool C = !std::is_same_v<observer_t, no_observer>,
|
||||
typename = std::enable_if_t<C>>
|
||||
observer_t& get_observer()
|
||||
{
|
||||
return const_cast<observer_t&>(std::as_const(*this).get_observer());
|
||||
}
|
||||
|
||||
/// Gets the observer of the state machine.
|
||||
/// See @ref state_machine_config::observer.
|
||||
template <bool C = !std::is_same_v<observer_t, no_observer>,
|
||||
typename = std::enable_if_t<C>>
|
||||
const observer_t& get_observer() const
|
||||
{
|
||||
if constexpr (NestingRole == nesting_role::root)
|
||||
{
|
||||
return this->m_observer;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (*m_root_sm)->m_observer;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Processes up to `max_events` from the event pool.
|
||||
*
|
||||
@@ -231,9 +292,24 @@ class state_machine_base
|
||||
protected:
|
||||
using context_member =
|
||||
detail::context_member<typename Config::context, NestingRole>;
|
||||
using observer_member =
|
||||
detail::observer_member<typename Config::observer, NestingRole>;
|
||||
|
||||
using context_member::context_member;
|
||||
|
||||
using observer_member::observer_member;
|
||||
|
||||
state_machine_base() = default;
|
||||
|
||||
template <typename T, typename = std::enable_if_t<
|
||||
context_member::has_context_member &&
|
||||
observer_member::has_observer_member &&
|
||||
std::is_constructible_v<observer_t, T>>>
|
||||
state_machine_base(context_t& context, T&& arg)
|
||||
: context_member(context), observer_member(std::forward<T>(arg))
|
||||
{
|
||||
}
|
||||
|
||||
machine_state get_machine_state() const
|
||||
{
|
||||
return m_machine_state;
|
||||
|
||||
@@ -247,6 +247,33 @@ struct transition_table_impl
|
||||
static process_result process(StateMachine& sm,
|
||||
uint8_t region_id,
|
||||
const Event& event)
|
||||
{
|
||||
if constexpr (!std::is_same_v<typename StateMachine::observer_t,
|
||||
no_observer>)
|
||||
{
|
||||
sm.get_observer()
|
||||
.template pre_process_transition<
|
||||
typename Row::Source, typename Row::Evt,
|
||||
typename Row::Target, typename Row::Action,
|
||||
typename Row::Guard>(sm, region_id);
|
||||
}
|
||||
const auto result = process_impl(sm, region_id, event);
|
||||
if constexpr (!std::is_same_v<typename StateMachine::observer_t,
|
||||
no_observer>)
|
||||
{
|
||||
sm.get_observer()
|
||||
.template post_process_transition<
|
||||
typename Row::Source, typename Row::Evt,
|
||||
typename Row::Target, typename Row::Action,
|
||||
typename Row::Guard>(sm, region_id, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Event>
|
||||
static process_result process_impl(StateMachine& sm,
|
||||
uint8_t region_id,
|
||||
const Event& event)
|
||||
{
|
||||
auto& state_id = sm.m_active_state_ids[region_id];
|
||||
[[maybe_unused]] constexpr auto current_state_id =
|
||||
@@ -309,12 +336,38 @@ struct transition_table_impl
|
||||
{
|
||||
using transition_event = typename Row::Evt;
|
||||
using current_state_type = State;
|
||||
using next_state_type = current_state_type;
|
||||
|
||||
template <typename Event>
|
||||
static process_result process(StateMachine& sm,
|
||||
uint8_t region_id,
|
||||
const Event& event)
|
||||
{
|
||||
if constexpr (!std::is_same_v<typename StateMachine::observer_t,
|
||||
no_observer>)
|
||||
{
|
||||
sm.get_observer()
|
||||
.template pre_process_transition<
|
||||
typename Row::Source, typename Row::Evt,
|
||||
typename front::none, typename Row::Action,
|
||||
typename Row::Guard>(sm, region_id);
|
||||
}
|
||||
const auto result = process_impl(sm, region_id, event);
|
||||
if constexpr (!std::is_same_v<typename StateMachine::observer_t,
|
||||
no_observer>)
|
||||
{
|
||||
sm.get_observer()
|
||||
.template post_process_transition<
|
||||
typename Row::Source, typename Row::Evt,
|
||||
typename front::none, typename Row::Action,
|
||||
typename Row::Guard>(sm, region_id, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Event>
|
||||
static process_result process_impl(StateMachine& sm,
|
||||
uint8_t region_id,
|
||||
const Event& event)
|
||||
{
|
||||
[[maybe_unused]] const auto state_id = sm.m_active_state_ids[region_id];
|
||||
BOOST_ASSERT(
|
||||
@@ -342,6 +395,32 @@ struct transition_table_impl
|
||||
template <typename Event>
|
||||
static process_result process(StateMachine& sm,
|
||||
const Event& event)
|
||||
{
|
||||
if constexpr (!std::is_same_v<typename StateMachine::observer_t,
|
||||
no_observer>)
|
||||
{
|
||||
sm.get_observer()
|
||||
.template pre_process_transition<typename Row::Evt,
|
||||
typename Row::Action,
|
||||
typename Row::Guard>(
|
||||
sm);
|
||||
}
|
||||
const auto result = process_impl(sm, event);
|
||||
if constexpr (!std::is_same_v<typename StateMachine::observer_t,
|
||||
no_observer>)
|
||||
{
|
||||
sm.get_observer()
|
||||
.template post_process_transition<typename Row::Evt,
|
||||
typename Row::Action,
|
||||
typename Row::Guard>(
|
||||
sm, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Event>
|
||||
static process_result process_impl(StateMachine& sm,
|
||||
const Event& event)
|
||||
{
|
||||
auto& source = sm;
|
||||
auto& target = source;
|
||||
|
||||
@@ -392,7 +392,7 @@ struct compile_policy_impl<favor_compile_time>
|
||||
static process_result call_process_event(StateMachine& sm, const any_event& event)
|
||||
{
|
||||
return sm.template get_state<Submachine&>()
|
||||
.process_event_internal(event, process_info::submachine_call);
|
||||
.process_event_observed(event, process_info::submachine_call);
|
||||
}
|
||||
|
||||
std::unordered_map<std::type_index, transition_chain> m_transition_chains;
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
// 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)
|
||||
|
||||
#ifndef BOOST_MSM_BACKMP11_INTERCEPTOR_HPP
|
||||
#define BOOST_MSM_BACKMP11_INTERCEPTOR_HPP
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <boost/msm/backmp11/common_types.hpp>
|
||||
|
||||
namespace boost::msm::backmp11
|
||||
{
|
||||
|
||||
/// Default observer for state machine activities.
|
||||
/// Defines no-op hooks for all observable activities.
|
||||
class default_observer
|
||||
{
|
||||
public:
|
||||
using process_result = backmp11::process_result;
|
||||
|
||||
/// Hook called before a (sub-)machine processes an event.
|
||||
template <typename StateMachine, typename Event>
|
||||
constexpr void pre_process_event(const StateMachine&, const Event&) const
|
||||
{
|
||||
}
|
||||
|
||||
/// Hook called after a (sub-)machine processes an event.
|
||||
template <typename StateMachine, typename Event>
|
||||
constexpr void post_process_event(const StateMachine&, const Event&,
|
||||
process_result) const
|
||||
{
|
||||
}
|
||||
|
||||
/// Hook called before a (sub-)machine processes a transition.
|
||||
template <typename Source, typename Event, typename Target, typename Action,
|
||||
typename Guard, typename StateMachine>
|
||||
constexpr void pre_process_transition(const StateMachine&,
|
||||
size_t /*region_id*/) const
|
||||
{
|
||||
}
|
||||
|
||||
/// Hook called after a (sub-)machine processes a transition.
|
||||
template <typename Source, typename Event, typename Target, typename Action,
|
||||
typename Guard, typename StateMachine>
|
||||
constexpr void post_process_transition(const StateMachine&,
|
||||
size_t /*region_id*/,
|
||||
process_result) const
|
||||
{
|
||||
}
|
||||
|
||||
/// Hook called before a (sub-)machine processes a sm-internal transition.
|
||||
template <typename Event, typename Action, typename Guard,
|
||||
typename StateMachine>
|
||||
constexpr void pre_process_transition(const StateMachine&) const
|
||||
{
|
||||
}
|
||||
|
||||
/// Hook called after a (sub-)machine processes a sm-internal transition.
|
||||
template <typename Event, typename Action, typename Guard,
|
||||
typename StateMachine>
|
||||
constexpr void post_process_transition(const StateMachine&,
|
||||
process_result) const
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/// Wrapper for an observer reference whose lifetime is managed externally.
|
||||
template <typename Observer>
|
||||
class observer_ref
|
||||
{
|
||||
public:
|
||||
observer_ref(Observer& observer) noexcept : m_observer(&observer) {}
|
||||
|
||||
Observer& get() const noexcept
|
||||
{
|
||||
return *m_observer;
|
||||
}
|
||||
|
||||
operator Observer&() const noexcept
|
||||
{
|
||||
return *m_observer;
|
||||
}
|
||||
|
||||
/// @copydoc observer::pre_process_event
|
||||
template <typename StateMachine, typename Event>
|
||||
void pre_process_event(StateMachine& sm, const Event& event) const
|
||||
{
|
||||
m_observer->pre_process_event(sm, event);
|
||||
}
|
||||
|
||||
/// @copydoc observer::post_process_event
|
||||
template <typename StateMachine, typename Event>
|
||||
void post_process_event(StateMachine& sm, const Event& event,
|
||||
process_result result) const
|
||||
{
|
||||
m_observer->post_process_event(sm, event, result);
|
||||
}
|
||||
|
||||
/// @copydoc observer::pre_process_transition
|
||||
template <typename Source, typename Event, typename Target, typename Action,
|
||||
typename Guard, typename StateMachine>
|
||||
void pre_process_transition(StateMachine& sm,
|
||||
size_t region_id) const
|
||||
{
|
||||
m_observer->template pre_process_transition<Source, Event, Target,
|
||||
Action, Guard>(sm,
|
||||
region_id);
|
||||
}
|
||||
|
||||
/// @copydoc observer::post_process_transition
|
||||
template <typename Source, typename Event, typename Target, typename Action,
|
||||
typename Guard, typename StateMachine>
|
||||
void post_process_transition(StateMachine& sm, size_t region_id,
|
||||
process_result result) const
|
||||
{
|
||||
m_observer->template post_process_transition<Source, Event, Target,
|
||||
Action, Guard>(
|
||||
sm, region_id, result);
|
||||
}
|
||||
|
||||
/// @copydoc observer::pre_process_transition
|
||||
template <typename Event, typename Action, typename Guard,
|
||||
typename StateMachine>
|
||||
void pre_process_transition(StateMachine& sm) const
|
||||
{
|
||||
m_observer->template pre_process_transition<Event, Action, Guard>(sm);
|
||||
}
|
||||
|
||||
/// @copydoc observer::post_process_transition
|
||||
template <typename Event, typename Action, typename Guard,
|
||||
typename StateMachine>
|
||||
void post_process_transition(StateMachine& sm,
|
||||
process_result result) const
|
||||
{
|
||||
m_observer->template post_process_transition<Event, Action, Guard>(
|
||||
sm, result);
|
||||
}
|
||||
|
||||
private:
|
||||
Observer* m_observer;
|
||||
};
|
||||
|
||||
} // namespace boost::msm::backmp11
|
||||
|
||||
#endif // BOOST_MSM_BACKMP11_INTERCEPTOR_HPP
|
||||
@@ -86,6 +86,8 @@ class state_machine
|
||||
using config_t = typename state_machine_base::config_t;
|
||||
/// Type of the context (see @ref state_machine_config::context).
|
||||
using context_t = typename state_machine_base::context_t;
|
||||
/// Type of the context (see @ref state_machine_config::observer).
|
||||
using observer_t = typename state_machine_base::observer_t;
|
||||
/// Type of the root machine (see @ref state_machine_config::root_sm).
|
||||
using root_sm_t = typename state_machine_base::root_sm_t;
|
||||
/// Type of the derived machine (corresponds to Derived).
|
||||
@@ -247,6 +249,7 @@ class state_machine
|
||||
*
|
||||
* Requires constructor arguments as configured:
|
||||
* - Context& (if context = Context is set)
|
||||
* - Observer& (if observer = observer_ref<Observer> is set)
|
||||
*/
|
||||
template <typename... Args>
|
||||
state_machine(Args&&... args)
|
||||
@@ -264,10 +267,8 @@ class state_machine
|
||||
}
|
||||
|
||||
state_machine(state_machine& rhs)
|
||||
: state_machine_base(rhs), m_states(rhs.m_states),
|
||||
m_active_state_ids(rhs.m_active_state_ids)
|
||||
: state_machine(static_cast<state_machine const&>(rhs))
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
// Copy assignment operator.
|
||||
@@ -343,7 +344,7 @@ class state_machine
|
||||
template<class Event>
|
||||
process_result process_event(Event const& event)
|
||||
{
|
||||
return process_event_internal(
|
||||
return process_event_observed(
|
||||
compile_policy_impl::normalize_event(event),
|
||||
detail::process_info::direct_call);
|
||||
}
|
||||
@@ -527,7 +528,23 @@ class state_machine
|
||||
private:
|
||||
// Main function used internally to process events.
|
||||
template <class Event>
|
||||
process_result process_event_internal(Event const& event, detail::process_info info)
|
||||
process_result process_event_observed(Event const& event,
|
||||
detail::process_info info)
|
||||
{
|
||||
if constexpr (!std::is_same_v<observer_t, no_observer>)
|
||||
{
|
||||
this->get_observer().pre_process_event(self(), event);
|
||||
}
|
||||
const auto result = process_event_impl(event, info);
|
||||
if constexpr (!std::is_same_v<observer_t, no_observer>)
|
||||
{
|
||||
this->get_observer().post_process_event(self(), event, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class Event>
|
||||
process_result process_event_impl(Event const& event, detail::process_info info)
|
||||
{
|
||||
if (this->m_machine_state != detail::machine_state::idle)
|
||||
{
|
||||
@@ -726,8 +743,22 @@ class state_machine
|
||||
template <typename State>
|
||||
void operator()(State& state)
|
||||
{
|
||||
if constexpr (!std::is_same_v<observer_t, no_observer>)
|
||||
{
|
||||
m_self.get_observer()
|
||||
.template pre_process_transition<front::none, Event, State,
|
||||
front::none, front::none>(
|
||||
m_self, m_region_id);
|
||||
}
|
||||
state.on_entry(m_event, m_self.get_fsm_argument());
|
||||
m_self.template on_state_entry_completed<State>(m_region_id++);
|
||||
if constexpr (!std::is_same_v<observer_t, no_observer>)
|
||||
{
|
||||
m_self.get_observer()
|
||||
.template post_process_transition<front::none, Event, State,
|
||||
front::none, front::none>(
|
||||
m_self, m_region_id, process_result::HANDLED_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -91,6 +91,9 @@ struct local_transition_owner {};
|
||||
template <typename T>
|
||||
struct no_event_pool_container {};
|
||||
|
||||
/// No observer is configured (see @ref state_machine_config::observer).
|
||||
struct no_observer {};
|
||||
|
||||
/// Default state machine configuration.
|
||||
struct default_state_machine_config
|
||||
{
|
||||
@@ -119,6 +122,8 @@ struct default_state_machine_config
|
||||
/// Type of the Fsm parameter passed in actions and guards.
|
||||
/// Defaults to @ref local_transition_owner.
|
||||
using fsm_parameter = local_transition_owner;
|
||||
/// Sets up an observer for monitoring state machine activities.
|
||||
using observer = no_observer;
|
||||
/// Identifies the upper-most machine in hierarchical state machines.
|
||||
/// Defaults to @ref no_root_sm.
|
||||
using root_sm = no_root_sm;
|
||||
|
||||
@@ -9,14 +9,16 @@
|
||||
// file LICENSE_1_0.txt or copy at
|
||||
// http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
// back-end
|
||||
#include "BackCommon.hpp"
|
||||
//front-end
|
||||
#include "FrontCommon.hpp"
|
||||
#ifndef BOOST_MSM_NONSTANDALONE_TEST
|
||||
#define BOOST_TEST_MODULE test_constructor
|
||||
#endif
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
// back-end
|
||||
#include "BackCommon.hpp"
|
||||
#include <boost/msm/backmp11/observer.hpp>
|
||||
//front-end
|
||||
#include "FrontCommon.hpp"
|
||||
#include <boost/config.hpp>
|
||||
|
||||
namespace msm = boost::msm;
|
||||
@@ -33,12 +35,6 @@ struct MyState : test::StateBase {};
|
||||
struct StateMachine_ : test::StateMachineBase_<StateMachine_>
|
||||
{
|
||||
using initial_state = MyState;
|
||||
|
||||
StateMachine_() = default;
|
||||
|
||||
StateMachine_(int n) : number(n) {}
|
||||
|
||||
int number{};
|
||||
};
|
||||
|
||||
struct Context
|
||||
@@ -51,6 +47,19 @@ struct context_config : state_machine_config
|
||||
using context = Context;
|
||||
};
|
||||
|
||||
using Observer = msm::backmp11::default_observer;
|
||||
|
||||
struct observer_config : state_machine_config
|
||||
{
|
||||
using observer = Observer;
|
||||
};
|
||||
|
||||
struct co_config : context_config
|
||||
{
|
||||
using observer = Observer;
|
||||
};
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(context_constructors)
|
||||
{
|
||||
using StateMachine = state_machine<StateMachine_, context_config>;
|
||||
@@ -59,4 +68,21 @@ BOOST_AUTO_TEST_CASE(context_constructors)
|
||||
StateMachine sm{context};
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(observer_constructors)
|
||||
{
|
||||
using StateMachine = state_machine<StateMachine_, observer_config>;
|
||||
Observer observer;
|
||||
|
||||
StateMachine sm{observer};
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(co_constructors)
|
||||
{
|
||||
using StateMachine = state_machine<StateMachine_, co_config>;
|
||||
Context context;
|
||||
Observer observer;
|
||||
|
||||
StateMachine sm{context, observer};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
// back-end
|
||||
#include "Backmp11.hpp"
|
||||
#include <boost/msm/backmp11/observer.hpp>
|
||||
//front-end
|
||||
#include "FrontCommon.hpp"
|
||||
|
||||
@@ -66,9 +67,11 @@ struct MyState : public test::StateBase
|
||||
|
||||
struct Context
|
||||
{
|
||||
int foo{};
|
||||
int foo{42};
|
||||
};
|
||||
|
||||
using Observer = msm::backmp11::default_observer;
|
||||
|
||||
template <typename Config = default_state_machine_config>
|
||||
struct hierarchical_state_machine
|
||||
{
|
||||
@@ -81,6 +84,7 @@ struct ConfigWithRootSm : Config
|
||||
{
|
||||
using root_sm = StateMachine;
|
||||
using context = Context;
|
||||
using observer = msm::backmp11::observer_ref<Observer>;
|
||||
};
|
||||
|
||||
struct Submachine_ : public test::StateMachineBase_<Submachine_>
|
||||
@@ -108,7 +112,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(copy_operators, StateMachine, TestMachines)
|
||||
{
|
||||
using TestMachine = typename StateMachine::StateMachine;
|
||||
Context context;
|
||||
TestMachine test_machine{context};
|
||||
Observer observer;
|
||||
TestMachine test_machine{context, observer};
|
||||
|
||||
test_machine.start();
|
||||
|
||||
@@ -117,6 +122,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(copy_operators, StateMachine, TestMachines)
|
||||
|
||||
{
|
||||
TestMachine other_test_machine{test_machine};
|
||||
BOOST_REQUIRE(other_test_machine.get_context().foo == 42);
|
||||
auto& other_submachine = other_test_machine.template get_state<typename StateMachine::Submachine>();
|
||||
BOOST_REQUIRE(other_test_machine.template is_state_active<typename StateMachine::Submachine>());
|
||||
BOOST_REQUIRE(&test_machine.get_root_sm() != &other_test_machine.get_root_sm());
|
||||
@@ -126,8 +132,9 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(copy_operators, StateMachine, TestMachines)
|
||||
}
|
||||
|
||||
{
|
||||
TestMachine other_test_machine{context};
|
||||
TestMachine other_test_machine{context, observer};
|
||||
other_test_machine = test_machine;
|
||||
BOOST_REQUIRE(other_test_machine.get_context().foo == 42);
|
||||
auto& other_submachine = other_test_machine.template get_state<typename StateMachine::Submachine>();
|
||||
BOOST_REQUIRE(other_test_machine.template is_state_active<typename StateMachine::Submachine>());
|
||||
BOOST_REQUIRE(&test_machine.get_root_sm() != &other_test_machine.get_root_sm());
|
||||
@@ -143,7 +150,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(move_operators, StateMachine, TestMachines)
|
||||
{
|
||||
using TestMachine = typename StateMachine::StateMachine;
|
||||
Context context;
|
||||
TestMachine test_machine{context};
|
||||
Observer observer;
|
||||
TestMachine test_machine{context, observer};
|
||||
|
||||
test_machine.start();
|
||||
|
||||
@@ -152,6 +160,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(move_operators, StateMachine, TestMachines)
|
||||
|
||||
{
|
||||
TestMachine other_test_machine{std::move(test_machine)};
|
||||
BOOST_REQUIRE(other_test_machine.get_context().foo == 42);
|
||||
auto& other_submachine = other_test_machine.template get_state<typename StateMachine::Submachine>();
|
||||
BOOST_REQUIRE(other_test_machine.template is_state_active<typename StateMachine::Submachine>());
|
||||
BOOST_REQUIRE(&test_machine.get_root_sm() != &other_test_machine.get_root_sm());
|
||||
@@ -161,8 +170,9 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(move_operators, StateMachine, TestMachines)
|
||||
}
|
||||
|
||||
{
|
||||
TestMachine other_test_machine{context};
|
||||
TestMachine other_test_machine{context, observer};
|
||||
other_test_machine = std::move(test_machine);
|
||||
BOOST_REQUIRE(other_test_machine.get_context().foo == 42);
|
||||
auto& other_submachine = other_test_machine.template get_state<typename StateMachine::Submachine>();
|
||||
BOOST_REQUIRE(other_test_machine.template is_state_active<typename StateMachine::Submachine>());
|
||||
BOOST_REQUIRE(&test_machine.get_root_sm() != &other_test_machine.get_root_sm());
|
||||
@@ -178,14 +188,15 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(copy_event_pool, StateMachine, TestMachines)
|
||||
{
|
||||
using TestMachine = typename StateMachine::StateMachine;
|
||||
Context context;
|
||||
TestMachine test_machine{context};
|
||||
Observer observer;
|
||||
TestMachine test_machine{context, observer};
|
||||
|
||||
test_machine.start();
|
||||
|
||||
test_machine.enqueue_event(EnterSubmachine{});
|
||||
|
||||
{
|
||||
TestMachine other_test_machine{context};
|
||||
TestMachine other_test_machine{context, observer};
|
||||
other_test_machine = test_machine;
|
||||
BOOST_REQUIRE(other_test_machine.process_event_pool() == 1);
|
||||
BOOST_REQUIRE(other_test_machine.template is_state_active<typename StateMachine::Submachine>());
|
||||
@@ -201,7 +212,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(move_event_pool, StateMachine, TestMachines)
|
||||
{
|
||||
using TestMachine = typename StateMachine::StateMachine;
|
||||
Context context;
|
||||
TestMachine test_machine{context};
|
||||
Observer observer;
|
||||
TestMachine test_machine{context, observer};
|
||||
|
||||
test_machine.start();
|
||||
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
// 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)
|
||||
|
||||
#ifndef BOOST_MSM_NONSTANDALONE_TEST
|
||||
#define BOOST_TEST_MODULE backmp11_observer_test
|
||||
#endif
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <boost/msm/backmp11/state_machine.hpp>
|
||||
#include <boost/msm/backmp11/favor_compile_time.hpp>
|
||||
|
||||
#include "FrontCommon.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include "attachments/backmp11/Logger.cpp"
|
||||
|
||||
namespace msm = boost::msm;
|
||||
namespace mp11 = boost::mp11;
|
||||
|
||||
using namespace msm::front;
|
||||
using namespace msm::backmp11;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class Observer : public Logger
|
||||
{
|
||||
public:
|
||||
template <typename StateMachine, typename Event>
|
||||
void pre_process_event(const StateMachine& sm, const Event& event)
|
||||
{
|
||||
Logger::pre_process_event(sm, event);
|
||||
pre_process_event_counter++;
|
||||
}
|
||||
|
||||
template <typename StateMachine, typename Event>
|
||||
void post_process_event(const StateMachine&, const Event&, process_result /*result*/)
|
||||
{
|
||||
post_process_event_counter++;
|
||||
}
|
||||
|
||||
template <typename Source, typename Event, typename Target, typename Action,
|
||||
typename Guard, typename StateMachine>
|
||||
void pre_process_transition(const StateMachine&, size_t /*region_id*/)
|
||||
{
|
||||
pre_process_transition_counter++;
|
||||
}
|
||||
|
||||
template <typename Source, typename Event, typename Target, typename Action,
|
||||
typename Guard, typename StateMachine>
|
||||
void post_process_transition(const StateMachine& sm, size_t region_id,
|
||||
process_result result)
|
||||
{
|
||||
Logger::post_process_transition<Source, Event, Target, Action, Guard>(
|
||||
sm, region_id, result);
|
||||
post_process_transition_counter++;
|
||||
}
|
||||
|
||||
template <typename Event, typename Action, typename Guard,
|
||||
typename StateMachine>
|
||||
void pre_process_transition(const StateMachine&)
|
||||
{
|
||||
pre_process_transition_counter++;
|
||||
}
|
||||
|
||||
template <typename Event, typename Action, typename Guard,
|
||||
typename StateMachine>
|
||||
void post_process_transition(const StateMachine& sm, process_result result)
|
||||
{
|
||||
Logger::post_process_transition<Event, Action, Guard>(sm, result);
|
||||
post_process_transition_counter++;
|
||||
}
|
||||
|
||||
void log(std::string_view msg) override
|
||||
{
|
||||
messages.push_back(std::string{msg});
|
||||
}
|
||||
|
||||
void assert_and_pop_msg(std::string_view msg)
|
||||
{
|
||||
BOOST_REQUIRE(messages.front() == msg);
|
||||
messages.pop_front();
|
||||
}
|
||||
|
||||
std::deque<std::string> messages;
|
||||
size_t pre_start_counter{};
|
||||
size_t post_start_counter{};
|
||||
size_t pre_stop_counter{};
|
||||
size_t post_stop_counter{};
|
||||
size_t pre_process_event_counter{};
|
||||
size_t post_process_event_counter{};
|
||||
size_t pre_process_transition_counter{};
|
||||
size_t post_process_transition_counter{};
|
||||
};
|
||||
|
||||
namespace observer_test
|
||||
{
|
||||
|
||||
struct default_config : state_machine_config
|
||||
{
|
||||
using observer = Observer;
|
||||
};
|
||||
struct favor_compile_time_config : default_config
|
||||
{
|
||||
using compile_policy = favor_compile_time;
|
||||
};
|
||||
|
||||
template <typename Config = default_config>
|
||||
using SmWithObserver = state_machine<MyStateMachine, Config>;
|
||||
|
||||
using TestMachines = mp11::mp_list<
|
||||
SmWithObserver<>,
|
||||
SmWithObserver<favor_compile_time_config>
|
||||
>;
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE_TEMPLATE(observer, StateMachine, TestMachines)
|
||||
{
|
||||
StateMachine test_machine;
|
||||
Observer& observer = test_machine.get_observer();
|
||||
|
||||
test_machine.start();
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"none + starting -> MyState\" (handled)");
|
||||
ASSERT_AND_RESET(observer.pre_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_transition_counter, 1);
|
||||
|
||||
test_machine.process_event(TransitionEvent{});
|
||||
observer.assert_and_pop_msg("MyStateMachine processing event TransitionEvent");
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"MyState + TransitionEvent [ NotMyGuard ] / MyAction -> MyOtherState\" (rejected)");
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"MyState + TransitionEvent [ MyGuard ] / MyAction -> MyOtherState\" (handled)");
|
||||
ASSERT_AND_RESET(observer.pre_process_event_counter, 1);
|
||||
ASSERT_AND_RESET(observer.pre_process_transition_counter, 2);
|
||||
ASSERT_AND_RESET(observer.post_process_transition_counter, 2);
|
||||
ASSERT_AND_RESET(observer.post_process_event_counter, 1);
|
||||
|
||||
test_machine.process_event(TransitionEvent{});
|
||||
observer.assert_and_pop_msg("MyStateMachine processing event TransitionEvent");
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"MyOtherState + TransitionEvent -> MyState\" (handled)");
|
||||
ASSERT_AND_RESET(observer.pre_process_event_counter, 1);
|
||||
ASSERT_AND_RESET(observer.pre_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_event_counter, 1);
|
||||
|
||||
test_machine.process_event(InternalTransitionEvent{});
|
||||
observer.assert_and_pop_msg("MyStateMachine processing event InternalTransitionEvent");
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"MyState + InternalTransitionEvent / MyAction\" (handled)");
|
||||
ASSERT_AND_RESET(observer.pre_process_event_counter, 1);
|
||||
ASSERT_AND_RESET(observer.pre_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_event_counter, 1);
|
||||
|
||||
test_machine.process_event(SmInternalTransitionEvent{});
|
||||
observer.assert_and_pop_msg("MyStateMachine processing event SmInternalTransitionEvent");
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"MyStateMachine + SmInternalTransitionEvent / MyAction\" (handled)");
|
||||
ASSERT_AND_RESET(observer.pre_process_event_counter, 1);
|
||||
ASSERT_AND_RESET(observer.pre_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_event_counter, 1);
|
||||
|
||||
test_machine.stop();
|
||||
}
|
||||
|
||||
} // namespace observer
|
||||
|
||||
namespace observer_ref_test
|
||||
{
|
||||
|
||||
struct default_config : state_machine_config
|
||||
{
|
||||
using observer = observer_ref<Observer>;
|
||||
};
|
||||
struct favor_compile_time_config : default_config
|
||||
{
|
||||
using compile_policy = favor_compile_time;
|
||||
};
|
||||
|
||||
template <typename Config = default_config>
|
||||
using SmWithObserver = state_machine<MyStateMachine, Config>;
|
||||
|
||||
using TestMachines = mp11::mp_list<
|
||||
SmWithObserver<>,
|
||||
SmWithObserver<favor_compile_time_config>
|
||||
>;
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE_TEMPLATE(observer_ref, StateMachine, TestMachines)
|
||||
{
|
||||
Observer observer;
|
||||
StateMachine test_machine{observer};
|
||||
|
||||
test_machine.start();
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"none + starting -> MyState\" (handled)");
|
||||
ASSERT_AND_RESET(observer.pre_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_transition_counter, 1);
|
||||
|
||||
test_machine.process_event(TransitionEvent{});
|
||||
observer.assert_and_pop_msg("MyStateMachine processing event TransitionEvent");
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"MyState + TransitionEvent [ NotMyGuard ] / MyAction -> MyOtherState\" (rejected)");
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"MyState + TransitionEvent [ MyGuard ] / MyAction -> MyOtherState\" (handled)");
|
||||
ASSERT_AND_RESET(observer.pre_process_event_counter, 1);
|
||||
ASSERT_AND_RESET(observer.pre_process_transition_counter, 2);
|
||||
ASSERT_AND_RESET(observer.post_process_transition_counter, 2);
|
||||
ASSERT_AND_RESET(observer.post_process_event_counter, 1);
|
||||
|
||||
test_machine.process_event(TransitionEvent{});
|
||||
observer.assert_and_pop_msg("MyStateMachine processing event TransitionEvent");
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"MyOtherState + TransitionEvent -> MyState\" (handled)");
|
||||
ASSERT_AND_RESET(observer.pre_process_event_counter, 1);
|
||||
ASSERT_AND_RESET(observer.pre_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_event_counter, 1);
|
||||
|
||||
test_machine.process_event(InternalTransitionEvent{});
|
||||
observer.assert_and_pop_msg("MyStateMachine processing event InternalTransitionEvent");
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"MyState + InternalTransitionEvent / MyAction\" (handled)");
|
||||
ASSERT_AND_RESET(observer.pre_process_event_counter, 1);
|
||||
ASSERT_AND_RESET(observer.pre_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_event_counter, 1);
|
||||
|
||||
test_machine.process_event(SmInternalTransitionEvent{});
|
||||
observer.assert_and_pop_msg("MyStateMachine processing event SmInternalTransitionEvent");
|
||||
observer.assert_and_pop_msg("MyStateMachine processed transition \"MyStateMachine + SmInternalTransitionEvent / MyAction\" (handled)");
|
||||
ASSERT_AND_RESET(observer.pre_process_event_counter, 1);
|
||||
ASSERT_AND_RESET(observer.pre_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_transition_counter, 1);
|
||||
ASSERT_AND_RESET(observer.post_process_event_counter, 1);
|
||||
|
||||
test_machine.stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -120,7 +120,7 @@ struct DimSwitch_ : front::state_machine_def<DimSwitch_>
|
||||
visitor.visit_member("brightness", brightness);
|
||||
}
|
||||
|
||||
uint8_t brightness;
|
||||
uint8_t brightness{};
|
||||
};
|
||||
|
||||
using DimSwitch = backmp11::state_machine<DimSwitch_>;
|
||||
|
||||
+2
-1
@@ -7,7 +7,7 @@ if(NOT TARGET tests)
|
||||
endif()
|
||||
|
||||
link_libraries(Boost::msm Boost::unit_test_framework)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../doc/modules/ROOT)
|
||||
add_compile_definitions("BOOST_MSM_NONSTANDALONE_TEST")
|
||||
if(BOOST_MSM_TEST_ONLY_BACKMP11)
|
||||
add_compile_definitions("BOOST_MSM_TEST_ONLY_BACKMP11")
|
||||
@@ -88,6 +88,7 @@ add_executable(boost_msm_cxx17_tests
|
||||
Backmp11FunctorApi.cpp
|
||||
Backmp11History.cpp
|
||||
Backmp11ManyDeferTransitions.cpp
|
||||
Backmp11Observer.cpp
|
||||
Backmp11RootSm.cpp
|
||||
Backmp11Serialization.cpp
|
||||
Backmp11Transitions.cpp
|
||||
|
||||
@@ -19,6 +19,7 @@ project
|
||||
requirements
|
||||
<library>/boost/msm//boost_msm
|
||||
<include>.
|
||||
<include>../doc/modules/ROOT
|
||||
<toolset>gcc:<cxxflags>"-ftemplate-depth-300 -g0"
|
||||
<toolset>darwin:<cxxflags>"-ftemplate-depth-300 -g0"
|
||||
<toolset>intel:<cxxflags>"-g0"
|
||||
@@ -87,6 +88,7 @@ test-suite msm-unit-tests-cxxstd17
|
||||
[ run Backmp11FunctorApi.cpp ]
|
||||
[ run Backmp11History.cpp ]
|
||||
[ run Backmp11ManyDeferTransitions.cpp ]
|
||||
[ run Backmp11Observer.cpp ]
|
||||
[ run Backmp11RootSm.cpp ]
|
||||
[ run Backmp11Serialization.cpp : : : <define>BOOST_MSM_TEST_NLOHMANN_JSON ]
|
||||
[ run Backmp11Transitions.cpp ]
|
||||
|
||||
Reference in New Issue
Block a user