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