use rand::Rng;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Timestamp<const RATE: u32>(u32);
impl<const RATE: u32> Timestamp<RATE> {
pub fn random() -> Self {
Self::from_ticks(rand::rng().random::<u32>())
}
pub const fn from_ticks(ticks: u32) -> Self {
Self(ticks)
}
pub const fn as_ticks(&self) -> u32 {
self.0
}
const fn is_before(&self, other: Self) -> bool {
(self.0.wrapping_sub(other.0) as i32) < 0
}
const fn wrapping_add(self, ticks: u32) -> Self {
Self(self.0.wrapping_add(ticks))
}
}
#[derive(Debug)]
pub struct Clock<const RATE: u32> {
epoch: Instant,
base: Timestamp<RATE>,
prev: Timestamp<RATE>,
}
impl<const RATE: u32> Clock<RATE> {
pub fn new(base: Timestamp<RATE>) -> Self {
Self::with_epoch(Instant::now(), base)
}
pub fn with_epoch(epoch: Instant, base: Timestamp<RATE>) -> Self {
Self { epoch, base, prev: base }
}
pub fn now(&mut self) -> Timestamp<RATE> {
self.at(Instant::now())
}
pub fn at(&mut self, instant: Instant) -> Timestamp<RATE> {
let elapsed = instant.duration_since(self.epoch);
let ticks = Self::duration_to_ticks(elapsed);
let mut ts = self.base.wrapping_add(ticks);
if ts.is_before(self.prev) {
ts = self.prev;
}
self.prev = ts;
ts
}
const fn duration_to_ticks(duration: Duration) -> u32 {
let nanos = duration.as_nanos();
let ticks = (nanos * RATE as u128 + 500_000_000) / 1_000_000_000;
ticks as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
type DefaultClock = Clock<90_000>;
#[test]
fn test_is_base_at_epoch() {
let epoch = Instant::now();
let base = Timestamp::from_ticks(1234);
let mut clock = DefaultClock::with_epoch(epoch, base);
assert_eq!(clock.at(epoch).as_ticks(), base.as_ticks());
assert_eq!(clock.prev.as_ticks(), base.as_ticks());
}
#[test]
fn test_monotonic() {
let epoch = Instant::now();
let base = Timestamp::from_ticks(0);
let mut clock = DefaultClock::with_epoch(epoch, base);
let t1 = epoch + Duration::from_millis(100);
let t0 = epoch + Duration::from_millis(50);
assert_eq!(clock.at(t1).as_ticks(), clock.at(t0).as_ticks(), "Clock went backwards");
}
}
#[cfg(test)]
impl<const RATE: u32> fake::Dummy<fake::Faker> for Timestamp<RATE> {
fn dummy_with_rng<R: rand::Rng + ?Sized>(_: &fake::Faker, rng: &mut R) -> Self {
Self(rng.random())
}
}