use std::cmp::Ordering;
use std::ops::{Add, Div, Sub};
#[derive(Debug, Clone, Copy)]
enum DurationImpl {
Zero, Ticks { ticks: u64, frequency: u64 },
Nanos(u64),
}
impl Ord for DurationImpl {
fn cmp(&self, other: &Self) -> Ordering {
fn normalize(value: DurationImpl) -> f64 {
match value {
DurationImpl::Zero => 0.0,
DurationImpl::Ticks { ticks, frequency } => {
ticks_to_nanos(ticks, frequency)
},
DurationImpl::Nanos(nanos) => nanos as f64,
}
}
let self_normalized = normalize(*self);
let other_normalized = normalize(*other);
self_normalized.total_cmp(&other_normalized)
}
}
impl PartialOrd for DurationImpl {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for DurationImpl {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for DurationImpl {}
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
pub enum StorageType {
Zero,
Ticks,
Nanos,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub struct Duration {
inner: DurationImpl,
}
impl Duration {
pub const ZERO: Duration = Duration {
inner: DurationImpl::Zero,
};
#[must_use]
pub fn from_nanos(nanos: u64) -> Duration {
match nanos {
0 => Duration::ZERO,
_ => Duration {
inner: DurationImpl::Nanos(nanos),
},
}
}
#[must_use]
pub fn from_ticks(ticks: u64, frequency: u64) -> Duration {
match ticks {
0 => Duration::ZERO,
_ => Duration {
inner: DurationImpl::Ticks { ticks, frequency },
},
}
}
#[must_use]
pub fn to_nanos(&self) -> f64 {
match self.inner {
DurationImpl::Nanos(nanos) => nanos as f64,
DurationImpl::Ticks { ticks, frequency } => {
ticks_to_nanos(ticks, frequency)
},
DurationImpl::Zero => 0.0,
}
}
#[must_use]
pub fn storage_type(&self) -> StorageType {
match self.inner {
DurationImpl::Zero => StorageType::Zero,
DurationImpl::Ticks { .. } => StorageType::Ticks,
DurationImpl::Nanos(_) => StorageType::Nanos,
}
}
#[must_use]
pub fn to_raw(&self) -> u64 {
match self.inner {
DurationImpl::Ticks { ticks, .. } => ticks,
DurationImpl::Nanos(nanos) => nanos,
DurationImpl::Zero => 0,
}
}
#[must_use]
pub fn to_std_duration(&self) -> std::time::Duration {
let nanos = match self.inner {
DurationImpl::Nanos(nanos) => nanos,
DurationImpl::Ticks { ticks, frequency } => {
ticks_to_nanos(ticks, frequency).round() as u64
},
DurationImpl::Zero => 0,
};
std::time::Duration::from_nanos(nanos)
}
}
impl Add for Duration {
type Output = Duration;
fn add(self, rhs: Self) -> Self::Output {
match (self.inner, rhs.inner) {
(DurationImpl::Nanos(self_nanos), DurationImpl::Nanos(rhs_nanos)) => {
Duration::from_nanos(self_nanos + rhs_nanos)
},
(
DurationImpl::Ticks { ticks, frequency },
DurationImpl::Ticks {
ticks: rhs_ticks, ..
},
) => Duration::from_ticks(ticks + rhs_ticks, frequency),
(DurationImpl::Zero, other) => Duration { inner: other },
(other, DurationImpl::Zero) => Duration { inner: other },
(
DurationImpl::Nanos(nanos),
DurationImpl::Ticks { ticks, frequency },
) => Duration::from_nanos(
nanos + ticks_to_nanos(ticks, frequency).round() as u64,
),
(
DurationImpl::Ticks { ticks, frequency },
DurationImpl::Nanos(nanos),
) => Duration::from_nanos(
ticks_to_nanos(ticks, frequency).round() as u64 + nanos,
),
}
}
}
impl Sub for Duration {
type Output = Duration;
fn sub(self, rhs: Self) -> Self::Output {
match (self.inner, rhs.inner) {
(DurationImpl::Nanos(self_nanos), DurationImpl::Nanos(rhs_nanos)) => {
Duration::from_nanos(self_nanos.saturating_sub(rhs_nanos))
},
(
DurationImpl::Ticks { ticks, frequency },
DurationImpl::Ticks {
ticks: rhs_ticks, ..
},
) => Duration::from_ticks(ticks.saturating_sub(rhs_ticks), frequency),
(DurationImpl::Zero, _) => Duration::ZERO,
(other, DurationImpl::Zero) => Duration { inner: other },
(
DurationImpl::Nanos(nanos),
DurationImpl::Ticks { ticks, frequency },
) => Duration::from_nanos(
nanos - ticks_to_nanos(ticks, frequency).round() as u64,
),
(
DurationImpl::Ticks { ticks, frequency },
DurationImpl::Nanos(nanos),
) => Duration::from_nanos(
ticks_to_nanos(ticks, frequency).round() as u64 - nanos,
),
}
}
}
impl<T> Div<T> for Duration
where
T: TryInto<u64> + Copy,
<T as std::convert::TryInto<u64>>::Error: std::fmt::Debug,
{
type Output = Duration;
fn div(self, rhs: T) -> Self::Output {
let rhs: u64 = rhs.try_into().expect("divisor must fit inside u64");
match self.inner {
DurationImpl::Zero => Duration::ZERO,
DurationImpl::Nanos(nanos) => Duration::from_nanos(div_round(nanos, rhs)),
DurationImpl::Ticks { ticks, frequency } => {
Duration::from_ticks(div_round(ticks, rhs), frequency)
},
}
}
}
fn div_round(lhs: u64, rhs: u64) -> u64 {
let div = lhs / rhs;
let rem = lhs % rhs;
div + u64::from(rem >= rhs - rem)
}
pub fn ticks_to_nanos(tsc_ticks: u64, tsc_freq_hz: u64) -> f64 {
assert_ne!(tsc_freq_hz, 0);
const NANOS_PER_SECOND: u128 = 1_000_000_000;
let ticks = u128::from(tsc_ticks);
let ticks_per_second = u128::from(tsc_freq_hz);
let numerator = ticks * NANOS_PER_SECOND;
let whole_nanos = numerator / ticks_per_second;
let nanos_remainder = numerator % ticks_per_second;
let fractional_nanos = (nanos_remainder as f64) / (ticks_per_second as f64);
whole_nanos as f64 + fractional_nanos
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Instant {
Ticks(u64),
Std(std::time::Instant),
}
#[cfg(test)]
mod tests {
use super::*;
use speculoos::prelude::*;
#[test]
fn ticks_to_nanos_small_ticks() {
assert_that(&ticks_to_nanos(124, 3_200_000_000)).is_close_to(38.75, 0.01);
assert_that(&ticks_to_nanos(123, 3_200_000_000))
.is_close_to(38.4375, 0.0001);
assert_that(&ticks_to_nanos(122, 3_200_000_000)).is_close_to(38.125, 0.001);
assert_that(&ticks_to_nanos(121, 3_200_000_000))
.is_close_to(37.8125, 0.0001);
assert_that(&ticks_to_nanos(120, 3_200_000_000)).is_close_to(37.5, 0.1);
assert_that(&ticks_to_nanos(119, 3_200_000_000))
.is_close_to(37.1875, 0.001);
}
#[test]
fn ticks_to_nanos_big_ticks() {
assert_eq!(
ticks_to_nanos(267_597_764_222_976u64, 3_200_000_000),
83_624_301_319_680f64
);
assert_that(&ticks_to_nanos(267_597_764_222_971, 3_200_000_000))
.is_close_to(83_624_301_319_678.44, 0.01);
}
#[test]
fn ticks_to_nanos_low_freq() {
assert_eq!(ticks_to_nanos(0, 24_000_000), 0.0);
assert_that(&ticks_to_nanos(1, 24_000_000)).is_close_to(41.67, 0.01);
assert_that(&ticks_to_nanos(2, 24_000_000)).is_close_to(83.34, 0.01);
}
#[test]
fn from_nanos_conversion() {
let duration = Duration::from_nanos(10);
assert_eq!(duration.to_nanos(), 10.0);
assert_eq!(duration.to_raw(), 10);
assert_eq!(
duration.to_std_duration(),
std::time::Duration::from_nanos(10)
);
}
#[test]
fn from_ticks_conversion() {
let duration = Duration::from_ticks(30, 3_000_000_000);
assert_eq!(duration.to_nanos(), 10.0);
assert_eq!(duration.to_raw(), 30);
assert_eq!(
duration.to_std_duration(),
std::time::Duration::from_nanos(10)
);
}
#[test]
fn from_zero_nanos() {
let duration = Duration::from_nanos(0);
assert_eq!(duration.to_nanos(), 0.0);
assert_eq!(duration.to_raw(), 0);
assert_eq!(
duration.to_std_duration(),
std::time::Duration::from_nanos(0)
);
assert_eq!(duration, Duration::ZERO);
}
#[test]
fn from_zero_ticks() {
let duration = Duration::from_ticks(0, 3_000_000_000);
assert_eq!(duration.to_nanos(), 0.0);
assert_eq!(duration.to_raw(), 0);
assert_eq!(
duration.to_std_duration(),
std::time::Duration::from_nanos(0)
);
assert_eq!(duration, Duration::ZERO);
}
#[test]
fn addition_ticks() {
let dur1 = Duration::from_ticks(10, 3_000_000_000);
let dur2 = Duration::from_ticks(20, 3_000_000_000);
assert_eq!((dur1 + dur2).to_raw(), 30);
assert_eq!((dur1 + Duration::ZERO).to_raw(), 10);
assert_eq!((Duration::ZERO + dur1).to_raw(), 10);
}
#[test]
fn addition_nanos() {
let dur1 = Duration::from_nanos(10);
let dur2 = Duration::from_nanos(20);
assert_eq!((dur1 + dur2).to_nanos(), 30.0);
assert_eq!((dur1 + Duration::ZERO).to_nanos(), 10.0);
assert_eq!((Duration::ZERO + dur1).to_nanos(), 10.0);
}
#[test]
fn addition_mix() {
let dur1 = Duration::from_nanos(10);
let dur2 = Duration::from_ticks(20, 1_000_000_000);
assert_eq!((dur1 + dur2).to_nanos(), 30.0);
assert_eq!((dur2 + dur1).to_nanos(), 30.0);
}
#[test]
fn subtraction_ticks() {
let dur1 = Duration::from_ticks(10, 3_000_000_000);
let dur2 = Duration::from_ticks(30, 3_000_000_000);
assert_eq!((dur2 - dur1).to_raw(), 20);
assert_eq!((dur1 - dur2).to_raw(), 0);
assert_eq!((dur1 - Duration::ZERO).to_raw(), 10);
assert_eq!((Duration::ZERO - dur1).to_raw(), 0); }
#[test]
fn subtraction_nanos() {
let dur1 = Duration::from_nanos(10);
let dur2 = Duration::from_nanos(30);
assert_eq!((dur2 - dur1).to_nanos(), 20.0);
assert_eq!((dur1 - dur2).to_nanos(), 0.0);
assert_eq!((dur1 - Duration::ZERO).to_nanos(), 10.0);
assert_eq!((Duration::ZERO - dur1).to_nanos(), 0.0); }
#[test]
fn subtraction_mix() {
let dur1 = Duration::from_nanos(10);
let dur2 = Duration::from_ticks(30, 1_000_000_000);
assert_eq!((dur2 - dur1).to_nanos(), 20.0);
let dur3 = Duration::from_nanos(50);
assert_eq!((dur3 - dur2).to_nanos(), 20.0);
}
#[test]
fn division_ticks() {
let duration = Duration::from_ticks(7, 3_000_000_000);
assert_eq!((duration / 3).to_raw(), 2);
assert_eq!((duration / 4).to_raw(), 2);
assert_eq!((duration / 5).to_raw(), 1);
assert_eq!((duration / 8).to_raw(), 1);
assert_eq!((duration / 15).to_raw(), 0);
}
#[test]
fn division_nanos() {
let duration = Duration::from_nanos(7);
assert_eq!((duration / 3).to_raw(), 2);
assert_eq!((duration / 4).to_raw(), 2);
assert_eq!((duration / 5).to_raw(), 1);
assert_eq!((duration / 8).to_raw(), 1);
assert_eq!((duration / 15).to_raw(), 0);
}
#[test]
fn ord_is_sensible() {
assert!(Duration::ZERO < Duration::from_ticks(1, 100));
assert!(Duration::ZERO < Duration::from_nanos(1));
assert!(Duration::from_nanos(1) < Duration::from_nanos(2));
assert!(Duration::from_ticks(1, 10) < Duration::from_ticks(2, 10));
assert!(Duration::from_ticks(1, 1_000_000_000) < Duration::from_nanos(2));
assert!(Duration::from_nanos(2) > Duration::from_ticks(1, 1_000_000_000));
}
#[test]
fn eq_is_sensible() {
assert!(Duration::ZERO == Duration::ZERO);
assert!(Duration::from_nanos(1) == Duration::from_nanos(1));
assert!(Duration::from_ticks(1, 10) == Duration::from_ticks(1, 10));
assert!(Duration::from_ticks(1, 1_000_000_000) == Duration::from_nanos(1));
assert!(Duration::from_ticks(0, 1_000_000_000) == Duration::from_nanos(0));
assert!(Duration::from_ticks(0, 1_000_000_000) == Duration::ZERO);
assert!(Duration::from_nanos(0) == Duration::ZERO);
}
#[test]
fn storage_type_works() {
assert_eq!(Duration::ZERO.storage_type(), StorageType::Zero);
assert_eq!(Duration::from_nanos(1).storage_type(), StorageType::Nanos);
assert_eq!(
Duration::from_ticks(1, 10).storage_type(),
StorageType::Ticks
);
}
}