zduny_wasm_timer/timer/
interval.rs1use pin_utils::unsafe_pinned;
2use std::pin::Pin;
3use std::task::{Context, Poll};
4use std::time::Duration;
5
6use futures::prelude::*;
7
8use crate::timer::delay;
9use crate::{Delay, Instant, TimerHandle};
10
11#[derive(Debug)]
21pub struct Interval {
22 delay: Delay,
23 interval: Duration,
24}
25
26impl Interval {
27 unsafe_pinned!(delay: Delay);
28
29 pub fn new(dur: Duration) -> Interval {
35 Interval::new_at(Instant::now() + dur, dur)
36 }
37
38 pub fn new_at(at: Instant, dur: Duration) -> Interval {
44 Interval {
45 delay: Delay::new_at(at),
46 interval: dur,
47 }
48 }
49
50 pub fn new_handle(at: Instant, dur: Duration, handle: TimerHandle) -> Interval {
55 Interval {
56 delay: Delay::new_handle(at, handle),
57 interval: dur,
58 }
59 }
60}
61
62impl Stream for Interval {
63 type Item = ();
64
65 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
66 if Pin::new(&mut *self).delay().poll(cx).is_pending() {
67 return Poll::Pending;
68 }
69 let next = next_interval(delay::fires_at(&self.delay), Instant::now(), self.interval);
70 self.delay.reset_at(next);
71 Poll::Ready(Some(()))
72 }
73}
74
75fn duration_to_nanos(dur: Duration) -> Option<u64> {
84 dur.as_secs()
85 .checked_mul(1_000_000_000)
86 .and_then(|v| v.checked_add(dur.subsec_nanos() as u64))
87}
88
89fn next_interval(prev: Instant, now: Instant, interval: Duration) -> Instant {
90 let new = prev + interval;
91 if new > now {
92 new
93 } else {
94 let spent_ns =
95 duration_to_nanos(now.duration_since(prev)).expect("interval should be expired");
96 let interval_ns =
97 duration_to_nanos(interval).expect("interval is less that 427 thousand years");
98 let mult = spent_ns / interval_ns + 1;
99 assert!(
100 mult < (1 << 32),
101 "can't skip more than 4 billion intervals of {:?} \
102 (trying to skip {})",
103 interval,
104 mult
105 );
106 prev + interval * (mult as u32)
107 }
108}
109
110#[cfg(test)]
111mod test {
112 use std::time::Duration;
113
114 use wasm_bindgen_test::wasm_bindgen_test;
115
116 use super::next_interval;
117 use crate::Instant;
118
119 struct Timeline(Instant);
120
121 impl Timeline {
122 fn new() -> Timeline {
123 Timeline(Instant::now())
124 }
125 fn at(&self, millis: u64) -> Instant {
126 self.0 + Duration::from_millis(millis)
127 }
128 fn at_ns(&self, sec: u64, nanos: u32) -> Instant {
129 self.0 + Duration::new(sec, nanos)
130 }
131 }
132
133 fn dur(millis: u64) -> Duration {
134 Duration::from_millis(millis)
135 }
136
137 fn almost_eq(a: Instant, b: Instant) -> bool {
140 if a == b {
141 true
142 } else if a > b {
143 a - b < Duration::from_millis(1)
144 } else {
145 b - a < Duration::from_millis(1)
146 }
147 }
148
149 #[wasm_bindgen_test]
150 fn norm_next() {
151 let tm = Timeline::new();
152 assert!(almost_eq(
153 next_interval(tm.at(1), tm.at(2), dur(10)),
154 tm.at(11)
155 ));
156 assert!(almost_eq(
157 next_interval(tm.at(7777), tm.at(7788), dur(100)),
158 tm.at(7877)
159 ));
160 assert!(almost_eq(
161 next_interval(tm.at(1), tm.at(1000), dur(2100)),
162 tm.at(2101)
163 ));
164 }
165
166 #[wasm_bindgen_test]
167 fn fast_forward() {
168 let tm = Timeline::new();
169 assert!(almost_eq(
170 next_interval(tm.at(1), tm.at(1000), dur(10)),
171 tm.at(1001)
172 ));
173 assert!(almost_eq(
174 next_interval(tm.at(7777), tm.at(8888), dur(100)),
175 tm.at(8977)
176 ));
177 assert!(almost_eq(
178 next_interval(tm.at(1), tm.at(10000), dur(2100)),
179 tm.at(10501)
180 ));
181 }
182
183 #[wasm_bindgen_test]
187 #[should_panic(expected = "can't skip more than 4 billion intervals")]
188 fn large_skip() {
189 let tm = Timeline::new();
190 assert_eq!(
191 next_interval(tm.at_ns(0, 1), tm.at_ns(25, 0), Duration::new(0, 2)),
192 tm.at_ns(25, 1)
193 );
194 }
195}