1use std::collections::VecDeque;
2
3use super::cursor::{OutputCursor, OutputCursorItem, OutputGap};
4use crate::TerminalPassthrough;
5
6pub const DEFAULT_OUTPUT_RING_CAPACITY: usize = 1024;
8pub const DEFAULT_RECENT_LIVE_BUFFER_CAPACITY: usize = 1024 * 1024;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct OutputEvent {
14 sequence: u64,
15 bytes: Vec<u8>,
16 passthroughs: Vec<TerminalPassthrough>,
17}
18
19impl OutputEvent {
20 #[must_use]
22 pub const fn sequence(&self) -> u64 {
23 self.sequence
24 }
25
26 #[must_use]
28 pub fn bytes(&self) -> &[u8] {
29 &self.bytes
30 }
31
32 #[must_use]
34 pub fn passthroughs(&self) -> &[TerminalPassthrough] {
35 &self.passthroughs
36 }
37
38 #[must_use]
40 pub fn into_bytes(self) -> Vec<u8> {
41 self.bytes
42 }
43
44 #[must_use]
46 pub fn into_parts(self) -> (Vec<u8>, Vec<TerminalPassthrough>) {
47 (self.bytes, self.passthroughs)
48 }
49
50 #[must_use]
52 pub fn with_passthroughs(mut self, passthroughs: Vec<TerminalPassthrough>) -> Self {
53 self.passthroughs = passthroughs;
54 self
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct RecentOutputSnapshot {
61 bytes: Vec<u8>,
62 oldest_sequence: Option<u64>,
63 newest_sequence: Option<u64>,
64 chunks: Vec<RecentOutputSnapshotChunk>,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
68struct RecentOutputSnapshotChunk {
69 sequence: u64,
70 start: usize,
71 starts_at_event_start: bool,
72}
73
74impl RecentOutputSnapshot {
75 #[must_use]
77 pub fn bytes(&self) -> &[u8] {
78 &self.bytes
79 }
80
81 #[must_use]
84 pub fn bytes_from_sequence(&self, min_sequence: u64) -> &[u8] {
85 let start = self
86 .chunks
87 .iter()
88 .find(|chunk| chunk.sequence >= min_sequence)
89 .map_or(self.bytes.len(), |chunk| chunk.start);
90 &self.bytes[start..]
91 }
92
93 #[must_use]
95 pub const fn oldest_sequence(&self) -> Option<u64> {
96 self.oldest_sequence
97 }
98
99 #[must_use]
101 pub const fn newest_sequence(&self) -> Option<u64> {
102 self.newest_sequence
103 }
104
105 #[must_use]
108 pub fn oldest_sequence_at_or_after(&self, min_sequence: u64) -> Option<u64> {
109 self.chunks
110 .iter()
111 .find(|chunk| chunk.sequence >= min_sequence)
112 .map(|chunk| chunk.sequence)
113 }
114
115 #[must_use]
118 pub fn starts_at_event_start(&self, sequence: u64) -> bool {
119 self.chunks
120 .iter()
121 .find(|chunk| chunk.sequence == sequence)
122 .is_some_and(|chunk| chunk.starts_at_event_start)
123 }
124
125 #[must_use]
127 pub fn len(&self) -> usize {
128 self.bytes.len()
129 }
130
131 #[must_use]
133 pub fn is_empty(&self) -> bool {
134 self.bytes.is_empty()
135 }
136}
137
138#[derive(Debug, Clone)]
140pub struct OutputRing {
141 event_capacity: usize,
142 recent_byte_capacity: usize,
143 next_sequence: u64,
144 events: VecDeque<OutputEvent>,
145 recent: RecentLiveBuffer,
146}
147
148impl OutputRing {
149 #[must_use]
154 pub fn new(event_capacity: usize, recent_byte_capacity: usize) -> Self {
155 assert!(event_capacity > 0, "output ring capacity must be positive");
156 assert!(
157 recent_byte_capacity > 0,
158 "recent live buffer capacity must be positive"
159 );
160 Self {
161 event_capacity,
162 recent_byte_capacity,
163 next_sequence: 0,
164 events: VecDeque::with_capacity(event_capacity),
165 recent: RecentLiveBuffer::new(recent_byte_capacity),
166 }
167 }
168
169 #[must_use]
171 pub fn with_default_capacities() -> Self {
172 Self::new(
173 DEFAULT_OUTPUT_RING_CAPACITY,
174 DEFAULT_RECENT_LIVE_BUFFER_CAPACITY,
175 )
176 }
177
178 pub fn push(&mut self, bytes: Vec<u8>) -> OutputEvent {
180 let event = OutputEvent {
181 sequence: self.next_sequence,
182 bytes,
183 passthroughs: Vec::new(),
184 };
185 self.next_sequence = self
186 .next_sequence
187 .checked_add(1)
188 .expect("output ring sequence space exhausted");
189 self.recent.push(event.sequence, &event.bytes);
190 self.events.push_back(event.clone());
191 while self.events.len() > self.event_capacity {
192 let _ = self.events.pop_front();
193 }
194 event
195 }
196
197 pub fn clear_retained(&mut self) {
199 self.events.clear();
200 self.recent.clear();
201 }
202
203 #[must_use]
205 pub fn cursor_from_oldest(&self) -> OutputCursor {
206 OutputCursor::new(self.oldest_sequence())
207 }
208
209 #[must_use]
211 pub fn cursor_from_now(&self) -> OutputCursor {
212 OutputCursor::new(self.next_sequence)
213 }
214
215 #[must_use]
217 pub const fn cursor_from_sequence(sequence: u64) -> OutputCursor {
218 OutputCursor::new(sequence)
219 }
220
221 pub fn poll_cursor(&self, cursor: &mut OutputCursor) -> Option<OutputCursorItem> {
223 let next = cursor.next_sequence();
224 let oldest = self.oldest_sequence();
225 if next < oldest {
226 let missed = oldest.saturating_sub(next);
227 cursor.record_gap(missed, oldest);
228 return Some(OutputCursorItem::Gap(OutputGap::new(
229 next,
230 oldest,
231 missed,
232 self.newest_sequence(),
233 self.recent_snapshot(),
234 )));
235 }
236
237 if next >= self.next_sequence {
238 return None;
239 }
240
241 let offset = usize::try_from(next.saturating_sub(oldest)).ok()?;
242 let event = self.events.get(offset).cloned()?;
243 cursor.advance_to(next.wrapping_add(1));
244 Some(OutputCursorItem::Event(event))
245 }
246
247 pub fn poll_cursor_batch(
254 &self,
255 cursor: &mut OutputCursor,
256 limit: usize,
257 ) -> Vec<OutputCursorItem> {
258 let mut items = Vec::new();
259 for _ in 0..limit {
260 let Some(item) = self.poll_cursor(cursor) else {
261 break;
262 };
263 let is_gap = matches!(item, OutputCursorItem::Gap(_));
264 items.push(item);
265 if is_gap {
266 break;
267 }
268 }
269 items
270 }
271
272 #[must_use]
274 pub fn oldest_sequence(&self) -> u64 {
275 self.events
276 .front()
277 .map_or(self.next_sequence, OutputEvent::sequence)
278 }
279
280 #[must_use]
282 pub const fn next_sequence(&self) -> u64 {
283 self.next_sequence
284 }
285
286 #[must_use]
288 pub fn newest_sequence(&self) -> u64 {
289 self.next_sequence.saturating_sub(1)
290 }
291
292 #[must_use]
294 pub const fn event_capacity(&self) -> usize {
295 self.event_capacity
296 }
297
298 #[must_use]
300 pub const fn recent_byte_capacity(&self) -> usize {
301 self.recent_byte_capacity
302 }
303
304 #[must_use]
306 pub fn retained_len(&self) -> usize {
307 self.events.len()
308 }
309
310 #[must_use]
312 pub fn recent_len(&self) -> usize {
313 self.recent.len()
314 }
315
316 #[must_use]
318 pub fn recent_snapshot(&self) -> RecentOutputSnapshot {
319 self.recent.snapshot()
320 }
321
322 #[must_use]
324 pub fn retained_events(&self) -> Vec<OutputEvent> {
325 self.events.iter().cloned().collect()
326 }
327}
328
329impl Default for OutputRing {
330 fn default() -> Self {
331 Self::with_default_capacities()
332 }
333}
334
335#[derive(Debug, Clone)]
336struct RecentLiveBuffer {
337 capacity: usize,
338 len: usize,
339 chunks: VecDeque<RecentLiveChunk>,
340}
341
342#[derive(Debug, Clone)]
343struct RecentLiveChunk {
344 sequence: u64,
345 bytes: Vec<u8>,
346 starts_at_event_start: bool,
347}
348
349impl RecentLiveBuffer {
350 fn new(capacity: usize) -> Self {
351 Self {
352 capacity,
353 len: 0,
354 chunks: VecDeque::new(),
355 }
356 }
357
358 fn push(&mut self, sequence: u64, bytes: &[u8]) {
359 if bytes.is_empty() {
360 return;
361 }
362 if bytes.len() >= self.capacity {
363 self.chunks.clear();
364 self.chunks.push_back(RecentLiveChunk {
365 sequence,
366 bytes: bytes[bytes.len() - self.capacity..].to_vec(),
367 starts_at_event_start: bytes.len() == self.capacity,
368 });
369 self.len = self.capacity;
370 return;
371 }
372 self.chunks.push_back(RecentLiveChunk {
373 sequence,
374 bytes: bytes.to_vec(),
375 starts_at_event_start: true,
376 });
377 self.len = self.len.saturating_add(bytes.len());
378 self.trim_front();
379 }
380
381 fn clear(&mut self) {
382 self.chunks.clear();
383 self.len = 0;
384 }
385
386 fn trim_front(&mut self) {
387 while self.len > self.capacity {
388 let overflow = self.len - self.capacity;
389 let Some(front) = self.chunks.front_mut() else {
390 self.len = 0;
391 return;
392 };
393 if front.bytes.len() <= overflow {
394 self.len -= front.bytes.len();
395 let _ = self.chunks.pop_front();
396 } else {
397 front.bytes = front.bytes.split_off(overflow);
398 front.starts_at_event_start = false;
399 self.len -= overflow;
400 }
401 }
402 }
403
404 const fn len(&self) -> usize {
405 self.len
406 }
407
408 fn oldest_sequence(&self) -> Option<u64> {
409 self.chunks.front().map(|chunk| chunk.sequence)
410 }
411
412 fn newest_sequence(&self) -> Option<u64> {
413 self.chunks.back().map(|chunk| chunk.sequence)
414 }
415
416 fn snapshot(&self) -> RecentOutputSnapshot {
417 let mut bytes = Vec::with_capacity(self.len);
418 let mut snapshot_chunks = Vec::with_capacity(self.chunks.len());
419 for chunk in &self.chunks {
420 let start = bytes.len();
421 bytes.extend_from_slice(&chunk.bytes);
422 snapshot_chunks.push(RecentOutputSnapshotChunk {
423 sequence: chunk.sequence,
424 start,
425 starts_at_event_start: chunk.starts_at_event_start,
426 });
427 }
428 RecentOutputSnapshot {
429 bytes,
430 oldest_sequence: self.oldest_sequence(),
431 newest_sequence: self.newest_sequence(),
432 chunks: snapshot_chunks,
433 }
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::{OutputRing, DEFAULT_OUTPUT_RING_CAPACITY, DEFAULT_RECENT_LIVE_BUFFER_CAPACITY};
440 use crate::events::{OutputCursor, OutputCursorItem};
441
442 #[test]
443 fn default_capacities_match_recorded_budget() {
444 let ring = OutputRing::default();
445 assert_eq!(ring.event_capacity(), DEFAULT_OUTPUT_RING_CAPACITY);
446 assert_eq!(
447 ring.recent_byte_capacity(),
448 DEFAULT_RECENT_LIVE_BUFFER_CAPACITY
449 );
450 assert_eq!(DEFAULT_OUTPUT_RING_CAPACITY, 1_024);
451 assert_eq!(DEFAULT_RECENT_LIVE_BUFFER_CAPACITY, 1_048_576);
452 }
453
454 #[test]
455 fn ring_rotation_keeps_only_most_recent_events() {
456 let mut ring = OutputRing::new(2, 64);
457 ring.push(b"zero".to_vec());
458 ring.push(b"one".to_vec());
459 ring.push(b"two".to_vec());
460
461 let sequences = ring
462 .retained_events()
463 .iter()
464 .map(|event| event.sequence())
465 .collect::<Vec<_>>();
466 assert_eq!(sequences, vec![1, 2]);
467 assert_eq!(ring.oldest_sequence(), 1);
468 assert_eq!(ring.next_sequence(), 3);
469 }
470
471 #[test]
472 fn recent_live_buffer_obeys_byte_bound() {
473 let mut ring = OutputRing::new(8, 5);
474 ring.push(b"abc".to_vec());
475 ring.push(b"defg".to_vec());
476 ring.push(b"hi".to_vec());
477
478 let snapshot = ring.recent_snapshot();
479 assert_eq!(snapshot.bytes(), b"efghi");
480 assert_eq!(snapshot.len(), 5);
481 assert_eq!(snapshot.oldest_sequence(), Some(1));
482 assert_eq!(snapshot.newest_sequence(), Some(2));
483 assert_eq!(ring.recent_len(), 5);
484 }
485
486 #[test]
487 fn recent_live_buffer_releases_trimmed_prefix_capacity() {
488 let mut ring = OutputRing::new(8, 4);
489 ring.push(b"abcd".to_vec());
490 ring.push(b"ef".to_vec());
491
492 assert_eq!(ring.recent_snapshot().bytes(), b"cdef");
493 assert_eq!(ring.recent_len(), 4);
494 let retained_capacity = ring
495 .recent
496 .chunks
497 .iter()
498 .map(|chunk| chunk.bytes.capacity())
499 .sum::<usize>();
500 assert!(
501 retained_capacity <= ring.recent_byte_capacity(),
502 "recent buffer retained capacity {retained_capacity} exceeds configured bound {}",
503 ring.recent_byte_capacity()
504 );
505 }
506
507 #[test]
508 fn recent_live_buffer_trims_oversized_single_event_to_bound() {
509 let mut ring = OutputRing::new(8, 4);
510 ring.push(b"012345".to_vec());
511
512 assert_eq!(ring.recent_snapshot().bytes(), b"2345");
513 assert_eq!(ring.recent_snapshot().oldest_sequence(), Some(0));
514 assert_eq!(ring.recent_snapshot().newest_sequence(), Some(0));
515 assert_eq!(ring.recent_len(), 4);
516 assert_eq!(ring.retained_events()[0].bytes(), b"012345");
517 }
518
519 #[test]
520 fn recent_snapshot_filters_bytes_by_contributing_sequence() {
521 let mut ring = OutputRing::new(8, 64);
522 ring.push(b"stale".to_vec());
523 ring.push(b"future".to_vec());
524 ring.push(b"tail".to_vec());
525
526 let snapshot = ring.recent_snapshot();
527
528 assert_eq!(snapshot.bytes_from_sequence(0), b"stalefuturetail");
529 assert_eq!(snapshot.bytes_from_sequence(1), b"futuretail");
530 assert_eq!(snapshot.bytes_from_sequence(2), b"tail");
531 assert_eq!(snapshot.bytes_from_sequence(3), b"");
532 assert_eq!(snapshot.oldest_sequence_at_or_after(1), Some(1));
533 assert_eq!(snapshot.oldest_sequence_at_or_after(3), None);
534 assert!(snapshot.starts_at_event_start(1));
535 }
536
537 #[test]
538 fn recent_snapshot_records_when_retained_event_prefix_was_trimmed() {
539 let mut ring = OutputRing::new(8, 4);
540 ring.push(b"012345".to_vec());
541
542 let snapshot = ring.recent_snapshot();
543
544 assert_eq!(snapshot.bytes_from_sequence(0), b"2345");
545 assert_eq!(snapshot.oldest_sequence_at_or_after(0), Some(0));
546 assert!(!snapshot.starts_at_event_start(0));
547 }
548
549 #[test]
550 fn cursor_lag_across_full_rotation_reports_all_missed_events() {
551 let mut ring = OutputRing::new(3, 16);
552 let mut cursor = OutputCursor::new(0);
553 for index in 0..6 {
554 ring.push(format!("{index}").into_bytes());
555 }
556
557 let Some(OutputCursorItem::Gap(gap)) = ring.poll_cursor(&mut cursor) else {
558 panic!("cursor should lag after ring rotation");
559 };
560 assert_eq!(gap.expected_sequence(), 0);
561 assert_eq!(gap.resume_sequence(), 3);
562 assert_eq!(gap.missed_events(), 3);
563 assert_eq!(gap.missed_range(), 0..3);
564 assert_eq!(gap.recent_snapshot().bytes(), b"012345");
565 assert_eq!(gap.recent_snapshot().oldest_sequence(), Some(0));
566 assert_eq!(gap.recent_snapshot().newest_sequence(), Some(5));
567 assert_eq!(cursor.missed_events(), 3);
568 }
569
570 #[test]
571 fn cursor_polls_rotated_ring_by_sequence_offset() {
572 let mut ring = OutputRing::new(3, 16);
573 for index in 0..6 {
574 ring.push(format!("{index}").into_bytes());
575 }
576 let mut cursor = OutputCursor::new(4);
577
578 let Some(OutputCursorItem::Event(event)) = ring.poll_cursor(&mut cursor) else {
579 panic!("cursor should read retained event from rotated ring");
580 };
581 assert_eq!(event.sequence(), 4);
582 assert_eq!(event.bytes(), b"4");
583 assert_eq!(cursor.next_sequence(), 5);
584 }
585
586 #[test]
587 fn batch_poll_reports_gap_only_for_lagged_cursor() {
588 let mut ring = OutputRing::new(2, 16);
589 let mut stale = OutputCursor::new(0);
590 let mut aligned = OutputCursor::new(2);
591 for index in 0..4 {
592 ring.push(format!("{index}").into_bytes());
593 }
594
595 let stale_batch = ring.poll_cursor_batch(&mut stale, 8);
596 assert_eq!(stale_batch.len(), 1);
597 let OutputCursorItem::Gap(gap) = &stale_batch[0] else {
598 panic!("stale cursor should report its own output gap");
599 };
600 assert_eq!(gap.expected_sequence(), 0);
601 assert_eq!(gap.resume_sequence(), 2);
602 assert_eq!(gap.missed_events(), 2);
603 assert_eq!(stale.next_sequence(), 2);
604
605 let aligned_batch = ring.poll_cursor_batch(&mut aligned, 8);
606 let sequences = aligned_batch
607 .iter()
608 .map(|item| match item {
609 OutputCursorItem::Event(event) => event.sequence(),
610 OutputCursorItem::Gap(gap) => {
611 panic!("aligned cursor must not inherit stale cursor lag: {gap:?}")
612 }
613 })
614 .collect::<Vec<_>>();
615 assert_eq!(sequences, vec![2, 3]);
616 assert_eq!(aligned.missed_events(), 0);
617 assert_eq!(aligned.next_sequence(), ring.next_sequence());
618 }
619
620 #[test]
621 fn clear_retained_drops_recent_snapshot_range_without_rewinding_sequence() {
622 let mut ring = OutputRing::new(3, 16);
623 let mut cursor = OutputCursor::new(0);
624 ring.push(b"one".to_vec());
625 ring.push(b"two".to_vec());
626
627 ring.clear_retained();
628
629 assert_eq!(ring.next_sequence(), 2);
630 let snapshot = ring.recent_snapshot();
631 assert!(snapshot.is_empty());
632 assert_eq!(snapshot.oldest_sequence(), None);
633 assert_eq!(snapshot.newest_sequence(), None);
634
635 let Some(OutputCursorItem::Gap(gap)) = ring.poll_cursor(&mut cursor) else {
636 panic!("cursor should observe cleared retained output as a gap");
637 };
638 assert_eq!(gap.expected_sequence(), 0);
639 assert_eq!(gap.resume_sequence(), 2);
640 assert_eq!(gap.missed_events(), 2);
641 assert_eq!(gap.missed_range(), 0..2);
642 assert_eq!(gap.newest_sequence(), 1);
643 assert!(gap.recent_snapshot().is_empty());
644 }
645}