I have the following startup system which successfully writes to local storage just before my Bevy app closes:
pub fn setup_window_onunload_save_world_state() {
let window = web_sys::window().expect("window not available");
let on_beforeunload = Closure::wrap(Box::new(move |_event: web_sys::BeforeUnloadEvent| {
LocalStorage::set::<bool>("example", true);
}) as Box<dyn FnMut(web_sys::BeforeUnloadEvent)>);
window
.add_event_listener_with_callback("beforeunload", on_beforeunload.as_ref().unchecked_ref());
on_beforeunload.forget();
}
In this example, the window closes, but when I reopen the window I am able to inspect local storage and see the example entry. I understand that this is "best effort" and that if the browser crashes that this code might not run. That's OK.
I am trying to figure out how to modify this code such that I am able to write state of the world to local storage. Originally, I mirrored my logic after some Bevy WindowResize internals: https://github.com/bevyengine/bevy/blob/main/crates/bevy_winit/src/web_resize.rs#L73 but this approach is too slow. If I update a resource in on_beforeunload closure, and then wait for another system to detect it, then the browser window closes before the change is detected. So, I am looking for a way to work around Bevy/ECS and access this data immediately. It's OK if the data is slightly stale.
I thought I might take inspiration from how exclusive systems work. I wrote code like:
pub fn setup_window_onunload_save_world_state(world: &mut World) {
let window = web_sys::window().expect("window not available");
let on_beforeunload = Closure::wrap(Box::new(move |_event: web_sys::BeforeUnloadEvent| {
let settings = world.resource::<Settings>();
LocalStorage::set::<isize>("example", settings.initial_ant_count);
}) as Box<dyn FnMut(web_sys::BeforeUnloadEvent)>);
window
.add_event_listener_with_callback("beforeunload", on_beforeunload.as_ref().unchecked_ref());
on_beforeunload.forget();
}
where I request access to world and then, inside the closure, query for some of the world's state. This doesn't work out of the box because of lifetimes. error: lifetime may not live long enough cast requires that '1must outlive'static`
I'm not confident that this lifetime issue is addressable while reading reasonably current world state. In a previous approach, where I was just setting a flag and attempting to react to it, I was able to navigate around the lifetime issue with ArcMutex. For example:
#[derive(Resource)]
pub struct IsWindowUnloading(Arc<Mutex<bool>>);
pub fn setup_window_onunload_save_world_state(is_window_unloading: ResMut<IsWindowUnloading>) {
let window = web_sys::window().expect("window not available");
let is_window_unloading_arc = is_window_unloading.0.clone();
let on_beforeunload = Closure::wrap(Box::new(move |_event: web_sys::BeforeUnloadEvent| {
let mut is_window_unloading = is_window_unloading_arc.lock().unwrap();
*is_window_unloading = true;
}) as Box<dyn FnMut(web_sys::BeforeUnloadEvent)>);
window
.add_event_listener_with_callback("beforeunload", on_beforeunload.as_ref().unchecked_ref());
on_beforeunload.forget();
}
Here I was able to successfully set a flag, but I was unable to react to the flag changing quickly enough to query my world state.
Are there any viable approaches here within Bevy? Otherwise, I guess my option is to continually take a snapshot of the world state I need to save, store that outside of Bevy, and write that when needed.