|
1 | | -//! Utilities for working with `CFRunLoop`. |
2 | | -//! |
3 | | -//! See Apple's documentation on Run Loops for details: |
4 | | -//! <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html> |
5 | | -use std::cell::Cell; |
6 | 1 | use std::ffi::c_void; |
7 | 2 | use std::ptr; |
8 | 3 | use std::time::Instant; |
9 | 4 |
|
10 | | -use objc2::MainThreadMarker; |
11 | 5 | use objc2_core_foundation::{ |
12 | | - kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFAbsoluteTimeGetCurrent, CFIndex, CFRetained, |
13 | | - CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, CFRunLoopObserverCallBack, |
14 | | - CFRunLoopObserverContext, CFRunLoopTimer, |
| 6 | + kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRetained, CFRunLoop, CFRunLoopTimer, |
15 | 7 | }; |
16 | | -use tracing::error; |
17 | | - |
18 | | -use super::app_state::AppState; |
19 | | - |
20 | | -// begin is queued with the highest priority to ensure it is processed before other observers |
21 | | -extern "C-unwind" fn control_flow_begin_handler( |
22 | | - _: *mut CFRunLoopObserver, |
23 | | - activity: CFRunLoopActivity, |
24 | | - _info: *mut c_void, |
25 | | -) { |
26 | | - match activity { |
27 | | - CFRunLoopActivity::AfterWaiting => { |
28 | | - AppState::get(MainThreadMarker::new().unwrap()).wakeup(); |
29 | | - }, |
30 | | - _ => unreachable!(), |
31 | | - } |
32 | | -} |
33 | | - |
34 | | -// end is queued with the lowest priority to ensure it is processed after other observers |
35 | | -// without that, LoopExiting would get sent after AboutToWait |
36 | | -extern "C-unwind" fn control_flow_end_handler( |
37 | | - _: *mut CFRunLoopObserver, |
38 | | - activity: CFRunLoopActivity, |
39 | | - _info: *mut c_void, |
40 | | -) { |
41 | | - match activity { |
42 | | - CFRunLoopActivity::BeforeWaiting => { |
43 | | - AppState::get(MainThreadMarker::new().unwrap()).cleared(); |
44 | | - }, |
45 | | - CFRunLoopActivity::Exit => (), // unimplemented!(), // not expected to ever happen |
46 | | - _ => unreachable!(), |
47 | | - } |
48 | | -} |
49 | | - |
50 | | -#[derive(Debug)] |
51 | | -pub struct RunLoop(CFRetained<CFRunLoop>); |
52 | | - |
53 | | -impl RunLoop { |
54 | | - pub fn main(mtm: MainThreadMarker) -> Self { |
55 | | - // SAFETY: We have a MainThreadMarker here, which means we know we're on the main thread, so |
56 | | - // scheduling (and scheduling a non-`Send` block) to that thread is allowed. |
57 | | - let _ = mtm; |
58 | | - RunLoop(CFRunLoop::main().unwrap()) |
59 | | - } |
60 | | - |
61 | | - pub fn wakeup(&self) { |
62 | | - self.0.wake_up(); |
63 | | - } |
64 | | - |
65 | | - unsafe fn add_observer( |
66 | | - &self, |
67 | | - flags: CFRunLoopActivity, |
68 | | - // The lower the value, the sooner this will run |
69 | | - priority: CFIndex, |
70 | | - handler: CFRunLoopObserverCallBack, |
71 | | - context: *mut CFRunLoopObserverContext, |
72 | | - ) { |
73 | | - let observer = |
74 | | - unsafe { CFRunLoopObserver::new(None, flags.0, true, priority, handler, context) } |
75 | | - .unwrap(); |
76 | | - self.0.add_observer(Some(&observer), unsafe { kCFRunLoopCommonModes }); |
77 | | - } |
78 | | - |
79 | | - /// Submit a closure to run on the main thread as the next step in the run loop, before other |
80 | | - /// event sources are processed. |
81 | | - /// |
82 | | - /// This is used for running event handlers, as those are not allowed to run re-entrantly. |
83 | | - /// |
84 | | - /// # Implementation |
85 | | - /// |
86 | | - /// This queuing could be implemented in the following several ways with subtle differences in |
87 | | - /// timing. This list is sorted in rough order in which they are run: |
88 | | - /// |
89 | | - /// 1. Using `CFRunLoopPerformBlock` or `-[NSRunLoop performBlock:]`. |
90 | | - /// |
91 | | - /// 2. Using `-[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]` or wrapping the |
92 | | - /// event in `NSEvent` and posting that to `-[NSApplication postEvent:atStart:]` (both |
93 | | - /// creates a custom `CFRunLoopSource`, and signals that to wake up the main event loop). |
94 | | - /// |
95 | | - /// a. `atStart = true`. |
96 | | - /// |
97 | | - /// b. `atStart = false`. |
98 | | - /// |
99 | | - /// 3. `dispatch_async` or `dispatch_async_f`. Note that this may appear before 2b, it does not |
100 | | - /// respect the ordering that runloop events have. |
101 | | - /// |
102 | | - /// We choose the first one, both for ease-of-implementation, but mostly for consistency, as we |
103 | | - /// want the event to be queued in a way that preserves the order the events originally arrived |
104 | | - /// in. |
105 | | - /// |
106 | | - /// As an example, let's assume that we receive two events from the user, a mouse click which we |
107 | | - /// handled by queuing it, and a window resize which we handled immediately. If we allowed |
108 | | - /// AppKit to choose the ordering when queuing the mouse event, it might get put in the back of |
109 | | - /// the queue, and the events would appear out of order to the user of Winit. So we must instead |
110 | | - /// put the event at the very front of the queue, to be handled as soon as possible after |
111 | | - /// handling whatever event it's currently handling. |
112 | | - pub fn queue_closure(&self, closure: impl FnOnce() + 'static) { |
113 | | - // Convert `FnOnce()` to `Block<dyn Fn()>`. |
114 | | - let closure = Cell::new(Some(closure)); |
115 | | - let block = block2::RcBlock::new(move || { |
116 | | - if let Some(closure) = closure.take() { |
117 | | - closure() |
118 | | - } else { |
119 | | - error!("tried to execute queued closure on main thread twice"); |
120 | | - } |
121 | | - }); |
122 | | - |
123 | | - // There are a few common modes (`kCFRunLoopCommonModes`) defined by Cocoa: |
124 | | - // - `NSDefaultRunLoopMode`, alias of `kCFRunLoopDefaultMode`. |
125 | | - // - `NSEventTrackingRunLoopMode`, used when mouse-dragging and live-resizing a window. |
126 | | - // - `NSModalPanelRunLoopMode`, used when running a modal inside the Winit event loop. |
127 | | - // - `NSConnectionReplyMode`: TODO. |
128 | | - // |
129 | | - // We only want to run event handlers in the default mode, as we support running a blocking |
130 | | - // modal inside a Winit event handler (see [#1779]) which outrules the modal panel mode, and |
131 | | - // resizing such panel window enters the event tracking run loop mode, so we can't directly |
132 | | - // trigger events inside that mode either. |
133 | | - // |
134 | | - // Any events that are queued while running a modal or when live-resizing will instead wait, |
135 | | - // and be delivered to the application afterwards. |
136 | | - // |
137 | | - // [#1779]: https://github.com/rust-windowing/winit/issues/1779 |
138 | | - let mode = unsafe { kCFRunLoopDefaultMode.unwrap() }; |
139 | | - |
140 | | - // SAFETY: The runloop is valid, the mode is a `CFStringRef`, and the block is `'static`. |
141 | | - unsafe { self.0.perform_block(Some(mode), Some(&block)) } |
142 | | - } |
143 | | -} |
144 | | - |
145 | | -pub fn setup_control_flow_observers(mtm: MainThreadMarker) { |
146 | | - let run_loop = RunLoop::main(mtm); |
147 | | - unsafe { |
148 | | - let mut context = CFRunLoopObserverContext { |
149 | | - info: ptr::null_mut(), |
150 | | - version: 0, |
151 | | - retain: None, |
152 | | - release: None, |
153 | | - copyDescription: None, |
154 | | - }; |
155 | | - run_loop.add_observer( |
156 | | - CFRunLoopActivity::AfterWaiting, |
157 | | - CFIndex::MIN, |
158 | | - Some(control_flow_begin_handler), |
159 | | - &mut context as *mut _, |
160 | | - ); |
161 | | - run_loop.add_observer( |
162 | | - CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting, |
163 | | - CFIndex::MAX, |
164 | | - Some(control_flow_end_handler), |
165 | | - &mut context as *mut _, |
166 | | - ); |
167 | | - } |
168 | | -} |
169 | 8 |
|
170 | 9 | #[derive(Debug)] |
171 | 10 | pub struct EventLoopWaker { |
|
0 commit comments