Skip to main content

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}