embassy_time/
timer.rs

1use core::future::{poll_fn, Future};
2use core::pin::Pin;
3use core::task::{Context, Poll};
4
5use futures_core::stream::FusedStream;
6use futures_core::Stream;
7
8use crate::{Duration, Instant};
9
10/// Error returned by [`with_timeout`] and [`with_deadline`] on timeout.
11#[derive(Debug, Clone, PartialEq, Eq)]
12#[cfg_attr(feature = "defmt", derive(defmt::Format))]
13pub struct TimeoutError;
14
15/// Runs a given future with a timeout.
16///
17/// If the future completes before the timeout, its output is returned. Otherwise, on timeout,
18/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned.
19pub fn with_timeout<F: Future>(timeout: Duration, fut: F) -> TimeoutFuture<F> {
20    TimeoutFuture {
21        timer: Timer::after(timeout),
22        fut,
23    }
24}
25
26/// Runs a given future with a deadline time.
27///
28/// If the future completes before the deadline, its output is returned. Otherwise, on timeout,
29/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned.
30pub fn with_deadline<F: Future>(at: Instant, fut: F) -> TimeoutFuture<F> {
31    TimeoutFuture {
32        timer: Timer::at(at),
33        fut,
34    }
35}
36
37/// Provides functions to run a given future with a timeout or a deadline.
38pub trait WithTimeout: Sized {
39    /// Output type of the future.
40    type Output;
41
42    /// Runs a given future with a timeout.
43    ///
44    /// If the future completes before the timeout, its output is returned. Otherwise, on timeout,
45    /// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned.
46    fn with_timeout(self, timeout: Duration) -> TimeoutFuture<Self>;
47
48    /// Runs a given future with a deadline time.
49    ///
50    /// If the future completes before the deadline, its output is returned. Otherwise, on timeout,
51    /// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned.
52    fn with_deadline(self, at: Instant) -> TimeoutFuture<Self>;
53}
54
55impl<F: Future> WithTimeout for F {
56    type Output = F::Output;
57
58    fn with_timeout(self, timeout: Duration) -> TimeoutFuture<Self> {
59        with_timeout(timeout, self)
60    }
61
62    fn with_deadline(self, at: Instant) -> TimeoutFuture<Self> {
63        with_deadline(at, self)
64    }
65}
66
67/// Future for the [`with_timeout`] and [`with_deadline`] functions.
68#[must_use = "futures do nothing unless you `.await` or poll them"]
69#[derive(Debug)]
70#[cfg_attr(feature = "defmt", derive(defmt::Format))]
71pub struct TimeoutFuture<F> {
72    timer: Timer,
73    fut: F,
74}
75
76impl<F: Unpin> Unpin for TimeoutFuture<F> {}
77
78impl<F: Future> Future for TimeoutFuture<F> {
79    type Output = Result<F::Output, TimeoutError>;
80
81    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
82        let this = unsafe { self.get_unchecked_mut() };
83        let fut = unsafe { Pin::new_unchecked(&mut this.fut) };
84        let timer = unsafe { Pin::new_unchecked(&mut this.timer) };
85        if let Poll::Ready(x) = fut.poll(cx) {
86            return Poll::Ready(Ok(x));
87        }
88        if let Poll::Ready(_) = timer.poll(cx) {
89            return Poll::Ready(Err(TimeoutError));
90        }
91        Poll::Pending
92    }
93}
94
95/// A future that completes at a specified [Instant](struct.Instant.html).
96#[must_use = "futures do nothing unless you `.await` or poll them"]
97#[derive(Debug)]
98#[cfg_attr(feature = "defmt", derive(defmt::Format))]
99pub struct Timer {
100    expires_at: Instant,
101    yielded_once: bool,
102}
103
104impl Timer {
105    /// Expire at specified [Instant](struct.Instant.html)
106    /// Will expire immediately if the Instant is in the past.
107    pub fn at(expires_at: Instant) -> Self {
108        Self {
109            expires_at,
110            yielded_once: false,
111        }
112    }
113
114    /// Expire after specified [Duration](struct.Duration.html).
115    /// This can be used as a `sleep` abstraction.
116    ///
117    /// Example:
118    /// ``` no_run
119    /// use embassy_time::{Duration, Timer};
120    ///
121    /// #[embassy_executor::task]
122    /// async fn demo_sleep_seconds() {
123    ///     // suspend this task for one second.
124    ///     Timer::after(Duration::from_secs(1)).await;
125    /// }
126    /// ```
127    pub fn after(duration: Duration) -> Self {
128        Self {
129            expires_at: Instant::now() + duration,
130            yielded_once: false,
131        }
132    }
133
134    /// Expire after the specified number of ticks.
135    ///
136    /// This method is a convenience wrapper for calling `Timer::after(Duration::from_ticks())`.
137    /// For more details, refer to [`Timer::after()`] and [`Duration::from_ticks()`].
138    #[inline]
139    pub fn after_ticks(ticks: u64) -> Self {
140        Self::after(Duration::from_ticks(ticks))
141    }
142
143    /// Expire after the specified number of nanoseconds.
144    ///
145    /// This method is a convenience wrapper for calling `Timer::after(Duration::from_nanos())`.
146    /// For more details, refer to [`Timer::after()`] and [`Duration::from_nanos()`].
147    #[inline]
148    pub fn after_nanos(nanos: u64) -> Self {
149        Self::after(Duration::from_nanos(nanos))
150    }
151
152    /// Expire after the specified number of microseconds.
153    ///
154    /// This method is a convenience wrapper for calling `Timer::after(Duration::from_micros())`.
155    /// For more details, refer to [`Timer::after()`] and [`Duration::from_micros()`].
156    #[inline]
157    pub fn after_micros(micros: u64) -> Self {
158        Self::after(Duration::from_micros(micros))
159    }
160
161    /// Expire after the specified number of milliseconds.
162    ///
163    /// This method is a convenience wrapper for calling `Timer::after(Duration::from_millis())`.
164    /// For more details, refer to [`Timer::after`] and [`Duration::from_millis()`].
165    #[inline]
166    pub fn after_millis(millis: u64) -> Self {
167        Self::after(Duration::from_millis(millis))
168    }
169
170    /// Expire after the specified number of seconds.
171    ///
172    /// This method is a convenience wrapper for calling `Timer::after(Duration::from_secs())`.
173    /// For more details, refer to [`Timer::after`] and [`Duration::from_secs()`].
174    #[inline]
175    pub fn after_secs(secs: u64) -> Self {
176        Self::after(Duration::from_secs(secs))
177    }
178}
179
180impl Unpin for Timer {}
181
182impl Future for Timer {
183    type Output = ();
184    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
185        if self.yielded_once && self.expires_at <= Instant::now() {
186            Poll::Ready(())
187        } else {
188            embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker());
189            self.yielded_once = true;
190            Poll::Pending
191        }
192    }
193}
194
195/// Asynchronous stream that yields every Duration, indefinitely.
196///
197/// This stream will tick at uniform intervals, even if blocking work is performed between ticks.
198///
199/// For instance, consider the following code fragment.
200/// ``` no_run
201/// use embassy_time::{Duration, Timer};
202/// # fn foo() {}
203///
204/// #[embassy_executor::task]
205/// async fn ticker_example_0() {
206///     loop {
207///         foo();
208///         Timer::after(Duration::from_secs(1)).await;
209///     }
210/// }
211/// ```
212///
213/// This fragment will not call `foo` every second.
214/// Instead, it will call it every second + the time it took to previously call `foo`.
215///
216/// Example using ticker, which will consistently call `foo` once a second.
217///
218/// ``` no_run
219/// use embassy_time::{Duration, Ticker};
220/// # fn foo(){}
221///
222/// #[embassy_executor::task]
223/// async fn ticker_example_1() {
224///     let mut ticker = Ticker::every(Duration::from_secs(1));
225///     loop {
226///         foo();
227///         ticker.next().await;
228///     }
229/// }
230/// ```
231///
232/// ## Cancel safety
233/// It is safe to cancel waiting for the next tick,
234/// meaning no tick is lost if the Future is dropped.
235#[derive(Debug)]
236#[cfg_attr(feature = "defmt", derive(defmt::Format))]
237pub struct Ticker {
238    expires_at: Instant,
239    duration: Duration,
240}
241
242impl Ticker {
243    /// Creates a new ticker that ticks at the specified duration interval.
244    pub fn every(duration: Duration) -> Self {
245        let expires_at = Instant::now() + duration;
246        Self { expires_at, duration }
247    }
248
249    /// Resets the ticker back to its original state.
250    /// This causes the ticker to go back to zero, even if the current tick isn't over yet.
251    pub fn reset(&mut self) {
252        self.expires_at = Instant::now() + self.duration;
253    }
254
255    /// Reset the ticker at the deadline.
256    /// If the deadline is in the past, the ticker will fire instantly.
257    pub fn reset_at(&mut self, deadline: Instant) {
258        self.expires_at = deadline + self.duration;
259    }
260
261    /// Resets the ticker, after the specified duration has passed.
262    /// If the specified duration is zero, the next tick will be after the duration of the ticker.
263    pub fn reset_after(&mut self, after: Duration) {
264        self.expires_at = Instant::now() + after + self.duration;
265    }
266
267    /// Waits for the next tick.
268    ///
269    /// ## Cancel safety
270    /// The produced Future is cancel safe, meaning no tick is lost if the Future is dropped.
271    pub fn next(&mut self) -> impl Future<Output = ()> + Send + Sync + '_ {
272        poll_fn(|cx| {
273            if self.expires_at <= Instant::now() {
274                let dur = self.duration;
275                self.expires_at += dur;
276                Poll::Ready(())
277            } else {
278                embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker());
279                Poll::Pending
280            }
281        })
282    }
283}
284
285impl Unpin for Ticker {}
286
287impl Stream for Ticker {
288    type Item = ();
289    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
290        if self.expires_at <= Instant::now() {
291            let dur = self.duration;
292            self.expires_at += dur;
293            Poll::Ready(Some(()))
294        } else {
295            embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker());
296            Poll::Pending
297        }
298    }
299}
300
301impl FusedStream for Ticker {
302    fn is_terminated(&self) -> bool {
303        // `Ticker` keeps yielding values until dropped, it never terminates.
304        false
305    }
306}