diff --git a/Cargo.toml b/Cargo.toml index d8036a4e33d229..48a6f7cfe73731 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -319,6 +319,11 @@ futures-lite = "2.0.1" crossbeam-channel = "0.5.0" argh = "0.1.12" +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +once_cell = "1.16" +wasm-bindgen = { version = "0.2" } +web-sys = { version = "0.3", features = ["Window"] } + [[example]] name = "hello_world" path = "examples/hello_world.rs" diff --git a/examples/window/framerate.rs b/examples/window/framerate.rs index 52940fcd45f22f..7ad1e99e1eb237 100644 --- a/examples/window/framerate.rs +++ b/examples/window/framerate.rs @@ -1,4 +1,5 @@ //! This example illustrates how to implement basic framerate limit through the Winit settings. +//! Additionally, for wasm, it shows how to force an external redraw, to wake up the app on demand. use std::time::Duration; @@ -11,7 +12,16 @@ fn main() { App::new() .insert_resource(Framerate(30.0)) .add_plugins(DefaultPlugins) - .add_systems(Startup, test_setup::setup) + .add_systems( + Startup, + ( + test_setup::setup, + // Improvement to force an external redraw in-between frames, so that switching + // from low FPS to high FPS happens instantly rather than at the next frame + #[cfg(target_arch = "wasm32")] + wasm::setup_external_redraw, + ), + ) .add_systems( Update, ( @@ -171,3 +181,49 @@ pub(crate) mod test_setup { )); } } + +#[cfg(target_arch = "wasm32")] +pub(crate) mod wasm { + use std::sync::{Arc, Mutex}; + + use bevy::{ecs::system::NonSend, window::RequestRedraw, winit::EventLoopProxy}; + use once_cell::sync::Lazy; + + use wasm_bindgen::prelude::*; + use wasm_bindgen::JsCast; + use web_sys::KeyboardEvent; + + pub static EVENT_LOOP_PROXY: Lazy>>> = + Lazy::new(|| Arc::new(Mutex::new(None))); + + #[wasm_bindgen] + pub fn request_redraw() -> Result<(), String> { + let proxy = EVENT_LOOP_PROXY.lock().unwrap(); + if let Some(proxy) = &*proxy { + proxy + .send_event(RequestRedraw) + .map_err(|_| "Request redraw error: failed to send event".to_string()) + } else { + Err("Request redraw error: event loop proxy not found".to_string()) + } + } + + pub(crate) fn setup_external_redraw(event_loop_proxy: NonSend) { + *EVENT_LOOP_PROXY.lock().unwrap() = Some((*event_loop_proxy).clone()); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + let closure = Closure::wrap(Box::new(move |event: KeyboardEvent| { + let key = event.key(); + if key.len() == 1 && key.chars().next().map_or(false, |ch| ch.is_digit(10)) { + request_redraw().unwrap(); + } + }) as Box); + + document + .add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref()) + .unwrap(); + closure.forget(); + } +}