use core::future::{Future, poll_fn};
use core::pin::Pin;
use core::task::{Context, Poll};
use std::time::Duration;
use super::Instant;
use super::sleep::{Sleep, sleep_until};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum MissedTickBehavior {
#[default]
Burst,
Delay,
Skip,
}
pub fn interval(period: Duration) -> Interval {
interval_at(Instant::now(), period)
}
pub fn interval_at(start: impl Into<Instant>, period: Duration) -> Interval {
let start = start.into();
assert!(
period > Duration::ZERO,
"an interval needs a period above zero"
);
Interval {
period,
sleep: sleep_until(start),
missed: MissedTickBehavior::default(),
}
}
#[derive(Debug)]
pub struct Interval {
period: Duration,
sleep: Sleep,
missed: MissedTickBehavior,
}
impl Interval {
pub async fn tick(&mut self) -> Instant {
poll_fn(|cx| self.poll_tick(cx)).await
}
pub fn poll_tick(&mut self, cx: &mut Context<'_>) -> Poll<Instant> {
if Pin::new(&mut self.sleep).poll(cx).is_pending() {
return Poll::Pending;
}
let due = self.sleep.deadline();
let now = Instant::now();
let next = match self.missed {
MissedTickBehavior::Burst => due + self.period,
MissedTickBehavior::Delay => now + self.period,
MissedTickBehavior::Skip => {
let late = now.saturating_duration_since(due);
let periods = late.as_nanos() / self.period.as_nanos() + 1;
due + self.period * u32::try_from(periods).unwrap_or(u32::MAX)
}
};
Pin::new(&mut self.sleep).reset(next);
Poll::Ready(due)
}
pub fn reset(&mut self) {
Pin::new(&mut self.sleep).reset(Instant::now() + self.period);
}
pub fn period(&self) -> Duration {
self.period
}
pub fn missed_tick_behavior(&self) -> MissedTickBehavior {
self.missed
}
pub fn set_missed_tick_behavior(&mut self, behavior: MissedTickBehavior) {
self.missed = behavior;
}
}