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}