mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-26 14:49:07 -04:00
Refactor usage of CFRunLoopObserver (#4349)
Added a common interface that: - Uses closures instead of static functions. This should allow easier refactoring in the future. - Returns a handle which is invalidated on `Drop`. This should avoid situations where the event loop has exited, but an observer is still called because the user spawned the application later on. - Is properly main-thread safe. This interface is placed in winit-common, to allow using it in both winit-appkit and winit-uikit.
This commit is contained in:
@@ -8,7 +8,7 @@ use dispatch2::MainThreadBound;
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSRunningApplication};
|
||||
use objc2_foundation::NSNotification;
|
||||
use winit_common::core_foundation::EventLoopProxy;
|
||||
use winit_common::core_foundation::{EventLoopProxy, MainRunLoop};
|
||||
use winit_common::event_handler::EventHandler;
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::event::{StartCause, WindowEvent};
|
||||
@@ -17,7 +17,7 @@ use winit_core::window::WindowId;
|
||||
|
||||
use super::event_loop::{ActiveEventLoop, notify_windows_of_exit, stop_app_immediately};
|
||||
use super::menu;
|
||||
use super::observer::{EventLoopWaker, RunLoop};
|
||||
use super::observer::EventLoopWaker;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct AppState {
|
||||
@@ -25,7 +25,7 @@ pub(super) struct AppState {
|
||||
activation_policy: Option<NSApplicationActivationPolicy>,
|
||||
default_menu: bool,
|
||||
activate_ignoring_other_apps: bool,
|
||||
run_loop: RunLoop,
|
||||
run_loop: MainRunLoop,
|
||||
event_loop_proxy: Arc<EventLoopProxy>,
|
||||
event_handler: EventHandler,
|
||||
stop_on_launch: Cell<bool>,
|
||||
@@ -68,7 +68,7 @@ impl AppState {
|
||||
activation_policy,
|
||||
default_menu,
|
||||
activate_ignoring_other_apps,
|
||||
run_loop: RunLoop::main(mtm),
|
||||
run_loop: MainRunLoop::get(mtm),
|
||||
event_loop_proxy,
|
||||
event_handler: EventHandler::new(),
|
||||
stop_on_launch: Cell::new(false),
|
||||
@@ -265,7 +265,7 @@ impl AppState {
|
||||
if !pending_redraw.contains(&window_id) {
|
||||
pending_redraw.push(window_id);
|
||||
}
|
||||
self.run_loop.wakeup();
|
||||
self.run_loop.wake_up();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@@ -310,6 +310,7 @@ impl AppState {
|
||||
// Called by RunLoopObserver after finishing waiting for new events
|
||||
pub fn wakeup(self: &Rc<Self>) {
|
||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||
// (we have registered to observe all modes, including modal event loops).
|
||||
if !self.event_handler.ready() || !self.is_running() {
|
||||
return;
|
||||
}
|
||||
@@ -338,8 +339,7 @@ impl AppState {
|
||||
// Called by RunLoopObserver before waiting for new events
|
||||
pub fn cleared(self: &Rc<Self>) {
|
||||
// Return when in event handler due to https://github.com/rust-windowing/winit/issues/1779
|
||||
// XXX: how does it make sense that `event_handler.ready()` can ever return `false` here if
|
||||
// we're about to return to the `CFRunLoop` to poll for new events?
|
||||
// (we have registered to observe all modes, including modal event loops).
|
||||
if !self.event_handler.ready() || !self.is_running() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ use objc2_app_kit::{
|
||||
NSApplication, NSApplicationActivationPolicy, NSApplicationDidFinishLaunchingNotification,
|
||||
NSApplicationWillTerminateNotification, NSWindow,
|
||||
};
|
||||
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopCommonModes};
|
||||
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
|
||||
use rwh_06::HasDisplayHandle;
|
||||
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver};
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::cursor::{CustomCursor as CoreCustomCursor, CustomCursorSource};
|
||||
use winit_core::error::{EventLoopError, RequestError};
|
||||
@@ -28,7 +30,6 @@ use super::cursor::CustomCursor;
|
||||
use super::event::dummy_event;
|
||||
use super::monitor;
|
||||
use super::notification_center::create_observer;
|
||||
use super::observer::setup_control_flow_observers;
|
||||
use crate::ActivationPolicy;
|
||||
use crate::window::Window;
|
||||
|
||||
@@ -150,6 +151,9 @@ pub struct EventLoop {
|
||||
// Though we do still need to keep the observers around to prevent them from being deallocated.
|
||||
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
|
||||
_before_waiting_observer: MainRunLoopObserver,
|
||||
_after_waiting_observer: MainRunLoopObserver,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -217,7 +221,31 @@ impl EventLoop {
|
||||
},
|
||||
);
|
||||
|
||||
setup_control_flow_observers(mtm);
|
||||
let main_loop = MainRunLoop::get(mtm);
|
||||
let mode = unsafe { kCFRunLoopCommonModes }.unwrap();
|
||||
|
||||
let app_state_clone = Rc::clone(&app_state);
|
||||
let _before_waiting_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::BeforeWaiting,
|
||||
true,
|
||||
// Queued with the lowest priority to ensure it is processed after other observers.
|
||||
// Without that, we'd get a `LoopExiting` after `AboutToWait`.
|
||||
CFIndex::MAX,
|
||||
move |_| app_state_clone.cleared(),
|
||||
);
|
||||
main_loop.add_observer(&_before_waiting_observer, mode);
|
||||
|
||||
let app_state_clone = Rc::clone(&app_state);
|
||||
let _after_waiting_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::AfterWaiting,
|
||||
true,
|
||||
// Queued with the highest priority to ensure it is processed before other observers.
|
||||
CFIndex::MIN,
|
||||
move |_| app_state_clone.wakeup(),
|
||||
);
|
||||
main_loop.add_observer(&_after_waiting_observer, mode);
|
||||
|
||||
Ok(EventLoop {
|
||||
app,
|
||||
@@ -225,6 +253,8 @@ impl EventLoop {
|
||||
window_target: ActiveEventLoop { app_state, mtm },
|
||||
_did_finish_launching_observer,
|
||||
_will_terminate_observer,
|
||||
_before_waiting_observer,
|
||||
_after_waiting_observer,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,171 +1,10 @@
|
||||
//! Utilities for working with `CFRunLoop`.
|
||||
//!
|
||||
//! See Apple's documentation on Run Loops for details:
|
||||
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
|
||||
use std::cell::Cell;
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::time::Instant;
|
||||
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{
|
||||
CFAbsoluteTimeGetCurrent, CFIndex, CFRetained, CFRunLoop, CFRunLoopActivity, CFRunLoopObserver,
|
||||
CFRunLoopObserverCallBack, CFRunLoopObserverContext, CFRunLoopTimer, kCFRunLoopCommonModes,
|
||||
kCFRunLoopDefaultMode,
|
||||
CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopTimer, kCFRunLoopCommonModes,
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
use super::app_state::AppState;
|
||||
|
||||
// begin is queued with the highest priority to ensure it is processed before other observers
|
||||
extern "C-unwind" fn control_flow_begin_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_info: *mut c_void,
|
||||
) {
|
||||
match activity {
|
||||
CFRunLoopActivity::AfterWaiting => {
|
||||
AppState::get(MainThreadMarker::new().unwrap()).wakeup();
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||
// without that, LoopExiting would get sent after AboutToWait
|
||||
extern "C-unwind" fn control_flow_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_info: *mut c_void,
|
||||
) {
|
||||
match activity {
|
||||
CFRunLoopActivity::BeforeWaiting => {
|
||||
AppState::get(MainThreadMarker::new().unwrap()).cleared();
|
||||
},
|
||||
CFRunLoopActivity::Exit => (), // unimplemented!(), // not expected to ever happen
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RunLoop(CFRetained<CFRunLoop>);
|
||||
|
||||
impl RunLoop {
|
||||
pub fn main(mtm: MainThreadMarker) -> Self {
|
||||
// SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so
|
||||
// scheduling (and scheduling a non-`Send` block) to that thread is allowed.
|
||||
let _ = mtm;
|
||||
RunLoop(CFRunLoop::main().unwrap())
|
||||
}
|
||||
|
||||
pub fn wakeup(&self) {
|
||||
self.0.wake_up();
|
||||
}
|
||||
|
||||
unsafe fn add_observer(
|
||||
&self,
|
||||
flags: CFRunLoopActivity,
|
||||
// The lower the value, the sooner this will run
|
||||
priority: CFIndex,
|
||||
handler: CFRunLoopObserverCallBack,
|
||||
context: *mut CFRunLoopObserverContext,
|
||||
) {
|
||||
let observer =
|
||||
unsafe { CFRunLoopObserver::new(None, flags.0, true, priority, handler, context) }
|
||||
.unwrap();
|
||||
self.0.add_observer(Some(&observer), unsafe { kCFRunLoopCommonModes });
|
||||
}
|
||||
|
||||
/// Submit a closure to run on the main thread as the next step in the run loop, before other
|
||||
/// event sources are processed.
|
||||
///
|
||||
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// This queuing could be implemented in the following several ways with subtle differences in
|
||||
/// timing. This list is sorted in rough order in which they are run:
|
||||
///
|
||||
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
|
||||
///
|
||||
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
|
||||
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
|
||||
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
|
||||
///
|
||||
/// a. `atStart = true`.
|
||||
///
|
||||
/// b. `atStart = false`.
|
||||
///
|
||||
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
|
||||
/// respect the ordering that runloop events have.
|
||||
///
|
||||
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
|
||||
/// want the event to be queued in a way that preserves the order the events originally arrived
|
||||
/// in.
|
||||
///
|
||||
/// As an example, let's assume that we receive two events from the user, a mouse click which we
|
||||
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
|
||||
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
|
||||
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
|
||||
/// put the event at the very front of the queue, to be handled as soon as possible after
|
||||
/// handling whatever event it's currently handling.
|
||||
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
|
||||
// Convert `FnOnce()` to `Block<dyn Fn()>`.
|
||||
let closure = Cell::new(Some(closure));
|
||||
let block = block2::RcBlock::new(move || {
|
||||
if let Some(closure) = closure.take() {
|
||||
closure()
|
||||
} else {
|
||||
error!("tried to execute queued closure on main thread twice");
|
||||
}
|
||||
});
|
||||
|
||||
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
|
||||
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
|
||||
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
|
||||
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
|
||||
// - `NSConnectionReplyMode`: TODO.
|
||||
//
|
||||
// We only want to run event handlers in the default mode, as we support running a blocking
|
||||
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
|
||||
// resizing such panel window enters the event tracking run loop mode, so we can't directly
|
||||
// trigger events inside that mode either.
|
||||
//
|
||||
// Any events that are queued while running a modal or when live-resizing will instead wait,
|
||||
// and be delivered to the application afterwards.
|
||||
//
|
||||
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
|
||||
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
|
||||
|
||||
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
|
||||
unsafe { self.0.perform_block(Some(mode), Some(&block)) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_control_flow_observers(mtm: MainThreadMarker) {
|
||||
let run_loop = RunLoop::main(mtm);
|
||||
unsafe {
|
||||
let mut context = CFRunLoopObserverContext {
|
||||
info: ptr::null_mut(),
|
||||
version: 0,
|
||||
retain: None,
|
||||
release: None,
|
||||
copyDescription: None,
|
||||
};
|
||||
run_loop.add_observer(
|
||||
CFRunLoopActivity::AfterWaiting,
|
||||
CFIndex::MIN,
|
||||
Some(control_flow_begin_handler),
|
||||
&mut context as *mut _,
|
||||
);
|
||||
run_loop.add_observer(
|
||||
CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting,
|
||||
CFIndex::MAX,
|
||||
Some(control_flow_end_handler),
|
||||
&mut context as *mut _,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoopWaker {
|
||||
|
||||
@@ -42,6 +42,7 @@ use objc2_foundation::{
|
||||
NSRect, NSSize, NSString, ns_string,
|
||||
};
|
||||
use tracing::{trace, warn};
|
||||
use winit_common::core_foundation::MainRunLoop;
|
||||
use winit_core::cursor::Cursor;
|
||||
use winit_core::error::{NotSupportedError, RequestError};
|
||||
use winit_core::event::{SurfaceSizeWriter, WindowEvent};
|
||||
@@ -56,7 +57,6 @@ use super::app_state::AppState;
|
||||
use super::cursor::{CustomCursor, cursor_from_icon};
|
||||
use super::ffi;
|
||||
use super::monitor::{self, MonitorHandle, flip_window_screen_coordinates, get_display_id};
|
||||
use super::observer::RunLoop;
|
||||
use super::util::cgerr;
|
||||
use super::view::WinitView;
|
||||
use super::window::{WinitPanel, WinitWindow, window_id};
|
||||
@@ -177,7 +177,7 @@ define_class!(
|
||||
|
||||
let mtm = MainThreadMarker::from(self);
|
||||
let this = self.retain();
|
||||
RunLoop::main(mtm).queue_closure(move || {
|
||||
MainRunLoop::get(mtm).queue_closure(move || {
|
||||
this.handle_scale_factor_changed(scale_factor);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ x11 = ["xkbcommon-dl?/x11", "dep:x11-dl"]
|
||||
xkb = ["dep:xkbcommon-dl", "dep:smol_str"]
|
||||
|
||||
# CoreFoundation
|
||||
core-foundation = ["dep:objc2", "dep:objc2-core-foundation"]
|
||||
core-foundation = ["dep:block2", "dep:objc2", "dep:objc2-core-foundation"]
|
||||
|
||||
[dependencies]
|
||||
smol_str = { workspace = true, optional = true }
|
||||
@@ -31,9 +31,12 @@ x11-dl = { workspace = true, optional = true }
|
||||
xkbcommon-dl = { workspace = true, optional = true }
|
||||
|
||||
# CoreFoundation
|
||||
block2 = { workspace = true, optional = true }
|
||||
objc2 = { workspace = true, optional = true }
|
||||
objc2-core-foundation = { workspace = true, optional = true, features = [
|
||||
"std",
|
||||
"block2",
|
||||
"objc2",
|
||||
"CFRunLoop",
|
||||
"CFString",
|
||||
] }
|
||||
|
||||
127
winit-common/src/core_foundation/main_run_loop.rs
Normal file
127
winit-common/src/core_foundation/main_run_loop.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
//! Various utilities for interfacing with the main run loop.
|
||||
//!
|
||||
//! These allow for using closures without the `Send + Sync` requirement that is otherwise required
|
||||
//! when interfacing with run loops.
|
||||
//!
|
||||
//! See also <https://github.com/madsmtm/objc2/issues/696> for work on this at a lower level.
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{CFRetained, CFRunLoop, CFRunLoopMode, kCFRunLoopDefaultMode};
|
||||
use tracing::error;
|
||||
|
||||
use super::MainRunLoopObserver;
|
||||
|
||||
/// Wrapper around [`CFRunLoop::main`].
|
||||
#[derive(Debug)]
|
||||
pub struct MainRunLoop {
|
||||
/// This is on the main thread.
|
||||
_mtm: MainThreadMarker,
|
||||
/// A cached reference to the main run loop.
|
||||
main_run_loop: CFRetained<CFRunLoop>,
|
||||
}
|
||||
|
||||
impl MainRunLoop {
|
||||
/// Get the main run loop.
|
||||
pub fn get(_mtm: MainThreadMarker) -> Self {
|
||||
let main_run_loop = CFRunLoop::main().unwrap();
|
||||
Self { _mtm, main_run_loop }
|
||||
}
|
||||
|
||||
/// Get a reference to the underlying [`CFRunLoop`].
|
||||
pub fn run_loop(&self) -> &CFRunLoop {
|
||||
&self.main_run_loop
|
||||
}
|
||||
|
||||
/// Wake the main run loop.
|
||||
pub fn wake_up(&self) {
|
||||
self.main_run_loop.wake_up();
|
||||
}
|
||||
|
||||
/// Submit a closure to run on the main thread as the next step in the run loop, before other
|
||||
/// event sources are processed.
|
||||
///
|
||||
/// This is used for running event handlers, as those are not allowed to run re-entrantly.
|
||||
///
|
||||
/// # Implementation
|
||||
///
|
||||
/// This queuing could be implemented in the following several ways with subtle differences in
|
||||
/// timing. This list is sorted in rough order in which they are run:
|
||||
///
|
||||
/// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`.
|
||||
///
|
||||
/// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the
|
||||
/// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both
|
||||
/// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop).
|
||||
///
|
||||
/// a. `atStart = true`.
|
||||
///
|
||||
/// b. `atStart = false`.
|
||||
///
|
||||
/// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not
|
||||
/// respect the ordering that runloop events have.
|
||||
///
|
||||
/// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we
|
||||
/// want the event to be queued in a way that preserves the order the events originally arrived
|
||||
/// in.
|
||||
///
|
||||
/// As an example, let's assume that we receive two events from the user, a mouse click which we
|
||||
/// handled by queuing it, and a window resize which we handled immediately. If we allowed
|
||||
/// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of
|
||||
/// the queue, and the events would appear out of order to the user of Winit. So we must instead
|
||||
/// put the event at the very front of the queue, to be handled as soon as possible after
|
||||
/// handling whatever event it's currently handling.
|
||||
pub fn queue_closure(&self, closure: impl FnOnce() + 'static) {
|
||||
// Convert `FnOnce()` to `Block<dyn Fn()>`.
|
||||
let closure = Cell::new(Some(closure));
|
||||
let block = block2::RcBlock::new(move || {
|
||||
debug_assert!(MainThreadMarker::new().is_some());
|
||||
if let Some(closure) = closure.take() {
|
||||
closure()
|
||||
} else {
|
||||
error!("tried to execute queued closure on main thread twice");
|
||||
}
|
||||
});
|
||||
|
||||
// There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa:
|
||||
// - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`.
|
||||
// - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window.
|
||||
// - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop.
|
||||
// - `NSConnectionReplyMode`: TODO.
|
||||
//
|
||||
// We only want to run event handlers in the default mode, as we support running a blocking
|
||||
// modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and
|
||||
// resizing such panel window enters the event tracking run loop mode, so we can't directly
|
||||
// trigger events inside that mode either.
|
||||
//
|
||||
// Any events that are queued while running a modal or when live-resizing will instead wait,
|
||||
// and be delivered to the application afterwards.
|
||||
//
|
||||
// [#1779]: https://github.com/rust-windowing/winit/issues/1779
|
||||
let mode = unsafe { kCFRunLoopDefaultMode.unwrap() };
|
||||
|
||||
let _ = self._mtm;
|
||||
// SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`.
|
||||
//
|
||||
// Additionally, we have a `MainThreadMarker` here, which means we know we're on the main
|
||||
// thread. We also know that the run loop is the main-thread run loop, so scheduling a
|
||||
// non-`Send` block to that is allowed.
|
||||
unsafe { self.main_run_loop.perform_block(Some(mode), Some(&block)) }
|
||||
}
|
||||
|
||||
/// Add an observer to the main run loop.
|
||||
pub fn add_observer(&self, observer: &MainRunLoopObserver, mode: &CFRunLoopMode) {
|
||||
// Accessing the `MainObserver`'s observer is fine here, since we're adding it to the main
|
||||
// run loop (which is on the same thread that the observer was created on).
|
||||
self.main_run_loop.add_observer(Some(&observer.observer), Some(mode));
|
||||
}
|
||||
|
||||
/// Remove an observer from the main run loop.
|
||||
///
|
||||
/// This is also done automatically when the [`MainRunLoopObserver`] is dropped.
|
||||
pub fn remove_observer(&self, observer: &MainRunLoopObserver, mode: &CFRunLoopMode) {
|
||||
// Same as in `add_observer`, accessing the main loop's observer is fine.
|
||||
self.main_run_loop.add_observer(Some(&observer.observer), Some(mode));
|
||||
}
|
||||
}
|
||||
55
winit-common/src/core_foundation/main_run_loop_observer.rs
Normal file
55
winit-common/src/core_foundation/main_run_loop_observer.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use block2::RcBlock;
|
||||
use objc2::MainThreadMarker;
|
||||
use objc2_core_foundation::{
|
||||
CFIndex, CFRetained, CFRunLoopActivity, CFRunLoopObserver, kCFAllocatorDefault,
|
||||
};
|
||||
|
||||
/// A [`CFRunLoopObserver`] on the main thread.
|
||||
///
|
||||
/// This type has "ownership" semantics, and invalidates the observer when dropped.
|
||||
#[derive(Debug)]
|
||||
pub struct MainRunLoopObserver {
|
||||
/// This must be private, otherwise the user might add it to an arbitrary run loop (but this
|
||||
/// observer is not designed to only be on the main thread).
|
||||
pub(crate) observer: CFRetained<CFRunLoopObserver>,
|
||||
}
|
||||
|
||||
impl MainRunLoopObserver {
|
||||
/// Create a new run loop observer that observes the main run loop.
|
||||
pub fn new(
|
||||
mtm: MainThreadMarker,
|
||||
activities: CFRunLoopActivity,
|
||||
repeats: bool,
|
||||
// The lower the value, the sooner this will run (inverse of a "priority").
|
||||
order: CFIndex,
|
||||
callback: impl Fn(CFRunLoopActivity) + 'static,
|
||||
) -> Self {
|
||||
let block = RcBlock::new(move |_: *mut _, activity| {
|
||||
debug_assert!(MainThreadMarker::new().is_some());
|
||||
callback(activity)
|
||||
});
|
||||
|
||||
let _ = mtm;
|
||||
// SAFETY: The callback is not Send + Sync, which would normally be unsound, but since we
|
||||
// restrict the callback to only ever be on the main thread (by taking `MainThreadMarker`,
|
||||
// and in `MainRunLoop::add_observer`), the callback doesn't have to be thread safe.
|
||||
let observer = unsafe {
|
||||
CFRunLoopObserver::with_handler(
|
||||
kCFAllocatorDefault,
|
||||
activities.0,
|
||||
repeats,
|
||||
order,
|
||||
Some(&block),
|
||||
)
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
Self { observer }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MainRunLoopObserver {
|
||||
fn drop(&mut self) {
|
||||
self.observer.invalidate();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
mod event_loop_proxy;
|
||||
//! Various wrappers around [`CFRunLoop`][objc2_core_foundation::CFRunLoop].
|
||||
//!
|
||||
//! See Apple's documentation on Run Loops for details:
|
||||
//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html>
|
||||
|
||||
pub use event_loop_proxy::*;
|
||||
mod event_loop_proxy;
|
||||
mod main_run_loop;
|
||||
mod main_run_loop_observer;
|
||||
|
||||
pub use self::event_loop_proxy::*;
|
||||
pub use self::main_run_loop::*;
|
||||
pub use self::main_run_loop_observer::*;
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::ProtocolObject;
|
||||
use objc2::{ClassType, MainThreadMarker, msg_send};
|
||||
use objc2_core_foundation::{
|
||||
CFIndex, CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, kCFRunLoopDefaultMode,
|
||||
};
|
||||
use objc2_core_foundation::{CFIndex, CFRunLoopActivity, kCFRunLoopDefaultMode};
|
||||
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
|
||||
use objc2_ui_kit::{
|
||||
UIApplication, UIApplicationDidBecomeActiveNotification,
|
||||
@@ -16,6 +12,7 @@ use objc2_ui_kit::{
|
||||
UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen,
|
||||
};
|
||||
use rwh_06::HasDisplayHandle;
|
||||
use winit_common::core_foundation::{MainRunLoop, MainRunLoopObserver};
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::cursor::{CustomCursor, CustomCursorSource};
|
||||
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
|
||||
@@ -136,6 +133,10 @@ pub struct EventLoop {
|
||||
_did_enter_background_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
|
||||
|
||||
_wakeup_observer: MainRunLoopObserver,
|
||||
_main_events_cleared_observer: MainRunLoopObserver,
|
||||
_events_cleared_observer: MainRunLoopObserver,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
@@ -151,9 +152,6 @@ impl EventLoop {
|
||||
return Err(EventLoopError::RecreationAttempt);
|
||||
}
|
||||
|
||||
// this line sets up the main run loop before `UIApplicationMain`
|
||||
setup_control_flow_observers();
|
||||
|
||||
let center = NSNotificationCenter::defaultCenter();
|
||||
|
||||
let _did_finish_launching_observer = create_observer(
|
||||
@@ -225,6 +223,50 @@ impl EventLoop {
|
||||
move |_| app_state::handle_memory_warning(mtm),
|
||||
);
|
||||
|
||||
let main_loop = MainRunLoop::get(mtm);
|
||||
let mode = unsafe { kCFRunLoopDefaultMode }.unwrap();
|
||||
|
||||
let _wakeup_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::AfterWaiting,
|
||||
true,
|
||||
// Queued with the highest priority to ensure it is processed before other observers.
|
||||
CFIndex::MIN,
|
||||
move |_| app_state::handle_wakeup_transition(mtm),
|
||||
);
|
||||
main_loop.add_observer(&_wakeup_observer, mode);
|
||||
|
||||
let _main_events_cleared_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::BeforeWaiting,
|
||||
true,
|
||||
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
||||
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the
|
||||
// main_end priority to be 0, in order to send `AboutToWait` before `RedrawRequested`.
|
||||
// This value was chosen conservatively to guard against apple using different
|
||||
// priorities for their redraw observers in different OS's or on different devices. If
|
||||
// it so happens that it's too conservative, the main symptom would be non-redraw
|
||||
// events coming in after `AboutToWait`.
|
||||
//
|
||||
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
|
||||
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
||||
//
|
||||
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
|
||||
0,
|
||||
move |_| app_state::handle_main_events_cleared(mtm),
|
||||
);
|
||||
main_loop.add_observer(&_main_events_cleared_observer, mode);
|
||||
|
||||
let _events_cleared_observer = MainRunLoopObserver::new(
|
||||
mtm,
|
||||
CFRunLoopActivity::BeforeWaiting,
|
||||
true,
|
||||
// Queued with the lowest priority to ensure it is processed after other observers.
|
||||
CFIndex::MAX,
|
||||
move |_| app_state::handle_events_cleared(mtm),
|
||||
);
|
||||
main_loop.add_observer(&_events_cleared_observer, mode);
|
||||
|
||||
Ok(EventLoop {
|
||||
mtm,
|
||||
window_target: ActiveEventLoop { mtm },
|
||||
@@ -235,6 +277,9 @@ impl EventLoop {
|
||||
_did_enter_background_observer,
|
||||
_will_terminate_observer,
|
||||
_did_receive_memory_warning_observer,
|
||||
_wakeup_observer,
|
||||
_main_events_cleared_observer,
|
||||
_events_cleared_observer,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -262,97 +307,3 @@ impl EventLoop {
|
||||
&self.window_target
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_control_flow_observers() {
|
||||
unsafe {
|
||||
// begin is queued with the highest priority to ensure it is processed before other
|
||||
// observers
|
||||
extern "C-unwind" fn control_flow_begin_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
||||
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
|
||||
// priority to be 0, in order to send AboutToWait before RedrawRequested. This value was
|
||||
// chosen conservatively to guard against apple using different priorities for their redraw
|
||||
// observers in different OS's or on different devices. If it so happens that it's too
|
||||
// conservative, the main symptom would be non-redraw events coming in after `AboutToWait`.
|
||||
//
|
||||
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
|
||||
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
||||
//
|
||||
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
|
||||
extern "C-unwind" fn control_flow_main_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm),
|
||||
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
||||
extern "C-unwind" fn control_flow_end_handler(
|
||||
_: *mut CFRunLoopObserver,
|
||||
activity: CFRunLoopActivity,
|
||||
_: *mut c_void,
|
||||
) {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
#[allow(non_upper_case_globals)]
|
||||
match activity {
|
||||
CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm),
|
||||
CFRunLoopActivity::Exit => {}, // may happen when running on macOS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let main_loop = CFRunLoop::main().unwrap();
|
||||
|
||||
let begin_observer = CFRunLoopObserver::new(
|
||||
None,
|
||||
CFRunLoopActivity::AfterWaiting.0,
|
||||
true,
|
||||
CFIndex::MIN,
|
||||
Some(control_flow_begin_handler),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
.unwrap();
|
||||
main_loop.add_observer(Some(&begin_observer), kCFRunLoopDefaultMode);
|
||||
|
||||
let main_end_observer = CFRunLoopObserver::new(
|
||||
None,
|
||||
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
|
||||
true,
|
||||
0, // see comment on `control_flow_main_end_handler`
|
||||
Some(control_flow_main_end_handler),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
.unwrap();
|
||||
main_loop.add_observer(Some(&main_end_observer), kCFRunLoopDefaultMode);
|
||||
|
||||
let end_observer = CFRunLoopObserver::new(
|
||||
None,
|
||||
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
|
||||
true,
|
||||
CFIndex::MAX,
|
||||
Some(control_flow_end_handler),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
.unwrap();
|
||||
main_loop.add_observer(Some(&end_observer), kCFRunLoopDefaultMode);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user