1#![forbid(unsafe_code)]
2
3use std::future::Future;
4use std::pin::Pin;
5use std::task::{Context, Poll};
6use std::time::{Duration, Instant};
7
8use futures_core::Stream;
9use pin_project_lite::pin_project;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub struct TimeoutError;
13
14impl std::fmt::Display for TimeoutError {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 write!(f, "operation timed out")
17 }
18}
19
20impl std::error::Error for TimeoutError {}
21
22pub type Elapsed = TimeoutError;
23
24pub async fn sleep(duration: Duration) {
25 if duration.is_zero() {
26 return;
27 }
28 std::thread::sleep(duration);
29}
30
31pin_project! {
32
33 pub struct Timeout<F> {
34 #[pin]
35 future: F,
36 deadline: Instant,
37 }
38}
39
40impl<F> Timeout<F> {
41 fn new(future: F, duration: Duration) -> Self {
42 Self {
43 future,
44 deadline: Instant::now() + duration,
45 }
46 }
47}
48
49impl<F: Future> Future for Timeout<F> {
50 type Output = Result<F::Output, TimeoutError>;
51
52 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
53 let this = self.project();
54 if let Poll::Ready(v) = this.future.poll(cx) {
55 return Poll::Ready(Ok(v));
56 }
57 if Instant::now() >= *this.deadline {
58 return Poll::Ready(Err(TimeoutError));
59 }
60
61 let waker = cx.waker().clone();
62 let wait = (*this.deadline).saturating_duration_since(Instant::now());
63 let wait = wait.min(Duration::from_millis(10));
64 std::thread::spawn(move || {
65 std::thread::sleep(wait);
66 waker.wake();
67 });
68 Poll::Pending
69 }
70}
71
72pub fn timeout<F>(duration: Duration, future: F) -> Timeout<F>
73where
74 F: Future,
75{
76 Timeout::new(future, duration)
77}
78
79pub fn with_timeout<F>(duration: Duration, future: F) -> Timeout<F>
80where
81 F: Future,
82{
83 timeout(duration, future)
84}
85
86#[derive(Clone, Copy, Debug)]
87pub struct Interval {
88 period: Duration,
89 next: Instant,
90}
91
92impl Interval {
93 pub fn new(period: Duration) -> Self {
94 Self {
95 period: period.max(Duration::from_millis(1)),
96 next: Instant::now() + period.max(Duration::from_millis(1)),
97 }
98 }
99
100 pub async fn tick(&mut self) {
101 let now = Instant::now();
102 if self.next > now {
103 std::thread::sleep(self.next - now);
104 }
105 self.next = Instant::now() + self.period;
106 }
107
108 pub fn period(&self) -> Duration {
109 self.period
110 }
111}
112
113pin_project! {
114
115 pub struct Ticks {
116 interval: Interval,
117 }
118}
119
120impl Ticks {
121 pub fn new(period: Duration) -> Self {
122 Self {
123 interval: Interval::new(period),
124 }
125 }
126}
127
128impl Stream for Ticks {
129 type Item = Instant;
130
131 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
132 let this = self.project();
133 let now = Instant::now();
134 if now >= this.interval.next {
135 this.interval.next = now + this.interval.period;
136 return Poll::Ready(Some(now));
137 }
138 let waker = cx.waker().clone();
139 let wait = this.interval.next.saturating_duration_since(now);
140 std::thread::spawn(move || {
141 std::thread::sleep(wait.min(Duration::from_millis(50)));
142 waker.wake();
143 });
144 Poll::Pending
145 }
146}
147
148pub fn ticks(period: Duration) -> Ticks {
149 Ticks::new(period)
150}
151
152#[derive(Clone, Debug, Default)]
153pub struct Debouncer {
154 generation: std::rc::Rc<std::cell::Cell<u64>>,
155}
156
157impl Debouncer {
158 pub fn new() -> Self {
159 Self {
160 generation: std::rc::Rc::new(std::cell::Cell::new(0)),
161 }
162 }
163
164 pub fn request(&self) -> u64 {
165 let next = self.generation.get() + 1;
166 self.generation.set(next);
167 next
168 }
169
170 pub fn is_latest(&self, generation: u64) -> bool {
171 generation == self.generation.get()
172 }
173
174 pub fn invalidate(&self) {
175 self.generation.set(self.generation.get() + 1);
176 }
177}
178
179#[derive(Clone, Debug)]
180pub struct Throttler {
181 min_interval: Duration,
182 last: std::rc::Rc<std::cell::Cell<Option<Instant>>>,
183}
184
185impl Throttler {
186 pub fn new(min_interval: Duration) -> Self {
187 Self {
188 min_interval,
189 last: std::rc::Rc::new(std::cell::Cell::new(None)),
190 }
191 }
192
193 pub fn should_run(&self) -> bool {
194 let now = Instant::now();
195 let last = self.last.get();
196 match last {
197 None => {
198 self.last.set(Some(now));
199 true
200 }
201 Some(t) if now.duration_since(t) >= self.min_interval => {
202 self.last.set(Some(now));
203 true
204 }
205 _ => false,
206 }
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn sleep_zero_returns_fast() {
216 let t = Instant::now();
217 pollster::block_on(sleep(Duration::ZERO));
218 assert!(t.elapsed() < Duration::from_secs(1));
219 }
220
221 #[test]
222 fn timeout_ok_when_fast() {
223 let out = pollster::block_on(timeout(Duration::from_secs(2), async { 7 }));
224 assert_eq!(out, Ok(7));
225 }
226
227 #[test]
228 fn timeout_errors_when_slow() {
229 let out = pollster::block_on(timeout(
230 Duration::from_millis(20),
231 std::future::pending::<u32>(),
232 ));
233 assert_eq!(out, Err(TimeoutError));
234 }
235
236 #[test]
237 fn interval_ticks_advance() {
238 let mut iv = Interval::new(Duration::from_millis(5));
239 assert_eq!(iv.period(), Duration::from_millis(5));
240 pollster::block_on(iv.tick());
241 pollster::block_on(iv.tick());
242 }
243}