From e48262a7971a116301d5c78de5ef136d3f9f3572 Mon Sep 17 00:00:00 2001 From: Steven Sheldon Date: Sun, 19 Jan 2020 10:47:55 -0800 Subject: [PATCH] Simplify code by switching to higher-level dispatch APIs (#1409) * Switch to higher-level dispatch APIs * Inline all the functions * Switch to autoreleasepool from objc --- Cargo.toml | 2 +- src/platform_impl/macos/util/async.rs | 485 +++++++------------------- 2 files changed, 129 insertions(+), 358 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1948b92..4dc41ed5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ objc = "0.2.3" cocoa = "0.19.1" core-foundation = "0.6" core-graphics = "0.17.3" -dispatch = "0.1.4" +dispatch = "0.2.0" objc = "0.2.6" [target.'cfg(target_os = "macos")'.dependencies.core-video-sys] diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index 8aa8db9a..977ba328 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -1,20 +1,36 @@ use std::{ - os::raw::c_void, + ops::Deref, sync::{Mutex, Weak}, }; use cocoa::{ appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask}, base::{id, nil}, - foundation::{NSAutoreleasePool, NSPoint, NSSize, NSString}, + foundation::{NSPoint, NSSize, NSString}, }; -use dispatch::ffi::{dispatch_async_f, dispatch_get_main_queue, dispatch_sync_f}; +use dispatch::Queue; +use objc::rc::autoreleasepool; use crate::{ dpi::LogicalSize, platform_impl::platform::{ffi, util::IdRef, window::SharedState}, }; +// Unsafe wrapper type that allows us to dispatch things that aren't Send. +// This should *only* be used to dispatch to the main queue. +// While it is indeed not guaranteed that these types can safely be sent to +// other threads, we know that they're safe to use on the main thread. +struct MainThreadSafe(T); + +unsafe impl Send for MainThreadSafe {} + +impl Deref for MainThreadSafe { + type Target = T; + fn deref(&self) -> &T { + &self.0 + } +} + unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { ns_window.setStyleMask_(mask); // If we don't do this, key handling will break @@ -22,203 +38,55 @@ unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { ns_window.makeFirstResponder_(ns_view); } -struct SetStyleMaskData { - ns_window: id, - ns_view: id, - mask: NSWindowStyleMask, -} -impl SetStyleMaskData { - fn new_ptr(ns_window: id, ns_view: id, mask: NSWindowStyleMask) -> *mut Self { - Box::into_raw(Box::new(SetStyleMaskData { - ns_window, - ns_view, - mask, - })) - } -} -extern "C" fn set_style_mask_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetStyleMaskData; - { - let context = &*context_ptr; - set_style_mask(context.ns_window, context.ns_view, context.mask); - } - Box::from_raw(context_ptr); - } -} // Always use this function instead of trying to modify `styleMask` directly! // `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch. // Otherwise, this would vomit out errors about not being on the main thread // and fail to do anything. pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { - let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_style_mask_callback, - ); + let ns_window = MainThreadSafe(ns_window); + let ns_view = MainThreadSafe(ns_view); + Queue::main().exec_async(move || { + set_style_mask(*ns_window, *ns_view, mask); + }); } pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { - let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask); if msg_send![class!(NSThread), isMainThread] { - set_style_mask_callback(context as *mut _); + set_style_mask(ns_window, ns_view, mask); } else { - dispatch_sync_f( - dispatch_get_main_queue(), - context as *mut _, - set_style_mask_callback, - ); + let ns_window = MainThreadSafe(ns_window); + let ns_view = MainThreadSafe(ns_view); + Queue::main().exec_sync(move || { + set_style_mask(*ns_window, *ns_view, mask); + }) } } -struct SetContentSizeData { - ns_window: id, - size: LogicalSize, -} -impl SetContentSizeData { - fn new_ptr(ns_window: id, size: LogicalSize) -> *mut Self { - Box::into_raw(Box::new(SetContentSizeData { ns_window, size })) - } -} -extern "C" fn set_content_size_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetContentSizeData; - { - let context = &*context_ptr; - NSWindow::setContentSize_( - context.ns_window, - NSSize::new( - context.size.width as CGFloat, - context.size.height as CGFloat, - ), - ); - } - Box::from_raw(context_ptr); - } -} // `setContentSize:` isn't thread-safe either, though it doesn't log any errors // and just fails silently. Anyway, GCD to the rescue! pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize) { - let context = SetContentSizeData::new_ptr(ns_window, size); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_content_size_callback, - ); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + ns_window.setContentSize_(NSSize::new(size.width as CGFloat, size.height as CGFloat)); + }); } -struct SetFrameTopLeftPointData { - ns_window: id, - point: NSPoint, -} -impl SetFrameTopLeftPointData { - fn new_ptr(ns_window: id, point: NSPoint) -> *mut Self { - Box::into_raw(Box::new(SetFrameTopLeftPointData { ns_window, point })) - } -} -extern "C" fn set_frame_top_left_point_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetFrameTopLeftPointData; - { - let context = &*context_ptr; - NSWindow::setFrameTopLeftPoint_(context.ns_window, context.point); - } - Box::from_raw(context_ptr); - } -} // `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy // to log errors. pub unsafe fn set_frame_top_left_point_async(ns_window: id, point: NSPoint) { - let context = SetFrameTopLeftPointData::new_ptr(ns_window, point); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_frame_top_left_point_callback, - ); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + ns_window.setFrameTopLeftPoint_(point); + }); } -struct SetLevelData { - ns_window: id, - level: ffi::NSWindowLevel, -} -impl SetLevelData { - fn new_ptr(ns_window: id, level: ffi::NSWindowLevel) -> *mut Self { - Box::into_raw(Box::new(SetLevelData { ns_window, level })) - } -} -extern "C" fn set_level_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetLevelData; - { - let context = &*context_ptr; - context.ns_window.setLevel_(context.level as _); - } - Box::from_raw(context_ptr); - } -} // `setFrameTopLeftPoint:` isn't thread-safe, and fails silently. pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) { - let context = SetLevelData::new_ptr(ns_window, level); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_level_callback, - ); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + ns_window.setLevel_(level as _); + }); } -struct ToggleFullScreenData { - ns_window: id, - ns_view: id, - not_fullscreen: bool, - shared_state: Weak>, -} -impl ToggleFullScreenData { - fn new_ptr( - ns_window: id, - ns_view: id, - not_fullscreen: bool, - shared_state: Weak>, - ) -> *mut Self { - Box::into_raw(Box::new(ToggleFullScreenData { - ns_window, - ns_view, - not_fullscreen, - shared_state, - })) - } -} -extern "C" fn toggle_full_screen_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut ToggleFullScreenData; - { - let context = &*context_ptr; - - // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we - // set a normal style temporarily. The previous state will be - // restored in `WindowDelegate::window_did_exit_fullscreen`. - if context.not_fullscreen { - let curr_mask = context.ns_window.styleMask(); - let required = NSWindowStyleMask::NSTitledWindowMask - | NSWindowStyleMask::NSResizableWindowMask; - if !curr_mask.contains(required) { - set_style_mask(context.ns_window, context.ns_view, required); - if let Some(shared_state) = context.shared_state.upgrade() { - trace!("Locked shared state in `toggle_full_screen_callback`"); - let mut shared_state_lock = shared_state.lock().unwrap(); - (*shared_state_lock).saved_style = Some(curr_mask); - trace!("Unlocked shared state in `toggle_full_screen_callback`"); - } - } - } - // Window level must be restored from `CGShieldingWindowLevel() - // + 1` back to normal in order for `toggleFullScreen` to do - // anything - context.ns_window.setLevel_(0); - context.ns_window.toggleFullScreen_(nil); - } - Box::from_raw(context_ptr); - } -} // `toggleFullScreen` is thread-safe, but our additional logic to account for // window styles isn't. pub unsafe fn toggle_full_screen_async( @@ -227,91 +95,42 @@ pub unsafe fn toggle_full_screen_async( not_fullscreen: bool, shared_state: Weak>, ) { - let context = ToggleFullScreenData::new_ptr(ns_window, ns_view, not_fullscreen, shared_state); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - toggle_full_screen_callback, - ); -} - -extern "C" fn restore_display_mode_callback(screen: *mut c_void) { - unsafe { - let screen = Box::from_raw(screen as *mut u32); - ffi::CGRestorePermanentDisplayConfiguration(); - assert_eq!(ffi::CGDisplayRelease(*screen), ffi::kCGErrorSuccess); - } -} -pub unsafe fn restore_display_mode_async(ns_screen: u32) { - dispatch_async_f( - dispatch_get_main_queue(), - Box::into_raw(Box::new(ns_screen)) as *mut _, - restore_display_mode_callback, - ); -} - -struct SetMaximizedData { - ns_window: id, - is_zoomed: bool, - maximized: bool, - shared_state: Weak>, -} -impl SetMaximizedData { - fn new_ptr( - ns_window: id, - is_zoomed: bool, - maximized: bool, - shared_state: Weak>, - ) -> *mut Self { - Box::into_raw(Box::new(SetMaximizedData { - ns_window, - is_zoomed, - maximized, - shared_state, - })) - } -} -extern "C" fn set_maximized_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetMaximizedData; - { - let context = &*context_ptr; - - if let Some(shared_state) = context.shared_state.upgrade() { - trace!("Locked shared state in `set_maximized`"); - let mut shared_state_lock = shared_state.lock().unwrap(); - - // Save the standard frame sized if it is not zoomed - if !context.is_zoomed { - shared_state_lock.standard_frame = Some(NSWindow::frame(context.ns_window)); + let ns_window = MainThreadSafe(ns_window); + let ns_view = MainThreadSafe(ns_view); + let shared_state = MainThreadSafe(shared_state); + Queue::main().exec_async(move || { + // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we + // set a normal style temporarily. The previous state will be + // restored in `WindowDelegate::window_did_exit_fullscreen`. + if not_fullscreen { + let curr_mask = ns_window.styleMask(); + let required = + NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; + if !curr_mask.contains(required) { + set_style_mask(*ns_window, *ns_view, required); + if let Some(shared_state) = shared_state.upgrade() { + trace!("Locked shared state in `toggle_full_screen_callback`"); + let mut shared_state_lock = shared_state.lock().unwrap(); + (*shared_state_lock).saved_style = Some(curr_mask); + trace!("Unlocked shared state in `toggle_full_screen_callback`"); } - - shared_state_lock.maximized = context.maximized; - - let curr_mask = context.ns_window.styleMask(); - if shared_state_lock.fullscreen.is_some() { - // Handle it in window_did_exit_fullscreen - return; - } else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) { - // Just use the native zoom if resizable - context.ns_window.zoom_(nil); - } else { - // if it's not resizable, we set the frame directly - let new_rect = if context.maximized { - let screen = NSScreen::mainScreen(nil); - NSScreen::visibleFrame(screen) - } else { - shared_state_lock.saved_standard_frame() - }; - context.ns_window.setFrame_display_(new_rect, 0); - } - - trace!("Unlocked shared state in `set_maximized`"); } } - Box::from_raw(context_ptr); - } + // Window level must be restored from `CGShieldingWindowLevel() + // + 1` back to normal in order for `toggleFullScreen` to do + // anything + ns_window.setLevel_(0); + ns_window.toggleFullScreen_(nil); + }); } + +pub unsafe fn restore_display_mode_async(ns_screen: u32) { + Queue::main().exec_async(move || { + ffi::CGRestorePermanentDisplayConfiguration(); + assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess); + }); +} + // `setMaximized` is not thread-safe pub unsafe fn set_maximized_async( ns_window: id, @@ -319,127 +138,79 @@ pub unsafe fn set_maximized_async( maximized: bool, shared_state: Weak>, ) { - let context = SetMaximizedData::new_ptr(ns_window, is_zoomed, maximized, shared_state); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_maximized_callback, - ); + let ns_window = MainThreadSafe(ns_window); + let shared_state = MainThreadSafe(shared_state); + Queue::main().exec_async(move || { + if let Some(shared_state) = shared_state.upgrade() { + trace!("Locked shared state in `set_maximized`"); + let mut shared_state_lock = shared_state.lock().unwrap(); + + // Save the standard frame sized if it is not zoomed + if !is_zoomed { + shared_state_lock.standard_frame = Some(NSWindow::frame(*ns_window)); + } + + shared_state_lock.maximized = maximized; + + let curr_mask = ns_window.styleMask(); + if shared_state_lock.fullscreen.is_some() { + // Handle it in window_did_exit_fullscreen + return; + } else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) { + // Just use the native zoom if resizable + ns_window.zoom_(nil); + } else { + // if it's not resizable, we set the frame directly + let new_rect = if maximized { + let screen = NSScreen::mainScreen(nil); + NSScreen::visibleFrame(screen) + } else { + shared_state_lock.saved_standard_frame() + }; + ns_window.setFrame_display_(new_rect, 0); + } + + trace!("Unlocked shared state in `set_maximized`"); + } + }); } -struct OrderOutData { - ns_window: id, -} -impl OrderOutData { - fn new_ptr(ns_window: id) -> *mut Self { - Box::into_raw(Box::new(OrderOutData { ns_window })) - } -} -extern "C" fn order_out_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut OrderOutData; - { - let context = &*context_ptr; - context.ns_window.orderOut_(nil); - } - Box::from_raw(context_ptr); - } -} // `orderOut:` isn't thread-safe. Calling it from another thread actually works, // but with an odd delay. pub unsafe fn order_out_async(ns_window: id) { - let context = OrderOutData::new_ptr(ns_window); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - order_out_callback, - ); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + ns_window.orderOut_(nil); + }); } -struct MakeKeyAndOrderFrontData { - ns_window: id, -} -impl MakeKeyAndOrderFrontData { - fn new_ptr(ns_window: id) -> *mut Self { - Box::into_raw(Box::new(MakeKeyAndOrderFrontData { ns_window })) - } -} -extern "C" fn make_key_and_order_front_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut MakeKeyAndOrderFrontData; - { - let context = &*context_ptr; - context.ns_window.makeKeyAndOrderFront_(nil); - } - Box::from_raw(context_ptr); - } -} // `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread // actually works, but with an odd delay. pub unsafe fn make_key_and_order_front_async(ns_window: id) { - let context = MakeKeyAndOrderFrontData::new_ptr(ns_window); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - make_key_and_order_front_callback, - ); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + ns_window.makeKeyAndOrderFront_(nil); + }); } -struct SetTitleData { - ns_window: id, - title: String, -} -impl SetTitleData { - fn new_ptr(ns_window: id, title: String) -> *mut Self { - Box::into_raw(Box::new(SetTitleData { ns_window, title })) - } -} -extern "C" fn set_title_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetTitleData; - { - let context = &*context_ptr; - let title = IdRef::new(NSString::alloc(nil).init_str(&context.title)); - context.ns_window.setTitle_(*title); - } - Box::from_raw(context_ptr); - } -} // `setTitle:` isn't thread-safe. Calling it from another thread invalidates the // window drag regions, which throws an exception when not done in the main // thread pub unsafe fn set_title_async(ns_window: id, title: String) { - let context = SetTitleData::new_ptr(ns_window, title); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_title_callback, - ); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + let title = IdRef::new(NSString::alloc(nil).init_str(&title)); + ns_window.setTitle_(*title); + }); } -struct CloseData { - ns_window: id, -} -impl CloseData { - fn new_ptr(ns_window: id) -> *mut Self { - Box::into_raw(Box::new(CloseData { ns_window })) - } -} -extern "C" fn close_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut CloseData; - { - let context = &*context_ptr; - let pool = NSAutoreleasePool::new(nil); - context.ns_window.close(); - pool.drain(); - } - Box::from_raw(context_ptr); - } -} // `close:` is thread-safe, but we want the event to be triggered from the main // thread. Though, it's a good idea to look into that more... pub unsafe fn close_async(ns_window: id) { - let context = CloseData::new_ptr(ns_window); - dispatch_async_f(dispatch_get_main_queue(), context as *mut _, close_callback); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + autoreleasepool(move || { + ns_window.close(); + }); + }); }