1
0

ric_raf.rs 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. //! This module provides some utilities around scheduling tasks on the main thread of the browser.
  2. //!
  3. //! The ultimate goal here is to not block the main thread during animation frames, so our animations don't result in "jank".
  4. //!
  5. //! Hence, this module provides Dioxus "Jank Free Rendering" on the web.
  6. //!
  7. //! Because RIC doesn't work on Safari, we polyfill using the "ricpolyfill.js" file and use some basic detection to see
  8. //! if RIC is available.
  9. use futures_util::StreamExt;
  10. use gloo_timers::future::TimeoutFuture;
  11. use js_sys::Function;
  12. use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
  13. use web_sys::{window, Window};
  14. pub(crate) struct RafLoop {
  15. window: Window,
  16. ric_receiver: futures_channel::mpsc::UnboundedReceiver<u32>,
  17. raf_receiver: futures_channel::mpsc::UnboundedReceiver<()>,
  18. ric_closure: Closure<dyn Fn(JsValue)>,
  19. raf_closure: Closure<dyn Fn(JsValue)>,
  20. }
  21. impl RafLoop {
  22. pub fn new() -> Self {
  23. let (raf_sender, raf_receiver) = futures_channel::mpsc::unbounded();
  24. let raf_closure: Closure<dyn Fn(JsValue)> = Closure::wrap(Box::new(move |_v: JsValue| {
  25. raf_sender.unbounded_send(()).unwrap()
  26. }));
  27. let (ric_sender, ric_receiver) = futures_channel::mpsc::unbounded();
  28. let has_idle_callback = {
  29. let bo = window().unwrap().dyn_into::<js_sys::Object>().unwrap();
  30. bo.has_own_property(&JsValue::from_str("requestIdleCallback"))
  31. };
  32. let ric_closure: Closure<dyn Fn(JsValue)> = Closure::wrap(Box::new(move |v: JsValue| {
  33. let time_remaining = if has_idle_callback {
  34. if let Ok(deadline) = v.dyn_into::<web_sys::IdleDeadline>() {
  35. deadline.time_remaining() as u32
  36. } else {
  37. 10
  38. }
  39. } else {
  40. 10
  41. };
  42. ric_sender.unbounded_send(time_remaining).unwrap()
  43. }));
  44. // execute the polyfill for safari
  45. Function::new_no_args(include_str!("./ricpolyfill.js"))
  46. .call0(&JsValue::NULL)
  47. .unwrap();
  48. let window = web_sys::window().unwrap();
  49. Self {
  50. window,
  51. raf_receiver,
  52. raf_closure,
  53. ric_receiver,
  54. ric_closure,
  55. }
  56. }
  57. /// waits for some idle time and returns a timeout future that expires after the idle time has passed
  58. pub async fn wait_for_idle_time(&mut self) -> TimeoutFuture {
  59. let ric_fn = self.ric_closure.as_ref().dyn_ref::<Function>().unwrap();
  60. let _cb_id: u32 = self.window.request_idle_callback(ric_fn).unwrap();
  61. let deadline = self.ric_receiver.next().await.unwrap();
  62. TimeoutFuture::new(deadline)
  63. }
  64. pub async fn wait_for_raf(&mut self) {
  65. let raf_fn = self.raf_closure.as_ref().dyn_ref::<Function>().unwrap();
  66. let _id: i32 = self.window.request_animation_frame(raf_fn).unwrap();
  67. self.raf_receiver.next().await.unwrap();
  68. }
  69. }