Skip to content

Commit

Permalink
Use setTimeout trick instead of requestIdleCallback
Browse files Browse the repository at this point in the history
  • Loading branch information
daxpedda committed Aug 24, 2023
1 parent 9979441 commit 2ba47b3
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 102 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ features = [
'IntersectionObserverEntry',
'KeyboardEvent',
'MediaQueryList',
'MessageChannel',
'MessagePort',
'Node',
'PageTransitionEvent',
'PointerEvent',
Expand Down
10 changes: 6 additions & 4 deletions src/platform_impl/web/event_loop/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,9 +627,11 @@ impl<T: 'static> Shared<T> {
ControlFlow::Poll => {
let cloned = self.clone();
State::Poll {
request: backend::IdleCallback::new(self.window().clone(), move || {
cloned.poll()
}),
request: backend::Timeout::new(
self.window().clone(),
move || cloned.poll(),
None,
),
}
}
ControlFlow::Wait => State::Wait {
Expand All @@ -652,7 +654,7 @@ impl<T: 'static> Shared<T> {
timeout: backend::Timeout::new(
self.window().clone(),
move || cloned.resume_time_reached(start, end),
delay,
Some(delay),
),
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/platform_impl/web/event_loop/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub enum State {
start: Instant,
},
Poll {
request: backend::IdleCallback,
request: backend::Timeout,
},
Exit,
}
Expand Down
2 changes: 1 addition & 1 deletion src/platform_impl/web/web_sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub use self::canvas::Canvas;
pub use self::event::ButtonsState;
pub use self::event_handle::EventListenerHandle;
pub use self::resize_scaling::ResizeScaleHandle;
pub use self::timeout::{IdleCallback, Timeout};
pub use self::timeout::Timeout;

use crate::dpi::{LogicalPosition, LogicalSize};
use wasm_bindgen::closure::Closure;
Expand Down
127 changes: 31 additions & 96 deletions src/platform_impl/web/web_sys/timeout.rs
Original file line number Diff line number Diff line change
@@ -1,124 +1,59 @@
use once_cell::unsync::OnceCell;
use std::cell::Cell;
use std::rc::Rc;
use std::time::Duration;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{MessageChannel, MessagePort};

#[derive(Debug)]
pub struct Timeout {
window: web_sys::Window,
handle: i32,
_closure: Closure<dyn FnMut()>,
port: MessagePort,
_message_closure: Closure<dyn FnMut()>,
_timeout_closure: Closure<dyn FnMut()>,
}

impl Timeout {
pub fn new<F>(window: web_sys::Window, f: F, duration: Duration) -> Timeout
pub fn new<F>(window: web_sys::Window, f: F, duration: Option<Duration>) -> Timeout
where
F: 'static + FnMut(),
{
let closure = Closure::wrap(Box::new(f) as Box<dyn FnMut()>);

let handle = window
.set_timeout_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
let channel = MessageChannel::new().unwrap();
let message_closure = Closure::new(f);
let port_1 = channel.port1();
port_1
.add_event_listener_with_callback("message", message_closure.as_ref().unchecked_ref())
.expect("Failed to set message handler");
port_1.start();

let port_2 = channel.port2();
let timeout_closure = Closure::new(move || {
port_2
.post_message(&JsValue::UNDEFINED)
.expect("Failed to send message")
});
let handle = if let Some(duration) = duration {
window.set_timeout_with_callback_and_timeout_and_arguments_0(
timeout_closure.as_ref().unchecked_ref(),
duration.as_millis() as i32,
)
.expect("Failed to set timeout");
} else {
window.set_timeout_with_callback(timeout_closure.as_ref().unchecked_ref())
}
.expect("Failed to set timeout");

Timeout {
window,
handle,
_closure: closure,
port: port_1,
_message_closure: message_closure,
_timeout_closure: timeout_closure,
}
}
}

impl Drop for Timeout {
fn drop(&mut self) {
self.window.clear_timeout_with_handle(self.handle);
self.port.close();
}
}

#[derive(Debug)]
pub struct IdleCallback {
window: web_sys::Window,
handle: Handle,
fired: Rc<Cell<bool>>,
_closure: Closure<dyn FnMut()>,
}

#[derive(Clone, Copy, Debug)]
enum Handle {
IdleCallback(u32),
Timeout(i32),
}

impl IdleCallback {
pub fn new<F>(window: web_sys::Window, mut f: F) -> IdleCallback
where
F: 'static + FnMut(),
{
let fired = Rc::new(Cell::new(false));
let c_fired = fired.clone();
let closure = Closure::wrap(Box::new(move || {
(*c_fired).set(true);
f();
}) as Box<dyn FnMut()>);

let handle = if has_idle_callback_support(&window) {
Handle::IdleCallback(
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"),
)
};

IdleCallback {
window,
handle,
fired,
_closure: closure,
}
}
}

impl Drop for IdleCallback {
fn drop(&mut self) {
if !(*self.fired).get() {
match self.handle {
Handle::IdleCallback(handle) => self.window.cancel_idle_callback(handle),
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()
})
})
}

0 comments on commit 2ba47b3

Please sign in to comment.