簡體   English   中英

在使用wasm-bindgen時,如何解決無法使用生命周期導出函數的問題?

[英]How can I work around not being able to export functions with lifetimes when using 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()函數。 這對我不起作用,因為我的游戲狀態需要生命周期參數,因為它實際上只包含了一個specs WorldDispatcher結構,后者包含生命周期參數。 最終,這意味着我無法使用#[wasm_bindgen]導出它。

我很難找到解決這些限制的方法,並且正在尋找建議。

對此進行建模的最簡單方法是將requestAnimationFrame調用留給JS,而只是在Rust中實現更新/繪制邏輯。

然而,在Rust中,您還可以利用這樣一個事實,即實際上沒有捕獲任何變量的Closure<T>是零大小的,這意味着該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

我是Rust的新手,但這就是我如何處理同樣的問題。

您可以通過從window.set_interval回調中調用window.request_animation_frame來消除有問題的window.request_animation_frame遞歸並同時實現FPS上限,該回調檢查Rc<RefCell<bool>>或其他東西以查看是否仍有動畫幀請求懸而未決。 我不確定非活動標簽行為在實踐中是否會有所不同。

我將bool置於我的應用程序狀態,因為我正在使用Rc<RefCell<...>>來進行其他事件處理。 我沒有檢查下面這個編譯原樣,但這里是我這樣做的相關部分:

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_closureOption您的游戲狀態s,以便在需要出於某種原因(可能?我還沒有試過這種游戲能自我清潔起來,這似乎導致自由的self ?)。 循環引用不會擦除自身,除非被破壞(然后你有效地將Rc存儲到應用程序內的應用程序中)。 它還應該允許您在運行時更改最大fps,方法是停止間隔並使用相同的閉包創建另一個間隔。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM