My rust program is managing memory for a 2d html canvas context, and I'm trying to hit ~60fps. I can calculate the delta between each frame easily, and it turns out to be roughly ~5ms.
I'm unclear on how to put my Rust webassembly program to sleep for the remaining 11ms. One option would be to have JavaScript call into Rust on every requestAnimationFrame
and use that as the driver, but I'm curious to keep it all in Rust if possible.
I'm effectively looking for the Rust equivalent of JavaScript's setTimeout(renderNext, 11)
when compiling out to the wasm target.
Building the packageCompiles your Rust code to WebAssembly. Runs wasm-bindgen on that WebAssembly, generating a JavaScript file that wraps up that WebAssembly file into a module the browser can understand. Creates a pkg directory and moves that JavaScript file and your WebAssembly code into it. Reads your Cargo.
The Rust programming language is the most frequently used language for developing WebAssembly applications, according to a recent survey. And WebAssembly is growing in popularity.
Unlike Javascript, WASM is statically typed, which means code optimization occurs far earlier in the compilation process before the code reaches the browser. Its binary files are considerably smaller than JavaScript's, resulting in significantly faster loading times.
A study done by IBM found that Rust and WebAssembly could be 15x (1,500%) faster than compiled languages, such as Scala, which is traditionally considerred a high performance language.
In your requestAnimationFrame
callback, call setTimeout
, and have that in turn make the next call to requestAnimationFrame
. You can see the JS version of this here.
Based on the example in the wasm-bindgen
book, here's how I do this in Rust:
fn animate_limited(mut draw_frame: impl FnMut() + 'static, max_fps: i32) {
// Based on:
// https://rustwasm.github.io/docs/wasm-bindgen/examples/request-animation-frame.html#srclibrs
// https://doc.rust-lang.org/book/ch15-05-interior-mutability.html
let animate_cb = Rc::new(RefCell::new(None));
let animate_cb2 = animate_cb.clone();
let timeout_cb = Rc::new(RefCell::new(None));
let timeout_cb2 = timeout_cb.clone();
let w = window();
*timeout_cb2.borrow_mut() = Some(Closure::wrap(Box::new(move || {
request_animation_frame(&w, animate_cb.borrow().as_ref().unwrap());
}) as Box<dyn FnMut()>));
let w2 = window();
*animate_cb2.borrow_mut() = Some(Closure::wrap(Box::new(move || {
draw_frame();
set_timeout(&w2, timeout_cb.borrow().as_ref().unwrap(), 1000 / max_fps);
}) as Box<dyn FnMut()>));
request_animation_frame(&window(), animate_cb2.borrow().as_ref().unwrap());
}
fn window() -> web_sys::Window {
web_sys::window().expect("no global `window` exists")
}
fn request_animation_frame(window: &web_sys::Window, f: &Closure<dyn FnMut()>) -> i32 {
window
.request_animation_frame(f.as_ref().unchecked_ref())
.expect("should register `requestAnimationFrame` OK")
}
fn set_timeout(window: &web_sys::Window, f: &Closure<dyn FnMut()>, timeout_ms: i32) -> i32 {
window
.set_timeout_with_callback_and_timeout_and_arguments_0(
f.as_ref().unchecked_ref(),
timeout_ms,
)
.expect("should register `setTimeout` OK")
}
Then you simply pass animate_limited
a function to do your drawing (a closure like move || { /* drawing logic here */ }
will do the trick), and the maximum framerate you want.
There are almost certainly improvements to be made, there. I'm very new to Rust and just spent far too long figuring out how to make this work. Hopefully this makes it quicker for someone else in the future.
I'm effectively looking for the Rust equivalent of JavaScript's
setTimeout(renderNext, 11)
when compiling out to the wasm target.
There are several Rust crates that have bindings to the JavaScript web API, most notably web-sys
. Take a look at the documentation for one of the setTimeout
overloads.
This is not really a Rust equivalent though, as it pretty directly calls the JS function. But you won't be able to get around that: sleeping or getting the current time are both functions that the host environment has to offer. They cannot be implemented in the raw language alone.
One option would be to have JavaScript call into Rust on every
requestAnimationFrame
and use that as the driver, but I'm curious to keep it all in Rust if possible.
Yes, you should use requestAnimationFrame
(link to web-sys
docs). This is much preferred over timing it yourself. In particular, this method will also pause calling your code when the tab is not active and stuff like that. In a desktop environment you would do the same: ask the host environment (i.e. the operating system, often via OpenGL or so) to synchronize your program to screen refreshes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With