Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I work around not being able to export functions with lifetimes when using wasm-bindgen?

I'm trying to write a simple game that runs in the browser, and I'm having a hard time modeling a game loop given the combination of restrictions imposed by the browser, rust, and wasm-bindgen.

A typical game loop in the browser follows this general pattern:

function mainLoop() {
    update();
    draw();
    requestAnimationFrame(mainLoop);
}

If I were to model this exact pattern in rust/wasm-bindgen, it would look like this:

let main_loop = Closure::wrap(Box::new(move || {
    update();
    draw();
    window.request_animation_frame(main_loop.as_ref().unchecked_ref()); // Not legal
}) as Box<FnMut()>);

Unlike javascript, I'm unable to reference main_loop from within itself, so this doesn't work.

An alternative approach that someone suggested is to follow the pattern illustrated in the game of life example. At a high-level, it involves exporting a type that contains the game state and includes public tick() and render() functions that can be called from within a javascript game loop. This doesn't work for me because my gamestate requires lifetime parameters, since it effectively just wraps a specs World and Dispatcher struct, the latter of which has lifetime parameters. Ultimately, this means that I can't export it using #[wasm_bindgen].

I'm having a hard time finding ways to work around these restrictions, and am looking for suggestions.

like image 796
w.brian Avatar asked Oct 26 '18 01:10

w.brian


People also ask

Is Rust WebAssembly faster than JavaScript?

Wasm is 1.15-1.67 times faster than JavaScript on Google Chrome on a desktop.

What is Wasm_bindgen?

The goal of wasm-bindgen is to provide a bridge between the types of JS and Rust. It allows JS to call a Rust API with a string, or a Rust function to catch a JS exception.

What is Rust WebAssembly?

WebAssembly is a compilation target for C, C++, Go, and Rust among others. Developers can code in any of those languages and then compile their source into Wasm binaries. These then run alongside JS code in their apps. There are many obvious reasons why WebAssembly is faster than JavaScript.


1 Answers

The easiest way to model this is likely to leave invocations of requestAnimationFrame to JS and instead just implement the update/draw logic in Rust.

In Rust, however, what you can also do is to exploit the fact that a closure which doesn't actually capture any variables is zero-size, meaning that Closure<T> of that closure won't allocate memory and you can safely forget it. For example something like this should work:

#[wasm_bindgen]
pub fn main_loop() {
    update();
    draw();
    let window = ...;
    let closure = Closure::wrap(Box::new(|| main_loop()) as Box<Fn()>);
    window.request_animation_frame(closure.as_ref().unchecked_ref());
    closure.forget(); // not actually leaking memory
}

If your state has lifetimes inside of it, that is unfortunately incompatible with returning back to JS because when you return all the way back to the JS event loop then all WebAssembly stack frames have been popped, meaning that any lifetime is invalidated. This means that your game state persisted across iterations of the main_loop will need to be 'static

like image 135
alexcrichton Avatar answered Sep 19 '22 00:09

alexcrichton