1use std::cmp::Ordering;
8use std::collections::BinaryHeap;
9
10use crate::context::{Context, ExecutionPhase};
11use crate::{trace, HashMap, HashMapExt};
12
13type Callback = dyn FnOnce(&mut Context);
14type BoxedCallback = Box<Callback>;
15
16struct QueuedPlan {
17 callback: BoxedCallback,
18 is_passive: bool,
19}
20
21pub(crate) struct PlanQueue {
27 queue: BinaryHeap<PlanSchedule>,
28 shutdown_queue: BinaryHeap<PlanSchedule>,
29 data_map: HashMap<u64, QueuedPlan>,
30 regular_plan_count: usize,
32 next_plan_id: u64,
34 #[cfg(feature = "profiling")]
37 pub(crate) max_plans_in_flight: u64,
38 #[cfg(feature = "profiling")]
39 pub(crate) max_memory_in_use: u64,
40}
41
42impl PlanQueue {
43 #[must_use]
45 pub(crate) fn new() -> PlanQueue {
46 PlanQueue {
47 queue: BinaryHeap::new(),
48 shutdown_queue: BinaryHeap::new(),
49 data_map: HashMap::new(),
50 regular_plan_count: 0,
51 next_plan_id: 0,
52 #[cfg(feature = "profiling")]
53 max_plans_in_flight: 0,
54 #[cfg(feature = "profiling")]
55 max_memory_in_use: 0,
56 }
57 }
58
59 pub(crate) fn add_plan(
64 &mut self,
65 time: f64,
66 callback: BoxedCallback,
67 phase: ExecutionPhase,
68 is_passive: bool,
69 ) -> PlanId {
70 trace!("adding plan at {time}");
71 let plan_id = self.next_plan_id;
72 self.queue.push(PlanSchedule {
73 plan_id,
74 time,
75 phase,
76 });
77 self.data_map.insert(
78 plan_id,
79 QueuedPlan {
80 callback,
81 is_passive,
82 },
83 );
84 if !is_passive {
85 self.regular_plan_count += 1;
86 }
87 self.next_plan_id += 1;
88 self.update_profiling_high_water_marks();
89
90 PlanId(plan_id)
91 }
92
93 pub(crate) fn add_shutdown_plan(
98 &mut self,
99 callback: BoxedCallback,
100 phase: ExecutionPhase,
101 ) -> PlanId {
102 trace!("adding shutdown-time plan");
103 let plan_id = self.next_plan_id;
104 self.shutdown_queue.push(PlanSchedule {
105 plan_id,
106 time: 0.0,
107 phase,
108 });
109 self.data_map.insert(
110 plan_id,
111 QueuedPlan {
112 callback,
113 is_passive: true,
114 },
115 );
116 self.next_plan_id += 1;
117 self.update_profiling_high_water_marks();
118
119 PlanId(plan_id)
120 }
121
122 pub(crate) fn cancel_plan(&mut self, plan_id: &PlanId) -> Option<BoxedCallback> {
124 trace!("cancel plan {plan_id:?}");
125 self.data_map.remove(&plan_id.0).map(|queued_plan| {
128 if !queued_plan.is_passive {
129 self.regular_plan_count -= 1;
130 }
131 queued_plan.callback
132 })
133 }
134
135 #[must_use]
137 pub(crate) fn next_time(&mut self) -> Option<f64> {
138 while let Some(entry) = self.queue.peek() {
139 if self.data_map.contains_key(&entry.plan_id) {
141 return Some(entry.time);
142 }
143 self.queue.pop();
145 }
146 None
147 }
148
149 #[allow(dead_code)]
151 pub(crate) fn clear(&mut self) {
152 self.data_map.clear();
153 self.queue.clear();
154 self.shutdown_queue.clear();
155 self.regular_plan_count = 0;
156 self.next_plan_id = 0;
157 }
158
159 pub(crate) fn pop_next_if_active(&mut self) -> Option<Plan> {
167 if self.regular_plan_count == 0 {
168 return None;
169 }
170
171 trace!("getting next plan");
172 loop {
173 let entry = self
176 .queue
177 .pop()
178 .expect("plan count was positive but no plan was available");
179
180 if let Some(queued_plan) = self.data_map.remove(&entry.plan_id) {
182 if !queued_plan.is_passive {
183 self.regular_plan_count -= 1;
184 }
185 return Some(Plan {
186 time: entry.time,
187 data: queued_plan.callback,
188 });
189 }
190 }
191 }
192
193 pub(crate) fn pop_next_at(&mut self, time: f64) -> Option<Plan> {
198 loop {
199 match self.queue.peek() {
200 Some(entry) if !self.data_map.contains_key(&entry.plan_id) => {
202 self.queue.pop();
203 }
204
205 Some(entry) if entry.time == time => {
207 let entry = self.queue.pop().unwrap();
209 let queued_plan = self
210 .data_map
211 .remove(&entry.plan_id)
212 .expect("live plan must have callback");
213 if !queued_plan.is_passive {
214 self.regular_plan_count -= 1;
215 }
216 return Some(Plan {
217 time,
218 data: queued_plan.callback,
219 });
220 }
221
222 _ => return None,
224 }
225 }
226 }
227
228 pub(crate) fn pop_next_shutdown(&mut self) -> Option<Plan> {
233 trace!("getting next shutdown-time plan");
234 std::iter::from_fn(|| self.shutdown_queue.pop()).find_map(|entry| {
235 let queued_plan = self.data_map.remove(&entry.plan_id)?;
238 if !queued_plan.is_passive {
239 self.regular_plan_count -= 1;
240 }
241 Some(Plan {
242 time: entry.time,
243 data: queued_plan.callback,
244 })
245 })
246 }
247
248 fn update_profiling_high_water_marks(&mut self) {
249 #[cfg(feature = "profiling")]
250 {
251 let plans_in_flight = self.queue.len() + self.shutdown_queue.len();
252 self.max_plans_in_flight = self.max_plans_in_flight.max(plans_in_flight as u64);
253 self.max_memory_in_use = self
254 .max_memory_in_use
255 .max(self.estimated_memory_in_use() as u64);
256 }
257 }
258
259 #[cfg(feature = "profiling")]
260 fn estimated_memory_in_use(&self) -> usize {
261 let queue_bytes =
262 (self.queue.capacity() + self.shutdown_queue.capacity()) * size_of::<PlanSchedule>();
263
264 let map_entry_bytes = self.data_map.capacity() * size_of::<(u64, QueuedPlan)>();
265
266 queue_bytes + map_entry_bytes
267 }
268}
269
270impl Default for PlanQueue {
271 fn default() -> Self {
272 Self::new()
273 }
274}
275
276#[derive(PartialEq, Debug, Clone, Copy)]
282pub(crate) struct PlanSchedule {
283 pub plan_id: u64,
284 pub time: f64,
285 pub phase: ExecutionPhase,
286}
287
288impl Eq for PlanSchedule {}
289
290impl PartialOrd for PlanSchedule {
291 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
292 Some(self.cmp(other))
293 }
294}
295
296impl Ord for PlanSchedule {
298 fn cmp(&self, other: &Self) -> Ordering {
299 let time_ordering = self.time.partial_cmp(&other.time).unwrap().reverse();
300 match time_ordering {
301 Ordering::Equal => {
302 let phase_ordering = self.phase.partial_cmp(&other.phase).unwrap().reverse();
303 match phase_ordering {
304 Ordering::Equal => self.plan_id.cmp(&other.plan_id).reverse(),
305 _ => phase_ordering,
306 }
307 }
308 _ => time_ordering,
309 }
310 }
311}
312
313#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
328pub struct PlanId(pub(crate) u64);
329
330pub(crate) struct Plan {
332 pub time: f64,
333 pub data: BoxedCallback,
334}
335
336#[cfg(test)]
337#[allow(clippy::float_cmp)]
338mod tests {
339 use std::cell::RefCell;
340 use std::rc::Rc;
341
342 use super::PlanQueue;
343 use crate::context::{Context, ExecutionPhase};
344
345 fn callback(value: u32, observed: Rc<RefCell<Vec<u32>>>) -> Box<dyn FnOnce(&mut Context)> {
346 Box::new(move |_| observed.borrow_mut().push(value))
347 }
348
349 fn run_plan(plan: super::Plan, context: &mut Context) {
350 (plan.data)(context);
351 }
352
353 #[test]
354 fn empty_queue() {
355 let mut plan_queue = PlanQueue::new();
356 assert!(plan_queue.pop_next_if_active().is_none());
357 }
358
359 #[test]
360 fn add_plans() {
361 let observed = Rc::new(RefCell::new(Vec::new()));
362 let mut context = Context::new();
363 let mut plan_queue = PlanQueue::new();
364 plan_queue.add_plan(
365 1.0,
366 callback(1, Rc::clone(&observed)),
367 ExecutionPhase::Normal,
368 false,
369 );
370 plan_queue.add_plan(
371 3.0,
372 callback(3, Rc::clone(&observed)),
373 ExecutionPhase::Normal,
374 false,
375 );
376 plan_queue.add_plan(
377 2.0,
378 callback(2, Rc::clone(&observed)),
379 ExecutionPhase::Normal,
380 false,
381 );
382 assert_eq!(plan_queue.next_time(), Some(1.0));
383
384 let next_plan = plan_queue.pop_next_if_active().unwrap();
385 assert_eq!(next_plan.time, 1.0);
386 run_plan(next_plan, &mut context);
387
388 assert_eq!(plan_queue.next_time(), Some(2.0));
389 let next_plan = plan_queue.pop_next_if_active().unwrap();
390 assert_eq!(next_plan.time, 2.0);
391 run_plan(next_plan, &mut context);
392
393 assert_eq!(plan_queue.next_time(), Some(3.0));
394 let next_plan = plan_queue.pop_next_if_active().unwrap();
395 assert_eq!(next_plan.time, 3.0);
396 run_plan(next_plan, &mut context);
397
398 assert!(plan_queue.pop_next_if_active().is_none());
399 assert_eq!(*observed.borrow(), vec![1, 2, 3]);
400 }
401
402 #[test]
403 fn add_plans_at_same_time_with_same_phase() {
404 let observed = Rc::new(RefCell::new(Vec::new()));
405 let mut context = Context::new();
406 let mut plan_queue = PlanQueue::new();
407 plan_queue.add_plan(
408 1.0,
409 callback(1, Rc::clone(&observed)),
410 ExecutionPhase::Normal,
411 false,
412 );
413 plan_queue.add_plan(
414 1.0,
415 callback(2, Rc::clone(&observed)),
416 ExecutionPhase::Normal,
417 false,
418 );
419
420 let next_plan = plan_queue.pop_next_if_active().unwrap();
421 assert_eq!(next_plan.time, 1.0);
422 run_plan(next_plan, &mut context);
423 let next_plan = plan_queue.pop_next_if_active().unwrap();
424 assert_eq!(next_plan.time, 1.0);
425 run_plan(next_plan, &mut context);
426
427 assert!(plan_queue.pop_next_if_active().is_none());
428 assert_eq!(*observed.borrow(), vec![1, 2]);
429 }
430
431 #[test]
432 fn add_plans_at_same_time_with_different_phase() {
433 let observed = Rc::new(RefCell::new(Vec::new()));
434 let mut context = Context::new();
435 let mut plan_queue = PlanQueue::new();
436 plan_queue.add_plan(
437 1.0,
438 callback(1, Rc::clone(&observed)),
439 ExecutionPhase::Normal,
440 false,
441 );
442 plan_queue.add_plan(
443 1.0,
444 callback(2, Rc::clone(&observed)),
445 ExecutionPhase::First,
446 false,
447 );
448
449 let next_plan = plan_queue.pop_next_if_active().unwrap();
450 assert_eq!(next_plan.time, 1.0);
451 run_plan(next_plan, &mut context);
452 let next_plan = plan_queue.pop_next_if_active().unwrap();
453 assert_eq!(next_plan.time, 1.0);
454 run_plan(next_plan, &mut context);
455
456 assert!(plan_queue.pop_next_if_active().is_none());
457 assert_eq!(*observed.borrow(), vec![2, 1]);
458 }
459
460 #[test]
461 fn cancel_plan() {
462 let observed = Rc::new(RefCell::new(Vec::new()));
463 let mut context = Context::new();
464 let mut plan_queue = PlanQueue::new();
465 plan_queue.add_plan(
466 1.0,
467 callback(1, Rc::clone(&observed)),
468 ExecutionPhase::Normal,
469 false,
470 );
471 let plan_to_cancel = plan_queue.add_plan(
472 2.0,
473 callback(2, Rc::clone(&observed)),
474 ExecutionPhase::Normal,
475 false,
476 );
477 plan_queue.add_plan(
478 3.0,
479 callback(3, Rc::clone(&observed)),
480 ExecutionPhase::Normal,
481 false,
482 );
483 plan_queue.cancel_plan(&plan_to_cancel);
484
485 let next_plan = plan_queue.pop_next_if_active().unwrap();
486 assert_eq!(next_plan.time, 1.0);
487 run_plan(next_plan, &mut context);
488
489 let next_plan = plan_queue.pop_next_if_active().unwrap();
490 assert_eq!(next_plan.time, 3.0);
491 run_plan(next_plan, &mut context);
492
493 assert!(plan_queue.pop_next_if_active().is_none());
494 assert_eq!(*observed.borrow(), vec![1, 3]);
495 }
496
497 #[test]
498 fn passive_only_plans_do_not_pop_during_active_execution() {
499 let observed = Rc::new(RefCell::new(Vec::new()));
500 let mut plan_queue = PlanQueue::new();
501 plan_queue.add_plan(
502 1.0,
503 callback(1, Rc::clone(&observed)),
504 ExecutionPhase::Normal,
505 true,
506 );
507
508 assert_eq!(plan_queue.regular_plan_count, 0);
509 assert!(plan_queue.pop_next_if_active().is_none());
510 assert_eq!(plan_queue.next_time(), Some(1.0));
511 assert!(observed.borrow().is_empty());
512 }
513
514 #[test]
515 fn passive_plan_can_pop_before_later_non_passive_plan() {
516 let observed = Rc::new(RefCell::new(Vec::new()));
517 let mut context = Context::new();
518 let mut plan_queue = PlanQueue::new();
519 plan_queue.add_plan(
520 1.0,
521 callback(1, Rc::clone(&observed)),
522 ExecutionPhase::Normal,
523 true,
524 );
525 plan_queue.add_plan(
526 2.0,
527 callback(2, Rc::clone(&observed)),
528 ExecutionPhase::Normal,
529 false,
530 );
531
532 assert_eq!(plan_queue.regular_plan_count, 1);
533 let next_plan = plan_queue.pop_next_if_active().unwrap();
534 assert_eq!(next_plan.time, 1.0);
535 run_plan(next_plan, &mut context);
536 assert_eq!(plan_queue.regular_plan_count, 1);
537
538 let next_plan = plan_queue.pop_next_if_active().unwrap();
539 assert_eq!(next_plan.time, 2.0);
540 run_plan(next_plan, &mut context);
541 assert_eq!(plan_queue.regular_plan_count, 0);
542
543 assert_eq!(*observed.borrow(), vec![1, 2]);
544 }
545
546 #[test]
547 fn canceling_non_passive_plan_decrements_regular_count() {
548 let observed = Rc::new(RefCell::new(Vec::new()));
549 let mut plan_queue = PlanQueue::new();
550 let non_passive = plan_queue.add_plan(
551 1.0,
552 callback(1, Rc::clone(&observed)),
553 ExecutionPhase::Normal,
554 false,
555 );
556 let passive = plan_queue.add_plan(
557 2.0,
558 callback(2, Rc::clone(&observed)),
559 ExecutionPhase::Normal,
560 true,
561 );
562
563 assert_eq!(plan_queue.regular_plan_count, 1);
564 plan_queue.cancel_plan(&passive);
565 assert_eq!(plan_queue.regular_plan_count, 1);
566 plan_queue.cancel_plan(&non_passive);
567 assert_eq!(plan_queue.regular_plan_count, 0);
568 }
569
570 #[test]
571 fn shutdown_plans_do_not_affect_regular_count() {
572 let observed = Rc::new(RefCell::new(Vec::new()));
573 let mut context = Context::new();
574 let mut plan_queue = PlanQueue::new();
575 plan_queue.add_shutdown_plan(callback(1, Rc::clone(&observed)), ExecutionPhase::Normal);
576
577 assert_eq!(plan_queue.regular_plan_count, 0);
578 let next_plan = plan_queue.pop_next_shutdown().unwrap();
579 run_plan(next_plan, &mut context);
580 assert_eq!(plan_queue.regular_plan_count, 0);
581 assert_eq!(*observed.borrow(), vec![1]);
582 }
583
584 #[test]
585 fn next_time_ignores_canceled_root() {
586 let observed = Rc::new(RefCell::new(Vec::new()));
587 let mut plan_queue = PlanQueue::new();
588 let plan_to_cancel = plan_queue.add_plan(
589 1.0,
590 callback(1, Rc::clone(&observed)),
591 ExecutionPhase::Normal,
592 false,
593 );
594 plan_queue.add_plan(
595 2.0,
596 callback(2, Rc::clone(&observed)),
597 ExecutionPhase::Normal,
598 false,
599 );
600
601 plan_queue.cancel_plan(&plan_to_cancel);
602
603 assert_eq!(plan_queue.next_time(), Some(2.0));
604 }
605
606 #[test]
607 fn pop_next_at_leaves_future_plan_in_queue() {
608 let observed = Rc::new(RefCell::new(Vec::new()));
609 let mut context = Context::new();
610 let mut plan_queue = PlanQueue::new();
611 plan_queue.add_plan(
612 2.0,
613 callback(2, Rc::clone(&observed)),
614 ExecutionPhase::Normal,
615 false,
616 );
617
618 assert!(plan_queue.pop_next_at(1.0).is_none());
619
620 let next_plan = plan_queue.pop_next_if_active().unwrap();
621 assert_eq!(next_plan.time, 2.0);
622 run_plan(next_plan, &mut context);
623 assert_eq!(*observed.borrow(), vec![2]);
624 }
625
626 #[test]
627 fn shutdown_plans_use_phase_and_fifo_order() {
628 let observed = Rc::new(RefCell::new(Vec::new()));
629 let mut context = Context::new();
630 let mut plan_queue = PlanQueue::new();
631 plan_queue.add_shutdown_plan(callback(3, Rc::clone(&observed)), ExecutionPhase::Last);
632 plan_queue.add_shutdown_plan(callback(1, Rc::clone(&observed)), ExecutionPhase::First);
633 plan_queue.add_shutdown_plan(callback(2, Rc::clone(&observed)), ExecutionPhase::Normal);
634 plan_queue.add_shutdown_plan(callback(4, Rc::clone(&observed)), ExecutionPhase::Last);
635
636 while let Some(plan) = plan_queue.pop_next_shutdown() {
637 run_plan(plan, &mut context);
638 }
639
640 assert_eq!(*observed.borrow(), vec![1, 2, 3, 4]);
641 }
642
643 #[test]
644 fn plan_ids_are_shared_between_regular_and_shutdown_queues() {
645 let observed = Rc::new(RefCell::new(Vec::new()));
646 let mut plan_queue = PlanQueue::new();
647 let regular_id = plan_queue.add_plan(
648 1.0,
649 callback(1, Rc::clone(&observed)),
650 ExecutionPhase::Normal,
651 false,
652 );
653 let shutdown_id =
654 plan_queue.add_shutdown_plan(callback(2, Rc::clone(&observed)), ExecutionPhase::Normal);
655
656 assert_ne!(regular_id, shutdown_id);
657 assert_eq!(regular_id.0, 0);
658 assert_eq!(shutdown_id.0, 1);
659 }
660}