futures_ticker/
lib.rs

1//! futures-ticker - Asynchronous, recurring delivery of a timer event.
2
3use futures::{stream::Stream, task::Context};
4use futures_timer::Delay;
5use std::{future::Future, pin::Pin, task::Poll, time::Duration};
6
7#[cfg(target_family = "wasm")]
8use instant::Instant;
9#[cfg(not(target_family = "wasm"))]
10use std::time::Instant;
11
12/// Yields the current time in regular intervals.
13///
14/// Tickers are an asynchronous notification mechanism which deliver
15/// the "current" time in regular intervals (a "tick"). In case any
16/// ticks were missed, they will be skipped, and only the nearest
17/// upcoming tick is delivered.
18#[derive(Debug)]
19pub struct Ticker {
20    interval: Duration,
21    next: Instant,
22    schedule: Delay,
23}
24
25impl Ticker {
26    /// Constructs a ticker that goes off once per `interval`. It
27    /// is scheduled to deliver the first tick at `interval` from now.
28    pub fn new(interval: Duration) -> Ticker {
29        Ticker::new_with_next(interval, interval)
30    }
31
32    /// Constructs a ticker that goes off once per `interval`. The first
33    /// tick is scheduled to arrive after `first_in` elapses.
34    pub fn new_with_next(interval: Duration, first_in: Duration) -> Ticker {
35        let first = Instant::now() + first_in;
36        Ticker {
37            interval,
38            next: first,
39            schedule: Delay::new(first_in),
40        }
41    }
42
43    /// Returns the next Instant at which the Ticker will be ready.
44    pub fn next_tick(&self) -> Instant {
45        self.next_tick_from(Instant::now())
46    }
47
48    /// Answers the hypothetical question, "at Instant `now`, when
49    /// would the next tick go off?"
50    ///
51    /// This function is useful mainly for tests. Use
52    /// [`Ticker::next_tick`] for real use cases instead.
53    pub fn next_tick_from(&self, now: Instant) -> Instant {
54        if self.next > now {
55            return self.next;
56        }
57        let raw_next = self.next + self.interval;
58        if raw_next > now {
59            return raw_next;
60        }
61        if self.interval.as_nanos() == 0 {
62            // Silly special case: If somebody specifies "now", the
63            // ticker is always ready to return a result.
64            return now;
65        }
66        // If the "next" tick would be in the past, let's schedule it
67        // to go off in the future at a multiple of the interval,
68        // instead:
69        let missed_times = 1 + ((now - raw_next).as_nanos() / self.interval.as_nanos()) as u32;
70        self.next + self.interval * (missed_times + 1)
71    }
72}
73
74impl Stream for Ticker {
75    type Item = Instant;
76
77    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
78        let schedule = Pin::new(&mut self.schedule);
79        match schedule.poll(cx) {
80            Poll::Pending => Poll::Pending,
81
82            Poll::Ready(_) => {
83                let now = Instant::now();
84                let next = self.next_tick_from(now);
85                self.next = next;
86                self.schedule.reset(
87                    next.checked_duration_since(now)
88                        .unwrap_or_else(|| Duration::from_nanos(0)),
89                );
90                Poll::Ready(Some(now))
91            }
92        }
93    }
94
95    fn size_hint(&self) -> (usize, Option<usize>) {
96        (1, None)
97    }
98}