Skip to main content

recovery_cycle/
recovery_cycle.rs

1//! Recovery cycle.
2//!
3//! Demonstrates a system that faults, recovers, and returns to Active
4//! repeatedly. The machine stays pure; the runtime tracks recovery count
5//! and decides when to give up.
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)]
25enum Event {
26    Activate,
27    Fault,
28    RecoveryComplete,
29}
30
31#[derive(Debug, PartialEq)]
32enum Command {
33    StartWork,
34    EnterSafeState,
35    ResetHardware,
36}
37
38struct RecoverableSystem;
39
40impl Machine for RecoverableSystem {
41    type Mode = Mode;
42    type Event = Event;
43    type Command = Command;
44
45    fn initial_mode(&self) -> Mode {
46        Mode::Ready
47    }
48
49    fn on_event(&self, mode: &Mode, event: &Event) -> Decision<Mode, Command> {
50        use Command::*;
51        use Event::*;
52        use Mode::*;
53
54        match (mode, event) {
55            (Ready, Activate) => transition(Active).emit(StartWork),
56            (Active, Fault) => transition(Safe).emit(EnterSafeState),
57            (Safe, RecoveryComplete) => transition(Ready).emit(ResetHardware),
58            _ => ignore(),
59        }
60    }
61}
62
63fn main() {
64    let system = RecoverableSystem;
65    let mut runner = Runner::new(&system);
66    let max_recoveries: u32 = 3;
67    let mut recovery_count: u32 = 0;
68
69    println!("Starting in {:?}", runner.mode());
70
71    // Drive: Ready -> Active -> Safe -> Ready -> Active -> ...
72    loop {
73        // Ready -> Active
74        runner.feed_and_dispatch(&Event::Activate, |cmd| {
75            println!("  dispatch: {cmd:?}");
76        });
77        println!("Mode: {:?}", runner.mode());
78
79        // Active -> Safe (fault)
80        runner.feed_and_dispatch(&Event::Fault, |cmd| {
81            println!("  dispatch: {cmd:?}");
82        });
83        println!("Mode: {:?} (fault!)", runner.mode());
84
85        recovery_count = recovery_count.saturating_add(1);
86        println!("Recovery #{recovery_count}");
87
88        if recovery_count >= max_recoveries {
89            println!("Max recoveries ({max_recoveries}) reached, staying in Safe.");
90            break;
91        }
92
93        // Safe -> Ready (recover)
94        runner.feed_and_dispatch(&Event::RecoveryComplete, |cmd| {
95            println!("  dispatch: {cmd:?}");
96        });
97        println!("Mode: {:?} (recovered)", runner.mode());
98    }
99
100    println!(
101        "Final mode: {:?}, total recoveries: {recovery_count}",
102        runner.mode()
103    );
104    assert_eq!(runner.mode(), &Mode::Safe);
105    assert_eq!(recovery_count, max_recoveries);
106}