Explorar o código

wip: ric_raf wired up

Jonathan Kelley %!s(int64=3) %!d(string=hai) anos
pai
achega
8b0d04c
Modificáronse 3 ficheiros con 112 adicións e 33 borrados
  1. 13 7
      packages/core/src/virtual_dom.rs
  2. 16 26
      packages/web/src/lib.rs
  3. 83 0
      packages/web/src/ric_raf.rs

+ 13 - 7
packages/core/src/virtual_dom.rs

@@ -225,8 +225,8 @@ impl VirtualDom {
     /// If there are pending tasks, they will be progressed before returning. This is useful when rendering an application
     /// that has suspended nodes or suspended tasks. Be warned - any async tasks running forever will prevent this method
     /// from completing. Consider using `run` and specifing a deadline.
-    pub async fn run_unbounded<'s>(&'s mut self) -> Result<Mutations<'s>> {
-        self.run_with_deadline(async {}).await
+    pub async fn run_unbounded<'s>(&'s mut self) -> Mutations<'s> {
+        self.run_with_deadline(async {}).await.unwrap()
     }
 
     /// Run the virtualdom with a deadline.
@@ -275,7 +275,7 @@ impl VirtualDom {
     pub async fn run_with_deadline<'s>(
         &'s mut self,
         deadline: impl Future<Output = ()>,
-    ) -> Result<Mutations<'s>> {
+    ) -> Option<Mutations<'s>> {
         let mut committed_mutations = Mutations::new();
         let mut deadline = Box::pin(deadline.fuse());
 
@@ -293,12 +293,12 @@ impl VirtualDom {
                 let deadline_expired = self.scheduler.wait_for_any_trigger(&mut deadline).await;
 
                 if deadline_expired {
-                    return Ok(committed_mutations);
+                    return Some(committed_mutations);
                 }
             }
 
             // Create work from the pending event queue
-            self.scheduler.consume_pending_events()?;
+            self.scheduler.consume_pending_events();
 
             // Work through the current subtree, and commit the results when it finishes
             // When the deadline expires, give back the work
@@ -316,10 +316,10 @@ impl VirtualDom {
                     */
 
                     if !self.scheduler.has_any_work() {
-                        return Ok(committed_mutations);
+                        return Some(committed_mutations);
                     }
                 }
-                FiberResult::Interrupted => return Ok(committed_mutations),
+                FiberResult::Interrupted => return None,
             }
         }
     }
@@ -327,6 +327,12 @@ impl VirtualDom {
     pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<EventTrigger> {
         self.shared.ui_event_sender.clone()
     }
+
+    pub fn has_work(&self) -> bool {
+        true
+    }
+
+    pub async fn wait_for_any_work(&self) {}
 }
 
 // TODO!

+ 16 - 26
packages/web/src/lib.rs

@@ -56,20 +56,16 @@ use std::rc::Rc;
 
 pub use crate::cfg::WebConfig;
 use crate::dom::load_document;
-use dioxus::prelude::{Context, Properties, VNode};
+use dioxus::prelude::Properties;
 use dioxus::virtual_dom::VirtualDom;
 pub use dioxus_core as dioxus;
-use dioxus_core::error::Result;
-use dioxus_core::{events::EventTrigger, prelude::FC};
-use futures_util::{pin_mut, Stream, StreamExt};
-use fxhash::FxHashMap;
-use js_sys::Iterator;
-use web_sys::{window, Document, Element, Event, Node, NodeList};
+use dioxus_core::prelude::FC;
 
 mod cache;
 mod cfg;
 mod dom;
 mod nodeslab;
+mod ric_raf;
 
 /// Launches the VirtualDOM from the specified component function.
 ///
@@ -100,15 +96,8 @@ where
 {
     let config = config(WebConfig::default());
     let fut = run_with_props(root, root_props, config);
-
-    wasm_bindgen_futures::spawn_local(async {
-        match fut.await {
-            Ok(_) => log::error!("Your app completed running... somehow?"),
-            Err(e) => log::error!("Your app crashed! {}", e),
-        }
-    });
+    wasm_bindgen_futures::spawn_local(fut);
 }
-
 /// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
 /// See DioxusErrors for more information on how these errors could occour.
 ///
@@ -122,11 +111,7 @@ where
 ///
 /// Run the app to completion, panicing if any error occurs while rendering.
 /// Pairs well with the wasm_bindgen async handler
-pub async fn run_with_props<T: Properties + 'static>(
-    root: FC<T>,
-    root_props: T,
-    cfg: WebConfig,
-) -> Result<()> {
+pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T, cfg: WebConfig) {
     let mut dom = VirtualDom::new_with_props(root, root_props);
 
     let hydrating = cfg.hydrate;
@@ -138,7 +123,6 @@ pub async fn run_with_props<T: Properties + 'static>(
     let mut websys_dom = dom::WebsysDom::new(root_el, cfg, sender_callback);
 
     let mut mutations = dom.rebuild();
-    log::info!("Mutations: {:#?}", mutations);
 
     // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
     // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
@@ -146,12 +130,18 @@ pub async fn run_with_props<T: Properties + 'static>(
         websys_dom.process_edits(&mut mutations.edits);
     }
 
+    let work_loop = ric_raf::RafLoop::new();
     loop {
-        let deadline = gloo_timers::future::TimeoutFuture::new(16);
-        let mut mutations = dom.run_with_deadline(deadline).await?;
+        // if virtualdom has nothing, wait for it to have something before requesting idle time
+        if !dom.has_work() {
+            dom.wait_for_any_work().await;
+        }
 
-        websys_dom.process_edits(&mut mutations.edits);
-    }
+        let deadline = work_loop.wait_for_idle_time().await;
 
-    Ok(())
+        if let Some(mut mutations) = dom.run_with_deadline(deadline).await {
+            work_loop.wait_for_raf().await;
+            websys_dom.process_edits(&mut mutations.edits);
+        }
+    }
 }

+ 83 - 0
packages/web/src/ric_raf.rs

@@ -0,0 +1,83 @@
+//! RequestAnimationFrame and RequestIdleCallback port and polyfill.
+
+use std::{cell::RefCell, fmt, rc::Rc};
+
+use gloo_timers::future::TimeoutFuture;
+use js_sys::Function;
+use wasm_bindgen::prelude::*;
+use wasm_bindgen::JsCast;
+use wasm_bindgen::{prelude::Closure, JsValue};
+use web_sys::Window;
+
+pub struct RafLoop {
+    window: Window,
+    ric_receiver: async_channel::Receiver<()>,
+    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();
+
+        let raf_closure: Closure<dyn Fn(JsValue)> =
+            Closure::wrap(Box::new(move |v: JsValue| raf_sender.try_send(()).unwrap()));
+
+        let (ric_sender, ric_receiver) = async_channel::unbounded();
+
+        let ric_closure: Closure<dyn Fn(JsValue)> =
+            Closure::wrap(Box::new(move |v: JsValue| ric_sender.try_send(()).unwrap()));
+
+        Self {
+            window: web_sys::window().unwrap(),
+            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 {
+        // comes with its own safari polyfill :)
+
+        let ric_fn = self.ric_closure.as_ref().dyn_ref::<Function>().unwrap();
+        let deadline: u32 = self.window.request_idle_callback(ric_fn).unwrap();
+
+        self.ric_receiver.recv().await.unwrap();
+
+        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();
+        let id: i32 = self.window.request_animation_frame(raf_fn).unwrap();
+        self.raf_receiver.recv().await.unwrap();
+    }
+}
+
+#[derive(Debug)]
+pub struct AnimationFrame {
+    render_id: i32,
+    closure: Closure<dyn Fn(JsValue)>,
+    callback_wrapper: Rc<RefCell<Option<CallbackWrapper>>>,
+}
+
+struct CallbackWrapper(Box<dyn FnOnce(f64) + 'static>);
+impl fmt::Debug for CallbackWrapper {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str("CallbackWrapper")
+    }
+}
+
+impl Drop for AnimationFrame {
+    fn drop(&mut self) {
+        if self.callback_wrapper.borrow_mut().is_some() {
+            web_sys::window()
+                .unwrap_throw()
+                .cancel_animation_frame(self.render_id)
+                .unwrap_throw()
+        }
+    }
+}