2
0
mirror of https://github.com/boostorg/msm.git synced 2026-01-28 19:32:09 +00:00

refactor(backmp11): entry/exit logic

This commit is contained in:
Christian Granzin
2026-01-27 16:02:27 -05:00
committed by Christian Granzin
parent 513d5d9cf6
commit 108de8bb85
6 changed files with 264 additions and 228 deletions

View File

@@ -34,8 +34,8 @@ It offers a significant reduction in compilation time and RAM usage, as can be s
| | Compile / sec | RAM / MB | Runtime / sec
| back | 68 | 2849 | 23
| back_favor_compile_time | 80 | 2551 | 261
| backmp11 | 8 | 357 | 11
| backmp11_favor_compile_time | 6 | 274 | 26
| backmp11 | 8 | 350 | 11
| backmp11_favor_compile_time | 6 | 266 | 26
| backmp11_favor_compile_time_multi_cu | 5 | ~915 | 26
| sml | 40 | 1056 | 11
|================================================================================

View File

@@ -2,10 +2,14 @@
= Version history
== Boost 1.91
* feat(backmp11): Further optimized compilation, with up to 25% faster compile times and less RAM consumption than the 1.90 version
== Boost 1.90
* feat/backmp11: New back-end backmp11, requires C++ 17
* fix/back: boost::any stopped working as Kleene event (https://github.com/boostorg/msm/issues/87[#87])
* feat(backmp11): New back-end backmp11, requires C++ 17
* fix(back): boost::any stopped working as Kleene event (https://github.com/boostorg/msm/issues/87[#87])
* feat: The documentation has been refurbished, it is now built with Antora
[[boost-185]]

View File

@@ -347,19 +347,6 @@ struct has_deferred_event_impl<mp11::mp_list<States...>, Event>
template <typename StateOrStates, typename Event>
using has_deferred_event = typename has_deferred_event_impl<StateOrStates, Event>::type;
// event used internally for wrapping a direct entry
template <class State, class Event>
struct direct_entry_event
{
public:
typedef int direct_entry;
typedef State active_state;
typedef Event contained_event;
direct_entry_event(Event const& event):m_event(event){}
Event const& m_event;
};
// Builds flags from flag_list + internal_flag_list,
// internal_flag_list is used for terminate/interrupt states.
template <typename State>

View File

@@ -33,24 +33,31 @@ struct transition_table_impl
boost::mp11::mp_eval_or<active_state_switch_after_entry,
get_active_state_switch_policy, front_end_t>;
template<typename Row, bool HasGuard, typename Event, typename Source, typename Target>
static bool call_guard_or_true(StateMachine& sm, const Event& event, Source& source, Target& target)
template <typename Row, bool HasGuard, typename Event, typename Source,
typename Target>
static bool call_guard_or_true(StateMachine& sm, const Event& event,
Source& source, Target& target)
{
if constexpr (HasGuard)
{
return Row::guard_call(sm.get_fsm_argument(), event, source, target, sm.m_states);
return Row::guard_call(
sm.get_fsm_argument(), event, source, target, sm.m_states);
}
else
{
return true;
}
}
template<typename Row, bool HasAction, typename Event, typename Source, typename Target>
static process_result call_action_or_true(StateMachine& sm, const Event& event, Source& source, Target& target)
template <typename Row, bool HasAction, typename Event, typename Source,
typename Target>
static process_result call_action_or_true(StateMachine& sm,
const Event& event,
Source& source, Target& target)
{
if constexpr (HasAction)
{
return Row::action_call(sm.get_fsm_argument(), event, source, target, sm.m_states);
return Row::action_call(
sm.get_fsm_argument(), event, source, target, sm.m_states);
}
else
{
@@ -58,21 +65,64 @@ struct transition_table_impl
}
}
template <typename DirectWrapper>
using get_state = typename DirectWrapper::state;
template <typename FeTarget>
using is_explicit_entry_point = mp11::mp_eval_if<
mpl::is_sequence<FeTarget>,
mp11::mp_true,
has_explicit_entry_be_tag,
FeTarget>;
template <typename Row, typename Event, typename Target>
static void call_entry(StateMachine& sm, const Event& event, Target& target)
{
using FeTarget = typename Row::Target;
auto& fsm = sm.get_fsm_argument();
if constexpr (is_explicit_entry_point<FeTarget>::value)
{
using targets = to_mp_list_t<FeTarget>;
using states = mp11::mp_transform<get_state, targets>;
target.template on_explicit_entry<states>(event, fsm);
}
else if constexpr (has_entry_pseudostate_be_tag<FeTarget>::value)
{
using targets = to_mp_list_t<FeTarget>;
using states = mp11::mp_transform<get_state, targets>;
target.template on_pseudo_entry<states>(event, fsm);
}
else
{
target.on_entry(event, fsm);
if constexpr (has_exit_pseudostate_be_tag<Target>::value)
{
// Execute the second part of the compound transition.
target.forward_event(event);
}
}
}
// Template used to create transitions from rows in the transition table.
template<typename Row, bool HasAction, bool HasGuard>
template <typename Row, bool HasAction, bool HasGuard>
struct transition
{
using transition_event = typename Row::Evt;
using current_state_type = convert_source_state<derived_t, typename Row::Source>;
using next_state_type = convert_target_state<derived_t, typename Row::Target>;
using current_state_type =
convert_source_state<derived_t, typename Row::Source>;
using next_state_type =
convert_target_state<derived_t, typename Row::Target>;
// Take the transition action and return the next state.
static process_result execute(StateMachine& sm,
int& state_id,
transition_event const& event)
{
static constexpr int current_state_id = StateMachine::template get_state_id<current_state_type>();
static constexpr int next_state_id = StateMachine::template get_state_id<next_state_type>();
static constexpr int current_state_id =
StateMachine::template get_state_id<current_state_type>();
static constexpr int next_state_id =
StateMachine::template get_state_id<next_state_type>();
BOOST_ASSERT(state_id == current_state_id);
auto& source = sm.template get_state<current_state_type>();
@@ -83,26 +133,33 @@ struct transition_table_impl
// guard rejected the event, we stay in the current one
return process_result::HANDLED_GUARD_REJECT;
}
state_id = active_state_switching::after_guard(current_state_id,next_state_id);
state_id = active_state_switching::after_guard(current_state_id,
next_state_id);
// first call the exit method of the current state
source.on_exit(event, sm.get_fsm_argument());
state_id = active_state_switching::after_exit(current_state_id,next_state_id);
state_id = active_state_switching::after_exit(current_state_id,
next_state_id);
// then call the action method
process_result res = call_action_or_true<Row, HasAction>(sm, event, source, target);
state_id = active_state_switching::after_action(current_state_id,next_state_id);
process_result res =
call_action_or_true<Row, HasAction>(sm, event, source, target);
state_id = active_state_switching::after_action(current_state_id,
next_state_id);
// and finally the entry method of the new current state
StateMachine::template convert_event_and_execute_entry<typename Row::Target>(target,event,sm);
state_id = active_state_switching::after_entry(current_state_id,next_state_id);
// and finally the entry method of the new state
call_entry<Row>(sm, event, target);
state_id = active_state_switching::after_entry(current_state_id,
next_state_id);
return res;
}
};
// Template used to create internal transitions from rows in the transition table.
template<typename Row, bool HasAction, bool HasGuard, typename State = typename Row::Source>
// Template used to create internal transitions
// from rows in the transition table.
template <typename Row, bool HasAction, bool HasGuard,
typename State = typename Row::Source>
struct internal_transition
{
using transition_event = typename Row::Evt;
@@ -114,8 +171,10 @@ struct transition_table_impl
[[maybe_unused]] int& state_id,
transition_event const& event)
{
BOOST_ASSERT(state_id == StateMachine::template get_state_id<current_state_type>());
BOOST_ASSERT(
state_id ==
StateMachine::template get_state_id<current_state_type>());
auto& source = sm.template get_state<current_state_type>();
auto& target = source;
@@ -127,7 +186,8 @@ struct transition_table_impl
}
};
// Template used to create sm-internal transitions from rows in the transition table.
// Template used to create sm-internal transitions
// from rows in the transition table.
template<typename Row, bool HasAction, bool HasGuard>
struct internal_transition<Row, HasAction, HasGuard, StateMachine>
{
@@ -250,9 +310,11 @@ struct transition_table_impl
append_internal_transition_table>>;
};
template <typename StateMachine>
using transition_table = typename transition_table_impl<StateMachine>::transition_table;
using transition_table =
typename transition_table_impl<StateMachine>::transition_table;
template <typename StateMachine>
using internal_transition_table = typename transition_table_impl<StateMachine>::internal_transition_table;
using internal_transition_table =
typename transition_table_impl<StateMachine>::internal_transition_table;
}

View File

@@ -302,12 +302,17 @@ struct compile_policy_impl<favor_compile_time>
// Dispatch an event to the SM's internal table.
static process_result internal_dispatch(StateMachine& sm, const any_event& event)
{
const dispatch_table& self = instance();
return self.m_internal_dispatch_table.dispatch(sm, event);
if constexpr (has_internal_transitions::value)
{
const dispatch_table& self = instance();
return self.m_internal_dispatch_table.dispatch(sm, event);
}
return process_result::HANDLED_FALSE;
}
private:
using state_set = typename StateMachine::internal::state_set;
using submachines = mp11::mp_copy_if<state_set, has_state_machine_tag>;
// Value used to initialize a cell of the dispatch table.
using cell_t = process_result (*)(StateMachine&, int&, any_event const&);
@@ -343,6 +348,9 @@ struct compile_policy_impl<favor_compile_time>
template<typename Transition>
using get_init_cell_constant = typename get_init_cell_constant_impl<Transition>::type;
using has_internal_transitions = mp11::mp_not<mp11::mp_empty<internal_transition_table<StateMachine>>>;
using has_transitions = mp11::mp_not<mp11::mp_empty<transition_table<StateMachine>>>;
// Dispatch table for one state.
class state_dispatch_table
{
@@ -467,7 +475,7 @@ struct compile_policy_impl<favor_compile_time>
StateMachine::template get_state_id<Submachine>();
m_state_dispatch_tables[state_id].template init_composite_state<Submachine>();
});
if constexpr (!mp11::mp_empty<transition_table<StateMachine>>::value)
if constexpr (has_transitions::value)
{
using init_cell_constants = mp11::mp_transform<
get_init_cell_constant,
@@ -488,7 +496,7 @@ struct compile_policy_impl<favor_compile_time>
}
// Initialize the sm-internal dispatch table.
if constexpr (!mp11::mp_empty<internal_transition_table<StateMachine>>::value)
if constexpr (has_internal_transitions::value)
{
using init_internal_cell_constants = mp11::mp_transform<
get_internal_init_cell_constant,

View File

@@ -18,17 +18,9 @@
#include <list>
#include <utility>
#include <boost/core/no_exceptions_support.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/mp11.hpp>
#include <boost/assert.hpp>
#include <boost/ref.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <boost/type_traits/add_reference.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_convertible.hpp>
#include <boost/core/no_exceptions_support.hpp>
#include <boost/mp11.hpp>
#include <boost/msm/active_state_switching_policies.hpp>
#include <boost/msm/row_tags.hpp>
@@ -141,92 +133,79 @@ class state_machine_base : public FrontEnd
// Used when the front-end does not define a final_event.
struct stopping {};
template <class ExitPoint>
struct exit_pt : public ExitPoint
// Wrapper for an exit pseudostate,
// which upper SMs can use to connect to it.
template <class ExitPseudostate>
struct exit_pt : public ExitPseudostate
{
// tags
struct internal
{
using tag = exit_pseudostate_be_tag;
};
typedef ExitPoint wrapped_exit;
typedef derived_t owner;
typedef int no_automatic_create;
typedef typename
ExitPoint::event Event;
typedef std::function<process_result (Event const&)>
forward_function;
using state = ExitPseudostate;
using owner = derived_t;
using event = typename ExitPseudostate::event;
using forward_function = std::function<process_result(event const&)>;
// forward event to the higher-level FSM
template <class ForwardEvent>
void forward_event(ForwardEvent const& incomingEvent)
{
// use helper to forward or not
ForwardHelper< ::boost::is_convertible<ForwardEvent,Event>::value>::helper(incomingEvent,m_forward);
static_assert(std::is_convertible_v<ForwardEvent, event>,
"ForwardEvent must be convertible to exit pseudostate's event");
// Call if handler set. If not, this state is simply a terminate state.
if (m_forward)
{
m_forward(incomingEvent);
}
}
void set_forward_fct(forward_function fct)
{
m_forward = fct;
}
exit_pt():m_forward(){}
exit_pt() = default;
// by assignments, we keep our forwarding functor unchanged as our containing SM did not change
template <class RHS>
exit_pt(RHS&):m_forward(){}
exit_pt<ExitPoint>& operator= (const exit_pt<ExitPoint>& )
exit_pt(RHS&) : m_forward()
{
}
exit_pt<ExitPseudostate>& operator=(const exit_pt<ExitPseudostate>&)
{
return *this;
}
private:
private:
forward_function m_forward;
// using partial specialization instead of enable_if because of VC8 bug
template <bool OwnEvent, int Dummy=0>
struct ForwardHelper
{
template <class ForwardEvent>
static void helper(ForwardEvent const& ,forward_function& )
{
// Not our event, assert
BOOST_ASSERT(false);
}
};
template <int Dummy>
struct ForwardHelper<true,Dummy>
{
template <class ForwardEvent>
static void helper(ForwardEvent const& incomingEvent,forward_function& forward_fct)
{
// call if handler set, if not, this state is simply a terminate state
if (forward_fct)
forward_fct(incomingEvent);
}
};
};
template <class EntryPoint>
struct entry_pt : public EntryPoint
// Wrapper for an entry pseudostate,
// which upper SMs can use to connect to it.
template <class EntryPseudostate>
struct entry_pt : public EntryPseudostate
{
// tags
struct internal
{
using tag = entry_pseudostate_be_tag;
};
typedef EntryPoint wrapped_entry;
typedef derived_t owner;
typedef int no_automatic_create;
using state = EntryPseudostate;
using owner = derived_t;
};
template <class EntryPoint>
struct direct : public EntryPoint
// Wrapper for a direct entry,
// which upper SMs can use to connect to it.
template <class State>
struct direct : public State
{
// tags
struct internal
{
using tag = explicit_entry_be_tag;
};
typedef EntryPoint wrapped_entry;
typedef derived_t owner;
typedef int no_automatic_create;
using state = State;
using owner = derived_t;
};
struct internal
@@ -240,21 +219,21 @@ class state_machine_base : public FrontEnd
using submachines = mp11::mp_copy_if<state_set, is_composite>;
};
typedef mp11::mp_rename<typename internal::state_set, std::tuple> states_t;
using states_t = mp11::mp_rename<typename internal::state_set, std::tuple>;
private:
using state_set = typename internal::state_set;
static constexpr int nr_regions = internal::nr_regions;
using active_state_ids_t = std::array<int, nr_regions>;
using initial_states_identity = mp11::mp_transform<mp11::mp_identity, typename internal::initial_states>;
using initial_state_identities = mp11::mp_transform<mp11::mp_identity, typename internal::initial_states>;
using compile_policy = typename config_t::compile_policy;
using compile_policy_impl = detail::compile_policy_impl<compile_policy>;
template<typename T>
using get_active_state_switch_policy = typename T::active_state_switch_policy;
using active_state_switching =
boost::mp11::mp_eval_or<active_state_switch_after_entry,
get_active_state_switch_policy, front_end_t>;
mp11::mp_eval_or<active_state_switch_after_entry,
get_active_state_switch_policy, front_end_t>;
template <class, class, class>
friend class state_machine_base;
@@ -278,12 +257,12 @@ class state_machine_base : public FrontEnd
template <typename T>
using get_initial_event = typename T::initial_event;
using fsm_initial_event =
boost::mp11::mp_eval_or<starting, get_initial_event, front_end_t>;
mp11::mp_eval_or<starting, get_initial_event, front_end_t>;
template <typename T>
using get_final_event = typename T::final_event;
using fsm_final_event =
boost::mp11::mp_eval_or<stopping, get_final_event, front_end_t>;
mp11::mp_eval_or<stopping, get_final_event, front_end_t>;
using state_map = generate_state_map<state_set>;
using history_impl = detail::history_impl<typename front_end_t::history, nr_regions>;
@@ -407,7 +386,7 @@ class state_machine_base : public FrontEnd
{
if (this != &rhs)
{
front_end_t::operator=(rhs);
front_end_t::operator=(rhs);
// Copy all members except the root sm pointer.
m_active_state_ids = rhs.m_active_state_ids;
m_optional_members = rhs.m_optional_members;
@@ -438,7 +417,7 @@ class state_machine_base : public FrontEnd
{
if (!m_running)
{
internal_start<Event, fsm_parameter_t, true>(initial_event, get_fsm_argument());
on_entry(initial_event, get_fsm_argument());
}
}
@@ -749,7 +728,7 @@ class state_machine_base : public FrontEnd
void reset_active_state_ids()
{
size_t index = 0;
mp11::mp_for_each<initial_states_identity>(
mp11::mp_for_each<initial_state_identities>(
[this, &index](auto state_identity)
{
using State = typename decltype(state_identity)::type;
@@ -996,102 +975,142 @@ class state_machine_base : public FrontEnd
}
private:
template <class Event, class Fsm, bool InitialStart = false>
void internal_start(Event const& event, Fsm& fsm)
template <class Event, class Fsm>
void preprocess_entry(Event const& event, Fsm& fsm)
{
m_running = true;
m_event_processing = true;
// Call on_entry on this SM first.
static_cast<front_end_t*>(this)->on_entry(event, fsm);
// Then call on_entry on the states.
if constexpr (InitialStart)
{
mp11::mp_for_each<initial_states_identity>(
[this, &event, &fsm](auto state_identity)
{
using State = typename decltype(state_identity)::type;
execute_entry(this->get_state<State>(), event, fsm);
});
}
else
{
// Visit active states non-recursively.
visit(
[&event, &fsm](auto& state)
{
// TODO:
// Add filter to rule out impossible entry states.
execute_entry(state, event, fsm);
});
}
// give a chance to handle an anonymous (eventless) transition
try_process_completion_event(EventSource::EVENT_SOURCE_DEFAULT, true);
}
// entry for states machines which are themselves embedded in other state machines (composites)
void postprocess_entry()
{
m_event_processing = false;
// Give a chance to handle a completion transition first.
try_process_completion_event(EventSource::EVENT_SOURCE_DEFAULT, true);
// Default:
// Handle deferred events queue with higher prio than events queue.
if constexpr (!has_event_queue_before_deferred_queue<front_end_t>::value)
{
try_process_deferred_events();
try_process_queued_events();
}
// Non-default:
// Handle events queue with higher prio than deferred events queue.
else
{
try_process_queued_events();
try_process_deferred_events();
}
}
template <class Event, class Fsm>
void on_entry(Event const& event, Fsm& fsm)
{
// block immediate handling of events
m_event_processing = true;
// by default we activate the history/init states, can be overwritten by direct events.
preprocess_entry(event, fsm);
// First set all active state ids...
m_active_state_ids = m_history.on_entry(event);
// this variant is for the standard case, entry due to activation of the containing FSM
if constexpr (!has_direct_entry<Event>::value)
// ... then execute each state entry.
if constexpr (std::is_same_v<typename front_end_t::history,
front::no_history>)
{
internal_start(event, fsm);
}
// this variant is for the direct entry case
else if constexpr (has_direct_entry<Event>::value)
{
// Set the new active state(s) first, this includes
// a normal direct entry (or entries) and a pseudo entry.
using entry_states = to_mp_list_t<typename Event::active_state>;
mp11::mp_for_each<mp11::mp_transform<mp11::mp_identity, entry_states>>(
[this](auto state_identity)
mp11::mp_for_each<initial_state_identities>(
[this, &event, &fsm](auto state_identity)
{
using State = typename decltype(state_identity)::type::wrapped_entry;
static constexpr int region_index = State::zone_index;
static_assert(region_index >= 0 && region_index < nr_regions);
m_active_state_ids[region_index] = get_state_id<State>();
}
);
internal_start(event.m_event, fsm);
// in case of a pseudo entry process the transition in the zone of the newly active state
// (entry pseudo states are, according to UML, a state connecting 1 transition outside to 1 inside
if constexpr (has_pseudo_entry<typename Event::active_state>::value)
{
static_assert(!mpl::is_sequence<typename Event::active_state>::value);
process_event(event.m_event);
}
using State = typename decltype(state_identity)::type;
auto& state = this->get_state<State>();
state.on_entry(event, fsm);
});
}
// handle messages which were generated and blocked in the init calls
m_event_processing = false;
// look for deferred events waiting
try_process_deferred_events();
try_process_queued_events();
else
{
visit<visit_mode::active_non_recursive>(
[&event, &fsm](auto& state)
{
state.on_entry(event, fsm);
});
}
postprocess_entry();
}
template <class TargetStates, class Event, class Fsm>
void on_explicit_entry(Event const& event, Fsm& fsm)
{
preprocess_entry(event, fsm);
using state_identities =
mp11::mp_transform<mp11::mp_identity, TargetStates>;
static constexpr bool all_regions_defined =
mp11::mp_size<state_identities>::value == nr_regions;
// First set all active state ids...
if constexpr (!all_regions_defined)
{
m_active_state_ids = m_history.on_entry(event);
}
mp11::mp_for_each<state_identities>(
[this](auto state_identity)
{
using State = typename decltype(state_identity)::type;
static constexpr int region_id = State::zone_index;
static_assert(region_id >= 0 && region_id < nr_regions);
m_active_state_ids[region_id] = get_state_id<State>();
}
);
// ... then execute each state entry.
if constexpr (all_regions_defined)
{
mp11::mp_for_each<state_identities>(
[this, &event, &fsm](auto state_identity)
{
using State = typename decltype(state_identity)::type;
auto& state = this->get_state<State>();
state.on_entry(event, fsm);
});
}
else
{
visit<visit_mode::active_non_recursive>(
[&event, &fsm](auto& state)
{
state.on_entry(event, fsm);
});
}
postprocess_entry();
}
template <class TargetStates, class Event, class Fsm>
void on_pseudo_entry(Event const& event, Fsm& fsm)
{
on_explicit_entry<TargetStates>(event, fsm);
// Execute the second part of the compound transition.
process_event(event);
}
template <class Event,class Fsm>
void on_exit(Event const& event, Fsm& fsm)
{
// first recursively exit the sub machines
// forward the event for handling by sub state machines
visit(
// First exit the substates.
visit<visit_mode::active_non_recursive>(
[&event, &fsm](auto& state)
{
// TODO:
// Filter out impossible exit states.
state.on_exit(event, fsm);
}
);
// then call our own exit
// Then call our own exit.
(static_cast<front_end_t*>(this))->on_exit(event,fsm);
// give the history a chance to handle this (or not).
// Give the history a chance to handle this (or not).
m_history.on_exit(this->m_active_state_ids);
// history decides what happens with deferred events
// History decides what happens with deferred events.
if (!m_history.process_deferred_events(event))
{
if constexpr (deferred_events_member::value)
@@ -1101,50 +1120,6 @@ private:
}
}
// calls entry or on_entry depending on the state type
template <class State, class Event, class Fsm>
static void execute_entry(State& state, Event const& event, Fsm& fsm)
{
// calls on_entry on the fsm then handles direct entries, fork, entry pseudo state
if constexpr (has_state_machine_tag<State>::value)
{
state.on_entry(event,fsm);
}
else if constexpr (has_exit_pseudostate_be_tag<State>::value)
{
// calls on_entry on the state then forward the event to the transition which should be defined inside the
// contained fsm
state.on_entry(event,fsm);
state.forward_event(event);
}
else if constexpr (has_direct_entry<Event>::value)
{
state.on_entry(event.m_event, fsm);
}
else
{
state.on_entry(event, fsm);
}
}
// helper allowing special handling of direct entries / fork
template <class Target,class State,class Event>
static void convert_event_and_execute_entry(State& state,Event const& event, state_machine_base& sm)
{
auto& fsm = sm.get_fsm_argument();
if constexpr (has_explicit_entry_state<Target>::value || mpl::is_sequence<Target>::value)
{
// for the direct entry, pack the event in a wrapper so that we handle it differently during fsm entry
execute_entry(state,direct_entry_event<Target,Event>(event),fsm);
}
else
{
// if the target is a normal state, do the standard entry handling
execute_entry(state,event,fsm);
}
}
template <typename State>
using state_requires_init =
mp11::mp_or<has_exit_pseudostate_be_tag<State>, has_state_machine_tag<State>>;