use std::time::{Duration, Instant, SystemTime};
use tai_time::MonotonicClock;
use crate::time::MonotonicTime;
pub trait Clock: Send + 'static {
fn synchronize(&mut self, deadline: MonotonicTime) -> SyncStatus;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[must_use]
pub enum SyncStatus {
Synchronized,
OutOfSync(Duration),
}
#[derive(Copy, Clone, Debug, Default)]
pub struct NoClock {}
impl NoClock {
pub fn new() -> Self {
Self {}
}
}
impl Clock for NoClock {
fn synchronize(&mut self, _: MonotonicTime) -> SyncStatus {
SyncStatus::Synchronized
}
}
#[derive(Copy, Clone, Debug)]
pub struct SystemClock(MonotonicClock);
impl SystemClock {
pub fn from_instant(simulation_ref: MonotonicTime, wall_clock_ref: Instant) -> Self {
Self(MonotonicClock::init_from_instant(
simulation_ref,
wall_clock_ref,
))
}
pub fn from_system_time(simulation_ref: MonotonicTime, wall_clock_ref: SystemTime) -> Self {
Self(MonotonicClock::init_from_system_time(
simulation_ref,
wall_clock_ref,
))
}
}
impl Clock for SystemClock {
fn synchronize(&mut self, deadline: MonotonicTime) -> SyncStatus {
let now = self.0.now();
if now <= deadline {
spin_sleep::sleep(deadline.duration_since(now));
return SyncStatus::Synchronized;
}
SyncStatus::OutOfSync(now.duration_since(deadline))
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct AutoSystemClock {
inner: Option<SystemClock>,
}
impl AutoSystemClock {
pub fn new() -> Self {
Self::default()
}
}
impl Clock for AutoSystemClock {
fn synchronize(&mut self, deadline: MonotonicTime) -> SyncStatus {
match &mut self.inner {
None => {
let now = Instant::now();
self.inner = Some(SystemClock::from_instant(deadline, now));
SyncStatus::Synchronized
}
Some(clock) => clock.synchronize(deadline),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn smoke_system_clock() {
let t0 = MonotonicTime::EPOCH;
const TOLERANCE: f64 = 0.0005;
let now = Instant::now();
let mut clock = SystemClock::from_instant(t0, now);
let t1 = t0 + Duration::from_millis(200);
assert_eq!(clock.synchronize(t1), SyncStatus::Synchronized);
let elapsed = now.elapsed().as_secs_f64();
let dt = t1.duration_since(t0).as_secs_f64();
assert!(
(dt - elapsed) <= TOLERANCE,
"Expected t = {dt:.6}s +/- {TOLERANCE:.6}s, measured t = {elapsed:.6}s",
);
}
}