use std::{
cmp, fmt, ops,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
time,
time::{Duration, SystemTime, UNIX_EPOCH},
};
#[allow(clippy::disallowed_methods)]
#[inline(always)]
fn platform_now_nanos() -> u64 {
SystemTime::now().duration_since(UNIX_EPOCH).expect("System time is before Unix epoch").as_nanos() as u64
}
#[derive(Clone)]
pub enum Clock {
Real,
Mock(MockClock),
}
impl Clock {
pub fn now_nanos(&self) -> u64 {
match self {
Clock::Real => platform_now_nanos(),
Clock::Mock(mock) => mock.now_nanos(),
}
}
pub fn now_micros(&self) -> u64 {
self.now_nanos() / 1_000
}
pub fn now_millis(&self) -> u64 {
self.now_nanos() / 1_000_000
}
pub fn now_secs(&self) -> u64 {
self.now_nanos() / 1_000_000_000
}
#[allow(clippy::disallowed_methods)]
pub fn instant(&self) -> Instant {
match self {
Clock::Real => Instant {
inner: InstantInner::Real(time::Instant::now()),
},
Clock::Mock(mock) => Instant {
inner: InstantInner::Mock {
captured_nanos: mock.now_nanos(),
clock: mock.clone(),
},
},
}
}
}
#[derive(Clone)]
pub struct MockClock {
inner: Arc<MockClockInner>,
}
struct MockClockInner {
time_nanos: AtomicU64,
}
impl MockClock {
pub fn new(initial_nanos: u64) -> Self {
Self {
inner: Arc::new(MockClockInner {
time_nanos: AtomicU64::new(initial_nanos),
}),
}
}
pub fn from_millis(millis: u64) -> Self {
Self::new(millis * 1_000_000)
}
pub fn now_nanos(&self) -> u64 {
self.inner.time_nanos.load(Ordering::Acquire)
}
pub fn now_micros(&self) -> u64 {
self.now_nanos() / 1_000
}
pub fn now_millis(&self) -> u64 {
self.now_nanos() / 1_000_000
}
pub fn now_secs(&self) -> u64 {
self.now_nanos() / 1_000_000_000
}
pub fn set_nanos(&self, nanos: u64) {
self.inner.time_nanos.store(nanos, Ordering::Release);
}
pub fn set_micros(&self, micros: u64) {
self.set_nanos(micros * 1_000);
}
pub fn set_millis(&self, millis: u64) {
self.set_nanos(millis * 1_000_000);
}
pub fn advance_nanos(&self, nanos: u64) {
self.set_nanos(self.now_nanos().saturating_add(nanos));
}
pub fn advance_micros(&self, micros: u64) {
self.advance_nanos(micros * 1_000);
}
pub fn advance_millis(&self, millis: u64) {
self.advance_nanos(millis * 1_000_000);
}
pub fn advance_secs(&self, secs: u64) {
self.advance_nanos(secs * 1_000_000_000);
}
pub fn advance_minutes(&self, minutes: u64) {
self.advance_secs(minutes * 60);
}
pub fn advance_hours(&self, hours: u64) {
self.advance_secs(hours * 3600);
}
pub fn advance_days(&self, days: u64) {
self.advance_secs(days * 86400);
}
}
#[derive(Clone)]
enum InstantInner {
Real(time::Instant),
Mock {
captured_nanos: u64,
clock: MockClock,
},
}
#[derive(Clone)]
pub struct Instant {
inner: InstantInner,
}
impl Instant {
#[inline]
pub fn elapsed(&self) -> Duration {
match &self.inner {
InstantInner::Real(instant) => instant.elapsed(),
InstantInner::Mock {
captured_nanos,
clock,
} => {
let now = clock.now_nanos();
let elapsed_nanos = now.saturating_sub(*captured_nanos);
Duration::from_nanos(elapsed_nanos)
}
}
}
#[inline]
pub fn duration_since(&self, earlier: &Instant) -> Duration {
match (&self.inner, &earlier.inner) {
(InstantInner::Real(this), InstantInner::Real(other)) => this.duration_since(*other),
(
InstantInner::Mock {
captured_nanos: this_nanos,
..
},
InstantInner::Mock {
captured_nanos: other_nanos,
..
},
) => {
let elapsed = this_nanos.saturating_sub(*other_nanos);
Duration::from_nanos(elapsed)
}
_ => panic!("Cannot compare instants from different clock types"),
}
}
}
impl PartialEq for Instant {
fn eq(&self, other: &Self) -> bool {
match (&self.inner, &other.inner) {
(InstantInner::Real(a), InstantInner::Real(b)) => a == b,
(
InstantInner::Mock {
captured_nanos: a,
..
},
InstantInner::Mock {
captured_nanos: b,
..
},
) => a == b,
_ => panic!("Cannot compare instants from different clock types"),
}
}
}
impl Eq for Instant {}
impl PartialOrd for Instant {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Instant {
fn cmp(&self, other: &Self) -> cmp::Ordering {
match (&self.inner, &other.inner) {
(InstantInner::Real(a), InstantInner::Real(b)) => a.cmp(b),
(
InstantInner::Mock {
captured_nanos: a,
..
},
InstantInner::Mock {
captured_nanos: b,
..
},
) => a.cmp(b),
_ => panic!("Cannot compare instants from different clock types"),
}
}
}
impl ops::Add<Duration> for Instant {
type Output = Instant;
fn add(self, duration: Duration) -> Instant {
match self.inner {
InstantInner::Real(instant) => Instant {
inner: InstantInner::Real(instant + duration),
},
InstantInner::Mock {
captured_nanos,
clock,
} => Instant {
inner: InstantInner::Mock {
captured_nanos: captured_nanos.saturating_add(duration.as_nanos() as u64),
clock,
},
},
}
}
}
impl ops::Sub for &Instant {
type Output = Duration;
fn sub(self, other: &Instant) -> Duration {
self.duration_since(other)
}
}
impl fmt::Debug for Instant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.inner {
InstantInner::Real(instant) => f.debug_tuple("Instant::Real").field(instant).finish(),
InstantInner::Mock {
captured_nanos,
..
} => f.debug_tuple("Instant::Mock").field(captured_nanos).finish(),
}
}
}