Skip to content

Commit

Permalink
don't forget raf closure
Browse files Browse the repository at this point in the history
  • Loading branch information
jprochazk committed May 27, 2024
1 parent 7a17a6d commit 5223dae
Showing 1 changed file with 37 additions and 12 deletions.
49 changes: 37 additions & 12 deletions crates/eframe/src/web/web_runner.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use std::{cell::RefCell, rc::Rc};

use wasm_bindgen::prelude::*;

Expand All @@ -28,8 +25,8 @@ pub struct WebRunner {
/// the panic handler, since they aren't `Send`.
events_to_unsubscribe: Rc<RefCell<Vec<EventToUnsubscribe>>>,

/// Used in `destroy` to cancel a pending frame.
request_animation_frame_id: Cell<Option<i32>>,
/// Current animation frame in flight.
frame: Rc<RefCell<Option<AnimationFrameRequest>>>,
}

impl WebRunner {
Expand All @@ -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(),
}
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<dyn FnMut() -> Result<(), JsValue>>,
}

struct TargetEvent {
target: web_sys::EventTarget,
event_name: String,
Expand Down

0 comments on commit 5223dae

Please sign in to comment.