use std::{
fmt,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
time::Duration,
};
use js_sys::Date;
use web_sys::window;
#[inline(always)]
fn platform_now_nanos() -> u128 {
let millis = Date::now();
(millis * 1_000_000.0) as u128
}
fn performance_now_ms() -> f64 {
window().and_then(|w| w.performance()).map(|p| p.now()).unwrap_or_else(|| Date::now())
}
#[derive(Clone)]
pub enum Clock {
Real,
Mock(MockClock),
}
impl Clock {
pub fn now_nanos(&self) -> u128 {
match self {
Clock::Real => platform_now_nanos(),
Clock::Mock(mock) => mock.now_nanos(),
}
}
pub fn now_micros(&self) -> u64 {
(self.now_nanos() / 1_000) as u64
}
pub fn now_millis(&self) -> u64 {
(self.now_nanos() / 1_000_000) as u64
}
pub fn now_secs(&self) -> u64 {
(self.now_nanos() / 1_000_000_000) as u64
}
pub fn instant(&self) -> Instant {
match self {
Clock::Real => Instant {
inner: InstantInner::Real {
timestamp_ms: performance_now_ms(),
},
},
Clock::Mock(mock) => Instant {
inner: InstantInner::Mock {
captured_nanos: mock.now_nanos(),
clock: mock.clone(),
},
},
}
}
}
impl Default for Clock {
fn default() -> Self {
Clock::Real
}
}
#[derive(Clone)]
pub struct MockClock {
inner: Arc<MockClockInner>,
}
struct MockClockInner {
time_high: AtomicU64,
time_low: AtomicU64,
}
impl MockClock {
pub fn new(initial_nanos: u128) -> Self {
Self {
inner: Arc::new(MockClockInner {
time_high: AtomicU64::new((initial_nanos >> 64) as u64),
time_low: AtomicU64::new(initial_nanos as u64),
}),
}
}
pub fn from_millis(millis: u64) -> Self {
Self::new(millis as u128 * 1_000_000)
}
pub fn now_nanos(&self) -> u128 {
let high = self.inner.time_high.load(Ordering::Acquire) as u128;
let low = self.inner.time_low.load(Ordering::Acquire) as u128;
(high << 64) | low
}
pub fn now_micros(&self) -> u64 {
(self.now_nanos() / 1_000) as u64
}
pub fn now_millis(&self) -> u64 {
(self.now_nanos() / 1_000_000) as u64
}
pub fn now_secs(&self) -> u64 {
(self.now_nanos() / 1_000_000_000) as u64
}
pub fn set_nanos(&self, nanos: u128) {
self.inner.time_high.store((nanos >> 64) as u64, Ordering::Release);
self.inner.time_low.store(nanos as u64, Ordering::Release);
}
pub fn set_micros(&self, micros: u64) {
self.set_nanos(micros as u128 * 1_000);
}
pub fn set_millis(&self, millis: u64) {
self.set_nanos(millis as u128 * 1_000_000);
}
pub fn advance_nanos(&self, nanos: u128) {
self.set_nanos(self.now_nanos() + nanos);
}
pub fn advance_micros(&self, micros: u64) {
self.advance_nanos(micros as u128 * 1_000);
}
pub fn advance_millis(&self, millis: u64) {
self.advance_nanos(millis as u128 * 1_000_000);
}
}
#[derive(Clone)]
enum InstantInner {
Real {
timestamp_ms: f64,
},
Mock {
captured_nanos: u128,
clock: MockClock,
},
}
#[derive(Clone)]
pub struct Instant {
inner: InstantInner,
}
impl Instant {
#[inline]
pub fn elapsed(&self) -> Duration {
match &self.inner {
InstantInner::Real {
timestamp_ms,
} => {
let now = performance_now_ms();
let elapsed_ms = (now - timestamp_ms).max(0.0);
let nanos = (elapsed_ms * 1_000_000.0) as u64;
Duration::from_nanos(nanos)
}
InstantInner::Mock {
captured_nanos,
clock,
} => {
let now = clock.now_nanos();
let elapsed_nanos = now.saturating_sub(*captured_nanos);
Duration::from_nanos(elapsed_nanos as u64)
}
}
}
#[inline]
pub fn duration_since(&self, earlier: Instant) -> Duration {
match (&self.inner, &earlier.inner) {
(
InstantInner::Real {
timestamp_ms: this,
},
InstantInner::Real {
timestamp_ms: other,
},
) => {
let elapsed_ms = (this - other).max(0.0);
let nanos = (elapsed_ms * 1_000_000.0) as u64;
Duration::from_nanos(nanos)
}
(
InstantInner::Mock {
captured_nanos: this_nanos,
..
},
InstantInner::Mock {
captured_nanos: other_nanos,
..
},
) => {
let elapsed = this_nanos.saturating_sub(*other_nanos);
Duration::from_nanos(elapsed as u64)
}
_ => panic!("Cannot compare instants from different clock types"),
}
}
}
impl fmt::Debug for Instant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.inner {
InstantInner::Real {
timestamp_ms,
} => f.debug_tuple("Instant::Real").field(timestamp_ms).finish(),
InstantInner::Mock {
captured_nanos,
..
} => f.debug_tuple("Instant::Mock").field(captured_nanos).finish(),
}
}
}