Skip to main content

replay/
replay.rs

1//! Journal and deterministic replay.
2//!
3//! Records every transition in a journal, then replays the full history
4//! to verify the machine produces identical results. The machine is pure,
5//! so replay is just running the same events again.
6
7#![allow(clippy::print_stdout, clippy::enum_glob_use)]
8
9use ready_active_safe::prelude::*;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12enum Mode {
13    Ready,
14    Active,
15    Safe,
16}
17
18impl core::fmt::Display for Mode {
19    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
20        write!(f, "{self:?}")
21    }
22}
23
24#[derive(Debug, Clone)]
25enum Event {
26    PowerOn,
27    SensorReady,
28    BeginMeasurement,
29    DataCollected,
30    AnomalyDetected,
31    OperatorReset,
32}
33
34#[derive(Debug, Clone, PartialEq)]
35enum Command {
36    InitializeSensors,
37    StartDataAcquisition,
38    StoreReading,
39    TriggerAlarm,
40    ClearAlarm,
41    RecalibrateSensors,
42}
43
44struct LabInstrument;
45
46impl Machine for LabInstrument {
47    type Mode = Mode;
48    type Event = Event;
49    type Command = Command;
50
51    fn initial_mode(&self) -> Mode {
52        Mode::Ready
53    }
54
55    fn on_event(
56        &self,
57        mode: &Self::Mode,
58        event: &Self::Event,
59    ) -> Decision<Self::Mode, Self::Command> {
60        use Command::*;
61        use Event::*;
62        use Mode::*;
63
64        match (mode, event) {
65            (Ready, PowerOn) => stay().emit(InitializeSensors),
66            (Ready, BeginMeasurement) => transition(Active).emit(StartDataAcquisition),
67            (Active, DataCollected) => stay().emit(StoreReading),
68            (Active, AnomalyDetected) => transition(Safe).emit(TriggerAlarm),
69            (Safe, OperatorReset) => transition(Ready).emit_all([ClearAlarm, RecalibrateSensors]),
70            _ => ignore(),
71        }
72    }
73}
74
75fn main() {
76    let instrument = LabInstrument;
77    let mut runner = Runner::new(&instrument);
78
79    let events = [
80        Event::PowerOn,
81        Event::SensorReady,
82        Event::BeginMeasurement,
83        Event::DataCollected,
84        Event::DataCollected,
85        Event::AnomalyDetected,
86        Event::OperatorReset,
87        Event::BeginMeasurement,
88    ];
89
90    // Record every transition in the journal
91    let mut journal: InMemoryJournal<Mode, Event, Command> = InMemoryJournal::new();
92
93    println!("--- Recording transitions ---\n");
94    for event in events {
95        let from = runner.mode().clone();
96        let commands = runner.feed(&event);
97        let to = runner.mode().clone();
98
99        println!("  {from:?} + {event:?} => {to:?}");
100        if !commands.is_empty() {
101            println!("    commands: {commands:?}");
102        }
103
104        journal.record_step(&from, &to, &event, &commands);
105    }
106
107    println!("\n--- Journal: {} records ---\n", journal.len());
108    for record in journal.records() {
109        println!(
110            "  step {}: {:?} => {:?} (via {:?})",
111            record.step, record.from, record.to, record.event,
112        );
113    }
114
115    // Replay: feed the same events through a fresh machine and verify
116    println!("\n--- Replaying ---\n");
117    match journal.replay(&instrument, instrument.initial_mode()) {
118        Ok(final_mode) => {
119            println!("  replay verified: final mode = {final_mode:?}");
120            assert_eq!(final_mode, runner.mode().clone());
121        }
122        Err(err) => {
123            println!("  {err}");
124        }
125    }
126}