use std::sync::OnceLock;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use crate::{Load, Off};
#[derive(Debug)]
pub struct StepClock {
epoch: OnceLock<Instant>,
last: AtomicU64,
now: AtomicU64,
}
impl StepClock {
pub const MAX_STEP: Duration = Duration::from_millis(250);
#[must_use]
pub const fn new() -> Self {
Self {
epoch: OnceLock::new(),
last: AtomicU64::new(0),
now: AtomicU64::new(0),
}
}
pub fn step(&self, at: Instant) {
let epoch = *self.epoch.get_or_init(|| at);
let at = u64::try_from(at.saturating_duration_since(epoch).as_nanos())
.unwrap_or(u64::MAX)
.saturating_add(1);
let last = self.last.fetch_max(at, Ordering::Relaxed);
if last != 0 && at > last {
let max = u64::try_from(Self::MAX_STEP.as_nanos()).unwrap_or(u64::MAX);
self.now.fetch_add((at - last).min(max), Ordering::Relaxed);
}
}
#[must_use]
pub fn now(&self) -> Duration {
Duration::from_nanos(self.now.load(Ordering::Relaxed))
}
}
impl Default for StepClock {
fn default() -> Self {
Self::new()
}
}
static CLOCK: crate::fresh::Fresh<StepClock> = crate::fresh::Fresh::new();
impl Load<'_> {
pub fn step_clock(&mut self) -> Result<&'static StepClock, Off> {
if let Some(clock) = CLOCK.get() {
return Ok(clock);
}
self.on::<crate::events::Ticked>(|_| {
if let Some(clock) = CLOCK.get() {
clock.step(Instant::now());
}
})?;
Ok(CLOCK.get_or_init(StepClock::new))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_first_step_starts_the_clock_and_later_ones_count() {
let clock = StepClock::new();
let t0 = Instant::now();
clock.step(t0);
assert_eq!(clock.now(), Duration::ZERO);
clock.step(t0 + Duration::from_millis(16));
assert_eq!(clock.now(), Duration::from_millis(16));
clock.step(t0 + Duration::from_secs(60));
assert_eq!(clock.now(), Duration::from_millis(16) + StepClock::MAX_STEP);
}
#[test]
fn a_late_step_out_of_order_counts_no_time_twice() {
let clock = StepClock::new();
let t0 = Instant::now();
for ms in [0, 100, 50, 116] {
clock.step(t0 + Duration::from_millis(ms));
}
assert_eq!(clock.now(), Duration::from_millis(116));
}
#[test]
fn a_step_out_of_order_counts_nothing() {
let clock = StepClock::new();
let t0 = Instant::now();
clock.step(t0 + Duration::from_millis(100));
clock.step(t0);
assert_eq!(clock.now(), Duration::ZERO);
}
}