Request_animation_frame in regular wasm_bindgen, not wasm_bindgen(start) function?

Standard example of request_animation_frame is inside wasm_bindgen(start).

https://rustwasm.github.io/wasm-bindgen/examples/request-animation-frame.html

Another way possibly of using request_animation_frame is directly from the JS side.

Question: is there any example of using request_animation_frame from a function that is wasm_bindgen but not wasm_bindgen(start).

I am getting a weird

Uncaught RuntimeError: memory access out of bounds

errr after looping for 3-4 times, it is almost as if there is some invisible GC call that frees some memory the callback needs.

I have a block of code that looks like:

        // request animation callback
        let t2 = t.clone();
        *t.cb_request_animation_frame.borrow_mut() = Closure::wrap(Box::new(move || {
            web_sys::console::log_1(&"ENTER: request animation frame".into());

            let f = format!("ptr_1: {:p}", &t2);
            web_sys::console::log_1(&f.into());

            let f = format!("ptr_2: {:p}", &t2.cb_request_animation_frame);
            web_sys::console::log_1(&f.into());

            // t2.inner.borrow().draw();
            Dom_Util::request_animation_frame(&t2.cb_request_animation_frame.borrow());
            web_sys::console::log_1(&"LEAVE: request animation frame".into());})
            as Box<dyn FnMut()>);
        Dom_Util::request_animation_frame(&t.cb_request_animation_frame.borrow());}}

and I get output of:

ENTER: request animation frame
client_w.js:470 ptr_1: 0x691470
client_w.js:470 ptr_2: 0x691458
client_w.js:470 LEAVE: request animation frame
client_w.js:470 ENTER: request animation frame
client_w.js:470 ptr_1: 0x691470
client_w.js:470 ptr_2: 0x691458
client_w.js:470 LEAVE: request animation frame
client_w.js:470 ENTER: request animation frame
client_w.js:470 ptr_1: 0x691470
client_w.js:470 ptr_2: 0x691458
client_w.js:470 LEAVE: request animation frame
client_w.js:470 ENTER: request animation frame
client_w.js:470 ptr_1: 0x691470

and then it crashes with:

client_w_bg.wasm:0x306fc Uncaught RuntimeError: memory access out of bounds

so basically, after a few loops, the memory is suddenly invalid

"memory access out of bounds" is undefined-behavior-that-was-caught. If it happens, and you didn't write any unsafe code, there's a bug in some library — not your code. Most likely the move from #[wasm_bindgen(start)] to #[wasm_bindgen] isn't actually a problem itself — it just shuffled things around so the bug was triggered rather than not triggered, or detected rather than not detected.

I recently hit a similarly problem in my game — except the thing that changed was my workspace configuration, not any of the code within the crate. So, I suspect wasm-bindgen has a bug somewhere that we're both hitting, perhaps related to Closures. I haven't gotten around to digging into it myself, but I'd be very much interested to hear any observations you make. Just remember — when it comes to memory errors, you can't assume that just because there's no error the problem isn't present.

I can definitely say that there's no reason you have to use #[wasm_bindgen(start)] — my code doesn't use it at all.

Thank you for sharing your experience. One question: in your game, do you do request_animation_frame in JS-land or in Rust-land ?

This is likely useless for everyone else, only intention is to not incorrectly blame wasm_bindgen:

In this particular case, the problem is my fault, in the JS side, I was calling some initialization procedure twice. There may or may not be bugs in wasm_bindgen, but for this particular error, I was 100% doing something stupid / wrong on the JS side.

1 Like

From Rust, as it happens.

If you are not writing unsafe code, a memory error cannot be your code's fault. As I said before: you may have found a way to avoid triggering the bug, but there is still a bug.

That's why I'm going to, someday, spend some time to try to make a small reproduction case for the one I encountered: the way to live in a world where our code doesn't randomly fail is to chase down bugs and report them, not just dodge them.

4 Likes

I did something like:

import init, * as M from "./rust_wasm_output.js"
await init(); // first step of init
M.default(); // <-- this is not a function I wrote, but the default from importing the module; this should not be called
M.rust_gfx_loop();

I suspect I caused the same region of memory to get 'initialized twice' - or something like that; i.e. I did something that if I read the official wasm bindgen docs in detail, it would probably say : don't do this on the JS side

Ah, okay, something of that sort could account for it.

I don't think any bindgen exports should be effectively unsafe either, though. Hmm.

Are you asking for a minimal failure case ?