sim_restaurant/tui/
app.rs1use crossterm::event::{KeyCode, KeyEvent};
4
5use crate::recording::{PlotStat, RecordedSimulation};
6
7const PLAYBACK_STEPS: [usize; 6] = [1, 2, 4, 8, 16, 32];
8const WINDOW_SIZES: [u64; 7] = [30, 60, 120, 300, 600, 1800, 3600];
9
10#[derive(Clone, Copy, Debug)]
11pub struct PlotConfig {
12 pub stat: PlotStat,
13 pub windowed: bool,
14}
15
16#[derive(Debug)]
17pub struct App {
18 pub recording: RecordedSimulation,
19 pub frame_index: usize,
20 pub playing: bool,
21 pub selected_plot: usize,
22 pub plots: [PlotConfig; 4],
23 pub speed_index: usize,
24 pub window_size_index: usize,
25}
26
27impl App {
28 #[must_use]
29 pub fn new(recording: RecordedSimulation) -> Self {
30 Self {
31 recording,
32 frame_index: 0,
33 playing: true,
34 selected_plot: 0,
35 plots: [
36 PlotConfig {
37 stat: PlotStat::TillQueueLen,
38 windowed: false,
39 },
40 PlotConfig {
41 stat: PlotStat::ActiveTillWorkers,
42 windowed: false,
43 },
44 PlotConfig {
45 stat: PlotStat::KitchenQueueLen,
46 windowed: false,
47 },
48 PlotConfig {
49 stat: PlotStat::ActiveKitchenWorkers,
50 windowed: false,
51 },
52 ],
53 speed_index: 2,
54 window_size_index: 3,
55 }
56 }
57
58 #[must_use]
59 pub fn current_tick(&self) -> u64 {
60 self.recording
61 .timeline
62 .get(self.frame_index)
63 .map_or(0, |point| point.tick)
64 }
65
66 #[must_use]
67 pub fn current_snapshot(&self) -> &crate::recording::SimulationSnapshot {
68 &self.recording.timeline[self.frame_index].snapshot
69 }
70
71 #[must_use]
72 pub fn speed_label(&self) -> String {
73 format!("{}x", PLAYBACK_STEPS[self.speed_index])
74 }
75
76 #[must_use]
77 pub fn window_size_ticks(&self) -> u64 {
78 WINDOW_SIZES[self.window_size_index]
79 }
80
81 pub fn advance(&mut self) {
82 if self.frame_index + 1 >= self.recording.timeline.len() {
83 self.playing = false;
84 return;
85 }
86
87 let step = PLAYBACK_STEPS[self.speed_index];
88 self.frame_index = (self.frame_index + step).min(self.recording.timeline.len() - 1);
89 }
90
91 pub fn rewind(&mut self) {
92 let step = PLAYBACK_STEPS[self.speed_index];
93 self.frame_index = self.frame_index.saturating_sub(step);
94 }
95
96 #[must_use]
97 pub fn recent_events(&self, count: usize) -> Vec<&crate::recording::TimelineEvent> {
98 let current_tick = self.current_tick();
99 self.recording
100 .events
101 .iter()
102 .filter(|event| event.tick <= current_tick)
103 .rev()
104 .take(count)
105 .collect()
106 }
107
108 pub fn handle_tick(&mut self) {
109 if self.playing {
110 self.advance();
111 }
112 }
113
114 #[must_use]
115 pub fn handle_key(&mut self, key: KeyEvent) -> bool {
116 match key.code {
117 KeyCode::Char('q') | KeyCode::Esc => return true,
118 KeyCode::Char(' ') => {
119 self.playing = !self.playing;
120 }
121 KeyCode::Left | KeyCode::Char('h') => {
122 self.playing = false;
123 self.rewind();
124 }
125 KeyCode::Right | KeyCode::Char('l') => {
126 self.playing = false;
127 self.advance();
128 }
129 KeyCode::Char('g') | KeyCode::Home => {
130 self.playing = false;
131 self.frame_index = 0;
132 }
133 KeyCode::Char('G') | KeyCode::End => {
134 self.playing = false;
135 self.frame_index = self.recording.timeline.len().saturating_sub(1);
136 }
137 KeyCode::Char('[') | KeyCode::Char('-') => {
138 self.speed_index = self.speed_index.saturating_sub(1);
139 }
140 KeyCode::Char(']') | KeyCode::Char('+') => {
141 self.speed_index = (self.speed_index + 1).min(PLAYBACK_STEPS.len() - 1);
142 }
143 KeyCode::Char('{') => {
144 self.window_size_index = self.window_size_index.saturating_sub(1);
145 }
146 KeyCode::Char('}') => {
147 self.window_size_index = (self.window_size_index + 1).min(WINDOW_SIZES.len() - 1);
148 }
149 KeyCode::Char('1') => self.selected_plot = 0,
150 KeyCode::Char('2') => self.selected_plot = 1,
151 KeyCode::Char('3') => self.selected_plot = 2,
152 KeyCode::Char('4') => self.selected_plot = 3,
153 KeyCode::Char('w') => {
154 let plot = &mut self.plots[self.selected_plot];
155 plot.windowed = !plot.windowed;
156 }
157 KeyCode::Up | KeyCode::Char('k') => {
158 self.plots[self.selected_plot].stat =
159 self.plots[self.selected_plot].stat.previous();
160 }
161 KeyCode::Down | KeyCode::Char('j') => {
162 self.plots[self.selected_plot].stat = self.plots[self.selected_plot].stat.next();
163 }
164 _ => {}
165 }
166 false
167 }
168}