use core::num::{NonZeroU32, NonZeroU64};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Nanos(u64);
impl Nanos {
pub const ZERO: Self = Self(0);
#[must_use]
pub const fn from_nanos(nanos: u64) -> Self {
Self(nanos)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Timestamp(u64);
impl Timestamp {
#[must_use]
pub const fn from_nanos(nanos: u64) -> Self {
Self(nanos)
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
#[must_use]
pub const fn saturating_since(self, earlier: Self) -> Nanos {
Nanos(self.0.saturating_sub(earlier.0))
}
#[must_use]
pub const fn saturating_add(self, span: Nanos) -> Self {
Self(self.0.saturating_add(span.0))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Timestep(NonZeroU64);
impl Timestep {
pub const HZ_60: Self = Self(match NonZeroU64::new(16_666_667) {
Some(nanos) => nanos,
None => NonZeroU64::MIN,
});
#[must_use]
pub const fn from_nanos(nanos: NonZeroU64) -> Self {
Self(nanos)
}
#[must_use]
pub const fn nanos(self) -> NonZeroU64 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StepBudget(NonZeroU32);
impl StepBudget {
pub const DEFAULT: Self = Self(match NonZeroU32::new(5) {
Some(steps) => steps,
None => NonZeroU32::MIN,
});
#[must_use]
pub const fn new(steps: NonZeroU32) -> Self {
Self(steps)
}
#[must_use]
pub const fn get(self) -> NonZeroU32 {
self.0
}
}
#[cfg(test)]
mod tests {
use super::{Nanos, StepBudget, Timestamp, Timestep};
use core::num::{NonZeroU32, NonZeroU64};
#[test]
fn nanos_round_trips_and_zero_is_zero() {
assert_eq!(Nanos::ZERO.get(), 0);
assert_eq!(Nanos::from_nanos(7).get(), 7);
assert_eq!(Nanos::from_nanos(u64::MAX).get(), u64::MAX);
assert!(Nanos::ZERO < Nanos::from_nanos(1));
}
#[test]
fn a_timestamp_reports_the_span_since_an_earlier_one() {
let earlier = Timestamp::from_nanos(1_000);
let later = Timestamp::from_nanos(1_700);
assert_eq!(later.saturating_since(earlier), Nanos::from_nanos(700));
assert_eq!(earlier.get(), 1_000);
}
#[test]
fn a_backwards_clock_yields_zero_rather_than_a_wrapped_span() {
let earlier = Timestamp::from_nanos(1_000);
let later = Timestamp::from_nanos(1_700);
assert_eq!(earlier.saturating_since(later), Nanos::ZERO);
assert_eq!(later.saturating_since(later), Nanos::ZERO);
}
#[test]
fn adding_a_span_saturates_at_the_end_of_the_timeline() {
let start = Timestamp::from_nanos(5);
assert_eq!(
start.saturating_add(Nanos::from_nanos(10)),
Timestamp::from_nanos(15)
);
assert_eq!(
start.saturating_add(Nanos::from_nanos(u64::MAX)),
Timestamp::from_nanos(u64::MAX)
);
}
#[test]
fn the_sixty_hertz_timestep_is_the_rounded_nanosecond_value() {
assert_eq!(Timestep::HZ_60.nanos().get(), 16_666_667);
assert_eq!(60 * Timestep::HZ_60.nanos().get(), 1_000_000_020);
}
#[test]
fn a_timestep_round_trips_through_its_non_zero_nanoseconds() {
let step = Timestep::from_nanos(NonZeroU64::new(8_000_000).expect("non-zero"));
assert_eq!(step.nanos().get(), 8_000_000);
assert_ne!(step, Timestep::HZ_60);
}
#[test]
fn the_default_step_budget_is_five() {
assert_eq!(StepBudget::DEFAULT.get().get(), 5);
}
#[test]
fn a_step_budget_round_trips_through_its_non_zero_count() {
let budget = StepBudget::new(NonZeroU32::new(12).expect("non-zero"));
assert_eq!(budget.get().get(), 12);
assert_ne!(budget, StepBudget::DEFAULT);
}
}