1#![allow(clippy::print_stdout, clippy::enum_glob_use)]
8
9use std::collections::HashMap;
10use std::time::Instant;
11
12use ready_active_safe::prelude::*;
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15enum Mode {
16 Ready,
17 Active,
18 Safe,
19}
20
21impl core::fmt::Display for Mode {
22 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
23 write!(f, "{self:?}")
24 }
25}
26
27#[derive(Debug)]
28enum Event {
29 Start,
30 Fault,
31 Recover,
32}
33
34#[derive(Debug)]
35enum Command {
36 Initialize,
37 EmergencyStop,
38 Reset,
39}
40
41struct System;
42
43impl Machine for System {
44 type Mode = Mode;
45 type Event = Event;
46 type Command = Command;
47
48 fn initial_mode(&self) -> Mode {
49 Mode::Ready
50 }
51
52 fn on_event(&self, mode: &Mode, event: &Event) -> Decision<Mode, Command> {
53 use Command::*;
54 use Event::*;
55 use Mode::*;
56
57 match (mode, event) {
58 (Ready, Start) => transition(Active).emit(Initialize),
59 (Active, Fault) => transition(Safe).emit(EmergencyStop),
60 (Safe, Recover) => transition(Ready).emit(Reset),
61 _ => ignore(),
62 }
63 }
64}
65
66struct MetricsRunner<'a, M: Machine> {
68 runner: Runner<'a, M>,
69 transition_count: u64,
70 commands_dispatched: u64,
71 mode_entries: HashMap<Mode, u64>,
72 mode_entered_at: Instant,
73 mode_durations: HashMap<Mode, std::time::Duration>,
74}
75
76impl<'a> MetricsRunner<'a, System> {
77 fn new(runner: Runner<'a, System>) -> Self {
78 let initial = runner.mode().clone();
79 let mut mode_entries = HashMap::new();
80 *mode_entries.entry(initial).or_insert(0) += 1;
81
82 Self {
83 runner,
84 transition_count: 0,
85 commands_dispatched: 0,
86 mode_entries,
87 mode_entered_at: Instant::now(),
88 mode_durations: HashMap::new(),
89 }
90 }
91
92 fn feed(&mut self, event: &Event) {
93 let before = self.runner.mode().clone();
94 let commands = self.runner.feed(event);
95
96 let after = self.runner.mode().clone();
97
98 self.commands_dispatched += commands.len() as u64;
99
100 if before != after {
101 self.transition_count += 1;
102
103 let elapsed = self.mode_entered_at.elapsed();
104 *self.mode_durations.entry(before).or_default() += elapsed;
105 self.mode_entered_at = Instant::now();
106
107 *self.mode_entries.entry(after.clone()).or_insert(0) += 1;
108 println!(" [metrics] transition to {after:?}");
109 }
110
111 for cmd in &commands {
112 println!(" [metrics] dispatch: {cmd:?}");
113 }
114 }
115
116 fn print_summary(&self) {
117 println!("\n=== Metrics Summary ===");
118 println!("Transitions: {}", self.transition_count);
119 println!("Commands dispatched: {}", self.commands_dispatched);
120
121 println!("Mode entries:");
122 for (mode, count) in &self.mode_entries {
123 println!(" {mode:?}: {count}");
124 }
125
126 println!("Time in mode:");
127 for (mode, dur) in &self.mode_durations {
128 println!(" {mode:?}: {dur:?}");
129 }
130
131 println!("Current mode: {:?}", self.runner.mode());
132 }
133}
134
135fn main() {
136 let system = System;
137 let runner = Runner::new(&system);
138 let mut metrics = MetricsRunner::new(runner);
139
140 let events = [
141 Event::Start,
142 Event::Fault,
143 Event::Recover,
144 Event::Start,
145 Event::Fault,
146 Event::Recover,
147 ];
148
149 for event in &events {
150 println!("Event: {event:?}");
151 metrics.feed(event);
152 }
153
154 metrics.print_summary();
155
156 assert_eq!(metrics.transition_count, 6);
157 assert_eq!(metrics.commands_dispatched, 6);
158}