use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ClockTime(pub Duration);
impl ClockTime {
pub fn since_anchor(&self) -> Duration {
self.0
}
}
#[derive(Debug, Clone)]
pub struct Clock {
anchor: Instant,
offset_ns: Arc<AtomicI64>,
}
impl Clock {
pub fn new() -> Self {
Self {
anchor: Instant::now(),
offset_ns: Arc::new(AtomicI64::new(0)),
}
}
pub fn anchor(&self) -> Instant {
self.anchor
}
pub fn now(&self) -> ClockTime {
let ns = self.offset_ns.load(Ordering::Relaxed);
if ns >= 0 {
ClockTime(Duration::from_nanos(ns as u64))
} else {
ClockTime(Duration::ZERO)
}
}
pub fn now_signed_ns(&self) -> i64 {
self.offset_ns.load(Ordering::Relaxed)
}
pub fn advance(&self, delta: Duration) {
self.offset_ns
.fetch_add(delta.as_nanos() as i64, Ordering::Relaxed);
}
pub fn skew_by(&self, delta_ns: i64) {
self.offset_ns.fetch_add(delta_ns, Ordering::Relaxed);
}
pub fn reset(&self) {
self.offset_ns.store(0, Ordering::Relaxed);
}
}
impl Default for Clock {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_clock_starts_at_zero_offset() {
let c = Clock::new();
assert_eq!(c.now().since_anchor(), Duration::ZERO);
assert_eq!(c.now_signed_ns(), 0);
}
#[test]
fn advance_moves_forward() {
let c = Clock::new();
c.advance(Duration::from_millis(100));
c.advance(Duration::from_millis(50));
assert_eq!(c.now().since_anchor(), Duration::from_millis(150));
}
#[test]
fn skew_negative_clamps_now_to_zero() {
let c = Clock::new();
c.advance(Duration::from_millis(100));
c.skew_by(-(Duration::from_millis(200).as_nanos() as i64));
assert_eq!(c.now().since_anchor(), Duration::ZERO);
assert!(c.now_signed_ns() < 0);
}
#[test]
fn skew_negative_within_positive_offset_works() {
let c = Clock::new();
c.advance(Duration::from_millis(500));
c.skew_by(-(Duration::from_millis(100).as_nanos() as i64));
assert_eq!(c.now().since_anchor(), Duration::from_millis(400));
}
#[test]
fn cloned_clocks_share_state() {
let c = Clock::new();
let d = c.clone();
c.advance(Duration::from_secs(1));
assert_eq!(d.now().since_anchor(), Duration::from_secs(1));
}
#[test]
fn reset_returns_to_zero() {
let c = Clock::new();
c.advance(Duration::from_secs(10));
c.reset();
assert_eq!(c.now().since_anchor(), Duration::ZERO);
}
#[test]
fn anchor_is_preserved_across_clones() {
let c = Clock::new();
let d = c.clone();
assert_eq!(c.anchor(), d.anchor());
}
#[test]
fn deterministic_sequence_across_runs() {
let c = Clock::new();
c.advance(Duration::from_millis(100));
c.advance(Duration::from_millis(50));
c.skew_by(-(Duration::from_millis(20).as_nanos() as i64));
let observed = c.now().since_anchor();
let c2 = Clock::new();
c2.advance(Duration::from_millis(100));
c2.advance(Duration::from_millis(50));
c2.skew_by(-(Duration::from_millis(20).as_nanos() as i64));
let observed2 = c2.now().since_anchor();
assert_eq!(observed, observed2);
}
}