platform_scheduler.rs

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