embassy_interval/
lib.rs

1#![no_std]
2
3use embassy_time::{Duration, Instant, Timer};
4
5pub struct Interval {
6    interval: Duration,
7    wake_time: Instant,
8    missed_tick_behavior: MissedTickBehavior,
9}
10
11/// This enum is mostly copied from tokio
12///
13/// Defines the behavior of an [`Interval`] when it misses a tick.
14///
15///
16/// Generally, a tick is missed if too much time is spent without calling
17/// [`Interval::tick()`].
18///
19/// By default, when a tick is missed, [`Interval`] fires ticks as quickly as it
20/// can until it is "caught up" in time to where it should be.
21/// `MissedTickBehavior` can be used to specify a different behavior for
22/// [`Interval`] to exhibit. Each variant represents a different strategy.
23///
24/// Note that because the executor cannot guarantee exact precision with timers,
25/// these strategies will only apply when the delay is greater than 5
26/// milliseconds.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum MissedTickBehavior {
29    /// Ticks as fast as possible until caught up.
30    ///
31    /// When this strategy is used, [`Interval`] schedules ticks "normally" (the
32    /// same as it would have if the ticks hadn't been delayed), which results
33    /// in it firing ticks as fast as possible until it is caught up in time to
34    /// where it should be. Unlike [`Delay`] and [`Skip`], the ticks yielded
35    /// when `Burst` is used (the [`Instant`]s that [`tick`](Interval::tick)
36    /// yields) aren't different than they would have been if a tick had not
37    /// been missed. Like [`Skip`], and unlike [`Delay`], the ticks may be
38    /// shortened.
39    ///
40    /// This looks something like this:
41    /// ```text
42    /// Expected ticks: |     1     |     2     |     3     |     4     |     5     |     6     |
43    /// Actual ticks:   | work -----|          delay          | work | work | work -| work -----|
44    /// ```
45    ///
46    /// This is the default behavior when [`Interval`] is created with
47    /// [`interval`] and [`interval_at`].
48    ///
49    /// [`Delay`]: MissedTickBehavior::Delay
50    /// [`Skip`]: MissedTickBehavior::Skip
51    #[default]
52    Burst,
53
54    /// Tick at multiples of `period` from when [`tick`] was called, rather than
55    /// from `start`.
56    ///
57    /// When this strategy is used and [`Interval`] has missed a tick, instead
58    /// of scheduling ticks to fire at multiples of `period` from `start` (the
59    /// time when the first tick was fired), it schedules all future ticks to
60    /// happen at a regular `period` from the point when [`tick`] was called.
61    /// Unlike [`Burst`] and [`Skip`], ticks are not shortened, and they aren't
62    /// guaranteed to happen at a multiple of `period` from `start` any longer.
63    ///
64    /// This looks something like this:
65    /// ```text
66    /// Expected ticks: |     1     |     2     |     3     |     4     |     5     |     6     |
67    /// Actual ticks:   | work -----|          delay          | work -----| work -----| work -----|
68    /// ```
69    ///
70    /// [`Burst`]: MissedTickBehavior::Burst
71    /// [`Skip`]: MissedTickBehavior::Skip
72    /// [`tick`]: Interval::tick
73    #[allow(unused)]
74    Delay,
75
76    /// Skips missed ticks and tick on the next multiple of `period` from
77    /// `start`.
78    ///
79    /// When this strategy is used, [`Interval`] schedules the next tick to fire
80    /// at the next-closest tick that is a multiple of `period` away from
81    /// `start` (the point where [`Interval`] first ticked). Like [`Burst`], all
82    /// ticks remain multiples of `period` away from `start`, but unlike
83    /// [`Burst`], the ticks may not be *one* multiple of `period` away from the
84    /// last tick. Like [`Delay`], the ticks are no longer the same as they
85    /// would have been if ticks had not been missed, but unlike [`Delay`], and
86    /// like [`Burst`], the ticks may be shortened to be less than one `period`
87    /// away from each other.
88    ///
89    /// This looks something like this:
90    /// ```text
91    /// Expected ticks: |     1     |     2     |     3     |     4     |     5     |     6     |
92    /// Actual ticks:   | work -----|          delay          | work ---| work -----| work -----|
93    /// ```
94    ///
95    /// [`Burst`]: MissedTickBehavior::Burst
96    /// [`Delay`]: MissedTickBehavior::Delay
97    #[allow(unused)]
98    Skip,
99}
100
101impl Interval {
102    pub fn new(interval: Duration, missed_tick_behavior: MissedTickBehavior) -> Self {
103        let wake_time = Instant::now();
104        Self {
105            interval,
106            wake_time,
107            missed_tick_behavior,
108        }
109    }
110
111    pub fn tick(&mut self) -> Timer {
112        let now = Instant::now();
113
114        if now > self.wake_time {
115            self.wake_time =
116                self.missed_tick_behavior
117                    .next_timeout(self.wake_time, now, self.interval);
118        }
119
120        let timer = Timer::at(self.wake_time);
121
122        self.wake_time += self.interval;
123
124        timer
125    }
126}
127
128impl MissedTickBehavior {
129    fn next_timeout(&self, timeout: Instant, now: Instant, period: Duration) -> Instant {
130        match self {
131            Self::Burst => timeout + period,
132            Self::Delay => now + period,
133            Self::Skip => {
134                now + period - Duration::from_ticks((now - timeout).as_ticks() % period.as_ticks())
135            }
136        }
137    }
138}