我正在尝试编写一个在浏览器中运行的简单游戏,考虑到浏览器、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,所以这不起作用。
有人建议的另一种方法是遵循game of life example中说明的模式。从高层次上讲,它涉及到导出一个类型,该类型包含游戏状态并包含可从javascript游戏循环内调用的公共tick()和render()函数。这对我不起作用,因为我的游戏状态需要生命周期参数,因为它实际上只是包装了一个specs World和Dispatcher结构,后者具有生命周期参数。最终,这意味着我不能使用#[wasm_bindgen]导出它。
我很难找到绕过这些限制的方法,并正在寻找建议。
发布于 2018-11-02 04:09:19
对此进行建模的最简单方法可能是将requestAnimationFrame的调用留给JS,而只需在Rust中实现更新/绘制逻辑。
然而,在Rust中,您还可以利用这样一个事实,即没有实际捕获任何变量的闭包是零大小的,这意味着该闭包的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
发布于 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<...>>来处理其他事件。我还没有检查以下代码是否按原样编译,但以下是我如何执行此操作的相关部分:
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的结果存储在您的游戏状态中的Options中,以便您的游戏可以在出于某些原因(可能?我还没有尝试过,它似乎会导致免费的self?)循环引用不会自行擦除,除非被破坏(然后您可以有效地将Rc存储到应用程序中)。它还应该允许您在运行时更改最大fps,方法是停止间隔并使用相同的闭包创建另一个间隔。
https://stackoverflow.com/questions/53000413
复制相似问题