use std::time::{Duration, Instant};
use super::sleep::Sleep;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MissedTickBehavior {
Skip,
CatchUp,
}
pub struct Interval {
period: Duration,
pub next_deadline: Option<Instant>,
missed_tick_behavior: MissedTickBehavior,
}
impl Interval {
#[inline]
pub fn new(period: Duration) -> Self {
Self {
period,
next_deadline: None,
missed_tick_behavior: MissedTickBehavior::Skip,
}
}
#[inline]
pub fn set_missed_tick_behavior(&mut self, behavior: MissedTickBehavior) {
self.missed_tick_behavior = behavior;
}
#[inline]
pub fn reset(&mut self) {
self.next_deadline = None;
}
pub async fn tick(&mut self) -> u64 {
let now = Instant::now();
let base_next = self.next_deadline.unwrap_or_else(|| now + self.period);
match self.missed_tick_behavior {
MissedTickBehavior::Skip => {
let mut target = base_next;
if target <= now {
if self.period.as_nanos() == 0 {
target = now;
} else {
let mut cnt: u64 = 0;
while target <= now {
target += self.period;
cnt = cnt.saturating_add(1);
if cnt == u64::MAX {
break;
}
}
}
}
let remaining = target.saturating_duration_since(Instant::now());
if remaining.as_millis() == 0 {
Sleep::new_with_zero_behavior(remaining, super::sleep::ZeroBehavior::Yield)
.await;
} else {
Sleep::new(remaining).await;
}
self.next_deadline = Some(target + self.period);
1
}
MissedTickBehavior::CatchUp => {
if base_next > now {
let remaining = base_next.saturating_duration_since(Instant::now());
if remaining.as_millis() == 0 {
Sleep::new_with_zero_behavior(remaining, super::sleep::ZeroBehavior::Yield)
.await;
} else {
Sleep::new(remaining).await;
}
self.next_deadline = Some(base_next + self.period);
1
} else {
if self.period.as_nanos() == 0 {
self.next_deadline = Some(Instant::now() + self.period);
return 1;
}
let elapsed = now.duration_since(base_next);
let missed = (elapsed.as_nanos() / self.period.as_nanos()) as u64 + 1;
let mut new_next = base_next;
for _ in 0..missed {
new_next += self.period;
}
self.next_deadline = Some(new_next + self.period);
missed
}
}
}
}
}