Skip to main content

nuclei/time/
interval.rs

1//! [`Interval`]: ticks at a fixed period.
2
3use core::future::{Future, poll_fn};
4use core::pin::Pin;
5use core::task::{Context, Poll};
6use std::time::Duration;
7
8use super::Instant;
9
10use super::sleep::{Sleep, sleep_until};
11
12/// What an interval does with ticks it missed because it was polled late.
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
14pub enum MissedTickBehavior {
15    /// Every missed tick fires as soon as it can, one after the other, until
16    /// the interval is back on its schedule.
17    #[default]
18    Burst,
19    /// The next tick comes one period after the late one: the schedule moves.
20    Delay,
21    /// Missed ticks are skipped: the next tick is the next one on the
22    /// original schedule after now.
23    Skip,
24}
25
26/// Ticks every `period`, the first tick at once.
27///
28/// # Panics
29///
30/// If `period` is zero.
31pub fn interval(period: Duration) -> Interval {
32    interval_at(Instant::now(), period)
33}
34
35/// Ticks every `period`, the first tick at `start`.
36///
37/// # Panics
38///
39/// If `period` is zero.
40pub fn interval_at(start: impl Into<Instant>, period: Duration) -> Interval {
41    let start = start.into();
42    assert!(
43        period > Duration::ZERO,
44        "an interval needs a period above zero"
45    );
46    Interval {
47        period,
48        sleep: sleep_until(start),
49        missed: MissedTickBehavior::default(),
50    }
51}
52
53/// Ticks at a fixed period. Made by [`interval`] and [`interval_at`].
54#[derive(Debug)]
55pub struct Interval {
56    period: Duration,
57    /// Until the next tick.
58    sleep: Sleep,
59    missed: MissedTickBehavior,
60}
61
62impl Interval {
63    /// Waits for the next tick and returns the instant it was due at.
64    pub async fn tick(&mut self) -> Instant {
65        poll_fn(|cx| self.poll_tick(cx)).await
66    }
67
68    /// Polls for the next tick; see [`tick`](Self::tick).
69    pub fn poll_tick(&mut self, cx: &mut Context<'_>) -> Poll<Instant> {
70        if Pin::new(&mut self.sleep).poll(cx).is_pending() {
71            return Poll::Pending;
72        }
73        let due = self.sleep.deadline();
74        let now = Instant::now();
75        let next = match self.missed {
76            MissedTickBehavior::Burst => due + self.period,
77            MissedTickBehavior::Delay => now + self.period,
78            MissedTickBehavior::Skip => {
79                let late = now.saturating_duration_since(due);
80                // The next deadline on the schedule after now.
81                let periods = late.as_nanos() / self.period.as_nanos() + 1;
82                due + self.period * u32::try_from(periods).unwrap_or(u32::MAX)
83            }
84        };
85        Pin::new(&mut self.sleep).reset(next);
86        Poll::Ready(due)
87    }
88
89    /// Restarts the schedule: the next tick is one period from now.
90    pub fn reset(&mut self) {
91        Pin::new(&mut self.sleep).reset(Instant::now() + self.period);
92    }
93
94    /// The period.
95    pub fn period(&self) -> Duration {
96        self.period
97    }
98
99    /// What happens to missed ticks.
100    pub fn missed_tick_behavior(&self) -> MissedTickBehavior {
101        self.missed
102    }
103
104    /// Sets what happens to missed ticks.
105    pub fn set_missed_tick_behavior(&mut self, behavior: MissedTickBehavior) {
106        self.missed = behavior;
107    }
108}