Save state on suspend on Android and iOS (#5601)
<!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md) before opening a Pull Request! * Keep your PR:s small and focused. * The PR title is what ends up in the changelog, so make it descriptive! * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to test and add commits to your PR. * Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! I will review your PR, but my time is limited! --> This pull request fixes a subset of #5492 by saving the application state when the `suspended` event is received on Android. This way, even if the user exits the app and closes it manually right after changing some state, it will be saved since `suspended` gets fired when the app is exited. It does not fix the `on_exit` function not being fired - this seems to be a winit bug (the `exiting` function in the winit application handler trait is not called on exit). Once it gets fixed, it may be possible to remove logic introduced by this PR (however, I am not sure how it would handle the app being killed by the system when in the background, that would have to be tested). I've tested the logic by: * Leaving from the app to the home screen, then killing it from the "recent apps" menu * Leaving from the app to the "recent apps" menu and killing it * Restarting the device while the app was running In all of these instances, the state was saved (the last one being a pleasant surprise). It was tested on the repository mentioned in #5492 with my forked repository as the source for eframe (I unfortunately am not able to test it in a larger project of mine due to dependence on "3rd party" egui libraries (like egui_notify) which do not compile along with the master branch of eframe (different versions of egui), but I believe it should work in the same manner in all scenarios). Tests were conducted on a Galaxy Tab S8 running Android 14, One UI 6.1.1. CI passed on my fork. * [x] I have followed the instructions in the PR template
This commit is contained in:
parent
bc5f908b80
commit
93d2144294
|
|
@ -366,6 +366,20 @@ impl WinitApp for GlowWinitApp<'_> {
|
|||
.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);
|
||||
|
||||
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!();
|
||||
|
|
@ -413,7 +427,7 @@ impl WinitApp for GlowWinitApp<'_> {
|
|||
if let Some(running) = &mut self.running {
|
||||
running.glutin.borrow_mut().on_suspend()?;
|
||||
}
|
||||
Ok(EventResult::Wait)
|
||||
Ok(EventResult::Save)
|
||||
}
|
||||
|
||||
fn device_event(
|
||||
|
|
@ -1214,10 +1228,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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:?}");
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -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…");
|
||||
|
|
|
|||
|
|
@ -355,6 +355,13 @@ impl WinitApp for WgpuWinitApp<'_> {
|
|||
)
|
||||
}
|
||||
|
||||
fn save(&mut self) {
|
||||
log::debug!("WinitApp::save called");
|
||||
if let Some(running) = self.running.as_mut() {
|
||||
running.save();
|
||||
}
|
||||
}
|
||||
|
||||
fn save_and_destroy(&mut self) {
|
||||
if let Some(mut running) = self.running.take() {
|
||||
running.save_and_destroy();
|
||||
|
|
@ -415,7 +422,7 @@ impl WinitApp for WgpuWinitApp<'_> {
|
|||
fn suspended(&mut self, _: &ActiveEventLoop) -> crate::Result<EventResult> {
|
||||
#[cfg(target_os = "android")]
|
||||
self.drop_window()?;
|
||||
Ok(EventResult::Wait)
|
||||
Ok(EventResult::Save)
|
||||
}
|
||||
|
||||
fn device_event(
|
||||
|
|
@ -488,13 +495,23 @@ impl WinitApp for WgpuWinitApp<'_> {
|
|||
}
|
||||
|
||||
impl WgpuWinitRunning<'_> {
|
||||
/// Saves the application state
|
||||
fn save(&mut self) {
|
||||
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.
|
||||
let window = if let Some(Viewport { window, .. }) = shared.viewports.get(&ViewportId::ROOT)
|
||||
{
|
||||
window.as_deref()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.integration.save(self.app.as_mut(), window);
|
||||
}
|
||||
|
||||
fn save_and_destroy(&mut self) {
|
||||
profiling::function_scope!();
|
||||
|
||||
let mut shared = self.shared.borrow_mut();
|
||||
if let Some(Viewport { window, .. }) = shared.viewports.get(&ViewportId::ROOT) {
|
||||
self.integration.save(self.app.as_mut(), window.as_deref());
|
||||
}
|
||||
self.save();
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
self.app.on_exit(None);
|
||||
|
|
@ -502,6 +519,7 @@ impl WgpuWinitRunning<'_> {
|
|||
#[cfg(not(feature = "glow"))]
|
||||
self.app.on_exit();
|
||||
|
||||
let mut shared = self.shared.borrow_mut();
|
||||
shared.painter.destroy();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
@ -119,6 +121,9 @@ pub enum EventResult {
|
|||
|
||||
RepaintAt(WindowId, Instant),
|
||||
|
||||
/// Causes a save of the client state when the persistence feature is enabled.
|
||||
Save,
|
||||
|
||||
Exit,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue