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