use futures::future::poll_fn;
use pin_utils::unsafe_pinned;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use futures::prelude::*;
use crate::std::Instant;
use crate::tokio::Sleep;
#[derive(Debug)]
pub struct Interval {
sleep: Sleep,
interval: Duration,
missed_tick_behavior: MissedTickBehavior,
}
impl Interval {
unsafe_pinned!(sleep: Sleep);
pub(crate) fn new(dur: Duration) -> Interval {
Interval::new_at(Instant::now(), dur)
}
pub(crate) fn new_at(at: Instant, dur: Duration) -> Interval {
Interval {
sleep: Sleep::new_at(at),
interval: dur,
missed_tick_behavior: MissedTickBehavior::default(),
}
}
pub async fn tick(&mut self) -> Instant {
let instant = poll_fn(|cx| self.poll_tick(cx));
instant.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 timeout = self.sleep.deadline();
let now = Instant::now();
let next = if now > timeout + Duration::from_millis(5) {
self.missed_tick_behavior
.next_timeout(timeout, now, self.interval)
} else {
timeout + self.interval
};
Pin::new(&mut self.sleep).reset(next);
Poll::Ready(timeout)
}
pub fn reset(&mut self) {
Pin::new(&mut self.sleep).reset(Instant::now() + self.interval);
}
pub fn missed_tick_behavior(&self) -> MissedTickBehavior {
self.missed_tick_behavior
}
pub fn set_missed_tick_behavior(&mut self, behavior: MissedTickBehavior) {
self.missed_tick_behavior = behavior;
}
pub fn period(&self) -> Duration {
self.interval
}
}
#[derive(Debug, PartialEq, Clone, Copy, Eq, Default)]
pub enum MissedTickBehavior {
#[default]
Burst,
Delay,
Skip,
}
impl MissedTickBehavior {
fn next_timeout(&self, timeout: Instant, now: Instant, period: Duration) -> Instant {
match self {
Self::Burst => timeout + period,
Self::Delay => now + period,
Self::Skip => {
now + period
- Duration::from_nanos(
((now - timeout).as_nanos() % period.as_nanos())
.try_into()
.expect(
"too much time has elapsed since the interval was supposed to tick",
),
)
}
}
}
}
pub fn interval(period: Duration) -> Interval {
Interval::new(period)
}
pub fn interval_at(start: Instant, period: Duration) -> Interval {
Interval::new_at(start, period)
}