如何通过 WebAssembly 将 Rust 闭包返回给 JavaScript? [英] How to return a Rust closure to JavaScript via WebAssembly?

查看:82
本文介绍了如何通过 WebAssembly 将 Rust 闭包返回给 JavaScript?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

closure.rs 的评论是非常棒,但是我无法让它从 WebAssembly 库返回闭包.

The comments on closure.rs are pretty great, however I can't make it work for returning a closure from a WebAssembly library.

我有一个这样的功能:

#[wasm_bindgen]
pub fn start_game(
    start_time: f64,
    screen_width: f32,
    screen_height: f32,
    on_render: &js_sys::Function,
    on_collision: &js_sys::Function,
) -> ClosureTypeHere {
    // ...
}

在该函数中,我创建了一个闭包,假设 Closure::wrap 是拼图的一部分,并从closure.rs 复制):

Inside that function I make a closure, assuming Closure::wrap is one piece of the puzzle, and copying from closure.rs):

let cb = Closure::wrap(Box::new(move |time| time * 4.2) as Box<FnMut(f64) -> f64>);

我如何从 start_game 返回这个回调,ClosureTypeHere 应该是什么?

How do I return this callback from start_game and what should ClosureTypeHere be?

这个想法是 start_game 将创建本地可变对象——比如一个相机,JavaScript 端应该能够调用 Rust 返回的函数来更新那个相机.

The idea is that start_game will create local mutable objects - like a camera, and the JavaScript side should be able to call the function Rust returns in order to update that camera.

推荐答案

这是一个很好的问题,而且也有一些细微差别!值得一提的是 wasm-bindgen 中的 闭包示例 指南(以及 部分关于传递对 JavaScript 的闭包),如果有必要,也可以对此做出贡献!

This is a good question, and one that has some nuance too! It's worth calling out the closures example in the wasm-bindgen guide (and the section about passing closures to JavaScript) as well, and it'd be good to contribute back to that as well if necessary!

不过,为了让您开始,您可以执行以下操作:

To get you started, though, you can do something like this:

use wasm_bindgen::{Closure, JsValue};

#[wasm_bindgen]
pub fn start_game(
    start_time: f64,
    screen_width: f32,
    screen_height: f32,
    on_render: &js_sys::Function,
    on_collision: &js_sys::Function,
) -> JsValue {
    let cb = Closure::wrap(Box::new(move |time| {
        time * 4.2
    }) as Box<FnMut(f64) -> f64>);

    // Extract the `JsValue` from this `Closure`, the handle
    // on a JS function representing the closure
    let ret = cb.as_ref().clone();

    // Once `cb` is dropped it'll "neuter" the closure and
    // cause invocations to throw a JS exception. Memory
    // management here will come later, so just leak it
    // for now.
    cb.forget();

    return ret;
}

在返回值之上只是一个普通的 JS 对象(此处为 JsValue),我们使用您已经看到的 Closure 类型创建它.这将允许您快速将闭包返回给 JS,并且您也可以从 JS 调用它.

Above the return value is just a plain-old JS object (here as a JsValue) and we create that with the Closure type you've seen already. This will allow you to quickly return a closure to JS and you'll be able to call it from JS as well.

您还询问了存储可变对象等问题,而这一切都可以通过普通的 Rust 闭包、捕获等来完成.例如,FnMut(f64) 的声明 ->上面的 f64 是 JS 函数的签名,可以是任何类型的集合,例如 FnMut(String, MyCustomWasmBindgenType, f64) ->Vec 如果你真的想要.要捕获本地对象,您可以执行以下操作:

You've also asked about storing mutable objects and such, and that can all be done through normal Rust closures, capturing, etc. For example the declaration of FnMut(f64) -> f64 above is the signature of the JS function, and that can be any set of types such as FnMut(String, MyCustomWasmBindgenType, f64) -> Vec<u8> if you really want. For capturing local objects you can do:

let mut camera = Camera::new();
let mut state = State::new();
let cb = Closure::wrap(Box::new(move |arg1, arg2| { // note the `move`
    if arg1 {
        camera.update(&arg2);
    } else {
        state.update(&arg2);
    }
}) as Box<_>);

(或类似的东西)

这里的 camerastate 变量将由闭包拥有并同时删除.更多关于闭包的信息可以在 Rust 书中找到.

Here the camera and state variables will be owned by the closure and dropped at the same time. More info about just closures can be found in the Rust book.

这里也值得简要介绍一下内存管理方面.在里面上面的例子我们正在调用 forget() 这会泄漏内存,如果多次调用 Rust 函数(因为它会泄漏大量内存),这可能会成为问题.这里的基本问题是在创建的 JS 函数对象引用的 WASM 堆上分配了内存.这个分配的内存理论上需要在 JS 函数对象被 GC 时被释放,但我们无法知道什么时候发生(直到 WeakRef 存在!).

It's also worth briefly covering the memory management aspect here. In the example above we're calling forget() which leaks memory and can be a problem if the Rust function is called many times (as it would leak a lot of memory). The fundamental problem here is that there's memory allocated on the WASM heap which the created JS function object references. This allocated memory in theory needs to be deallocated whenever the JS function object is GC'd, but we have no way of knowing when that happens (until WeakRef exists!).

与此同时,我们选择了另一种策略.相关的内存是每当 Closure 类型本身被删除时释放,提供确定性破坏.然而,这使得它很难处理,因为我们需要手动确定何时删除 Closure.如果 forget 不适用于您的用例,则删除 Closure 的一些想法是:

In the meantime we've chosen an alternate strategy. The associated memory is deallocated whenever the Closure type itself is dropped, providing deterministic destruction. This, however, makes it difficult to work with as we need to figure out manually when to drop the Closure. If forget doesn't work for your use case, some ideas for dropping the Closure are:

  • 首先,如果是只调用一次的JS闭包,那么可以使用Rc/RefCellClosure 放在闭包本身内(使用一些内部可变性恶作剧).我们还应该最终提供原生支持对于 wasm-bindgen 中的 FnOnce 也是如此!

  • First, if it's a JS closure only invoked once, then you can use Rc/RefCell to drop the Closure inside the the closure itself (using some interior mutability shenanigans). We should also eventually provide native support for FnOnce in wasm-bindgen as well!

接下来,你可以返回一个辅助 JS 对象给 Rust,它有一个手动free方法.例如,带有 #[wasm_bindgen] 注释的包装器.这个包装器会然后需要在适当的时候在 JS 中手动释放.

Next, you can return an auxiliary JS object to Rust which has a manual free method. For example a #[wasm_bindgen]-annotated wrapper. This wrapper would then need to be manually freed in JS when appropriate.

如果你能熬过去,forget 是迄今为止最简单的事情现在,但这绝对是一个痛点!我们等不及 WeakRef 的存在 :)

If you can get by, forget is by far the easiest thing to do for now, but this is definitely a pain point! We can't wait for WeakRef to exist :)

这篇关于如何通过 WebAssembly 将 Rust 闭包返回给 JavaScript?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆