diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index f39fc0dcace..fd8a7cf236e 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -1,7 +1,4 @@ -use std::{ - cell::{Cell, RefCell}, - rc::Rc, -}; +use std::{cell::RefCell, rc::Rc}; use wasm_bindgen::prelude::*; @@ -28,8 +25,8 @@ pub struct WebRunner { /// the panic handler, since they aren't `Send`. events_to_unsubscribe: Rc>>, - /// Used in `destroy` to cancel a pending frame. - request_animation_frame_id: Cell>, + /// Current animation frame in flight. + frame: Rc>>, } impl WebRunner { @@ -47,7 +44,7 @@ impl WebRunner { panic_handler, runner: Rc::new(RefCell::new(None)), events_to_unsubscribe: Rc::new(RefCell::new(Default::default())), - request_animation_frame_id: Cell::new(None), + frame: Default::default(), } } @@ -115,9 +112,10 @@ impl WebRunner { pub fn destroy(&self) { self.unsubscribe_from_all_events(); - if let Some(id) = self.request_animation_frame_id.get() { + if let Some(frame) = self.frame.take() { let window = web_sys::window().unwrap(); - window.cancel_animation_frame(id).ok(); + window.cancel_animation_frame(frame.id).ok(); + drop(frame.closure); } if let Some(runner) = self.runner.replace(None) { @@ -193,20 +191,47 @@ impl WebRunner { } pub(crate) fn request_animation_frame(&self) -> Result<(), wasm_bindgen::JsValue> { + if self.frame.borrow().is_some() { + // there is already an animation frame in flight + return Ok(()); + } + let window = web_sys::window().unwrap(); let closure = Closure::once({ let runner_ref = self.clone(); - move || events::paint_and_schedule(&runner_ref) + move || { + // we can paint now, so clear the animation frame + // this drop the `closure` and allows another + // animation frame to be scheduled + let _ = runner_ref.frame.take(); + events::paint_and_schedule(&runner_ref) + } }); + let id = window.request_animation_frame(closure.as_ref().unchecked_ref())?; - self.request_animation_frame_id.set(Some(id)); - closure.forget(); // We must forget it, or else the callback is canceled on drop + self.frame + .borrow_mut() + .replace(AnimationFrameRequest { id, closure }); + Ok(()) } } // ---------------------------------------------------------------------------- +// https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/closure/struct.Closure.html#using-fnonce-and-closureonce-with-requestanimationframe +struct AnimationFrameRequest { + /// Represents the ID of a frame in flight. + /// + /// This is only set between a call to `request_animation_frame` and the invocation of its callback, + /// which means that repeated calls to `request_animation_frame` will be ignored. + id: i32, + + /// The callback given to `request_animation_frame`, stored here both to prevent it + /// from being canceled, and from having to `.forget()` it. + closure: Closure Result<(), JsValue>>, +} + struct TargetEvent { target: web_sys::EventTarget, event_name: String,