18#define FSM_VERSION_MAJOR 0
19#define FSM_VERSION_MINOR 4
20#define FSM_VERSION_PATCH 1
29#if defined(ESP_PLATFORM)
30# include "sdkconfig.h"
31# ifndef FSM_ENABLE_OBSERVER
32# ifdef CONFIG_FSM_OBSERVER
33# define FSM_ENABLE_OBSERVER 1
35# define FSM_ENABLE_OBSERVER 0
38# ifndef FSM_ENABLE_INTROSPECTION
39# ifdef CONFIG_FSM_INTROSPECTION
40# define FSM_ENABLE_INTROSPECTION 1
42# define FSM_ENABLE_INTROSPECTION 0
46#ifndef FSM_ENABLE_OBSERVER
47# define FSM_ENABLE_OBSERVER 1
49#ifndef FSM_ENABLE_INTROSPECTION
50# define FSM_ENABLE_INTROSPECTION 1
60#ifndef FSM_ENABLE_PAYLOAD_CHECKS
62# define FSM_ENABLE_PAYLOAD_CHECKS 0
64# define FSM_ENABLE_PAYLOAD_CHECKS 1
67#if FSM_ENABLE_PAYLOAD_CHECKS && !defined(FSM_PAYLOAD_ASSERT)
69# define FSM_PAYLOAD_ASSERT(cond) assert(cond)
78#ifndef FSM_ENABLE_DEADLINE_CHECKS
80# define FSM_ENABLE_DEADLINE_CHECKS 0
82# define FSM_ENABLE_DEADLINE_CHECKS 1
85#if FSM_ENABLE_DEADLINE_CHECKS && !defined(FSM_DEADLINE_ASSERT)
87# define FSM_DEADLINE_ASSERT(cond) assert(cond)
99inline constexpr uint16_t
no_row = 0xFFFF;
125template <
class State,
class Event,
class Reason>
136template <
class State,
class Event,
class Reason>
143 explicit constexpr any_t() =
default;
186template <
class State>
199template <
class Event>
211template <
class State>
215template <
class Reason>
223template <
class Context,
class Payload>
225 using guard = bool (*)(Context&,
const Payload&);
226 using action = void (*)(Context&,
const Payload&);
228template <
class Context>
229struct fn_sigs<Context, void> {
230 using guard = bool (*)(Context&);
231 using action = void (*)(Context&);
242template <
class State,
class Reason>
250 constexpr target(detail::refuse_t<Reason> f)
258template <
class State>
259constexpr detail::to_t<State>
to(State s) {
270template <
class Reason>
271constexpr detail::refuse_t<Reason>
refuse(Reason r) {
279template <
class State>
306template <
class State>
316template <
class State>
325template <
class State>
337template <
class State>
353template <
class Event>
370template <
class Event>
397template <
class Event>
415template <
class State,
class Event,
class Context,
class Payload =
void,
class Reason = u
int8_t>
420 using guard_fn =
typename detail::fn_sigs<Context, Payload>::guard;
422 using action_fn =
typename detail::fn_sigs<Context, Payload>::action;
440template <
class State,
class Event,
class Context>
463template <
class State,
class Event,
class Reason>
478constexpr bool time_le(Time a, Time b) {
479 using S = std::make_signed_t<Time>;
480 return static_cast<S
>(a - b) <= 0;
485constexpr bool time_lt(Time a, Time b) {
486 using S = std::make_signed_t<Time>;
487 return static_cast<S
>(a - b) < 0;
496template <
class Spec,
size_t N,
class State>
497constexpr size_t spec_index(
const std::array<Spec, N>& specs, State s) {
498 for (
size_t i = 0; i < N; ++i)
504template <
class Spec,
size_t N,
class State>
505constexpr bool is_composite(
const std::array<Spec, N>& specs, State s) {
506 for (
size_t i = 0; i < N; ++i)
512template <
class Spec,
size_t N,
class State>
513constexpr bool is_ancestor(
const std::array<Spec, N>& specs, State a, State x) {
515 for (
size_t fuse = 0; fuse <= N; ++fuse) {
516 const size_t i = spec_index(specs, cur);
518 cur = specs[i].parent.s;
519 if (cur == a)
return true;
525template <
class Spec,
size_t N,
class State>
526constexpr size_t depth_of(
const std::array<Spec, N>& specs, State s) {
529 for (
size_t fuse = 0; fuse <= N; ++fuse) {
530 const size_t i = spec_index(specs, cur);
532 cur = specs[i].parent.s;
539template <
class Spec,
size_t N>
540constexpr size_t max_depth(
const std::array<Spec, N>& specs) {
542 for (
size_t i = 0; i < N; ++i) {
544 const size_t d = depth_of(specs, specs[i].
state.s);
551template <
class Spec,
size_t N,
class State>
552constexpr State leaf_of(
const std::array<Spec, N>& specs, State s) {
554 for (
size_t fuse = 0; fuse <= N; ++fuse) {
555 const size_t i = spec_index(specs, cur);
557 cur = specs[i].initial.s;
563template <
size_t StateCount,
class Spec,
size_t N>
564constexpr bool states_complete(
const std::array<Spec, N>& specs) {
565 for (
size_t i = 0; i < N; ++i)
567 if (N != StateCount)
return false;
568 for (
size_t v = 0; v < StateCount; ++v) {
570 for (
size_t i = 0; i < N; ++i)
571 if (
static_cast<size_t>(specs[i].
state.s) == v) found =
true;
572 if (!found)
return false;
578template <
class Spec,
size_t N>
579constexpr bool no_duplicate_specs(
const std::array<Spec, N>& specs) {
580 for (
size_t i = 0; i < N; ++i)
581 for (
size_t j = i + 1; j < N; ++j)
583 specs[i].state.s == specs[j].state.s)
589template <
class Spec,
size_t N>
590constexpr bool parents_valid(
const std::array<Spec, N>& specs) {
591 for (
size_t i = 0; i < N; ++i) {
593 if (spec_index(specs, specs[i].parent.s) == N)
return false;
595 auto cur = specs[i].state.s;
597 for (; steps <= N; ++steps) {
598 const size_t p = spec_index(specs, cur);
600 cur = specs[p].parent.s;
602 if (steps > N)
return false;
608template <
class Spec,
size_t N>
609constexpr bool composites_declare_initial(
const std::array<Spec, N>& specs) {
610 for (
size_t i = 0; i < N; ++i) {
612 if (is_composite(specs, specs[i].
state.s) &&
620template <
class Spec,
size_t N>
621constexpr bool leaves_have_no_initial(
const std::array<Spec, N>& specs) {
622 for (
size_t i = 0; i < N; ++i) {
624 if (!is_composite(specs, specs[i].
state.s) &&
635template <
class Spec,
size_t N>
636constexpr bool initials_belong(
const std::array<Spec, N>& specs) {
637 for (
size_t i = 0; i < N; ++i) {
640 const auto s = specs[i].state.s;
641 const size_t c = spec_index(specs, specs[i].initial.s);
642 if (c == N)
return false;
646 bool leaf_reached =
false;
647 for (
size_t fuse = 0; fuse <= N; ++fuse) {
648 const size_t k = spec_index(specs, cur);
649 if (k == N)
return false;
654 cur = specs[k].initial.s;
656 if (!leaf_reached)
return false;
662template <
class Row,
size_t M>
663constexpr bool rows_have_targets(
const std::array<Row, M>& rows) {
664 for (
size_t i = 0; i < M; ++i)
672template <
size_t StateCount,
size_t EventCount,
class Row,
size_t M,
class Spec,
size_t N>
673constexpr bool rows_reference_valid(
const std::array<Row, M>& rows,
674 const std::array<Spec, N>& specs) {
675 for (
size_t i = 0; i < M; ++i) {
676 if (rows[i].from.k ==
from_kind::state && spec_index(specs, rows[i].from.s) == N)
678 if (rows[i].on.k !=
on_kind::event ||
static_cast<size_t>(rows[i].on.e) >= EventCount)
687constexpr bool same_slot(
const Row& a,
const Row& b) {
688 if (a.from.k != b.from.k)
return false;
694template <
class Row,
size_t M>
695constexpr bool rows_no_duplicate_unguarded(
const std::array<Row, M>& rows) {
696 for (
size_t i = 0; i < M; ++i)
697 for (
size_t j = i + 1; j < M; ++j)
698 if (same_slot(rows[i], rows[j]) && rows[i].when ==
nullptr && rows[j].when ==
nullptr)
704template <
class Row,
size_t M>
705constexpr bool rows_no_unreachable(
const std::array<Row, M>& rows) {
706 for (
size_t i = 0; i < M; ++i)
707 for (
size_t j = i + 1; j < M; ++j)
708 if (same_slot(rows[i], rows[j]) && rows[i].when ==
nullptr && rows[j].when !=
nullptr)
719template <
class Row,
size_t M>
720constexpr bool rows_refuse_have_no_action(
const std::array<Row, M>& rows) {
721 for (
size_t i = 0; i < M; ++i)
727template <
class Spec,
size_t N>
728constexpr bool parents_declared(
const std::array<Spec, N>& specs) {
729 for (
size_t i = 0; i < N; ++i)
735template <
class Spec,
size_t N>
736constexpr bool any_dynamic_deadline(
const std::array<Spec, N>& specs) {
737 for (
size_t i = 0; i < N; ++i)
743template <
class Spec,
size_t N>
744constexpr bool deadlines_declared(
const std::array<Spec, N>& specs) {
745 for (
size_t i = 0; i < N; ++i)
755template <
class Time,
class Spec,
size_t N>
756constexpr bool deadlines_fit(
const std::array<Spec, N>& specs) {
757 constexpr uint64_t half =
static_cast<uint64_t
>(
static_cast<Time
>(-1)) / 2;
758 for (
size_t i = 0; i < N; ++i)
760 static_cast<uint64_t
>(specs[i].deadline.ms) > half)
769template <
class Spec,
size_t N,
class Row,
size_t M>
770constexpr bool timeouts_consumable(
const std::array<Spec, N>& specs,
771 const std::array<Row, M>& rows) {
772 for (
size_t i = 0; i < N; ++i) {
776 const auto s = specs[i].state.s;
777 const auto ev = specs[i].deadline.event;
779 for (
size_t r = 0; r < M && !ok; ++r) {
780 if (rows[r].on.k !=
on_kind::event || rows[r].on.e != ev)
continue;
783 (rows[r].from.s == s || is_ancestor(specs, rows[r].from.s, s)))
786 if (!ok)
return false;
796template <
class Row,
class State,
class Event>
797constexpr bool row_matches_state(
const Row& r, State lvl, Event e) {
803template <
class Row,
class Event>
804constexpr bool row_matches_any(
const Row& r, Event e) {
809template <
class Spec,
size_t N,
class Row,
size_t M,
class State,
class Event>
810constexpr bool matches(
const std::array<Row, M>& rows,
const std::array<Spec, N>& specs,
811 State
leaf, Event e) {
813 for (
size_t fuse = 0; fuse <= N; ++fuse) {
814 for (
size_t r = 0; r < M; ++r)
815 if (row_matches_state(rows[r], lvl, e))
return true;
816 const size_t i = spec_index(specs, lvl);
818 lvl = specs[i].parent.s;
820 for (
size_t r = 0; r < M; ++r)
821 if (row_matches_any(rows[r], e))
return true;
829template <
size_t EventCount,
class Spec,
size_t N,
class Row,
size_t M>
830constexpr bool exhaustive(
const std::array<Spec, N>& specs,
const std::array<Row, M>& rows) {
831 for (
size_t i = 0; i < N; ++i) {
833 const auto s = specs[i].state.s;
834 if (is_composite(specs, s))
continue;
835 for (
size_t e = 0; e < EventCount; ++e)
836 if (!matches(rows, specs, s,
static_cast<decltype(specs[i].deadline.
event)
>(e)))
842#if FSM_ENABLE_INTROSPECTION
850 constexpr void put(
char c) {
851 if (n + 1 <
sizeof b) b[n++] = c;
854 constexpr void str(
const char* s) {
855 while (*s !=
'\0') put(*s++);
857 constexpr void u64(uint64_t v) {
861 tmp[i++] =
static_cast<char>(
'0' + v % 10);
864 while (i > 0) put(tmp[--i]);
866 constexpr void reset() {
876concept has_time_trait =
requires {
typename T::Time; };
878concept has_strict_trait =
requires {
879 { T::strict } -> std::convertible_to<bool>;
881template <
class T,
class State>
882concept has_state_names =
requires(State s) {
883 { T::state_name(s) } -> std::convertible_to<const char*>;
885template <
class T,
class Event>
886concept has_event_names =
requires(Event e) {
887 { T::event_name(e) } -> std::convertible_to<const char*>;
889template <
class T,
class Reason>
890concept has_reason_names =
requires(Reason r) {
891 { T::reason_name(r) } -> std::convertible_to<const char*>;
893template <
class T,
class Event,
class Payload>
894concept has_payload_matches =
requires(Event e,
const Payload& p) {
895 { T::payload_matches(e, p) } -> std::convertible_to<bool>;
897template <
class T,
class State,
class Context>
898concept has_deadline_ms =
requires(State s,
const Context& c) {
899 { T::deadline_ms(s, c) } -> std::convertible_to<uint32_t>;
902concept has_post_depth =
requires {
903 { T::post_queue_depth } -> std::convertible_to<size_t>;
907constexpr size_t post_depth_of() {
908 if constexpr (has_post_depth<T>)
return T::post_queue_depth;
914 using type = uint32_t;
916template <has_time_trait T>
918 using type =
typename T::Time;
922constexpr bool strict_of() {
923 if constexpr (has_strict_trait<T>)
return T::strict;
971template <
class Traits>
977 using rows_array = std::remove_cvref_t<
decltype(Traits::rows)>;
978 using specs_array = std::remove_cvref_t<
decltype(Traits::states)>;
983 using time_type =
typename detail::time_of<Traits>::type;
987 static constexpr size_t state_count =
static_cast<size_t>(state_type::Count);
988 static constexpr size_t event_count =
static_cast<size_t>(event_type::Count);
989 static constexpr size_t row_count = Traits::rows.size();
990 static constexpr bool strict = detail::strict_of<Traits>();
994 static_assert(
state_count <= 256,
"fsm: at most 256 states (spec index is uint8_t)");
995 static_assert(row_count < static_cast<size_t>(
no_row),
996 "fsm: row count must stay below fsm::no_row (0xFFFF)");
1000 static_assert(detail::no_duplicate_specs(Traits::states),
"fsm: duplicate state_spec");
1001 static_assert(detail::states_complete<state_count>(Traits::states),
1002 "fsm: every state needs exactly one state_spec");
1003 static_assert(detail::parents_declared(Traits::states),
1004 "fsm: state_spec .parent is mandatory (fsm::root/fsm::child_of)");
1005 static_assert(detail::parents_valid(Traits::states),
"fsm: parent graph must be acyclic");
1006 static_assert(detail::composites_declare_initial(Traits::states),
1007 "fsm: composite state must declare start_at initial");
1008 static_assert(detail::leaves_have_no_initial(Traits::states),
1009 "fsm: leaf state must not declare initial");
1010 static_assert(detail::initials_belong(Traits::states),
1011 "fsm: initial child's parent must be its composite");
1012 static_assert(detail::rows_have_targets(Traits::rows),
1013 "fsm: row .to is mandatory (fsm::to/internal/refuse)");
1014 static_assert(detail::rows_reference_valid<state_count, event_count>(Traits::rows,
1016 "fsm: row references invalid state or event");
1017 static_assert(detail::rows_no_duplicate_unguarded(Traits::rows),
1018 "fsm: ambiguous rows: duplicate unguarded (from, event)");
1019 static_assert(detail::rows_no_unreachable(Traits::rows),
1020 "fsm: unreachable row after unguarded row for same (from, event)");
1021 static_assert(detail::rows_refuse_have_no_action(Traits::rows),
1022 "fsm: refuse row must not declare an action (never runs on refusal)");
1023 static_assert(detail::deadlines_declared(Traits::states),
1024 "fsm: state_spec .deadline is mandatory (fsm::after/no_timeout)");
1025 static_assert(detail::deadlines_fit<time_type>(Traits::states),
1026 "fsm: deadline duration exceeds half the Time range");
1027 static_assert(detail::timeouts_consumable(Traits::states, Traits::rows),
1028 "fsm: timeout event not consumable from its state");
1029 static_assert(!detail::any_dynamic_deadline(Traits::states) ||
1030 detail::has_deadline_ms<Traits, state_type, context_type>,
1031 "fsm: after_dyn states require Traits::deadline_ms(State, const Context&)");
1032 static_assert(!
strict || detail::exhaustive<event_count>(Traits::states, Traits::rows),
1033 "fsm: unhandled (state, event) pair in strict machine");
1034 static_assert(detail::spec_index(Traits::states, Traits::initial) <
state_count,
1035 "fsm: machine initial state has no state_spec");
1036 static_assert(std::is_unsigned_v<time_type>,
"fsm: Time must be an unsigned integer type");
1037 static_assert(std::is_void_v<payload_type> ||
1038 (std::is_default_constructible_v<payload_type> &&
1039 std::is_trivially_copyable_v<payload_type>),
1040 "fsm: Payload must be default-constructible and trivially copyable");
1043 using payload_arg = std::conditional_t<std::is_void_v<payload_type>, detail::empty,
1044 std::remove_cv_t<payload_type>>;
1046 static constexpr size_t max_depth_v = detail::max_depth(Traits::states);
1049 static constexpr std::array<uint8_t, state_count> spec_pos_ = [] {
1050 std::array<uint8_t, state_count> a{};
1052 a[v] =
static_cast<uint8_t
>(
1053 detail::spec_index(Traits::states,
static_cast<state_type>(v)));
1058 return Traits::states[spec_pos_[
static_cast<size_t>(s)]];
1062 static constexpr std::array<uint8_t, state_count> depth_ = [] {
1063 std::array<uint8_t, state_count> a{};
1065 a[v] =
static_cast<uint8_t
>(
1066 detail::depth_of(Traits::states,
static_cast<state_type>(v)));
1077 static constexpr size_t post_depth = detail::post_depth_of<Traits>();
1078 static_assert(post_depth >= 1 && post_depth <= 255,
1079 "fsm: post_queue_depth must be in [1, 255]");
1088 bool started_ =
false;
1089 bool in_dispatch_ =
false;
1090#if FSM_ENABLE_OBSERVER
1091 bool in_notify_ =
false;
1094 pending posts_[post_depth] = {};
1095 uint8_t post_head_ = 0;
1096 uint8_t post_count_ = 0;
1097#if FSM_ENABLE_OBSERVER
1122 if (started_ || in_dispatch_)
return;
1124 in_dispatch_ =
true;
1125 enter_chain_to(Traits::initial, now);
1126 descend_initials(now);
1127 in_dispatch_ =
false;
1167 template <event_type E,
class Arg>
1168 requires(!std::is_void_v<payload_type> &&
requires(
const Arg& a) {
1169 { Traits::make_payload(
event_tag<E>{}, a) } -> std::convertible_to<payload_arg>;
1172 return post_impl(E, Traits::make_payload(
event_tag<E>{}, arg));
1176 template <event_type E>
1177 requires(!std::is_void_v<payload_type> &&
requires {
1178 { Traits::make_payload(
event_tag<E>{}) } -> std::convertible_to<payload_arg>;
1181 return post_impl(E, Traits::make_payload(
event_tag<E>{}));
1185 template <
class P = payload_type>
1186 requires(!std::is_void_v<P> && std::is_same_v<P, payload_type>)
1188 return post_impl(e, p);
1201 const result_type r = do_dispatch(e, payload_arg{}, now);
1202 if (!in_dispatch_) drain_posts(now);
1215 template <
class P = payload_type>
1216 requires(!std::is_void_v<P> && std::is_same_v<P, payload_type>)
1219 if (!in_dispatch_) drain_posts(now);
1237 template <event_type E,
class Arg>
1238 requires(!std::is_void_v<payload_type> &&
requires(
const Arg& a) {
1239 { Traits::make_payload(
event_tag<E>{}, a) } -> std::convertible_to<payload_arg>;
1246 template <event_type E>
1247 requires(!std::is_void_v<payload_type> &&
requires {
1248 { Traits::make_payload(
event_tag<E>{}) } -> std::convertible_to<payload_arg>;
1267 if (!slots_[i].armed)
continue;
1268 if (!found || detail::time_lt<time_type>(slots_[i].at, out)) {
1295 if (in_dispatch_)
return 0;
1297 for (
size_t fuse = 0; fuse < 4 *
state_count; ++fuse) {
1300 if (!slots_[i].armed || !detail::time_le<time_type>(slots_[i].at, now))
continue;
1302 detail::time_lt<time_type>(slots_[i].at, slots_[best].at))
1306 slots_[best].armed =
false;
1308 const event_type ev = spec_of(owner).deadline.event;
1309#if FSM_ENABLE_OBSERVER
1310 if (obs_ !=
nullptr && obs_->on_timeout_fired !=
nullptr)
1311 obs_->on_timeout_fired(obs_->user, owner, ev);
1313 do_dispatch(ev, payload_arg{}, now);
1325 if (!started_)
return false;
1327 for (
size_t fuse = 0; fuse <=
state_count; ++fuse) {
1328 if (cur == s)
return true;
1329 const auto& p = spec_of(cur).parent;
1339#if FSM_ENABLE_OBSERVER
1348#if FSM_ENABLE_INTROSPECTION
1360 detail::line_buf lb;
1363 lb.str(
" states, ");
1365 lb.str(
" rows, leaf=");
1367 lb.str(
", started=");
1368 lb.u64(started_ ? 1 : 0);
1369 write(
static_cast<const char*
>(lb.b));
1370 for (
size_t i = 0; i <
row_count; ++i) {
1371 const auto& r = Traits::rows[i];
1380 if (r.when !=
nullptr) lb.str(
"[guard]");
1381 if (r.then !=
nullptr) lb.str(
"/action");
1391 default: lb.str(
"?");
break;
1393 if (r.label !=
nullptr) {
1397 write(
static_cast<const char*
>(lb.b));
1400 if (!slots_[i].armed)
continue;
1405 lb.u64(
static_cast<uint64_t
>(slots_[i].at));
1406 write(
static_cast<const char*
>(lb.b));
1413 for (
size_t i = 0; i <
row_count; ++i) f(Traits::rows[i], i);
1419 for (
size_t i = 0; i <
state_count; ++i) f(Traits::states[i], i);
1424 if constexpr (detail::has_reason_names<Traits, reason_type>) {
1425 lb.str(Traits::reason_name(r));
1427 lb.u64(
static_cast<uint64_t
>(r));
1433 if constexpr (detail::has_state_names<Traits, state_type>) {
1434 lb.str(Traits::state_name(s));
1437 lb.u64(
static_cast<uint64_t
>(s));
1443 if constexpr (detail::has_event_names<Traits, event_type>) {
1444 lb.str(Traits::event_name(e));
1447 lb.u64(
static_cast<uint64_t
>(e));
1465#if FSM_ENABLE_OBSERVER
1466 if (in_notify_)
return r;
1468 if (obs_ !=
nullptr && obs_->on_event !=
nullptr) obs_->on_event(obs_->user, r);
1469 if (obs_ !=
nullptr && obs_->on_transition !=
nullptr &&
1471 obs_->on_transition(obs_->user, r.from, r.to, r.event);
1477 bool post_impl(
event_type e,
const payload_arg& p) {
1478 if (!started_ || post_count_ >= post_depth)
return false;
1479 posts_[(post_head_ + post_count_) % post_depth] = pending{e, p};
1488 for (
size_t fuse = 0; fuse < 4 * post_depth && post_count_ > 0; ++fuse) {
1489 const pending pn = posts_[post_head_];
1490 post_head_ =
static_cast<uint8_t
>((post_head_ + 1) % post_depth);
1492 do_dispatch(pn.e, pn.p, now);
1496 bool call_guard(
typename row_type::guard_fn g,
const payload_arg& p) {
1497 if constexpr (std::is_void_v<payload_type>) {
1505 void call_action(
typename row_type::action_fn a,
const payload_arg& p) {
1506 if (a ==
nullptr)
return;
1507 if constexpr (std::is_void_v<payload_type>) {
1516 const auto& sp = spec_of(s);
1517 if (sp.entry !=
nullptr) sp.entry(*ctx_);
1519 slots_[
static_cast<size_t>(s)] = {
static_cast<time_type>(now + sp.deadline.ms),
true};
1520 }
else if constexpr (detail::has_deadline_ms<Traits, state_type, context_type>) {
1524 constexpr uint64_t half =
static_cast<uint64_t
>(
static_cast<time_type>(-1)) / 2;
1525 uint64_t ms = Traits::deadline_ms(s, *ctx_);
1526#if FSM_ENABLE_DEADLINE_CHECKS
1529 if (ms > half) ms = half;
1530 slots_[
static_cast<size_t>(s)] = {
1537 slots_[
static_cast<size_t>(s)].armed =
false;
1538 const auto& sp = spec_of(s);
1539 if (sp.exit !=
nullptr) sp.exit(*ctx_);
1550 if (have_above && cur == above)
break;
1552 const auto& p = spec_of(cur).parent;
1556 while (n > 0) do_enter(path[--n], now);
1562 enter_path(target, target,
false, now);
1568 for (
size_t fuse = 0; fuse <=
state_count; ++fuse) {
1569 const auto& ini = spec_of(cur).initial;
1579 void exit_up(
state_type stop,
bool inclusive) {
1581 for (
size_t fuse = 0; fuse <=
state_count; ++fuse) {
1582 if (!inclusive && cur == stop)
return;
1584 if (inclusive && cur == stop)
return;
1585 const auto& p = spec_of(cur).parent;
1594 for (
size_t fuse = 0; fuse <=
state_count; ++fuse) {
1596 const auto& p = spec_of(cur).parent;
1604 size_t da = depth_[
static_cast<size_t>(a)];
1605 size_t db = depth_[
static_cast<size_t>(b)];
1607 a = spec_of(a).parent.s;
1611 b = spec_of(b).parent.s;
1614 for (
size_t fuse = 0; fuse <=
state_count; ++fuse) {
1619 const auto& pa = spec_of(a).parent;
1620 const auto& pb = spec_of(b).parent;
1636 call_action(r.then, p);
1646 if (tgt == main_src) {
1648 exit_up(main_src,
true);
1649 call_action(r.then, p);
1650 const auto& ps = spec_of(main_src).parent;
1653 bool have_lca =
false;
1654 const state_type lca = lca_of(main_src, tgt, have_lca);
1656 exit_up(lca,
false);
1657 call_action(r.then, p);
1658 enter_path(tgt, lca,
true, now);
1661 call_action(r.then, p);
1662 enter_path(tgt, tgt,
false, now);
1665 descend_initials(now);
1670#if FSM_ENABLE_PAYLOAD_CHECKS
1674 if constexpr (!std::is_void_v<payload_type> &&
1675 detail::has_payload_matches<Traits, event_type, payload_arg>) {
1685 in_dispatch_ =
true;
1686 uint16_t first_match =
no_row;
1688 for (
size_t fuse = 0; fuse <=
state_count; ++fuse) {
1689 for (uint16_t i = 0; i <
row_count; ++i) {
1690 const auto& r = Traits::rows[i];
1691 if (!detail::row_matches_state(r, lvl, e))
continue;
1692 if (first_match ==
no_row) first_match = i;
1693 if (r.when !=
nullptr && !call_guard(r.when, p))
continue;
1694 const result_type res = notify(execute(r, i, lvl, e, p, now));
1695 in_dispatch_ =
false;
1698 const auto& par = spec_of(lvl).parent;
1702 for (uint16_t i = 0; i <
row_count; ++i) {
1703 const auto& r = Traits::rows[i];
1704 if (!detail::row_matches_any(r, e))
continue;
1705 if (first_match ==
no_row) first_match = i;
1706 if (r.when !=
nullptr && !call_guard(r.when, p))
continue;
1707 const result_type res = notify(execute(r, i, leaf_, e, p, now));
1708 in_dispatch_ =
false;
1715 in_dispatch_ =
false;
The state machine engine interpreting a Traits' constexpr tables.
Definition fsm.hpp:972
std::remove_cvref_t< decltype(Traits::rows)> rows_array
Definition fsm.hpp:977
observer< state_type, event_type, reason_type > observer_type
Definition fsm.hpp:985
result_type dispatch(event_type e, time_type now)
Deliver an event and return the verdict.
Definition fsm.hpp:1200
bool next_deadline(time_type &out) const
Earliest armed deadline across the machine's own timeout slots.
Definition fsm.hpp:1264
state_type state() const
Current leaf state.
Definition fsm.hpp:1321
typename row_type::reason_type reason_type
Definition fsm.hpp:982
typename row_type::payload_type payload_type
void if none declared
Definition fsm.hpp:981
bool in(state_type s) const
True if s is the current leaf or one of its active ancestors.
Definition fsm.hpp:1324
typename Traits::Event event_type
Definition fsm.hpp:975
constexpr machine(context_type &ctx)
Bind the machine to a caller-owned context. Does not enter any state — call start() with the current ...
Definition fsm.hpp:1106
result_type dispatch(const Arg &arg, time_type now)
Typed dispatch: the event is a compile-time value, the payload is built by the traits' factory — wron...
Definition fsm.hpp:1241
std::remove_cvref_t< decltype(Traits::states)> specs_array
Definition fsm.hpp:978
bool post(event_type e, const P &p)
post() carrying input data (machines with a Payload).
Definition fsm.hpp:1187
unsigned service(time_type now)
Fire every expired deadline, earliest first.
Definition fsm.hpp:1294
static constexpr bool strict
Definition fsm.hpp:990
static constexpr size_t row_count
Definition fsm.hpp:989
bool started() const
True once start() has run.
Definition fsm.hpp:1337
typename rows_array::value_type row_type
Definition fsm.hpp:979
bool post()
Typed post() for events whose factory takes no data.
Definition fsm.hpp:1180
static constexpr size_t state_count
Definition fsm.hpp:987
static constexpr void for_each_state(F &&f)
Visit every state spec as f(spec, index) (constexpr-capable).
Definition fsm.hpp:1418
typename Traits::State state_type
Definition fsm.hpp:974
result< state_type, event_type, reason_type > result_type
Definition fsm.hpp:984
static constexpr void for_each_row(F &&f)
Visit every transition row as f(row, index) (constexpr-capable).
Definition fsm.hpp:1412
typename Traits::Context context_type
Definition fsm.hpp:976
void start(time_type now)
Enter the initial state chain (entry actions run, deadlines arm). Idempotent: a second call is a no-o...
Definition fsm.hpp:1121
bool post(const Arg &arg)
Typed post(): same factory-checked construction as the typed dispatch() wrapper, for run-to-completio...
Definition fsm.hpp:1171
result_type dispatch(event_type e, const P &p, time_type now)
Deliver an event carrying input data (machines with a Payload).
Definition fsm.hpp:1217
static constexpr void put_reason_name(detail::line_buf &lb, reason_type r)
Append the (hooked or numeric) name of refusal reason r to lb.
Definition fsm.hpp:1423
void set_observer(const observer_type *o)
Attach a debug observer (nullptr detaches). Not owned — the observer must outlive its attachment....
Definition fsm.hpp:1345
static constexpr size_t event_count
Definition fsm.hpp:988
bool post(event_type e)
Queue an event for delivery after the current dispatch completes (run-to-completion),...
Definition fsm.hpp:1152
static constexpr void put_event_name(detail::line_buf &lb, event_type e)
Append the (hooked or numeric) name of e to lb.
Definition fsm.hpp:1442
machine(const machine &)=delete
machine & operator=(const machine &)=delete
typename detail::time_of< Traits >::type time_type
Definition fsm.hpp:983
static constexpr void put_state_name(detail::line_buf &lb, state_type s)
Append the (hooked or numeric) name of s to lb.
Definition fsm.hpp:1432
size_t posts_pending() const
Number of post()ed events still queued (not yet drained).
Definition fsm.hpp:1163
typename specs_array::value_type spec_type
Definition fsm.hpp:980
result_type dispatch(time_type now)
Typed dispatch for events whose factory takes no data.
Definition fsm.hpp:1250
void dump(W &&write) const
Stream a human-readable description of the machine — the live table, current leaf,...
Definition fsm.hpp:1359
#define FSM_DEADLINE_ASSERT(cond)
Definition fsm.hpp:87
#define FSM_PAYLOAD_ASSERT(cond)
Definition fsm.hpp:69
constexpr internal_t internal
Row target: handle the event (run the action) without exit/entry.
Definition fsm.hpp:153
constexpr any_t any
Wildcard row source: matches any state, after all ancestor levels.
Definition fsm.hpp:146
constexpr parent_spec< State > child_of(State s)
Declare a state's parent in the hierarchy.
Definition fsm.hpp:317
target_kind
Kinds for row::to. unspecified is the poison default.
Definition fsm.hpp:208
parent_kind
Kinds for state_spec::parent. unspecified is the poison default.
Definition fsm.hpp:288
id_kind
Kinds for state_spec::state. unspecified is the poison default.
Definition fsm.hpp:276
on_kind
Kinds for row::on. unspecified is the poison default.
Definition fsm.hpp:196
constexpr timeout_spec< Event > after_dyn(Event e)
Declare a dynamic deadline: duration read from the context at entry time instead of the table.
Definition fsm.hpp:398
constexpr bool accepted(const result< State, Event, Reason > &r)
True when the event was acted on (transitioned or handled internally).
Definition fsm.hpp:137
constexpr timeout_spec< Event > after(uint32_t ms, Event e)
Declare "dispatch @p e after @p ms milliseconds in this state".
Definition fsm.hpp:371
constexpr detail::refuse_t< Reason > refuse(Reason r)
Make a declarative refusal target carrying a reason.
Definition fsm.hpp:271
status
Verdict category returned by machine::dispatch().
Definition fsm.hpp:107
@ refused_guard
Row(s) matched (from, event) but every guard declined.
@ refused_by_row
An explicit fsm::refuse row matched; result carries its reason.
@ handled_internally
A row matched with target fsm::internal; no state change.
@ transitioned
A row matched and an external transition completed.
@ refused_reentrant
dispatch() called from inside a guard/action; not allowed.
@ not_started
dispatch() before machine::start().
@ unhandled
No row considered this (state, event) pair at all.
constexpr uint16_t no_row
Sentinel row index meaning "no table row was involved".
Definition fsm.hpp:99
constexpr no_timeout_t no_timeout
State deadline declaration: this state has no timeout (explicitly).
Definition fsm.hpp:160
constexpr root_t root
Parent declaration: this state is a hierarchy root (no parent).
Definition fsm.hpp:295
from_kind
Kinds for row::from. unspecified is the poison default.
Definition fsm.hpp:178
initial_kind
Kinds for state_spec::initial. Default is leaf (no children).
Definition fsm.hpp:322
timeout_kind
Kinds for state_spec::deadline. unspecified is the poison default.
Definition fsm.hpp:343
Tag type for fsm::any.
Definition fsm.hpp:142
constexpr any_t()=default
Tag carrying an event as a compile-time value.
Definition fsm.hpp:173
constexpr event_tag()=default
Row source: a concrete state or the fsm::any wildcard.
Definition fsm.hpp:187
from_kind k
Definition fsm.hpp:188
constexpr from_spec(State st)
Definition fsm.hpp:191
constexpr from_spec(any_t)
Definition fsm.hpp:192
State s
Definition fsm.hpp:189
constexpr from_spec()=default
Initial-child declaration for composite states.
Definition fsm.hpp:326
State s
Definition fsm.hpp:328
initial_kind k
Definition fsm.hpp:327
Tag type for fsm::internal.
Definition fsm.hpp:149
constexpr internal_t()=default
Tag type for fsm::no_timeout.
Definition fsm.hpp:156
constexpr no_timeout_t()=default
Runtime-attachable debug observer: plain nullable function pointers.
Definition fsm.hpp:464
void(* on_event)(void *user, const result< State, Event, Reason > &r)
Definition fsm.hpp:467
void(* on_transition)(void *user, State from, State to, Event e)
Definition fsm.hpp:469
void(* on_timeout_fired)(void *user, State armed, Event e)
Definition fsm.hpp:471
void * user
Opaque pointer passed back to every hook.
Definition fsm.hpp:465
Row trigger event, with poison default for omitted .on.
Definition fsm.hpp:200
on_kind k
Definition fsm.hpp:201
constexpr on_spec()=default
Event e
Definition fsm.hpp:202
constexpr on_spec(Event ev)
Definition fsm.hpp:204
Parent link — a mandatory column: fsm::root or fsm::child_of(P).
Definition fsm.hpp:307
constexpr parent_spec()=default
parent_kind k
Definition fsm.hpp:308
State s
Definition fsm.hpp:309
constexpr parent_spec(parent_kind kk, State st)
Definition fsm.hpp:312
constexpr parent_spec(root_t)
Definition fsm.hpp:311
Dispatch verdict: what happened, where, and why.
Definition fsm.hpp:126
Reason reason
Refusal reason for refused_by_row; Reason{} otherwise.
Definition fsm.hpp:132
State from
Leaf state before dispatch.
Definition fsm.hpp:128
uint16_t row
Deciding (or first-matching) row index; fsm::no_row if none.
Definition fsm.hpp:131
State to
Leaf state after dispatch (== from unless transitioned).
Definition fsm.hpp:129
fsm::status status
Verdict category.
Definition fsm.hpp:127
Event event
The dispatched event.
Definition fsm.hpp:130
Tag type for fsm::root.
Definition fsm.hpp:291
constexpr root_t()=default
One transition-table row: {from, on, when, then, to}.
Definition fsm.hpp:416
const char * label
Definition fsm.hpp:432
Payload payload_type
Definition fsm.hpp:417
guard_fn when
Optional guard.
Definition fsm.hpp:426
Reason reason_type
Definition fsm.hpp:418
typename detail::fn_sigs< Context, Payload >::guard guard_fn
Definition fsm.hpp:420
target< State, Reason > to
Transition/internal/refuse (mandatory).
Definition fsm.hpp:428
action_fn then
Optional action.
Definition fsm.hpp:427
on_spec< Event > on
Trigger event (mandatory).
Definition fsm.hpp:425
from_spec< State > from
Source state or fsm::any (mandatory).
Definition fsm.hpp:424
typename detail::fn_sigs< Context, Payload >::action action_fn
Definition fsm.hpp:422
State identifier for a state_spec, with poison default.
Definition fsm.hpp:280
constexpr state_id(State st)
Definition fsm.hpp:284
State s
Definition fsm.hpp:282
id_kind k
Definition fsm.hpp:281
constexpr state_id()=default
One state's specification: hierarchy links, actions, deadline.
Definition fsm.hpp:441
hook_fn entry
Run on entry (before the deadline is armed).
Definition fsm.hpp:448
timeout_spec< Event > deadline
Mandatory: fsm::after(...) or fsm::no_timeout.
Definition fsm.hpp:450
initial_spec< State > initial
fsm::start_at(C) on composites; default leaf.
Definition fsm.hpp:447
parent_spec< State > parent
Mandatory: fsm::root or fsm::child_of(P).
Definition fsm.hpp:446
void(*)(Context &) hook_fn
Definition fsm.hpp:443
hook_fn exit
Run on exit (deadline already disarmed).
Definition fsm.hpp:449
state_id< State > state
Which state this spec describes (mandatory).
Definition fsm.hpp:445
Row target: external transition, internal handling, or refusal.
Definition fsm.hpp:243
target_kind k
Definition fsm.hpp:244
State s
Destination for target_kind::to.
Definition fsm.hpp:245
constexpr target(detail::refuse_t< Reason > f)
Definition fsm.hpp:250
constexpr target()=default
constexpr target(internal_t)
Definition fsm.hpp:249
Reason r
Reason for target_kind::refuse_.
Definition fsm.hpp:246
constexpr target(detail::to_t< State > t)
Definition fsm.hpp:248
Per-state deadline declaration — a mandatory column.
Definition fsm.hpp:354
timeout_kind k
Definition fsm.hpp:355
Event event
Event dispatched when the deadline expires.
Definition fsm.hpp:357
uint32_t ms
Duration in milliseconds after state entry.
Definition fsm.hpp:356
constexpr timeout_spec()=default
constexpr timeout_spec(no_timeout_t)
Definition fsm.hpp:359
constexpr timeout_spec(timeout_kind kk, uint32_t m, Event e)
Definition fsm.hpp:360