use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::sync::OnceLock;
use std::time::Duration;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Mono(u64);
impl Mono {
pub fn as_nanos(self) -> u64 {
self.0
}
pub fn duration_since(self, earlier: Mono) -> Duration {
Duration::from_nanos(self.0.saturating_sub(earlier.0))
}
pub fn elapsed(self) -> Duration {
mono_now().duration_since(self)
}
}
pub struct VirtualClock {
now_ns: AtomicU64,
epoch_base: hifitime::Epoch,
}
impl VirtualClock {
pub fn new(epoch_base: hifitime::Epoch) -> Arc<Self> {
Arc::new(Self {
now_ns: AtomicU64::new(0),
epoch_base,
})
}
pub fn advance(&self, d: Duration) {
self.now_ns
.fetch_add(d.as_nanos() as u64, Ordering::SeqCst);
}
pub fn now_ns(&self) -> u64 {
self.now_ns.load(Ordering::SeqCst)
}
pub fn reset(&self) {
self.now_ns.store(0, Ordering::SeqCst);
}
}
enum Source {
Real {
origin: std::time::Instant,
},
Virtual(Arc<VirtualClock>),
}
static SOURCE: OnceLock<Source> = OnceLock::new();
fn source() -> &'static Source {
SOURCE.get_or_init(|| Source::Real {
origin: std::time::Instant::now(),
})
}
pub fn install_virtual(clock: Arc<VirtualClock>) -> Result<(), ()> {
SOURCE.set(Source::Virtual(clock)).map_err(|_| ())
}
pub fn mono_now() -> Mono {
match source() {
Source::Real { origin } => Mono(origin.elapsed().as_nanos() as u64),
Source::Virtual(vc) => Mono(vc.now_ns()),
}
}
pub fn epoch_now() -> hifitime::Epoch {
match source() {
Source::Real { .. } => {
hifitime::Epoch::now().expect("system wall clock unreadable")
}
Source::Virtual(vc) => {
vc.epoch_base
+ hifitime::Duration::from_total_nanoseconds(vc.now_ns() as i128)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mono_is_monotonic() {
let a = mono_now();
let b = mono_now();
assert!(b >= a);
assert_eq!(b.duration_since(a), b.duration_since(a));
}
#[test]
fn duration_since_saturates() {
let a = Mono(100);
let b = Mono(50);
assert_eq!(b.duration_since(a), Duration::ZERO);
assert_eq!(a.duration_since(b), Duration::from_nanos(50));
}
#[test]
fn virtual_clock_advances() {
let vc = VirtualClock::new(hifitime::Epoch::from_tai_seconds(0.0));
assert_eq!(vc.now_ns(), 0);
vc.advance(Duration::from_millis(5));
assert_eq!(vc.now_ns(), 5_000_000);
}
}