mecha10_core/priority_queue/
mod.rs1use crate::context::Receiver;
63use std::collections::VecDeque;
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
66pub enum Priority {
67 Low = 0,
68 #[default]
69 Normal = 1,
70 High = 2,
71 Critical = 3,
72}
73
74#[derive(Debug, Clone)]
75pub struct PriorityQueueConfig {
76 pub max_per_priority: Option<usize>,
77
78 pub prevent_starvation: bool,
79
80 pub starvation_threshold: usize,
81}
82
83impl Default for PriorityQueueConfig {
84 fn default() -> Self {
85 Self {
86 max_per_priority: Some(1000),
87 prevent_starvation: true,
88 starvation_threshold: 100,
89 }
90 }
91}
92
93pub struct PriorityReceiver<T> {
94 sources: Vec<PrioritySource<T>>,
95 config: PriorityQueueConfig,
96 high_priority_count: usize,
97}
98
99struct PrioritySource<T> {
100 receiver: Receiver<T>,
101 priority: Priority,
102 buffer: VecDeque<T>,
103}
104
105impl<T> PriorityReceiver<T>
106where
107 T: Send + 'static,
108{
109 pub fn new() -> Self {
111 Self::with_config(PriorityQueueConfig::default())
112 }
113
114 pub fn with_config(config: PriorityQueueConfig) -> Self {
116 Self {
117 sources: Vec::new(),
118 config,
119 high_priority_count: 0,
120 }
121 }
122
123 pub fn add_source(&mut self, receiver: Receiver<T>, priority: Priority) {
148 self.sources.push(PrioritySource {
149 receiver,
150 priority,
151 buffer: VecDeque::new(),
152 });
153 }
154
155 pub async fn recv(&mut self) -> Option<(T, Priority)> {
165 if self.sources.is_empty() {
166 return None;
167 }
168
169 loop {
170 self.try_fill_buffers();
171
172 if self.config.prevent_starvation && self.high_priority_count >= self.config.starvation_threshold {
173 if let Some((msg, prio)) = self.take_lower_priority_message() {
174 self.high_priority_count = 0;
175 return Some((msg, prio));
176 }
177 }
178
179 if let Some((msg, prio)) = self.take_highest_priority_message() {
180 if prio >= Priority::High {
181 self.high_priority_count += 1;
182 }
183 return Some((msg, prio));
184 }
185
186 if !self.wait_for_message().await {
187 return None;
188 }
189 }
190 }
191
192 fn try_fill_buffers(&mut self) {
194 for source in &mut self.sources {
195 let max_size = self.config.max_per_priority.unwrap_or(usize::MAX);
196
197 while source.buffer.len() < max_size {
198 match source.receiver.try_recv() {
199 Ok(msg) => source.buffer.push_back(msg),
200 Err(_) => break,
201 }
202 }
203 }
204 }
205
206 async fn wait_for_message(&mut self) -> bool {
208 if self.sources.is_empty() {
209 return false;
210 }
211
212 loop {
213 for source in &mut self.sources {
214 if let Some(msg) = source.receiver.recv().await {
215 source.buffer.push_back(msg);
216 return true;
217 }
218 }
219
220 self.sources.retain(|s| !s.buffer.is_empty() || !s.receiver.is_empty());
221
222 if self.sources.is_empty() {
223 return false;
224 }
225 }
226 }
227
228 fn take_highest_priority_message(&mut self) -> Option<(T, Priority)> {
230 self.take_extreme_priority_message(|candidate, best| candidate > best)
231 }
232
233 fn take_lower_priority_message(&mut self) -> Option<(T, Priority)> {
235 self.take_extreme_priority_message(|candidate, best| candidate < best)
236 }
237
238 fn take_extreme_priority_message(
241 &mut self,
242 is_better: impl Fn(Priority, Priority) -> bool,
243 ) -> Option<(T, Priority)> {
244 let mut best: Option<(usize, Priority)> = None;
245
246 for (idx, source) in self.sources.iter().enumerate() {
247 if source.buffer.is_empty() {
248 continue;
249 }
250 match best {
251 None => best = Some((idx, source.priority)),
252 Some((_, best_priority)) if is_better(source.priority, best_priority) => {
253 best = Some((idx, source.priority));
254 }
255 _ => {}
256 }
257 }
258
259 best.and_then(|(idx, _)| {
260 self.sources[idx]
261 .buffer
262 .pop_front()
263 .map(|msg| (msg, self.sources[idx].priority))
264 })
265 }
266
267 pub fn stats(&self) -> PriorityQueueStats {
269 let mut counts_by_priority = [0; 4];
270 let mut total_buffered = 0;
271
272 for source in &self.sources {
273 let idx = source.priority as usize;
274 counts_by_priority[idx] += source.buffer.len();
275 total_buffered += source.buffer.len();
276 }
277
278 PriorityQueueStats {
279 total_buffered,
280 critical_buffered: counts_by_priority[Priority::Critical as usize],
281 high_buffered: counts_by_priority[Priority::High as usize],
282 normal_buffered: counts_by_priority[Priority::Normal as usize],
283 low_buffered: counts_by_priority[Priority::Low as usize],
284 source_count: self.sources.len(),
285 }
286 }
287}
288
289impl<T> Default for PriorityReceiver<T>
290where
291 T: Send + 'static,
292{
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 fn make_source(priority: Priority, messages: Vec<u32>) -> PrioritySource<u32> {
303 let (_tx, rx) = tokio::sync::mpsc::unbounded_channel();
304 PrioritySource {
305 receiver: Receiver::from(rx),
306 priority,
307 buffer: messages.into_iter().collect(),
308 }
309 }
310
311 #[test]
319 fn take_highest_priority_message_selects_all_low_messages() {
320 let mut prio_rx: PriorityReceiver<u32> = PriorityReceiver::new();
321 prio_rx.sources.push(make_source(Priority::Low, vec![1, 2, 3]));
322
323 let (msg, priority) = prio_rx
324 .take_highest_priority_message()
325 .expect("a Low-priority message should still be returned when it's the only one buffered");
326 assert_eq!(msg, 1);
327 assert_eq!(priority, Priority::Low);
328
329 assert_eq!(prio_rx.take_highest_priority_message(), Some((2, Priority::Low)));
330 assert_eq!(prio_rx.take_highest_priority_message(), Some((3, Priority::Low)));
331 assert_eq!(prio_rx.take_highest_priority_message(), None);
332 }
333
334 #[test]
337 fn take_lower_priority_message_selects_all_critical_messages() {
338 let mut prio_rx: PriorityReceiver<u32> = PriorityReceiver::new();
339 prio_rx.sources.push(make_source(Priority::Critical, vec![10, 20]));
340
341 let (msg, priority) = prio_rx
342 .take_lower_priority_message()
343 .expect("a Critical-priority message should still be returned when it's the only one buffered");
344 assert_eq!(msg, 10);
345 assert_eq!(priority, Priority::Critical);
346
347 assert_eq!(prio_rx.take_lower_priority_message(), Some((20, Priority::Critical)));
348 assert_eq!(prio_rx.take_lower_priority_message(), None);
349 }
350
351 #[tokio::test]
355 async fn recv_delivers_messages_from_a_low_priority_only_source() {
356 let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<u32>();
357 let mut prio_rx: PriorityReceiver<u32> = PriorityReceiver::new();
358 prio_rx.add_source(Receiver::from(rx), Priority::Low);
359
360 tx.send(42).unwrap();
361 drop(tx);
362
363 let result = tokio::time::timeout(std::time::Duration::from_secs(5), prio_rx.recv())
364 .await
365 .expect("recv() should not hang starving a Low-only source");
366 assert_eq!(result, Some((42, Priority::Low)));
367 }
368
369 #[test]
372 fn take_highest_priority_message_prefers_higher_priority() {
373 let mut prio_rx: PriorityReceiver<u32> = PriorityReceiver::new();
374 prio_rx.sources.push(make_source(Priority::Low, vec![1]));
375 prio_rx.sources.push(make_source(Priority::Critical, vec![2]));
376
377 assert_eq!(prio_rx.take_highest_priority_message(), Some((2, Priority::Critical)));
378 }
379}
380
381#[derive(Debug, Clone)]
382pub struct PriorityQueueStats {
383 pub total_buffered: usize,
384 pub critical_buffered: usize,
385 pub high_buffered: usize,
386 pub normal_buffered: usize,
387 pub low_buffered: usize,
388 pub source_count: usize,
389}