move to roor folder
This commit is contained in:
295
fsm.h
Normal file
295
fsm.h
Normal file
@@ -0,0 +1,295 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
/** @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 <typename TContext, typename TFrom, typename TEvent>
|
||||
bool operator() (TContext &, const TFrom &, const TEvent &) const noexcept {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/** @brief Default action: do nothing. */
|
||||
struct do_nothing {
|
||||
template <typename TContext, typename TFrom, typename TTo, typename TEvent>
|
||||
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 <typename TTo, typename TGuard = always_allow, typename TAction = do_nothing>
|
||||
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 <typename TFrom, typename TEvent>
|
||||
struct transition {
|
||||
using type = no_transition;
|
||||
};
|
||||
|
||||
/** @brief Optional mapping to names for logs. */
|
||||
template <typename T>
|
||||
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 <typename TState>
|
||||
struct on_exit_state {
|
||||
template <typename TContext>
|
||||
static void call (TContext &, const TState &) noexcept {}
|
||||
};
|
||||
|
||||
template <typename TState>
|
||||
struct on_entry_state {
|
||||
template <typename TContext>
|
||||
static void call (TContext &, const TState &) noexcept {}
|
||||
};
|
||||
|
||||
/** @brief Hooks: event-specific. */
|
||||
template <typename TState, typename TEvent>
|
||||
struct on_exit_event {
|
||||
template <typename TContext>
|
||||
static void call (TContext &, const TState &, const TEvent &) noexcept {}
|
||||
};
|
||||
|
||||
template <typename TState, typename TEvent>
|
||||
struct on_entry_event {
|
||||
template <typename TContext>
|
||||
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<TContext>().
|
||||
*
|
||||
* Default: To{}.
|
||||
* Specialize state_factory<TContext, From, Event, To> as needed.
|
||||
*/
|
||||
template <typename TContext, typename TFrom, typename TEvent, typename TTo>
|
||||
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 <typename TFrom, typename TEvent>
|
||||
struct missing_transition {
|
||||
static constexpr missing_transition_policy policy = missing_transition_policy::ignore;
|
||||
};
|
||||
|
||||
/** @brief Helper: is T one of Ts... */
|
||||
template <typename T, typename... Ts>
|
||||
struct is_one_of : std::disjunction<std::is_same<T, Ts>...> {};
|
||||
|
||||
/** @brief Helper: transition exists? */
|
||||
template <typename TFrom, typename TEvent>
|
||||
struct has_transition
|
||||
: std::bool_constant < !std::is_same_v<typename transition<TFrom, TEvent>::type, no_transition >> {};
|
||||
|
||||
/** @brief Factory validation (call-form). */
|
||||
template <typename TContext, typename TFrom, typename TEvent, typename TTo, typename = void>
|
||||
struct is_factory_invocable : std::false_type {};
|
||||
|
||||
template <typename TContext, typename TFrom, typename TEvent, typename TTo>
|
||||
struct is_factory_invocable <
|
||||
TContext, TFrom, TEvent, TTo,
|
||||
std::void_t<decltype (state_factory<TContext, TFrom, TEvent, TTo>::make (
|
||||
std::declval<TContext &>(),
|
||||
std::declval<const TFrom &>(),
|
||||
std::declval<const TEvent &>())) >>
|
||||
: std::bool_constant<std::is_same_v<
|
||||
decltype (state_factory<TContext, TFrom, TEvent, TTo>::make (
|
||||
std::declval<TContext &>(),
|
||||
std::declval<const TFrom &>(),
|
||||
std::declval<const TEvent &>())),
|
||||
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<State,Event>::policy (ignore/strict)
|
||||
*
|
||||
* validate_events<Ev...>():
|
||||
* - compile-time checks ONLY for pairs marked strict.
|
||||
*/
|
||||
template <typename TContext, typename TLogger, typename... TStates>
|
||||
class fsm {
|
||||
public:
|
||||
using state_variant = std::variant<TStates...>;
|
||||
|
||||
template <typename TInitial,
|
||||
typename = std::enable_if_t<is_one_of<TInitial, TStates...>::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 <typename TEvent>
|
||||
bool dispatch (const TEvent &ev) {
|
||||
dispatch_visitor<TEvent> v{*this, ev};
|
||||
return std::visit (v, m_state);
|
||||
}
|
||||
|
||||
template <typename... TEvents>
|
||||
static constexpr void validate_events() {
|
||||
(validate_one_event<TEvents>(), ...);
|
||||
}
|
||||
|
||||
const state_variant &state() const noexcept {
|
||||
return m_state;
|
||||
}
|
||||
state_variant &state() noexcept {
|
||||
return m_state;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename TEvent>
|
||||
struct dispatch_visitor {
|
||||
fsm &self;
|
||||
const TEvent &ev;
|
||||
|
||||
template <typename TFrom>
|
||||
bool operator() (TFrom &from) const {
|
||||
return self.template dispatch_from<TFrom> (from, ev);
|
||||
}
|
||||
};
|
||||
|
||||
struct init_entry_visitor {
|
||||
fsm &self;
|
||||
template <typename TState>
|
||||
void operator() (TState &st) const {
|
||||
on_entry_state<TState>::call (self.m_ctx, st);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TEvent>
|
||||
struct entry_after_commit_visitor {
|
||||
fsm &self;
|
||||
const TEvent &ev;
|
||||
|
||||
template <typename TState>
|
||||
void operator() (TState &st) const {
|
||||
on_entry_state<TState>::call (self.m_ctx, st);
|
||||
on_entry_event<TState, TEvent>::call (self.m_ctx, st, ev);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TFrom, typename TEvent>
|
||||
bool dispatch_from (TFrom &from, const TEvent &ev) {
|
||||
using tr = typename transition<TFrom, TEvent>::type;
|
||||
|
||||
if constexpr (std::is_same_v<tr, no_transition>) {
|
||||
if constexpr (missing_transition<TFrom, TEvent>::policy == missing_transition_policy::strict)
|
||||
m_logger.unhandled_transition (name_of<TFrom>::value, name_of<TEvent>::value);
|
||||
else
|
||||
m_logger.ignored_transition (name_of<TFrom>::value, name_of<TEvent>::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<to_state, TStates...>::value,
|
||||
"FSM: transition target state not in FSM state list.");
|
||||
|
||||
static_assert (std::is_invocable_r_v<bool, guard_t, TContext &, const TFrom &, const TEvent &>,
|
||||
"FSM: guard must be callable as bool(Context&, const From&, const Event&).");
|
||||
|
||||
static_assert (std::is_invocable_r_v<void, action_t, TContext &, const TFrom &, const to_state &, const TEvent &>,
|
||||
"FSM: action must be callable as void(Context&, const From&, const To&, const Event&).");
|
||||
|
||||
static_assert (is_factory_invocable<TContext, TFrom, TEvent, to_state>::value,
|
||||
"FSM: state_factory<Context,From,Event,To>::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<TFrom>::value, name_of<TEvent>::value);
|
||||
return false;
|
||||
}
|
||||
|
||||
on_exit_event<TFrom, TEvent>::call (m_ctx, from, ev);
|
||||
on_exit_state<TFrom>::call (m_ctx, from);
|
||||
|
||||
to_state to = state_factory<TContext, TFrom, TEvent, to_state>::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<TEvent> {*this, ev}, m_state);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TEvent>
|
||||
static constexpr void validate_one_event() {
|
||||
(validate_pair<TStates, TEvent>(), ...);
|
||||
}
|
||||
|
||||
template <typename TState, typename TEvent>
|
||||
static constexpr void validate_pair() {
|
||||
if constexpr (missing_transition<TState, TEvent>::policy == missing_transition_policy::strict) {
|
||||
static_assert (has_transition<TState, TEvent>::value,
|
||||
"FSM validation: required (strict) transition is missing for (State, Event).");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
TContext &m_ctx;
|
||||
TLogger m_logger;
|
||||
state_variant m_state;
|
||||
};
|
||||
Reference in New Issue
Block a user