1use std::{
7 sync::atomic::{AtomicU64, Ordering},
8 time::Instant,
9};
10
11#[repr(C)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
17pub struct SyncedTimestamp(pub u64);
18
19impl SyncedTimestamp {
20 pub const fn from_micros(micros: u64) -> Self {
22 Self(micros)
23 }
24
25 pub const fn as_micros(&self) -> u64 {
27 self.0
28 }
29
30 pub fn to_sample_offset(
41 &self,
42 buffer_start_time: SyncedTimestamp,
43 sample_rate: f64,
44 buffer_size: usize,
45 ) -> Option<u32> {
46 if self.0 < buffer_start_time.0 {
47 return Some(0);
48 }
49
50 let delta_micros = self.0 - buffer_start_time.0;
51 let delta_seconds = delta_micros as f64 / 1_000_000.0;
52 let sample_offset = (delta_seconds * sample_rate) as usize;
53
54 if sample_offset < buffer_size {
55 Some(sample_offset as u32)
56 } else {
57 None
58 }
59 }
60
61 pub fn delta(&self, other: SyncedTimestamp) -> i64 {
63 self.0 as i64 - other.0 as i64
64 }
65}
66
67pub struct ClockSync {
72 start_instant: Instant,
73 current_time: AtomicU64,
74}
75
76impl ClockSync {
77 pub fn new() -> Self {
79 Self {
80 start_instant: Instant::now(),
81 current_time: AtomicU64::new(0),
82 }
83 }
84
85 #[inline]
96 pub fn now(&self) -> SyncedTimestamp {
97 let elapsed = self.start_instant.elapsed();
98 SyncedTimestamp(elapsed.as_micros() as u64)
99 }
100
101 pub fn tick(&self) {
113 let now = self.now();
114 self.current_time.store(now.0, Ordering::Relaxed);
115 }
116
117 #[inline]
122 pub fn cached_now(&self) -> SyncedTimestamp {
123 SyncedTimestamp(self.current_time.load(Ordering::Relaxed))
124 }
125
126 pub fn elapsed_micros(&self) -> u64 {
128 self.start_instant.elapsed().as_micros() as u64
129 }
130
131 pub fn calculate_offset(
147 client_send_time: u64,
148 server_receive_time: u64,
149 server_send_time: u64,
150 client_receive_time: u64,
151 ) -> i64 {
152 let t1 = client_send_time as i64;
153 let t2 = server_receive_time as i64;
154 let t3 = server_send_time as i64;
155 let t4 = client_receive_time as i64;
156
157 ((t2 - t1) + (t3 - t4)) / 2
158 }
159}
160
161impl Default for ClockSync {
162 fn default() -> Self {
163 Self::new()
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use std::{thread, time::Duration};
170
171 use super::*;
172
173 #[test]
174 fn test_synced_timestamp_default() {
175 let ts = SyncedTimestamp::default();
176 assert_eq!(ts.as_micros(), 0);
177 }
178
179 #[test]
180 fn test_synced_timestamp_from_micros() {
181 let ts = SyncedTimestamp::from_micros(1_000_000);
182 assert_eq!(ts.as_micros(), 1_000_000);
183 }
184
185 #[test]
186 fn test_synced_timestamp_ordering() {
187 let ts1 = SyncedTimestamp::from_micros(100);
188 let ts2 = SyncedTimestamp::from_micros(200);
189 assert!(ts1 < ts2);
190 }
191
192 #[test]
193 fn test_synced_timestamp_delta() {
194 let ts1 = SyncedTimestamp::from_micros(1000);
195 let ts2 = SyncedTimestamp::from_micros(500);
196 assert_eq!(ts1.delta(ts2), 500);
197 assert_eq!(ts2.delta(ts1), -500);
198 }
199
200 #[test]
201 fn test_to_sample_offset_in_buffer() {
202 let buffer_start = SyncedTimestamp::from_micros(0);
203 let event_time = SyncedTimestamp::from_micros(500);
204
205 let offset = event_time.to_sample_offset(buffer_start, 44100.0, 512).unwrap();
206
207 let expected = (0.0005 * 44100.0) as u32;
208 assert_eq!(offset, expected);
209 }
210
211 #[test]
212 fn test_to_sample_offset_past_event() {
213 let buffer_start = SyncedTimestamp::from_micros(1000);
214 let event_time = SyncedTimestamp::from_micros(500);
215
216 let offset = event_time.to_sample_offset(buffer_start, 44100.0, 512).unwrap();
217 assert_eq!(offset, 0);
218 }
219
220 #[test]
221 fn test_to_sample_offset_future_buffer() {
222 let buffer_start = SyncedTimestamp::from_micros(0);
223 let event_time = SyncedTimestamp::from_micros(1_000_000);
224
225 let offset = event_time.to_sample_offset(buffer_start, 44100.0, 512);
226 assert!(offset.is_none());
227 }
228
229 #[test]
230 fn test_clock_sync_now_increases() {
231 let clock = ClockSync::new();
232 let t1 = clock.now();
233 thread::sleep(Duration::from_millis(10));
234 let t2 = clock.now();
235 assert!(t2 > t1);
236 }
237
238 #[test]
239 fn test_clock_sync_tick_updates_cached() {
240 let clock = ClockSync::new();
241 clock.tick();
242 let cached1 = clock.cached_now();
243 thread::sleep(Duration::from_millis(10));
244 clock.tick();
245 let cached2 = clock.cached_now();
246 assert!(cached2 > cached1);
247 }
248
249 #[test]
250 fn test_calculate_offset_symmetric() {
251 let offset = ClockSync::calculate_offset(100, 200, 200, 300);
252 assert_eq!(offset, 0);
253 }
254
255 #[test]
256 fn test_calculate_offset_client_ahead() {
257 let offset = ClockSync::calculate_offset(100, 50, 50, 100);
258 assert!(offset < 0);
259 }
260
261 #[test]
262 fn test_calculate_offset_client_behind() {
263 let offset = ClockSync::calculate_offset(100, 250, 250, 300);
264 assert!(offset > 0);
265 }
266}