use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
use core::num::NonZeroU64;
use core::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use core::time::Duration;
#[cfg(test)]
mod tests;
const NANOS_PER_SECOND: u128 = 1_000_000_000;
pub(crate) const HUNDRED_NANOSECOND_TICKS: NonZeroU64 = NonZeroU64::new(10_000_000).unwrap();
pub trait Timeline: 'static {
fn ticks_per_second() -> NonZeroU64;
}
pub struct TimePoint<T: Timeline> {
ticks: u64,
timeline: PhantomData<fn() -> T>,
}
pub struct Ticks<T: Timeline> {
count: i64,
timeline: PhantomData<fn() -> T>,
}
impl<T: Timeline> TimePoint<T> {
#[must_use]
pub const fn from_ticks(ticks: u64) -> Self {
Self {
ticks,
timeline: PhantomData,
}
}
#[must_use]
pub const fn ticks(self) -> u64 {
self.ticks
}
#[must_use]
pub fn checked_since(self, earlier: Self) -> Option<Ticks<T>> {
let distance = i128::from(self.ticks) - i128::from(earlier.ticks);
i64::try_from(distance).ok().map(Ticks::new)
}
#[must_use]
pub fn checked_add(self, ticks: Ticks<T>) -> Option<Self> {
let moved = i128::from(self.ticks) + i128::from(ticks.count);
u64::try_from(moved).ok().map(Self::from_ticks)
}
#[must_use]
pub fn checked_sub(self, ticks: Ticks<T>) -> Option<Self> {
let moved = i128::from(self.ticks) - i128::from(ticks.count);
u64::try_from(moved).ok().map(Self::from_ticks)
}
}
impl<T: Timeline> Ticks<T> {
pub const ZERO: Self = Self::new(0);
#[must_use]
pub const fn new(count: i64) -> Self {
Self {
count,
timeline: PhantomData,
}
}
#[must_use]
pub const fn count(self) -> i64 {
self.count
}
#[must_use]
pub const fn is_negative(self) -> bool {
self.count < 0
}
#[must_use]
pub fn to_duration(self) -> Option<Duration> {
(!self.is_negative()).then(|| self.abs_duration())
}
#[must_use]
pub fn abs_duration(self) -> Duration {
let per_second = T::ticks_per_second().get();
let magnitude = self.count.unsigned_abs();
let seconds = magnitude / per_second;
let remainder = u128::from(magnitude % per_second);
let nanos = remainder * NANOS_PER_SECOND / u128::from(per_second);
Duration::new(
seconds,
u32::try_from(nanos).expect("below one second of nanoseconds"),
)
}
#[must_use]
pub fn from_duration(duration: Duration) -> Option<Self> {
let per_second = u128::from(T::ticks_per_second().get());
let whole = u128::from(duration.as_secs()).checked_mul(per_second)?;
let part = u128::from(duration.subsec_nanos()) * per_second / NANOS_PER_SECOND;
i64::try_from(whole.checked_add(part)?).ok().map(Self::new)
}
#[must_use]
pub fn checked_add(self, other: Self) -> Option<Self> {
self.count.checked_add(other.count).map(Self::new)
}
#[must_use]
pub fn checked_sub(self, other: Self) -> Option<Self> {
self.count.checked_sub(other.count).map(Self::new)
}
#[must_use]
pub fn checked_neg(self) -> Option<Self> {
self.count.checked_neg().map(Self::new)
}
}
impl<T: Timeline> Clone for TimePoint<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Timeline> Copy for TimePoint<T> {}
impl<T: Timeline> PartialEq for TimePoint<T> {
fn eq(&self, other: &Self) -> bool {
self.ticks == other.ticks
}
}
impl<T: Timeline> Eq for TimePoint<T> {}
impl<T: Timeline> PartialOrd for TimePoint<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T: Timeline> Ord for TimePoint<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.ticks.cmp(&other.ticks)
}
}
impl<T: Timeline> Hash for TimePoint<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.ticks.hash(state);
}
}
impl<T: Timeline> fmt::Debug for TimePoint<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TimePoint")
.field("timeline", &core::any::type_name::<T>())
.field("ticks", &self.ticks)
.finish()
}
}
impl<T: Timeline> Clone for Ticks<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Timeline> Copy for Ticks<T> {}
impl<T: Timeline> PartialEq for Ticks<T> {
fn eq(&self, other: &Self) -> bool {
self.count == other.count
}
}
impl<T: Timeline> Eq for Ticks<T> {}
impl<T: Timeline> PartialOrd for Ticks<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T: Timeline> Ord for Ticks<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.count.cmp(&other.count)
}
}
impl<T: Timeline> Hash for Ticks<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.count.hash(state);
}
}
impl<T: Timeline> Default for Ticks<T> {
fn default() -> Self {
Self::ZERO
}
}
impl<T: Timeline> fmt::Debug for Ticks<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ticks")
.field("timeline", &core::any::type_name::<T>())
.field("count", &self.count)
.finish()
}
}
impl<T: Timeline> Sub for TimePoint<T> {
type Output = Ticks<T>;
fn sub(self, earlier: Self) -> Ticks<T> {
self.checked_since(earlier)
.expect("the distance between two time points overflowed")
}
}
impl<T: Timeline> Add<Ticks<T>> for TimePoint<T> {
type Output = Self;
fn add(self, ticks: Ticks<T>) -> Self {
self.checked_add(ticks)
.expect("a time point moved outside its timeline")
}
}
impl<T: Timeline> Sub<Ticks<T>> for TimePoint<T> {
type Output = Self;
fn sub(self, ticks: Ticks<T>) -> Self {
self.checked_sub(ticks)
.expect("a time point moved outside its timeline")
}
}
impl<T: Timeline> AddAssign<Ticks<T>> for TimePoint<T> {
fn add_assign(&mut self, ticks: Ticks<T>) {
*self = *self + ticks;
}
}
impl<T: Timeline> SubAssign<Ticks<T>> for TimePoint<T> {
fn sub_assign(&mut self, ticks: Ticks<T>) {
*self = *self - ticks;
}
}
impl<T: Timeline> Add for Ticks<T> {
type Output = Self;
fn add(self, other: Self) -> Self {
self.checked_add(other).expect("a tick count overflowed")
}
}
impl<T: Timeline> Sub for Ticks<T> {
type Output = Self;
fn sub(self, other: Self) -> Self {
self.checked_sub(other).expect("a tick count overflowed")
}
}
impl<T: Timeline> Neg for Ticks<T> {
type Output = Self;
fn neg(self) -> Self {
self.checked_neg().expect("a tick count overflowed")
}
}
impl<T: Timeline> AddAssign for Ticks<T> {
fn add_assign(&mut self, other: Self) {
*self = *self + other;
}
}
impl<T: Timeline> SubAssign for Ticks<T> {
fn sub_assign(&mut self, other: Self) {
*self = *self - other;
}
}