use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
fn micros_of(duration: Duration) -> u64 {
u64::try_from(duration.as_micros()).unwrap_or(u64::MAX)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SimInstant {
micros: u64,
}
impl SimInstant {
pub const EPOCH: Self = Self { micros: 0 };
pub fn as_micros(self) -> u64 {
self.micros
}
pub fn duration_since(self, earlier: Self) -> Duration {
Duration::from_micros(self.micros.saturating_sub(earlier.micros))
}
}
impl std::ops::Add<Duration> for SimInstant {
type Output = SimInstant;
fn add(self, rhs: Duration) -> SimInstant {
SimInstant {
micros: self.micros.saturating_add(micros_of(rhs)),
}
}
}
impl std::ops::Sub for SimInstant {
type Output = Duration;
fn sub(self, rhs: SimInstant) -> Duration {
self.duration_since(rhs)
}
}
#[derive(Debug)]
pub struct SimClock {
micros: AtomicU64,
}
impl SimClock {
pub fn new() -> Self {
Self::with_start(0)
}
pub fn with_start(micros: u64) -> Self {
Self {
micros: AtomicU64::new(micros),
}
}
pub fn now(&self) -> SimInstant {
SimInstant {
micros: self.micros.load(Ordering::Relaxed),
}
}
pub fn advance(&self, duration: Duration) -> SimInstant {
let micros = micros_of(duration);
let prev = self.micros.fetch_add(micros, Ordering::Relaxed);
SimInstant {
micros: prev.saturating_add(micros),
}
}
pub fn elapsed(&self) -> Duration {
Duration::from_micros(self.micros.load(Ordering::Relaxed))
}
}
impl Default for SimClock {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clock_moves_only_when_advanced() {
let clock = SimClock::new();
assert_eq!(clock.now(), SimInstant::EPOCH);
assert_eq!(
clock.now(),
SimInstant::EPOCH,
"reading must not advance time"
);
let t = clock.advance(Duration::from_millis(5));
assert_eq!(t.as_micros(), 5_000);
assert_eq!(clock.now().as_micros(), 5_000);
}
#[test]
fn instant_arithmetic_saturates() {
let t = SimInstant { micros: u64::MAX };
let later = t + Duration::from_secs(1_000);
assert_eq!(later.as_micros(), u64::MAX);
assert_eq!(
later.duration_since(SimInstant::EPOCH),
Duration::from_micros(u64::MAX)
);
}
#[test]
fn duration_since_and_sub() {
let a = SimInstant { micros: 1_000 };
let b = SimInstant { micros: 1_500 };
assert_eq!(b - a, Duration::from_micros(500));
assert_eq!(a.duration_since(b), Duration::ZERO, "saturating");
}
}