use std::future::{poll_fn, Future};
use std::pin::pin;
use std::sync::Arc;
use std::task::Poll;
use std::time::Duration;
use crate::{Instant, Runtime};
const TOLERANCE: Duration = Duration::from_millis(5);
const FAR: Duration = Duration::from_secs(86_400 * 365 * 30);
pub async fn timeout<F: Future>(
runtime: &dyn Runtime,
duration: Duration,
future: F,
) -> Option<F::Output> {
let mut future = pin!(future);
let mut deadline = runtime.sleep(duration);
poll_fn(|cx| {
if let Poll::Ready(output) = future.as_mut().poll(cx) {
return Poll::Ready(Some(output));
}
deadline.as_mut().poll(cx).map(|()| None)
})
.await
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum MissedTick {
#[default]
Burst,
Delay,
Skip,
}
pub fn interval(runtime: Arc<dyn Runtime>, period: Duration) -> Interval {
assert!(!period.is_zero(), "interval period must be non-zero");
let next = runtime.now();
Interval {
runtime,
period,
next,
behaviour: MissedTick::default(),
}
}
#[derive(Debug)]
pub struct Interval {
runtime: Arc<dyn Runtime>,
period: Duration,
next: Instant,
behaviour: MissedTick,
}
impl Interval {
pub async fn tick(&mut self) -> Instant {
let due = self.next;
self.runtime.sleep_until(due).await;
self.next = self.after(due, self.runtime.now());
due
}
pub fn reset(&mut self) {
self.next = later(self.runtime.now(), self.period);
}
pub fn period(&self) -> Duration {
self.period
}
pub fn set_missed_tick(&mut self, behaviour: MissedTick) {
self.behaviour = behaviour;
}
fn after(&self, due: Instant, now: Instant) -> Instant {
let next = later(due, self.period);
if now.saturating_duration_since(due) <= TOLERANCE {
return next;
}
match self.behaviour {
MissedTick::Burst => next,
MissedTick::Delay => later(now, self.period),
MissedTick::Skip => {
if next > now {
return next;
}
let behind = now.saturating_duration_since(due).as_nanos();
let periods = behind / self.period.as_nanos();
u32::try_from(periods + 1)
.ok()
.and_then(|whole| self.period.checked_mul(whole))
.and_then(|grid| due.checked_add(grid))
.unwrap_or_else(|| later(now, self.period))
}
}
}
}
fn later(instant: Instant, wait: Duration) -> Instant {
instant.checked_add(wait).unwrap_or_else(|| instant + FAR)
}