use std::cmp::max;
use std::time::{Duration, Instant};
pub type TimeStamp = i32;
pub type TimeSpan = i32;
#[derive(Copy, Clone, Debug)]
pub struct TimeBase(Instant);
impl TimeBase {
pub fn new(start_time: Instant) -> Self {
Self(start_time)
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn timestamp_from(&self, instant: Instant) -> TimeStamp {
if self.0 > instant {
-((self.0 - instant).as_micros() as i32)
} else {
(instant - self.0).as_micros() as i32
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn instant_from(&self, timestamp: TimeStamp) -> Instant {
if timestamp < 0 {
self.0 - Duration::from_micros(timestamp.abs() as u64)
} else {
self.0 + Duration::from_micros(timestamp as u64)
}
}
pub fn adjust(&mut self, delta: TimeSpan) {
match delta {
delta if delta > 0 => {
self.0 += Duration::from_micros(delta as u64);
}
delta if delta < 0 => {
self.0 -= Duration::from_micros(delta.abs() as u64);
}
_ => {}
}
}
}
#[cfg(test)]
mod timebase {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn timestamp_roundtrip(expected_ts: i32) {
let timebase = TimeBase::new(Instant::now());
let ts = timebase.timestamp_from(timebase.instant_from(expected_ts));
prop_assert_eq!(ts, expected_ts);
}
#[test]
fn timestamp_from(expected_ts: i32, time_domain in -5i64..5) {
let micros = |micros| Duration::from_micros(micros as u64);
let now = Instant::now();
let timebase = TimeBase::new(now);
let rollover_interval = std::u32::MAX as i64 + 1;
let delta = time_domain * rollover_interval + expected_ts as i64;
let instant = if delta > 0 { now + micros(delta) } else { now - micros(delta.abs()) };
let ts = timebase.timestamp_from(instant);
prop_assert_eq!(ts, expected_ts);
}
#[test]
fn adjust(drift: i16) {
let now = Instant::now();
let mut timebase = TimeBase::new(now);
let original_ts = timebase.timestamp_from(now);
timebase.adjust(drift as TimeSpan);
let ts = timebase.timestamp_from(now);
prop_assert_eq!(ts, original_ts - drift as TimeSpan);
}
}
}
pub struct Timer {
period: Duration,
last: Instant,
}
impl Timer {
const MIN_PERIOD: Duration = Duration::from_micros(1);
pub fn new(period: Duration, now: Instant) -> Timer {
Timer {
period: max(period, Self::MIN_PERIOD),
last: now,
}
}
pub fn next_instant(&self) -> Instant {
self.last + self.period
}
pub fn reset(&mut self, now: Instant) {
self.last = now;
}
pub fn set_period(&mut self, period: Duration) {
self.period = max(period, Self::MIN_PERIOD);
}
pub fn check_expired(&mut self, now: Instant) -> Option<Instant> {
if now >= self.next_instant() {
let elapsed = now - self.last;
let elapsed_periods = elapsed.as_nanos() / self.period.as_nanos();
self.last += self.period * elapsed_periods as u32;
Some(self.last)
} else {
None
}
}
}