Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

android: Hold NativeWindow lock until after notifying the user with Event::Suspended #2307

Merged
merged 1 commit into from
Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre

# Unreleased

- On Android, `ndk-glue`'s `NativeWindow` lock is now held between `Event::Resumed` and `Event::Suspended`.
- On Web, added `EventLoopExtWebSys` with a `spawn` method to start the event loop without throwing an exception.
- Added `WindowEvent::Occluded(bool)`, currently implemented on macOS and X11.
- On X11, fix events for caps lock key not being sent
Expand Down Expand Up @@ -64,7 +65,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`.
- **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior.
- On Wayland, add support for `Window::set_cursor_position`.
- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value.
- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value.
- `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once.
- Added `From<u64>` for `WindowId` and `From<WindowId>` for `u64`.
- Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate.
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ simple_logger = "2.1.0"

[target.'cfg(target_os = "android")'.dependencies]
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
ndk = { git = "https://github.com/rust-windowing/android-ndk-rs", rev = "7e33384" }
ndk-glue = { git = "https://github.com/rust-windowing/android-ndk-rs", rev = "7e33384" }
ndk = { git = "https://github.com/rust-windowing/android-ndk-rs", rev = "a0c5e99" }
ndk-glue = { git = "https://github.com/rust-windowing/android-ndk-rs", rev = "a0c5e99" }

[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
objc = "0.2.7"
Expand Down
53 changes: 38 additions & 15 deletions src/platform_impl/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ use ndk::{
configuration::Configuration,
event::{InputEvent, KeyAction, Keycode, MotionAction},
looper::{ForeignLooper, Poll, ThreadLooper},
native_window::NativeWindow,
};
use ndk_glue::{Event, Rect};
use ndk_glue::{Event, LockReadGuard, Rect};
use once_cell::sync::Lazy;
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};

Expand Down Expand Up @@ -239,6 +240,7 @@ pub struct EventLoop<T: 'static> {
start_cause: event::StartCause,
looper: ThreadLooper,
running: bool,
window_lock: Option<LockReadGuard<NativeWindow>>,
}

#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -268,6 +270,7 @@ impl<T: 'static> EventLoop<T> {
start_cause: event::StartCause::Init,
looper: ThreadLooper::for_thread().unwrap(),
running: false,
window_lock: None,
}
}

Expand Down Expand Up @@ -300,22 +303,42 @@ impl<T: 'static> EventLoop<T> {
match self.first_event.take() {
Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() {
Event::WindowCreated => {
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event::Event::Resumed
);
// Acquire a lock on the window to prevent Android from destroying
// it until we've notified and waited for the user in Event::Suspended.
// WARNING: ndk-glue is inherently racy (https://github.com/rust-windowing/winit/issues/2293)
// and may have already received onNativeWindowDestroyed while this thread hasn't yet processed
// the event, and would see a `None` lock+window in that case.
if let Some(next_window_lock) = ndk_glue::native_window() {
assert!(
self.window_lock.replace(next_window_lock).is_none(),
"Received `Event::WindowCreated` while we were already holding a lock"
);
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event::Event::Resumed
);
} else {
warn!("Received `Event::WindowCreated` while `ndk_glue::native_window()` provides no window");
}
}
Event::WindowResized => resized = true,
Event::WindowRedrawNeeded => redraw = true,
Event::WindowDestroyed => {
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event::Event::Suspended
);
// Release the lock, allowing Android to clean up this surface
// WARNING: See above - if ndk-glue is racy, this event may be called
// without having a `self.window_lock` in place.
if self.window_lock.take().is_some() {
call_event_handler!(
event_handler,
self.window_target(),
control_flow,
event::Event::Suspended
);
} else {
warn!("Received `Event::WindowDestroyed` while we were not holding a window lock");
}
}
Event::Pause => self.running = false,
Event::Resume => self.running = true,
Expand Down Expand Up @@ -369,7 +392,7 @@ impl<T: 'static> EventLoop<T> {
},
Some(EventSource::InputQueue) => {
if let Some(input_queue) = ndk_glue::input_queue().as_ref() {
while let Some(event) = input_queue.get_event() {
while let Some(event) = input_queue.get_event().expect("get_event") {
if let Some(event) = input_queue.pre_dispatch(event) {
let mut handled = true;
let window_id = window::WindowId(WindowId);
Expand Down Expand Up @@ -799,7 +822,7 @@ impl Window {
}

pub fn raw_window_handle(&self) -> RawWindowHandle {
if let Some(native_window) = ndk_glue::native_window().as_ref() {
if let Some(native_window) = ndk_glue::native_window() {
native_window.raw_window_handle()
} else {
panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
Expand Down
13 changes: 11 additions & 2 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1039,8 +1039,17 @@ unsafe impl raw_window_handle::HasRawWindowHandle for Window {
///
/// ## Platform-specific
///
/// - **Android:** Only available after receiving the Resumed event and before Suspended. *If you*
/// *try to get the handle outside of that period, this function will panic*!
/// ### Android
///
/// Only available after receiving [`Event::Resumed`] and before [`Event::Suspended`]. *If you
/// try to get the handle outside of that period, this function will panic*!
///
/// Make sure to release or destroy any resources created from this `RawWindowHandle` (ie. Vulkan
/// or OpenGL surfaces) before returning from [`Event::Suspended`], at which point Android will
/// release the underlying window/surface: any subsequent interaction is undefined behavior.
///
/// [`Event::Resumed`]: crate::event::Event::Resumed
/// [`Event::Suspended`]: crate::event::Event::Suspended
fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
self.window.raw_window_handle()
}
Expand Down