Android: handle suspend / resume (#9937)

# Objective

- Handle suspend / resume events on Android without exiting

## Solution

- On suspend: despawn the window, and set the control flow to wait for
events from the OS
- On resume: spawn a new window, and set the control flow to poll


In this video, you can see the Android example being suspended, stopping
receiving events, and working again after being resumed



https://github.com/bevyengine/bevy/assets/8672791/aaaf4b09-ee6a-4a0d-87ad-41f05def7945
This commit is contained in:
François 2023-10-02 15:06:13 +02:00 committed by GitHub
parent 1bf271d56e
commit eb1effa643
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 61 additions and 6 deletions

View file

@ -109,6 +109,8 @@ fn extract_windows(
screenshot_manager: Extract<Res<ScreenshotManager>>,
mut closed: Extract<EventReader<WindowClosed>>,
windows: Extract<Query<(Entity, &Window, &RawHandleWrapper, Option<&PrimaryWindow>)>>,
mut removed: Extract<RemovedComponents<RawHandleWrapper>>,
mut window_surfaces: ResMut<WindowSurfaces>,
) {
for (entity, window, handle, primary) in windows.iter() {
if primary.is_some() {
@ -166,6 +168,11 @@ fn extract_windows(
for closed_window in closed.read() {
extracted_windows.remove(&closed_window.window);
window_surfaces.remove(&closed_window.window);
}
for removed_window in removed.read() {
extracted_windows.remove(&removed_window);
window_surfaces.remove(&removed_window);
}
// This lock will never block because `callbacks` is `pub(crate)` and this is the singular callsite where it's locked.
// Even if a user had multiple copies of this system, since the system has a mutable resource access the two systems would never run
@ -195,6 +202,13 @@ pub struct WindowSurfaces {
configured_windows: HashSet<Entity>,
}
impl WindowSurfaces {
fn remove(&mut self, window: &Entity) {
self.surfaces.remove(window);
self.configured_windows.remove(window);
}
}
/// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering.
///
/// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is

View file

@ -43,6 +43,8 @@ use bevy_window::{
WindowCloseRequested, WindowCreated, WindowDestroyed, WindowFocused, WindowMoved,
WindowResized, WindowScaleFactorChanged, WindowThemeChanged,
};
#[cfg(target_os = "android")]
use bevy_window::{PrimaryWindow, RawHandleWrapper};
#[cfg(target_os = "android")]
pub use winit::platform::android::activity::AndroidApp;
@ -664,16 +666,55 @@ pub fn winit_runner(mut app: App) {
runner_state.is_active = false;
#[cfg(target_os = "android")]
{
// Android sending this event invalidates all render surfaces.
// TODO
// Upon resume, check if the new render surfaces are compatible with the
// existing render device. If not (which should basically never happen),
// then try to rebuild the renderer.
*control_flow = ControlFlow::Exit;
// Remove the `RawHandleWrapper` from the primary window.
// This will trigger the surface destruction.
let mut query = app.world.query_filtered::<Entity, With<PrimaryWindow>>();
let entity = query.single(&app.world);
app.world.entity_mut(entity).remove::<RawHandleWrapper>();
*control_flow = ControlFlow::Wait;
}
}
event::Event::Resumed => {
runner_state.is_active = true;
#[cfg(target_os = "android")]
{
// Get windows that are cached but without raw handles. Those window were already created, but got their
// handle wrapper removed when the app was suspended.
let mut query = app
.world
.query_filtered::<(Entity, &Window), (With<CachedWindow>, Without<bevy_window::RawHandleWrapper>)>();
if let Ok((entity, window)) = query.get_single(&app.world) {
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
let window = window.clone();
let (
_,
_,
_,
mut winit_windows,
mut adapters,
mut handlers,
accessibility_requested,
) = create_window_system_state.get_mut(&mut app.world);
let winit_window = winit_windows.create_window(
event_loop,
entity,
&window,
&mut adapters,
&mut handlers,
&accessibility_requested,
);
let wrapper = RawHandleWrapper {
window_handle: winit_window.raw_window_handle(),
display_handle: winit_window.raw_display_handle(),
};
app.world.entity_mut(entity).insert(wrapper);
}
*control_flow = ControlFlow::Poll;
}
}
event::Event::MainEventsCleared => {
if runner_state.is_active {