auralis_task/timer.rs
1//! Timer primitives for the Auralis executor.
2//!
3//! Provides [`sleep`], an async delay future that cooperatively yields
4//! to the executor for the given duration.
5
6use std::future::Future;
7use std::pin::Pin;
8use std::task::{Context, Poll};
9use std::time::Duration;
10
11use crate::executor;
12
13/// A future that completes after a given duration.
14///
15/// Created by [`sleep`]. On first poll it registers a timer with the
16/// executor and computes a deadline. Subsequent polls check the
17/// deadline — this makes the future robust against premature re-polls
18/// (e.g. from `select!` or executor wake-ups unrelated to the timer).
19pub struct SleepFuture {
20 registered: bool,
21 duration_ms: u64,
22 /// Deadline in milliseconds, computed on first poll.
23 deadline_ms: u64,
24}
25
26impl SleepFuture {
27 #[allow(clippy::cast_possible_truncation)]
28 pub(crate) fn new(duration: Duration) -> Self {
29 Self {
30 registered: false,
31 duration_ms: duration.as_millis() as u64,
32 deadline_ms: 0,
33 }
34 }
35}
36
37impl Future for SleepFuture {
38 type Output = ();
39
40 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
41 if self.registered {
42 let now = executor::current_time_ms();
43 // Without a TimeSource (now == 0), all timers fire on each
44 // flush; treat as expired.
45 if now == 0 || now >= self.deadline_ms {
46 return Poll::Ready(());
47 }
48 return Poll::Pending;
49 }
50 self.registered = true;
51
52 // Discover our task id from the executor's thread-local.
53 let task_id = executor::with_current_polling_task(|id| {
54 id.expect("timer::sleep must be called from within an auralis task")
55 });
56
57 let now = executor::current_time_ms();
58 self.deadline_ms = now.saturating_add(self.duration_ms);
59
60 // Return immediately for zero-duration sleeps or when the
61 // deadline has already passed (a TimeSource advanced past it).
62 if self.duration_ms == 0 || (now > 0 && self.deadline_ms <= now) {
63 return Poll::Ready(());
64 }
65
66 executor::Executor::schedule_timer(
67 &executor::current_executor_instance(),
68 self.deadline_ms,
69 task_id,
70 );
71
72 // Don't self-wake — the executor will re-poll this task when
73 // the timer expires (flush step 0). Without a TimeSource all
74 // timers expire on the next flush (equivalent to yield_now).
75 Poll::Pending
76 }
77}
78
79/// Pause the current task for at least `duration`.
80///
81/// # Panics
82///
83/// Panics if called outside of an auralis task context (i.e. not from
84/// within a future spawned via [`TaskScope::spawn`](crate::TaskScope::spawn)
85/// or [`spawn_global`](crate::spawn_global)).
86///
87/// # Example
88///
89/// ```rust,ignore
90/// use std::time::Duration;
91/// use auralis_task::timer;
92///
93/// scope.spawn(async {
94/// timer::sleep(Duration::from_millis(500)).await;
95/// // ... do something after delay
96/// });
97/// ```
98pub async fn sleep(duration: Duration) {
99 SleepFuture::new(duration).await;
100}