1#![allow(dead_code)]
2use std::cmp::Ordering;
9use std::collections::BinaryHeap;
10use std::fmt;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum Priority {
15 Low,
17 Normal,
19 High,
21 Critical,
23}
24
25impl Priority {
26 fn weight(self) -> u32 {
28 match self {
29 Self::Low => 0,
30 Self::Normal => 1,
31 Self::High => 2,
32 Self::Critical => 3,
33 }
34 }
35}
36
37impl fmt::Display for Priority {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 Self::Low => write!(f, "Low"),
41 Self::Normal => write!(f, "Normal"),
42 Self::High => write!(f, "High"),
43 Self::Critical => write!(f, "Critical"),
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct PriorityTask {
51 pub task_id: String,
53 pub priority: Priority,
55 pub submitted_at: u64,
57 pub deadline: Option<u64>,
59 pub age_bumps: u32,
61 pub estimated_duration_ms: u64,
63}
64
65impl PriorityTask {
66 pub fn new(task_id: impl Into<String>, priority: Priority, submitted_at: u64) -> Self {
68 Self {
69 task_id: task_id.into(),
70 priority,
71 submitted_at,
72 deadline: None,
73 age_bumps: 0,
74 estimated_duration_ms: 0,
75 }
76 }
77
78 #[must_use]
80 pub fn with_deadline(mut self, deadline_ms: u64) -> Self {
81 self.deadline = Some(deadline_ms);
82 self
83 }
84
85 #[must_use]
87 pub fn with_estimated_duration(mut self, ms: u64) -> Self {
88 self.estimated_duration_ms = ms;
89 self
90 }
91
92 fn effective_weight(&self) -> u32 {
94 self.priority.weight() + self.age_bumps
95 }
96
97 fn sort_key(&self) -> (u32, bool, u64, u64) {
102 let has_dl = self.deadline.is_some();
103 let inverse_dl = self.deadline.map_or(0, |d| u64::MAX - d);
104 let inverse_submit = u64::MAX - self.submitted_at;
105 (self.effective_weight(), has_dl, inverse_dl, inverse_submit)
106 }
107}
108
109impl PartialEq for PriorityTask {
110 fn eq(&self, other: &Self) -> bool {
111 self.task_id == other.task_id
112 }
113}
114
115impl Eq for PriorityTask {}
116
117impl PartialOrd for PriorityTask {
118 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
119 Some(self.cmp(other))
120 }
121}
122
123impl Ord for PriorityTask {
124 fn cmp(&self, other: &Self) -> Ordering {
125 self.sort_key().cmp(&other.sort_key())
126 }
127}
128
129#[derive(Debug)]
131pub struct TaskPriorityQueue {
132 heap: BinaryHeap<PriorityTask>,
134 capacity: usize,
136 age_bump_amount: u32,
138}
139
140impl TaskPriorityQueue {
141 #[must_use]
143 pub fn new() -> Self {
144 Self {
145 heap: BinaryHeap::new(),
146 capacity: 0,
147 age_bump_amount: 1,
148 }
149 }
150
151 #[must_use]
153 pub fn with_capacity(capacity: usize) -> Self {
154 Self {
155 heap: BinaryHeap::with_capacity(capacity),
156 capacity,
157 age_bump_amount: 1,
158 }
159 }
160
161 pub fn set_age_bump_amount(&mut self, amount: u32) {
163 self.age_bump_amount = amount;
164 }
165
166 pub fn push(&mut self, task: PriorityTask) -> bool {
170 if self.capacity > 0 && self.heap.len() >= self.capacity {
171 return false;
172 }
173 self.heap.push(task);
174 true
175 }
176
177 pub fn pop(&mut self) -> Option<PriorityTask> {
179 self.heap.pop()
180 }
181
182 #[must_use]
184 pub fn peek(&self) -> Option<&PriorityTask> {
185 self.heap.peek()
186 }
187
188 #[must_use]
190 pub fn len(&self) -> usize {
191 self.heap.len()
192 }
193
194 #[must_use]
196 pub fn is_empty(&self) -> bool {
197 self.heap.is_empty()
198 }
199
200 pub fn clear(&mut self) {
202 self.heap.clear();
203 }
204
205 pub fn apply_aging(&mut self) {
210 let bump = self.age_bump_amount;
211 let items: Vec<PriorityTask> = self.heap.drain().collect();
212 for mut task in items {
213 if task.priority != Priority::Critical {
214 task.age_bumps += bump;
215 }
216 self.heap.push(task);
217 }
218 }
219
220 pub fn remove_expired(&mut self, now_ms: u64) -> Vec<PriorityTask> {
225 let mut expired = Vec::new();
226 let mut kept = Vec::new();
227 for task in self.heap.drain() {
228 if let Some(dl) = task.deadline {
229 if dl < now_ms {
230 expired.push(task);
231 continue;
232 }
233 }
234 kept.push(task);
235 }
236 for task in kept {
237 self.heap.push(task);
238 }
239 expired
240 }
241
242 pub fn drain_priority(&mut self, priority: Priority) -> Vec<PriorityTask> {
244 let mut matched = Vec::new();
245 let mut rest = Vec::new();
246 for task in self.heap.drain() {
247 if task.priority == priority {
248 matched.push(task);
249 } else {
250 rest.push(task);
251 }
252 }
253 for task in rest {
254 self.heap.push(task);
255 }
256 matched
257 }
258
259 #[must_use]
261 pub fn capacity(&self) -> usize {
262 self.capacity
263 }
264
265 pub fn try_preempt(&mut self, incoming: PriorityTask) -> Option<PriorityTask> {
275 if self.capacity == 0 || self.heap.len() < self.capacity {
276 return None;
278 }
279 let min_weight = self.heap.iter().map(|t| t.effective_weight()).min()?;
281
282 if incoming.effective_weight() <= min_weight {
283 return None;
285 }
286
287 let mut items: Vec<PriorityTask> = self.heap.drain().collect();
290 let victim_idx = items
291 .iter()
292 .position(|t| t.effective_weight() == min_weight)?;
293 let victim = items.remove(victim_idx);
294 for task in items {
295 self.heap.push(task);
296 }
297 self.heap.push(incoming);
298 Some(victim)
299 }
300
301 #[must_use]
304 pub fn would_preempt(&self, incoming: &PriorityTask) -> bool {
305 if self.capacity == 0 || self.heap.len() < self.capacity {
306 return false;
307 }
308 let min_weight = self.heap.iter().map(|t| t.effective_weight()).min();
309 min_weight.is_some_and(|m| incoming.effective_weight() > m)
310 }
311}
312
313impl Default for TaskPriorityQueue {
314 fn default() -> Self {
315 Self::new()
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn test_priority_weight() {
325 assert!(Priority::Critical.weight() > Priority::High.weight());
326 assert!(Priority::High.weight() > Priority::Normal.weight());
327 assert!(Priority::Normal.weight() > Priority::Low.weight());
328 }
329
330 #[test]
331 fn test_priority_display() {
332 assert_eq!(Priority::Low.to_string(), "Low");
333 assert_eq!(Priority::Critical.to_string(), "Critical");
334 }
335
336 #[test]
337 fn test_new_queue_is_empty() {
338 let q = TaskPriorityQueue::new();
339 assert!(q.is_empty());
340 assert_eq!(q.len(), 0);
341 }
342
343 #[test]
344 fn test_push_and_pop_single() {
345 let mut q = TaskPriorityQueue::new();
346 let task = PriorityTask::new("t1", Priority::Normal, 100);
347 assert!(q.push(task));
348 assert_eq!(q.len(), 1);
349 let popped = q.pop().expect("pop should return a value");
350 assert_eq!(popped.task_id, "t1");
351 assert!(q.is_empty());
352 }
353
354 #[test]
355 fn test_pop_order_by_priority() {
356 let mut q = TaskPriorityQueue::new();
357 q.push(PriorityTask::new("low", Priority::Low, 100));
358 q.push(PriorityTask::new("high", Priority::High, 100));
359 q.push(PriorityTask::new("normal", Priority::Normal, 100));
360 assert_eq!(q.pop().expect("pop should return a value").task_id, "high");
361 assert_eq!(
362 q.pop().expect("pop should return a value").task_id,
363 "normal"
364 );
365 assert_eq!(q.pop().expect("pop should return a value").task_id, "low");
366 }
367
368 #[test]
369 fn test_same_priority_earlier_submit_first() {
370 let mut q = TaskPriorityQueue::new();
371 q.push(PriorityTask::new("later", Priority::Normal, 200));
372 q.push(PriorityTask::new("earlier", Priority::Normal, 100));
373 assert_eq!(
374 q.pop().expect("pop should return a value").task_id,
375 "earlier"
376 );
377 }
378
379 #[test]
380 fn test_deadline_tasks_preferred() {
381 let mut q = TaskPriorityQueue::new();
382 q.push(PriorityTask::new("no_dl", Priority::Normal, 100));
383 q.push(PriorityTask::new("with_dl", Priority::Normal, 100).with_deadline(5000));
384 assert_eq!(
385 q.pop().expect("pop should return a value").task_id,
386 "with_dl"
387 );
388 }
389
390 #[test]
391 fn test_earlier_deadline_first() {
392 let mut q = TaskPriorityQueue::new();
393 q.push(PriorityTask::new("late_dl", Priority::Normal, 100).with_deadline(9000));
394 q.push(PriorityTask::new("early_dl", Priority::Normal, 100).with_deadline(3000));
395 assert_eq!(
396 q.pop().expect("pop should return a value").task_id,
397 "early_dl"
398 );
399 }
400
401 #[test]
402 fn test_capacity_limit() {
403 let mut q = TaskPriorityQueue::with_capacity(2);
404 assert!(q.push(PriorityTask::new("t1", Priority::Normal, 100)));
405 assert!(q.push(PriorityTask::new("t2", Priority::Normal, 200)));
406 assert!(!q.push(PriorityTask::new("t3", Priority::Normal, 300)));
407 assert_eq!(q.len(), 2);
408 }
409
410 #[test]
411 fn test_peek() {
412 let mut q = TaskPriorityQueue::new();
413 q.push(PriorityTask::new("t1", Priority::High, 100));
414 let peeked = q.peek().expect("peek should return a value");
415 assert_eq!(peeked.task_id, "t1");
416 assert_eq!(q.len(), 1); }
418
419 #[test]
420 fn test_clear() {
421 let mut q = TaskPriorityQueue::new();
422 q.push(PriorityTask::new("t1", Priority::Normal, 100));
423 q.push(PriorityTask::new("t2", Priority::Normal, 200));
424 q.clear();
425 assert!(q.is_empty());
426 }
427
428 #[test]
429 fn test_apply_aging() {
430 let mut q = TaskPriorityQueue::new();
431 q.push(PriorityTask::new("low", Priority::Low, 100));
432 q.push(PriorityTask::new("crit", Priority::Critical, 100));
433 q.apply_aging();
435 q.apply_aging();
436 q.apply_aging();
437 let first = q.pop().expect("pop should return a value");
439 assert!(first.task_id == "low" || first.task_id == "crit");
441 }
442
443 #[test]
444 fn test_remove_expired() {
445 let mut q = TaskPriorityQueue::new();
446 q.push(PriorityTask::new("expired", Priority::Normal, 100).with_deadline(500));
447 q.push(PriorityTask::new("active", Priority::Normal, 100).with_deadline(2000));
448 q.push(PriorityTask::new("no_dl", Priority::Normal, 100));
449 let expired = q.remove_expired(1000);
450 assert_eq!(expired.len(), 1);
451 assert_eq!(expired[0].task_id, "expired");
452 assert_eq!(q.len(), 2);
453 }
454
455 #[test]
456 fn test_drain_priority() {
457 let mut q = TaskPriorityQueue::new();
458 q.push(PriorityTask::new("h1", Priority::High, 100));
459 q.push(PriorityTask::new("n1", Priority::Normal, 100));
460 q.push(PriorityTask::new("h2", Priority::High, 200));
461 let high_tasks = q.drain_priority(Priority::High);
462 assert_eq!(high_tasks.len(), 2);
463 assert_eq!(q.len(), 1);
464 }
465
466 #[test]
467 fn test_with_estimated_duration() {
468 let task = PriorityTask::new("t1", Priority::Normal, 100).with_estimated_duration(5000);
469 assert_eq!(task.estimated_duration_ms, 5000);
470 }
471
472 #[test]
475 fn test_preempt_replaces_lowest_priority() {
476 let mut q = TaskPriorityQueue::with_capacity(2);
477 q.push(PriorityTask::new("low", Priority::Low, 100));
478 q.push(PriorityTask::new("normal", Priority::Normal, 100));
479 assert_eq!(q.len(), 2);
480
481 let victim = q
483 .try_preempt(PriorityTask::new("crit", Priority::Critical, 200))
484 .expect("preemption should succeed");
485 assert_eq!(victim.task_id, "low");
486 assert_eq!(q.len(), 2);
487 let first = q.pop().expect("pop should return a value");
489 assert_eq!(first.task_id, "crit");
490 }
491
492 #[test]
493 fn test_preempt_not_triggered_when_not_at_capacity() {
494 let mut q = TaskPriorityQueue::with_capacity(5);
495 q.push(PriorityTask::new("t1", Priority::Low, 100));
496
497 let result = q.try_preempt(PriorityTask::new("crit", Priority::Critical, 200));
499 assert!(result.is_none());
500 }
501
502 #[test]
503 fn test_preempt_not_triggered_equal_priority() {
504 let mut q = TaskPriorityQueue::with_capacity(1);
505 q.push(PriorityTask::new("existing", Priority::High, 100));
506
507 let result = q.try_preempt(PriorityTask::new("newcomer", Priority::High, 200));
509 assert!(result.is_none());
510 }
511
512 #[test]
513 fn test_would_preempt_logic() {
514 let mut q = TaskPriorityQueue::with_capacity(1);
515 q.push(PriorityTask::new("low", Priority::Low, 100));
516
517 let crit = PriorityTask::new("crit", Priority::Critical, 200);
518 let low2 = PriorityTask::new("low2", Priority::Low, 300);
519
520 assert!(q.would_preempt(&crit));
521 assert!(!q.would_preempt(&low2));
522 }
523
524 #[test]
525 fn test_preempt_unlimited_queue_returns_none() {
526 let mut q = TaskPriorityQueue::new(); q.push(PriorityTask::new("t1", Priority::Low, 100));
528
529 let result = q.try_preempt(PriorityTask::new("crit", Priority::Critical, 200));
530 assert!(result.is_none());
531 }
532}