retroglyph_recorder/
replay.rs1use retroglyph_core::app::{App, Flow, Frame};
4use retroglyph_core::backend::Backend;
5use retroglyph_core::terminal::Terminal;
6use retroglyph_core::testing::InputRecording;
7
8pub fn replay_live<B, A>(
27 mut term: Terminal<B>,
28 app: &mut A,
29 recording: &InputRecording,
30) -> Result<(), B::Error>
31where
32 B: Backend,
33 A: App<B>,
34{
35 app.init(&mut term);
36 let mut frame_count = 0u64;
37 for (delay, event) in recording.events() {
38 if !delay.is_zero() {
39 std::thread::sleep(*delay);
40 }
41 term.backend_mut().push_event(event.clone());
42
43 let frame = Frame::new(*delay, frame_count);
44 frame_count = frame_count.wrapping_add(1);
45
46 let present_count_before = term.present_count();
47 let flow = app.update(&mut term, &frame);
48 if flow != Flow::Idle && term.present_count() == present_count_before {
49 term.present()?;
50 }
51 if flow == Flow::Exit {
52 return Ok(());
53 }
54 }
55 Ok(())
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use retroglyph_core::backend::Headless;
62 use retroglyph_core::color::Style;
63 use retroglyph_core::event::{Event, KeyCode, KeyEvent, KeyModifiers};
64 use retroglyph_core::testing::TestHarness;
65 use std::time::Duration;
66
67 struct Counter {
70 presses: u32,
71 }
72
73 impl<B: Backend> App<B> for Counter {
74 fn update(&mut self, term: &mut Terminal<B>, _frame: &Frame) -> Flow {
75 for event in term.drain_events() {
76 if matches!(event, Event::Key(_)) {
77 self.presses += 1;
78 }
79 }
80 term.surface().put(
81 (0, 0),
82 char::from_digit(self.presses, 10).unwrap_or('?'),
83 Style::default(),
84 );
85 Flow::Continue
86 }
87 }
88
89 fn recording() -> InputRecording {
90 let mut recording = InputRecording::new(4, 1);
91 recording.push(
92 Duration::ZERO,
93 Event::Key(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE)),
94 );
95 recording.push(
96 Duration::from_millis(1),
97 Event::Key(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::NONE)),
98 );
99 recording
100 }
101
102 #[test]
103 fn replay_live_reaches_the_same_state_as_test_harness_replay() {
104 let recording = recording();
105
106 let term = Terminal::new(Headless::new(recording.width(), recording.height()));
107 let mut live_app = Counter { presses: 0 };
108 replay_live(term, &mut live_app, &recording).expect("replay_live");
109
110 let mut harness = TestHarness::from_recording(&recording);
111 let mut harness_app = Counter { presses: 0 };
112 harness.replay(&recording, &mut harness_app);
113
114 assert_eq!(live_app.presses, harness_app.presses);
115 assert_eq!(live_app.presses, 2);
116 }
117
118 #[test]
119 fn replay_live_stops_early_on_flow_exit() {
120 struct ExitsAfterFirst {
121 updates: u32,
122 }
123 impl<B: Backend> App<B> for ExitsAfterFirst {
124 fn update(&mut self, _term: &mut Terminal<B>, _frame: &Frame) -> Flow {
125 self.updates += 1;
126 Flow::Exit
127 }
128 }
129
130 let recording = recording();
131 let term = Terminal::new(Headless::new(recording.width(), recording.height()));
132 let mut app = ExitsAfterFirst { updates: 0 };
133 replay_live(term, &mut app, &recording).expect("replay_live");
134
135 assert_eq!(app.updates, 1, "must stop after the first Flow::Exit");
136 }
137
138 #[test]
139 fn replay_live_calls_init_once_before_the_first_update() {
140 struct RecordsInit {
141 init_calls: u32,
142 updates: u32,
143 }
144 impl<B: Backend> App<B> for RecordsInit {
145 fn init(&mut self, _term: &mut Terminal<B>) {
146 self.init_calls += 1;
147 }
148 fn update(&mut self, _term: &mut Terminal<B>, _frame: &Frame) -> Flow {
149 self.updates += 1;
150 Flow::Continue
151 }
152 }
153
154 let recording = recording();
155 let term = Terminal::new(Headless::new(recording.width(), recording.height()));
156 let mut app = RecordsInit {
157 init_calls: 0,
158 updates: 0,
159 };
160 replay_live(term, &mut app, &recording).expect("replay_live");
161
162 assert_eq!(app.init_calls, 1, "init must run exactly once");
163 assert_eq!(app.updates, 2);
164 }
165}