vtcode_commons/interjection/
events.rs1#![expect(
2 unused_results,
3 reason = "Queue maintenance intentionally discards fluent collection mutation results."
4)]
5
6use std::sync::{Arc, Mutex, MutexGuard};
7
8#[derive(Debug)]
9pub struct EventQueue<E> {
10 events: Arc<Mutex<Vec<E>>>,
11}
12
13impl<E> Clone for EventQueue<E> {
14 fn clone(&self) -> Self {
15 Self { events: Arc::clone(&self.events) }
16 }
17}
18
19impl<E> Default for EventQueue<E> {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl<E> EventQueue<E> {
26 pub(crate) fn new() -> Self {
27 Self { events: Arc::new(Mutex::new(Vec::new())) }
28 }
29
30 pub(crate) fn push(&self, event: E) {
31 self.lock().push(event);
32 }
33
34 fn push_capped(&self, event: E, max: usize) {
35 let mut q = self.lock();
36 q.push(event);
37 if q.len() > max {
38 let excess = q.len() - max;
39 q.drain(..excess);
40 }
41 }
42
43 fn len(&self) -> usize {
44 self.lock().len()
45 }
46
47 pub(crate) fn is_empty(&self) -> bool {
48 self.lock().is_empty()
49 }
50
51 fn drain_matching(&self, take: impl Fn(&E) -> bool) -> Vec<E> {
52 let mut q = self.lock();
53 let (matched, kept): (Vec<E>, Vec<E>) = std::mem::take(&mut *q).into_iter().partition(|e| take(e));
54 *q = kept;
55 matched
56 }
57
58 pub(crate) fn drain_all(&self) -> Vec<E> {
59 std::mem::take(&mut *self.lock())
60 }
61
62 fn clear(&self) {
63 self.lock().clear();
64 }
65
66 fn lock(&self) -> MutexGuard<'_, Vec<E>> {
67 self.events.lock().unwrap_or_else(|e| e.into_inner())
68 }
69}
70
71impl<E: Clone> EventQueue<E> {
72 fn snapshot(&self) -> Vec<E> {
73 self.lock().clone()
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn push_and_len() {
83 let q: EventQueue<u32> = EventQueue::new();
84 assert!(q.is_empty());
85 q.push(1);
86 q.push(2);
87 assert_eq!(q.len(), 2);
88 }
89
90 #[test]
91 fn clones_share_one_queue() {
92 let q: EventQueue<u32> = EventQueue::new();
93 let q2 = q.clone();
94 q.push(7);
95 assert_eq!(q2.len(), 1);
96 }
97
98 #[test]
99 fn push_capped_drops_oldest() {
100 let q: EventQueue<u32> = EventQueue::new();
101 for i in 0..5 {
102 q.push_capped(i, 3);
103 }
104 assert_eq!(q.drain_matching(|_| true), vec![2, 3, 4]);
105 assert!(q.is_empty());
106 }
107
108 #[test]
109 fn drain_matching_returns_matched_retains_rest_fifo() {
110 let q: EventQueue<u32> = EventQueue::new();
111 for i in 0..6 {
112 q.push(i);
113 }
114 let evens = q.drain_matching(|n| n % 2 == 0);
115 assert_eq!(evens, vec![0, 2, 4]);
116 assert_eq!(q.drain_matching(|_| true), vec![1, 3, 5]);
117 }
118
119 #[test]
120 fn push_capped_under_limit_keeps_all() {
121 let q: EventQueue<u32> = EventQueue::new();
122 q.push_capped(1, 5);
123 q.push_capped(2, 5);
124 assert_eq!(q.drain_matching(|_| true), vec![1, 2]);
125 }
126
127 #[test]
128 fn drain_matching_none_match_retains_all() {
129 let q: EventQueue<u32> = EventQueue::new();
130 q.push(1);
131 q.push(2);
132 assert!(q.drain_matching(|n| *n > 10).is_empty());
133 assert_eq!(q.len(), 2);
134 }
135
136 #[test]
137 fn drain_matching_on_empty_is_empty() {
138 let q: EventQueue<u32> = EventQueue::new();
139 assert!(q.drain_matching(|_| true).is_empty());
140 }
141
142 #[test]
143 fn drain_all_empties_in_fifo_order() {
144 let q: EventQueue<u32> = EventQueue::new();
145 q.push(1);
146 q.push(2);
147 assert_eq!(q.drain_all(), vec![1, 2]);
148 assert!(q.is_empty());
149 }
150
151 #[test]
152 fn clear_discards_all() {
153 let q: EventQueue<u32> = EventQueue::new();
154 q.push(1);
155 q.clear();
156 assert!(q.is_empty());
157 }
158
159 #[test]
160 fn snapshot_reads_without_draining() {
161 let q: EventQueue<u32> = EventQueue::new();
162 q.push(9);
163 assert_eq!(q.snapshot(), vec![9]);
164 assert_eq!(q.len(), 1);
165 }
166}