microlock/
timer.rs

1use std::{
2    cmp::Ordering,
3    time::{Duration, Instant},
4};
5
6/// Our real representation of an Infinite duration.
7pub const INFINITE_DURATION: Duration = Duration::new(u64::MAX, 1_000_000_000 - 1);
8
9#[derive(Clone, Copy, PartialEq, Eq)]
10/// A duration wrapper supporting elapsed/negative, real, and infinite
11/// durations. Due to the solar system's impending collapse before the 64 bit
12/// integer limit of seconds is reached, an Infinite duration is translated
13/// to that when turning this back info a normal duration. If you run your
14/// program for longer than the sun's entire lifespan including the past, this
15/// may cause an issue. But I think you'll have much bigger issues long before
16/// that, including the fact that you even came up with that idea.
17pub enum TimerDuration {
18    Elapsed,
19    Real(Duration),
20    Infinite,
21}
22impl TimerDuration {
23    /// Takes the difference between a TimerDuration a and a Duration b,
24    /// resulting in either a negative (Elapsed) duration or a Real one.
25    /// The Infinite duration is passed through as-is, with no subtraction
26    /// done to it.
27    pub fn from_difference(a: TimerDuration, b: Duration) -> TimerDuration {
28        if a == TimerDuration::Infinite {
29            return a;
30        }
31        let a = a.to_real();
32        if a < b {
33            TimerDuration::Elapsed
34        } else {
35            TimerDuration::Real(a - b)
36        }
37    }
38
39    /// Converts this into a Duration that is waitable by rust's standard
40    /// functions, for use in e.g. specifying timeouts to std operations.
41    pub fn to_real(&self) -> Duration {
42        match *self {
43            Self::Real(d) if d > Duration::ZERO => d,
44            Self::Infinite => INFINITE_DURATION,
45            _ => Duration::new(0, 0),
46        }
47    }
48
49    pub const fn is_elapsed(&self) -> bool {
50        matches!(self, TimerDuration::Elapsed)
51    }
52
53    pub const fn is_real(&self) -> bool {
54        matches!(self, TimerDuration::Real(_))
55    }
56
57    pub const fn is_infinite(&self) -> bool {
58        matches!(self, TimerDuration::Infinite)
59    }
60}
61impl From<Duration> for TimerDuration {
62    fn from(value: Duration) -> Self {
63        if value == Duration::ZERO {
64            return Self::Elapsed;
65        }
66        if value == INFINITE_DURATION {
67            return Self::Infinite;
68        }
69        Self::Real(value)
70    }
71}
72impl From<u64> for TimerDuration {
73    fn from(value: u64) -> Self {
74        if value == 0 {
75            return Self::Elapsed;
76        }
77        if value == u64::MAX {
78            return Self::Infinite;
79        }
80        Self::Real(Duration::from_millis(value))
81    }
82}
83impl PartialOrd for TimerDuration {
84    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
85        Some(self.cmp(other))
86    }
87}
88impl Ord for TimerDuration {
89    fn cmp(&self, other: &Self) -> Ordering {
90        match (self, other) {
91            (TimerDuration::Elapsed, TimerDuration::Elapsed) => Ordering::Equal,
92            (TimerDuration::Elapsed, _) => Ordering::Less,
93            (TimerDuration::Real(_), TimerDuration::Elapsed) => Ordering::Greater,
94            (TimerDuration::Real(a), TimerDuration::Real(b)) => a.cmp(b),
95            (TimerDuration::Real(_), TimerDuration::Infinite) => Ordering::Less,
96            (TimerDuration::Infinite, TimerDuration::Infinite) => Ordering::Equal,
97            (TimerDuration::Infinite, _) => Ordering::Greater,
98        }
99    }
100}
101
102#[derive(Clone, Copy)]
103/// Holds a start time and a TimerDuration and allows for the time elapsed
104/// and the time left to be queried. Timers are immutable.
105pub struct Timer(Option<Instant>, TimerDuration);
106impl Timer {
107    pub fn new(d: TimerDuration) -> Self {
108        Self(Some(Instant::now()), d)
109    }
110
111    pub const fn new_const(d: TimerDuration) -> Self {
112        if d.is_real() {
113            panic!("TimerDuration is only const-compatible when Elapsed or Infinite.");
114        }
115        Self(None, d)
116    }
117
118    pub fn time_elapsed(&self) -> Duration {
119        self.0.unwrap_or_else(Instant::now).elapsed()
120    }
121
122    pub fn restart(&mut self) {
123        self.0 = Some(Instant::now());
124    }
125
126}
127
128/// Something with a timeout.
129pub trait Timed {
130    /// Returns if the time has elapsed or not.
131    fn has_elapsed(&self) -> bool;
132    /// Returns how much time is left. Unbounded and elapsed durations use
133    /// Infinite and Elapsed respectively. Others always return Real.
134    fn time_left(&self) -> TimerDuration;
135}
136impl Timed for Timer {
137    fn has_elapsed(&self) -> bool {
138        if self.1.is_elapsed() {
139            return true;
140        }
141        if self.1.is_infinite() {
142            return false;
143        }
144        self.0.unwrap().elapsed() >= self.1.to_real()
145    }
146    fn time_left(&self) -> TimerDuration {
147        if !self.1.is_real() {
148            return self.1;
149        }
150        TimerDuration::from_difference(self.1, self.0.unwrap().elapsed())
151    }
152}