gen_statem: state and named timeouts, info events

Add the two timeout flavours, both surfacing as ordinary events matched
in on-state arms:

- cx.state_timeout(d): fires a state_timeout event after d in the current
  state, auto-reset by the loop on every real transition.
- cx.timeout(name, d): fires a timeout(name) event after d, surviving state
  changes, keyed by name, with cx.cancel_timeout(name).

Both ride the existing timer min-heap via send_after_to onto a new per-loop
system channel, selected above the inbox so a fire can't be starved by inbox
traffic. A local-id stamp on each fire lets a reset/cancel that loses the race
discard a stale fire. The macro grows an info: clause and folds three internal
Ev variants (Info, StateTimeout, Timeout) alongside cast/call, with new row
keywords info / state_timeout / timeout. Unmatched info silently drops (the
gen_server default); state/named timeouts have no default, so a state that can
see one must handle it or the match is non-exhaustive.

Rename the hand-written expansion-target example fused -> expanded, retire the
deprecated Switch demo machine (its round-trip / enter / panic-down coverage
moves onto the timer machine), and refresh the macro docs to the door machine.

cargo build --all-targets warning-free; cargo test green.
This commit is contained in:
smarm-agent
2026-06-20 12:22:45 +00:00
parent bfa513cd6d
commit acf67fef06
4 changed files with 600 additions and 132 deletions
+8 -4
View File
@@ -1,4 +1,4 @@
//! The **same** machine as `examples/gen_statem_fused.rs`, written through the
//! The **same** machine as `examples/gen_statem_expanded.rs`, written through the
//! `gen_statem!` macro. Diff this file against that one to see exactly what the
//! macro buys: every `// ===` section there that was boilerplate (the `Ev`
//! enum, the `DoorSm` struct, `start`, the whole `Machine` impl, the `enter`
@@ -21,7 +21,7 @@ use smarm::gen_statem;
use smarm::run;
use smarm::gen_statem::Reply;
// === user types (identical to gen_statem_fused.rs) =========================
// === user types (identical to gen_statem_expanded.rs) =========================
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Door {
@@ -87,7 +87,7 @@ fn on_unlock(key: u32) -> UnlockOutcome {
gen_statem! {
machine: DoorSm { state: Door, data: Data };
event: Ev { cast: Cast, call: Call };
event: Ev { cast: Cast, call: Call, info: () };
// You name the bindings the bodies use; the macro can't lend you its own
// `self`/`cx` across macro hygiene. `data` = &mut Data, `prev` = current
@@ -117,6 +117,10 @@ gen_statem! {
call Call::GetState(r) => { r.reply(prev); prev },
call Call::GetEnters(r) => { r.reply(data.enters); prev },
call Call::GetPushes(r) => { r.reply(data.pushes); prev },
// This machine arms no timeouts, so refuse them everywhere. (Info has a
// built-in silent-drop default, so it needs no row.)
state_timeout => unhandled,
timeout _ => unhandled,
}
}
@@ -145,7 +149,7 @@ fn main() {
// ===========================================================================
// BREAK-CASE MENU — the four guarantees, through the macro. Each fires exactly
// as it does in the hand-written gen_statem_fused.rs.
// as it does in the hand-written gen_statem_expanded.rs.
//
// 1. ORPHAN HANDLER (dead_code -> error):
// add `fn on_slam(_d: &mut Data) {}` and don't reference it.