Use `dispatch2` crate (#50171)

John Tur created

Release Notes:

- N/A

Change summary

Cargo.lock                            |  8 ++-
crates/gpui_macos/Cargo.toml          |  4 +-
crates/gpui_macos/build.rs            | 35 -------------------
crates/gpui_macos/src/dispatch.h      |  2 -
crates/gpui_macos/src/dispatcher.rs   | 51 +++++++---------------------
crates/gpui_macos/src/display_link.rs | 50 ++++++++++------------------
crates/gpui_macos/src/platform.rs     | 17 ++------
crates/gpui_macos/src/window.rs       | 40 ++++++++++-----------
8 files changed, 62 insertions(+), 145 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5001,11 +5001,13 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
 
 [[package]]
 name = "dispatch2"
-version = "0.3.0"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
+checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
 dependencies = [
  "bitflags 2.10.0",
+ "block2",
+ "libc",
  "objc2",
 ]
 
@@ -7677,7 +7679,6 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "async-task",
- "bindgen 0.71.1",
  "block",
  "cbindgen",
  "cocoa 0.26.0",
@@ -7689,6 +7690,7 @@ dependencies = [
  "core-video",
  "ctor",
  "derive_more 0.99.20",
+ "dispatch2",
  "etagere",
  "foreign-types 0.5.0",
  "futures 0.3.31",

crates/gpui_macos/Cargo.toml 🔗

@@ -34,6 +34,7 @@ core-text = "21"
 core-video.workspace = true
 ctor.workspace = true
 derive_more.workspace = true
+dispatch2 = "0.3.1"
 etagere = "0.2"
 # WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
 font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "110523127440aefb11ce0cf280ae7c5071337ec5", package = "zed-font-kit", version = "0.14.1-zed", optional = true }
@@ -57,6 +58,5 @@ util.workspace = true
 uuid.workspace = true
 
 [target.'cfg(target_os = "macos")'.build-dependencies]
-bindgen = "0.71"
 cbindgen = { version = "0.28.0", default-features = false }
-gpui.workspace = true
+gpui.workspace = true

crates/gpui_macos/build.rs 🔗

@@ -15,8 +15,6 @@ mod macos_build {
     use cbindgen::Config;
 
     pub fn run() {
-        generate_dispatch_bindings();
-
         let header_path = generate_shader_bindings();
 
         #[cfg(feature = "runtime_shaders")]
@@ -25,39 +23,6 @@ mod macos_build {
         compile_metal_shaders(&header_path);
     }
 
-    fn generate_dispatch_bindings() {
-        println!("cargo:rustc-link-lib=framework=System");
-
-        let bindings = bindgen::Builder::default()
-            .header("src/dispatch.h")
-            .allowlist_var("_dispatch_main_q")
-            .allowlist_var("_dispatch_source_type_data_add")
-            .allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
-            .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
-            .allowlist_var("DISPATCH_QUEUE_PRIORITY_LOW")
-            .allowlist_var("DISPATCH_TIME_NOW")
-            .allowlist_function("dispatch_get_global_queue")
-            .allowlist_function("dispatch_async_f")
-            .allowlist_function("dispatch_after_f")
-            .allowlist_function("dispatch_time")
-            .allowlist_function("dispatch_source_merge_data")
-            .allowlist_function("dispatch_source_create")
-            .allowlist_function("dispatch_source_set_event_handler_f")
-            .allowlist_function("dispatch_resume")
-            .allowlist_function("dispatch_suspend")
-            .allowlist_function("dispatch_source_cancel")
-            .allowlist_function("dispatch_set_context")
-            .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
-            .layout_tests(false)
-            .generate()
-            .expect("unable to generate bindings");
-
-        let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
-        bindings
-            .write_to_file(out_path.join("dispatch_sys.rs"))
-            .expect("couldn't write dispatch bindings");
-    }
-
     fn generate_shader_bindings() -> PathBuf {
         let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
 

crates/gpui_macos/src/dispatcher.rs 🔗

@@ -1,7 +1,4 @@
-#![allow(non_upper_case_globals)]
-#![allow(non_camel_case_types)]
-#![allow(non_snake_case)]
-
+use dispatch2::{DispatchQueue, DispatchQueueGlobalPriority, DispatchTime, GlobalQueueIdentifier};
 use gpui::{
     GLOBAL_THREAD_TIMINGS, PlatformDispatcher, Priority, RunnableMeta, RunnableVariant,
     THREAD_TIMINGS, TaskTiming, ThreadTaskTimings,
@@ -26,21 +23,10 @@ use objc::{
 };
 use std::{
     ffi::c_void,
-    ptr::{NonNull, addr_of},
+    ptr::NonNull,
     time::{Duration, Instant},
 };
 
-/// All items in the generated file are marked as pub, so we're gonna wrap it in a separate mod to prevent
-/// these pub items from leaking into public API.
-pub(crate) mod dispatch_sys {
-    include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
-}
-
-use dispatch_sys::*;
-pub(crate) fn dispatch_get_main_queue() -> dispatch_queue_t {
-    addr_of!(_dispatch_main_q) as *const _ as dispatch_queue_t
-}
-
 pub(crate) struct MacDispatcher;
 
 impl MacDispatcher {
@@ -89,43 +75,32 @@ impl PlatformDispatcher for MacDispatcher {
             Priority::RealtimeAudio => {
                 panic!("RealtimeAudio priority should use spawn_realtime, not dispatch")
             }
-            Priority::High => DISPATCH_QUEUE_PRIORITY_HIGH as isize,
-            Priority::Medium => DISPATCH_QUEUE_PRIORITY_DEFAULT as isize,
-            Priority::Low => DISPATCH_QUEUE_PRIORITY_LOW as isize,
+            Priority::High => DispatchQueueGlobalPriority::High,
+            Priority::Medium => DispatchQueueGlobalPriority::Default,
+            Priority::Low => DispatchQueueGlobalPriority::Low,
         };
 
         unsafe {
-            dispatch_async_f(
-                dispatch_get_global_queue(queue_priority, 0),
-                context,
-                Some(trampoline as unsafe extern "C" fn(*mut c_void)),
-            );
+            DispatchQueue::global_queue(GlobalQueueIdentifier::Priority(queue_priority))
+                .exec_async_f(context, trampoline);
         }
     }
 
     fn dispatch_on_main_thread(&self, runnable: RunnableVariant, _priority: Priority) {
         let context = runnable.into_raw().as_ptr() as *mut c_void;
         unsafe {
-            dispatch_async_f(
-                dispatch_get_main_queue(),
-                context,
-                Some(trampoline as unsafe extern "C" fn(*mut c_void)),
-            );
+            DispatchQueue::main().exec_async_f(context, trampoline);
         }
     }
 
     fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant) {
         let context = runnable.into_raw().as_ptr() as *mut c_void;
+        let queue = DispatchQueue::global_queue(GlobalQueueIdentifier::Priority(
+            DispatchQueueGlobalPriority::High,
+        ));
+        let when = DispatchTime::NOW.time(duration.as_nanos() as i64);
         unsafe {
-            let queue =
-                dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH.try_into().unwrap(), 0);
-            let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64);
-            dispatch_after_f(
-                when,
-                queue,
-                context,
-                Some(trampoline as unsafe extern "C" fn(*mut c_void)),
-            );
+            DispatchQueue::exec_after_f(when, &queue, context, trampoline);
         }
     }
 

crates/gpui_macos/src/display_link.rs 🔗

@@ -1,26 +1,21 @@
-use crate::{
-    dispatch_get_main_queue,
-    dispatcher::dispatch_sys::{
-        _dispatch_source_type_data_add, dispatch_resume, dispatch_set_context,
-        dispatch_source_cancel, dispatch_source_create, dispatch_source_merge_data,
-        dispatch_source_set_event_handler_f, dispatch_source_t, dispatch_suspend,
-    },
-};
 use anyhow::Result;
 use core_graphics::display::CGDirectDisplayID;
+use dispatch2::{
+    _dispatch_source_type_data_add, DispatchObject, DispatchQueue, DispatchRetained, DispatchSource,
+};
 use std::ffi::c_void;
 use util::ResultExt;
 
 pub struct DisplayLink {
     display_link: Option<sys::DisplayLink>,
-    frame_requests: dispatch_source_t,
+    frame_requests: DispatchRetained<DispatchSource>,
 }
 
 impl DisplayLink {
     pub fn new(
         display_id: CGDirectDisplayID,
         data: *mut c_void,
-        callback: unsafe extern "C" fn(*mut c_void),
+        callback: extern "C" fn(*mut c_void),
     ) -> Result<DisplayLink> {
         unsafe extern "C" fn display_link_callback(
             _display_link_out: *mut sys::CVDisplayLink,
@@ -31,31 +26,26 @@ impl DisplayLink {
             frame_requests: *mut c_void,
         ) -> i32 {
             unsafe {
-                let frame_requests = frame_requests as dispatch_source_t;
-                dispatch_source_merge_data(frame_requests, 1);
+                let frame_requests = &*(frame_requests as *const DispatchSource);
+                frame_requests.merge_data(1);
                 0
             }
         }
 
         unsafe {
-            let frame_requests = dispatch_source_create(
-                &_dispatch_source_type_data_add,
+            let frame_requests = DispatchSource::new(
+                &raw const _dispatch_source_type_data_add as *mut _,
                 0,
                 0,
-                dispatch_get_main_queue(),
-            );
-            dispatch_set_context(
-                crate::dispatch_sys::dispatch_object_t {
-                    _ds: frame_requests,
-                },
-                data,
+                Some(DispatchQueue::main()),
             );
-            dispatch_source_set_event_handler_f(frame_requests, Some(callback));
+            frame_requests.set_context(data);
+            frame_requests.set_event_handler_f(callback);
 
             let display_link = sys::DisplayLink::new(
                 display_id,
                 display_link_callback,
-                frame_requests as *mut c_void,
+                &*frame_requests as *const DispatchSource as *mut c_void,
             )?;
 
             Ok(Self {
@@ -67,9 +57,7 @@ impl DisplayLink {
 
     pub fn start(&mut self) -> Result<()> {
         unsafe {
-            dispatch_resume(crate::dispatch_sys::dispatch_object_t {
-                _ds: self.frame_requests,
-            });
+            self.frame_requests.resume();
             self.display_link.as_mut().unwrap().start()?;
         }
         Ok(())
@@ -77,9 +65,7 @@ impl DisplayLink {
 
     pub fn stop(&mut self) -> Result<()> {
         unsafe {
-            dispatch_suspend(crate::dispatch_sys::dispatch_object_t {
-                _ds: self.frame_requests,
-            });
+            self.frame_requests.suspend();
             self.display_link.as_mut().unwrap().stop()?;
         }
         Ok(())
@@ -97,9 +83,9 @@ impl Drop for DisplayLink {
         //
         // We might also want to upgrade to CADisplayLink, but that requires dropping old macOS support.
         std::mem::forget(self.display_link.take());
-        unsafe {
-            dispatch_source_cancel(self.frame_requests);
-        }
+        self.frame_requests.cancel();
+        // A suspended DispatchSource cannot be destroyed.
+        self.frame_requests.resume();
     }
 }
 

crates/gpui_macos/src/platform.rs 🔗

@@ -24,6 +24,7 @@ use core_foundation::{
     string::{CFString, CFStringRef},
 };
 use ctor::ctor;
+use dispatch2::DispatchQueue;
 use futures::channel::oneshot;
 use gpui::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor,
@@ -493,13 +494,11 @@ impl Platform for MacPlatform {
         // this, we make quitting the application asynchronous so that we aren't holding borrows to
         // the app state on the stack when we actually terminate the app.
 
-        use crate::dispatcher::{dispatch_get_main_queue, dispatch_sys::dispatch_async_f};
-
         unsafe {
-            dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
+            DispatchQueue::main().exec_async_f(ptr::null_mut(), quit);
         }
 
-        unsafe extern "C" fn quit(_: *mut c_void) {
+        extern "C" fn quit(_: *mut c_void) {
             unsafe {
                 let app = NSApplication::sharedApplication(nil);
                 let _: () = msg_send![app, terminate: nil];
@@ -1261,19 +1260,13 @@ extern "C" fn on_thermal_state_change(this: &mut Object, _: Sel, _: id) {
     // Defer to the next run loop iteration to avoid re-entrant borrows of the App RefCell,
     // as NSNotificationCenter delivers this notification synchronously and it may fire while
     // the App is already borrowed (same pattern as quit() above).
-    use crate::dispatcher::{dispatch_get_main_queue, dispatch_sys::dispatch_async_f};
-
     let platform = unsafe { get_mac_platform(this) };
     let platform_ptr = platform as *const MacPlatform as *mut c_void;
     unsafe {
-        dispatch_async_f(
-            dispatch_get_main_queue(),
-            platform_ptr,
-            Some(on_thermal_state_change),
-        );
+        DispatchQueue::main().exec_async_f(platform_ptr, on_thermal_state_change);
     }
 
-    unsafe extern "C" fn on_thermal_state_change(context: *mut c_void) {
+    extern "C" fn on_thermal_state_change(context: *mut c_void) {
         let platform = unsafe { &*(context as *const MacPlatform) };
         let mut lock = platform.0.lock();
         if let Some(mut callback) = lock.on_thermal_state_change.take() {

crates/gpui_macos/src/window.rs 🔗

@@ -1,7 +1,6 @@
 use crate::{
-    BoolExt, DisplayLink, MacDisplay, NSRange, NSStringExt, dispatch_get_main_queue,
-    dispatcher::dispatch_sys::dispatch_async_f, events::platform_input_from_native, ns_string,
-    renderer,
+    BoolExt, DisplayLink, MacDisplay, NSRange, NSStringExt, events::platform_input_from_native,
+    ns_string, renderer,
 };
 #[cfg(any(test, feature = "test-support"))]
 use anyhow::Result;
@@ -22,6 +21,7 @@ use cocoa::{
         NSUserDefaults,
     },
 };
+use dispatch2::DispatchQueue;
 use gpui::{
     AnyWindowHandle, BackgroundExecutor, Bounds, Capslock, ExternalPaths, FileDropEvent,
     ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
@@ -1050,34 +1050,32 @@ impl PlatformWindow for MacWindow {
 
     fn merge_all_windows(&self) {
         let native_window = self.0.lock().native_window;
-        unsafe extern "C" fn merge_windows_async(context: *mut std::ffi::c_void) {
-            let native_window = context as id;
-            let _: () = msg_send![native_window, mergeAllWindows:nil];
+        extern "C" fn merge_windows_async(context: *mut std::ffi::c_void) {
+            unsafe {
+                let native_window = context as id;
+                let _: () = msg_send![native_window, mergeAllWindows:nil];
+            }
         }
 
         unsafe {
-            dispatch_async_f(
-                dispatch_get_main_queue(),
-                native_window as *mut std::ffi::c_void,
-                Some(merge_windows_async),
-            );
+            DispatchQueue::main()
+                .exec_async_f(native_window as *mut std::ffi::c_void, merge_windows_async);
         }
     }
 
     fn move_tab_to_new_window(&self) {
         let native_window = self.0.lock().native_window;
-        unsafe extern "C" fn move_tab_async(context: *mut std::ffi::c_void) {
-            let native_window = context as id;
-            let _: () = msg_send![native_window, moveTabToNewWindow:nil];
-            let _: () = msg_send![native_window, makeKeyAndOrderFront: nil];
+        extern "C" fn move_tab_async(context: *mut std::ffi::c_void) {
+            unsafe {
+                let native_window = context as id;
+                let _: () = msg_send![native_window, moveTabToNewWindow:nil];
+                let _: () = msg_send![native_window, makeKeyAndOrderFront: nil];
+            }
         }
 
         unsafe {
-            dispatch_async_f(
-                dispatch_get_main_queue(),
-                native_window as *mut std::ffi::c_void,
-                Some(move_tab_async),
-            );
+            DispatchQueue::main()
+                .exec_async_f(native_window as *mut std::ffi::c_void, move_tab_async);
         }
     }
 
@@ -2252,7 +2250,7 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
     }
 }
 
-unsafe extern "C" fn step(view: *mut c_void) {
+extern "C" fn step(view: *mut c_void) {
     let view = view as id;
     let window_state = unsafe { get_window_state(&*view) };
     let mut lock = window_state.lock();