iOS: Refactor event handling to share code with macOS (#3865)
Instead of storing the event handler within the AppState, and extracting it our every time we need it, we now use the same event handling implementation as for macOS that ensures we don't re-entrantly call the event handler, and that we un-register the handler again after we're done using it (`UIApplicationMain` won't return, but may still unwind, so this is very important for panic safety).
This commit is contained in:
parent
7fbc2118b6
commit
a61e7bb4f4
6 changed files with 147 additions and 219 deletions
|
|
@ -8,7 +8,7 @@ use std::time::Instant;
|
|||
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy};
|
||||
use objc2_foundation::{MainThreadMarker, NSNotification};
|
||||
|
||||
use super::event_handler::EventHandler;
|
||||
use super::super::event_handler::EventHandler;
|
||||
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
|
||||
use super::observer::{EventLoopWaker, RunLoop};
|
||||
use super::{menu, WindowId};
|
||||
|
|
@ -291,7 +291,7 @@ impl AppState {
|
|||
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop),
|
||||
) {
|
||||
let event_loop = ActiveEventLoop { app_state: Rc::clone(self), mtm: self.mtm };
|
||||
self.event_handler.handle(callback, &event_loop);
|
||||
self.event_handler.handle(|app| callback(app, &event_loop));
|
||||
}
|
||||
|
||||
/// dispatch `NewEvents(Init)` + `Resumed`
|
||||
|
|
|
|||
|
|
@ -1,138 +0,0 @@
|
|||
use std::cell::RefCell;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use crate::application::ApplicationHandler;
|
||||
use crate::platform_impl::ActiveEventLoop;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct EventHandler {
|
||||
/// This can be in the following states:
|
||||
/// - Not registered by the event loop (None).
|
||||
/// - Present (Some(handler)).
|
||||
/// - Currently executing the handler / in use (RefCell borrowed).
|
||||
inner: RefCell<Option<&'static mut dyn ApplicationHandler>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for EventHandler {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let state = match self.inner.try_borrow().as_deref() {
|
||||
Ok(Some(_)) => "<available>",
|
||||
Ok(None) => "<not set>",
|
||||
Err(_) => "<in use>",
|
||||
};
|
||||
f.debug_struct("EventHandler").field("state", &state).finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventHandler {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { inner: RefCell::new(None) }
|
||||
}
|
||||
|
||||
/// Set the event loop handler for the duration of the given closure.
|
||||
///
|
||||
/// This is similar to using the `scoped-tls` or `scoped-tls-hkt` crates
|
||||
/// to store the handler in a thread local, such that it can be accessed
|
||||
/// from within the closure.
|
||||
pub(crate) fn set<'handler, R>(
|
||||
&self,
|
||||
app: &'handler mut dyn ApplicationHandler,
|
||||
closure: impl FnOnce() -> R,
|
||||
) -> R {
|
||||
// SAFETY: We extend the lifetime of the handler here so that we can
|
||||
// store it in `EventHandler`'s `RefCell`.
|
||||
//
|
||||
// This is sound, since we make sure to unset the handler again at the
|
||||
// end of this function, and as such the lifetime isn't actually
|
||||
// extended beyond `'handler`.
|
||||
let handler = unsafe {
|
||||
mem::transmute::<
|
||||
&'handler mut dyn ApplicationHandler,
|
||||
&'static mut dyn ApplicationHandler,
|
||||
>(app)
|
||||
};
|
||||
|
||||
match self.inner.try_borrow_mut().as_deref_mut() {
|
||||
Ok(Some(_)) => {
|
||||
unreachable!("tried to set handler while another was already set");
|
||||
},
|
||||
Ok(data @ None) => {
|
||||
*data = Some(handler);
|
||||
},
|
||||
Err(_) => {
|
||||
unreachable!("tried to set handler that is currently in use");
|
||||
},
|
||||
}
|
||||
|
||||
struct ClearOnDrop<'a>(&'a EventHandler);
|
||||
|
||||
impl Drop for ClearOnDrop<'_> {
|
||||
fn drop(&mut self) {
|
||||
match self.0.inner.try_borrow_mut().as_deref_mut() {
|
||||
Ok(data @ Some(_)) => {
|
||||
*data = None;
|
||||
},
|
||||
Ok(None) => {
|
||||
tracing::error!("tried to clear handler, but no handler was set");
|
||||
},
|
||||
Err(_) => {
|
||||
// Note: This is not expected to ever happen, this
|
||||
// module generally controls the `RefCell`, and
|
||||
// prevents it from ever being borrowed outside of it.
|
||||
//
|
||||
// But if it _does_ happen, it is a serious error, and
|
||||
// we must abort the process, it'd be unsound if we
|
||||
// weren't able to unset the handler.
|
||||
eprintln!("tried to clear handler that is currently in use");
|
||||
std::process::abort();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _clear_on_drop = ClearOnDrop(self);
|
||||
|
||||
// Note: The RefCell should not be borrowed while executing the
|
||||
// closure, that'd defeat the whole point.
|
||||
closure()
|
||||
|
||||
// `_clear_on_drop` will be dropped here, or when unwinding, ensuring
|
||||
// soundness.
|
||||
}
|
||||
|
||||
pub(crate) fn in_use(&self) -> bool {
|
||||
self.inner.try_borrow().is_err()
|
||||
}
|
||||
|
||||
pub(crate) fn ready(&self) -> bool {
|
||||
matches!(self.inner.try_borrow().as_deref(), Ok(Some(_)))
|
||||
}
|
||||
|
||||
pub(crate) fn handle(
|
||||
&self,
|
||||
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop),
|
||||
event_loop: &ActiveEventLoop,
|
||||
) {
|
||||
match self.inner.try_borrow_mut().as_deref_mut() {
|
||||
Ok(Some(user_app)) => {
|
||||
// It is important that we keep the reference borrowed here,
|
||||
// so that `in_use` can properly detect that the handler is
|
||||
// still in use.
|
||||
//
|
||||
// If the handler unwinds, the `RefMut` will ensure that the
|
||||
// handler is no longer borrowed.
|
||||
callback(*user_app, event_loop);
|
||||
},
|
||||
Ok(None) => {
|
||||
// `NSApplication`, our app state and this handler are all
|
||||
// global state and so it's not impossible that we could get
|
||||
// an event after the application has exited the `EventLoop`.
|
||||
tracing::error!("tried to run event handler, but no handler was set");
|
||||
},
|
||||
Err(_) => {
|
||||
// Prevent re-entrancy.
|
||||
panic!("tried to handle event while another event is currently being handled");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ mod app;
|
|||
mod app_state;
|
||||
mod cursor;
|
||||
mod event;
|
||||
mod event_handler;
|
||||
mod event_loop;
|
||||
mod ffi;
|
||||
mod menu;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue