1use core::future::Future;
17use core::pin::Pin;
18use core::task::{Context, Poll};
19use core::time::Duration;
20
21use futures::stream::Stream;
22use pin_project_lite::pin_project;
23
24pub mod drivers;
25mod missed_tick;
26mod types;
27
28pub use missed_tick::MissedTickBehavior;
33pub use types::{Driver, Instant};
34
35include!(concat!(env!("OUT_DIR"), "/proxima_time_bound_driver.rs"));
36
37#[must_use]
41pub fn now() -> Instant {
42 BOUND_DRIVER.now()
43}
44
45#[derive(Debug, thiserror::Error)]
48#[error("deadline has elapsed")]
49pub struct Elapsed(());
50
51pub struct Sleep {
54 deadline: Instant,
55}
56
57impl Sleep {
58 #[must_use]
60 pub fn until(deadline: Instant) -> Self {
61 Self { deadline }
62 }
63
64 #[must_use]
66 pub fn deadline(&self) -> Instant {
67 self.deadline
68 }
69}
70
71impl Future for Sleep {
72 type Output = ();
73 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<()> {
74 if BOUND_DRIVER.now() >= self.deadline {
75 Poll::Ready(())
76 } else {
77 BOUND_DRIVER.schedule_wake(self.deadline, context.waker().clone());
78 Poll::Pending
79 }
80 }
81}
82
83#[must_use]
86pub fn sleep(duration: Duration) -> Sleep {
87 Sleep::until(now() + duration)
88}
89
90#[must_use]
94pub fn sleep_until(deadline: Instant) -> Sleep {
95 Sleep::until(deadline)
96}
97
98pin_project! {
99 struct Timeout<Fut> {
105 #[pin]
106 future: Fut,
107 deadline: Instant,
108 }
109}
110
111impl<Fut: Future> Future for Timeout<Fut> {
112 type Output = Result<Fut::Output, Elapsed>;
113 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
114 let this = self.project();
115
116 if let Poll::Ready(value) = this.future.poll(context) {
117 return Poll::Ready(Ok(value));
118 }
119
120 if BOUND_DRIVER.now() >= *this.deadline {
121 return Poll::Ready(Err(Elapsed(())));
122 }
123
124 BOUND_DRIVER.schedule_wake(*this.deadline, context.waker().clone());
125 Poll::Pending
126 }
127}
128
129pub fn timeout<Fut>(
132 duration: Duration,
133 future: Fut,
134) -> impl Future<Output = Result<Fut::Output, Elapsed>>
135where
136 Fut: Future,
137{
138 Timeout {
139 future,
140 deadline: now() + duration,
141 }
142}
143
144pub fn timeout_at<Fut>(
147 deadline: Instant,
148 future: Fut,
149) -> impl Future<Output = Result<Fut::Output, Elapsed>>
150where
151 Fut: Future,
152{
153 Timeout { future, deadline }
154}
155
156#[must_use]
161pub fn interval(period: Duration) -> Interval {
162 Interval {
163 period,
164 next_deadline: None,
165 missed_tick_behavior: MissedTickBehavior::default(),
166 }
167}
168
169#[must_use]
172pub fn interval_at(start: Instant, period: Duration) -> Interval {
173 Interval {
174 period,
175 next_deadline: Some(start),
176 missed_tick_behavior: MissedTickBehavior::default(),
177 }
178}
179
180pub struct Interval {
183 period: Duration,
184 next_deadline: Option<Instant>,
187 missed_tick_behavior: MissedTickBehavior,
188}
189
190impl Interval {
191 pub fn set_missed_tick_behavior(&mut self, behavior: MissedTickBehavior) {
193 self.missed_tick_behavior = behavior;
194 }
195
196 #[must_use]
198 pub fn missed_tick_behavior(&self) -> MissedTickBehavior {
199 self.missed_tick_behavior
200 }
201
202 #[must_use]
204 pub fn period(&self) -> Duration {
205 self.period
206 }
207
208 pub fn tick(&mut self) -> Tick<'_> {
210 Tick { interval: self }
211 }
212}
213
214pub struct Tick<'a> {
216 interval: &'a mut Interval,
217}
218
219impl Future for Tick<'_> {
220 type Output = Instant;
221 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Instant> {
222 let now = now();
223 let interval = &mut *self.interval;
224
225 if interval.next_deadline.is_none() {
227 interval.next_deadline = Some(now + interval.period);
228 return Poll::Ready(now);
229 }
230
231 let Some(deadline) = interval.next_deadline else {
232 return Poll::Pending;
233 };
234
235 if now < deadline {
236 BOUND_DRIVER.schedule_wake(deadline, context.waker().clone());
237 return Poll::Pending;
238 }
239
240 let fired_at = deadline;
242 let next_deadline = match interval.missed_tick_behavior {
243 MissedTickBehavior::Burst => fired_at + interval.period,
244 MissedTickBehavior::Delay => now + interval.period,
245 MissedTickBehavior::Skip => {
246 let elapsed_periods = now
247 .saturating_duration_since(fired_at)
248 .as_nanos()
249 .checked_div(interval.period.as_nanos().max(1))
250 .unwrap_or(0);
251 let skip = u32::try_from(elapsed_periods).unwrap_or(u32::MAX);
252 fired_at + interval.period.saturating_mul(skip.saturating_add(1))
253 }
254 };
255 interval.next_deadline = Some(next_deadline);
256 Poll::Ready(fired_at)
257 }
258}
259
260impl Stream for Interval {
261 type Item = ();
262 fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<()>> {
263 let interval: &mut Interval = &mut self;
264 let mut tick = Tick { interval };
265 match Pin::new(&mut tick).poll(context) {
266 Poll::Ready(_instant) => Poll::Ready(Some(())),
267 Poll::Pending => Poll::Pending,
268 }
269 }
270}
271
272#[cfg(all(test, feature = "time-driver-std-thread"))]
277#[allow(clippy::unwrap_used, clippy::expect_used)]
278mod tests {
279 use super::*;
280 use futures::executor::block_on;
281 use futures::stream::StreamExt;
282
283 #[test]
284 fn sleep_resolves_after_duration() {
285 let start = now();
286 block_on(sleep(Duration::from_millis(30)));
287 assert!(now().saturating_duration_since(start) >= Duration::from_millis(25));
288 }
289
290 #[test]
291 fn timeout_returns_ok_when_future_completes_in_time() {
292 let outcome = block_on(timeout(Duration::from_millis(100), async { 7_u32 }));
293 assert_eq!(outcome.expect("ok"), 7);
294 }
295
296 #[test]
297 fn timeout_returns_elapsed_when_timer_fires_first() {
298 let outcome: Result<u32, Elapsed> = block_on(timeout(Duration::from_millis(10), async {
299 sleep(Duration::from_secs(1)).await;
300 7_u32
301 }));
302 assert!(outcome.is_err());
303 }
304
305 #[test]
306 fn interval_emits_first_tick_immediately_then_periodic() {
307 block_on(async {
308 let mut interval = interval(Duration::from_millis(20));
309 let start = now();
310 for _ in 0..3_u32 {
311 interval.next().await.expect("tick");
312 }
313 assert!(now().saturating_duration_since(start) >= Duration::from_millis(30));
315 });
316 }
317}