Skip to content

Commit

Permalink
Android: handle suspend / resume (#9937)
Browse files Browse the repository at this point in the history
# 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
  • Loading branch information
mockersf authored and ameknite committed Oct 3, 2023
1 parent f647d10 commit 1313f9d
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 6 deletions.
14 changes: 14 additions & 0 deletions crates/bevy_render/src/view/window/mod.rs
Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
53 changes: 47 additions & 6 deletions crates/bevy_winit/src/lib.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 1313f9d

Please sign in to comment.