use core::fmt;
use crate::Now;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Deadline(u64);
impl Deadline {
pub const NEVER: Self = Self(u64::MAX);
pub const IMMEDIATE: Self = Self(0);
pub const fn from_millis(millis: u64) -> Self {
Self(millis)
}
pub const fn as_millis(self) -> u64 {
self.0
}
pub const fn is_never(self) -> bool {
self.0 == u64::MAX
}
pub const fn is_expired_at(self, now: Now) -> bool {
!self.is_never() && now.monotonic_ms >= self.0
}
pub const fn millis_until(self, now: Now) -> u64 {
if self.is_never() {
return u64::MAX;
}
self.0.saturating_sub(now.monotonic_ms)
}
pub const fn saturating_add_millis(self, millis: u64) -> Self {
Self(self.0.saturating_add(millis))
}
pub const fn earliest(self, other: Self) -> Self {
if self.0 <= other.0 { self } else { other }
}
pub const fn earliest_opt(left: Option<Self>, right: Option<Self>) -> Option<Self> {
match (left, right) {
(Some(left), Some(right)) => Some(left.earliest(right)),
(Some(only), None) | (None, Some(only)) => Some(only),
(None, None) => None,
}
}
}
impl fmt::Display for Deadline {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_never() {
f.write_str("never")
} else {
write!(f, "{}ms", self.0)
}
}
}
impl From<u64> for Deadline {
fn from(value: u64) -> Self {
Self::from_millis(value)
}
}
impl From<Deadline> for u64 {
fn from(value: Deadline) -> Self {
value.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
#[test]
fn expires_once_now_reaches_it() {
let deadline = Deadline::from_millis(100);
assert!(!deadline.is_expired_at(Now::from_millis(99)));
assert!(deadline.is_expired_at(Now::from_millis(100)));
assert!(deadline.is_expired_at(Now::from_millis(101)));
}
#[test]
fn never_does_not_expire_at_end_of_timeline() {
assert!(Deadline::NEVER.is_never());
assert!(!Deadline::NEVER.is_expired_at(Now::from_millis(u64::MAX)));
}
#[test]
fn never_reports_full_remaining_time_however_far_now_has_advanced() {
for now in [0, 1, 1_000_000, u64::MAX / 2, u64::MAX - 1, u64::MAX] {
let now = Now::from_millis(now);
assert_eq!(
Deadline::NEVER.millis_until(now),
u64::MAX,
"millis_until disagreed with is_expired_at at {now:?}"
);
assert!(!Deadline::NEVER.is_expired_at(now));
}
}
#[test]
fn immediate_is_always_due() {
for now in [0, 1, 1_000_000, u64::MAX] {
let now = Now::from_millis(now);
assert!(
Deadline::IMMEDIATE.is_expired_at(now),
"IMMEDIATE must be due at {now:?}"
);
assert_eq!(Deadline::IMMEDIATE.millis_until(now), 0);
}
assert_eq!(
Deadline::IMMEDIATE.earliest(Deadline::from_millis(5)),
Deadline::IMMEDIATE
);
}
#[test]
fn remaining_time_saturates_at_zero() {
let deadline = Deadline::from(100u64);
assert_eq!(deadline.as_millis(), 100);
assert_eq!(u64::from(deadline), 100);
assert_eq!(deadline.millis_until(Now::from_millis(40)), 60);
assert_eq!(deadline.millis_until(Now::from_millis(100)), 0);
assert_eq!(deadline.millis_until(Now::from_millis(500)), 0);
}
#[test]
fn adding_saturates_at_never() {
assert_eq!(
Deadline::from_millis(10).saturating_add_millis(5),
Deadline::from_millis(15)
);
assert_eq!(
Deadline::from_millis(10).saturating_add_millis(u64::MAX),
Deadline::NEVER
);
}
#[test]
fn earliest_picks_the_sooner_deadline() {
let soon = Deadline::from_millis(10);
let late = Deadline::from_millis(20);
assert_eq!(soon.earliest(late), soon);
assert_eq!(late.earliest(soon), soon);
assert_eq!(soon.earliest(Deadline::NEVER), soon);
}
#[test]
fn earliest_opt_treats_none_as_unscheduled() {
let soon = Some(Deadline::from_millis(10));
let late = Some(Deadline::from_millis(20));
assert_eq!(Deadline::earliest_opt(soon, late), soon);
assert_eq!(Deadline::earliest_opt(late, soon), soon);
assert_eq!(Deadline::earliest_opt(soon, None), soon);
assert_eq!(Deadline::earliest_opt(None, late), late);
assert_eq!(Deadline::earliest_opt(None, None), None);
}
#[test]
fn ordering_is_chronological_with_never_last() {
let mut deadlines = [
Deadline::NEVER,
Deadline::from_millis(30),
Deadline::from_millis(10),
];
deadlines.sort();
assert_eq!(
deadlines,
[
Deadline::from_millis(10),
Deadline::from_millis(30),
Deadline::NEVER
]
);
assert_eq!(
deadlines.iter().copied().min(),
Some(Deadline::from_millis(10))
);
}
#[test]
fn display_names_the_never_sentinel() {
assert_eq!(format!("{}", Deadline::from_millis(25)), "25ms");
assert_eq!(format!("{}", Deadline::NEVER), "never");
}
}