feat(observe)!: unify journal and trace into single observation pipeline

The codegen now emits one `__chart_observe!` invocation per observable
atom. The macro fans out at compile time to whichever sinks are enabled:
the in-memory journal (`feature = "journal"`), `defmt` (`trace-defmt`),
`log` (`trace-log`), and/or `tracing` (`trace-tracing`). Trace and
journal share the same vocabulary by construction — they cannot
diverge. With no sink feature on, every site expands to `()` (zero
overhead). Multiple trace backends now compose; previously they were
mutually exclusive.

Trace lines use a logfmt-style format with a `[statechart:Name]` prefix
and `key=value` pairs, machine-parseable and greppable. Twenty verbs
cover every observable atom — entries/exits with begin/end pairs,
actions with kind, durings, transitions with the reason that triggered
them, event lifecycle (received/delivered/dropped/queued/failed),
timers, and termination.

BREAKING CHANGES (chart syntax unchanged; observability surface only):

- `TraceEvent::TransitionFired` now carries `reason: TransitionReason`
  showing whether the transition was driven by an event, a timer, or
  internal logic.
- New `TraceEvent` variants for phase pairing: `EnterBegan`, `ExitBegan`,
  `TransitionComplete`, `EventReceived`.
- Trace prefix changed from `[Name]` to `[statechart:Name]`.
- The chart-level `trace;` keyword is parsed for backwards compatibility
  but no longer gates emission.
- `__chart_trace!` / `__chart_journal!` macros removed (doc-hidden).

Verification: 159 integration tests pass, 0 missed mutants, miri 68
tests clean, Creusot 14/14 VCs discharged. Zero overhead confirmed via
`cargo expand` — every observation site expands to `{}` with no sinks
enabled.
This commit is contained in:
swaits committed 2026-04-28 15:24:14 -06:00
1 parent a6d6cd2674
commit 4ea7463089
21 files changed
+2026 -488

No files matched your search

+413 -175
View File
@@ -88,22 +88,6 @@ fn compute_chart_hash(ir: &Ir) -> u64 {
h
}
/// Emit a single trace call routed through the hsmc-internal dispatcher
/// (`::hsmc::__chart_trace!`), or nothing if the chart didn't declare
/// `trace;`. The dispatcher picks the actual backend (defmt / log /
/// tracing / no-op) based on which `hsmc/trace-*` cargo feature the
/// user enabled in their `Cargo.toml` — no extra wiring required.
///
/// Zero-overhead when `trace_enabled` is false: generates an empty
/// TokenStream so the compiler sees no extra work at the call site.
fn emit_trace_call(trace_enabled: bool, args: TokenStream) -> TokenStream {
if trace_enabled {
quote! { ::hsmc::__chart_trace!(#args); }
} else {
TokenStream::new()
}
}
/// For each user-declared leaf state that has one or more `during:` activities
/// active (either declared on it, on any ancestor, or on root), return its
/// state id and the flattened list of durings in leaf-first order. Durings
@@ -533,15 +517,29 @@ pub fn generate(ir: &Ir) -> TokenStream {
})
.collect();
// Per-id name lookups, only emitted when the chart opted into tracing.
// Chart-name string is reused at every trace site as a literal.
// ── Per-id name lookup tables ─────────────────────────────────────
//
// Generated unconditionally at chart scope so any code in the
// emitted module (machine impl, action context, etc.) can resolve
// a u16 id to a `&'static str`. The tables are dead-code-eliminated
// when no observation feature is on: with neither `journal` nor any
// `trace-*` feature enabled, the `__chart_observe!` macro arms are
// empty, so the call sites never reference these helpers and the
// linker drops them. Marked `dead_code` to silence rustc when off.
let chart_name_lit = ir.name.to_string();
let state_name_arms: Vec<_> = ir
.states
.iter()
.map(|s| {
let sid = s.id;
let nm = s.name.to_string();
let nm = if s.parent.is_none() {
// Synthesized root: render as the chart name (per spec
// the root state IS the chart).
ir.name.to_string()
} else {
s.name.to_string()
};
quote! { #sid => #nm, }
})
.collect();
@@ -555,174 +553,357 @@ pub fn generate(ir: &Ir) -> TokenStream {
quote! { #aid => #nm, }
})
.collect();
let trace_helpers = if ir.trace_enabled {
quote! {
#[inline(always)]
#[doc(hidden)]
fn __state_name(sid: u16) -> &'static str {
match sid {
#(#state_name_arms)*
_ => "<unknown>",
}
}
#[inline(always)]
#[doc(hidden)]
fn __action_name(aid: u16) -> &'static str {
match aid {
#(#action_name_arms)*
_ => "<unknown>",
}
let event_name_arms: Vec<_> = ir
.event_variants
.iter()
.enumerate()
.map(|(i, v)| {
let eid = i as u16;
let nm = v.name.to_string();
quote! { #eid => #nm, }
})
.collect();
let timer_name_arms: Vec<_> = ir
.duration_triggers
.iter()
.enumerate()
.map(|(i, d)| {
let tid = i as u16;
// The intern key looks like "<state>::<expr>:<repeat>". For
// human readability render the expr text — fall back to
// numeric id if the key parses oddly.
let label = format!("t{}", tid);
// Try to extract the expression body so the trace shows
// something like `Duration::from_secs(5)`. Using `key` as
// a stable label is OK for now; the expr token stream isn't
// round-tripped through `to_string()` cleanly across rustc
// versions, so prefer a synthesized short name.
let _ = &d.key; // mark used for non-warn
quote! { #tid => #label, }
})
.collect();
// Per-(state, during-index) name lookup. The during's fn-name is
// the closest thing to a stable identifier the user wrote.
let during_name_arms: Vec<_> = {
let mut arms = Vec::new();
for s in &ir.states {
let sid = s.id;
for (di, d) in s.durings.iter().enumerate() {
let did = di as u16;
let nm = d.fn_name.to_string();
arms.push(quote! { (#sid, #did) => #nm, });
}
}
} else {
TokenStream::new()
arms
};
// Pre-rendered transition reason strings for trace output.
// `event:<VariantName>` for event-driven transitions.
let event_reason_str_arms: Vec<_> = ir
.event_variants
.iter()
.enumerate()
.map(|(i, v)| {
let eid = i as u16;
let s = format!("event:{}", v.name);
quote! { #eid => #s, }
})
.collect();
// `timer:<state>/<timer>` for timer-driven transitions. We
// enumerate (state, timer) pairs that actually occur.
let timer_reason_str_arms: Vec<_> = {
let mut arms = Vec::new();
for s in &ir.states {
for &tid in &s.owned_timers {
let sid = s.id;
let sname = if s.parent.is_none() {
ir.name.to_string()
} else {
s.name.to_string()
};
let tlabel = format!("t{}", tid);
let s_str = format!("timer:{}/{}", sname, tlabel);
arms.push(quote! { (#sid, #tid) => #s_str, });
}
}
arms
};
let name_helpers = quote! {
#[allow(dead_code)]
#[doc(hidden)]
#[inline]
fn __state_name(sid: u16) -> &'static str {
match sid {
#(#state_name_arms)*
_ => "<unknown>",
}
}
#[allow(dead_code)]
#[doc(hidden)]
#[inline]
fn __action_name(aid: u16) -> &'static str {
match aid {
#(#action_name_arms)*
_ => "<unknown>",
}
}
#[allow(dead_code, unreachable_patterns)]
#[doc(hidden)]
#[inline]
fn __event_name(eid: u16) -> &'static str {
match eid {
#(#event_name_arms)*
_ => "<unknown>",
}
}
#[allow(dead_code, unreachable_patterns)]
#[doc(hidden)]
#[inline]
fn __timer_name(tid: u16) -> &'static str {
match tid {
#(#timer_name_arms)*
_ => "<unknown>",
}
}
#[allow(dead_code, unreachable_patterns, unused_variables)]
#[doc(hidden)]
#[inline]
fn __during_name(sid: u16, did: u16) -> &'static str {
match (sid, did) {
#(#during_name_arms)*
_ => "<unknown>",
}
}
#[allow(dead_code, unreachable_patterns)]
#[doc(hidden)]
#[inline]
fn __event_reason_str(eid: u16) -> &'static str {
match eid {
#(#event_reason_str_arms)*
_ => "event:?",
}
}
#[allow(dead_code, unreachable_patterns, unused_variables)]
#[doc(hidden)]
#[inline]
fn __timer_reason_str(sid: u16, tid: u16) -> &'static str {
match (sid, tid) {
#(#timer_reason_str_arms)*
_ => "timer:?",
}
}
#[allow(dead_code)]
#[doc(hidden)]
#[inline]
fn __from_name(opt: Option<u16>) -> &'static str {
match opt {
Some(id) => __state_name(id),
None => "<none>",
}
}
};
// ── Observation hook snippets ─────────────────────────────────────
//
// Trace and journal want the same call sites: enter, exit, action,
// transition. We emit a single `observe_*` snippet per site that
// contains BOTH the trace call (gated on `ir.trace_enabled` at
// parse time, may also be no-op'd by the trace-* feature gate) and
// the journal call (gated on the `journal` cargo feature inside the
// macro). Either gate independently produces zero overhead when off.
// Each snippet emits ONE `__chart_observe!` invocation. The macro
// dispatches per-variant to whichever sinks (journal + trace-*) are
// enabled at compile time. Trace and journal share the same call
// sites by construction — they cannot diverge.
//
// Journal-only sites (timers, durings, emits, event dispatch,
// termination) get plain `journal_*` snippets — trace doesn't have
// a string format for them.
// Trace-side primitives, recreated here so each observe_* snippet
// can splice them in.
let trace_enter_call = emit_trace_call(
ir.trace_enabled,
quote! { enter #chart_name_lit, Self::__state_name(sid) },
);
let trace_exit_call = emit_trace_call(
ir.trace_enabled,
quote! { exit #chart_name_lit, Self::__state_name(sid) },
);
let trace_action_call = emit_trace_call(
ir.trace_enabled,
quote! { action #chart_name_lit, Self::__action_name(aid) },
);
let trace_transition_call = emit_trace_call(
ir.trace_enabled,
quote! {
transition #chart_name_lit,
self.current.map(Self::__state_name).unwrap_or("<none>"),
Self::__state_name(target)
},
);
// Names (state/action/event/timer/during) are passed as runtime
// lookups against the small static tables generated above. With no
// sink feature on, the macro arm body is empty and the lookup is
// never expanded — zero overhead at the call site.
let observe_started = quote! {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::Started { chart_hash: Self::CHART_HASH }
::hsmc::__chart_observe!(
Started,
&mut self.__journal,
#chart_name_lit,
Self::CHART_HASH
);
};
let observe_enter_began = quote! {
::hsmc::__chart_observe!(
EnterBegan,
&mut self.__journal,
#chart_name_lit,
sid,
__state_name(sid)
);
};
let observe_entered = quote! {
#trace_enter_call
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::Entered { state: sid }
::hsmc::__chart_observe!(
Entered,
&mut self.__journal,
#chart_name_lit,
sid,
__state_name(sid)
);
};
let observe_exit_began = quote! {
::hsmc::__chart_observe!(
ExitBegan,
&mut self.__journal,
#chart_name_lit,
sid,
__state_name(sid)
);
};
let observe_exited = quote! {
#trace_exit_call
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::Exited { state: sid }
::hsmc::__chart_observe!(
Exited,
&mut self.__journal,
#chart_name_lit,
sid,
__state_name(sid)
);
};
let observe_action_entry = quote! {
#trace_action_call
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::ActionInvoked {
state: sid,
action: aid,
kind: ::hsmc::ActionKind::Entry,
}
::hsmc::__chart_observe!(
ActionInvoked,
&mut self.__journal,
#chart_name_lit,
sid, __state_name(sid),
aid, __action_name(aid),
Entry
);
};
let observe_action_exit = quote! {
#trace_action_call
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::ActionInvoked {
state: sid,
action: aid,
kind: ::hsmc::ActionKind::Exit,
}
::hsmc::__chart_observe!(
ActionInvoked,
&mut self.__journal,
#chart_name_lit,
sid, __state_name(sid),
aid, __action_name(aid),
Exit
);
};
let observe_action_handler = quote! {
#trace_action_call
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::ActionInvoked {
state: __handler_state,
action: aid,
kind: ::hsmc::ActionKind::Handler,
}
::hsmc::__chart_observe!(
ActionInvoked,
&mut self.__journal,
#chart_name_lit,
__handler_state, __state_name(__handler_state),
aid, __action_name(aid),
Handler
);
};
let observe_transition = quote! {
#trace_transition_call
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::TransitionFired {
from: self.current,
to: target,
}
// TransitionFired — three call-site flavours. Codegen splices in
// the appropriate one based on what triggered the transition.
let observe_transition_fired_event = quote! {
::hsmc::__chart_observe!(
TransitionFired,
&mut self.__journal,
#chart_name_lit,
self.current, __from_name(self.current),
target, __state_name(target),
::hsmc::TransitionReason::Event { event: __ev_id },
__event_reason_str(__ev_id)
);
};
let observe_transition_fired_timer = quote! {
::hsmc::__chart_observe!(
TransitionFired,
&mut self.__journal,
#chart_name_lit,
self.current, __from_name(self.current),
target, __state_name(target),
::hsmc::TransitionReason::Timer {
state: __t_state,
timer: __t_trigger,
},
__timer_reason_str(__t_state, __t_trigger)
);
};
#[allow(dead_code)]
let _observe_transition_fired_internal = quote! {
::hsmc::__chart_observe!(
TransitionFired,
&mut self.__journal,
#chart_name_lit,
self.current, __from_name(self.current),
target, __state_name(target),
::hsmc::TransitionReason::Internal,
"internal"
);
};
let observe_transition_complete = quote! {
::hsmc::__chart_observe!(
TransitionComplete,
&mut self.__journal,
#chart_name_lit,
__t_complete_from, __from_name(__t_complete_from),
target, __state_name(target)
);
};
let observe_event_received = quote! {
::hsmc::__chart_observe!(
EventReceived,
&mut self.__journal,
#chart_name_lit,
__ev_id,
__event_name(__ev_id)
);
};
let observe_event_delivered = quote! {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::EventDelivered {
handler_state: __handler_state,
event: __ev_id,
}
::hsmc::__chart_observe!(
EventDelivered,
&mut self.__journal,
#chart_name_lit,
__ev_id, __event_name(__ev_id),
__handler_state, __state_name(__handler_state)
);
};
let observe_event_dropped = quote! {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::EventDropped { event: __ev_id }
::hsmc::__chart_observe!(
EventDropped,
&mut self.__journal,
#chart_name_lit,
__ev_id, __event_name(__ev_id)
);
};
let observe_timer_armed = quote! {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::TimerArmed {
state: sid,
timer: tid,
ns: __duration_for(tid).as_nanos() as u64,
}
::hsmc::__chart_observe!(
TimerArmed,
&mut self.__journal,
#chart_name_lit,
sid, __state_name(sid),
tid, __timer_name(tid),
__duration_for(tid).as_nanos() as u64
);
};
let observe_timer_cancelled = quote! {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::TimerCancelled { state: sid, timer: tid }
::hsmc::__chart_observe!(
TimerCancelled,
&mut self.__journal,
#chart_name_lit,
sid, __state_name(sid),
tid, __timer_name(tid)
);
};
let observe_timer_fired = quote! {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::TimerFired { state: __t_state, timer: __t_trigger }
::hsmc::__chart_observe!(
TimerFired,
&mut self.__journal,
#chart_name_lit,
__t_state, __state_name(__t_state),
__t_trigger, __timer_name(__t_trigger)
);
};
let observe_terminated = quote! {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::Terminated
::hsmc::__chart_observe!(
Terminated,
&mut self.__journal,
#chart_name_lit
);
};
let observe_terminate_requested = quote! {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::TerminateRequested { event: __ev_id }
::hsmc::__chart_observe!(
TerminateRequested,
&mut self.__journal,
#chart_name_lit,
__ev_id, __event_name(__ev_id)
);
};
@@ -741,9 +922,12 @@ pub fn generate(ir: &Ir) -> TokenStream {
let __dn = Self::__durings_count(sid);
let mut __di: u16 = 0;
while __di < __dn {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::DuringStarted { state: sid, during: __di }
::hsmc::__chart_observe!(
DuringStarted,
&mut self.__journal,
#chart_name_lit,
sid, __state_name(sid),
__di, __during_name(sid, __di)
);
__di += 1;
}
@@ -752,9 +936,12 @@ pub fn generate(ir: &Ir) -> TokenStream {
let __dn = Self::__durings_count(sid);
let mut __di: u16 = 0;
while __di < __dn {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::DuringCancelled { state: sid, during: __di }
::hsmc::__chart_observe!(
DuringCancelled,
&mut self.__journal,
#chart_name_lit,
sid, __state_name(sid),
__di, __during_name(sid, __di)
);
__di += 1;
}
@@ -1766,14 +1953,18 @@ pub fn generate(ir: &Ir) -> TokenStream {
let __ev_id = __event_to_id(&event);
let __result = self.__queue.push(event);
if __result.is_ok() {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::EmitQueued { event: __ev_id }
::hsmc::__chart_observe!(
EmitQueued,
&mut *self.__journal,
#chart_name_lit,
__ev_id, __event_name(__ev_id)
);
} else {
::hsmc::__chart_journal!(
self.__journal,
::hsmc::TraceEvent::EmitFailed { event: __ev_id }
::hsmc::__chart_observe!(
EmitFailed,
&mut *self.__journal,
#chart_name_lit,
__ev_id, __event_name(__ev_id)
);
}
__result
@@ -1807,6 +1998,8 @@ pub fn generate(ir: &Ir) -> TokenStream {
const __DEPTH: [u8; #n_states] = [#(#depth_table),*];
const __DEFAULT_CHILD: [Option<u16>; #n_states] = [#(#default_child_table),*];
#name_helpers
#[allow(dead_code, unreachable_patterns)]
fn __state_id_to_public(id: u16) -> #state_enum_name {
match id {
@@ -2016,13 +2209,13 @@ pub fn generate(ir: &Ir) -> TokenStream {
}
if let Some(ev) = self.queue.pop_front() {
let __ev_id = __event_to_id(&ev);
#observe_event_received
if matches!(__check_terminate(&ev), TerminateCheck::Yes) {
let __ev_id = __event_to_id(&ev);
#observe_terminate_requested
self.do_terminate();
return None;
}
let __ev_id = __event_to_id(&ev);
if __ev_id != u16::MAX {
let mut __cur = self.current;
let mut __delivered = false;
@@ -2030,7 +2223,10 @@ pub fn generate(ir: &Ir) -> TokenStream {
if let Some((actions, target)) = __handler_lookup(s, 0, __ev_id) {
let __handler_state = s;
#observe_event_delivered
self.run_handlers(s, actions, target, Some(&ev));
self.run_handlers(
s, actions, target, Some(&ev),
1u8, __ev_id, 0u16,
);
__delivered = true;
break;
}
@@ -2077,13 +2273,13 @@ pub fn generate(ir: &Ir) -> TokenStream {
}
if let Some(ev) = self.queue.pop_front() {
let __ev_id = __event_to_id(&ev);
#observe_event_received
if matches!(__check_terminate(&ev), TerminateCheck::Yes) {
let __ev_id = __event_to_id(&ev);
#observe_terminate_requested
self.do_terminate().await;
return None;
}
let __ev_id = __event_to_id(&ev);
if __ev_id != u16::MAX {
let mut __cur = self.current;
let mut __delivered = false;
@@ -2091,7 +2287,10 @@ pub fn generate(ir: &Ir) -> TokenStream {
if let Some((actions, target)) = __handler_lookup(s, 0, __ev_id) {
let __handler_state = s;
#observe_event_delivered
self.run_handlers(s, actions, target, Some(&ev)).await;
self.run_handlers(
s, actions, target, Some(&ev),
1u8, __ev_id, 0u16,
).await;
__delivered = true;
break;
}
@@ -2145,14 +2344,21 @@ pub fn generate(ir: &Ir) -> TokenStream {
Ok(())
}
// Reason kind passed by callers to `run_handlers`:
// 1 → event-driven (reason_a = event id)
// 2 → timer-driven (reason_a = state id, reason_b = timer id)
// 0 → internal/other (unused today; reserved)
#[cfg(not(any(feature = "tokio", feature = "embassy")))]
#[allow(unused_variables)]
#[allow(unused_variables, clippy::too_many_arguments)]
fn run_handlers(
&mut self,
source_state: u16,
actions: &'static [u16],
target: Option<u16>,
event: Option<&#ev_ty>,
__reason_kind: u8,
__reason_a: u16,
__reason_b: u16,
) {
let __handler_state = source_state;
for &aid in actions {
@@ -2160,19 +2366,36 @@ pub fn generate(ir: &Ir) -> TokenStream {
self.run_action(aid, event);
if self.terminated { return; }
}
if let Some(tgt) = target {
self.transition(source_state, tgt);
if let Some(target) = target {
match __reason_kind {
1 => {
let __ev_id = __reason_a;
#observe_transition_fired_event
}
2 => {
let __t_state = __reason_a;
let __t_trigger = __reason_b;
#observe_transition_fired_timer
}
_ => {}
}
let __t_complete_from = self.current;
self.transition(source_state, target);
#observe_transition_complete
}
}
#[cfg(any(feature = "tokio", feature = "embassy"))]
#[allow(unused_variables)]
#[allow(unused_variables, clippy::too_many_arguments)]
async fn run_handlers(
&mut self,
source_state: u16,
actions: &'static [u16],
target: Option<u16>,
event: Option<&#ev_ty>,
__reason_kind: u8,
__reason_a: u16,
__reason_b: u16,
) {
let __handler_state = source_state;
for &aid in actions {
@@ -2180,8 +2403,22 @@ pub fn generate(ir: &Ir) -> TokenStream {
self.run_action(aid, event).await;
if self.terminated { return; }
}
if let Some(tgt) = target {
self.transition(source_state, tgt).await;
if let Some(target) = target {
match __reason_kind {
1 => {
let __ev_id = __reason_a;
#observe_transition_fired_event
}
2 => {
let __t_state = __reason_a;
let __t_trigger = __reason_b;
#observe_transition_fired_timer
}
_ => {}
}
let __t_complete_from = self.current;
self.transition(source_state, target).await;
#observe_transition_complete
}
}
@@ -2272,7 +2509,6 @@ pub fn generate(ir: &Ir) -> TokenStream {
#[cfg(not(any(feature = "tokio", feature = "embassy")))]
fn transition(&mut self, _source_matched_state: u16, target: u16) {
#observe_transition
if Self::is_up_transition(self.current, target) {
if let Some(mut cur) = self.current {
while cur != target {
@@ -2319,7 +2555,6 @@ pub fn generate(ir: &Ir) -> TokenStream {
#[cfg(any(feature = "tokio", feature = "embassy"))]
async fn transition(&mut self, _source_matched_state: u16, target: u16) {
#observe_transition
if Self::is_up_transition(self.current, target) {
if let Some(mut cur) = self.current {
while cur != target {
@@ -2394,7 +2629,7 @@ pub fn generate(ir: &Ir) -> TokenStream {
#[cfg(not(any(feature = "tokio", feature = "embassy")))]
#[allow(unused_variables)]
fn enter_state_no_descent(&mut self, sid: u16) {
#observe_entered
#observe_enter_began
let entries = Self::entries_of(sid);
for &aid in entries {
#observe_action_entry
@@ -2408,12 +2643,13 @@ pub fn generate(ir: &Ir) -> TokenStream {
}
self.current = Some(sid);
#observe_durings_started
#observe_entered
}
#[cfg(any(feature = "tokio", feature = "embassy"))]
#[allow(unused_variables)]
async fn enter_state_no_descent(&mut self, sid: u16) {
#observe_entered
#observe_enter_began
let entries = Self::entries_of(sid);
for &aid in entries {
#observe_action_entry
@@ -2427,6 +2663,7 @@ pub fn generate(ir: &Ir) -> TokenStream {
}
self.current = Some(sid);
#observe_durings_started
#observe_entered
}
#[cfg(not(any(feature = "tokio", feature = "embassy")))]
@@ -2451,7 +2688,9 @@ pub fn generate(ir: &Ir) -> TokenStream {
#[allow(unused_variables)]
fn exit_state(&mut self, sid: u16) {
// Per spec §"Entry and exit ordering":
// 1. cancel durings 2. cancel timers 3. exit actions 4. Exited
// 1. ExitBegan 2. cancel durings 3. cancel timers
// 4. exit actions 5. Exited
#observe_exit_began
#observe_durings_cancelled
for &tid in __owned_timers(sid) {
#observe_timer_cancelled
@@ -2469,6 +2708,7 @@ pub fn generate(ir: &Ir) -> TokenStream {
#[cfg(any(feature = "tokio", feature = "embassy"))]
#[allow(unused_variables)]
async fn exit_state(&mut self, sid: u16) {
#observe_exit_began
#observe_durings_cancelled
for &tid in __owned_timers(sid) {
#observe_timer_cancelled
@@ -2504,8 +2744,6 @@ pub fn generate(ir: &Ir) -> TokenStream {
}
}
#trace_helpers
#[cfg(not(any(feature = "tokio", feature = "embassy")))]
fn do_terminate(&mut self) {
if let Some(mut cur) = self.current {
@@ -2543,15 +2781,15 @@ pub fn generate(ir: &Ir) -> TokenStream {
#[cfg(not(any(feature = "tokio", feature = "embassy")))]
fn dispatch_trigger(&mut self, state: u16, kind: u8, trig: u16) {
if let Some((actions, target)) = __handler_lookup(state, kind, trig) {
// Timer-driven dispatch: no event payload in scope.
self.run_handlers(state, actions, target, None);
// Timer-driven dispatch: no event payload, reason = timer.
self.run_handlers(state, actions, target, None, 2u8, state, trig);
}
}
#[cfg(any(feature = "tokio", feature = "embassy"))]
async fn dispatch_trigger(&mut self, state: u16, kind: u8, trig: u16) {
if let Some((actions, target)) = __handler_lookup(state, kind, trig) {
self.run_handlers(state, actions, target, None).await;
self.run_handlers(state, actions, target, None, 2u8, state, trig).await;
}
}
}