rusty_time_core/
vclock.rs1#[derive(Clone, Copy, Debug)]
9pub struct VirtualClock {
10 offset: f64,
12 skew_ppm: f64,
14 last_update_mono: Option<f64>,
16 err_at_update: f64,
18 err_growth_ppm: f64,
20 high_water: f64,
22}
23
24impl VirtualClock {
25 pub fn new() -> Self {
26 VirtualClock {
27 offset: 0.0,
28 skew_ppm: 0.0,
29 last_update_mono: None,
30 err_at_update: f64::INFINITY,
31 err_growth_ppm: 15.0, high_water: f64::NEG_INFINITY,
33 }
34 }
35
36 pub fn update(&mut self, mono: f64, offset: f64, skew_ppm: Option<f64>, error_bound: f64) {
43 self.offset = offset;
44 if let Some(s) = skew_ppm {
45 self.skew_ppm = s;
46 }
47 self.last_update_mono = Some(mono);
48 self.err_at_update = error_bound.max(0.0);
49 }
50
51 pub fn now(&mut self, mono: f64, raw_wall: f64) -> f64 {
54 let corrected = match self.last_update_mono {
55 Some(t0) => {
56 let dt = (mono - t0).max(0.0);
57 raw_wall + self.offset - self.skew_ppm * 1e-6 * dt
59 }
60 None => raw_wall,
61 };
62 if corrected < self.high_water {
64 self.high_water
65 } else {
66 self.high_water = corrected;
67 corrected
68 }
69 }
70
71 pub fn confidence(&self, mono: f64) -> f64 {
75 match self.last_update_mono {
76 Some(t0) => {
77 let dt = (mono - t0).max(0.0);
78 self.err_at_update + self.err_growth_ppm * 1e-6 * dt
79 }
80 None => f64::INFINITY,
81 }
82 }
83
84 pub fn is_synchronized(&self) -> bool {
85 self.last_update_mono.is_some()
86 }
87}
88
89impl Default for VirtualClock {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn unsynchronized_reports_infinite_error() {
101 let vc = VirtualClock::new();
102 assert!(vc.confidence(100.0).is_infinite());
103 }
104
105 #[test]
106 fn correction_and_skew_are_applied() {
107 let mut vc = VirtualClock::new();
108 vc.update(1000.0, 0.002, Some(10.0), 1e-4);
110 let t = vc.now(1000.0, 5000.0);
111 assert!((t - 5000.002).abs() < 1e-9);
112 let t = vc.now(1100.0, 5100.0);
114 assert!((t - 5100.001).abs() < 1e-9, "{t}");
115 }
116
117 #[test]
118 fn output_never_steps_backwards() {
119 let mut vc = VirtualClock::new();
120 vc.update(0.0, 0.0, None, 1e-4);
121 let t1 = vc.now(10.0, 100.0);
122 vc.update(10.0, -0.050, None, 1e-4);
125 let t2 = vc.now(10.001, 100.001);
126 assert!(t2 >= t1, "stepped backwards: {t1} -> {t2}");
127 }
128
129 #[test]
130 fn error_bound_grows_over_time() {
131 let mut vc = VirtualClock::new();
132 vc.update(0.0, 0.0, None, 1e-4);
133 let e0 = vc.confidence(0.0);
134 let e1 = vc.confidence(3600.0);
135 assert!(e1 > e0);
136 assert!((e1 - (1e-4 + 0.054)).abs() < 1e-3, "{e1}");
138 }
139}