Rust编译wasm(webassembly)时,怎么休眠10毫秒?

When compiling Rust to wasm (web assembly), how can I sleep for 10 milliseconds?

我的 Rust 程序正在管理 2d html canvas 上下文的内存,我正试图达到 ~60fps。我可以很容易地计算出每帧之间的增量,结果大约是 ~5ms。

我不清楚如何让我的 Rust webassembly 程序休眠剩余的 11 毫秒。一种选择是让 JavaScript 在每个 requestAnimationFrame 上调用 Rust 并将其用作驱动程序,但我很想尽可能将其全部保留在 Rust 中。

在编译到 wasm 目标时,我正在有效地寻找 JavaScript 的 setTimeout(renderNext, 11) 的 Rust 等价物。

I'm effectively looking for the Rust equivalent of JavaScript's setTimeout(renderNext, 11) when compiling out to the wasm target.

有几个 Rust crate 绑定到 JavaScript 网络 API,最著名的是 web-sys。看看the documentation for one of the setTimeout overloads.

虽然这并不是真正的 Rust 等价物,因为它非常直接地调用了 JS 函数。但是您将无法解决这个问题:休眠或获取当前时间都是主机环境必须提供的功能。它们不能单独用原始语言实现。

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.

是的,您应该使用 requestAnimationFrame (link to web-sys docs)。这比自己计时要好得多。特别是,当标签不活动和类似的内容时,此方法还将暂停调用您的代码。在桌面环境中,你会做同样的事情:要求主机环境(即操作系统,通常通过 OpenGL 左右)将你的程序同步到屏幕刷新。

在您的 requestAnimationFrame 回调中,调用 setTimeout,然后让它对 requestAnimationFrame 进行下一次调用。你可以看到这个here.

的JS版本

基于the example in the wasm-bindgen book,以下是我在 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")
}

然后您只需传递 animate_limited 一个函数来进行绘图(像 move || { /* drawing logic here */ } 这样的闭包就可以了),以及您想要的最大帧率。

几乎肯定会有改进。我是 Rust 的新手,只是花了太长时间来弄清楚如何让它工作。希望这会让其他人在未来更快。