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

Save state on suspend on Android and iOS #5601

Merged
merged 12 commits into from
Jan 27, 2025
33 changes: 30 additions & 3 deletions crates/eframe/src/native/glow_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,25 @@ impl<'app> WinitApp for GlowWinitApp<'app> {
.and_then(|r| r.glutin.borrow().window_from_viewport.get(&id).copied())
}

fn save(&mut self) {
log::debug!("WinitApp::save called");
if let Some(running) = self.running.as_mut() {
profiling::function_scope!();

// This is used because of the "save on suspend" logic on Android. Once the application is suspended, there is no window associated to it, which was causing panics when `.window().expect()` was used.
let window_opt = running.glutin.borrow().window_opt(ViewportId::ROOT);
if window_opt.is_some() {
log::debug!("Saving application state with a window");
} else {
log::debug!("Saving application state without a window");
}

running
.integration
.save(running.app.as_mut(), window_opt.as_deref());
}
}

fn save_and_destroy(&mut self) {
if let Some(mut running) = self.running.take() {
profiling::function_scope!();
Expand Down Expand Up @@ -413,6 +432,12 @@ impl<'app> WinitApp for GlowWinitApp<'app> {
if let Some(running) = &mut self.running {
running.glutin.borrow_mut().on_suspend()?;
}
#[cfg(target_os = "android")]
{
log::debug!("Android application suspended - asking to save state");
Ok(EventResult::Save)
}
#[cfg(not(target_os = "android"))]
Ok(EventResult::Wait)
}

Expand Down Expand Up @@ -1214,10 +1239,12 @@ impl GlutinWindowContext {
.expect("viewport doesn't exist")
}

fn window_opt(&self, viewport_id: ViewportId) -> Option<Arc<Window>> {
self.viewport(viewport_id).window.clone()
}

fn window(&self, viewport_id: ViewportId) -> Arc<Window> {
self.viewport(viewport_id)
.window
.clone()
self.window_opt(viewport_id)
.expect("winit window doesn't exist")
}

Expand Down
10 changes: 10 additions & 0 deletions crates/eframe/src/native/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl<T: WinitApp> WinitAppWrapper<T> {
event_result: Result<EventResult>,
) {
let mut exit = false;
let mut save = false;

log::trace!("event_result: {event_result:?}");

Expand Down Expand Up @@ -126,6 +127,10 @@ impl<T: WinitApp> WinitAppWrapper<T> {
);
Ok(event_result)
}
EventResult::Save => {
save = true;
Ok(event_result)
}
EventResult::Exit => {
exit = true;
Ok(event_result)
Expand All @@ -139,6 +144,11 @@ impl<T: WinitApp> WinitAppWrapper<T> {
self.return_result = Err(err);
};

if save {
log::debug!("Received an EventResult::Save - saving app state");
self.winit_app.save();
}

if exit {
if self.run_and_return {
log::debug!("Asking to exit event loop…");
Expand Down
33 changes: 28 additions & 5 deletions crates/eframe/src/native/wgpu_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,13 @@ impl<'app> WinitApp for WgpuWinitApp<'app> {
)
}

fn save(&mut self) {
log::debug!("WinitApp::save called");
if let Some(running) = self.running.as_mut() {
running.save(true);
}
}

fn save_and_destroy(&mut self) {
if let Some(mut running) = self.running.take() {
running.save_and_destroy();
Expand Down Expand Up @@ -414,7 +421,12 @@ impl<'app> WinitApp for WgpuWinitApp<'app> {

fn suspended(&mut self, _: &ActiveEventLoop) -> crate::Result<EventResult> {
#[cfg(target_os = "android")]
self.drop_window()?;
{
log::debug!("Android application suspended - asking to save state");
self.drop_window()?;
Ok(EventResult::Save)
}
#[cfg(not(target_os = "android"))]
Ok(EventResult::Wait)
}

Expand Down Expand Up @@ -488,20 +500,31 @@ impl<'app> WinitApp for WgpuWinitApp<'app> {
}

impl<'app> WgpuWinitRunning<'app> {
fn save_and_destroy(&mut self) {
profiling::function_scope!();

let mut shared = self.shared.borrow_mut();
/// Saves the application state. Set `force_save_without_window` to `true` if the state should be saved even if no window is associated to the application (this happens on Android when the `suspended` event is received).
fn save(&mut self, force_save_without_window: bool) {
let shared = self.shared.borrow();
// This is done because of the "save on suspend" logic on Android. Once the application is suspended, there is no window associated to it.
if let Some(Viewport { window, .. }) = shared.viewports.get(&ViewportId::ROOT) {
log::debug!("Saving application state with a window");
self.integration.save(self.app.as_mut(), window.as_deref());
} else if force_save_without_window {
log::debug!("Saving application state without a window");
self.integration.save(self.app.as_mut(), None);
}
}

fn save_and_destroy(&mut self) {
profiling::function_scope!();

self.save(false);

#[cfg(feature = "glow")]
self.app.on_exit(None);

#[cfg(not(feature = "glow"))]
self.app.on_exit();

let mut shared = self.shared.borrow_mut();
shared.painter.destroy();
}

Expand Down
6 changes: 6 additions & 0 deletions crates/eframe/src/native/winit_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ pub trait WinitApp {

fn window_id_from_viewport_id(&self, id: ViewportId) -> Option<WindowId>;

fn save(&mut self);

fn save_and_destroy(&mut self);

fn run_ui_and_paint(
Expand Down Expand Up @@ -119,6 +121,10 @@ pub enum EventResult {

RepaintAt(WindowId, Instant),

/// Causes a save of the client state when the persistence feature is enabled.
#[allow(dead_code)] // Not used outside of Android
Save,

Exit,
}

Expand Down
Loading