use std::sync::atomic::{AtomicI64, Ordering};
#[derive(Debug, Default)]
pub struct SkewClock {
offset_ms: AtomicI64,
}
impl SkewClock {
pub const fn new() -> Self {
Self { offset_ms: AtomicI64::new(0) }
}
pub fn observe(&self, server_ms: i64, local_ms: i64) {
self.offset_ms.store(server_ms - local_ms, Ordering::Relaxed);
}
pub fn corrected(&self, local_ms: i64) -> i64 {
local_ms.saturating_add(self.offset_ms.load(Ordering::Relaxed))
}
pub fn offset_ms(&self) -> i64 {
self.offset_ms.load(Ordering::Relaxed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identity_until_observed() {
let c = SkewClock::new();
assert_eq!(c.corrected(1_000), 1_000);
assert_eq!(c.offset_ms(), 0);
}
#[test]
fn corrects_local_into_server_domain() {
let c = SkewClock::new();
c.observe(6_000, 1_000);
assert_eq!(c.offset_ms(), 5_000);
assert_eq!(c.corrected(2_000), 7_000);
}
#[test]
fn offline_events_keep_relative_order() {
let ahead = SkewClock::new();
ahead.observe(0, 10_000); let behind = SkewClock::new();
behind.observe(0, 0); let behind_ts = behind.corrected(100);
let ahead_ts = ahead.corrected(10_200);
assert!(behind_ts < ahead_ts, "earlier real event must sort earlier");
}
}