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 if let Poll::Ready(()) = future.as_mut().poll(&mut cx) {
57 return true;
58 }
59 loop {
60 match deadline {
61 Some(deadline) if !parker.park_deadline(deadline) && deadline <= Instant::now() => {
62 return false;
63 }
64 Some(_) => (),
65 None => parker.park(),
66 }
67 if let Poll::Ready(()) = future.as_mut().poll(&mut cx) {
68 break true;
69 }
70 }
71 }
72
73 fn schedule_foreground(&self, _session_id: SessionId, runnable: Runnable<RunnableMeta>) {
74 self.dispatcher
75 .dispatch_on_main_thread(runnable, Priority::default());
76 }
77
78 fn schedule_background_with_priority(
79 &self,
80 runnable: Runnable<RunnableMeta>,
81 priority: Priority,
82 ) {
83 self.dispatcher.dispatch(runnable, priority);
84 }
85
86 fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>) {
87 self.dispatcher.spawn_realtime(f);
88 }
89
90 fn timer(&self, duration: Duration) -> Timer {
91 use std::sync::{Arc, atomic::AtomicBool};
92
93 let (tx, rx) = oneshot::channel();
94 let dispatcher = self.dispatcher.clone();
95
96 // Create a runnable that will send the completion signal
97 let location = std::panic::Location::caller();
98 let closed = Arc::new(AtomicBool::new(false));
99 let (runnable, _task) = async_task::Builder::new()
100 .metadata(RunnableMeta { location, closed })
101 .spawn(
102 move |_| async move {
103 let _ = tx.send(());
104 },
105 move |runnable| {
106 dispatcher.dispatch_after(duration, runnable);
107 },
108 );
109 runnable.schedule();
110
111 Timer::new(rx)
112 }
113
114 fn clock(&self) -> Arc<dyn Clock> {
115 self.clock.clone()
116 }
117
118 fn as_test(&self) -> Option<&TestScheduler> {
119 None
120 }
121}
122
123/// A production clock that uses the platform dispatcher's time.
124struct PlatformClock {
125 dispatcher: Arc<dyn PlatformDispatcher>,
126}
127
128impl Clock for PlatformClock {
129 fn utc_now(&self) -> DateTime<Utc> {
130 Utc::now()
131 }
132
133 fn now(&self) -> Instant {
134 self.dispatcher.now()
135 }
136}