use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering::Relaxed;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Source {
System,
Fixed,
}
#[derive(Debug, Clone)]
pub struct Clock {
now_ms: Arc<AtomicU64>,
source: Source,
}
impl Clock {
pub fn system() -> Clock {
Clock::at(Clock::fine_now_ms(), Source::System)
}
pub fn fixed(ms: u64) -> Clock {
Clock::at(ms, Source::Fixed)
}
fn at(ms: u64, source: Source) -> Clock {
Clock {
now_ms: Arc::new(AtomicU64::new(ms)),
source,
}
}
#[inline]
pub fn now_ms(&self) -> u64 {
self.now_ms.load(Relaxed)
}
#[inline]
pub fn refresh(&self) {
if self.source == Source::System {
let ms = Clock::fine_now_ms();
if self.now_ms() != ms {
self.now_ms.store(ms, Relaxed);
}
}
}
#[inline]
pub fn set(&self, ms: u64) {
self.now_ms.store(ms, Relaxed);
}
#[inline]
pub fn advance(&self, ms: u64) {
self.set(self.now_ms().saturating_add(ms));
}
#[inline]
pub fn fine_now_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_millis() as u64)
}
}
impl Default for Clock {
fn default() -> Clock {
Clock::system()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fixed_clock_stays_where_it_is_put() {
let c = Clock::fixed(1_000);
assert_eq!(c.now_ms(), 1_000);
c.refresh();
assert_eq!(c.now_ms(), 1_000, "refresh moved a fixed clock");
c.advance(500);
assert_eq!(c.now_ms(), 1_500);
c.set(7);
assert_eq!(c.now_ms(), 7);
}
#[test]
fn a_system_clock_reads_a_plausible_time() {
let c = Clock::system();
assert!(c.now_ms() > 1_577_836_800_000, "clock read {}", c.now_ms());
}
#[test]
fn a_system_clock_does_not_move_until_it_is_refreshed() {
let c = Clock::system();
let first = c.now_ms();
let mut spin = 0u64;
for i in 0..200_000u64 {
spin = spin.wrapping_add(i);
}
assert_eq!(c.now_ms(), first, "the clock moved on its own {spin}");
c.refresh();
assert!(c.now_ms() >= first);
}
#[test]
fn a_cloned_clock_reads_what_the_original_was_moved_to() {
let one = Clock::fixed(1_000);
let two = one.clone();
one.advance(500);
assert_eq!(two.now_ms(), 1_500);
two.set(9);
assert_eq!(one.now_ms(), 9);
}
#[test]
fn advancing_past_the_end_of_time_stops_there() {
let c = Clock::fixed(u64::MAX - 1);
c.advance(10);
assert_eq!(c.now_ms(), u64::MAX);
}
}