Use Window.requestIdleCallback()

This commit is contained in:
dAxpeDDa 2023-06-13 16:39:06 +02:00 committed by daxpedda
parent a444637b18
commit 7ce86c3d2a
6 changed files with 59 additions and 26 deletions

View file

@ -78,6 +78,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- On Web, fix the bfcache by not using the `beforeunload` event. - On Web, fix the bfcache by not using the `beforeunload` event.
- On Web, fix scale factor resize suggestion always overwriting the canvas size. - On Web, fix scale factor resize suggestion always overwriting the canvas size.
- On macOS, fix crash when dropping `Window`. - On macOS, fix crash when dropping `Window`.
- On Web, use `Window.requestIdleCallback()` for `ControlFlow::Poll` when available.
# 0.28.6 # 0.28.6

View file

@ -158,13 +158,6 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
pub enum ControlFlow { pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of /// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process. /// whether or not new events are available to process.
///
/// ## Platform-specific
///
/// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes
/// the events in the queue may be sent before the next `requestAnimationFrame` callback, for
/// example when the scaling of the page has changed. This should be treated as an implementation
/// detail which should not be relied on.
Poll, Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives. /// When the current loop iteration finishes, suspend the thread until another event arrives.

View file

@ -442,10 +442,9 @@ impl<T: 'static> Shared<T> {
ControlFlow::Poll => { ControlFlow::Poll => {
let cloned = self.clone(); let cloned = self.clone();
State::Poll { State::Poll {
request: backend::AnimationFrameRequest::new( request: backend::IdleCallback::new(self.window().clone(), move || {
self.window().clone(), cloned.poll()
move || cloned.poll(), }),
),
} }
} }
ControlFlow::Wait => State::Wait { ControlFlow::Wait => State::Wait {

View file

@ -15,7 +15,7 @@ pub enum State {
start: Instant, start: Instant,
}, },
Poll { Poll {
request: backend::AnimationFrameRequest, request: backend::IdleCallback,
}, },
Exit, Exit,
} }

View file

@ -9,7 +9,7 @@ mod timeout;
pub use self::canvas::Canvas; pub use self::canvas::Canvas;
pub use self::event::ButtonsState; pub use self::event::ButtonsState;
pub use self::scaling::ScaleChangeDetector; pub use self::scaling::ScaleChangeDetector;
pub use self::timeout::{AnimationFrameRequest, Timeout}; pub use self::timeout::{IdleCallback, Timeout};
use crate::dpi::{LogicalSize, Size}; use crate::dpi::{LogicalSize, Size};
use crate::platform::web::WindowExtWebSys; use crate::platform::web::WindowExtWebSys;

View file

@ -1,8 +1,11 @@
use once_cell::unsync::OnceCell;
use std::cell::Cell; use std::cell::Cell;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use wasm_bindgen::closure::Closure; use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
#[derive(Debug)] #[derive(Debug)]
pub struct Timeout { pub struct Timeout {
@ -40,16 +43,21 @@ impl Drop for Timeout {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct AnimationFrameRequest { pub struct IdleCallback {
window: web_sys::Window, window: web_sys::Window,
handle: i32, handle: Handle,
// track callback state, because `cancelAnimationFrame` is slow
fired: Rc<Cell<bool>>, fired: Rc<Cell<bool>>,
_closure: Closure<dyn FnMut()>, _closure: Closure<dyn FnMut()>,
} }
impl AnimationFrameRequest { #[derive(Clone, Copy, Debug)]
pub fn new<F>(window: web_sys::Window, mut f: F) -> AnimationFrameRequest enum Handle {
IdleCallback(u32),
Timeout(i32),
}
impl IdleCallback {
pub fn new<F>(window: web_sys::Window, mut f: F) -> IdleCallback
where where
F: 'static + FnMut(), F: 'static + FnMut(),
{ {
@ -60,11 +68,21 @@ impl AnimationFrameRequest {
f(); f();
}) as Box<dyn FnMut()>); }) as Box<dyn FnMut()>);
let handle = window let handle = if has_idle_callback_support(&window) {
.request_animation_frame(closure.as_ref().unchecked_ref()) Handle::IdleCallback(
.expect("Failed to request animation frame"); window
.request_idle_callback(closure.as_ref().unchecked_ref())
.expect("Failed to request idle callback"),
)
} else {
Handle::Timeout(
window
.set_timeout_with_callback(closure.as_ref().unchecked_ref())
.expect("Failed to set timeout"),
)
};
AnimationFrameRequest { IdleCallback {
window, window,
handle, handle,
fired, fired,
@ -73,12 +91,34 @@ impl AnimationFrameRequest {
} }
} }
impl Drop for AnimationFrameRequest { impl Drop for IdleCallback {
fn drop(&mut self) { fn drop(&mut self) {
if !(*self.fired).get() { if !(*self.fired).get() {
self.window match self.handle {
.cancel_animation_frame(self.handle) Handle::IdleCallback(handle) => self.window.cancel_idle_callback(handle),
.expect("Failed to cancel animation frame"); Handle::Timeout(handle) => self.window.clear_timeout_with_handle(handle),
}
} }
} }
} }
fn has_idle_callback_support(window: &web_sys::Window) -> bool {
thread_local! {
static IDLE_CALLBACK_SUPPORT: OnceCell<bool> = OnceCell::new();
}
IDLE_CALLBACK_SUPPORT.with(|support| {
*support.get_or_init(|| {
#[wasm_bindgen]
extern "C" {
type IdleCallbackSupport;
#[wasm_bindgen(method, getter, js_name = requestIdleCallback)]
fn has_request_idle_callback(this: &IdleCallbackSupport) -> JsValue;
}
let support: &IdleCallbackSupport = window.unchecked_ref();
!support.has_request_idle_callback().is_undefined()
})
})
}