Skip to main content

retroglyph_recorder/
input_recorder.rs

1//! [`InputRecorder`] and its [`RecorderHandle`].
2
3use retroglyph_core::backend::{Compositing, Cursor, CursorStyle, DrawCell, Input, Output};
4use retroglyph_core::event::Event;
5use retroglyph_core::grid::{HasSize, Pos, Size};
6use retroglyph_core::testing::InputRecording;
7use std::io;
8use std::path::Path;
9use std::sync::{Arc, Mutex};
10use std::time::{Duration, Instant};
11
12/// State shared between an [`InputRecorder`] and every [`RecorderHandle`] cloned from it.
13struct State {
14    recording: InputRecording,
15    /// `true` while events seen through [`Input::poll_event`] are being appended to `recording`.
16    /// [`InputRecorder::new`] starts `true`, matching a `TestHarness`-style "recording starts
17    /// immediately" default; [`RecorderHandle::pause`] flips it off without discarding anything
18    /// already captured.
19    active: bool,
20    /// When the most recently recorded event was seen, so the next one's delay (time since the
21    /// *previous* event, per [`InputRecording`]'s own convention) can be computed. `None` before
22    /// the first event, or immediately after [`RecorderHandle::start`] resumes from a pause (the
23    /// gap spent paused is not itself a delay worth recording).
24    last_event_at: Option<Instant>,
25}
26
27/// A cheap, cloneable handle onto an [`InputRecorder`]'s captured state.
28///
29/// Exists so a call stack that doesn't own the recorder itself -- most notably
30/// [`install_panic_recorder`](crate::install_panic_recorder), which runs from inside a panic
31/// hook -- can still start/pause/stop it or save whatever it has captured so far. Backed by
32/// `Arc<Mutex<..>>`: cloning shares the same underlying recording, it does not fork one.
33#[derive(Clone)]
34pub struct RecorderHandle {
35    state: Arc<Mutex<State>>,
36}
37
38impl RecorderHandle {
39    fn new(width: u16, height: u16) -> Self {
40        Self {
41            state: Arc::new(Mutex::new(State {
42                recording: InputRecording::new(width, height),
43                active: true,
44                last_event_at: None,
45            })),
46        }
47    }
48
49    /// Appends `event` to the recording (with the elapsed time since the previously recorded
50    /// event as its delay), unless [`pause`](Self::pause) has left recording inactive.
51    fn record(&self, event: Event) {
52        let mut state = self.lock();
53        if !state.active {
54            return;
55        }
56        let now = Instant::now();
57        let delay = state
58            .last_event_at
59            .map_or(Duration::ZERO, |previous| now.duration_since(previous));
60        state.last_event_at = Some(now);
61        state.recording.push(delay, event);
62    }
63
64    /// Locks the shared state, recovering it if a previous holder panicked while holding the
65    /// lock rather than poisoning every future access: losing a few milliseconds of in-flight
66    /// capture to a poisoned mutex would defeat the point of a recorder meant to survive a panic
67    /// (see [`install_panic_recorder`](crate::install_panic_recorder)).
68    fn lock(&self) -> std::sync::MutexGuard<'_, State> {
69        self.state
70            .lock()
71            .unwrap_or_else(std::sync::PoisonError::into_inner)
72    }
73
74    /// (Re)starts recording. A no-op if already active. The gap since the previous event (or
75    /// since [`pause`](Self::pause), if that's why recording stopped) is not itself recorded as
76    /// a delay: the next event's delay is measured from when it actually arrives.
77    pub fn start(&self) {
78        let mut state = self.lock();
79        state.active = true;
80        state.last_event_at = None;
81    }
82
83    /// Stops appending new events, without discarding what's already captured. Recording resumes
84    /// on the next [`start`](Self::start).
85    pub fn pause(&self) {
86        self.lock().active = false;
87    }
88
89    /// A snapshot of everything captured so far.
90    #[must_use]
91    pub fn snapshot(&self) -> InputRecording {
92        self.lock().recording.clone()
93    }
94
95    /// [`pause`](Self::pause)s recording and returns everything captured so far.
96    #[must_use]
97    pub fn stop(&self) -> InputRecording {
98        let mut state = self.lock();
99        state.active = false;
100        state.recording.clone()
101    }
102
103    /// Writes a [`snapshot`](Self::snapshot) of the current recording to `path` as `.rgrec`.
104    ///
105    /// # Errors
106    ///
107    /// Returns an error if `path` cannot be created or written to.
108    pub fn save(&self, path: impl AsRef<Path>) -> io::Result<()> {
109        crate::rgrec::write(&self.snapshot(), path)
110    }
111}
112
113/// Wraps a backend and records every event it sees.
114///
115/// Works over a [`Backend`](retroglyph_core::backend::Backend), or any subset of
116/// [`Output`]/[`Input`]/[`Cursor`]: taps [`Input::poll_event`] to capture, while delegating
117/// everything else unchanged.
118///
119/// Recording starts immediately on construction; use [`handle`](Self::handle) (or the
120/// convenience [`start`](Self::start)/[`pause`](Self::pause)/[`stop`](Self::stop)/
121/// [`save`](Self::save) methods on this type, which forward to it) to control capture and save
122/// it to a `.rgrec` file.
123///
124/// # Examples
125///
126/// ```
127/// use retroglyph_core::backend::{Headless, Input};
128/// use retroglyph_core::event::{Event, KeyCode, KeyEvent, KeyModifiers};
129/// use retroglyph_recorder::InputRecorder;
130///
131/// let mut recorder = InputRecorder::new(Headless::new(10, 3));
132/// recorder.push_event(Event::Key(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE)));
133/// let event = recorder.poll_event(std::time::Duration::ZERO);
134/// assert!(event.is_some());
135///
136/// let recording = recorder.stop();
137/// assert_eq!(recording.len(), 1);
138/// ```
139pub struct InputRecorder<B> {
140    inner: B,
141    handle: RecorderHandle,
142}
143
144impl<B: Output> InputRecorder<B> {
145    /// Wraps `inner`, sizing the recording to `inner`'s current [`Output::size`].
146    pub fn new(inner: B) -> Self {
147        let size = inner.size();
148        Self {
149            inner,
150            handle: RecorderHandle::new(size.width(), size.height()),
151        }
152    }
153}
154
155impl<B> InputRecorder<B> {
156    /// A cloneable [`RecorderHandle`] onto this recorder's shared state, for reaching in from
157    /// another call stack (see [`RecorderHandle`]'s own docs).
158    #[must_use]
159    pub fn handle(&self) -> RecorderHandle {
160        self.handle.clone()
161    }
162
163    /// [`RecorderHandle::start`] on this recorder's own handle.
164    pub fn start(&self) {
165        self.handle.start();
166    }
167
168    /// [`RecorderHandle::pause`] on this recorder's own handle.
169    pub fn pause(&self) {
170        self.handle.pause();
171    }
172
173    /// [`RecorderHandle::snapshot`] on this recorder's own handle.
174    #[must_use]
175    pub fn snapshot(&self) -> InputRecording {
176        self.handle.snapshot()
177    }
178
179    /// [`RecorderHandle::stop`] on this recorder's own handle.
180    #[must_use]
181    pub fn stop(&self) -> InputRecording {
182        self.handle.stop()
183    }
184
185    /// [`RecorderHandle::save`] on this recorder's own handle.
186    ///
187    /// # Errors
188    ///
189    /// Returns an error if `path` cannot be created or written to.
190    pub fn save(&self, path: impl AsRef<Path>) -> io::Result<()> {
191        self.handle.save(path)
192    }
193
194    /// The wrapped backend, for anything not exposed through the decorator itself.
195    pub const fn inner(&self) -> &B {
196        &self.inner
197    }
198
199    /// The wrapped backend, mutably.
200    pub const fn inner_mut(&mut self) -> &mut B {
201        &mut self.inner
202    }
203}
204
205impl<B: Output> Output for InputRecorder<B> {
206    type Error = B::Error;
207
208    fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
209    where
210        I: Iterator<Item = DrawCell<'a>>,
211    {
212        self.inner.draw(content)
213    }
214
215    fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
216    where
217        I: Iterator<Item = DrawCell<'a>>,
218    {
219        self.inner.draw_layers(content)
220    }
221
222    fn compositing(&self) -> Compositing {
223        self.inner.compositing()
224    }
225
226    fn flush(&mut self) -> Result<(), Self::Error> {
227        self.inner.flush()
228    }
229
230    fn size(&self) -> Size {
231        self.inner.size()
232    }
233
234    fn clear(&mut self) -> Result<(), Self::Error> {
235        self.inner.clear()
236    }
237
238    fn resize(&mut self, size: Size) {
239        self.inner.resize(size);
240    }
241}
242
243impl<B: Input> Input for InputRecorder<B> {
244    fn poll_event(&mut self, timeout: Duration) -> Option<Event> {
245        let event = self.inner.poll_event(timeout)?;
246        self.handle.record(event.clone());
247        Some(event)
248    }
249
250    fn push_event(&mut self, event: Event) {
251        self.inner.push_event(event);
252    }
253}
254
255impl<B: Cursor> Cursor for InputRecorder<B> {
256    fn set_cursor_visible(&mut self, visible: bool) {
257        self.inner.set_cursor_visible(visible);
258    }
259
260    fn set_cursor_position(&mut self, position: Pos) {
261        self.inner.set_cursor_position(position);
262    }
263
264    fn set_cursor_style(&mut self, style: CursorStyle) {
265        self.inner.set_cursor_style(style);
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272    use retroglyph_core::backend::Headless;
273    use retroglyph_core::event::{KeyCode, KeyEvent, KeyModifiers};
274
275    #[test]
276    fn records_events_seen_through_poll_event() {
277        let mut recorder = InputRecorder::new(Headless::new(10, 3));
278        recorder.push_event(Event::Key(KeyEvent::new(
279            KeyCode::Char('a'),
280            KeyModifiers::NONE,
281        )));
282        recorder.push_event(Event::Key(KeyEvent::new(
283            KeyCode::Char('b'),
284            KeyModifiers::NONE,
285        )));
286
287        assert!(recorder.poll_event(Duration::ZERO).is_some());
288        assert!(recorder.poll_event(Duration::ZERO).is_some());
289        assert!(recorder.poll_event(Duration::ZERO).is_none());
290
291        let recording = recorder.snapshot();
292        assert_eq!(recording.width(), 10);
293        assert_eq!(recording.height(), 3);
294        let events: Vec<_> = recording.events().map(|(_, event)| event.clone()).collect();
295        assert_eq!(
296            events,
297            vec![
298                Event::Key(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE)),
299                Event::Key(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::NONE)),
300            ]
301        );
302    }
303
304    #[test]
305    fn pause_stops_recording_without_discarding_captured_events() {
306        let mut recorder = InputRecorder::new(Headless::new(4, 1));
307        recorder.push_event(Event::Key(KeyEvent::new(
308            KeyCode::Char('a'),
309            KeyModifiers::NONE,
310        )));
311        recorder.poll_event(Duration::ZERO);
312        assert_eq!(recorder.snapshot().len(), 1);
313
314        recorder.pause();
315        recorder.push_event(Event::Key(KeyEvent::new(
316            KeyCode::Char('b'),
317            KeyModifiers::NONE,
318        )));
319        recorder.poll_event(Duration::ZERO);
320        assert_eq!(
321            recorder.snapshot().len(),
322            1,
323            "paused recorder must not append new events"
324        );
325
326        recorder.start();
327        recorder.push_event(Event::Key(KeyEvent::new(
328            KeyCode::Char('c'),
329            KeyModifiers::NONE,
330        )));
331        recorder.poll_event(Duration::ZERO);
332        assert_eq!(
333            recorder.snapshot().len(),
334            2,
335            "resumed recorder must append new events again"
336        );
337    }
338
339    #[test]
340    fn handle_reaches_the_same_state_as_the_recorder() {
341        let mut recorder = InputRecorder::new(Headless::new(4, 1));
342        let handle = recorder.handle();
343        recorder.push_event(Event::Key(KeyEvent::new(
344            KeyCode::Char('a'),
345            KeyModifiers::NONE,
346        )));
347        recorder.poll_event(Duration::ZERO);
348
349        assert_eq!(handle.snapshot().len(), 1);
350        handle.pause();
351        recorder.push_event(Event::Key(KeyEvent::new(
352            KeyCode::Char('b'),
353            KeyModifiers::NONE,
354        )));
355        recorder.poll_event(Duration::ZERO);
356        assert_eq!(recorder.snapshot().len(), 1);
357    }
358
359    #[test]
360    fn stop_pauses_and_returns_the_captured_recording() {
361        let mut recorder = InputRecorder::new(Headless::new(4, 1));
362        recorder.push_event(Event::Key(KeyEvent::new(
363            KeyCode::Char('a'),
364            KeyModifiers::NONE,
365        )));
366        recorder.poll_event(Duration::ZERO);
367
368        let stopped = recorder.stop();
369        assert_eq!(stopped.len(), 1);
370
371        recorder.push_event(Event::Key(KeyEvent::new(
372            KeyCode::Char('b'),
373            KeyModifiers::NONE,
374        )));
375        recorder.poll_event(Duration::ZERO);
376        assert_eq!(recorder.snapshot().len(), 1, "stop() must pause recording");
377    }
378}