gen_statem: postpone events for replay after a transition
A `=> postpone` row (cast/call/info) defers the current event untouched, to be replayed after the next real transition. `handle` is now two-phase: a borrow-only postpone pre-pass that hands the event back as `Step::Postponed(ev)`, then the existing consuming `match (state, event)`. The loop owns a FIFO queue, drained in the new state ahead of further intake; a replayed event may postpone again. A postponed `call` keeps its Reply, so a later state answers it. `handle` returns `Step` (Postponed / Transitioned / Stayed) so the loop can see both deferral and transition without reading the state cell. `Resolution::Postpone` is removed: postpone is a pre-dispatch routing decision, not a consuming-dispatch outcome.
This commit is contained in:
@@ -33,6 +33,7 @@ enum Door {
|
||||
struct Data {
|
||||
enters: u32, // total state entries (incl. initial)
|
||||
pushes: u32, // times a push closed the door
|
||||
knocks: u32, // knocks answered (a Locked knock is postponed, then counted)
|
||||
}
|
||||
|
||||
enum Cast {
|
||||
@@ -40,12 +41,14 @@ enum Cast {
|
||||
Pull,
|
||||
Lock,
|
||||
Unlock(u32), // carries a key
|
||||
Knock, // counted when the door is reachable; postponed while Locked
|
||||
}
|
||||
|
||||
enum Call {
|
||||
GetState(Reply<Door>),
|
||||
GetEnters(Reply<u32>),
|
||||
GetPushes(Reply<u32>),
|
||||
GetKnocks(Reply<u32>),
|
||||
}
|
||||
|
||||
const CODE: u32 = 1234;
|
||||
@@ -100,15 +103,20 @@ gen_statem! {
|
||||
|
||||
on Door::Open => {
|
||||
cast Cast::Push => { on_push(data); Door::Closed },
|
||||
cast Cast::Knock => { data.knocks += 1; prev },
|
||||
cast Cast::Pull | Cast::Lock | Cast::Unlock(_) => unhandled,
|
||||
}
|
||||
on Door::Closed => {
|
||||
cast Cast::Pull => Door::Open,
|
||||
cast Cast::Lock => Door::Locked,
|
||||
cast Cast::Knock => { data.knocks += 1; prev },
|
||||
cast Cast::Push | Cast::Unlock(_) => unhandled,
|
||||
}
|
||||
on Door::Locked => {
|
||||
cast Cast::Unlock(key) => on_unlock(key), // branch -> UnlockOutcome
|
||||
// A knock at a locked door waits: defer it until the door is reachable,
|
||||
// where the replay counts it.
|
||||
cast Cast::Knock => postpone,
|
||||
cast Cast::Push | Cast::Pull | Cast::Lock => unhandled,
|
||||
}
|
||||
|
||||
@@ -117,6 +125,7 @@ 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 },
|
||||
call Call::GetKnocks(r) => { r.reply(data.knocks); 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,
|
||||
@@ -126,23 +135,26 @@ gen_statem! {
|
||||
|
||||
fn main() {
|
||||
run(|| {
|
||||
let door = DoorSm::start(Door::Closed, Data { enters: 0, pushes: 0 });
|
||||
let door = DoorSm::start(Door::Closed, Data { enters: 0, pushes: 0, knocks: 0 });
|
||||
|
||||
door.send(Ev::Cast(Cast::Lock)).unwrap(); // Closed -> Locked
|
||||
door.send(Ev::Cast(Cast::Knock)).unwrap(); // Locked: postponed (not yet counted)
|
||||
door.send(Ev::Cast(Cast::Push)).unwrap(); // Locked: Push invalid -> Unhandled
|
||||
door.send(Ev::Cast(Cast::Unlock(0))).unwrap(); // Locked: wrong key -> stay
|
||||
door.send(Ev::Cast(Cast::Unlock(CODE))).unwrap(); // Locked -> Closed
|
||||
door.send(Ev::Cast(Cast::Unlock(0))).unwrap(); // Locked: wrong key -> stay (knock still deferred)
|
||||
door.send(Ev::Cast(Cast::Unlock(CODE))).unwrap(); // Locked -> Closed; deferred Knock replays here
|
||||
door.send(Ev::Cast(Cast::Pull)).unwrap(); // Closed -> Open
|
||||
door.send(Ev::Cast(Cast::Push)).unwrap(); // Open -> Closed (pushes=1)
|
||||
|
||||
let st = door.call(|r| Ev::Call(Call::GetState(r))).unwrap();
|
||||
let enters = door.call(|r| Ev::Call(Call::GetEnters(r))).unwrap();
|
||||
let pushes = door.call(|r| Ev::Call(Call::GetPushes(r))).unwrap();
|
||||
let knocks = door.call(|r| Ev::Call(Call::GetKnocks(r))).unwrap();
|
||||
|
||||
println!("state={st:?} enters={enters} pushes={pushes}");
|
||||
println!("state={st:?} enters={enters} pushes={pushes} knocks={knocks}");
|
||||
assert_eq!(st, Door::Closed);
|
||||
assert_eq!(enters, 5); // Closed(start) + Locked + Closed + Open + Closed
|
||||
assert_eq!(pushes, 1);
|
||||
assert_eq!(knocks, 1); // the locked-door knock, replayed once unlocked
|
||||
println!("ok");
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user