1use crate::{PlatformDispatcher, RunnableMeta};
2use async_task::Runnable;
3use chrono::{DateTime, Utc};
4use futures::channel::oneshot;
5use scheduler::{Clock, Priority, Scheduler, SessionId, TestScheduler, Timer};
6use std::{
7 future::Future,
8 pin::Pin,
9 sync::{
10 Arc,
11 atomic::{AtomicU16, Ordering},
12 },
13 task::{Context, Poll},
14 time::{Duration, Instant},
15};
16use waker_fn::waker_fn;
17
18/// A production implementation of [`Scheduler`] that wraps a [`PlatformDispatcher`].
19///
20/// This allows GPUI to use the scheduler crate's executor types with the platform's
21/// native dispatch mechanisms (e.g., Grand Central Dispatch on macOS).
22pub struct PlatformScheduler {
23 dispatcher: Arc<dyn PlatformDispatcher>,
24 clock: Arc<PlatformClock>,
25 next_session_id: AtomicU16,
26}
27
28impl PlatformScheduler {
29 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
30 Self {
31 dispatcher: dispatcher.clone(),
32 clock: Arc::new(PlatformClock { dispatcher }),
33 next_session_id: AtomicU16::new(0),
34 }
35 }
36
37 pub fn allocate_session_id(&self) -> SessionId {
38 SessionId::new(self.next_session_id.fetch_add(1, Ordering::SeqCst))
39 }
40}
41
42impl Scheduler for PlatformScheduler {
43 fn block(
44 &self,
45 _session_id: Option<SessionId>,
46 mut future: Pin<&mut dyn Future<Output = ()>>,
47 timeout: Option<Duration>,
48 ) -> bool {
49 let deadline = timeout.map(|t| Instant::now() + t);
50 let parker = parking::Parker::new();
51 let unparker = parker.unparker();
52 let waker = waker_fn(move || {
53 unparker.unpark();
54 });
55 let mut cx = Context::from_waker(&waker);
56
57 loop {
58 match future.as_mut().poll(&mut cx) {
59 Poll::Ready(()) => return true,
60 Poll::Pending => {
61 if let Some(deadline) = deadline {
62 let now = Instant::now();
63 if now >= deadline {
64 return false;
65 }
66 parker.park_timeout(deadline - now);
67 } else {
68 parker.park();
69 }
70 }
71 }
72 }
73 }
74
75 fn schedule_foreground(&self, _session_id: SessionId, runnable: Runnable<RunnableMeta>) {
76 self.dispatcher
77 .dispatch_on_main_thread(runnable, Priority::default());
78 }
79
80 fn schedule_background_with_priority(
81 &self,
82 runnable: Runnable<RunnableMeta>,
83 priority: Priority,
84 ) {
85 self.dispatcher.dispatch(runnable, priority);
86 }
87
88 fn timer(&self, duration: Duration) -> Timer {
89 use std::sync::{Arc, atomic::AtomicBool};
90
91 let (tx, rx) = oneshot::channel();
92 let dispatcher = self.dispatcher.clone();
93
94 // Create a runnable that will send the completion signal
95 let location = std::panic::Location::caller();
96 let closed = Arc::new(AtomicBool::new(false));
97 let (runnable, _task) = async_task::Builder::new()
98 .metadata(RunnableMeta { location, closed })
99 .spawn(
100 move |_| async move {
101 let _ = tx.send(());
102 },
103 move |runnable| {
104 dispatcher.dispatch_after(duration, runnable);
105 },
106 );
107 runnable.schedule();
108
109 Timer::new(rx)
110 }
111
112 fn clock(&self) -> Arc<dyn Clock> {
113 self.clock.clone()
114 }
115
116 fn as_test(&self) -> Option<&TestScheduler> {
117 None
118 }
119}
120
121/// A production clock that uses the platform dispatcher's time.
122struct PlatformClock {
123 dispatcher: Arc<dyn PlatformDispatcher>,
124}
125
126impl Clock for PlatformClock {
127 fn utc_now(&self) -> DateTime<Utc> {
128 Utc::now()
129 }
130
131 fn now(&self) -> Instant {
132 self.dispatcher.now()
133 }
134}