1use std::collections::VecDeque;
4
5use crate::ids::{EntityId, EventId, OwnerEpoch, StationId, Tick};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum EventPriority {
10 Critical,
12 Important,
14 BestEffort,
16}
17
18#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum EventKind {
21 Custom(u32),
23 HandoffPrepare {
25 entity_id: EntityId,
27 },
28 HandoffCommit {
30 entity_id: EntityId,
32 owner_epoch: OwnerEpoch,
34 },
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct StationEvent {
40 pub id: EventId,
42 pub source: StationId,
44 pub target: StationId,
46 pub source_tick: Tick,
48 pub target_tick: Tick,
50 pub priority: EventPriority,
52 pub kind: EventKind,
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58pub struct EventQueueLimits {
59 pub critical: usize,
61 pub important: usize,
63 pub best_effort: usize,
65}
66
67impl Default for EventQueueLimits {
68 fn default() -> Self {
69 Self {
70 critical: 1024,
71 important: 4096,
72 best_effort: 8192,
73 }
74 }
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
79pub enum PushOutcome {
80 Accepted,
82 DroppedOldestBestEffort,
84}
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub enum EventQueueError {
89 QueueFull(EventPriority),
91}
92
93impl core::fmt::Display for EventQueueError {
94 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
95 match self {
96 Self::QueueFull(priority) => write!(f, "{priority:?} event queue is full"),
97 }
98 }
99}
100
101impl std::error::Error for EventQueueError {}
102
103#[derive(Clone, Debug)]
105pub struct EventQueues {
106 limits: EventQueueLimits,
107 critical: VecDeque<StationEvent>,
108 important: VecDeque<StationEvent>,
109 best_effort: VecDeque<StationEvent>,
110}
111
112impl EventQueues {
113 pub fn new(limits: EventQueueLimits) -> Self {
115 Self {
116 limits,
117 critical: VecDeque::new(),
118 important: VecDeque::new(),
119 best_effort: VecDeque::new(),
120 }
121 }
122
123 pub fn push(&mut self, event: StationEvent) -> Result<PushOutcome, EventQueueError> {
125 match event.priority {
126 EventPriority::Critical => {
127 if self.critical.len() >= self.limits.critical {
128 Err(EventQueueError::QueueFull(EventPriority::Critical))
129 } else {
130 self.critical.push_back(event);
131 Ok(PushOutcome::Accepted)
132 }
133 }
134 EventPriority::Important => {
135 if self.important.len() >= self.limits.important {
136 Err(EventQueueError::QueueFull(EventPriority::Important))
137 } else {
138 self.important.push_back(event);
139 Ok(PushOutcome::Accepted)
140 }
141 }
142 EventPriority::BestEffort => {
143 let outcome = if self.best_effort.len() >= self.limits.best_effort {
144 self.best_effort.pop_front();
145 PushOutcome::DroppedOldestBestEffort
146 } else {
147 PushOutcome::Accepted
148 };
149 self.best_effort.push_back(event);
150 Ok(outcome)
151 }
152 }
153 }
154
155 pub fn pop_next(&mut self) -> Option<StationEvent> {
157 self.critical
158 .pop_front()
159 .or_else(|| self.important.pop_front())
160 .or_else(|| self.best_effort.pop_front())
161 }
162
163 pub fn drain_ready_into(&mut self, current_tick: Tick, out: &mut Vec<StationEvent>) -> usize {
168 let before = out.len();
169 drain_priority_ready(&mut self.critical, current_tick, out);
170 drain_priority_ready(&mut self.important, current_tick, out);
171 drain_priority_ready(&mut self.best_effort, current_tick, out);
172 out.len() - before
173 }
174
175 pub fn len(&self) -> usize {
177 self.critical.len() + self.important.len() + self.best_effort.len()
178 }
179
180 pub fn is_empty(&self) -> bool {
182 self.len() == 0
183 }
184
185 pub fn retained_capacity(&self, priority: EventPriority) -> usize {
187 match priority {
188 EventPriority::Critical => self.critical.capacity(),
189 EventPriority::Important => self.important.capacity(),
190 EventPriority::BestEffort => self.best_effort.capacity(),
191 }
192 }
193
194 pub fn total_retained_capacity(&self) -> usize {
196 self.critical
197 .capacity()
198 .saturating_add(self.important.capacity())
199 .saturating_add(self.best_effort.capacity())
200 }
201}
202
203fn drain_priority_ready(
204 queue: &mut VecDeque<StationEvent>,
205 current_tick: Tick,
206 out: &mut Vec<StationEvent>,
207) {
208 let queued = queue.len();
209 for _ in 0..queued {
210 let event = queue
211 .pop_front()
212 .expect("initial queue length bounds the drain loop");
213 if event.target_tick <= current_tick {
214 out.push(event);
215 } else {
216 queue.push_back(event);
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::ids::EventId;
225
226 fn event(id: u64, priority: EventPriority, target_tick: u64) -> StationEvent {
227 StationEvent {
228 id: EventId::new(id),
229 source: StationId::new(1),
230 target: StationId::new(2),
231 source_tick: Tick::new(0),
232 target_tick: Tick::new(target_tick),
233 priority,
234 kind: EventKind::Custom(u32::try_from(id).expect("test id fits u32")),
235 }
236 }
237
238 #[test]
239 fn ready_drain_preserves_priority_fifo_and_delayed_order() {
240 let mut queues = EventQueues::new(EventQueueLimits {
241 critical: 8,
242 important: 8,
243 best_effort: 8,
244 });
245 for value in [
246 event(1, EventPriority::Critical, 3),
247 event(2, EventPriority::Critical, 1),
248 event(3, EventPriority::Important, 1),
249 event(4, EventPriority::Important, 4),
250 event(5, EventPriority::BestEffort, 1),
251 event(6, EventPriority::BestEffort, 5),
252 ] {
253 queues.push(value).expect("test queue has capacity");
254 }
255 let mut ready = Vec::with_capacity(8);
256
257 assert_eq!(queues.drain_ready_into(Tick::new(1), &mut ready), 3);
258 assert_eq!(
259 ready.iter().map(|event| event.id).collect::<Vec<_>>(),
260 [EventId::new(2), EventId::new(3), EventId::new(5)]
261 );
262 assert_eq!(queues.len(), 3);
263
264 ready.clear();
265 assert_eq!(queues.drain_ready_into(Tick::new(5), &mut ready), 3);
266 assert_eq!(
267 ready.iter().map(|event| event.id).collect::<Vec<_>>(),
268 [EventId::new(1), EventId::new(4), EventId::new(6)]
269 );
270 assert!(queues.is_empty());
271 }
272
273 #[test]
274 fn event_queues_allocate_lazily_and_retain_peak_capacity() {
275 let mut queues = EventQueues::new(EventQueueLimits::default());
276 assert_eq!(queues.total_retained_capacity(), 0);
277
278 for (offset, priority) in [
279 EventPriority::Critical,
280 EventPriority::Important,
281 EventPriority::BestEffort,
282 ]
283 .into_iter()
284 .enumerate()
285 {
286 for index in 0..8 {
287 queues
288 .push(event(
289 u64::try_from(offset * 8 + index).expect("test id fits u64"),
290 priority,
291 0,
292 ))
293 .expect("event burst should queue");
294 }
295 assert!(queues.retained_capacity(priority) >= 8);
296 }
297 let peak = queues.total_retained_capacity();
298 while queues.pop_next().is_some() {}
299 assert_eq!(queues.total_retained_capacity(), peak);
300 }
301}