macOS: Refactor event handler storage (#3532)
This makes our use of `unsafe` to make the event handler temporarily 'static be local to a module, in a way that's (hopefully) much easier to reason about.
This commit is contained in:
parent
a4480a0652
commit
a5dbd3ee52
4 changed files with 213 additions and 234 deletions
|
|
@ -1,11 +1,10 @@
|
|||
use std::{
|
||||
any::Any,
|
||||
cell::{Cell, RefCell},
|
||||
cell::Cell,
|
||||
collections::VecDeque,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
os::raw::c_void,
|
||||
panic::{catch_unwind, resume_unwind, AssertUnwindSafe, RefUnwindSafe, UnwindSafe},
|
||||
panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe},
|
||||
ptr,
|
||||
rc::{Rc, Weak},
|
||||
sync::mpsc,
|
||||
|
|
@ -79,6 +78,15 @@ pub struct ActiveEventLoop {
|
|||
}
|
||||
|
||||
impl ActiveEventLoop {
|
||||
pub(super) fn new_root(delegate: Id<ApplicationDelegate>) -> RootWindowTarget {
|
||||
let mtm = MainThreadMarker::from(&*delegate);
|
||||
let p = Self { delegate, mtm };
|
||||
RootWindowTarget {
|
||||
p,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_custom_cursor(&self, source: CustomCursorSource) -> RootCustomCursor {
|
||||
RootCustomCursor {
|
||||
inner: CustomCursor::new(source.inner),
|
||||
|
|
@ -188,17 +196,8 @@ pub struct EventLoop<T: 'static> {
|
|||
sender: mpsc::Sender<T>,
|
||||
receiver: Rc<mpsc::Receiver<T>>,
|
||||
|
||||
window_target: Rc<RootWindowTarget>,
|
||||
window_target: RootWindowTarget,
|
||||
panic_info: Rc<PanicInfo>,
|
||||
|
||||
/// We make sure that the callback closure is dropped during a panic
|
||||
/// by making the event loop own it.
|
||||
///
|
||||
/// Every other reference should be a Weak reference which is only upgraded
|
||||
/// into a strong reference in order to call the callback but then the
|
||||
/// strong reference should be dropped as soon as possible.
|
||||
#[allow(clippy::type_complexity)]
|
||||
_callback: Option<Rc<RefCell<dyn FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget)>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
|
|
@ -257,12 +256,11 @@ impl<T> EventLoop<T> {
|
|||
delegate: delegate.clone(),
|
||||
sender,
|
||||
receiver: Rc::new(receiver),
|
||||
window_target: Rc::new(RootWindowTarget {
|
||||
window_target: RootWindowTarget {
|
||||
p: ActiveEventLoop { delegate, mtm },
|
||||
_marker: PhantomData,
|
||||
}),
|
||||
},
|
||||
panic_info,
|
||||
_callback: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -270,56 +268,25 @@ impl<T> EventLoop<T> {
|
|||
&self.window_target
|
||||
}
|
||||
|
||||
pub fn run<F>(mut self, callback: F) -> Result<(), EventLoopError>
|
||||
pub fn run<F>(mut self, handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<T>, &RootWindowTarget),
|
||||
{
|
||||
self.run_on_demand(callback)
|
||||
self.run_on_demand(handler)
|
||||
}
|
||||
|
||||
// NB: we don't base this on `pump_events` because for `MacOs` we can't support
|
||||
// `pump_events` elegantly (we just ask to run the loop for a "short" amount of
|
||||
// time and so a layered implementation would end up using a lot of CPU due to
|
||||
// redundant wake ups.
|
||||
pub fn run_on_demand<F>(&mut self, callback: F) -> Result<(), EventLoopError>
|
||||
pub fn run_on_demand<F>(&mut self, handler: F) -> Result<(), EventLoopError>
|
||||
where
|
||||
F: FnMut(Event<T>, &RootWindowTarget),
|
||||
{
|
||||
let callback = map_user_event(callback, self.receiver.clone());
|
||||
let handler = map_user_event(handler, self.receiver.clone());
|
||||
|
||||
// # Safety
|
||||
// We are erasing the lifetime of the application callback here so that we
|
||||
// can (temporarily) store it within 'static app delegate that's
|
||||
// accessible to objc delegate callbacks.
|
||||
//
|
||||
// The safety of this depends on on making sure to also clear the callback
|
||||
// from the app delegate before we return from here, ensuring that we don't
|
||||
// retain a reference beyond the real lifetime of the callback.
|
||||
let callback = unsafe {
|
||||
mem::transmute::<
|
||||
Rc<RefCell<dyn FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget)>>,
|
||||
Rc<RefCell<dyn FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget)>>,
|
||||
>(Rc::new(RefCell::new(callback)))
|
||||
};
|
||||
|
||||
self._callback = Some(Rc::clone(&callback));
|
||||
|
||||
autoreleasepool(|_| {
|
||||
// A bit of juggling with the callback references to make sure
|
||||
// that `self.callback` is the only owner of the callback.
|
||||
let weak_cb: Weak<_> = Rc::downgrade(&callback);
|
||||
drop(callback);
|
||||
|
||||
// # Safety
|
||||
// We make sure to call `delegate.clear_callback` before returning
|
||||
unsafe {
|
||||
self.delegate
|
||||
.set_callback(weak_cb, Rc::clone(&self.window_target));
|
||||
}
|
||||
|
||||
// catch panics to make sure we can't unwind without clearing the set callback
|
||||
// (which would leave the app delegate in an undefined, unsafe state)
|
||||
let catch_result = catch_unwind(AssertUnwindSafe(|| {
|
||||
self.delegate.set_event_handler(handler, || {
|
||||
autoreleasepool(|_| {
|
||||
// clear / normalize pump_events state
|
||||
self.delegate.set_wait_timeout(None);
|
||||
self.delegate.set_stop_before_wait(false);
|
||||
|
|
@ -331,6 +298,8 @@ impl<T> EventLoop<T> {
|
|||
self.delegate.set_is_running(true);
|
||||
self.delegate.dispatch_init_events();
|
||||
}
|
||||
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
|
||||
// While the app is running it's possible that we catch a panic
|
||||
|
|
@ -343,73 +312,28 @@ impl<T> EventLoop<T> {
|
|||
}
|
||||
|
||||
self.delegate.internal_exit()
|
||||
}));
|
||||
|
||||
// # Safety
|
||||
// This pairs up with the `unsafe` call to `set_callback` above and ensures that
|
||||
// we always clear the application callback from the app delegate before returning.
|
||||
drop(self._callback.take());
|
||||
self.delegate.clear_callback();
|
||||
|
||||
if let Err(payload) = catch_result {
|
||||
resume_unwind(payload)
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, callback: F) -> PumpStatus
|
||||
pub fn pump_events<F>(&mut self, timeout: Option<Duration>, handler: F) -> PumpStatus
|
||||
where
|
||||
F: FnMut(Event<T>, &RootWindowTarget),
|
||||
{
|
||||
let callback = map_user_event(callback, self.receiver.clone());
|
||||
let handler = map_user_event(handler, self.receiver.clone());
|
||||
|
||||
// # Safety
|
||||
// We are erasing the lifetime of the application callback here so that we
|
||||
// can (temporarily) store it within 'static global app delegate that's
|
||||
// accessible to objc delegate callbacks.
|
||||
//
|
||||
// The safety of this depends on on making sure to also clear the callback
|
||||
// from the app delegate before we return from here, ensuring that we don't
|
||||
// retain a reference beyond the real lifetime of the callback.
|
||||
|
||||
let callback = unsafe {
|
||||
mem::transmute::<
|
||||
Rc<RefCell<dyn FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget)>>,
|
||||
Rc<RefCell<dyn FnMut(Event<HandlePendingUserEvents>, &RootWindowTarget)>>,
|
||||
>(Rc::new(RefCell::new(callback)))
|
||||
};
|
||||
|
||||
self._callback = Some(Rc::clone(&callback));
|
||||
|
||||
autoreleasepool(|_| {
|
||||
// A bit of juggling with the callback references to make sure
|
||||
// that `self.callback` is the only owner of the callback.
|
||||
let weak_cb: Weak<_> = Rc::downgrade(&callback);
|
||||
drop(callback);
|
||||
|
||||
// # Safety
|
||||
// We will make sure to call `delegate.clear_callback` before returning
|
||||
// to ensure that we don't hold on to the callback beyond its (erased)
|
||||
// lifetime
|
||||
unsafe {
|
||||
self.delegate
|
||||
.set_callback(weak_cb, Rc::clone(&self.window_target));
|
||||
}
|
||||
|
||||
// catch panics to make sure we can't unwind without clearing the set callback
|
||||
// (which would leave the app delegate in an undefined, unsafe state)
|
||||
let catch_result = catch_unwind(AssertUnwindSafe(|| {
|
||||
self.delegate.set_event_handler(handler, || {
|
||||
autoreleasepool(|_| {
|
||||
// As a special case, if the application hasn't been launched yet then we at least run
|
||||
// the loop until it has fully launched.
|
||||
if !self.delegate.is_launched() {
|
||||
debug_assert!(!self.delegate.is_running());
|
||||
|
||||
self.delegate.set_stop_on_launch();
|
||||
unsafe {
|
||||
self.app.run();
|
||||
}
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
|
||||
// Note: we dispatch `NewEvents(Init)` + `Resumed` events after the application has launched
|
||||
} else if !self.delegate.is_running() {
|
||||
|
|
@ -438,9 +362,8 @@ impl<T> EventLoop<T> {
|
|||
}
|
||||
}
|
||||
self.delegate.set_stop_on_redraw(true);
|
||||
unsafe {
|
||||
self.app.run();
|
||||
}
|
||||
// SAFETY: We do not run the application re-entrantly
|
||||
unsafe { self.app.run() };
|
||||
}
|
||||
|
||||
// While the app is running it's possible that we catch a panic
|
||||
|
|
@ -458,18 +381,7 @@ impl<T> EventLoop<T> {
|
|||
} else {
|
||||
PumpStatus::Continue
|
||||
}
|
||||
}));
|
||||
|
||||
// # Safety
|
||||
// This pairs up with the `unsafe` call to `set_callback` above and ensures that
|
||||
// we always clear the application callback from the app delegate before returning
|
||||
self.delegate.clear_callback();
|
||||
drop(self._callback.take());
|
||||
|
||||
match catch_result {
|
||||
Ok(pump_status) => pump_status,
|
||||
Err(payload) => resume_unwind(payload),
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue