From b06522e978b5ed24bcc2cf07a6de794179d69176 Mon Sep 17 00:00:00 2001 From: John Tur Date: Fri, 27 Feb 2026 21:54:15 -0500 Subject: [PATCH] Use `dispatch2` crate (#50171) Release Notes: - N/A --- 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(-) delete mode 100644 crates/gpui_macos/src/dispatch.h diff --git a/Cargo.lock b/Cargo.lock index d37563dc8595c72f71901dd84cdd4fca5a34ee84..6ae4f57301f2882e7f5e66c5960078393d2ac2de 100644 --- a/Cargo.lock +++ b/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", diff --git a/crates/gpui_macos/Cargo.toml b/crates/gpui_macos/Cargo.toml index 4aedb1f4f1bed02e22f0dc6a881d60cc39ddd3a1..06e5d0e7321af523a249f19ec0d5ac50e2da5d3f 100644 --- a/crates/gpui_macos/Cargo.toml +++ b/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 \ No newline at end of file +gpui.workspace = true diff --git a/crates/gpui_macos/build.rs b/crates/gpui_macos/build.rs index 32dfc571d257495c9c0a8cae54bc9fb567b51489..d5c1893f4ce18190a546aed1a708685cf66dc0e9 100644 --- a/crates/gpui_macos/build.rs +++ b/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"); diff --git a/crates/gpui_macos/src/dispatch.h b/crates/gpui_macos/src/dispatch.h deleted file mode 100644 index 54f3818738042b00938ad566ec0269fc0d80241d..0000000000000000000000000000000000000000 --- a/crates/gpui_macos/src/dispatch.h +++ /dev/null @@ -1,2 +0,0 @@ -#include -#include diff --git a/crates/gpui_macos/src/dispatcher.rs b/crates/gpui_macos/src/dispatcher.rs index 755016e44be84f585631fbf311ef499adfc69367..07638639e4bf5d3f002c1babfc213bc330e63dce 100644 --- a/crates/gpui_macos/src/dispatcher.rs +++ b/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); } } diff --git a/crates/gpui_macos/src/display_link.rs b/crates/gpui_macos/src/display_link.rs index b086cc1b12182db661e5fa1cb82b671c7fd5b8bc..bd1c21ca5c063b2ed9fa79d939f205698023f42b 100644 --- a/crates/gpui_macos/src/display_link.rs +++ b/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, - frame_requests: dispatch_source_t, + frame_requests: DispatchRetained, } 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 { 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(); } } diff --git a/crates/gpui_macos/src/platform.rs b/crates/gpui_macos/src/platform.rs index c982f6da191f6b657e51238d8b6ac3d11f724149..d9c22cbea0354caff9bd5dd80d7ea98fa7e891de 100644 --- a/crates/gpui_macos/src/platform.rs +++ b/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() { diff --git a/crates/gpui_macos/src/window.rs b/crates/gpui_macos/src/window.rs index 87cd5ee21d5e448ee43b604657ddbe89e705035b..456ee31ac3b03780e68267621d66435b1ceab4a9 100644 --- a/crates/gpui_macos/src/window.rs +++ b/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();