use core::fmt;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Timestamp(u64);
impl Timestamp {
pub const ZERO: Self = Self(0);
#[must_use]
pub const fn from_nanos(nanos: u64) -> Self {
Self(nanos)
}
#[must_use]
pub const fn from_timespec(secs: i64, nanos: i64) -> Self {
let secs = if secs < 0 { 0 } else { secs.cast_unsigned() };
let nanos = if nanos < 0 { 0 } else { nanos.cast_unsigned() };
Self(secs.saturating_mul(1_000_000_000).saturating_add(nanos))
}
#[must_use]
pub const fn from_timeval(secs: i64, micros: i64) -> Self {
let secs = if secs < 0 { 0 } else { secs.cast_unsigned() };
let micros = if micros < 0 {
0
} else {
micros.cast_unsigned()
};
Self(
secs.saturating_mul(1_000_000_000)
.saturating_add(micros.saturating_mul(1_000)),
)
}
#[must_use]
pub const fn as_nanos(self) -> u64 {
self.0
}
#[must_use]
pub const fn saturating_duration_since(self, earlier: Self) -> Duration {
Duration::from_nanos(self.0.saturating_sub(earlier.0))
}
#[must_use]
pub const fn checked_duration_since(self, earlier: Self) -> Option<Duration> {
match self.0.checked_sub(earlier.0) {
Some(delta) => Some(Duration::from_nanos(delta)),
None => None,
}
}
}
impl Add<Duration> for Timestamp {
type Output = Self;
fn add(self, rhs: Duration) -> Self {
Self(self.0.saturating_add(saturating_nanos(rhs)))
}
}
impl AddAssign<Duration> for Timestamp {
fn add_assign(&mut self, rhs: Duration) {
*self = *self + rhs;
}
}
impl Sub<Duration> for Timestamp {
type Output = Self;
fn sub(self, rhs: Duration) -> Self {
Self(self.0.saturating_sub(saturating_nanos(rhs)))
}
}
impl SubAssign<Duration> for Timestamp {
fn sub_assign(&mut self, rhs: Duration) {
*self = *self - rhs;
}
}
impl Sub for Timestamp {
type Output = Duration;
fn sub(self, rhs: Self) -> Duration {
self.saturating_duration_since(rhs)
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}.{:09}s",
self.0 / 1_000_000_000,
self.0 % 1_000_000_000
)
}
}
fn saturating_nanos(d: Duration) -> u64 {
u64::try_from(d.as_nanos()).unwrap_or(u64::MAX)
}
pub trait Clock: Send + Sync + fmt::Debug {
fn now(&self) -> Timestamp;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct SteadyClock;
impl SteadyClock {
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl Clock for SteadyClock {
fn now(&self) -> Timestamp {
let ts = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic);
Timestamp::from_timespec(ts.tv_sec, ts.tv_nsec as i64)
}
}
#[must_use]
pub fn default_clock() -> &'static SteadyClock {
static CLOCK: SteadyClock = SteadyClock::new();
&CLOCK
}
#[derive(Debug, Default)]
pub struct ManualClock {
nanos: core::sync::atomic::AtomicU64,
}
impl ManualClock {
#[must_use]
pub const fn new() -> Self {
Self {
nanos: core::sync::atomic::AtomicU64::new(0),
}
}
#[must_use]
pub const fn starting_at(start: Timestamp) -> Self {
Self {
nanos: core::sync::atomic::AtomicU64::new(start.as_nanos()),
}
}
pub fn advance(&self, delta: Duration) {
let step = saturating_nanos(delta);
let mut current = self.nanos.load(core::sync::atomic::Ordering::Relaxed);
loop {
let clamped = current.saturating_add(step);
match self.nanos.compare_exchange_weak(
current,
clamped,
core::sync::atomic::Ordering::Relaxed,
core::sync::atomic::Ordering::Relaxed,
) {
Ok(_) => break,
Err(observed) => current = observed,
}
}
}
pub fn set(&self, at: Timestamp) {
self.nanos
.store(at.as_nanos(), core::sync::atomic::Ordering::Relaxed);
}
}
impl Clock for ManualClock {
fn now(&self) -> Timestamp {
Timestamp::from_nanos(self.nanos.load(core::sync::atomic::Ordering::Relaxed))
}
}
#[cfg(test)]
mod tests;