Browse Source

wip: moving over instructions

Jonathan Kelley 3 năm trước cách đây
mục cha
commit
0987760958

+ 1 - 1
packages/core/.vscode/settings.json

@@ -1,3 +1,3 @@
 {
-  "rust-analyzer.inlayHints.enable": true
+  "rust-analyzer.inlayHints.enable": false
 }

+ 198 - 444
packages/core/src/diff.rs

@@ -1,9 +1,10 @@
-//! This module contains the stateful PriorityFiber and all methods to diff VNodes, their properties, and their children.
+//! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children.
 //!
-//! The [`PriorityFiber`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
+//! The [`DiffMachine`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
 //! of mutations for the RealDom to apply.
 //!
 //! ## Notice:
+//!
 //! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
 //! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancelation, NodeRefs, and additional
 //! batching operations.
@@ -23,12 +24,13 @@
 //! and brick the user's page.
 //!
 //! ### Fragment Support
-//!
+//! --------------------
 //! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments
-//! can be particularly challenging when they are empty, so the placeholder node lets us "reserve" a spot for the empty
+//! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty
 //! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the NodeFactory - it is
-//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. This is
-//! slightly inefficient, but represents a such an uncommon use case that it is not worth optimizing.
+//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding
+//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to
+//!  the platform.
 //!
 //! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is
 //! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children.
@@ -44,7 +46,7 @@
 //! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so its up to the reconciler to
 //! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time
 //! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as
-//! a way of short-circuiting the most expensive checks.
+//! a way of short-circuiting the most mem-cmp expensive checks.
 //!
 //! ## Bloom Filter and Heuristics
 //! ------------------------------
@@ -62,7 +64,7 @@
 //! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes
 //! for the client. As new nodes are created, old nodes will be over-written.
 //!
-//! Further Reading and Thoughts
+//! ## Further Reading and Thoughts
 //! ----------------------------
 //! There are more ways of increasing diff performance here that are currently not implemented.
 //! More info on how to improve this diffing algorithm:
@@ -81,13 +83,16 @@ use DomEdit::*;
 
 /// Our DiffMachine is an iterative tree differ.
 ///
-/// It uses techniques of a register-based Turing Machines to allow pausing and restarting of the diff algorithm. This
+/// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This
 /// was origially implemented using recursive techniques, but Rust lacks the abilty to call async functions recursively,
-/// meaning we could not "pause" the diffing algorithm.
+/// meaning we could not "pause" the original diffing algorithm.
 ///
 /// Instead, we use a traditional stack machine approach to diff and create new nodes. The diff algorithm periodically
 /// calls "yield_now" which allows the machine to pause and return control to the caller. The caller can then wait for
-/// the next period of idle time, preventing our diff algorithm from blocking the maint thread.
+/// the next period of idle time, preventing our diff algorithm from blocking the main thread.
+///
+/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
+/// stack machines all the way down!
 pub struct DiffMachine<'bump> {
     vdom: &'bump SharedResources,
 
@@ -112,7 +117,6 @@ pub enum DiffInstruction<'a> {
     DiffNode {
         old: &'a VNode<'a>,
         new: &'a VNode<'a>,
-        progress: usize,
     },
 
     Append {},
@@ -143,22 +147,6 @@ impl<'bump> DiffMachine<'bump> {
         }
     }
 
-    /// Allows the creation of a diff machine without the concept of scopes or a virtualdom
-    /// this is mostly useful for testing
-    ///
-    /// This will PANIC if any component elements are passed in.
-    pub fn new_headless(shared: &'bump SharedResources) -> Self {
-        todo!()
-        // Self {
-        //     node_stack: smallvec![],
-        //     mutations: Mutations::new(),
-        //     scope_stack: smallvec![ScopeId(0)],
-        //     vdom: shared,
-        //     diffed: FxHashSet::default(),
-        //     seen_scopes: FxHashSet::default(),
-        // }
-    }
-
     //
     pub async fn diff_scope(&mut self, id: ScopeId) -> Result<()> {
         let component = self.get_scope_mut(&id).ok_or_else(|| Error::NotMounted)?;
@@ -173,15 +161,14 @@ impl<'bump> DiffMachine<'bump> {
     ///
     /// We do depth-first to maintain high cache locality (nodes were originally generated recursively) and because we
     /// only need a stack (not a queue) of lists
-    ///
-    ///
-    ///
     pub async fn work(&mut self) -> Result<()> {
         // todo: don't move the reused instructions around
+        // defer to individual functions so the compiler produces better code
+        // large functions tend to be difficult for the compiler to work with
         while let Some(mut make) = self.node_stack.pop() {
             match &mut make {
-                DiffInstruction::DiffNode { old, new, progress } => {
-                    //
+                DiffInstruction::DiffNode { old, new, .. } => {
+                    self.diff_node(old, new);
                 }
 
                 DiffInstruction::Append {} => {
@@ -195,18 +182,7 @@ impl<'bump> DiffMachine<'bump> {
                 }
 
                 DiffInstruction::Create { node, .. } => {
-                    // defer to individual functions so the compiler produces better code
-                    // large functions tend to be difficult for the compiler to work with
-                    match &node {
-                        VNode::Text(vtext) => self.create_text_node(vtext),
-                        VNode::Suspended(suspended) => self.create_suspended_node(suspended),
-                        VNode::Anchor(anchor) => self.create_anchor_node(anchor),
-                        VNode::Element(element) => self.create_element_node(element),
-                        VNode::Fragment(frag) => self.create_fragment_node(frag),
-                        VNode::Component(_) => {
-                            //
-                        }
-                    }
+                    self.create_node(node);
                 }
             }
         }
@@ -214,6 +190,21 @@ impl<'bump> DiffMachine<'bump> {
         Ok(())
     }
 
+    // =================================
+    //  Tools for creating new nodes
+    // =================================
+
+    fn create_node(&mut self, node: &'bump VNode<'bump>) {
+        match node {
+            VNode::Text(vtext) => self.create_text_node(vtext),
+            VNode::Suspended(suspended) => self.create_suspended_node(suspended),
+            VNode::Anchor(anchor) => self.create_anchor_node(anchor),
+            VNode::Element(element) => self.create_element_node(element),
+            VNode::Fragment(frag) => self.create_fragment_node(frag),
+            VNode::Component(component) => self.create_component_node(component),
+        }
+    }
+
     fn create_text_node(&mut self, vtext: &'bump VText<'bump>) {
         let real_id = self.vdom.reserve_node();
         self.edit_create_text_node(vtext.text, real_id);
@@ -338,459 +329,222 @@ impl<'bump> DiffMachine<'bump> {
         // Push the new scope onto the stack
         self.scope_stack.push(new_idx);
 
-        // Run the creation algorithm with this scope on the stack
-        let meta = self.create_vnode(nextnode);
+        todo!();
+        // // Run the creation algorithm with this scope on the stack
+        // let meta = self.create_vnode(nextnode);
 
-        // pop the scope off the stack
-        self.scope_stack.pop();
+        // // pop the scope off the stack
+        // self.scope_stack.pop();
 
-        if meta.added_to_stack == 0 {
-            panic!("Components should *always* generate nodes - even if they fail");
-        }
+        // if meta.added_to_stack == 0 {
+        //     panic!("Components should *always* generate nodes - even if they fail");
+        // }
 
-        // Finally, insert this scope as a seen node.
-        self.seen_scopes.insert(new_idx);
+        // // Finally, insert this scope as a seen node.
+        // self.seen_scopes.insert(new_idx);
     }
 
-    pub fn diff_iterative(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
-        match (&old_node, &new_node) {
-            // Handle the "sane" cases first.
-            // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
-            // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
-            (VNode::Text(old), VNode::Text(new)) => {
-                let root = old_node.direct_id();
-
-                if old.text != new.text {
-                    self.edit_push_root(root);
-                    self.edit_set_text(new.text);
-                    self.edit_pop();
-                }
-
-                new.dom_id.set(Some(root));
-            }
+    // =================================
+    //  Tools for diffing nodes
+    // =================================
 
-            (VNode::Element(old), VNode::Element(new)) => {
-                let root = old_node.direct_id();
-
-                // If the element type is completely different, the element needs to be re-rendered completely
-                // This is an optimization React makes due to how users structure their code
-                //
-                // This case is rather rare (typically only in non-keyed lists)
-                if new.tag_name != old.tag_name || new.namespace != old.namespace {
-                    self.replace_node_with_node(root, old_node, new_node);
-                    return;
-                }
-
-                new.dom_id.set(Some(root));
-
-                // Don't push the root if we don't have to
-                let mut has_comitted = false;
-                let mut please_commit = |edits: &mut Vec<DomEdit>| {
-                    if !has_comitted {
-                        has_comitted = true;
-                        edits.push(PushRoot { id: root.as_u64() });
-                    }
-                };
-
-                // Diff Attributes
-                //
-                // It's extraordinarily rare to have the number/order of attributes change
-                // In these cases, we just completely erase the old set and make a new set
-                //
-                // TODO: take a more efficient path than this
-                if old.attributes.len() == new.attributes.len() {
-                    for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
-                        if old_attr.value != new_attr.value {
-                            please_commit(&mut self.mutations.edits);
-                            self.edit_set_attribute(new_attr);
-                        }
-                    }
-                } else {
-                    // TODO: provide some sort of report on how "good" the diffing was
-                    please_commit(&mut self.mutations.edits);
-                    for attribute in old.attributes {
-                        self.edit_remove_attribute(attribute);
-                    }
-                    for attribute in new.attributes {
-                        self.edit_set_attribute(attribute)
-                    }
-                }
-
-                // Diff listeners
-                //
-                // It's extraordinarily rare to have the number/order of listeners change
-                // In the cases where the listeners change, we completely wipe the data attributes and add new ones
-                //
-                // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
-                //
-                // TODO: take a more efficient path than this
-                let cur_scope: ScopeId = self.scope_stack.last().unwrap().clone();
-                if old.listeners.len() == new.listeners.len() {
-                    for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
-                        if old_l.event != new_l.event {
-                            please_commit(&mut self.mutations.edits);
-                            self.edit_remove_event_listener(old_l.event);
-                            self.edit_new_event_listener(new_l, cur_scope);
-                        }
-                        new_l.mounted_node.set(old_l.mounted_node.get());
-                        self.fix_listener(new_l);
-                    }
-                } else {
-                    please_commit(&mut self.mutations.edits);
-                    for listener in old.listeners {
-                        self.edit_remove_event_listener(listener.event);
-                    }
-                    for listener in new.listeners {
-                        listener.mounted_node.set(Some(root));
-                        self.edit_new_event_listener(listener, cur_scope);
-
-                        // Make sure the listener gets attached to the scope list
-                        self.fix_listener(listener);
-                    }
-                }
-
-                if has_comitted {
-                    self.edit_pop();
-                }
-
-                self.diff_children(old.children, new.children);
-            }
-
-            (VNode::Component(old), VNode::Component(new)) => {
-                let scope_addr = old.ass_scope.get().unwrap();
-
-                // Make sure we're dealing with the same component (by function pointer)
-                if old.user_fc == new.user_fc {
-                    //
-                    self.scope_stack.push(scope_addr);
-
-                    // Make sure the new component vnode is referencing the right scope id
-                    new.ass_scope.set(Some(scope_addr));
-
-                    // make sure the component's caller function is up to date
-                    let scope = self.get_scope_mut(&scope_addr).unwrap();
-
-                    scope
-                        .update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children));
-
-                    // React doesn't automatically memoize, but we do.
-                    let compare = old.comparator.unwrap();
-
-                    match compare(new) {
-                        true => {
-                            // the props are the same...
-                        }
-                        false => {
-                            // the props are different...
-                            scope.run_scope().unwrap();
-                            self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
-                        }
-                    }
-
-                    self.scope_stack.pop();
-
-                    self.seen_scopes.insert(scope_addr);
-                } else {
-                    let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
-                    let first = old_iter
-                        .next()
-                        .expect("Components should generate a placeholder root");
-
-                    // remove any leftovers
-                    for to_remove in old_iter {
-                        self.edit_push_root(to_remove.direct_id());
-                        self.edit_remove();
-                    }
-
-                    // seems like we could combine this into a single instruction....
-                    self.replace_node_with_node(first.direct_id(), old_node, new_node);
-
-                    // Wipe the old one and plant the new one
-                    let old_scope = old.ass_scope.get().unwrap();
-                    self.destroy_scopes(old_scope);
-                }
-            }
-
-            (VNode::Fragment(old), VNode::Fragment(new)) => {
-                // This is the case where options or direct vnodes might be used.
-                // In this case, it's faster to just skip ahead to their diff
-                if old.children.len() == 1 && new.children.len() == 1 {
-                    self.diff_node(&old.children[0], &new.children[0]);
-                    return;
-                }
-
-                self.diff_children(old.children, new.children);
-            }
-
-            (VNode::Anchor(old), VNode::Anchor(new)) => {
-                new.dom_id.set(old.dom_id.get());
-            }
+    pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
+        use VNode::*;
+        match (old_node, new_node) {
+            // Check the most common cases first
+            (Text(old), Text(new)) => self.diff_text_nodes(old, new),
+            (Element(old), Element(new)) => self.diff_element_nodes(old, new),
+            (Component(old), Component(new)) => self.diff_component_nodes(old, new),
+            (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
+            (Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()),
 
-            // The strategy here is to pick the first possible node from the previous set and use that as our replace with root
-            //
-            // We also walk the "real node" list to make sure all latent roots are claened up
-            // This covers the case any time a fragment or component shows up with pretty much anything else
-            //
-            // This likely isn't the fastest way to go about replacing one node with a virtual node, but the "insane" cases
-            // are pretty rare.  IE replacing a list (component or fragment) with a single node.
             (
-                VNode::Component(_)
-                | VNode::Fragment(_)
-                | VNode::Text(_)
-                | VNode::Element(_)
-                | VNode::Anchor(_),
-                VNode::Component(_)
-                | VNode::Fragment(_)
-                | VNode::Text(_)
-                | VNode::Element(_)
-                | VNode::Anchor(_),
+                Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_),
+                Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_),
             ) => {
                 self.replace_and_create_many_with_many([old_node], [new_node]);
             }
 
-            // TODO
-            (VNode::Suspended(old), new) => {
-                //
+            // TODO: these don't properly clean up any data
+            (Suspended(old), new) => {
                 self.replace_and_create_many_with_many([old_node], [new_node]);
             }
+
             // a node that was once real is now suspended
-            (old, VNode::Suspended(_)) => {
-                //
+            (old, Suspended(_)) => {
                 self.replace_and_create_many_with_many([old_node], [new_node]);
             }
         }
     }
 
-    // Diff the `old` node with the `new` node. Emits instructions to modify a
-    // physical DOM node that reflects `old` into something that reflects `new`.
-    //
-    // the real stack should be what it is coming in and out of this function (ideally empty)
-    //
-    // each function call assumes the stack is fresh (empty).
-    pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
-        match (&old_node, &new_node) {
-            // Handle the "sane" cases first.
-            // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
-            // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
-            (VNode::Text(old), VNode::Text(new)) => {
-                let root = old_node.direct_id();
-
-                if old.text != new.text {
-                    self.edit_push_root(root);
-                    self.edit_set_text(new.text);
-                    self.edit_pop();
-                }
+    fn diff_text_nodes(&mut self, old: &'bump VText<'bump>, new: &'bump VText<'bump>) {
+        let root = old.dom_id.get().unwrap();
 
-                new.dom_id.set(Some(root));
-            }
+        if old.text != new.text {
+            self.edit_push_root(root);
+            self.edit_set_text(new.text);
+            self.edit_pop();
+        }
 
-            (VNode::Element(old), VNode::Element(new)) => {
-                let root = old_node.direct_id();
+        new.dom_id.set(Some(root));
+    }
 
-                // If the element type is completely different, the element needs to be re-rendered completely
-                // This is an optimization React makes due to how users structure their code
-                //
-                // This case is rather rare (typically only in non-keyed lists)
-                if new.tag_name != old.tag_name || new.namespace != old.namespace {
-                    self.replace_node_with_node(root, old_node, new_node);
-                    return;
-                }
+    fn diff_element_nodes(&mut self, old: &'bump VElement<'bump>, new: &'bump VElement<'bump>) {
+        let root = old.dom_id.get().unwrap();
 
-                new.dom_id.set(Some(root));
+        // If the element type is completely different, the element needs to be re-rendered completely
+        // This is an optimization React makes due to how users structure their code
+        //
+        // This case is rather rare (typically only in non-keyed lists)
+        if new.tag_name != old.tag_name || new.namespace != old.namespace {
+            self.replace_node_with_node(root, old_node, new_node);
+            return;
+        }
 
-                // Don't push the root if we don't have to
-                let mut has_comitted = false;
-                let mut please_commit = |edits: &mut Vec<DomEdit>| {
-                    if !has_comitted {
-                        has_comitted = true;
-                        edits.push(PushRoot { id: root.as_u64() });
-                    }
-                };
+        new.dom_id.set(Some(root));
 
-                // Diff Attributes
-                //
-                // It's extraordinarily rare to have the number/order of attributes change
-                // In these cases, we just completely erase the old set and make a new set
-                //
-                // TODO: take a more efficient path than this
-                if old.attributes.len() == new.attributes.len() {
-                    for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
-                        if old_attr.value != new_attr.value {
-                            please_commit(&mut self.mutations.edits);
-                            self.edit_set_attribute(new_attr);
-                        }
-                    }
-                } else {
-                    // TODO: provide some sort of report on how "good" the diffing was
-                    please_commit(&mut self.mutations.edits);
-                    for attribute in old.attributes {
-                        self.edit_remove_attribute(attribute);
-                    }
-                    for attribute in new.attributes {
-                        self.edit_set_attribute(attribute)
-                    }
-                }
+        // Don't push the root if we don't have to
+        let mut has_comitted = false;
+        let mut please_commit = |edits: &mut Vec<DomEdit>| {
+            if !has_comitted {
+                has_comitted = true;
+                edits.push(PushRoot { id: root.as_u64() });
+            }
+        };
 
-                // Diff listeners
-                //
-                // It's extraordinarily rare to have the number/order of listeners change
-                // In the cases where the listeners change, we completely wipe the data attributes and add new ones
-                //
-                // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
-                //
-                // TODO: take a more efficient path than this
-                let cur_scope: ScopeId = self.scope_stack.last().unwrap().clone();
-                if old.listeners.len() == new.listeners.len() {
-                    for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
-                        if old_l.event != new_l.event {
-                            please_commit(&mut self.mutations.edits);
-                            self.edit_remove_event_listener(old_l.event);
-                            self.edit_new_event_listener(new_l, cur_scope);
-                        }
-                        new_l.mounted_node.set(old_l.mounted_node.get());
-                        self.fix_listener(new_l);
-                    }
-                } else {
+        // Diff Attributes
+        //
+        // It's extraordinarily rare to have the number/order of attributes change
+        // In these cases, we just completely erase the old set and make a new set
+        //
+        // TODO: take a more efficient path than this
+        if old.attributes.len() == new.attributes.len() {
+            for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
+                if old_attr.value != new_attr.value {
                     please_commit(&mut self.mutations.edits);
-                    for listener in old.listeners {
-                        self.edit_remove_event_listener(listener.event);
-                    }
-                    for listener in new.listeners {
-                        listener.mounted_node.set(Some(root));
-                        self.edit_new_event_listener(listener, cur_scope);
-
-                        // Make sure the listener gets attached to the scope list
-                        self.fix_listener(listener);
-                    }
+                    self.edit_set_attribute(new_attr);
                 }
+            }
+        } else {
+            // TODO: provide some sort of report on how "good" the diffing was
+            please_commit(&mut self.mutations.edits);
+            for attribute in old.attributes {
+                self.edit_remove_attribute(attribute);
+            }
+            for attribute in new.attributes {
+                self.edit_set_attribute(attribute)
+            }
+        }
 
-                if has_comitted {
-                    self.edit_pop();
+        // Diff listeners
+        //
+        // It's extraordinarily rare to have the number/order of listeners change
+        // In the cases where the listeners change, we completely wipe the data attributes and add new ones
+        //
+        // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
+        //
+        // TODO: take a more efficient path than this
+        let cur_scope: ScopeId = self.scope_stack.last().unwrap().clone();
+        if old.listeners.len() == new.listeners.len() {
+            for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
+                if old_l.event != new_l.event {
+                    please_commit(&mut self.mutations.edits);
+                    self.edit_remove_event_listener(old_l.event);
+                    self.edit_new_event_listener(new_l, cur_scope);
                 }
-
-                self.diff_children(old.children, new.children);
+                new_l.mounted_node.set(old_l.mounted_node.get());
+                self.fix_listener(new_l);
             }
+        } else {
+            please_commit(&mut self.mutations.edits);
+            for listener in old.listeners {
+                self.edit_remove_event_listener(listener.event);
+            }
+            for listener in new.listeners {
+                listener.mounted_node.set(Some(root));
+                self.edit_new_event_listener(listener, cur_scope);
 
-            (VNode::Component(old), VNode::Component(new)) => {
-                let scope_addr = old.ass_scope.get().unwrap();
-
-                // Make sure we're dealing with the same component (by function pointer)
-                if old.user_fc == new.user_fc {
-                    //
-                    self.scope_stack.push(scope_addr);
+                // Make sure the listener gets attached to the scope list
+                self.fix_listener(listener);
+            }
+        }
 
-                    // Make sure the new component vnode is referencing the right scope id
-                    new.ass_scope.set(Some(scope_addr));
+        if has_comitted {
+            self.edit_pop();
+        }
 
-                    // make sure the component's caller function is up to date
-                    let scope = self.get_scope_mut(&scope_addr).unwrap();
+        self.diff_children(old.children, new.children);
+    }
 
-                    scope
-                        .update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children));
+    fn diff_component_nodes(
+        &mut self,
+        old: &'bump VComponent<'bump>,
+        new: &'bump VComponent<'bump>,
+    ) {
+        let scope_addr = old.ass_scope.get().unwrap();
 
-                    // React doesn't automatically memoize, but we do.
-                    let compare = old.comparator.unwrap();
+        // Make sure we're dealing with the same component (by function pointer)
+        if old.user_fc == new.user_fc {
+            //
+            self.scope_stack.push(scope_addr);
 
-                    match compare(new) {
-                        true => {
-                            // the props are the same...
-                        }
-                        false => {
-                            // the props are different...
-                            scope.run_scope().unwrap();
-                            self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
-                        }
-                    }
+            // Make sure the new component vnode is referencing the right scope id
+            new.ass_scope.set(Some(scope_addr));
 
-                    self.scope_stack.pop();
+            // make sure the component's caller function is up to date
+            let scope = self.get_scope_mut(&scope_addr).unwrap();
 
-                    self.seen_scopes.insert(scope_addr);
-                } else {
-                    let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
-                    let first = old_iter
-                        .next()
-                        .expect("Components should generate a placeholder root");
-
-                    // remove any leftovers
-                    for to_remove in old_iter {
-                        self.edit_push_root(to_remove.direct_id());
-                        self.edit_remove();
-                    }
+            scope.update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children));
 
-                    // seems like we could combine this into a single instruction....
-                    self.replace_node_with_node(first.direct_id(), old_node, new_node);
+            // React doesn't automatically memoize, but we do.
+            let compare = old.comparator.unwrap();
 
-                    // Wipe the old one and plant the new one
-                    let old_scope = old.ass_scope.get().unwrap();
-                    self.destroy_scopes(old_scope);
+            match compare(new) {
+                true => {
+                    // the props are the same...
+                }
+                false => {
+                    // the props are different...
+                    scope.run_scope().unwrap();
+                    self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
                 }
             }
 
-            (VNode::Fragment(old), VNode::Fragment(new)) => {
-                // This is the case where options or direct vnodes might be used.
-                // In this case, it's faster to just skip ahead to their diff
-                if old.children.len() == 1 && new.children.len() == 1 {
-                    self.diff_node(&old.children[0], &new.children[0]);
-                    return;
-                }
+            self.scope_stack.pop();
 
-                self.diff_children(old.children, new.children);
-            }
+            self.seen_scopes.insert(scope_addr);
+        } else {
+            todo!();
 
-            (VNode::Anchor(old), VNode::Anchor(new)) => {
-                new.dom_id.set(old.dom_id.get());
-            }
+            // let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
+            // let first = old_iter
+            //     .next()
+            //     .expect("Components should generate a placeholder root");
 
-            // The strategy here is to pick the first possible node from the previous set and use that as our replace with root
-            //
-            // We also walk the "real node" list to make sure all latent roots are claened up
-            // This covers the case any time a fragment or component shows up with pretty much anything else
-            //
-            // This likely isn't the fastest way to go about replacing one node with a virtual node, but the "insane" cases
-            // are pretty rare.  IE replacing a list (component or fragment) with a single node.
-            (
-                VNode::Component(_)
-                | VNode::Fragment(_)
-                | VNode::Text(_)
-                | VNode::Element(_)
-                | VNode::Anchor(_),
-                VNode::Component(_)
-                | VNode::Fragment(_)
-                | VNode::Text(_)
-                | VNode::Element(_)
-                | VNode::Anchor(_),
-            ) => {
-                self.replace_and_create_many_with_many([old_node], [new_node]);
-            }
+            // // remove any leftovers
+            // for to_remove in old_iter {
+            //     self.edit_push_root(to_remove.direct_id());
+            //     self.edit_remove();
+            // }
 
-            // TODO
-            (VNode::Suspended(old), new) => {
-                //
-                self.replace_and_create_many_with_many([old_node], [new_node]);
-            }
-            // a node that was once real is now suspended
-            (old, VNode::Suspended(_)) => {
-                //
-                self.replace_and_create_many_with_many([old_node], [new_node]);
-            }
+            // // seems like we could combine this into a single instruction....
+            // self.replace_node_with_node(first.direct_id(), old_node, new_node);
+
+            // // Wipe the old one and plant the new one
+            // let old_scope = old.ass_scope.get().unwrap();
+            // self.destroy_scopes(old_scope);
         }
     }
 
-    fn create_children(&mut self, children: &'bump [VNode<'bump>]) -> CreateMeta {
-        let mut is_static = true;
-        let mut added_to_stack = 0;
-
-        // add them backwards
-        for child in children.iter().rev() {
-            let child_meta = self.create_vnode(child);
-            is_static = is_static && child_meta.is_static;
-            added_to_stack += child_meta.added_to_stack;
+    fn diff_fragment_nodes(&mut self, old: &'bump VFragment<'bump>, new: &'bump VFragment<'bump>) {
+        // This is the case where options or direct vnodes might be used.
+        // In this case, it's faster to just skip ahead to their diff
+        if old.children.len() == 1 && new.children.len() == 1 {
+            self.diff_node(&old.children[0], &new.children[0]);
+            return;
         }
 
-        CreateMeta {
-            is_static,
-            added_to_stack,
-        }
+        self.diff_children(old.children, new.children);
     }
 
     /// Destroy a scope and all of its descendents.

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

@@ -159,17 +159,19 @@ impl VirtualDom {
             .get_scope_mut(&self.base_scope)
             .expect("The base scope should never be moved");
 
-        // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
-        if cur_component.run_scope().is_ok() {
-            let meta = diff_machine.create_vnode(cur_component.frames.fin_head());
-            diff_machine.edit_append_children(meta.added_to_stack);
-        } else {
-            // todo: should this be a hard error?
-            log::warn!(
-                "Component failed to run succesfully during rebuild.
-                This does not result in a failed rebuild, but indicates a logic failure within your app."
-            );
-        }
+        todo!();
+
+        // // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
+        // if cur_component.run_scope().is_ok() {
+        //     let meta = diff_machine.create_vnode(cur_component.frames.fin_head());
+        //     diff_machine.edit_append_children(meta.added_to_stack);
+        // } else {
+        //     // todo: should this be a hard error?
+        //     log::warn!(
+        //         "Component failed to run succesfully during rebuild.
+        //         This does not result in a failed rebuild, but indicates a logic failure within your app."
+        //     );
+        // }
 
         Ok(diff_machine.mutations)
     }

+ 4 - 0
packages/core/src/yield_now.rs

@@ -1,3 +1,7 @@
+//!  Utility Code taken from async_std to immediately yield the current task to the executor.
+//!
+//!
+
 use std::future::Future;
 use std::pin::Pin;
 use std::task::{Context, Poll};

+ 10 - 0
packages/core/tests/iterative.rs

@@ -45,3 +45,13 @@ async fn test_iterative_diff() {
     let mut machine = DiffMachine::new_headless(&shared);
     let a = machine.work().await.unwrap();
 }
+
+#[async_std::test]
+async fn websys_loop() {
+    ///loop {
+    ///    let deadline = request_idle_callback().await;
+    ///    let edits = dom.work(deadline);
+    ///    request_animation_frame().await;
+    ///    apply(edits);
+    ///}
+}