1mod clock;
2mod executor;
3mod test_scheduler;
4#[cfg(test)]
5mod tests;
6
7pub use clock::*;
8pub use executor::*;
9pub use test_scheduler::*;
10
11use async_task::Runnable;
12use futures::channel::oneshot;
13use std::{
14 future::Future,
15 panic::Location,
16 pin::Pin,
17 sync::{
18 Arc,
19 atomic::{AtomicBool, Ordering},
20 },
21 task::{Context, Poll},
22 time::Duration,
23};
24
25/// Task priority for background tasks.
26///
27/// Higher priority tasks are more likely to be scheduled before lower priority tasks,
28/// but this is not a strict guarantee - the scheduler may interleave tasks of different
29/// priorities to prevent starvation.
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
31#[repr(u8)]
32pub enum Priority {
33 /// Realtime priority
34 ///
35 /// Spawning a task with this priority will spin it off on a separate thread dedicated just to that task. Only use for audio.
36 RealtimeAudio,
37 /// High priority - use for tasks critical to user experience/responsiveness.
38 High,
39 /// Medium priority - suitable for most use cases.
40 #[default]
41 Medium,
42 /// Low priority - use for background work that can be deprioritized.
43 Low,
44}
45
46impl Priority {
47 /// Returns the relative probability weight for this priority level.
48 /// Used by schedulers to determine task selection probability.
49 pub const fn weight(self) -> u32 {
50 match self {
51 Priority::High => 60,
52 Priority::Medium => 30,
53 Priority::Low => 10,
54 // realtime priorities are not considered for probability scheduling
55 Priority::RealtimeAudio => 0,
56 }
57 }
58}
59
60/// Metadata attached to runnables for debugging and profiling.
61#[derive(Clone)]
62pub struct RunnableMeta {
63 /// The source location where the task was spawned.
64 pub location: &'static Location<'static>,
65 /// Shared flag indicating whether the scheduler has been closed.
66 /// When true, tasks should be dropped without running.
67 pub closed: Arc<AtomicBool>,
68}
69
70impl RunnableMeta {
71 /// Returns true if the scheduler has been closed and this task should not run.
72 pub fn is_closed(&self) -> bool {
73 self.closed.load(Ordering::SeqCst)
74 }
75}
76
77impl std::fmt::Debug for RunnableMeta {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 f.debug_struct("RunnableMeta")
80 .field("location", &self.location)
81 .field("closed", &self.is_closed())
82 .finish()
83 }
84}
85
86pub trait Scheduler: Send + Sync {
87 /// Block until the given future completes or timeout occurs.
88 ///
89 /// Returns `true` if the future completed, `false` if it timed out.
90 /// The future is passed as a pinned mutable reference so the caller
91 /// retains ownership and can continue polling or return it on timeout.
92 fn block(
93 &self,
94 session_id: Option<SessionId>,
95 future: Pin<&mut dyn Future<Output = ()>>,
96 timeout: Option<Duration>,
97 ) -> bool;
98
99 fn schedule_foreground(&self, session_id: SessionId, runnable: Runnable<RunnableMeta>);
100
101 /// Schedule a background task with the given priority.
102 fn schedule_background_with_priority(
103 &self,
104 runnable: Runnable<RunnableMeta>,
105 priority: Priority,
106 );
107
108 /// Schedule a background task with default (medium) priority.
109 fn schedule_background(&self, runnable: Runnable<RunnableMeta>) {
110 self.schedule_background_with_priority(runnable, Priority::default());
111 }
112
113 fn timer(&self, timeout: Duration) -> Timer;
114 fn clock(&self) -> Arc<dyn Clock>;
115
116 fn as_test(&self) -> Option<&TestScheduler> {
117 None
118 }
119}
120
121#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
122pub struct SessionId(u16);
123
124impl SessionId {
125 pub fn new(id: u16) -> Self {
126 SessionId(id)
127 }
128}
129
130pub struct Timer(oneshot::Receiver<()>);
131
132impl Timer {
133 pub fn new(rx: oneshot::Receiver<()>) -> Self {
134 Timer(rx)
135 }
136}
137
138impl Future for Timer {
139 type Output = ();
140
141 fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> {
142 match Pin::new(&mut self.0).poll(cx) {
143 Poll::Ready(_) => Poll::Ready(()),
144 Poll::Pending => Poll::Pending,
145 }
146 }
147}