Skip to main content

rmux_core/events/
cursor.rs

1use super::ring::{OutputEvent, RecentOutputSnapshot};
2use std::ops::Range;
3
4/// Independent read position for one pane-output subscriber.
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct OutputCursor {
7    next_sequence: u64,
8    missed_events: u64,
9}
10
11impl OutputCursor {
12    /// Creates a cursor that will next read `next_sequence`.
13    #[must_use]
14    pub const fn new(next_sequence: u64) -> Self {
15        Self {
16            next_sequence,
17            missed_events: 0,
18        }
19    }
20
21    /// Returns the next sequence this cursor expects to read.
22    #[must_use]
23    pub const fn next_sequence(&self) -> u64 {
24        self.next_sequence
25    }
26
27    /// Returns the total number of events this cursor has explicitly missed.
28    #[must_use]
29    pub const fn missed_events(&self) -> u64 {
30        self.missed_events
31    }
32
33    pub(super) fn advance_to(&mut self, next_sequence: u64) {
34        self.next_sequence = next_sequence;
35    }
36
37    /// Advances past `sequence` only when it is exactly the next retained event.
38    ///
39    /// This lets live-output fast paths consume an already identified event
40    /// without exposing arbitrary cursor rewinds to callers.
41    pub fn advance_past_sequence(&mut self, sequence: u64) -> bool {
42        if self.next_sequence != sequence {
43            return false;
44        }
45        self.next_sequence = sequence.wrapping_add(1);
46        true
47    }
48
49    pub(super) fn record_gap(&mut self, missed: u64, resume_sequence: u64) {
50        self.missed_events = self.missed_events.saturating_add(missed);
51        self.next_sequence = resume_sequence;
52    }
53}
54
55/// One cursor poll result from an [`OutputRing`](super::ring::OutputRing).
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum OutputCursorItem {
58    /// A retained output event.
59    Event(OutputEvent),
60    /// The cursor fell behind the oldest retained event.
61    Gap(OutputGap),
62}
63
64/// Explicit report for output events that no longer fit in the ring.
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct OutputGap {
67    expected_sequence: u64,
68    resume_sequence: u64,
69    missed_events: u64,
70    newest_sequence: u64,
71    recent_snapshot: RecentOutputSnapshot,
72}
73
74impl OutputGap {
75    pub(super) const fn new(
76        expected_sequence: u64,
77        resume_sequence: u64,
78        missed_events: u64,
79        newest_sequence: u64,
80        recent_snapshot: RecentOutputSnapshot,
81    ) -> Self {
82        Self {
83            expected_sequence,
84            resume_sequence,
85            missed_events,
86            newest_sequence,
87            recent_snapshot,
88        }
89    }
90
91    /// Returns the sequence the cursor expected before lag was detected.
92    #[must_use]
93    pub const fn expected_sequence(&self) -> u64 {
94        self.expected_sequence
95    }
96
97    /// Returns the oldest retained sequence the cursor can resume from.
98    #[must_use]
99    pub const fn resume_sequence(&self) -> u64 {
100        self.resume_sequence
101    }
102
103    /// Returns the number of events skipped by this gap.
104    #[must_use]
105    pub const fn missed_events(&self) -> u64 {
106        self.missed_events
107    }
108
109    /// Returns the half-open output sequence range skipped by this gap.
110    #[must_use]
111    pub fn missed_range(&self) -> Range<u64> {
112        self.expected_sequence..self.resume_sequence
113    }
114
115    /// Returns the newest appended sequence when the gap was reported.
116    #[must_use]
117    pub const fn newest_sequence(&self) -> u64 {
118        self.newest_sequence
119    }
120
121    /// Returns the bounded recent live bytes available at gap detection time.
122    #[must_use]
123    pub const fn recent_snapshot(&self) -> &RecentOutputSnapshot {
124        &self.recent_snapshot
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::{OutputCursor, OutputCursorItem};
131    use crate::events::OutputRing;
132
133    #[test]
134    fn cursor_advances_independently_through_retained_events() {
135        let mut ring = OutputRing::new(8, 64);
136        ring.push(b"one".to_vec());
137        ring.push(b"two".to_vec());
138        let mut first = ring.cursor_from_oldest();
139        let mut second = ring.cursor_from_oldest();
140
141        assert_eq!(
142            ring.poll_cursor(&mut first),
143            Some(OutputCursorItem::Event(ring.retained_events()[0].clone()))
144        );
145        assert_eq!(first.next_sequence(), 1);
146        assert_eq!(second.next_sequence(), 0);
147
148        assert_eq!(
149            ring.poll_cursor(&mut first),
150            Some(OutputCursorItem::Event(ring.retained_events()[1].clone()))
151        );
152        assert_eq!(ring.poll_cursor(&mut first), None);
153        assert_eq!(first.next_sequence(), ring.next_sequence());
154
155        assert_eq!(
156            ring.poll_cursor(&mut second),
157            Some(OutputCursorItem::Event(ring.retained_events()[0].clone()))
158        );
159        assert_eq!(second.next_sequence(), 1);
160    }
161
162    #[test]
163    fn lagged_cursor_reports_explicit_gap_and_resumes_at_oldest_event() {
164        let mut ring = OutputRing::new(2, 64);
165        let mut cursor = OutputCursor::new(0);
166        for bytes in [b"zero".as_slice(), b"one".as_slice(), b"two".as_slice()] {
167            ring.push(bytes.to_vec());
168        }
169
170        let Some(OutputCursorItem::Gap(gap)) = ring.poll_cursor(&mut cursor) else {
171            panic!("cursor should report lag");
172        };
173        assert_eq!(gap.expected_sequence(), 0);
174        assert_eq!(gap.resume_sequence(), 1);
175        assert_eq!(gap.missed_events(), 1);
176        assert_eq!(gap.missed_range(), 0..1);
177        assert_eq!(gap.newest_sequence(), 2);
178        assert_eq!(gap.recent_snapshot().oldest_sequence(), Some(0));
179        assert_eq!(gap.recent_snapshot().newest_sequence(), Some(2));
180        assert_eq!(cursor.missed_events(), 1);
181        assert_eq!(cursor.next_sequence(), 1);
182
183        let Some(OutputCursorItem::Event(event)) = ring.poll_cursor(&mut cursor) else {
184            panic!("cursor should resume with oldest retained event");
185        };
186        assert_eq!(event.sequence(), 1);
187        assert_eq!(event.bytes(), b"one");
188    }
189}