1use crate::network::NodeId;
11use std::collections::BTreeMap;
12
13pub type Micros = u64;
15
16#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
18pub enum ClockError {
19 #[error("cannot move virtual clock backwards from {now} to {target}")]
21 Backwards {
22 now: Micros,
24 target: Micros,
26 },
27}
28
29#[derive(Debug, Clone, Default, PartialEq, Eq)]
35pub struct SkewSchedule {
36 segments: Vec<(Micros, i64)>,
37}
38
39impl SkewSchedule {
40 pub fn constant(skew_micros: i64) -> Self {
42 Self {
43 segments: vec![(0, skew_micros)],
44 }
45 }
46
47 pub fn piecewise(segments: Vec<(Micros, i64)>) -> Self {
50 assert!(
51 segments.windows(2).all(|w| w[0].0 < w[1].0),
52 "skew schedule segments must have strictly increasing start times"
53 );
54 Self { segments }
55 }
56
57 pub fn skew_at(&self, at: Micros) -> i64 {
59 let mut skew = 0;
60 for &(start, value) in &self.segments {
61 if start > at {
62 break;
63 }
64 skew = value;
65 }
66 skew
67 }
68}
69
70#[derive(Debug, Default)]
72pub struct Clock {
73 now: Micros,
74 skews: BTreeMap<NodeId, SkewSchedule>,
75}
76
77impl Clock {
78 pub fn new() -> Self {
80 Self::default()
81 }
82
83 pub fn now(&self) -> Micros {
85 self.now
86 }
87
88 pub fn advance_to(&mut self, target: Micros) -> Result<(), ClockError> {
90 if target < self.now {
91 return Err(ClockError::Backwards {
92 now: self.now,
93 target,
94 });
95 }
96 self.now = target;
97 Ok(())
98 }
99
100 pub fn advance_by(&mut self, delta: Micros) -> Micros {
102 self.now += delta;
103 self.now
104 }
105
106 pub fn set_skew(&mut self, node: NodeId, schedule: SkewSchedule) {
108 self.skews.insert(node, schedule);
109 }
110
111 pub fn node_now(&self, node: NodeId) -> i64 {
113 let skew = self.skews.get(&node).map_or(0, |s| s.skew_at(self.now));
114 self.now as i64 + skew
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn clock_only_moves_forward() {
124 let mut clock = Clock::new();
125 assert_eq!(clock.now(), 0);
126 assert_eq!(clock.advance_by(100), 100);
127 clock.advance_to(250).unwrap();
128 clock.advance_to(250).unwrap();
129 assert_eq!(
130 clock.advance_to(100),
131 Err(ClockError::Backwards {
132 now: 250,
133 target: 100
134 })
135 );
136 }
137
138 #[test]
139 fn piecewise_skew_switches_at_segment_starts() {
140 let schedule = SkewSchedule::piecewise(vec![(100, -50), (500, 200)]);
141 assert_eq!(schedule.skew_at(0), 0);
142 assert_eq!(schedule.skew_at(99), 0);
143 assert_eq!(schedule.skew_at(100), -50);
144 assert_eq!(schedule.skew_at(499), -50);
145 assert_eq!(schedule.skew_at(500), 200);
146 }
147
148 #[test]
149 #[should_panic(expected = "strictly increasing")]
150 fn piecewise_rejects_unsorted_segments() {
151 let _ = SkewSchedule::piecewise(vec![(100, 1), (50, 2)]);
152 }
153
154 #[test]
155 fn node_now_applies_constant_skew() {
156 let node = NodeId(7);
157 let mut clock = Clock::new();
158 clock.set_skew(node, SkewSchedule::constant(250));
159 assert_eq!(clock.node_now(node), 250);
160 clock.advance_by(100);
161 assert_eq!(clock.node_now(node), 350);
162 assert_eq!(clock.node_now(NodeId(8)), 100);
163 }
164}