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:
parent
1bf271d56e
commit
eb1effa643
@ -109,6 +109,8 @@ fn extract_windows(
|
|||||||
screenshot_manager: Extract<Res<ScreenshotManager>>,
|
screenshot_manager: Extract<Res<ScreenshotManager>>,
|
||||||
mut closed: Extract<EventReader<WindowClosed>>,
|
mut closed: Extract<EventReader<WindowClosed>>,
|
||||||
windows: Extract<Query<(Entity, &Window, &RawHandleWrapper, Option<&PrimaryWindow>)>>,
|
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() {
|
for (entity, window, handle, primary) in windows.iter() {
|
||||||
if primary.is_some() {
|
if primary.is_some() {
|
||||||
@ -166,6 +168,11 @@ fn extract_windows(
|
|||||||
|
|
||||||
for closed_window in closed.read() {
|
for closed_window in closed.read() {
|
||||||
extracted_windows.remove(&closed_window.window);
|
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.
|
// 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
|
// 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>,
|
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.
|
/// 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
|
/// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is
|
||||||
|
@ -43,6 +43,8 @@ use bevy_window::{
|
|||||||
WindowCloseRequested, WindowCreated, WindowDestroyed, WindowFocused, WindowMoved,
|
WindowCloseRequested, WindowCreated, WindowDestroyed, WindowFocused, WindowMoved,
|
||||||
WindowResized, WindowScaleFactorChanged, WindowThemeChanged,
|
WindowResized, WindowScaleFactorChanged, WindowThemeChanged,
|
||||||
};
|
};
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
use bevy_window::{PrimaryWindow, RawHandleWrapper};
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
pub use winit::platform::android::activity::AndroidApp;
|
pub use winit::platform::android::activity::AndroidApp;
|
||||||
@ -664,16 +666,55 @@ pub fn winit_runner(mut app: App) {
|
|||||||
runner_state.is_active = false;
|
runner_state.is_active = false;
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
{
|
{
|
||||||
// Android sending this event invalidates all render surfaces.
|
// Remove the `RawHandleWrapper` from the primary window.
|
||||||
// TODO
|
// This will trigger the surface destruction.
|
||||||
// Upon resume, check if the new render surfaces are compatible with the
|
let mut query = app.world.query_filtered::<Entity, With<PrimaryWindow>>();
|
||||||
// existing render device. If not (which should basically never happen),
|
let entity = query.single(&app.world);
|
||||||
// then try to rebuild the renderer.
|
app.world.entity_mut(entity).remove::<RawHandleWrapper>();
|
||||||
*control_flow = ControlFlow::Exit;
|
*control_flow = ControlFlow::Wait;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event::Event::Resumed => {
|
event::Event::Resumed => {
|
||||||
runner_state.is_active = true;
|
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 => {
|
event::Event::MainEventsCleared => {
|
||||||
if runner_state.is_active {
|
if runner_state.is_active {
|
||||||
|
Loading…
Reference in New Issue
Block a user