1use std::collections::VecDeque;
3#[derive(Clone, Debug)]
4pub struct History<T> {
5 samples: VecDeque<(u64, T)>,
6 capacity: usize,
7}
8impl<T> History<T> {
9 pub fn new(capacity: usize) -> Self {
10 assert!((1..=4096).contains(&capacity));
11 Self {
12 samples: VecDeque::with_capacity(capacity),
13 capacity,
14 }
15 }
16 pub fn push(&mut self, tick: u64, state: T) -> bool {
17 if self.samples.back().is_some_and(|(last, _)| tick <= *last) {
18 return false;
19 }
20 if self.samples.len() == self.capacity {
21 self.samples.pop_front();
22 }
23 self.samples.push_back((tick, state));
24 true
25 }
26 pub fn bracket(&self, tick: f64) -> Option<(&T, &T, f32)> {
28 if !tick.is_finite()
29 || tick < self.samples.front()?.0 as f64
30 || tick > self.samples.back()?.0 as f64
31 {
32 return None;
33 }
34 for i in 0..self.samples.len() {
35 let (a, left) = &self.samples[i];
36 if tick == *a as f64 {
37 return Some((left, left, 0.0));
38 }
39 if let Some((b, right)) = self.samples.get(i + 1)
40 && tick < *b as f64
41 {
42 return Some((left, right, ((tick - *a as f64) / (*b - *a) as f64) as f32));
43 }
44 }
45 None
46 }
47 pub fn len(&self) -> usize {
48 self.samples.len()
49 }
50 pub fn is_empty(&self) -> bool {
51 self.samples.is_empty()
52 }
53}
54#[derive(Default, Clone, Copy, Debug)]
55pub struct ClockSync {
56 pub offset_seconds: f64,
57 pub rtt_seconds: f64,
58 samples: u32,
59}
60impl ClockSync {
61 pub fn observe(
63 &mut self,
64 local_send: f64,
65 server_receive: f64,
66 server_send: f64,
67 local_receive: f64,
68 ) -> bool {
69 if ![local_send, server_receive, server_send, local_receive]
70 .iter()
71 .all(|v| v.is_finite())
72 || local_receive < local_send
73 || server_send < server_receive
74 {
75 return false;
76 }
77 let rtt = (local_receive - local_send) - (server_send - server_receive);
78 if !(0.0..=5.0).contains(&rtt) {
79 return false;
80 }
81 let offset = ((server_receive - local_send) + (server_send - local_receive)) * 0.5;
82 let weight = if self.samples == 0 { 1.0 } else { 0.125 };
83 self.offset_seconds += (offset - self.offset_seconds) * weight;
84 self.rtt_seconds += (rtt - self.rtt_seconds) * weight;
85 self.samples = self.samples.saturating_add(1);
86 true
87 }
88 pub fn server_time(&self, local: f64) -> f64 {
89 local + self.offset_seconds
90 }
91}
92#[cfg(test)]
93mod tests {
94 use super::*;
95 #[test]
96 fn old_or_future_shots_are_rejected_and_history_interpolates() {
97 let mut h = History::new(3);
98 for i in 0..5 {
99 assert!(h.push(i, i as f32));
100 }
101 assert_eq!(h.len(), 3);
102 assert!(h.bracket(1.0).is_none());
103 assert!(h.bracket(5.0).is_none());
104 assert!(!h.push(3, 0.0));
105 assert_eq!(h.bracket(2.5), Some((&2.0, &3.0, 0.5)));
106 assert_eq!(h.bracket(4.0), Some((&4.0, &4.0, 0.0)));
107 }
108 #[test]
109 fn clock_offset_accounts_for_server_processing() {
110 let mut c = ClockSync::default();
111 assert!(c.observe(1.0, 11.1, 11.2, 1.3));
112 assert!((c.offset_seconds - 10.0).abs() < 1e-9);
113 assert!((c.rtt_seconds - 0.2).abs() < 1e-9);
114 assert!(!c.observe(f64::NAN, 0., 0., 0.));
115 }
116}