Is it possible to mutate a struct inside a wasm-bindgen Closure?

I am currently trying to get started with wasm-bindgen (https://crates.io/crates/wasm-bindgen) by building a simple 2D WebGL application.

I would like to subscribe to the wheel event (https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event) from WASM. I managed to do so as described in the docs (https://rustwasm.github.io/docs/wasm-bindgen/examples/paint.html?highlight=event#srclibrs), like that:

pub fn add_2d_camera_controls(canvas: HtmlCanvasElement, canvas_ref: &HtmlCanvasElement, camera: Camera) -> Result<(), JsValue> {
    let closure = Closure::wrap(Box::new(move |event: web_sys::WheelEvent| {
        event.prevent_default();

        let (clip_x, clip_y) = get_clip_space_mouse_position(&event, &canvas);

        const MAX_ZOOM: f32 = std::f32::MAX;
        const MIN_ZOOM: f32 = 0f32;

        let new_zoom = camera.zoom * (event.delta_y() as f32 * -0.01f32).powf(2f32);
        //camera.zoom = new_zoom.clamp(MIN_ZOOM, MAX_ZOOM); // This is where the main problem arises.

        #[allow(unused_unsafe)]
        unsafe {
            crate::js_functions::log(clip_x.to_string().as_str());
            crate::js_functions::log(clip_y.to_string().as_str());
        }

    }) as Box<dyn FnMut(_)>);

    canvas_ref.add_event_listener_with_callback("wheel", closure.as_ref().unchecked_ref())?;
    closure.forget();

    Ok(())
}

The problem I have is that whenever I try to use something borrowed in the Closure, it apparently needs to have lifetime 'static. What I wanted to do is mutate the value of camera inside the closure, but I can not mutably borrow it, because it would need to have a 'static lifetime and I can't assign any lifetime to the struct containing camera, because this is not possible when using #[wasm_bindgen] on the struct due to some restriction in wasm-bindgen.

I managed to work around this problem for canvas by using canvas: HtmlCanvasElement and canvas_ref: &HtmlCanvasElement but I also think this is a very ugly solution and it does not work, when I need to mutate a value inside the Closure.

I was able to get this to work the way I wanted by defining a function zoom in my struct, that I mainly use as the wasm interface like so:

pub fn zoom (&mut self, event: web_sys::WheelEvent){
        event.prevent_default();

        let (clip_x, clip_y) = webgl_setup::get_clip_space_mouse_position(&event, &self.canvas);

        const MAX_ZOOM: f32 = std::f32::MAX;
        const MIN_ZOOM: f32 = 0f32;

        let new_zoom = self.camera.zoom * (event.delta_y() as f32 * -0.01f32).powf(2f32);
        self.camera.zoom = new_zoom.clamp(MIN_ZOOM, MAX_ZOOM); // This is not a problem anymore now.

        #[allow(unused_unsafe)]
        unsafe {
            crate::js_functions::log(clip_x.to_string().as_str());
            crate::js_functions::log(clip_y.to_string().as_str());
            crate::js_functions::log(format!("Zoom: {}", self.camera.zoom.to_string()).as_str());
        }
 }

and now I can call it from Javascript like this:

const rust = import('./pkg');

rust
  .then(m => {
    let wagl = new m.WaGl('myCanvas');
    wagl.render();

    document.getElementById('myCanvas').addEventListener('wheel', (event) => {
      wagl.zoom(event);
    });
  })
  .catch(console.error);

However I want to subscribe the event listener in WASM and not have to do this in Javascript.

I have uploaded both the working and the not working (but the version I want to get working :slight_smile:) version to Github.

I have been struggling with this for quite some time, so I appreciate any hints...

P.S.: I have seen many people use lazy_static``(https://crates.io/crates/lazy_static) in similar occasions, but that is not an option for me, because I want to be able to have multiple WaGl instances.

HtmlCanvasElement: Clone so you can just clone it before moving it into the closure. For variables you want mutably shared between an event listener and other code you will typically wrap in a Rc<Cell> or Rc<RefCell>.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.