![](/img/trans.png)
[英]How can I call a JavaScript function that is a module with 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 World
和Dispatcher
結構,后者包含生命周期參數。 最終,這意味着我無法使用#[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_interval
和timer_closure
中Option
您的游戲狀態s,以便在需要出於某種原因(可能?我還沒有試過這種游戲能自我清潔起來,這似乎導致自由的self
?)。 循環引用不會擦除自身,除非被破壞(然后你有效地將Rc
存儲到應用程序內的應用程序中)。 它還應該允許您在運行時更改最大fps,方法是停止間隔並使用相同的閉包創建另一個間隔。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.