2021-09-25 00:11:30 +00:00
|
|
|
//! This module provides some utilities around scheduling tasks on the main thread of the browser.
|
|
|
|
//!
|
|
|
|
//! The ultimate goal here is to not block the main thread during animation frames, so our animations don't result in "jank".
|
|
|
|
//!
|
|
|
|
//! Hence, this module provides Dioxus "Jank Free Rendering" on the web.
|
|
|
|
//!
|
|
|
|
//! Because RIC doesn't work on Safari, we polyfill using the "ricpolyfill.js" file and use some basic detection to see
|
|
|
|
//! if RIC is available.
|
2021-08-24 20:29:10 +00:00
|
|
|
|
|
|
|
use gloo_timers::future::TimeoutFuture;
|
|
|
|
use js_sys::Function;
|
2021-09-25 00:11:30 +00:00
|
|
|
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
|
2021-09-24 04:05:56 +00:00
|
|
|
use web_sys::{window, Window};
|
2021-08-24 20:29:10 +00:00
|
|
|
|
2021-09-25 00:11:30 +00:00
|
|
|
pub(crate) struct RafLoop {
|
2021-08-24 20:29:10 +00:00
|
|
|
window: Window,
|
2021-09-22 08:11:27 +00:00
|
|
|
ric_receiver: async_channel::Receiver<u32>,
|
2021-08-24 20:29:10 +00:00
|
|
|
raf_receiver: async_channel::Receiver<()>,
|
|
|
|
ric_closure: Closure<dyn Fn(JsValue)>,
|
|
|
|
raf_closure: Closure<dyn Fn(JsValue)>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RafLoop {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
let (raf_sender, raf_receiver) = async_channel::unbounded();
|
|
|
|
|
2021-08-25 14:49:18 +00:00
|
|
|
let raf_closure: Closure<dyn Fn(JsValue)> = Closure::wrap(Box::new(move |_v: JsValue| {
|
|
|
|
raf_sender.try_send(()).unwrap()
|
|
|
|
}));
|
2021-08-24 20:29:10 +00:00
|
|
|
|
|
|
|
let (ric_sender, ric_receiver) = async_channel::unbounded();
|
|
|
|
|
2021-09-24 04:05:56 +00:00
|
|
|
let has_idle_callback = {
|
|
|
|
let bo = window().unwrap().dyn_into::<js_sys::Object>().unwrap();
|
|
|
|
bo.has_own_property(&JsValue::from_str("requestIdleCallback"))
|
|
|
|
};
|
|
|
|
let ric_closure: Closure<dyn Fn(JsValue)> = Closure::wrap(Box::new(move |v: JsValue| {
|
|
|
|
let time_remaining = if has_idle_callback {
|
|
|
|
if let Ok(deadline) = v.dyn_into::<web_sys::IdleDeadline>() {
|
|
|
|
deadline.time_remaining() as u32
|
|
|
|
} else {
|
|
|
|
10
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
10
|
|
|
|
};
|
|
|
|
|
2021-09-22 08:11:27 +00:00
|
|
|
ric_sender.try_send(time_remaining).unwrap()
|
2021-08-25 14:49:18 +00:00
|
|
|
}));
|
2021-08-24 20:29:10 +00:00
|
|
|
|
2021-08-24 20:36:00 +00:00
|
|
|
// execute the polyfill for safari
|
|
|
|
Function::new_no_args(include_str!("./ricpolyfill.js"))
|
|
|
|
.call0(&JsValue::NULL)
|
|
|
|
.unwrap();
|
|
|
|
|
2021-09-22 05:25:28 +00:00
|
|
|
let window = web_sys::window().unwrap();
|
|
|
|
|
2021-08-24 20:29:10 +00:00
|
|
|
Self {
|
2021-09-22 05:25:28 +00:00
|
|
|
window,
|
2021-08-24 20:29:10 +00:00
|
|
|
raf_receiver,
|
|
|
|
raf_closure,
|
|
|
|
ric_receiver,
|
|
|
|
ric_closure,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/// waits for some idle time and returns a timeout future that expires after the idle time has passed
|
|
|
|
pub async fn wait_for_idle_time(&self) -> TimeoutFuture {
|
|
|
|
let ric_fn = self.ric_closure.as_ref().dyn_ref::<Function>().unwrap();
|
2021-09-22 08:11:27 +00:00
|
|
|
let _cb_id: u32 = self.window.request_idle_callback(ric_fn).unwrap();
|
|
|
|
let deadline = self.ric_receiver.recv().await.unwrap();
|
2021-08-24 20:29:10 +00:00
|
|
|
let deadline = TimeoutFuture::new(deadline);
|
|
|
|
deadline
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn wait_for_raf(&self) {
|
|
|
|
let raf_fn = self.raf_closure.as_ref().dyn_ref::<Function>().unwrap();
|
2021-08-25 14:49:18 +00:00
|
|
|
let _id: i32 = self.window.request_animation_frame(raf_fn).unwrap();
|
2021-08-24 20:29:10 +00:00
|
|
|
self.raf_receiver.recv().await.unwrap();
|
|
|
|
}
|
|
|
|
}
|