use std::collections::BTreeSet;
use std::ops::{Add, AddAssign, Sub, SubAssign};
use std::time::{Duration, SystemTime};
#[derive(Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug, From)]
#[wrapper(Display, LowerHex, UpperHex, Octal, Add, Sub)]
#[wrapper_mut(AddAssign, SubAssign)]
pub struct Timestamp(u128);
impl Timestamp {
pub fn now() -> Self {
let duration =
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).expect("system time");
Self(duration.as_millis())
}
pub fn from_secs(secs: u64) -> Timestamp { Timestamp(secs as u128 * 1000) }
pub fn from_millis(millis: u128) -> Timestamp { Timestamp(millis) }
#[deprecated(note = "use Timestamp::as_secs")]
pub fn into_secs(self) -> u64 { self.as_secs() }
pub fn as_secs(&self) -> u64 { (self.0 / 1000) as u64 }
pub fn as_millis(&self) -> u64 {
self.0 as u64
}
}
impl Add<Duration> for Timestamp {
type Output = Timestamp;
fn add(self, rhs: Duration) -> Self::Output { Timestamp(self.0 + rhs.as_millis()) }
}
impl Sub<Duration> for Timestamp {
type Output = Timestamp;
fn sub(self, rhs: Duration) -> Self::Output { Timestamp(self.0 - rhs.as_millis()) }
}
impl AddAssign<Duration> for Timestamp {
fn add_assign(&mut self, rhs: Duration) { self.0 += rhs.as_millis() }
}
impl SubAssign<Duration> for Timestamp {
fn sub_assign(&mut self, rhs: Duration) { self.0 -= rhs.as_millis() }
}
#[derive(Debug, Default)]
pub struct Timer {
timeouts: BTreeSet<Timestamp>,
}
impl Timer {
pub fn new() -> Self { Self { timeouts: bset! {} } }
pub fn count(&self) -> usize { self.timeouts.len() }
pub fn has_timeouts(&self) -> bool { !self.timeouts.is_empty() }
pub fn set_timeout(&mut self, timeout: Duration, after: Timestamp) {
let time = after + Timestamp(timeout.as_millis());
self.timeouts.insert(time);
}
pub fn next_expiring_from(&self, time: impl Into<Timestamp>) -> Option<Duration> {
let time = time.into();
let last = *self.timeouts.first()?;
Some(if last >= time {
Duration::from_millis(last.as_millis() - time.as_millis())
} else {
Duration::from_secs(0)
})
}
pub fn remove_expired_by(&mut self, time: Timestamp) -> usize {
let at = time + Timestamp::from_millis(1);
let unexpired = self.timeouts.split_off(&at);
let fired = self.timeouts.len();
self.timeouts = unexpired;
fired
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wake_exact() {
let mut tm = Timer::new();
let now = Timestamp::now();
tm.set_timeout(Duration::from_secs(8), now);
tm.set_timeout(Duration::from_secs(9), now);
tm.set_timeout(Duration::from_secs(10), now);
assert_eq!(tm.remove_expired_by(now + Duration::from_secs(9)), 2);
assert_eq!(tm.count(), 1);
}
#[test]
fn test_wake() {
let mut tm = Timer::new();
let now = Timestamp::now();
tm.set_timeout(Duration::from_secs(8), now);
tm.set_timeout(Duration::from_secs(16), now);
tm.set_timeout(Duration::from_secs(64), now);
tm.set_timeout(Duration::from_secs(72), now);
assert_eq!(tm.remove_expired_by(now), 0);
assert_eq!(tm.count(), 4);
assert_eq!(tm.remove_expired_by(now + Duration::from_secs(9)), 1);
assert_eq!(tm.count(), 3, "one timeout has expired");
assert_eq!(tm.remove_expired_by(now + Duration::from_secs(66)), 2);
assert_eq!(tm.count(), 1, "another two timeouts have expired");
assert_eq!(tm.remove_expired_by(now + Duration::from_secs(96)), 1);
assert!(!tm.has_timeouts(), "all timeouts have expired");
}
#[test]
fn test_next() {
let mut tm = Timer::new();
let mut now = Timestamp::now();
tm.set_timeout(Duration::from_secs(3), now);
assert_eq!(tm.next_expiring_from(now), Some(Duration::from_secs(3)));
now += Duration::from_secs(2);
assert_eq!(tm.next_expiring_from(now), Some(Duration::from_secs(1)));
now += Duration::from_secs(1);
assert_eq!(tm.next_expiring_from(now), Some(Duration::from_secs(0)));
now += Duration::from_secs(1);
assert_eq!(tm.next_expiring_from(now), Some(Duration::from_secs(0)));
assert_eq!(tm.remove_expired_by(now), 1);
assert_eq!(tm.count(), 0);
assert_eq!(tm.next_expiring_from(now), None);
}
}