首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在使用wasm-bindgen时,我如何解决无法导出具有生存期的函数的问题?

在使用wasm-bindgen时,我如何解决无法导出具有生存期的函数的问题?
EN

Stack Overflow用户
提问于 2018-10-26 09:58:53
回答 2查看 1.3K关注 0票数 6

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

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

代码语言:javascript
复制
function mainLoop() {
    update();
    draw();
    requestAnimationFrame(mainLoop);
}

如果我在rust/wasm-bindgen中对这种模式进行建模,它将如下所示:

代码语言:javascript
复制
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,所以这不起作用。

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

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

EN

回答 2

Stack Overflow用户

发布于 2018-11-02 04:09:19

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

然而,在Rust中,您还可以利用这样一个事实,即没有实际捕获任何变量的闭包是零大小的,这意味着该闭包的Closure<T>不会分配内存,您可以安全地忘记它。例如,下面这样的代码应该是有效的:

代码语言:javascript
复制
#[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

票数 7
EN

Stack Overflow用户

发布于 2018-11-20 02:39:48

我是一个Rust新手,但我是如何解决同样的问题的。

您可以消除有问题的window.request_animation_frame递归,同时通过从window.set_interval回调调用window.request_animation_frame来实现FPS上限,该回调检查Rc<RefCell<bool>>或其他内容,以查看是否有动画帧请求仍处于挂起状态。我不确定非活动选项卡的行为在实践中是否会有所不同。

我将bool放入我的应用程序状态,因为我无论如何都要使用Rc<RefCell<...>>来处理其他事件。我还没有检查以下代码是否按原样编译,但以下是我如何执行此操作的相关部分:

代码语言:javascript
复制
pub struct MyGame {
    ...
    should_request_render: bool, // Don't request another render until the previous runs, init to false since we'll fire the first one immediately.
}

...

let window = web_sys::window().expect("should have a window in this context");
let application_reference = Rc::new(RefCell::new(MyGame::new()));

let request_animation_frame = { // request_animation_frame is not forgotten! Its ownership is moved into the timer callback.
    let application_reference = application_reference.clone();
    let request_animation_frame_callback = Closure::wrap(Box::new(move || {
        let mut application = application_reference.borrow_mut();
        application.should_request_render = true;
        application.handle_animation_frame(); // handle_animation_frame being your main loop.
    }) as Box<FnMut()>);
    let window = window.clone();
    move || {
        window
            .request_animation_frame(
                request_animation_frame_callback.as_ref().unchecked_ref(),
            )
            .unwrap();
    }
};
request_animation_frame(); // fire the first request immediately

let timer_closure = Closure::wrap(
    Box::new(move || { // move both request_animation_frame and application_reference here.
        let mut application = application_reference.borrow_mut();
        if application.should_request_render {
            application.should_request_render = false;
            request_animation_frame();
        }
    }) as Box<FnMut()>
);
window.set_interval_with_callback_and_timeout_and_arguments_0(
    timer_closure.as_ref().unchecked_ref(),
    25, // minimum ms per frame
)?;
timer_closure.forget(); // this leaks it, you could store it somewhere or whatever, depends if it's guaranteed to live as long as the page

您可以将set_intervaltimer_closure的结果存储在您的游戏状态中的Options中,以便您的游戏可以在出于某些原因(可能?我还没有尝试过,它似乎会导致免费的self?)循环引用不会自行擦除,除非被破坏(然后您可以有效地将Rc存储到应用程序中)。它还应该允许您在运行时更改最大fps,方法是停止间隔并使用相同的闭包创建另一个间隔。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53000413

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档