use std::{
cmp::Ordering,
time::{Duration, Instant},
};
pub const INFINITE_DURATION: Duration = Duration::new(u64::MAX, 1_000_000_000 - 1);
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum TimerDuration {
Elapsed,
Real(Duration),
Infinite,
}
impl TimerDuration {
pub fn from_difference(a: TimerDuration, b: Duration) -> TimerDuration {
if a == TimerDuration::Infinite {
return a;
}
let a = a.to_real();
if a < b {
TimerDuration::Elapsed
} else {
TimerDuration::Real(a - b)
}
}
pub fn to_real(&self) -> Duration {
match *self {
Self::Real(d) if d > Duration::ZERO => d,
Self::Infinite => INFINITE_DURATION,
_ => Duration::new(0, 0),
}
}
pub const fn is_elapsed(&self) -> bool {
matches!(self, TimerDuration::Elapsed)
}
pub const fn is_real(&self) -> bool {
matches!(self, TimerDuration::Real(_))
}
pub const fn is_infinite(&self) -> bool {
matches!(self, TimerDuration::Infinite)
}
}
impl From<Duration> for TimerDuration {
fn from(value: Duration) -> Self {
if value == Duration::ZERO {
return Self::Elapsed;
}
if value == INFINITE_DURATION {
return Self::Infinite;
}
Self::Real(value)
}
}
impl From<u64> for TimerDuration {
fn from(value: u64) -> Self {
if value == 0 {
return Self::Elapsed;
}
if value == u64::MAX {
return Self::Infinite;
}
Self::Real(Duration::from_millis(value))
}
}
impl PartialOrd for TimerDuration {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TimerDuration {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(TimerDuration::Elapsed, TimerDuration::Elapsed) => Ordering::Equal,
(TimerDuration::Elapsed, _) => Ordering::Less,
(TimerDuration::Real(_), TimerDuration::Elapsed) => Ordering::Greater,
(TimerDuration::Real(a), TimerDuration::Real(b)) => a.cmp(b),
(TimerDuration::Real(_), TimerDuration::Infinite) => Ordering::Less,
(TimerDuration::Infinite, TimerDuration::Infinite) => Ordering::Equal,
(TimerDuration::Infinite, _) => Ordering::Greater,
}
}
}
#[derive(Clone, Copy)]
pub struct Timer(Option<Instant>, TimerDuration);
impl Timer {
pub fn new(d: TimerDuration) -> Self {
Self(Some(Instant::now()), d)
}
pub const fn new_const(d: TimerDuration) -> Self {
if d.is_real() {
panic!("TimerDuration is only const-compatible when Elapsed or Infinite.");
}
Self(None, d)
}
pub fn time_elapsed(&self) -> Duration {
self.0.unwrap_or_else(Instant::now).elapsed()
}
pub fn restart(&mut self) {
self.0 = Some(Instant::now());
}
}
pub trait Timed {
fn has_elapsed(&self) -> bool;
fn time_left(&self) -> TimerDuration;
}
impl Timed for Timer {
fn has_elapsed(&self) -> bool {
if self.1.is_elapsed() {
return true;
}
if self.1.is_infinite() {
return false;
}
self.0.unwrap().elapsed() >= self.1.to_real()
}
fn time_left(&self) -> TimerDuration {
if !self.1.is_real() {
return self.1;
}
TimerDuration::from_difference(self.1, self.0.unwrap().elapsed())
}
}