use std::sync::atomic::{AtomicI64, Ordering};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub const TICKS_PER_SECOND: i64 = 10_000_000;
const NANOS_PER_TICK: i64 = 100;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timestamp(i64);
impl Timestamp {
pub const MIN: Timestamp = Timestamp(i64::MIN);
pub const MAX: Timestamp = Timestamp(i64::MAX);
#[must_use]
pub const fn from_ticks(ticks: i64) -> Self {
Self(ticks)
}
#[must_use]
pub const fn ticks(self) -> i64 {
self.0
}
#[must_use]
pub fn saturating_add(self, duration: Duration) -> Self {
Self(self.0.saturating_add(duration_to_ticks(duration)))
}
#[must_use]
pub fn saturating_duration_since(self, earlier: Timestamp) -> Duration {
let delta = self.0.saturating_sub(earlier.0);
if delta <= 0 {
Duration::ZERO
} else {
ticks_to_duration(delta)
}
}
#[must_use]
pub fn is_before(self, other: Timestamp) -> bool {
self < other
}
}
#[must_use]
pub fn duration_to_ticks(duration: Duration) -> i64 {
let nanos = duration.as_nanos();
let ticks = nanos / (NANOS_PER_TICK as u128);
i64::try_from(ticks).unwrap_or(i64::MAX)
}
#[must_use]
pub fn ticks_to_duration(ticks: i64) -> Duration {
let nanos = (ticks.max(0) as u64).saturating_mul(NANOS_PER_TICK as u64);
Duration::from_nanos(nanos)
}
pub trait Clock: Send + Sync {
fn now(&self) -> Timestamp;
}
impl<T: Clock + ?Sized> Clock for std::sync::Arc<T> {
fn now(&self) -> Timestamp {
(**self).now()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now(&self) -> Timestamp {
let since_epoch = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO);
Timestamp(duration_to_ticks(since_epoch))
}
}
#[derive(Debug)]
pub struct ManualClock {
ticks: AtomicI64,
}
impl ManualClock {
#[must_use]
pub fn new(start: Timestamp) -> Self {
Self {
ticks: AtomicI64::new(start.0),
}
}
pub fn advance(&self, duration: Duration) {
self.ticks
.fetch_add(duration_to_ticks(duration), Ordering::SeqCst);
}
pub fn set(&self, at: Timestamp) {
self.ticks.store(at.0, Ordering::SeqCst);
}
}
impl Default for ManualClock {
fn default() -> Self {
Self::new(Timestamp(1_000_000_000 * TICKS_PER_SECOND))
}
}
impl Clock for ManualClock {
fn now(&self) -> Timestamp {
Timestamp(self.ticks.load(Ordering::SeqCst))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Timeout {
#[default]
Infinite,
After(Duration),
}
impl Timeout {
#[must_use]
pub fn from_option(duration: Option<Duration>) -> Self {
match duration {
Some(d) => Timeout::After(d),
None => Timeout::Infinite,
}
}
#[must_use]
pub fn is_infinite(self) -> bool {
matches!(self, Timeout::Infinite)
}
#[must_use]
pub fn is_immediate(self) -> bool {
matches!(self, Timeout::After(d) if d.is_zero())
}
#[must_use]
pub fn as_duration(self) -> Option<Duration> {
match self {
Timeout::Infinite => None,
Timeout::After(d) => Some(d),
}
}
#[must_use]
pub fn min(self, other: Timeout) -> Timeout {
match (self, other) {
(Timeout::Infinite, o) => o,
(s, Timeout::Infinite) => s,
(Timeout::After(a), Timeout::After(b)) => Timeout::After(a.min(b)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn duration_round_trips_through_ticks() {
let d = Duration::from_millis(1500);
assert_eq!(ticks_to_duration(duration_to_ticks(d)), d);
}
#[test]
fn saturating_add_then_since_recovers_duration() {
let now = Timestamp::from_ticks(5 * TICKS_PER_SECOND);
let later = now.saturating_add(Duration::from_secs(3));
assert_eq!(later.saturating_duration_since(now), Duration::from_secs(3));
assert_eq!(now.saturating_duration_since(later), Duration::ZERO);
}
#[test]
fn manual_clock_advances() {
let clock = ManualClock::new(Timestamp::from_ticks(0));
assert_eq!(clock.now(), Timestamp::from_ticks(0));
clock.advance(Duration::from_secs(2));
assert_eq!(clock.now(), Timestamp::from_ticks(2 * TICKS_PER_SECOND));
}
#[test]
fn timeout_min_treats_infinite_as_longest() {
assert_eq!(
Timeout::Infinite.min(Timeout::After(Duration::from_secs(1))),
Timeout::After(Duration::from_secs(1))
);
assert_eq!(
Timeout::After(Duration::from_secs(2)).min(Timeout::After(Duration::from_secs(1))),
Timeout::After(Duration::from_secs(1))
);
assert_eq!(Timeout::Infinite.min(Timeout::Infinite), Timeout::Infinite);
}
}