Skip to main content

rosace_trace/subscribers/
ring_buffer.rs

1use std::collections::VecDeque;
2use std::sync::{Arc, Mutex};
3// `web_time::Instant` (not `std::time`) — `std::time::Instant::now()` panics
4// on wasm ("time not implemented"), which aborted the whole app on the first
5// trace. `event.rs` already uses `web_time`; this subscriber was the one spot
6// still on `std`. Drop-in identical on native.
7use web_time::Instant;
8
9use crate::bus::TraceSubscriber;
10use crate::event::RosaceTrace;
11
12/// Predicate deciding whether an event is recorded. See
13/// [`RingBufferSubscriber::filtered`].
14type EventFilter = Arc<dyn Fn(&RosaceTrace) -> bool + Send + Sync>;
15
16/// Retains the last `capacity` trace events in a circular buffer.
17///
18/// Used for time-travel debugging — the dev tools can read the buffer to replay
19/// the sequence of events leading up to the current state or a crash.
20///
21/// Default capacity: 1000 events.
22pub struct RingBufferSubscriber {
23    buffer: Arc<Mutex<VecDeque<(Instant, RosaceTrace)>>>,
24    capacity: usize,
25    /// When set, only events for which this returns true are retained
26    /// (D123/O1 — the flight recorder excludes high-frequency events).
27    filter: Option<EventFilter>,
28}
29
30impl RingBufferSubscriber {
31    /// Creates a new ring buffer with the given capacity.
32    pub fn new(capacity: usize) -> Self {
33        Self {
34            buffer: Arc::new(Mutex::new(VecDeque::with_capacity(capacity))),
35            capacity,
36            filter: None,
37        }
38    }
39
40    /// A ring buffer that only records events passing `filter` — the basis
41    /// of the always-on flight recorder, which excludes high-frequency
42    /// events so it never becomes a per-frame firehose (D123/O1).
43    pub fn filtered(
44        capacity: usize,
45        filter: impl Fn(&RosaceTrace) -> bool + Send + Sync + 'static,
46    ) -> Self {
47        Self {
48            buffer: Arc::new(Mutex::new(VecDeque::with_capacity(capacity))),
49            capacity,
50            filter: Some(Arc::new(filter)),
51        }
52    }
53
54    /// Returns the number of events currently held in the buffer.
55    pub fn len(&self) -> usize {
56        self.buffer
57            .lock()
58            .expect("RingBufferSubscriber lock poisoned")
59            .len()
60    }
61
62    /// Returns true if the buffer contains no events.
63    pub fn is_empty(&self) -> bool {
64        self.len() == 0
65    }
66
67    /// Drains all buffered events into a `Vec`, oldest first.
68    pub fn drain(&self) -> Vec<RosaceTrace> {
69        self.buffer
70            .lock()
71            .expect("RingBufferSubscriber lock poisoned")
72            .drain(..)
73            .map(|(_, e)| e)
74            .collect()
75    }
76
77    /// Returns a snapshot of all buffered events, oldest first, without clearing.
78    pub fn snapshot(&self) -> Vec<RosaceTrace> {
79        self.buffer
80            .lock()
81            .expect("RingBufferSubscriber lock poisoned")
82            .iter()
83            .map(|(_, e)| e.clone())
84            .collect()
85    }
86
87    /// Like [`Self::snapshot`], but pairs each event with the `Instant` it
88    /// was recorded at (D123/O1 — the Perfetto/Chrome trace JSON export
89    /// needs a timestamp per event; the events themselves mostly don't
90    /// carry one).
91    pub fn snapshot_timestamped(&self) -> Vec<(Instant, RosaceTrace)> {
92        self.buffer
93            .lock()
94            .expect("RingBufferSubscriber lock poisoned")
95            .iter()
96            .cloned()
97            .collect()
98    }
99
100    /// Export the current buffer as a Chrome/Perfetto-loadable trace JSON
101    /// (Trace Event Format) — drop it in Perfetto UI's "Open trace file"
102    /// for a full flamegraph, for free (D123/O1).
103    pub fn export_perfetto_json(&self) -> String {
104        super::perfetto::to_chrome_trace_json(&self.snapshot_timestamped())
105    }
106}
107
108impl TraceSubscriber for RingBufferSubscriber {
109    fn on_trace(&self, event: &RosaceTrace) {
110        if let Some(f) = &self.filter {
111            if !f(event) {
112                return;
113            }
114        }
115        let mut buf = self
116            .buffer
117            .lock()
118            .expect("RingBufferSubscriber lock poisoned");
119        if buf.len() >= self.capacity {
120            buf.pop_front();
121        }
122        buf.push_back((Instant::now(), event.clone()));
123    }
124}