[macos] Avoid panic when callback is None.

This can happen when window is destroyed/created during a call to user
callback as this causes WindowDelegate method to be called.

Instead if the user callback is `None` store the event in
`pending_events`.
This commit is contained in:
mitchmindtree 2017-03-19 19:09:20 +11:00
parent 4b39f81621
commit 41e7572147
2 changed files with 42 additions and 14 deletions

View file

@ -19,7 +19,7 @@ pub struct EventsLoop {
// //
// This is *only* `Some` for the duration of a call to either of these methods and will be // This is *only* `Some` for the duration of a call to either of these methods and will be
// `None` otherwise. // `None` otherwise.
pub user_callback: UserCallback, user_callback: UserCallback,
} }
struct Modifiers { struct Modifiers {
@ -61,15 +61,19 @@ impl UserCallback {
// Emits the given event via the user-given callback. // Emits the given event via the user-given callback.
// //
// This is *only* called within the `poll_events` and `run_forever` methods so we know that it
// is safe to `unwrap` the callback without causing a panic as there must be at least one
// callback stored.
//
// This is unsafe as it requires dereferencing the pointer to the user-given callback. We // This is unsafe as it requires dereferencing the pointer to the user-given callback. We
// guarantee this is safe by ensuring the `UserCallback` never lives longer than the user-given // guarantee this is safe by ensuring the `UserCallback` never lives longer than the user-given
// callback. // callback.
pub unsafe fn call_with_event(&self, event: Event) { //
let callback: *mut FnMut(Event) = self.mutex.lock().unwrap().take().unwrap(); // Note that the callback may not always be `Some`. This is because some `NSWindowDelegate`
// callbacks can be triggered by means other than `NSApp().sendEvent`. For example, if a window
// is destroyed or created during a call to the user's callback, the `WindowDelegate` methods
// may be called with `windowShouldClose` or `windowDidResignKey`.
unsafe fn call_with_event(&self, event: Event) {
let callback = match self.mutex.lock().unwrap().take() {
Some(callback) => callback,
None => return,
};
(*callback)(event); (*callback)(event);
*self.mutex.lock().unwrap() = Some(callback); *self.mutex.lock().unwrap() = Some(callback);
} }
@ -117,9 +121,7 @@ impl EventsLoop {
loop { loop {
unsafe { unsafe {
// First, yield all pending events. // First, yield all pending events.
while let Some(event) = self.pending_events.lock().unwrap().pop_front() { self.call_user_callback_with_pending_events();
self.user_callback.call_with_event(event);
}
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
@ -161,9 +163,7 @@ impl EventsLoop {
loop { loop {
unsafe { unsafe {
// First, yield all pending events. // First, yield all pending events.
while let Some(event) = self.pending_events.lock().unwrap().pop_front() { self.call_user_callback_with_pending_events();
self.user_callback.call_with_event(event);
}
let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil);
@ -217,6 +217,34 @@ impl EventsLoop {
} }
} }
fn call_user_callback_with_pending_events(&self) {
loop {
let event = match self.pending_events.lock().unwrap().pop_front() {
Some(event) => event,
None => return,
};
unsafe {
self.user_callback.call_with_event(event);
}
}
}
// Calls the user callback if one exists.
//
// Otherwise, stores the event in the `pending_events` queue.
//
// This is necessary for the case when `WindowDelegate` callbacks are triggered during a call
// to the user's callback.
pub fn call_user_callback_with_event_or_store_in_pending(&self, event: Event) {
if self.user_callback.mutex.lock().unwrap().is_some() {
unsafe {
self.user_callback.call_with_event(event);
}
} else {
self.pending_events.lock().unwrap().push_back(event);
}
}
// Convert some given `NSEvent` into a winit `Event`. // Convert some given `NSEvent` into a winit `Event`.
unsafe fn ns_event_to_event(&self, ns_event: cocoa::base::id) -> Option<Event> { unsafe fn ns_event_to_event(&self, ns_event: cocoa::base::id) -> Option<Event> {
if ns_event == cocoa::base::nil { if ns_event == cocoa::base::nil {

View file

@ -57,7 +57,7 @@ impl WindowDelegate {
}; };
if let Some(events_loop) = state.events_loop.upgrade() { if let Some(events_loop) = state.events_loop.upgrade() {
events_loop.user_callback.call_with_event(event); events_loop.call_user_callback_with_event_or_store_in_pending(event);
} }
} }