use spin_sleep::SpinSleeper;
use std::time::{Duration, Instant};
#[track_caller]
pub fn interval(period: Duration) -> Interval {
interval_at(Instant::now(), period)
}
#[track_caller]
pub fn interval_at(start: Instant, period: Duration) -> Interval {
assert!(period > Duration::ZERO, "`period` must be non-zero.");
Interval {
next_tick: start,
period,
missed_tick_behavior: <_>::default(),
sleeper: <_>::default(),
}
}
#[derive(Debug)]
pub struct Interval {
next_tick: Instant,
period: Duration,
missed_tick_behavior: MissedTickBehavior,
sleeper: SpinSleeper,
}
impl Interval {
pub fn tick(&mut self) -> Instant {
self.tick_with_spin(true)
}
pub fn tick_no_spin(&mut self) -> Instant {
self.tick_with_spin(false)
}
#[inline]
fn tick_with_spin(&mut self, spin: bool) -> Instant {
let tick = self.next_tick;
let now = Instant::now();
if now > tick {
self.next_tick = self.missed_tick_behavior.next_tick(tick, now, self.period);
return tick;
}
match spin {
true => self.sleeper.sleep(tick - now),
false => spin_sleep::native_sleep(tick - now),
};
self.next_tick = tick + self.period;
tick
}
pub fn reset(&mut self) {
self.next_tick = Instant::now() + self.period;
}
pub fn missed_tick_behavior(&self) -> MissedTickBehavior {
self.missed_tick_behavior
}
pub fn period(&self) -> Duration {
self.period
}
#[track_caller]
pub fn set_period(&mut self, period: Duration) {
assert!(period > Duration::ZERO, "`period` must be non-zero.");
self.period = period;
}
pub fn set_missed_tick_behavior(&mut self, behavior: MissedTickBehavior) {
self.missed_tick_behavior = behavior;
}
pub fn with_missed_tick_behavior(mut self, behavior: MissedTickBehavior) -> Self {
self.missed_tick_behavior = behavior;
self
}
pub fn spin_sleeper(&self) -> SpinSleeper {
self.sleeper
}
pub fn set_spin_sleeper(&mut self, sleeper: SpinSleeper) {
self.sleeper = sleeper;
}
pub fn with_spin_sleeper(mut self, sleeper: SpinSleeper) -> Self {
self.sleeper = sleeper;
self
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum MissedTickBehavior {
Burst,
Delay,
#[default]
Skip,
}
impl MissedTickBehavior {
fn next_tick(&self, missed_tick: Instant, now: Instant, period: Duration) -> Instant {
match self {
Self::Burst => missed_tick + period,
Self::Delay => now + period,
Self::Skip => {
now + period
- Duration::from_nanos(
((now - missed_tick).as_nanos() % period.as_nanos())
.try_into()
.expect(
"too much time has elapsed since the interval was supposed to tick",
),
)
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[should_panic]
fn at_zero_period() {
interval_at(Instant::now(), Duration::ZERO);
}
#[test]
#[should_panic]
fn zero_period() {
interval(Duration::ZERO);
}
}