1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! 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::{Span, 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:]` to queue a closure to run
/// the next time runloop sources are processed.
///
/// 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) {
// We use this to run a closure asynchronously at a later point, so it also makes sense to
// re-enter the current span when running the queued closure.
let span = Span::current();
// Convert `FnOnce()` to `Block<dyn Fn()>`.
let closure = Cell::new(Some(closure));
let block = block2::RcBlock::new(move || {
// Running this block happens inside a `CFRunLoopSource`, but the spans that we emit for
// that are (intentionally) overwritten by entering the span from before.
let _enter = span.enter();
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));
}
}