Kaynağa Gözat

wip: groundwork for noderefs

Jonathan Kelley 3 yıl önce
ebeveyn
işleme
c1afeba

+ 1 - 0
.vscode/spellright.dict

@@ -65,3 +65,4 @@ asynchronicity
 constified
 SegVec
 contentful
+Jank

+ 5 - 2
Cargo.toml

@@ -51,11 +51,14 @@ argh = "0.1.5"
 env_logger = "*"
 async-std = { version = "1.9.0", features = ["attributes"] }
 rand = { version = "0.8.4", features = ["small_rng"] }
-surf = {version = "2.2.0", git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm"}
+surf = { version = "2.2.0", git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm" }
+gloo-timers = "0.2.1"
 
 [target.'cfg(target_arch = "wasm32")'.dev-dependencies]
 gloo-timers = "0.2.1"
-surf = {version = "2.2.0", default-features = false, features = ["wasm-client"], git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm"}
+surf = { version = "2.2.0", default-features = false, features = [
+    "wasm-client",
+], git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm" }
 
 
 [dependencies.getrandom]

+ 30 - 29
README.md

@@ -147,35 +147,36 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
 
 ### Phase 1: The Basics
 
-| Feature                   | Dioxus | React | Notes for Dioxus                                            |
-| ------------------------- | ------ | ----- | ----------------------------------------------------------- |
-| Conditional Rendering     | ✅      | ✅     | if/then to hide/show component                              |
-| Map, Iterator             | ✅      | ✅     | map/filter/reduce to produce rsx!                           |
-| Keyed Components          | ✅      | ✅     | advanced diffing with keys                                  |
-| Web                       | ✅      | ✅     | renderer for web browser                                    |
-| Desktop (webview)         | ✅      | ✅     | renderer for desktop                                        |
-| Shared State (Context)    | ✅      | ✅     | share state through the tree                                |
-| Hooks                     | ✅      | ✅     | memory cells in components                                  |
-| SSR                       | ✅      | ✅     | render directly to string                                   |
-| Component Children        | ✅      | ✅     | cx.children() as a list of nodes                            |
-| Headless components       | ✅      | ✅     | components that don't return real elements                  |
-| Fragments                 | ✅      | ✅     | multiple elements without a real root                       |
-| Manual Props              | ✅      | ✅     | Manually pass in props with spread syntax                   |
-| Controlled Inputs         | ✅      | ✅     | stateful wrappers around inputs                             |
-| CSS/Inline Styles         | ✅      | ✅     | syntax for inline styles/attribute groups                   |
-| Custom elements           | ✅      | ✅     | Define new element primitives                               |
-| Suspense                  | ✅      | ✅     | schedule future render from future/promise                  |
-| Integrated error handling | ✅      | ✅     | Gracefully handle errors with ? syntax                      |
-| Re-hydration              | ✅      | ✅     | Pre-render to HTML to speed up first contentful paint       |
-| Cooperative Scheduling    | 🛠      | ✅     | Prioritize important events over non-important events       |
-| Runs natively             | ✅      | ❓     | runs as a portable binary w/o a runtime (Node)              |
-| 1st class global state    | ✅      | ❓     | redux/recoil/mobx on top of context                         |
-| Subtree Memoization       | ✅      | ❓     | skip diffing static element subtrees                        |
-| Compile-time correct      | ✅      | ❓     | Throw errors on invalid template layouts                    |
-| Heuristic Engine          | 🛠      | ❓     | track component memory usage to minimize future allocations |
-| Fine-grained reactivity   | 🛠      | ❓     | Skip diffing for fine-grain updates                         |
-| Effects                   | 🛠      | ✅     | Run effects after a component has been committed to render  |
-| NodeRef                   | 🛠      | ✅     | gain direct access to nodes [1]                             |
+| Feature                   | Dioxus | React | Notes for Dioxus                                                     |
+| ------------------------- | ------ | ----- | -------------------------------------------------------------------- |
+| Conditional Rendering     | ✅      | ✅     | if/then to hide/show component                                       |
+| Map, Iterator             | ✅      | ✅     | map/filter/reduce to produce rsx!                                    |
+| Keyed Components          | ✅      | ✅     | advanced diffing with keys                                           |
+| Web                       | ✅      | ✅     | renderer for web browser                                             |
+| Desktop (webview)         | ✅      | ✅     | renderer for desktop                                                 |
+| Shared State (Context)    | ✅      | ✅     | share state through the tree                                         |
+| Hooks                     | ✅      | ✅     | memory cells in components                                           |
+| SSR                       | ✅      | ✅     | render directly to string                                            |
+| Component Children        | ✅      | ✅     | cx.children() as a list of nodes                                     |
+| Headless components       | ✅      | ✅     | components that don't return real elements                           |
+| Fragments                 | ✅      | ✅     | multiple elements without a real root                                |
+| Manual Props              | ✅      | ✅     | Manually pass in props with spread syntax                            |
+| Controlled Inputs         | ✅      | ✅     | stateful wrappers around inputs                                      |
+| CSS/Inline Styles         | ✅      | ✅     | syntax for inline styles/attribute groups                            |
+| Custom elements           | ✅      | ✅     | Define new element primitives                                        |
+| Suspense                  | ✅      | ✅     | schedule future render from future/promise                           |
+| Integrated error handling | ✅      | ✅     | Gracefully handle errors with ? syntax                               |
+| NodeRef                   | ✅      | ✅     | gain direct access to nodes [1]                                      |
+| Re-hydration              | ✅      | ✅     | Pre-render to HTML to speed up first contentful paint                |
+| Jank-Free Rendering       | ✅      | ✅     | Large diffs are segmented across frames for silky-smooth transitions |
+| Cooperative Scheduling    | ✅      | ✅     | Prioritize important events over non-important events                |
+| Runs natively             | ✅      | ❓     | runs as a portable binary w/o a runtime (Node)                       |
+| 1st class global state    | ✅      | ❓     | redux/recoil/mobx on top of context                                  |
+| Subtree Memoization       | ✅      | ❓     | skip diffing static element subtrees                                 |
+| Compile-time correct      | ✅      | ❓     | Throw errors on invalid template layouts                             |
+| Heuristic Engine          | ✅      | ❓     | track component memory usage to minimize future allocations          |
+| Fine-grained reactivity   | 🛠      | ❓     | Skip diffing for fine-grain updates                                  |
+| Effects                   | 🛠      | ✅     | Run effects after a component has been committed to render           |
 
 - [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API.
 

+ 2 - 0
examples/reference/noderefs.rs

@@ -2,6 +2,8 @@ use dioxus::prelude::*;
 fn main() {}
 
 pub static Example: FC<()> = |cx| {
+    let p = 10;
+
     cx.render(rsx! {
         div {
 

+ 1 - 1
examples/reference/task.rs

@@ -30,7 +30,7 @@ pub static Example: FC<()> = |cx| {
 
     // Tasks are 'static, so we need to copy relevant items in
     let (async_count, dir) = (count.for_async(), *direction);
-    
+
     let (task, result) = use_task(cx, move || async move {
         loop {
             gloo_timers::future::TimeoutFuture::new(250).await;

+ 4 - 8
packages/core-macro/src/rsx/ambiguous.rs

@@ -39,7 +39,8 @@ impl Parse for AmbiguousElement<AS_RSX> {
             }
         }
 
-        if let Ok(name) = input.fork().parse::<Ident>() {
+        use syn::ext::IdentExt;
+        if let Ok(name) = input.fork().call(Ident::parse_any) {
             let name_str = name.to_string();
 
             let first_char = name_str.chars().next().unwrap();
@@ -53,9 +54,6 @@ impl Parse for AmbiguousElement<AS_RSX> {
                     .map(|c| AmbiguousElement::Element(c))
             }
         } else {
-            if input.peek(LitStr) {
-                panic!("it's actually a litstr");
-            }
             Err(Error::new(input.span(), "Not a valid Html tag"))
         }
     }
@@ -80,7 +78,8 @@ impl Parse for AmbiguousElement<AS_HTML> {
             }
         }
 
-        if let Ok(name) = input.fork().parse::<Ident>() {
+        use syn::ext::IdentExt;
+        if let Ok(name) = input.fork().call(Ident::parse_any) {
             let name_str = name.to_string();
 
             let first_char = name_str.chars().next().unwrap();
@@ -94,9 +93,6 @@ impl Parse for AmbiguousElement<AS_HTML> {
                     .map(|c| AmbiguousElement::Element(c))
             }
         } else {
-            if input.peek(LitStr) {
-                panic!("it's actually a litstr");
-            }
             Err(Error::new(input.span(), "Not a valid Html tag"))
         }
     }

+ 6 - 2
packages/core-macro/src/rsx/element.rs

@@ -32,6 +32,7 @@ impl Parse for Element<AS_RSX> {
         let mut listeners: Vec<ElementAttr<AS_RSX>> = vec![];
         let mut children: Vec<BodyNode<AS_RSX>> = vec![];
         let mut key = None;
+        let mut el_ref = None;
 
         'parsing: loop {
             // [1] Break if empty
@@ -45,6 +46,7 @@ impl Parse for Element<AS_RSX> {
                     &mut attributes,
                     &mut listeners,
                     &mut key,
+                    &mut el_ref,
                     name.clone(),
                 )?;
             } else {
@@ -237,6 +239,7 @@ fn parse_rsx_element_field(
     attrs: &mut Vec<ElementAttr<AS_RSX>>,
     listeners: &mut Vec<ElementAttr<AS_RSX>>,
     key: &mut Option<LitStr>,
+    el_ref: &mut Option<Expr>,
     element_name: Ident,
 ) -> Result<()> {
     let name = Ident::parse_any(stream)?;
@@ -311,8 +314,9 @@ fn parse_rsx_element_field(
         "namespace" => {
             todo!("custom namespace not supported")
         }
-        "ref" => {
-            todo!("NodeRefs are currently not supported! This is currently a reserved keyword.")
+        "node_ref" => {
+            *el_ref = Some(stream.parse::<Expr>()?);
+            return Ok(());
         }
 
         // Fall through

+ 3 - 0
packages/core/Cargo.toml

@@ -41,6 +41,9 @@ slab = "0.4.3"
 
 futures-channel = "0.3.16"
 
+# used for noderefs
+once_cell = "1.8.0"
+
 
 [dev-dependencies]
 anyhow = "1.0.42"

+ 1 - 1
packages/core/src/diff.rs

@@ -110,7 +110,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
     /// This will PANIC if any component elements are passed in.
     pub fn new_headless(shared: &'bump SharedResources) -> Self {
         Self {
-            mutations: Mutations { edits: Vec::new() },
+            mutations: Mutations::new(),
             scope_stack: smallvec![ScopeId(0)],
             vdom: shared,
             diffed: FxHashSet::default(),

+ 3 - 0
packages/core/src/events.rs

@@ -447,7 +447,10 @@ pub mod on {
             onmousemove
             /// onmouseout
             onmouseout
+
             /// onmouseover
+            ///
+            /// Triggered when the users's mouse hovers over an element.
             onmouseover
             /// onmouseup
             onmouseup

+ 28 - 0
packages/core/src/hooks.rs

@@ -256,3 +256,31 @@ impl<'src> SuspendedContext<'src> {
         Some(lazy_nodes.into_vnode(NodeFactory { bump }))
     }
 }
+
+#[derive(Clone, Copy)]
+pub struct NodeRef<'src, T: 'static>(&'src RefCell<T>);
+
+// impl NodeRef<'src, T> {
+// fn set_ref(&self, new: Box<dyn Any>)
+// }
+// impl<'a, T> std::ops::Deref for NodeRef<'a, T> {
+//     type Target = Option<&'a T>;
+
+//     fn deref(&self) -> &Self::Target {
+//         // &self.node
+//     }
+// }
+
+pub fn use_node_ref<T, P>(cx: Context<P>) -> NodeRef<T> {
+    cx.use_hook(
+        |f| {},
+        |f| {
+            //
+            todo!()
+            // NodeRef {}
+        },
+        |f| {
+            //
+        },
+    )
+}

+ 0 - 0
packages/core/src/noderef.rs


+ 55 - 14
packages/core/src/virtual_dom.rs

@@ -47,11 +47,11 @@ pub struct VirtualDom {
     ///
     /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
     /// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
-    pub shared: SharedResources,
+    shared: SharedResources,
 
     /// The index of the root component
     /// Should always be the first (gen=0, id=0)
-    pub base_scope: ScopeId,
+    base_scope: ScopeId,
 
     active_fibers: Vec<Fiber<'static>>,
 
@@ -197,8 +197,7 @@ impl VirtualDom {
     ///
     pub fn rebuild<'s>(&'s mut self) -> Result<Vec<DomEdit<'s>>> {
         let mut edits = Vec::new();
-        let mutations = Mutations { edits: Vec::new() };
-        let mut diff_machine = DiffMachine::new(mutations, self.base_scope, &self.shared);
+        let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.shared);
 
         let cur_component = diff_machine
             .get_scope_mut(&self.base_scope)
@@ -274,7 +273,7 @@ impl VirtualDom {
         self.run_with_deadline(async {}).await
     }
 
-    /// Run the virtualdom with a time limit.
+    /// Run the virtualdom with a deadline.
     ///
     /// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
     /// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
@@ -287,7 +286,7 @@ impl VirtualDom {
     /// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
     /// deadline closure manually.
     ///
-    /// The deadline is checked before starting to diff components. This strikes a balance between the overhead of checking
+    /// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
     /// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
     /// the screen will "jank" up. In debug, this will trigger an alert.
     ///
@@ -309,6 +308,14 @@ impl VirtualDom {
     ///     apply_mutations(mutations);
     /// }
     /// ```
+    ///
+    /// ## Mutations
+    ///
+    /// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
+    /// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
+    /// applied the edits.
+    ///
+    /// Mutations are the only link between the RealDOM and the VirtualDOM.
     pub async fn run_with_deadline<'s>(
         &'s mut self,
         mut deadline: impl Future<Output = ()>,
@@ -319,11 +326,7 @@ impl VirtualDom {
 
         let is_ready = || -> bool { (&mut deadline_future).now_or_never().is_some() };
 
-        let mut diff_machine = DiffMachine::new(
-            Mutations { edits: Vec::new() },
-            self.base_scope,
-            &self.shared,
-        );
+        let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.shared);
 
         /*
         Strategy:
@@ -348,6 +351,11 @@ impl VirtualDom {
         // 1. Consume any pending events and create new fibers
         let mut receiver = self.shared.task_receiver.borrow_mut();
 
+        let current_fiber = {
+            //
+            self.active_fibers.get_mut(0).unwrap()
+        };
+
         // On the primary event queue, there is no batching.
         let mut trigger = {
             match receiver.try_next() {
@@ -357,6 +365,8 @@ impl VirtualDom {
                     let mut tasks = self.shared.async_tasks.borrow_mut();
                     let tasks_tasks = tasks.next();
 
+                    // if the new event generates work more important than our current fiber, we should consider switching
+                    // only switch if it impacts different scopes.
                     let mut receiver = self.shared.task_receiver.borrow_mut();
                     let reciv_task = receiver.next();
 
@@ -365,10 +375,9 @@ impl VirtualDom {
 
                     // Poll the event receiver and the future pool for work
                     // Abort early if our deadline has ran out
-                    use futures_util::select;
                     let mut deadline = (&mut deadline_future).fuse();
 
-                    let trig = select! {
+                    let trig = futures_util::select! {
                         trigger = tasks_tasks => trigger,
                         trigger = reciv_task => trigger,
                         _ = deadline => { return Ok(diff_machine.mutations); }
@@ -401,6 +410,7 @@ impl VirtualDom {
             | VirtualEvent::ToggleEvent(_)
             | VirtualEvent::MouseEvent(_)
             | VirtualEvent::PointerEvent(_) => {
+                //
                 if let Some(scope) = self.shared.get_scope_mut(trigger.originator) {
                     scope.call_listener(trigger)?;
                 }
@@ -411,7 +421,7 @@ impl VirtualDom {
             // These shouldn't normally be received, but if they are, it's done because some task set state manually
             // Instead of processing it serially,
             // We will batch all the scheduled updates together in one go.
-            VirtualEvent::ScheduledUpdate { height: u32 } => {}
+            VirtualEvent::ScheduledUpdate { .. } => {}
 
             // Suspense Events! A component's suspended node is updated
             VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
@@ -556,4 +566,35 @@ pub struct Mutations<'s> {
     // todo: apply node refs
     // todo: apply effects
     pub edits: Vec<DomEdit<'s>>,
+
+    pub noderefs: Vec<NodeRefMutation<'s>>,
+}
+
+impl<'s> Mutations<'s> {
+    pub fn new() -> Self {
+        let edits = Vec::new();
+        let noderefs = Vec::new();
+        Self { edits, noderefs }
+    }
+}
+
+// refs are only assigned once
+pub struct NodeRefMutation<'a> {
+    element: &'a mut Option<once_cell::sync::OnceCell<Box<dyn Any>>>,
+    element_id: ElementId,
+}
+
+impl<'a> NodeRefMutation<'a> {
+    pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
+        self.element
+            .as_ref()
+            .and_then(|f| f.get())
+            .and_then(|f| f.downcast_ref::<T>())
+    }
+    pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
+        self.element
+            .as_mut()
+            .and_then(|f| f.get_mut())
+            .and_then(|f| f.downcast_mut::<T>())
+    }
 }

+ 4 - 4
packages/core/tests/diffing.rs

@@ -40,7 +40,7 @@ impl TestDom {
 
     fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Vec<DomEdit<'a>> {
         let mut edits = Vec::new();
-        let mut machine = DiffMachine::new_headless(&mut edits, &self.resources);
+        let mut machine = DiffMachine::new_headless(&self.resources);
         machine.diff_node(old, new);
         edits
     }
@@ -52,7 +52,7 @@ impl TestDom {
         let old = self.bump.alloc(self.render(left));
         let mut edits = Vec::new();
 
-        let mut machine = DiffMachine::new_headless(&mut edits, &self.resources);
+        let mut machine = DiffMachine::new_headless(&self.resources);
         let meta = machine.create_vnode(old);
         (meta, edits)
     }
@@ -72,11 +72,11 @@ impl TestDom {
 
         let mut create_edits = Vec::new();
 
-        let mut machine = DiffMachine::new_headless(&mut create_edits, &self.resources);
+        let mut machine = DiffMachine::new_headless(&self.resources);
         machine.create_vnode(old);
 
         let mut edits = Vec::new();
-        let mut machine = DiffMachine::new_headless(&mut edits, &self.resources);
+        let mut machine = DiffMachine::new_headless(&self.resources);
         machine.diff_node(old, new);
         (create_edits, edits)
     }

+ 28 - 0
packages/core/tests/hooks.rs

@@ -0,0 +1,28 @@
+use anyhow::{Context, Result};
+use dioxus::{
+    arena::SharedResources,
+    diff::{CreateMeta, DiffMachine},
+    prelude::*,
+    DomEdit,
+};
+use dioxus_core as dioxus;
+use dioxus_html as dioxus_elements;
+
+#[test]
+fn sample_refs() {
+    static App: FC<()> = |cx| {
+        let div_ref = use_node_ref::<MyRef, _>(cx);
+
+        cx.render(rsx! {
+            div {
+                style: { color: "red" }
+                node_ref: div_ref
+                onmouseover: move |_| {
+                    div_ref.borrow_mut().focus();
+                }
+            }
+        })
+    };
+}
+
+struct MyRef {}

+ 1 - 7
packages/desktop/src/dom.rs

@@ -1,6 +1,6 @@
 //! webview dom
 
-use dioxus_core::{DomEdit, RealDom};
+use dioxus_core::DomEdit;
 
 // pub struct WebviewRegistry {}
 
@@ -29,9 +29,3 @@ impl WebviewDom<'_> {
     //     self.registry
     // }
 }
-impl RealDom for WebviewDom<'_> {
-    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        todo!()
-        // self.edits.push(PushRoot { root });
-    }
-}

+ 1 - 7
packages/mobile/src/dom.rs

@@ -1,6 +1,6 @@
 //! webview dom
 
-use dioxus_core::{DomEdit, ElementId, RealDom, ScopeId};
+use dioxus_core::{DomEdit, ElementId, ScopeId};
 use DomEdit::*;
 
 pub struct WebviewRegistry {}
@@ -30,9 +30,3 @@ impl WebviewDom<'_> {
         self.registry
     }
 }
-impl<'bump> RealDom for WebviewDom<'bump> {
-    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        todo!()
-        // self.edits.push(PushRoot { root });
-    }
-}

+ 4 - 4
packages/mobile/src/lib.rs

@@ -117,8 +117,8 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
 
                                         // Serialize the edit stream
                                         let edits = {
-                                            let mut edits = Vec::new();
-                                            lock.rebuild(&mut edits).unwrap();
+                                            let mut edits = Vec::<DomEdit>::new();
+                                            // lock.rebuild(&mut edits).unwrap();
                                             serde_json::to_value(edits).unwrap()
                                         };
 
@@ -139,8 +139,8 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
 
                                     // Serialize the edit stream
                                     let edits = {
-                                        let mut edits = Vec::new();
-                                        lock.rebuild(&mut edits).unwrap();
+                                        let mut edits = Vec::<DomEdit>::new();
+                                        // lock.rebuild(&mut edits).unwrap();
                                         serde_json::to_value(edits).unwrap()
                                     };
 

+ 1 - 2
packages/web/Cargo.toml

@@ -23,7 +23,7 @@ wasm-bindgen-test = "0.3.21"
 once_cell = "1.8"
 async-channel = "1.6.1"
 anyhow = "1.0"
-
+gloo-timers = { version = "0.2.1", features = ["futures"] }
 futures-util = "0.3.15"
 
 [dependencies.web-sys]
@@ -78,7 +78,6 @@ im-rc = "15.0.0"
 separator = "0.4.1"
 uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
 dioxus-hooks = { path = "../hooks" }
-gloo-timers = { version = "0.2.1", features = ["futures"] }
 serde = { version = "1.0.126", features = ["derive"] }
 surf = { git = "https://github.com/http-rs/surf", rev = "1ffaba8873", default-features = false, features = [
     "wasm-client",

+ 0 - 12
packages/web/src/dom.rs

@@ -395,18 +395,6 @@ impl WebsysDom {
     }
 }
 
-impl<'a> dioxus_core::diff::RealDom for WebsysDom {
-    // fn request_available_node(&mut self) -> ElementId {
-    //     let key = self.nodes.insert(None);
-    //     log::debug!("making new key: {:#?}", key);
-    //     ElementId(key.data().as_ffi())
-    // }
-
-    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        todo!()
-    }
-}
-
 #[derive(Debug, Default)]
 pub struct Stack {
     pub list: Vec<Node>,

+ 6 - 10
packages/web/src/lib.rs

@@ -107,8 +107,6 @@ pub async fn run_with_props<T: Properties + 'static>(
 
     let tasks = dom.get_event_sender();
 
-    let mut real = RealDomWebsys {};
-
     // initialize the virtualdom first
     if cfg.hydrate {
         dom.rebuild_in_place()?;
@@ -120,7 +118,12 @@ pub async fn run_with_props<T: Properties + 'static>(
         Rc::new(move |event| tasks.unbounded_send(event).unwrap()),
     );
 
-    dom.run_with_deadline(&mut websys_dom).await?;
+    loop {
+        let deadline = gloo_timers::future::TimeoutFuture::new(16);
+        let mut mutations = dom.run_with_deadline(deadline).await?;
+
+        websys_dom.process_edits(&mut mutations.edits);
+    }
 
     Ok(())
 }
@@ -129,10 +132,3 @@ struct HydrationNode {
     id: usize,
     node: Node,
 }
-
-struct RealDomWebsys {}
-impl dioxus::RealDom for RealDomWebsys {
-    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        todo!()
-    }
-}