vihaco_stdlib/clock/
queue.rs1use std::cmp::Ordering;
5use std::collections::BinaryHeap;
6
7use super::{ClockFault, GlobalDuration, GlobalTick};
8
9pub struct GlobalClock<E> {
11 now: GlobalTick,
12 seq: u64,
13 pending: BinaryHeap<Scheduled<E>>,
14}
15
16struct Scheduled<E> {
17 tick: GlobalTick,
18 seq: u64,
19 event: E,
20}
21
22impl<E> Ord for Scheduled<E> {
24 fn cmp(&self, other: &Self) -> Ordering {
25 other
26 .tick
27 .cmp(&self.tick)
28 .then_with(|| other.seq.cmp(&self.seq))
29 }
30}
31
32impl<E> PartialOrd for Scheduled<E> {
33 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
34 Some(self.cmp(other))
35 }
36}
37
38impl<E> PartialEq for Scheduled<E> {
39 fn eq(&self, other: &Self) -> bool {
40 (self.tick, self.seq) == (other.tick, other.seq)
41 }
42}
43
44impl<E> Eq for Scheduled<E> {}
45
46impl<E> GlobalClock<E> {
47 pub fn new() -> Self {
48 Self {
49 now: GlobalTick::ZERO,
50 seq: 0,
51 pending: BinaryHeap::new(),
52 }
53 }
54
55 pub fn schedule_at(&mut self, tick: GlobalTick, event: E) -> Result<(), ClockFault> {
57 if tick < self.now {
58 return Err(ClockFault::SchedulingInPast);
59 }
60
61 let seq = self
62 .seq
63 .checked_add(1)
64 .ok_or(ClockFault::SequenceOverflow)?;
65 self.seq = seq;
66 self.pending.push(Scheduled { tick, seq, event });
67 Ok(())
68 }
69
70 pub fn schedule_after(&mut self, after: GlobalDuration, event: E) -> Result<(), ClockFault> {
72 self.schedule_at(self.now.checked_add(after)?, event)
73 }
74
75 pub fn pop_earliest(&mut self) -> Option<(GlobalTick, E)> {
77 let Scheduled { tick, event, .. } = self.pending.pop()?;
78 self.now = tick;
79 Some((tick, event))
80 }
81
82 pub fn now(&self) -> GlobalTick {
83 self.now
84 }
85
86 pub fn is_empty(&self) -> bool {
87 self.pending.is_empty()
88 }
89}
90
91impl<E> Default for GlobalClock<E> {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn events_are_ordered_by_tick_then_insertion_order() {
103 let mut clock = GlobalClock::new();
104 clock.schedule_at(GlobalTick(10), 10).unwrap();
105 clock.schedule_at(GlobalTick(2), 2).unwrap();
106 clock.schedule_at(GlobalTick(2), 3).unwrap();
107 clock.schedule_at(GlobalTick(1), 1).unwrap();
108
109 assert_eq!(clock.pop_earliest(), Some((GlobalTick(1), 1)));
110 assert_eq!(clock.pop_earliest(), Some((GlobalTick(2), 2)));
111 assert_eq!(clock.pop_earliest(), Some((GlobalTick(2), 3)));
112 assert_eq!(clock.pop_earliest(), Some((GlobalTick(10), 10)));
113 assert_eq!(clock.pop_earliest(), None);
114 assert_eq!(clock.now(), GlobalTick(10));
115 }
116
117 #[test]
118 fn relative_scheduling_uses_current_time() {
119 let mut clock = GlobalClock::new();
120 clock.schedule_at(GlobalTick(4), "first").unwrap();
121 assert_eq!(clock.pop_earliest(), Some((GlobalTick(4), "first")));
122
123 clock.schedule_after(GlobalDuration(3), "second").unwrap();
124 assert_eq!(clock.pop_earliest(), Some((GlobalTick(7), "second")));
125 }
126
127 #[test]
128 fn rejects_scheduling_in_the_past() {
129 let mut clock = GlobalClock::new();
130 clock.schedule_at(GlobalTick(4), ()).unwrap();
131 clock.pop_earliest();
132
133 assert_eq!(
134 clock.schedule_at(GlobalTick(3), ()),
135 Err(ClockFault::SchedulingInPast)
136 );
137 }
138}