Как вернуть закрытие Rust в JavaScript через WebAssembly? - PullRequest
0 голосов
/ 08 ноября 2018

Комментарии к closure.rs довольно хороши, однако я не могу заставить его работать с возвратом замыкания из библиотеки WebAssembly.

У меня есть такая функция:

#[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):

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

Как мне вернуть этот обратный вызов из start_game и каким должен быть ClosureTypeHere?

Идея состоит в том, что start_game создаст локальные изменяемые объекты - например, камеру, и сторона JavaScript должна иметь возможность вызывать функцию, возвращаемую Rust для обновления этой камеры.

Ответы [ 2 ]

0 голосов
/ 09 ноября 2018

Это хороший вопрос, и у него тоже есть нюанс! Стоит упомянуть пример замыканий в руководстве wasm-bindgen (и раздел о передаче замыканий в JavaScript ), и было бы неплохо внести свой вклад в это как ну если надо!

Чтобы начать, вы можете сделать что-то вроде этого:

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.

Вы также спрашивали о хранении изменяемых объектов и тому подобного, и все это можно сделать с помощью обычных замыканий в Rust, захвата и т. Д. Например, объявление FnMut(f64) -> f64 выше является сигнатурой функции JS, и это может быть любым набором типов, таких как FnMut(String, MyCustomWasmBindgenType, f64) -> Vec<u8>, если вы действительно хотите. Для захвата локальных объектов вы можете сделать:

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

(или что-то в этом роде)

Здесь переменные camera и state будут принадлежать замыканию и удаляться одновременно. Больше информации о замыканиях можно найти в книге Rust .

Здесь также стоит кратко остановиться на аспекте управления памятью. в В приведенном выше примере мы вызываем forget(), что приводит к утечке памяти и может стать проблемой, если функция Rust вызывается много раз (так как это приведет к утечке большой памяти). Основная проблема заключается в том, что в куче WASM выделена память, на которую ссылается созданный объект функции JS. Эта распределенная память теоретически должна быть освобождена всякий раз, когда объект функции JS является GC'd, но у нас нет никакой возможности узнать, когда это произойдет (до WeakRef существует !).

Тем временем мы выбрали альтернативную стратегию. Связанная память освобождается всякий раз, когда сам тип Closure отбрасывается, обеспечивая детерминированное разрушение. Это, однако, затрудняет работу, так как нам нужно выяснить вручную, когда отбрасывать Closure. Если forget не подходит для вашего варианта использования, некоторые идеи для отбрасывания Closure:

  • Во-первых, если JS-замыкание вызывается только один раз, тогда вы можете использовать Rc / RefCell бросить Closure внутри самого затвора (используя некоторый интерьер изменчивость махинаций). Мы должны также в конце концов обеспечить встроенную поддержку для FnOnce в wasm-bindgen, а также!

  • Далее вы можете вернуть вспомогательный JS-объект в Rust с руководством free метод. Например, #[wasm_bindgen] -аннотированная оболочка. Эта обертка будет затем необходимо освободить вручную в JS при необходимости.

Если вы можете обойтись, forget, безусловно, самая простая вещь для сейчас, но это определенно болевая точка! Мы не можем дождаться существования WeakRef:)

0 голосов
/ 09 ноября 2018

Насколько я понимаю из документации, не предполагается экспортировать замыкания Rust, они могут передаваться только в качестве параметров импортированным функциям JS, но все это происходит в коде Rust.

https://rustwasm.github.io/wasm-bindgen/reference/passing-rust-closures-to-js.html#passing-rust-closures-to-imported-javascript-functions

Я провел пару экспериментов, и когда функция Rust возвращает упомянутый тип 'Closure', компилятор выдает исключение: the trait wasm_bindgen::convert::IntoWasmAbi is not implemented for wasm_bindgen::prelude::Closure<(dyn std::ops::FnMut() -> u32 + 'static)>

Во всех примерах замыкания заключены в произвольный контур, но после этого вы уже не можете вызывать это на стороне JS.

...