diff --git a/src/platform_impl/apple/appkit/app_state.rs b/src/platform_impl/apple/appkit/app_state.rs index e9be6032f2..5a2f33fb2f 100644 --- a/src/platform_impl/apple/appkit/app_state.rs +++ b/src/platform_impl/apple/appkit/app_state.rs @@ -1,7 +1,6 @@ use std::cell::{Cell, OnceCell, RefCell}; use std::mem; use std::rc::{Rc, Weak}; -use std::sync::atomic::Ordering as AtomicOrdering; use std::sync::Arc; use std::time::Instant; @@ -11,7 +10,8 @@ use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningAppli use objc2_foundation::NSNotification; use super::super::event_handler::EventHandler; -use super::event_loop::{stop_app_immediately, ActiveEventLoop, EventLoopProxy, PanicInfo}; +use super::super::event_loop_proxy::EventLoopProxy; +use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo}; use super::menu; use super::observer::{EventLoopWaker, RunLoop}; use crate::application::ApplicationHandler; @@ -59,13 +59,17 @@ impl AppState { default_menu: bool, activate_ignoring_other_apps: bool, ) -> Rc { - let this = Rc::new(AppState { + let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || { + Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop)); + })); + + let this = Rc::new(Self { mtm, activation_policy, - event_loop_proxy: Arc::new(EventLoopProxy::new()), default_menu, activate_ignoring_other_apps, run_loop: RunLoop::main(mtm), + event_loop_proxy, event_handler: EventHandler::new(), stop_on_launch: Cell::new(false), stop_before_wait: Cell::new(false), @@ -351,10 +355,6 @@ impl AppState { return; } - if self.event_loop_proxy.wake_up.swap(false, AtomicOrdering::Relaxed) { - self.with_handler(|app, event_loop| app.proxy_wake_up(event_loop)); - } - let redraw = mem::take(&mut *self.pending_redraw.borrow_mut()); for window_id in redraw { self.with_handler(|app, event_loop| { diff --git a/src/platform_impl/apple/appkit/event_loop.rs b/src/platform_impl/apple/appkit/event_loop.rs index c8bb44733b..64d16ea2c2 100644 --- a/src/platform_impl/apple/appkit/event_loop.rs +++ b/src/platform_impl/apple/appkit/event_loop.rs @@ -1,10 +1,7 @@ use std::any::Any; use std::cell::Cell; -use std::os::raw::c_void; use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe}; -use std::ptr; use std::rc::{Rc, Weak}; -use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -15,11 +12,6 @@ use objc2_app_kit::{ NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification, NSApplicationWillTerminateNotification, NSWindow, }; -use objc2_core_foundation::{ - kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoopAddSource, CFRunLoopGetMain, - CFRunLoopSource, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceSignal, - CFRunLoopWakeUp, -}; use objc2_foundation::{NSNotificationCenter, NSObjectProtocol}; use rwh_06::HasDisplayHandle; @@ -34,8 +26,7 @@ use crate::application::ApplicationHandler; use crate::error::{EventLoopError, RequestError}; use crate::event_loop::{ ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, - EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, - OwnedDisplayHandle as CoreOwnedDisplayHandle, + EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle, }; use crate::monitor::MonitorHandle as RootMonitorHandle; use crate::platform::macos::ActivationPolicy; @@ -429,54 +420,3 @@ pub fn stop_app_on_panic R + UnwindSafe, R>( }, } } - -#[derive(Debug)] -pub struct EventLoopProxy { - pub(crate) wake_up: AtomicBool, - source: CFRetained, -} - -unsafe impl Send for EventLoopProxy {} -unsafe impl Sync for EventLoopProxy {} - -impl EventLoopProxy { - pub(crate) fn new() -> Self { - unsafe { - // just wake up the eventloop - extern "C-unwind" fn event_loop_proxy_handler(_context: *mut c_void) {} - - // adding a Source to the main CFRunLoop lets us wake it up and - // process user events through the normal OS EventLoop mechanisms. - let rl = CFRunLoopGetMain().unwrap(); - let mut context = CFRunLoopSourceContext { - version: 0, - info: ptr::null_mut(), - retain: None, - release: None, - copyDescription: None, - equal: None, - hash: None, - schedule: None, - cancel: None, - perform: Some(event_loop_proxy_handler), - }; - let source = CFRunLoopSourceCreate(None, CFIndex::MAX - 1, &mut context).unwrap(); - CFRunLoopAddSource(&rl, Some(&source), kCFRunLoopCommonModes); - CFRunLoopWakeUp(&rl); - - EventLoopProxy { wake_up: AtomicBool::new(false), source } - } - } -} - -impl EventLoopProxyProvider for EventLoopProxy { - fn wake_up(&self) { - self.wake_up.store(true, AtomicOrdering::Relaxed); - unsafe { - // Let the main thread know there's a new event. - CFRunLoopSourceSignal(&self.source); - let rl = CFRunLoopGetMain().unwrap(); - CFRunLoopWakeUp(&rl); - } - } -} diff --git a/src/platform_impl/apple/event_loop_proxy.rs b/src/platform_impl/apple/event_loop_proxy.rs new file mode 100644 index 0000000000..34e06357fa --- /dev/null +++ b/src/platform_impl/apple/event_loop_proxy.rs @@ -0,0 +1,126 @@ +use std::os::raw::c_void; +use std::sync::Arc; + +use objc2::MainThreadMarker; +use objc2_core_foundation::{ + kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoop, CFRunLoopAddSource, CFRunLoopGetMain, + CFRunLoopSource, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, + CFRunLoopSourceSignal, CFRunLoopWakeUp, +}; + +use crate::event_loop::EventLoopProxyProvider; + +/// A waker that signals a `CFRunLoopSource` on the main thread. +/// +/// We use this to integrate with the system as cleanly as possible (instead of e.g. keeping an +/// atomic around that we check on each iteration of the event loop). +/// +/// See . +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct EventLoopProxy { + source: CFRetained, + /// Cached value of `CFRunLoopGetMain`. + main_loop: CFRetained, +} + +// FIXME(madsmtm): Mark `CFRunLoopSource` + `CFRunLoop` as `Send` + `Sync`. +unsafe impl Send for EventLoopProxy {} +unsafe impl Sync for EventLoopProxy {} + +impl EventLoopProxy { + /// Create a new proxy, registering it to be performed on the main thread. + /// + /// The provided closure should call `proxy_wake_up` on the application. + pub(crate) fn new(mtm: MainThreadMarker, signaller: F) -> Self { + // We use an `Arc` here to make sure that the reference-counting of the signal container is + // atomic (`Retained`/`CFRetained` would be valid alternatives too). + let signaller = Arc::new(signaller); + + unsafe extern "C-unwind" fn retain(info: *const c_void) -> *const c_void { + // SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below. + unsafe { Arc::increment_strong_count(info.cast::()) }; + info + } + unsafe extern "C-unwind" fn release(info: *const c_void) { + // SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below. + unsafe { Arc::decrement_strong_count(info.cast::()) }; + } + + // Pointer equality / hashing. + extern "C-unwind" fn equal(info1: *const c_void, info2: *const c_void) -> u8 { + (info1 == info2) as u8 + } + extern "C-unwind" fn hash(info: *const c_void) -> usize { + info as usize + } + + // Call the provided closure. + unsafe extern "C-unwind" fn perform(info: *mut c_void) { + // SAFETY: The pointer was passed to `CFRunLoopSourceContext.info` below. + let signaller = unsafe { &*info.cast::() }; + (signaller)(); + } + + // Fire last. + let order = CFIndex::MAX - 1; + + // This is marked `mut` to match the signature of `CFRunLoopSourceCreate`, but the + // information is copied, and not actually mutated. + let mut context = CFRunLoopSourceContext { + version: 0, + // This is retained on creation. + info: Arc::as_ptr(&signaller) as *mut c_void, + retain: Some(retain::), + release: Some(release::), + copyDescription: None, + equal: Some(equal), + hash: Some(hash), + schedule: None, + cancel: None, + perform: Some(perform::), + }; + + // SAFETY: The normal callbacks are thread-safe (`retain`/`release` use atomics, and + // `equal`/`hash` only access a pointer). + // + // Note that the `perform` callback isn't thread-safe (we don't have `F: Send + Sync`), but + // that's okay, since we are on the main thread, and the source is only added to the main + // run loop (below), and hence only performed there. + // + // Keeping the closure alive beyond this scope is fine, because `F: 'static`. + let source = unsafe { + let _ = mtm; + CFRunLoopSourceCreate(None, order, &mut context).unwrap() + }; + + // Register the source to be performed on the main thread. + let main_loop = unsafe { CFRunLoopGetMain() }.unwrap(); + unsafe { CFRunLoopAddSource(&main_loop, Some(&source), kCFRunLoopCommonModes) }; + + Self { source, main_loop } + } + + // FIXME(madsmtm): Use this on macOS too. + // More difficult there, since the user can re-start the event loop. + #[cfg_attr(target_os = "macos", allow(dead_code))] + pub(crate) fn invalidate(&self) { + // NOTE: We do NOT fire this on `Drop`, since we want the proxy to be cloneable, such that + // we only need to register a single source even if there's multiple proxies in use. + unsafe { CFRunLoopSourceInvalidate(&self.source) }; + } +} + +impl EventLoopProxyProvider for EventLoopProxy { + fn wake_up(&self) { + // Signal the source, which ends up later invoking `perform` on the main thread. + // + // Multiple signals in quick succession are automatically coalesced into a single signal. + unsafe { CFRunLoopSourceSignal(&self.source) }; + + // Let the main thread know there's a new event. + // + // This is required since we may be (probably are) running on a different thread, and the + // main loop may be sleeping (and `CFRunLoopSourceSignal` won't wake it). + unsafe { CFRunLoopWakeUp(&self.main_loop) }; + } +} diff --git a/src/platform_impl/apple/mod.rs b/src/platform_impl/apple/mod.rs index 7bb4af90e1..f30a8d19cd 100644 --- a/src/platform_impl/apple/mod.rs +++ b/src/platform_impl/apple/mod.rs @@ -3,6 +3,7 @@ #[cfg(target_os = "macos")] mod appkit; mod event_handler; +mod event_loop_proxy; mod notification_center; #[cfg(not(target_os = "macos"))] mod uikit; diff --git a/src/platform_impl/apple/uikit/app_state.rs b/src/platform_impl/apple/uikit/app_state.rs index afaca969e8..4ba639f5a5 100644 --- a/src/platform_impl/apple/uikit/app_state.rs +++ b/src/platform_impl/apple/uikit/app_state.rs @@ -3,7 +3,6 @@ use std::cell::{OnceCell, RefCell, RefMut}; use std::collections::HashSet; use std::os::raw::c_void; -use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; use std::time::Instant; use std::{mem, ptr}; @@ -19,8 +18,9 @@ use objc2_core_foundation::{ use objc2_ui_kit::{UIApplication, UICoordinateSpace, UIView}; use super::super::event_handler::EventHandler; +use super::super::event_loop_proxy::EventLoopProxy; use super::window::WinitUIWindow; -use super::{ActiveEventLoop, EventLoopProxy}; +use super::ActiveEventLoop; use crate::application::ApplicationHandler; use crate::dpi::PhysicalSize; use crate::event::{StartCause, SurfaceSizeWriter, WindowEvent}; @@ -102,7 +102,7 @@ pub(crate) struct AppState { } impl AppState { - pub(crate) fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> { + pub(crate) fn get_mut(mtm: MainThreadMarker) -> RefMut<'static, AppState> { // basically everything in UIKit requires the main thread, so it's pointless to use the // std::sync APIs. // must be mut because plain `static` requires `Sync` @@ -114,17 +114,21 @@ impl AppState { if guard.is_none() { #[inline(never)] #[cold] - fn init_guard(guard: &mut RefMut<'static, Option>) { + fn init_guard(guard: &mut RefMut<'static, Option>, mtm: MainThreadMarker) { let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain().unwrap() }); + let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || { + get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm })); + })); + **guard = Some(AppState { app_state: Some(AppStateImpl::Initial { queued_gpu_redraws: HashSet::new() }), control_flow: ControlFlow::default(), waker, - event_loop_proxy: Arc::new(EventLoopProxy::new()), + event_loop_proxy, queued_events: Vec::new(), }); } - init_guard(&mut guard); + init_guard(&mut guard, mtm); } RefMut::map(guard, |state| state.as_mut().unwrap()) } @@ -394,13 +398,8 @@ fn handle_user_events(mtm: MainThreadMarker) { if matches!(this.state(), AppStateImpl::ProcessingRedraws { .. }) { bug!("user events attempted to be sent out while `ProcessingRedraws`"); } - let event_loop_proxy = this.event_loop_proxy().clone(); drop(this); - if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) { - get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm })); - } - loop { let mut this = AppState::get_mut(mtm); let queued_events = mem::take(&mut this.queued_events); @@ -412,10 +411,6 @@ fn handle_user_events(mtm: MainThreadMarker) { for event in queued_events { handle_wrapped_event(mtm, event); } - - if event_loop_proxy.wake_up.swap(false, Ordering::Relaxed) { - get_handler(mtm).handle(|app| app.proxy_wake_up(&ActiveEventLoop { mtm })); - } } } @@ -503,6 +498,10 @@ pub(crate) fn terminated(application: &UIApplication) { drop(this); get_handler(mtm).handle(|app| app.exiting(&ActiveEventLoop { mtm })); + + let this = AppState::get_mut(mtm); + // Prevent EventLoopProxy from firing again. + this.event_loop_proxy.invalidate(); } fn handle_wrapped_event(mtm: MainThreadMarker, event: EventWrapper) { diff --git a/src/platform_impl/apple/uikit/event_loop.rs b/src/platform_impl/apple/uikit/event_loop.rs index 99a97e7226..8c17dbd620 100644 --- a/src/platform_impl/apple/uikit/event_loop.rs +++ b/src/platform_impl/apple/uikit/event_loop.rs @@ -1,16 +1,13 @@ use std::ffi::{c_char, c_int, c_void}; use std::ptr::{self, NonNull}; -use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; use std::sync::Arc; use objc2::rc::Retained; use objc2::runtime::ProtocolObject; use objc2::{msg_send, ClassType, MainThreadMarker}; use objc2_core_foundation::{ - kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFIndex, CFRetained, CFRunLoopActivity, - CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopObserver, - CFRunLoopObserverCreate, CFRunLoopSource, CFRunLoopSourceContext, CFRunLoopSourceCreate, - CFRunLoopSourceInvalidate, CFRunLoopSourceSignal, CFRunLoopWakeUp, + kCFRunLoopDefaultMode, CFIndex, CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopGetMain, + CFRunLoopObserver, CFRunLoopObserverCreate, }; use objc2_foundation::{NSNotificationCenter, NSObjectProtocol}; use objc2_ui_kit::{ @@ -29,8 +26,7 @@ use crate::application::ApplicationHandler; use crate::error::{EventLoopError, NotSupportedError, RequestError}; use crate::event_loop::{ ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, - EventLoopProxy as CoreEventLoopProxy, EventLoopProxyProvider, - OwnedDisplayHandle as CoreOwnedDisplayHandle, + EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle, }; use crate::monitor::MonitorHandle as RootMonitorHandle; use crate::platform_impl::Window; @@ -277,62 +273,6 @@ impl EventLoop { } } -pub struct EventLoopProxy { - pub(crate) wake_up: AtomicBool, - source: CFRetained, -} - -unsafe impl Send for EventLoopProxy {} -unsafe impl Sync for EventLoopProxy {} - -impl Drop for EventLoopProxy { - fn drop(&mut self) { - unsafe { CFRunLoopSourceInvalidate(&self.source) }; - } -} - -impl EventLoopProxy { - pub(crate) fn new() -> EventLoopProxy { - unsafe { - // just wake up the eventloop - extern "C-unwind" fn event_loop_proxy_handler(_: *mut c_void) {} - - // adding a Source to the main CFRunLoop lets us wake it up and - // process user events through the normal OS EventLoop mechanisms. - let rl = CFRunLoopGetMain().unwrap(); - let mut context = CFRunLoopSourceContext { - version: 0, - info: ptr::null_mut(), - retain: None, - release: None, - copyDescription: None, - equal: None, - hash: None, - schedule: None, - cancel: None, - perform: Some(event_loop_proxy_handler), - }; - let source = CFRunLoopSourceCreate(None, CFIndex::MAX - 1, &mut context).unwrap(); - CFRunLoopAddSource(&rl, Some(&source), kCFRunLoopCommonModes); - CFRunLoopWakeUp(&rl); - - EventLoopProxy { wake_up: AtomicBool::new(false), source } - } - } -} - -impl EventLoopProxyProvider for EventLoopProxy { - fn wake_up(&self) { - self.wake_up.store(true, AtomicOrdering::Relaxed); - unsafe { - // let the main thread know there's a new event - CFRunLoopSourceSignal(&self.source); - let rl = CFRunLoopGetMain().unwrap(); - CFRunLoopWakeUp(&rl); - } - } -} - fn setup_control_flow_observers() { unsafe { // begin is queued with the highest priority to ensure it is processed before other diff --git a/src/platform_impl/apple/uikit/mod.rs b/src/platform_impl/apple/uikit/mod.rs index 947a0852ba..44924a670a 100644 --- a/src/platform_impl/apple/uikit/mod.rs +++ b/src/platform_impl/apple/uikit/mod.rs @@ -10,7 +10,7 @@ mod window; use std::fmt; pub(crate) use self::event_loop::{ - ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes, + ActiveEventLoop, EventLoop, PlatformSpecificEventLoopAttributes, }; pub(crate) use self::monitor::MonitorHandle; pub(crate) use self::window::{PlatformSpecificWindowAttributes, Window};