#pragma once #include #include #include /** @brief Missing transition policy. */ enum class missing_transition_policy { /** @brief Missing transition is considered required by validation. */ strict, /** @brief Missing transition is allowed (event ignored). */ ignore }; /** @brief Marker for undefined transition. */ struct no_transition {}; /** @brief Default guard: always allow. */ struct always_allow { template bool operator() (TContext &, const TFrom &, const TEvent &) const noexcept { return true; } }; /** @brief Default action: do nothing. */ struct do_nothing { template void operator() (TContext &, const TFrom &, const TTo &, const TEvent &) const noexcept { } }; /** * @brief Transition descriptor. * * @tparam TTo Destination state type. * @tparam TGuard Guard functor type. * @tparam TAction Action functor type. */ template struct transition_to { using to_state = TTo; using guard = TGuard; using action = TAction; }; /** * @brief Transition table mapping: specialize to define transitions. * Default: no_transition. */ template struct transition { using type = no_transition; }; /** @brief Optional mapping to names for logs. */ template struct name_of { static constexpr const char *value = "?"; }; /** * @brief Logger interface (default no-op). * * You can implement: * - ignored_transition(from, event) * - unhandled_transition(from, event) // policy=strict but missing transition at runtime * - guard_blocked(from, event) */ struct null_logger { void ignored_transition (const char *, const char *) noexcept {} void unhandled_transition (const char *, const char *) noexcept {} void guard_blocked (const char *, const char *) noexcept {} }; /** @brief Hooks: state-only. */ template struct on_exit_state { template static void call (TContext &, const TState &) noexcept {} }; template struct on_entry_state { template static void call (TContext &, const TState &) noexcept {} }; /** @brief Hooks: event-specific. */ template struct on_exit_event { template static void call (TContext &, const TState &, const TEvent &) noexcept {} }; template struct on_entry_event { template static void call (TContext &, const TState &, const TEvent &) noexcept {} }; /** * @brief Factory: construct destination state. * * IMPORTANT: Now includes TContext in template parameters to avoid templated make(). * * Default: To{}. * Specialize state_factory as needed. */ template struct state_factory { static TTo make (TContext &, const TFrom &, const TEvent &) { return TTo{}; } }; /** * @brief Per (State,Event) missing transition policy. * * Default is ignore. Mark only required pairs strict. */ template struct missing_transition { static constexpr missing_transition_policy policy = missing_transition_policy::ignore; }; /** @brief Helper: is T one of Ts... */ template struct is_one_of : std::disjunction...> {}; /** @brief Helper: transition exists? */ template struct has_transition : std::bool_constant < !std::is_same_v::type, no_transition >> {}; /** @brief Factory validation (call-form). */ template struct is_factory_invocable : std::false_type {}; template struct is_factory_invocable < TContext, TFrom, TEvent, TTo, std::void_t::make ( std::declval(), std::declval(), std::declval())) >> : std::bool_constant::make ( std::declval(), std::declval(), std::declval())), TTo>> {}; /** * @brief FSM with runtime state storage and explicit compile-time validation. * * dispatch(): * - never static_asserts on missing transitions (avoids StateƗEvent explosion with std::visit) * - handles missing transitions via missing_transition::policy (ignore/strict) * * validate_events(): * - compile-time checks ONLY for pairs marked strict. */ template class fsm { public: using state_variant = std::variant; template ::value>> explicit fsm (TContext &ctx, TInitial initial, TLogger logger = TLogger{}) : m_ctx (ctx) , m_logger (std::move (logger)) , m_state (std::move (initial)) { std::visit (init_entry_visitor{*this}, m_state); } template bool dispatch (const TEvent &ev) { dispatch_visitor v{*this, ev}; return std::visit (v, m_state); } template static constexpr void validate_events() { (validate_one_event(), ...); } const state_variant &state() const noexcept { return m_state; } state_variant &state() noexcept { return m_state; } private: template struct dispatch_visitor { fsm &self; const TEvent &ev; template bool operator() (TFrom &from) const { return self.template dispatch_from (from, ev); } }; struct init_entry_visitor { fsm &self; template void operator() (TState &st) const { on_entry_state::call (self.m_ctx, st); } }; template struct entry_after_commit_visitor { fsm &self; const TEvent &ev; template void operator() (TState &st) const { on_entry_state::call (self.m_ctx, st); on_entry_event::call (self.m_ctx, st, ev); } }; template bool dispatch_from (TFrom &from, const TEvent &ev) { using tr = typename transition::type; if constexpr (std::is_same_v) { if constexpr (missing_transition::policy == missing_transition_policy::strict) m_logger.unhandled_transition (name_of::value, name_of::value); else m_logger.ignored_transition (name_of::value, name_of::value); return false; } else { using to_state = typename tr::to_state; using guard_t = typename tr::guard; using action_t = typename tr::action; static_assert (is_one_of::value, "FSM: transition target state not in FSM state list."); static_assert (std::is_invocable_r_v, "FSM: guard must be callable as bool(Context&, const From&, const Event&)."); static_assert (std::is_invocable_r_v, "FSM: action must be callable as void(Context&, const From&, const To&, const Event&)."); static_assert (is_factory_invocable::value, "FSM: state_factory::make must be callable as " "To make(Context&, const From&, const Event&)."); guard_t guard{}; if (!guard (m_ctx, from, ev)) { m_logger.guard_blocked (name_of::value, name_of::value); return false; } on_exit_event::call (m_ctx, from, ev); on_exit_state::call (m_ctx, from); to_state to = state_factory::make (m_ctx, from, ev); action_t action{}; action (m_ctx, from, to, ev); m_state = std::move (to); std::visit (entry_after_commit_visitor {*this, ev}, m_state); return true; } } template static constexpr void validate_one_event() { (validate_pair(), ...); } template static constexpr void validate_pair() { if constexpr (missing_transition::policy == missing_transition_policy::strict) { static_assert (has_transition::value, "FSM validation: required (strict) transition is missing for (State, Event)."); } } private: TContext &m_ctx; TLogger m_logger; state_variant m_state; };