首页 > 解决方案 > 使用 wasm-bindgen 时,如何解决无法导出具有生命周期的函数的问题?

问题描述

我正在尝试编写一个在浏览器中运行的简单游戏,考虑到浏览器、rust 和 wasm-bindgen 施加的限制组合,我很难对游戏循环进行建模。

浏览器中的典型游戏循环遵循以下一般模式:

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

如果我要在 rust/wasm-bindgen 中模拟这个精确的模式,它看起来像这样:

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()>);

与 javascript 不同,我无法main_loop从自身内部引用,所以这不起作用。

有人建议的另一种方法是遵循生命游戏示例中说明的模式。在高层次上,它涉及导出一个包含游戏状态的类型,并包括可以从 javascript 游戏循环中调用的公共tick()和函数。render()这对我不起作用,因为我的游戏状态需要生命周期参数,因为它实际上只是包装了规范 WorldDispatcher结构,后者具有生命周期参数。最终,这意味着我无法使用#[wasm_bindgen].

我很难找到解决这些限制的方法,并且正在寻找建议。

标签: rustwebassemblywasm-bindgen

解决方案


对此建模的最简单方法可能是将调用留给requestAnimationFrameJS,而只是在 Rust 中实现更新/绘制逻辑。

然而,在 Rust 中,您还可以利用这样一个事实,即实际上不捕获任何变量的闭包大小为零,这意味着Closure<T>该闭包不会分配内存,您可以放心地忘记它。例如,这样的事情应该可以工作:

#[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
}

如果您的状态在其中包含生命周期,那么不幸的是,这与返回 JS 不兼容,因为当您一直返回到 JS 事件循环时,所有 WebAssembly 堆栈帧都已弹出,这意味着任何生命周期都无效。这意味着您的游戏状态在main_loop需要的迭代中持续存在'static


推荐阅读