rmux_core/events/
cursor.rs1use super::ring::{OutputEvent, RecentOutputSnapshot};
2use std::ops::Range;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct OutputCursor {
7 next_sequence: u64,
8 missed_events: u64,
9}
10
11impl OutputCursor {
12 #[must_use]
14 pub const fn new(next_sequence: u64) -> Self {
15 Self {
16 next_sequence,
17 missed_events: 0,
18 }
19 }
20
21 #[must_use]
23 pub const fn next_sequence(&self) -> u64 {
24 self.next_sequence
25 }
26
27 #[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 pub(super) fn record_gap(&mut self, missed: u64, resume_sequence: u64) {
38 self.missed_events = self.missed_events.saturating_add(missed);
39 self.next_sequence = resume_sequence;
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum OutputCursorItem {
46 Event(OutputEvent),
48 Gap(OutputGap),
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct OutputGap {
55 expected_sequence: u64,
56 resume_sequence: u64,
57 missed_events: u64,
58 newest_sequence: u64,
59 recent_snapshot: RecentOutputSnapshot,
60}
61
62impl OutputGap {
63 pub(super) const fn new(
64 expected_sequence: u64,
65 resume_sequence: u64,
66 missed_events: u64,
67 newest_sequence: u64,
68 recent_snapshot: RecentOutputSnapshot,
69 ) -> Self {
70 Self {
71 expected_sequence,
72 resume_sequence,
73 missed_events,
74 newest_sequence,
75 recent_snapshot,
76 }
77 }
78
79 #[must_use]
81 pub const fn expected_sequence(&self) -> u64 {
82 self.expected_sequence
83 }
84
85 #[must_use]
87 pub const fn resume_sequence(&self) -> u64 {
88 self.resume_sequence
89 }
90
91 #[must_use]
93 pub const fn missed_events(&self) -> u64 {
94 self.missed_events
95 }
96
97 #[must_use]
99 pub fn missed_range(&self) -> Range<u64> {
100 self.expected_sequence..self.resume_sequence
101 }
102
103 #[must_use]
105 pub const fn newest_sequence(&self) -> u64 {
106 self.newest_sequence
107 }
108
109 #[must_use]
111 pub const fn recent_snapshot(&self) -> &RecentOutputSnapshot {
112 &self.recent_snapshot
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::{OutputCursor, OutputCursorItem};
119 use crate::events::OutputRing;
120
121 #[test]
122 fn cursor_advances_independently_through_retained_events() {
123 let mut ring = OutputRing::new(8, 64);
124 ring.push(b"one".to_vec());
125 ring.push(b"two".to_vec());
126 let mut first = ring.cursor_from_oldest();
127 let mut second = ring.cursor_from_oldest();
128
129 assert_eq!(
130 ring.poll_cursor(&mut first),
131 Some(OutputCursorItem::Event(ring.retained_events()[0].clone()))
132 );
133 assert_eq!(first.next_sequence(), 1);
134 assert_eq!(second.next_sequence(), 0);
135
136 assert_eq!(
137 ring.poll_cursor(&mut first),
138 Some(OutputCursorItem::Event(ring.retained_events()[1].clone()))
139 );
140 assert_eq!(ring.poll_cursor(&mut first), None);
141 assert_eq!(first.next_sequence(), ring.next_sequence());
142
143 assert_eq!(
144 ring.poll_cursor(&mut second),
145 Some(OutputCursorItem::Event(ring.retained_events()[0].clone()))
146 );
147 assert_eq!(second.next_sequence(), 1);
148 }
149
150 #[test]
151 fn lagged_cursor_reports_explicit_gap_and_resumes_at_oldest_event() {
152 let mut ring = OutputRing::new(2, 64);
153 let mut cursor = OutputCursor::new(0);
154 for bytes in [b"zero".as_slice(), b"one".as_slice(), b"two".as_slice()] {
155 ring.push(bytes.to_vec());
156 }
157
158 let Some(OutputCursorItem::Gap(gap)) = ring.poll_cursor(&mut cursor) else {
159 panic!("cursor should report lag");
160 };
161 assert_eq!(gap.expected_sequence(), 0);
162 assert_eq!(gap.resume_sequence(), 1);
163 assert_eq!(gap.missed_events(), 1);
164 assert_eq!(gap.missed_range(), 0..1);
165 assert_eq!(gap.newest_sequence(), 2);
166 assert_eq!(gap.recent_snapshot().oldest_sequence(), Some(0));
167 assert_eq!(gap.recent_snapshot().newest_sequence(), Some(2));
168 assert_eq!(cursor.missed_events(), 1);
169 assert_eq!(cursor.next_sequence(), 1);
170
171 let Some(OutputCursorItem::Event(event)) = ring.poll_cursor(&mut cursor) else {
172 panic!("cursor should resume with oldest retained event");
173 };
174 assert_eq!(event.sequence(), 1);
175 assert_eq!(event.bytes(), b"one");
176 }
177}