1use std::future::{poll_fn, Future};
4use std::pin::pin;
5use std::sync::Arc;
6use std::task::Poll;
7use std::time::Duration;
8
9use crate::{Instant, Runtime};
10
11const TOLERANCE: Duration = Duration::from_millis(5);
15
16const FAR: Duration = Duration::from_secs(86_400 * 365 * 30);
18
19pub async fn timeout<F: Future>(
26 runtime: &dyn Runtime,
27 duration: Duration,
28 future: F,
29) -> Option<F::Output> {
30 let mut future = pin!(future);
31 let mut deadline = runtime.sleep(duration);
32 poll_fn(|cx| {
33 if let Poll::Ready(output) = future.as_mut().poll(cx) {
34 return Poll::Ready(Some(output));
35 }
36 deadline.as_mut().poll(cx).map(|()| None)
37 })
38 .await
39}
40
41#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
43#[non_exhaustive]
44pub enum MissedTick {
45 #[default]
48 Burst,
49 Delay,
51 Skip,
53}
54
55pub fn interval(runtime: Arc<dyn Runtime>, period: Duration) -> Interval {
61 assert!(!period.is_zero(), "interval period must be non-zero");
62 let next = runtime.now();
63 Interval {
64 runtime,
65 period,
66 next,
67 behaviour: MissedTick::default(),
68 }
69}
70
71#[derive(Debug)]
76pub struct Interval {
77 runtime: Arc<dyn Runtime>,
78 period: Duration,
79 next: Instant,
80 behaviour: MissedTick,
81}
82
83impl Interval {
84 pub async fn tick(&mut self) -> Instant {
88 let due = self.next;
89 self.runtime.sleep_until(due).await;
90 self.next = self.after(due, self.runtime.now());
91 due
92 }
93
94 pub fn reset(&mut self) {
96 self.next = later(self.runtime.now(), self.period);
97 }
98
99 pub fn period(&self) -> Duration {
101 self.period
102 }
103
104 pub fn set_missed_tick(&mut self, behaviour: MissedTick) {
106 self.behaviour = behaviour;
107 }
108
109 fn after(&self, due: Instant, now: Instant) -> Instant {
111 let next = later(due, self.period);
112 if now.saturating_duration_since(due) <= TOLERANCE {
113 return next;
114 }
115 match self.behaviour {
116 MissedTick::Burst => next,
117 MissedTick::Delay => later(now, self.period),
118 MissedTick::Skip => {
119 if next > now {
120 return next;
121 }
122 let behind = now.saturating_duration_since(due).as_nanos();
123 let periods = behind / self.period.as_nanos();
124 u32::try_from(periods + 1)
125 .ok()
126 .and_then(|whole| self.period.checked_mul(whole))
127 .and_then(|grid| due.checked_add(grid))
128 .unwrap_or_else(|| later(now, self.period))
129 }
130 }
131 }
132}
133
134fn later(instant: Instant, wait: Duration) -> Instant {
136 instant.checked_add(wait).unwrap_or_else(|| instant + FAR)
137}