Я новичок в Rust, но вот как я решил ту же проблему.
Вы можете устранить проблемную рекурсию window.request_animation_frame
и одновременно реализовать ограничение FPS, вызвав window.request_animation_frame
изwindow.set_interval
обратный вызов, который проверяет 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
s для приложения внутри приложения).Он также должен позволять вам изменять максимальный fps во время работы, останавливая интервал и создавая другой, используя то же замыкание.