// Copyright 2025 Christian Granzin // Copyright 2008 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_DETAIL_TRANSITION_TABLE_HPP #define BOOST_MSM_BACKMP11_DETAIL_TRANSITION_TABLE_HPP #include #include #include #include #include #include #include namespace boost::msm::front { struct Defer; } namespace boost::msm::backmp11::detail { // Adapter for the basic front-end. constexpr process_result to_process_result(back::HandledEnum value) { return static_cast(value); } constexpr process_result to_process_result(process_result result) { return result; } // Chain of priority tags for SFINAE handling: // priority_tag_0 // ↓ (SFINAE fails?) // priority_tag_1 (base of priority_tag_0) // ↓ (SFINAE fails?) // ... struct priority_tag_3 {}; struct priority_tag_2 : priority_tag_3 {}; struct priority_tag_1 : priority_tag_2 {}; struct priority_tag_0 : priority_tag_1 {}; template auto invoke_functor(priority_tag_0, Functor&&, const Event& event, Fsm& fsm, Source& source, Target& target) -> decltype(Functor{}(event, fsm, source, target)) { return Functor{}(event, fsm, source, target); } template auto invoke_functor(priority_tag_1, Functor&&, const Event& event, Fsm& fsm, Source&, Target&) -> decltype(Functor{}(event, fsm)) { return Functor{}(event, fsm); } template auto invoke_functor(priority_tag_2, Functor&&, const Event&, Fsm& fsm, Source&, Target&) -> decltype(Functor{}(fsm)) { return Functor{}(fsm); } template inline constexpr bool invokable = false; template auto invoke_functor(priority_tag_3, Functor&&, const Event&, Fsm&, Source&, Target&) { static_assert( invokable, "Action/Guard must be invokable with one of these signatures: " "(event, fsm, source, target), (event, fsm), or (fsm)"); } template using get_Guard = typename Row::Guard; template struct has_Guard : mp11::mp_valid {}; template struct invoke_guard_functor { template static bool execute(const Event& event, Fsm& fsm, Source& source, Target& target) { return invoke_functor(priority_tag_0{}, Functor{}, event, fsm, source, target); } }; template <> struct invoke_guard_functor { template static bool execute(const Event&, Fsm&, Source&, Target&) { return true; } }; template using get_Action = typename Row::Action; template struct has_Action : mp11::mp_valid {}; template struct invoke_action_functor { template static process_result execute(const Event& event, Fsm& fsm, Source& source, Target& target) { invoke_functor(priority_tag_0{}, Functor{}, event, fsm, source, target); return process_result::consumed; } }; template <> struct invoke_action_functor { template static process_result execute(const Event&, Fsm&, Source&, Target&) { return process_result::consumed; } }; template <> struct invoke_action_functor { template static process_result execute(const Event& event, Fsm& fsm, Source&, Target&) { fsm.defer_event(event); return process_result::deferred; } }; template struct transition_table_impl { using front_end_t = typename StateMachine::front_end_t; using derived_t = typename StateMachine::derived_t; using state_set = typename StateMachine::state_set; template using get_active_state_switch_policy = typename T::active_state_switch_policy; using active_state_switching = boost::mp11::mp_eval_or; template static bool call_guard_or_true(StateMachine& sm, const Event& event, Source& source, Target& target) { if constexpr (has_Guard::value) { return invoke_guard_functor::execute( event, sm.get_fsm_argument(), source, target); } else if constexpr (HasGuard) { return Row::guard_call( sm.get_fsm_argument(), event, source, target, sm.m_states); } else { return true; } } template static process_result call_action_or_true(StateMachine& sm, const Event& event, Source& source, Target& target) { if constexpr (has_Action::value) { return invoke_action_functor::execute( event, sm.get_fsm_argument(), source, target); } else if constexpr (HasAction) { return to_process_result(Row::action_call( sm.get_fsm_argument(), event, source, target, sm.m_states)); } else { return process_result::consumed; } } template using get_state = typename DirectWrapper::state; template using is_explicit_entry_point = mp11::mp_eval_if< mpl::is_sequence, mp11::mp_true, has_explicit_entry_be_tag, FeTarget>; template 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::value) { using targets = to_mp_list_t; using states = mp11::mp_transform; target.template on_explicit_entry(event, fsm); } else if constexpr (has_entry_pseudostate_be_tag::value) { using targets = to_mp_list_t; using states = mp11::mp_transform; target.template on_pseudo_entry(event, fsm); } else { target.on_entry(event, fsm); if constexpr (has_exit_pseudostate_be_tag::value) { // Execute the second part of the compound transition. target.forward_event(*sm.m_root_sm, event); } } } // Template used to create transitions from rows in the transition table. template struct transition { using transition_event = typename Row::Evt; using current_state_type = convert_source_state; using next_state_type = convert_target_state; template static process_result process(StateMachine& sm, uint8_t region_id, const Event& event) { if constexpr (!std::is_same_v) { 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) { 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 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 = StateMachine::template get_state_id(); BOOST_ASSERT(state_id == current_state_id); constexpr auto next_state_id = StateMachine::template get_state_id(); auto& source = sm.template get_state(); auto& target = sm.template get_state(); if (!call_guard_or_true(sm, event, source, target)) { // guard rejected the event, we stay in the current one return process_result::rejected; } if constexpr (std::is_same_v) { state_id = next_state_id; } // first call the exit method of the current state source.on_exit(event, sm.get_fsm_argument()); if constexpr (std::is_same_v) { state_id = next_state_id; } // then call the action method process_result res = call_action_or_true(sm, event, source, target); if constexpr (std::is_same_v) { state_id = next_state_id; } // and finally the entry method of the new state call_entry(sm, event, target); if constexpr (std::is_same_v) { state_id = next_state_id; } // Give a chance to handle completion transitions. sm.on_state_entry_completed(target, region_id); return res; } }; // Template used to create internal transitions // from rows in the transition table. template struct internal_transition { using transition_event = typename Row::Evt; using current_state_type = State; template static process_result process(StateMachine& sm, uint8_t region_id, const Event& event) { if constexpr (!std::is_same_v) { 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) { 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 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( state_id == StateMachine::template get_state_id()); auto& source = sm.template get_state(); auto& target = source; if (!call_guard_or_true(sm, event, source, target)) { return process_result::rejected; } return call_action_or_true(sm, event, source, target); } }; // Template used to create sm-internal transitions // from rows in the transition table. template struct internal_transition { using transition_event = typename Row::Evt; template static process_result process(StateMachine& sm, const Event& event) { if constexpr (!std::is_same_v) { sm.get_observer() .template pre_process_transition( sm); } const auto result = process_impl(sm, event); if constexpr (!std::is_same_v) { sm.get_observer() .template post_process_transition( sm, result); } return result; } template static process_result process_impl(StateMachine& sm, const Event& event) { auto& source = sm; auto& target = source; if (!call_guard_or_true(sm, event, source, target)) { return process_result::rejected; } return call_action_or_true(sm, event, source, target); } }; // Helpers used to create back-end transitions // from front-end transitions. template struct create_transition_impl; template struct create_transition_impl { using type = transition; }; template struct create_transition_impl { using type = transition; }; template struct create_transition_impl<_row_tag, Row, State> { using type = transition; }; template struct create_transition_impl { using type = transition; }; template struct create_transition_impl { using type = internal_transition; }; template struct create_transition_impl { using type = internal_transition; }; template struct create_transition_impl { using type = internal_transition; }; template struct create_transition_impl<_irow_tag, Row, State> { using type = internal_transition; }; template struct create_transition_impl { using type = internal_transition; }; template struct create_transition_impl { using type = internal_transition; }; template struct create_transition_impl { using type = internal_transition; }; template struct create_transition_impl { using type = internal_transition; }; template using create_transition = typename create_transition_impl::type; // Transform the front-end's internal transition table // to a back-end transition table, // with reversed ordering for backward compatibility. using internal_transition_table = mp11::mp_reverse< mp11::mp_transform_q< mp11::mp_bind_back, to_mp_list_t>>; // Expand the front-end's transition table(s) // to a single back-end transition table, // with reversed ordering for backward compatibility. // First transform the main transition table of the SM. using sm_transition_table = mp11::mp_transform_q< mp11::mp_bind_back, to_mp_list_t>; // Then transform and append the states' internal transition tables. template using append_state_transition_tables_predicate = mp11::mp_or< is_composite, mp11::mp_empty>>; template using append_internal_transition_table = mp11::mp_append< TransitionTable, mp11::mp_transform_q< mp11::mp_bind_back, to_mp_list_t>>; using transition_table = mp11::mp_reverse< mp11::mp_fold< mp11::mp_remove_if, sm_transition_table, append_internal_transition_table>>; // Completion transitions are handled separately per state. template using has_completion_event = has_completion_event; using completion_transition_table = mp11::mp_copy_if; static_assert(mp11::mp_empty::value || StateMachine::has_event_pool, "Completion transitions require an event pool"); template struct completion_transitions_impl { template using predicate = std::is_same; using type = mp11::mp_copy_if; }; template struct completion_transitions_impl> { using type = mp11::mp_list<>; }; template using completion_transitions = typename completion_transitions_impl::type; }; template using transition_table = typename transition_table_impl::transition_table; template using internal_transition_table = typename transition_table_impl::internal_transition_table; template using completion_transitions = typename transition_table_impl::template completion_transitions; template using has_completion_transitions = mp11::mp_not>>; // Class used to execute a chain of transitions for a given event and state. // Handles transition conflicts. template struct transition_chain { using current_state_type = State; using transition_event = Event; static process_result process(StateMachine& sm, uint8_t region_id, const Event& evt) { process_result result = process_result::discarded; mp_for_each_until( [&result, &sm, region_id, &evt](auto transition) { using Transition = decltype(transition); result |= Transition::process(sm, region_id, evt); if (any(result & consumed_or_deferred)) { // If a guard rejected previously, ensure this bit is not present. result &= consumed_or_deferred; return true; } return false; } ); return result; } }; } // namespace boost::msm::backmp11::detail #endif // BOOST_MSM_BACKMP11_DETAIL_TRANSITION_TABLE_HPP