소스 검색

Feat: wire up a very basic dom updater

Jonathan Kelley 4 년 전
부모
커밋
c4e8d8bb31

+ 1 - 1
.vscode/settings.json

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

+ 2 - 0
.vscode/spellright.dict

@@ -23,3 +23,5 @@ deref
 derefs
 Derefing
 rei
+RefMut
+diffed

+ 1 - 0
LICENSE

@@ -0,0 +1 @@
+MIT/Apache-2 

+ 3 - 0
notes/ARCHITECTURE.md

@@ -0,0 +1,3 @@
+# Dioxus Architecture
+
+:) 

+ 0 - 0
CHANGELOG.md → notes/CHANGELOG.md


+ 0 - 0
notes/CONTRIBUTING.md


+ 27 - 2
SOLVEDPROBLEMS.md → notes/SOLVEDPROBLEMS.md

@@ -244,16 +244,41 @@ A few notes:
 
 ## Concurrency
 
-I don't even know yet
-
+For Dioxus, concurrency is built directly into the VirtualDOM lifecycle and event system. Suspended components prove "no changes" while diffing, and will cause a lifecycle update later. This is considered a "trigger" and will cause targeted diffs and re-renders. Renderers will need to await the Dioxus suspense queue if they want to process these updates. This will typically involve joining the suspense queue and event listeners together like:
+```rust
+// wait for an even either from the suspense queue or our own custom listener system
+let (left, right) = join!(vdom.suspense_queue, self.custom_event_listener);
+```
+LiveView is built on this model, and updates from the WebSocket connection to the host server are treated as external updates. This means any renderer can feed targeted EditLists (the underlying message of this event) directly into the VirtualDOM.
 
 ## Execution Model
+<!-- todo -->
 
+## Diffing
 
+Diffing is an interesting story. Since we don't re-render the entire DOM, we need a way to patch up the DOM without visiting every component. To get this working, we need to think in cycles, queues, and stacks. Most of the original logic is pulled from Dodrio as Dioxus and Dodrio share much of the same DNA. 
 
+When an event is triggered, we find the callback that installed the listener and run it. We then record all components affected by the running of the "subscription" primitive. In practice, many hooks will initiate a subscription, so it is likely that many components throughout the entire tree will need to be re-rendered. For each component, we attach its index and the type of update it needs. 
 
+In practice, removals trump prop updates which trump subscription updates. Therefore, we only process updates where props are directly changed first, as this will likely flow into child components. 
 
+Roughly, the flow looks like:
+- Process the initiating event
+- Mark components affected by the subscription API (the only way of causing forward updates)
+- Descend from the root into children, ignoring those not affected by the subscription API. (walking the tree until we hit the first affected component, or choosing the highest component)
+- Run this component and then immediately diff its output, marking any children that also need to be updated and putting them into the immediate queue
+- Mark this component as already-ran and remove it from the need_to_diff list, instead moving it into the "already diffed list"
+- Run the marked children until the immediate queue is empty
 
+```rust
+struct DiffMachine {
+    immediate_queue: Vec<Index>,
+    diffed: HashSet<Index>,
+    need_to_diff: HashSet<Index>
+    marked_for_removal: Vec<Index>
+}
+```
 
+On the actual diffing level, we're using the diffing algorithm pulled from Dodrio, but plan to move to a dedicated crate that implements Meyers/Patience for us. During the diffing phase, we track our current position using a "Traversal" which implements the "MoveTo". When "MoveTo" is combined with "Edit", it is possible for renderers to fully interpret a series of Moves and Edits together to update their internal node structures for rendering.
 
 

+ 2 - 2
old/virtual-dom-rs/src/patch/apply_patches.rs

@@ -1,9 +1,9 @@
+use crate::dom_updater::ActiveClosures;
 use crate::patch::Patch;
+
 use std::cmp::min;
 use std::collections::HashMap;
 use std::collections::HashSet;
-
-use crate::dom_updater::ActiveClosures;
 use wasm_bindgen::JsCast;
 use wasm_bindgen::JsValue;
 use web_sys::{Element, Node, Text};

+ 6 - 7
old/virtual-dom-rs/src/patch/mod.rs

@@ -30,13 +30,12 @@ pub use apply_patches::patch;
 ///        .─.         .─.
 ///       ( 1 )       ( 4 )
 ///        `┬'         `─'
-///    ┌────┴───┐       │
-///    │        │       ├─────┬─────┐
-///    ▼        ▼       │     │     │
-///   .─.      .─.      ▼     ▼     ▼
-///  ( 2 )    ( 3 )    .─.   .─.   .─.
-///   `─'      `─'    ( 5 ) ( 6 ) ( 7 )
-///                    `─'   `─'   `─'
+///    ┌────┴───┐       ├─────┬─────┐
+///    │        │       │     │     │
+///    ▼        ▼       ▼     ▼     ▼
+///   .─.      .─.     .─.   .─.   .─.
+///  ( 2 )    ( 3 )   ( 5 ) ( 6 ) ( 7 )
+///   `─'      `─'     `─'   `─'   `─'                  
 /// ```
 ///
 /// The patching process is tested in a real browser in crates/virtual-dom-rs/tests/diff_patch.rs

+ 1 - 0
packages/core/Cargo.toml

@@ -31,3 +31,4 @@ id-arena = "2.2.1"
 thiserror = "1.0.23"
 fxhash = "0.2.1"
 longest-increasing-subsequence = "0.1.0"
+# hashbrown = { version = "0.9.1", features = ["bumpalo"] }

+ 256 - 1049
packages/core/src/diff.rs

@@ -1,1120 +1,327 @@
-/// Diff the `old` node with the `new` node. Emits instructions to modify a
-/// physical DOM node that reflects `old` into something that reflects `new`.
-///
-/// Upon entry to this function, the physical DOM node must be on the top of the
-/// change list stack:
-///
-///     [... node]
-///
-/// The change list stack is in the same state when this function exits.
-///
-/// ----
-///
-/// There are more ways of increasing diff performance here that are currently not implemented.
-/// Additionally, the caching mechanism has also been tweaked.
-///
-/// Instead of having "cached" nodes, each component is, by default, a cached node. This leads to increased
-/// memory overhead for large numbers of small components, but we can optimize this by tracking alloc size over time
-/// and shrinking bumps down if possible.
-///
-/// Additionally, clean up of these components is not done at diff time (though it should), but rather, the diffing
-/// proprogates removal lifecycle events for affected components into the event queue. It's not imperative that these
-/// are ran immediately, but it should be noted that cleanup of components might be able to emit changes.
-///
-/// This diffing only ever occurs on a component-by-component basis (not entire trees at once).
-///
-/// Currently, the listener situation is a bit broken.
-/// We aren't removing listeners (choosing to leak them instead) :(
-/// Eventually, we'll set things up so add/remove listener is an instruction again
-///
-/// A major assumption of this diff algorithm when combined with the ChangeList is that the Changelist will be
-/// fresh and the event queue is clean. This lets us continue to batch edits together under the same ChangeList
-///
-/// More info on how to improve this diffing algorithm:
-///  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
-use fxhash::{FxHashMap, FxHashSet};
+//! A primitive diffing algorithm
+//!
+//!
+//!
+//!
+//!
+
+use std::{collections::HashMap, mem};
+
+use crate::innerlude::*;
+use crate::patch::Patch;
+use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
 use generational_arena::Index;
 
-use crate::{
-    changelist::EditList,
-    innerlude::{Attribute, Listener, Scope, VElement, VNode, VText},
-    virtual_dom::LifecycleEvent,
-};
-
-use std::cmp::Ordering;
-
-/// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while
-/// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event.
-///
-/// By re-entering via NodeDiff, we can connect disparate edits together into a single EditList. This batching of edits
-/// leads to very fast re-renders (all done in a single animation frame).
-///
-/// It also means diffing two trees is only ever complex as diffing a single smaller tree, and then re-entering at a
-/// different cursor position.
-///
-/// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components
-/// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via
-/// subscriptions and props changes.
-struct DiffingMachine<'a> {
-    change_list: &'a mut EditList<'a>,
-    queue: FxHashMap<Index, NeedToDiff>,
-}
-
-enum NeedToDiff {
-    PropsChanged,
-    Subscription,
+pub struct DiffMachine {
+    immediate_queue: Vec<Index>,
+    diffed: FxHashSet<Index>,
+    need_to_diff: FxHashSet<Index>,
+    marked_for_removal: Vec<Index>,
 }
 
-impl<'a> DiffingMachine<'a> {
-    fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
-        /*
-        For each valid case, we "commit traversal", meaning we save this current position in the tree.
-        Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
-        When re-entering, we reuse the EditList in DiffState
-        */
-        match (old, new) {
-            // This case occurs when two text nodes are generation
-            (VNode::Text(VText { text: old_text }), VNode::Text(VText { text: new_text })) => {
-                if old_text != new_text {
-                    self.change_list.commit_traversal();
-                    self.change_list.set_text(new_text);
-                }
-            }
-
-            // Definitely different, need to commit update
-            (VNode::Text(_), VNode::Element(_)) => {
-                // TODO: Hook up the events properly
-                // todo!("Hook up events registry");
-                self.change_list.commit_traversal();
-                // diff_support::create(cached_set, self.change_list, registry, new, cached_roots);
-                self.create(new);
-                // registry.remove_subtree(&old);
-                self.change_list.replace_with();
-            }
-
-            // Definitely different, need to commit update
-            (VNode::Element(_), VNode::Text(_)) => {
-                self.change_list.commit_traversal();
-                self.create(new);
-
-                // create(cached_set, self.change_list, registry, new, cached_roots);
-                // Note: text nodes cannot have event listeners, so we don't need to
-                // remove the old node's listeners from our registry her.
-                self.change_list.replace_with();
-            }
-            // compare elements
-            // if different, schedule different types of update
-            (VNode::Element(eold), VNode::Element(enew)) => {
-                // If the element type is completely different, the element needs to be re-rendered completely
-                if enew.tag_name != eold.tag_name || enew.namespace != eold.namespace {
-                    self.change_list.commit_traversal();
-                    // create(cached_set, self.change_list, registry, new, cached_roots);
-                    // registry.remove_subtree(&old);
-                    self.change_list.replace_with();
-                    return;
-                }
-
-                self.diff_listeners(eold.listeners, enew.listeners);
-
-                self.diff_attr(eold.attributes, enew.attributes, enew.namespace.is_some());
-
-                self.diff_children(eold.children, enew.children);
-            }
-            // No immediate change to dom. If props changed though, queue a "props changed" update
-            // However, mark these for a
-            (VNode::Component(_), VNode::Component(_)) => {
-                todo!("Usage of component VNode not currently supported");
-                //     // Both the new and old nodes are cached.
-                //     (&NodeKind::Cached(ref new), &NodeKind::Cached(ref old)) => {
-                //         cached_roots.insert(new.id);
-                //         if new.id == old.id {
-                //             // This is the same cached node, so nothing has changed!
-                //             return;
-                //         }
-                //         let (new, new_template) = cached_set.get(new.id);
-                //         let (old, old_template) = cached_set.get(old.id);
-                //         if new_template == old_template {
-                //             // If they are both using the same template, then just diff the
-                //             // subtrees.
-                //             diff(cached_set, change_list, registry, old, new, cached_roots);
-                //         } else {
-                //             // Otherwise, they are probably different enough that
-                //             // re-constructing the subtree from scratch should be faster.
-                //             // This doubly holds true if we have a new template.
-                //             change_list.commit_traversal();
-                //             create_and_replace(
-                //                 cached_set,
-                //                 change_list,
-                //                 registry,
-                //                 new_template,
-                //                 old,
-                //                 new,
-                //                 cached_roots,
-                //             );
-                //         }
-                //     }
-                // queue a lifecycle event.
-                // no change
-            }
-
-            // A component has been birthed!
-            // Queue its arrival
-            (_, VNode::Component(_)) => {
-                todo!("Usage of component VNode not currently supported");
-                //     // Old cached node and new non-cached node. Again, assume that they are
-                //     // probably pretty different and create the new non-cached node afresh.
-                //     (_, &NodeKind::Cached(_)) => {
-                //         change_list.commit_traversal();
-                //         create(cached_set, change_list, registry, new, cached_roots);
-                //         registry.remove_subtree(&old);
-                //         change_list.replace_with();
-                //     }
-                // }
-            }
-
-            // A component was removed :(
-            // Queue its removal
-            (VNode::Component(_), _) => {
-                //     // New cached node when the old node was not cached. In this scenario,
-                //     // we assume that they are pretty different, and it isn't worth diffing
-                //     // the subtrees, so we just create the new cached node afresh.
-                //     (&NodeKind::Cached(ref c), _) => {
-                //         change_list.commit_traversal();
-                //         cached_roots.insert(c.id);
-                //         let (new, new_template) = cached_set.get(c.id);
-                //         create_and_replace(
-                //             cached_set,
-                //             change_list,
-                //             registry,
-                //             new_template,
-                //             old,
-                //             new,
-                //             cached_roots,
-                //         );
-                //     }
-                todo!("Usage of component VNode not currently supported");
-            }
-
-            // A suspended component appeared!
-            // Don't do much, just wait
-            (VNode::Suspended, _) | (_, VNode::Suspended) => {
-                // (VNode::Element(_), VNode::Suspended) => {}
-                // (VNode::Text(_), VNode::Suspended) => {}
-                // (VNode::Component(_), VNode::Suspended) => {}
-                // (VNode::Suspended, VNode::Element(_)) => {}
-                // (VNode::Suspended, VNode::Text(_)) => {}
-                // (VNode::Suspended, VNode::Suspended) => {}
-                // (VNode::Suspended, VNode::Component(_)) => {}
-                todo!("Suspended components not currently available")
-            }
+impl DiffMachine {
+    pub fn new() -> Self {
+        Self {
+            immediate_queue: vec![],
+            diffed: FxHashSet::default(),
+            need_to_diff: FxHashSet::default(),
+            marked_for_removal: vec![],
         }
     }
 
-    // Diff event listeners between `old` and `new`.
-    //
-    // The listeners' node must be on top of the change list stack:
-    //
-    //     [... node]
-    //
-    // The change list stack is left unchanged.
-    fn diff_listeners(&mut self, old: &[Listener<'a>], new: &[Listener<'a>]) {
-        if !old.is_empty() || !new.is_empty() {
-            self.change_list.commit_traversal();
+    /// Given two VirtualNode's generate Patch's that would turn the old virtual node's
+    /// real DOM node equivalent into the new VirtualNode's real DOM node equivalent.
+    pub fn diff<'a>(&mut self, old: &'a VNode, new: &'a VNode) -> Vec<Patch<'a>> {
+        self.diff_recursive(&old, &new, &mut 0)
+    }
+
+    pub fn diff_recursive<'a, 'b>(
+        &mut self,
+        old: &'a VNode,
+        new: &'a VNode,
+        cur_node_idx: &'b mut usize,
+    ) -> Vec<Patch<'a>> {
+        let mut patches = vec![];
+        let mut replace = false;
+
+        // Different enum variants, replace!
+        if mem::discriminant(old) != mem::discriminant(new) {
+            replace = true;
         }
 
-        'outer1: for new_l in new {
-            unsafe {
-                // Safety relies on removing `new_l` from the registry manually at
-                // the end of its lifetime. This happens below in the `'outer2`
-                // loop, and elsewhere in diffing when removing old dom trees.
-                // registry.add(new_l);
+        if let (VNode::Element(old_element), VNode::Element(new_element)) = (old, new) {
+            // Replace if there are different element tags
+            if old_element.tag_name != new_element.tag_name {
+                // if old_element.tag != new_element.tag {
+                replace = true;
             }
 
-            for old_l in old {
-                if new_l.event == old_l.event {
-                    self.change_list.update_event_listener(new_l);
-                    continue 'outer1;
+            // Replace if two elements have different keys
+            // TODO: More robust key support. This is just an early stopgap to allow you to force replace
+            // an element... say if it's event changed. Just change the key name for now.
+            // In the future we want keys to be used to create a Patch::ReOrder to re-order siblings
+            // todo!
+            // if old_element.attributes.get("key").is_some()
+            //     && old_element.attrs.get("key") != new_element.attrs.get("key")
+            // {
+            //     replace = true;
+            // }
+        }
+
+        // Handle replacing of a node
+        if replace {
+            patches.push(Patch::Replace(*cur_node_idx, &new));
+            if let VNode::Element(old_element_node) = old {
+                for child in old_element_node.children.iter() {
+                    increment_node_idx_for_children(child, cur_node_idx);
                 }
             }
-
-            self.change_list.new_event_listener(new_l);
+            return patches;
         }
 
-        'outer2: for old_l in old {
-            // registry.remove(old_l);
-
-            for new_l in new {
-                if new_l.event == old_l.event {
-                    continue 'outer2;
+        // The following comparison can only contain identical variants, other
+        // cases have already been handled above by comparing variant
+        // discriminants.
+        match (old, new) {
+            // We're comparing two text nodes
+            (VNode::Text(old_text), VNode::Text(new_text)) => {
+                if old_text != new_text {
+                    patches.push(Patch::ChangeText(*cur_node_idx, &new_text));
                 }
             }
-            self.change_list.remove_event_listener(old_l.event);
-        }
-    }
 
-    // Diff a node's attributes.
-    //
-    // The attributes' node must be on top of the change list stack:
-    //
-    //     [... node]
-    //
-    // The change list stack is left unchanged.
-    fn diff_attr(
-        &mut self,
-        old: &'a [Attribute<'a>],
-        new: &'a [Attribute<'a>],
-        is_namespaced: bool,
-    ) {
-        // Do O(n^2) passes to add/update and remove attributes, since
-        // there are almost always very few attributes.
-        'outer: for new_attr in new {
-            if new_attr.is_volatile() {
-                self.change_list.commit_traversal();
-                self.change_list
-                    .set_attribute(new_attr.name, new_attr.value, is_namespaced);
-            } else {
-                for old_attr in old {
-                    if old_attr.name == new_attr.name {
-                        if old_attr.value != new_attr.value {
-                            self.change_list.commit_traversal();
-                            self.change_list.set_attribute(
-                                new_attr.name,
-                                new_attr.value,
-                                is_namespaced,
-                            );
+            // We're comparing two element nodes
+            (VNode::Element(old_element), VNode::Element(new_element)) => {
+                // let b: HashMap<&str, &str, FxBuildHasher>  = HashMap::new()
+                let old_attrs = old_element
+                    .attributes
+                    .iter()
+                    .map(|f| (f.name, f.value))
+                    .collect::<HashMap<&'static str, &str, FxBuildHasher>>();
+
+                let new_attrs = old_element
+                    .attributes
+                    .iter()
+                    .map(|f| (f.name, f.value))
+                    .collect::<HashMap<&'static str, &str, FxBuildHasher>>();
+
+                let mut add_attributes = FxHashMap::<&'static str, &str>::default();
+                // [("blah", "blah")]
+                // .into_iter()
+                // .map(|f| (f.0, f.1))
+                // .collect::<HashMap<&'static str, &str, FxBuildHasher>>();
+
+                // let mut add_attribute = HashMap::<&str, &str, FxBuildHasher>::new();
+                let mut remove_attributes: Vec<&str> = vec![];
+
+                // TODO: -> split out into func
+                for (new_attr_name, new_attr_val) in new_attrs.iter() {
+                    // for (new_attr_name, new_attr_val) in new_element.attrs.iter() {
+                    match old_attrs.get(new_attr_name) {
+                        // match old_element.attrs.get(new_attr_name) {
+                        Some(ref old_attr_val) => {
+                            if old_attr_val != &new_attr_val {
+                                add_attributes.insert(new_attr_name, new_attr_val);
+                            }
                         }
-                        continue 'outer;
-                    }
+                        None => {
+                            add_attributes.insert(new_attr_name, new_attr_val);
+                        }
+                    };
                 }
 
-                self.change_list.commit_traversal();
-                self.change_list
-                    .set_attribute(new_attr.name, new_attr.value, is_namespaced);
-            }
-        }
-
-        'outer2: for old_attr in old {
-            for new_attr in new {
-                if old_attr.name == new_attr.name {
-                    continue 'outer2;
+                // TODO: -> split out into func
+                for (old_attr_name, old_attr_val) in old_attrs.iter() {
+                    // for (old_attr_name, old_attr_val) in old_element.attrs.iter() {
+                    if add_attributes.get(&old_attr_name[..]).is_some() {
+                        continue;
+                    };
+
+                    match new_attrs.get(old_attr_name) {
+                        // match new_element.attrs.get(old_attr_name) {
+                        Some(ref new_attr_val) => {
+                            if new_attr_val != &old_attr_val {
+                                remove_attributes.push(old_attr_name);
+                            }
+                        }
+                        None => {
+                            remove_attributes.push(old_attr_name);
+                        }
+                    };
                 }
-            }
 
-            self.change_list.commit_traversal();
-            self.change_list.remove_attribute(old_attr.name);
-        }
-    }
+                if add_attributes.len() > 0 {
+                    patches.push(Patch::AddAttributes(*cur_node_idx, add_attributes));
+                }
+                if remove_attributes.len() > 0 {
+                    patches.push(Patch::RemoveAttributes(*cur_node_idx, remove_attributes));
+                }
 
-    // Diff the given set of old and new children.
-    //
-    // The parent must be on top of the change list stack when this function is
-    // entered:
-    //
-    //     [... parent]
-    //
-    // the change list stack is in the same state when this function returns.
-    fn diff_children(&mut self, old: &'a [VNode<'a>], new: &'a [VNode<'a>]) {
-        if new.is_empty() {
-            if !old.is_empty() {
-                self.change_list.commit_traversal();
-                self.remove_all_children(old);
-            }
-            return;
-        }
+                let old_child_count = old_element.children.len();
+                let new_child_count = new_element.children.len();
 
-        if new.len() == 1 {
-            match (old.first(), &new[0]) {
-                (
-                    Some(&VNode::Text(VText { text: old_text })),
-                    &VNode::Text(VText { text: new_text }),
-                ) if old_text == new_text => {
-                    // Don't take this fast path...
+                if new_child_count > old_child_count {
+                    let append_patch: Vec<&'a VNode> =
+                        new_element.children[old_child_count..].iter().collect();
+                    patches.push(Patch::AppendChildren(*cur_node_idx, append_patch))
                 }
 
-                (_, &VNode::Text(VText { text })) => {
-                    self.change_list.commit_traversal();
-                    self.change_list.set_text(text);
-                    // for o in old {
-                    //     registry.remove_subtree(o);
-                    // }
-                    return;
+                if new_child_count < old_child_count {
+                    patches.push(Patch::TruncateChildren(*cur_node_idx, new_child_count))
                 }
 
-                (_, _) => {}
+                let min_count = std::cmp::min(old_child_count, new_child_count);
+                for index in 0..min_count {
+                    *cur_node_idx = *cur_node_idx + 1;
+                    let old_child = &old_element.children[index];
+                    let new_child = &new_element.children[index];
+                    patches.append(&mut self.diff_recursive(&old_child, &new_child, cur_node_idx))
+                }
+                if new_child_count < old_child_count {
+                    for child in old_element.children[min_count..].iter() {
+                        increment_node_idx_for_children(child, cur_node_idx);
+                    }
+                }
             }
-        }
 
-        if old.is_empty() {
-            if !new.is_empty() {
-                self.change_list.commit_traversal();
-                self.create_and_append_children(new);
+            (VNode::Suspended, _)
+            | (_, VNode::Suspended)
+            | (VNode::Component(_), _)
+            | (_, VNode::Component(_)) => {
+                todo!("cant yet handle these two")
             }
-            return;
-        }
-
-        let new_is_keyed = new[0].key().is_some();
-        let old_is_keyed = old[0].key().is_some();
-
-        debug_assert!(
-            new.iter().all(|n| n.key().is_some() == new_is_keyed),
-            "all siblings must be keyed or all siblings must be non-keyed"
-        );
-        debug_assert!(
-            old.iter().all(|o| o.key().is_some() == old_is_keyed),
-            "all siblings must be keyed or all siblings must be non-keyed"
-        );
-
-        if new_is_keyed && old_is_keyed {
-            let t = self.change_list.next_temporary();
-            // diff_keyed_children(self.change_list, old, new);
-            // diff_keyed_children(self.change_list, old, new, cached_roots);
-            // diff_keyed_children(cached_set, self.change_list, registry, old, new, cached_roots);
-            self.change_list.set_next_temporary(t);
-        } else {
-            self.diff_non_keyed_children(old, new);
-            // diff_non_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
-        }
-    }
-
-    // Diffing "keyed" children.
-    //
-    // With keyed children, we care about whether we delete, move, or create nodes
-    // versus mutate existing nodes in place. Presumably there is some sort of CSS
-    // transition animation that makes the virtual DOM diffing algorithm
-    // observable. By specifying keys for nodes, we know which virtual DOM nodes
-    // must reuse (or not reuse) the same physical DOM nodes.
-    //
-    // This is loosely based on Inferno's keyed patching implementation. However, we
-    // have to modify the algorithm since we are compiling the diff down into change
-    // list instructions that will be executed later, rather than applying the
-    // changes to the DOM directly as we compare virtual DOMs.
-    //
-    // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
-    //
-    // When entering this function, the parent must be on top of the change list
-    // stack:
-    //
-    //     [... parent]
-    //
-    // Upon exiting, the change list stack is in the same state.
-    fn diff_keyed_children(&mut self, old: &[VNode<'a>], new: &[VNode<'a>]) {
-        // let DiffState { change_list, queue } = &*state;
 
-        if cfg!(debug_assertions) {
-            let mut keys = fxhash::FxHashSet::default();
-            let mut assert_unique_keys = |children: &[VNode]| {
-                keys.clear();
-                for child in children {
-                    let key = child.key();
-                    debug_assert!(
-                        key.is_some(),
-                        "if any sibling is keyed, all siblings must be keyed"
-                    );
-                    keys.insert(key);
-                }
-                debug_assert_eq!(
-                    children.len(),
-                    keys.len(),
-                    "keyed siblings must each have a unique key"
-                );
-            };
-            assert_unique_keys(old);
-            assert_unique_keys(new);
-        }
-
-        // First up, we diff all the nodes with the same key at the beginning of the
-        // children.
-        //
-        // `shared_prefix_count` is the count of how many nodes at the start of
-        // `new` and `old` share the same keys.
-        let shared_prefix_count = match self.diff_keyed_prefix(old, new) {
-            KeyedPrefixResult::Finished => return,
-            KeyedPrefixResult::MoreWorkToDo(count) => count,
-        };
-
-        match self.diff_keyed_prefix(old, new) {
-            KeyedPrefixResult::Finished => return,
-            KeyedPrefixResult::MoreWorkToDo(count) => count,
+            (VNode::Text(_), VNode::Element(_))
+            | (VirtualNode::Element(_), VirtualNode::Text(_)) => {
+                unreachable!("Unequal variant discriminants should already have been handled");
+            }
         };
 
-        // Next, we find out how many of the nodes at the end of the children have
-        // the same key. We do _not_ diff them yet, since we want to emit the change
-        // list instructions such that they can be applied in a single pass over the
-        // DOM. Instead, we just save this information for later.
-        //
-        // `shared_suffix_count` is the count of how many nodes at the end of `new`
-        // and `old` share the same keys.
-        let shared_suffix_count = old[shared_prefix_count..]
-            .iter()
-            .rev()
-            .zip(new[shared_prefix_count..].iter().rev())
-            .take_while(|&(old, new)| old.key() == new.key())
-            .count();
-
-        let old_shared_suffix_start = old.len() - shared_suffix_count;
-        let new_shared_suffix_start = new.len() - shared_suffix_count;
-
-        // Ok, we now hopefully have a smaller range of children in the middle
-        // within which to re-order nodes with the same keys, remove old nodes with
-        // now-unused keys, and create new nodes with fresh keys.
-        self.diff_keyed_middle(
-            &old[shared_prefix_count..old_shared_suffix_start],
-            &new[shared_prefix_count..new_shared_suffix_start],
-            shared_prefix_count,
-            shared_suffix_count,
-            old_shared_suffix_start,
-        );
-
-        // Finally, diff the nodes at the end of `old` and `new` that share keys.
-        let old_suffix = &old[old_shared_suffix_start..];
-        let new_suffix = &new[new_shared_suffix_start..];
-        debug_assert_eq!(old_suffix.len(), new_suffix.len());
-        if !old_suffix.is_empty() {
-            self.diff_keyed_suffix(old_suffix, new_suffix, new_shared_suffix_start)
-        }
+        //    new_root.create_element()
+        patches
     }
+}
 
-    // Diff the prefix of children in `new` and `old` that share the same keys in
-    // the same order.
-    //
-    // Upon entry of this function, the change list stack must be:
-    //
-    //     [... parent]
-    //
-    // Upon exit, the change list stack is the same.
-    fn diff_keyed_prefix(&mut self, old: &[VNode<'a>], new: &[VNode<'a>]) -> KeyedPrefixResult {
-        self.change_list.go_down();
-        let mut shared_prefix_count = 0;
-
-        for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
-            if old.key() != new.key() {
-                break;
-            }
-
-            self.change_list.go_to_sibling(i);
-
-            self.diff_node(old, new);
-
-            shared_prefix_count += 1;
-        }
-
-        // If that was all of the old children, then create and append the remaining
-        // new children and we're finished.
-        if shared_prefix_count == old.len() {
-            self.change_list.go_up();
-            self.change_list.commit_traversal();
-            self.create_and_append_children(&new[shared_prefix_count..]);
-            return KeyedPrefixResult::Finished;
-        }
-
-        // And if that was all of the new children, then remove all of the remaining
-        // old children and we're finished.
-        if shared_prefix_count == new.len() {
-            self.change_list.go_to_sibling(shared_prefix_count);
-            self.change_list.commit_traversal();
-            self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
-            return KeyedPrefixResult::Finished;
+fn increment_node_idx_for_children<'a, 'b>(old: &'a VirtualNode, cur_node_idx: &'b mut usize) {
+    *cur_node_idx += 1;
+    if let VirtualNode::Element(element_node) = old {
+        for child in element_node.children.iter() {
+            increment_node_idx_for_children(&child, cur_node_idx);
         }
-
-        self.change_list.go_up();
-        KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
     }
+}
 
-    // The most-general, expensive code path for keyed children diffing.
-    //
-    // We find the longest subsequence within `old` of children that are relatively
-    // ordered the same way in `new` (via finding a longest-increasing-subsequence
-    // of the old child's index within `new`). The children that are elements of
-    // this subsequence will remain in place, minimizing the number of DOM moves we
-    // will have to do.
-    //
-    // Upon entry to this function, the change list stack must be:
-    //
-    //     [... parent]
-    //
-    // Upon exit from this function, it will be restored to that same state.
-    fn diff_keyed_middle(
-        &mut self,
-        old: &[VNode<'a>],
-        mut new: &[VNode<'a>],
-        shared_prefix_count: usize,
-        shared_suffix_count: usize,
-        old_shared_suffix_start: usize,
-    ) {
-        // Should have already diffed the shared-key prefixes and suffixes.
-        debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
-        debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
-
-        // The algorithm below relies upon using `u32::MAX` as a sentinel
-        // value, so if we have that many new nodes, it won't work. This
-        // check is a bit academic (hence only enabled in debug), since
-        // wasm32 doesn't have enough address space to hold that many nodes
-        // in memory.
-        debug_assert!(new.len() < u32::MAX as usize);
-
-        // Map from each `old` node's key to its index within `old`.
-        let mut old_key_to_old_index = FxHashMap::default();
-        old_key_to_old_index.reserve(old.len());
-        old_key_to_old_index.extend(old.iter().enumerate().map(|(i, o)| (o.key(), i)));
-
-        // The set of shared keys between `new` and `old`.
-        let mut shared_keys = FxHashSet::default();
-        // Map from each index in `new` to the index of the node in `old` that
-        // has the same key.
-        let mut new_index_to_old_index = Vec::with_capacity(new.len());
-        new_index_to_old_index.extend(new.iter().map(|n| {
-            let key = n.key();
-            if let Some(&i) = old_key_to_old_index.get(&key) {
-                shared_keys.insert(key);
-                i
-            } else {
-                u32::MAX as usize
-            }
-        }));
-
-        // If none of the old keys are reused by the new children, then we
-        // remove all the remaining old children and create the new children
-        // afresh.
-        if shared_suffix_count == 0 && shared_keys.is_empty() {
-            if shared_prefix_count == 0 {
-                self.change_list.commit_traversal();
-                self.remove_all_children(old);
-            } else {
-                self.change_list.go_down_to_child(shared_prefix_count);
-                self.change_list.commit_traversal();
-                self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
-            }
-
-            self.create_and_append_children(new);
-
-            return;
-        }
+// #[cfg(test)]
+mod tests {
+    use bumpalo::Bump;
 
-        // Save each of the old children whose keys are reused in the new
-        // children.
-        let mut old_index_to_temp = vec![u32::MAX; old.len()];
-        let mut start = 0;
-        loop {
-            let end = (start..old.len())
-                .find(|&i| {
-                    let key = old[i].key();
-                    !shared_keys.contains(&key)
-                })
-                .unwrap_or(old.len());
+    use super::*;
 
-            if end - start > 0 {
-                self.change_list.commit_traversal();
-                let mut t = self.change_list.save_children_to_temporaries(
-                    shared_prefix_count + start,
-                    shared_prefix_count + end,
-                );
-                for i in start..end {
-                    old_index_to_temp[i] = t;
-                    t += 1;
-                }
-            }
+    fn test_diff(
+        tree1: impl Fn(&Bump) -> VNode<'_>,
+        tree2: impl Fn(&Bump) -> VNode<'_>,
+        expected_patches: Vec<Patch>,
+        description: &'static str,
+    ) {
+        let bump = Bump::new();
 
-            debug_assert!(end <= old.len());
-            if end == old.len() {
-                break;
-            } else {
-                start = end + 1;
-            }
-        }
+        let nodes1 = tree1(&bump);
+        let nodes2 = tree1(&bump);
 
-        // Remove any old children whose keys were not reused in the new
-        // children. Remove from the end first so that we don't mess up indices.
-        let mut removed_count = 0;
-        for (i, old_child) in old.iter().enumerate().rev() {
-            if !shared_keys.contains(&old_child.key()) {
-                // registry.remove_subtree(old_child);
-                // todo
-                self.change_list.commit_traversal();
-                self.change_list.remove_child(i + shared_prefix_count);
-                removed_count += 1;
-            }
-        }
+        let mut machine = DiffMachine::new();
 
-        // If there aren't any more new children, then we are done!
-        if new.is_empty() {
-            return;
-        }
+        let patches = machine.diff(&nodes1, &nodes2);
 
-        // The longest increasing subsequence within `new_index_to_old_index`. This
-        // is the longest sequence on DOM nodes in `old` that are relatively ordered
-        // correctly within `new`. We will leave these nodes in place in the DOM,
-        // and only move nodes that are not part of the LIS. This results in the
-        // maximum number of DOM nodes left in place, AKA the minimum number of DOM
-        // nodes moved.
-        let mut new_index_is_in_lis = FxHashSet::default();
-        new_index_is_in_lis.reserve(new_index_to_old_index.len());
-        let mut predecessors = vec![0; new_index_to_old_index.len()];
-        let mut starts = vec![0; new_index_to_old_index.len()];
-        longest_increasing_subsequence::lis_with(
-            &new_index_to_old_index,
-            &mut new_index_is_in_lis,
-            |a, b| a < b,
-            &mut predecessors,
-            &mut starts,
-        );
+        patches
+            .iter()
+            .zip(expected_patches.iter())
+            .for_each(|f| assert_eq!(compare_patch(f.0, f.1), true, "{}", description));
+    }
 
-        // Now we will iterate from the end of the new children back to the
-        // beginning, diffing old children we are reusing and if they aren't in the
-        // LIS moving them to their new destination, or creating new children. Note
-        // that iterating in reverse order lets us use `Node.prototype.insertBefore`
-        // to move/insert children.
-        //
-        // But first, we ensure that we have a child on the change list stack that
-        // we can `insertBefore`. We handle this once before looping over `new`
-        // children, so that we don't have to keep checking on every loop iteration.
-        if shared_suffix_count > 0 {
-            // There is a shared suffix after these middle children. We will be
-            // inserting before that shared suffix, so add the first child of that
-            // shared suffix to the change list stack.
-            //
-            // [... parent]
-            self.change_list
-                .go_down_to_child(old_shared_suffix_start - removed_count);
-        // [... parent first_child_of_shared_suffix]
-        } else {
-            // There is no shared suffix coming after these middle children.
-            // Therefore we have to process the last child in `new` and move it to
-            // the end of the parent's children if it isn't already there.
-            let last_index = new.len() - 1;
-            // uhhhh why an unwrap?
-            let last = new.last().unwrap();
-            // let last = new.last().unwrap_throw();
-            new = &new[..new.len() - 1];
-            if shared_keys.contains(&last.key()) {
-                let old_index = new_index_to_old_index[last_index];
-                let temp = old_index_to_temp[old_index];
-                // [... parent]
-                self.change_list.go_down_to_temp_child(temp);
-                // [... parent last]
-                self.diff_node(&old[old_index], last);
+    fn compare_patch(patch1: &Patch, patch2: &Patch) -> bool {
+        match (patch1, patch2) {
+            (Patch::AppendChildren(_, _), Patch::AppendChildren(_, _)) => true,
+            (Patch::AppendChildren(_, _), _) => false,
 
-                if new_index_is_in_lis.contains(&last_index) {
-                    // Don't move it, since it is already where it needs to be.
-                } else {
-                    self.change_list.commit_traversal();
-                    // [... parent last]
-                    self.change_list.append_child();
-                    // [... parent]
-                    self.change_list.go_down_to_temp_child(temp);
-                    // [... parent last]
-                }
-            } else {
-                self.change_list.commit_traversal();
-                // [... parent]
-                self.create(last);
+            (Patch::TruncateChildren(_, _), Patch::TruncateChildren(_, _)) => true,
+            (Patch::TruncateChildren(_, _), _) => false,
 
-                // [... parent last]
-                self.change_list.append_child();
-                // [... parent]
-                self.change_list.go_down_to_reverse_child(0);
-                // [... parent last]
-            }
-        }
+            (Patch::Replace(_, _), Patch::Replace(_, _)) => true,
+            (Patch::Replace(_, _), _) => false,
 
-        for (new_index, new_child) in new.iter().enumerate().rev() {
-            let old_index = new_index_to_old_index[new_index];
-            if old_index == u32::MAX as usize {
-                debug_assert!(!shared_keys.contains(&new_child.key()));
-                self.change_list.commit_traversal();
-                // [... parent successor]
-                self.create(new_child);
-                // [... parent successor new_child]
-                self.change_list.insert_before();
-            // [... parent new_child]
-            } else {
-                debug_assert!(shared_keys.contains(&new_child.key()));
-                let temp = old_index_to_temp[old_index];
-                debug_assert_ne!(temp, u32::MAX);
+            (Patch::AddAttributes(_, _), Patch::AddAttributes(_, _)) => true,
+            (Patch::AddAttributes(_, _), _) => false,
 
-                if new_index_is_in_lis.contains(&new_index) {
-                    // [... parent successor]
-                    self.change_list.go_to_temp_sibling(temp);
-                // [... parent new_child]
-                } else {
-                    self.change_list.commit_traversal();
-                    // [... parent successor]
-                    self.change_list.push_temporary(temp);
-                    // [... parent successor new_child]
-                    self.change_list.insert_before();
-                    // [... parent new_child]
-                }
+            (Patch::RemoveAttributes(_, _), Patch::RemoveAttributes(_, _)) => true,
+            (Patch::RemoveAttributes(_, _), _) => false,
 
-                self.diff_node(&old[old_index], new_child);
-            }
+            (Patch::ChangeText(_, _), Patch::ChangeText(_, _)) => true,
+            (Patch::ChangeText(_, _), _) => false,
         }
-
-        // [... parent child]
-        self.change_list.go_up();
-        // [... parent]
     }
 
-    // Diff the suffix of keyed children that share the same keys in the same order.
-    //
-    // The parent must be on the change list stack when we enter this function:
-    //
-    //     [... parent]
-    //
-    // When this function exits, the change list stack remains the same.
-    fn diff_keyed_suffix(
-        &mut self,
-        old: &[VNode<'a>],
-        new: &[VNode<'a>],
-        new_shared_suffix_start: usize,
+    fn printdiff(
+        tree1: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
+        tree2: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
+        desc: &'static str,
     ) {
-        debug_assert_eq!(old.len(), new.len());
-        debug_assert!(!old.is_empty());
+        let bump = Bump::new();
 
-        // [... parent]
-        self.change_list.go_down();
-        // [... parent new_child]
+        let nodes1 = tree1(&bump);
+        let nodes2 = tree2(&bump);
 
-        for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
-            self.change_list.go_to_sibling(new_shared_suffix_start + i);
+        let mut machine = DiffMachine::new();
 
-            self.diff_node(old_child, new_child);
-        }
-
-        // [... parent]
-        self.change_list.go_up();
-    }
+        let patches = machine.diff(&nodes1, &nodes2);
 
-    // Diff children that are not keyed.
-    //
-    // The parent must be on the top of the change list stack when entering this
-    // function:
-    //
-    //     [... parent]
-    //
-    // the change list stack is in the same state when this function returns.
-    fn diff_non_keyed_children(&mut self, old: &'a [VNode<'a>], new: &'a [VNode<'a>]) {
-        // Handled these cases in `diff_children` before calling this function.
-        debug_assert!(!new.is_empty());
-        debug_assert!(!old.is_empty());
-
-        //     [... parent]
-        self.change_list.go_down();
-        //     [... parent child]
-
-        for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
-            // [... parent prev_child]
-            self.change_list.go_to_sibling(i);
-            // [... parent this_child]
-            self.diff_node(old_child, new_child);
-        }
-
-        match old.len().cmp(&new.len()) {
-            Ordering::Greater => {
-                // [... parent prev_child]
-                self.change_list.go_to_sibling(new.len());
-                // [... parent first_child_to_remove]
-                self.change_list.commit_traversal();
-                // support::remove_self_and_next_siblings(state, &old[new.len()..]);
-                self.remove_self_and_next_siblings(&old[new.len()..]);
-                // [... parent]
+        patches.iter().for_each(|f| match f {
+            Patch::AppendChildren(idx, a) => {
+                println!("AppendChildren");
             }
-            Ordering::Less => {
-                // [... parent last_child]
-                self.change_list.go_up();
-                // [... parent]
-                self.change_list.commit_traversal();
-                self.create_and_append_children(&new[old.len()..]);
+            Patch::TruncateChildren(idx, a) => {
+                println!("TruncateChildren");
             }
-            Ordering::Equal => {
-                // [... parent child]
-                self.change_list.go_up();
-                // [... parent]
+            Patch::Replace(idx, a) => {
+                println!("Replace");
             }
-        }
-    }
-
-    // ======================
-    // Support methods
-    // ======================
-
-    // Emit instructions to create the given virtual node.
-    //
-    // The change list stack may have any shape upon entering this function:
-    //
-    //     [...]
-    //
-    // When this function returns, the new node is on top of the change list stack:
-    //
-    //     [... node]
-    fn create(&mut self, node: &VNode<'a>) {
-        debug_assert!(self.change_list.traversal_is_committed());
-        match node {
-            VNode::Text(VText { text }) => {
-                self.change_list.create_text_node(text);
+            Patch::AddAttributes(idx, a) => {
+                println!("AddAttributes");
             }
-            VNode::Element(&VElement {
-                key: _,
-                tag_name,
-                listeners,
-                attributes,
-                children,
-                namespace,
-            }) => {
-                if let Some(namespace) = namespace {
-                    self.change_list.create_element_ns(tag_name, namespace);
-                } else {
-                    self.change_list.create_element(tag_name);
-                }
-
-                for l in listeners {
-                    // unsafe {
-                    //     registry.add(l);
-                    // }
-                    self.change_list.new_event_listener(l);
-                }
-
-                for attr in attributes {
-                    self.change_list
-                        .set_attribute(&attr.name, &attr.value, namespace.is_some());
-                }
-
-                // Fast path: if there is a single text child, it is faster to
-                // create-and-append the text node all at once via setting the
-                // parent's `textContent` in a single change list instruction than
-                // to emit three instructions to (1) create a text node, (2) set its
-                // text content, and finally (3) append the text node to this
-                // parent.
-                if children.len() == 1 {
-                    if let VNode::Text(VText { text }) = children[0] {
-                        self.change_list.set_text(text);
-                        return;
-                    }
-                }
-
-                for child in children {
-                    self.create(child);
-                    self.change_list.append_child();
-                }
-            }
-
-            /*
-            todo: integrate re-entrace
-            */
-            // NodeKind::Cached(ref c) => {
-            //     cached_roots.insert(c.id);
-            //     let (node, template) = cached_set.get(c.id);
-            //     if let Some(template) = template {
-            //         create_with_template(
-            //             cached_set,
-            //             self.change_list,
-            //             registry,
-            //             template,
-            //             node,
-            //             cached_roots,
-            //         );
-            //     } else {
-            //         create(cached_set, change_list, registry, node, cached_roots);
-            //     }
-            // }
-            VNode::Suspended => {
-                todo!("Creation of VNode::Suspended not yet supported")
+            Patch::RemoveAttributes(idx, a) => {
+                println!("RemoveAttributes");
             }
-            VNode::Component(_) => {
-                todo!("Creation of VNode::Component not yet supported")
+            Patch::ChangeText(idx, a) => {
+                println!("ChangeText");
             }
-        }
-    }
-
-    // Remove all of a node's children.
-    //
-    // The change list stack must have this shape upon entry to this function:
-    //
-    //     [... parent]
-    //
-    // When this function returns, the change list stack is in the same state.
-    pub fn remove_all_children(&mut self, old: &[VNode<'a>]) {
-        debug_assert!(self.change_list.traversal_is_committed());
-        for child in old {
-            // registry.remove_subtree(child);
-        }
-        // Fast way to remove all children: set the node's textContent to an empty
-        // string.
-        self.change_list.set_text("");
+        });
     }
 
-    // Create the given children and append them to the parent node.
-    //
-    // The parent node must currently be on top of the change list stack:
-    //
-    //     [... parent]
-    //
-    // When this function returns, the change list stack is in the same state.
-    pub fn create_and_append_children(&mut self, new: &[VNode<'a>]) {
-        debug_assert!(self.change_list.traversal_is_committed());
-        for child in new {
-            self.create(child);
-            self.change_list.append_child();
-        }
-    }
+    #[test]
+    fn example_diff() {
+        printdiff(
+            html! { <div> </div> },
+            html! { <div>"Hello world!" </div> },
+            "demo the difference between two simple dom tree",
+        );
 
-    // Remove the current child and all of its following siblings.
-    //
-    // The change list stack must have this shape upon entry to this function:
-    //
-    //     [... parent child]
-    //
-    // After the function returns, the child is no longer on the change list stack:
-    //
-    //     [... parent]
-    pub fn remove_self_and_next_siblings(&mut self, old: &[VNode<'a>]) {
-        debug_assert!(self.change_list.traversal_is_committed());
-        for child in old {
-            // registry.remove_subtree(child);
-        }
-        self.change_list.remove_self_and_next_siblings();
+        printdiff(
+            html! {
+                <div>
+                    "Hello world!"
+                </div>
+            },
+            html! {
+                <div>
+                    <div>
+                        "Hello world!"
+                        "Hello world!"
+                        "Hello world!"
+                        "Hello world!"
+                        "Hello world!"
+                    </div>
+                </div>
+            },
+            "demo the difference between two simple dom tree",
+        );
     }
 }
-
-enum KeyedPrefixResult {
-    // Fast path: we finished diffing all the children just by looking at the
-    // prefix of shared keys!
-    Finished,
-    // There is more diffing work to do. Here is a count of how many children at
-    // the beginning of `new` and `old` we already processed.
-    MoreWorkToDo(usize),
-}
-
-mod support {
-    use super::*;
-
-    // // Get or create the template.
-    // //
-    // // Upon entering this function the change list stack may be in any shape:
-    // //
-    // //     [...]
-    // //
-    // // When this function returns, it leaves a freshly cloned copy of the template
-    // // on the top of the change list stack:
-    // //
-    // //     [... template]
-    // #[inline]
-    // pub fn get_or_create_template<'a>(// cached_set: &'a CachedSet,
-    //     // change_list: &mut ChangeListBuilder,
-    //     // registry: &mut EventsRegistry,
-    //     // cached_roots: &mut FxHashSet<CacheId>,
-    //     // template_id: CacheId,
-    // ) -> (&'a Node<'a>, bool) {
-    //     let (template, template_template) = cached_set.get(template_id);
-    //     debug_assert!(
-    //         template_template.is_none(),
-    //         "templates should not be templated themselves"
-    //     );
-
-    //     // If we haven't already created and saved the physical DOM subtree for this
-    //     // template, do that now.
-    //     if change_list.has_template(template_id) {
-    //         // Clone the template and push it onto the stack.
-    //         //
-    //         // [...]
-    //         change_list.push_template(template_id);
-    //         // [... template]
-
-    //         (template, true)
-    //     } else {
-    //         // [...]
-    //         create(cached_set, change_list, registry, template, cached_roots);
-    //         // [... template]
-    //         change_list.save_template(template_id);
-    //         // [... template]
-
-    //         (template, false)
-    //     }
-    // }
-
-    // pub fn create_and_replace(
-    //     cached_set: &CachedSet,
-    //     change_list: &mut ChangeListBuilder,
-    //     registry: &mut EventsRegistry,
-    //     new_template: Option<CacheId>,
-    //     old: &Node,
-    //     new: &Node,
-    //     cached_roots: &mut FxHashSet<CacheId>,
-    // ) {
-    //     debug_assert!(change_list.traversal_is_committed());
-
-    //     if let Some(template_id) = new_template {
-    //         let (template, needs_listeners) = get_or_create_template(
-    //             cached_set,
-    //             change_list,
-    //             registry,
-    //             cached_roots,
-    //             template_id,
-    //         );
-    //         change_list.replace_with();
-
-    //         let mut old_forcing = None;
-    //         if needs_listeners {
-    //             old_forcing = Some(change_list.push_force_new_listeners());
-    //         }
-
-    //         diff(
-    //             cached_set,
-    //             change_list,
-    //             registry,
-    //             template,
-    //             new,
-    //             cached_roots,
-    //         );
-
-    //         if let Some(old) = old_forcing {
-    //             change_list.pop_force_new_listeners(old);
-    //         }
-
-    //         change_list.commit_traversal();
-    //     } else {
-    //         create(cached_set, change_list, registry, new, cached_roots);
-    //         change_list.replace_with();
-    //     }
-    //     registry.remove_subtree(old);
-    // }
-
-    // pub fn create_with_template(
-    //     cached_set: &CachedSet,
-    //     change_list: &mut ChangeListBuilder,
-    //     registry: &mut EventsRegistry,
-    //     template_id: CacheId,
-    //     node: &Node,
-    //     cached_roots: &mut FxHashSet<CacheId>,
-    // ) {
-    //     debug_assert!(change_list.traversal_is_committed());
-
-    //     // [...]
-    //     let (template, needs_listeners) =
-    //         get_or_create_template(cached_set, change_list, registry, cached_roots, template_id);
-    //     // [... template]
-
-    //     // Now diff the node with its template.
-    //     //
-    //     // We must force adding new listeners instead of updating existing ones,
-    //     // since listeners don't get cloned in `cloneNode`.
-    //     let mut old_forcing = None;
-    //     if needs_listeners {
-    //         old_forcing = Some(change_list.push_force_new_listeners());
-    //     }
-
-    //     diff(
-    //         cached_set,
-    //         change_list,
-    //         registry,
-    //         template,
-    //         node,
-    //         cached_roots,
-    //     );
-
-    //     if let Some(old) = old_forcing {
-    //         change_list.pop_force_new_listeners(old);
-    //     }
-
-    //     // Make sure that we come back up to the level we were at originally.
-    //     change_list.commit_traversal();
-    // }
-}

+ 1122 - 0
packages/core/src/dodriodiff.rs

@@ -0,0 +1,1122 @@
+/// Diff the `old` node with the `new` node. Emits instructions to modify a
+/// physical DOM node that reflects `old` into something that reflects `new`.
+///
+/// Upon entry to this function, the physical DOM node must be on the top of the
+/// change list stack:
+///
+///     [... node]
+///
+/// The change list stack is in the same state when this function exits.
+///
+/// ----
+///
+/// There are more ways of increasing diff performance here that are currently not implemented.
+/// Additionally, the caching mechanism has also been tweaked.
+///
+/// Instead of having "cached" nodes, each component is, by default, a cached node. This leads to increased
+/// memory overhead for large numbers of small components, but we can optimize this by tracking alloc size over time
+/// and shrinking bumps down if possible.
+///
+/// Additionally, clean up of these components is not done at diff time (though it should), but rather, the diffing
+/// proprogates removal lifecycle events for affected components into the event queue. It's not imperative that these
+/// are ran immediately, but it should be noted that cleanup of components might be able to emit changes.
+///
+/// This diffing only ever occurs on a component-by-component basis (not entire trees at once).
+///
+/// Currently, the listener situation is a bit broken.
+/// We aren't removing listeners (choosing to leak them instead) :(
+/// Eventually, we'll set things up so add/remove listener is an instruction again
+///
+/// A major assumption of this diff algorithm when combined with the ChangeList is that the Changelist will be
+/// fresh and the event queue is clean. This lets us continue to batch edits together under the same ChangeList
+///
+/// More info on how to improve this diffing algorithm:
+///  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
+use fxhash::{FxHashMap, FxHashSet};
+use generational_arena::Index;
+
+use crate::{
+    changelist::EditList,
+    innerlude::{Attribute, Listener, Scope, VElement, VNode, VText},
+    virtual_dom::LifecycleEvent,
+};
+
+use std::cmp::Ordering;
+
+/// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while
+/// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event.
+///
+/// By re-entering via NodeDiff, we can connect disparate edits together into a single EditList. This batching of edits
+/// leads to very fast re-renders (all done in a single animation frame).
+///
+/// It also means diffing two trees is only ever complex as diffing a single smaller tree, and then re-entering at a
+/// different cursor position.
+///
+/// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components
+/// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via
+/// subscriptions and props changes.
+struct DiffingMachine<'a> {
+    change_list: &'a mut EditList<'a>,
+    immediate_queue: Vec<Index>,
+    diffed: FxHashSet<Index>,
+    need_to_diff: FxHashSet<Index>,
+}
+
+enum NeedToDiff {
+    PropsChanged,
+    Subscription,
+}
+
+impl<'a> DiffingMachine<'a> {
+    fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
+        /*
+        For each valid case, we "commit traversal", meaning we save this current position in the tree.
+        Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
+        When re-entering, we reuse the EditList in DiffState
+        */
+        match (old, new) {
+            // This case occurs when two text nodes are generation
+            (VNode::Text(VText { text: old_text }), VNode::Text(VText { text: new_text })) => {
+                if old_text != new_text {
+                    self.change_list.commit_traversal();
+                    self.change_list.set_text(new_text);
+                }
+            }
+
+            // Definitely different, need to commit update
+            (VNode::Text(_), VNode::Element(_)) => {
+                // TODO: Hook up the events properly
+                // todo!("Hook up events registry");
+                self.change_list.commit_traversal();
+                // diff_support::create(cached_set, self.change_list, registry, new, cached_roots);
+                self.create(new);
+                // registry.remove_subtree(&old);
+                self.change_list.replace_with();
+            }
+
+            // Definitely different, need to commit update
+            (VNode::Element(_), VNode::Text(_)) => {
+                self.change_list.commit_traversal();
+                self.create(new);
+
+                // create(cached_set, self.change_list, registry, new, cached_roots);
+                // Note: text nodes cannot have event listeners, so we don't need to
+                // remove the old node's listeners from our registry her.
+                self.change_list.replace_with();
+            }
+            // compare elements
+            // if different, schedule different types of update
+            (VNode::Element(eold), VNode::Element(enew)) => {
+                // If the element type is completely different, the element needs to be re-rendered completely
+                if enew.tag_name != eold.tag_name || enew.namespace != eold.namespace {
+                    self.change_list.commit_traversal();
+                    // create(cached_set, self.change_list, registry, new, cached_roots);
+                    // registry.remove_subtree(&old);
+                    self.change_list.replace_with();
+                    return;
+                }
+
+                self.diff_listeners(eold.listeners, enew.listeners);
+
+                self.diff_attr(eold.attributes, enew.attributes, enew.namespace.is_some());
+
+                self.diff_children(eold.children, enew.children);
+            }
+            // No immediate change to dom. If props changed though, queue a "props changed" update
+            // However, mark these for a
+            (VNode::Component(_), VNode::Component(_)) => {
+                todo!("Usage of component VNode not currently supported");
+                //     // Both the new and old nodes are cached.
+                //     (&NodeKind::Cached(ref new), &NodeKind::Cached(ref old)) => {
+                //         cached_roots.insert(new.id);
+                //         if new.id == old.id {
+                //             // This is the same cached node, so nothing has changed!
+                //             return;
+                //         }
+                //         let (new, new_template) = cached_set.get(new.id);
+                //         let (old, old_template) = cached_set.get(old.id);
+                //         if new_template == old_template {
+                //             // If they are both using the same template, then just diff the
+                //             // subtrees.
+                //             diff(cached_set, change_list, registry, old, new, cached_roots);
+                //         } else {
+                //             // Otherwise, they are probably different enough that
+                //             // re-constructing the subtree from scratch should be faster.
+                //             // This doubly holds true if we have a new template.
+                //             change_list.commit_traversal();
+                //             create_and_replace(
+                //                 cached_set,
+                //                 change_list,
+                //                 registry,
+                //                 new_template,
+                //                 old,
+                //                 new,
+                //                 cached_roots,
+                //             );
+                //         }
+                //     }
+                // queue a lifecycle event.
+                // no change
+            }
+
+            // A component has been birthed!
+            // Queue its arrival
+            (_, VNode::Component(_)) => {
+                todo!("Usage of component VNode not currently supported");
+                //     // Old cached node and new non-cached node. Again, assume that they are
+                //     // probably pretty different and create the new non-cached node afresh.
+                //     (_, &NodeKind::Cached(_)) => {
+                //         change_list.commit_traversal();
+                //         create(cached_set, change_list, registry, new, cached_roots);
+                //         registry.remove_subtree(&old);
+                //         change_list.replace_with();
+                //     }
+                // }
+            }
+
+            // A component was removed :(
+            // Queue its removal
+            (VNode::Component(_), _) => {
+                //     // New cached node when the old node was not cached. In this scenario,
+                //     // we assume that they are pretty different, and it isn't worth diffing
+                //     // the subtrees, so we just create the new cached node afresh.
+                //     (&NodeKind::Cached(ref c), _) => {
+                //         change_list.commit_traversal();
+                //         cached_roots.insert(c.id);
+                //         let (new, new_template) = cached_set.get(c.id);
+                //         create_and_replace(
+                //             cached_set,
+                //             change_list,
+                //             registry,
+                //             new_template,
+                //             old,
+                //             new,
+                //             cached_roots,
+                //         );
+                //     }
+                todo!("Usage of component VNode not currently supported");
+            }
+
+            // A suspended component appeared!
+            // Don't do much, just wait
+            (VNode::Suspended, _) | (_, VNode::Suspended) => {
+                // (VNode::Element(_), VNode::Suspended) => {}
+                // (VNode::Text(_), VNode::Suspended) => {}
+                // (VNode::Component(_), VNode::Suspended) => {}
+                // (VNode::Suspended, VNode::Element(_)) => {}
+                // (VNode::Suspended, VNode::Text(_)) => {}
+                // (VNode::Suspended, VNode::Suspended) => {}
+                // (VNode::Suspended, VNode::Component(_)) => {}
+                todo!("Suspended components not currently available")
+            }
+        }
+    }
+
+    // Diff event listeners between `old` and `new`.
+    //
+    // The listeners' node must be on top of the change list stack:
+    //
+    //     [... node]
+    //
+    // The change list stack is left unchanged.
+    fn diff_listeners(&mut self, old: &[Listener<'a>], new: &[Listener<'a>]) {
+        if !old.is_empty() || !new.is_empty() {
+            self.change_list.commit_traversal();
+        }
+
+        'outer1: for new_l in new {
+            unsafe {
+                // Safety relies on removing `new_l` from the registry manually at
+                // the end of its lifetime. This happens below in the `'outer2`
+                // loop, and elsewhere in diffing when removing old dom trees.
+                // registry.add(new_l);
+            }
+
+            for old_l in old {
+                if new_l.event == old_l.event {
+                    self.change_list.update_event_listener(new_l);
+                    continue 'outer1;
+                }
+            }
+
+            self.change_list.new_event_listener(new_l);
+        }
+
+        'outer2: for old_l in old {
+            // registry.remove(old_l);
+
+            for new_l in new {
+                if new_l.event == old_l.event {
+                    continue 'outer2;
+                }
+            }
+            self.change_list.remove_event_listener(old_l.event);
+        }
+    }
+
+    // Diff a node's attributes.
+    //
+    // The attributes' node must be on top of the change list stack:
+    //
+    //     [... node]
+    //
+    // The change list stack is left unchanged.
+    fn diff_attr(
+        &mut self,
+        old: &'a [Attribute<'a>],
+        new: &'a [Attribute<'a>],
+        is_namespaced: bool,
+    ) {
+        // Do O(n^2) passes to add/update and remove attributes, since
+        // there are almost always very few attributes.
+        'outer: for new_attr in new {
+            if new_attr.is_volatile() {
+                self.change_list.commit_traversal();
+                self.change_list
+                    .set_attribute(new_attr.name, new_attr.value, is_namespaced);
+            } else {
+                for old_attr in old {
+                    if old_attr.name == new_attr.name {
+                        if old_attr.value != new_attr.value {
+                            self.change_list.commit_traversal();
+                            self.change_list.set_attribute(
+                                new_attr.name,
+                                new_attr.value,
+                                is_namespaced,
+                            );
+                        }
+                        continue 'outer;
+                    }
+                }
+
+                self.change_list.commit_traversal();
+                self.change_list
+                    .set_attribute(new_attr.name, new_attr.value, is_namespaced);
+            }
+        }
+
+        'outer2: for old_attr in old {
+            for new_attr in new {
+                if old_attr.name == new_attr.name {
+                    continue 'outer2;
+                }
+            }
+
+            self.change_list.commit_traversal();
+            self.change_list.remove_attribute(old_attr.name);
+        }
+    }
+
+    // Diff the given set of old and new children.
+    //
+    // The parent must be on top of the change list stack when this function is
+    // entered:
+    //
+    //     [... parent]
+    //
+    // the change list stack is in the same state when this function returns.
+    fn diff_children(&mut self, old: &'a [VNode<'a>], new: &'a [VNode<'a>]) {
+        if new.is_empty() {
+            if !old.is_empty() {
+                self.change_list.commit_traversal();
+                self.remove_all_children(old);
+            }
+            return;
+        }
+
+        if new.len() == 1 {
+            match (old.first(), &new[0]) {
+                (
+                    Some(&VNode::Text(VText { text: old_text })),
+                    &VNode::Text(VText { text: new_text }),
+                ) if old_text == new_text => {
+                    // Don't take this fast path...
+                }
+
+                (_, &VNode::Text(VText { text })) => {
+                    self.change_list.commit_traversal();
+                    self.change_list.set_text(text);
+                    // for o in old {
+                    //     registry.remove_subtree(o);
+                    // }
+                    return;
+                }
+
+                (_, _) => {}
+            }
+        }
+
+        if old.is_empty() {
+            if !new.is_empty() {
+                self.change_list.commit_traversal();
+                self.create_and_append_children(new);
+            }
+            return;
+        }
+
+        let new_is_keyed = new[0].key().is_some();
+        let old_is_keyed = old[0].key().is_some();
+
+        debug_assert!(
+            new.iter().all(|n| n.key().is_some() == new_is_keyed),
+            "all siblings must be keyed or all siblings must be non-keyed"
+        );
+        debug_assert!(
+            old.iter().all(|o| o.key().is_some() == old_is_keyed),
+            "all siblings must be keyed or all siblings must be non-keyed"
+        );
+
+        if new_is_keyed && old_is_keyed {
+            let t = self.change_list.next_temporary();
+            // diff_keyed_children(self.change_list, old, new);
+            // diff_keyed_children(self.change_list, old, new, cached_roots);
+            // diff_keyed_children(cached_set, self.change_list, registry, old, new, cached_roots);
+            self.change_list.set_next_temporary(t);
+        } else {
+            self.diff_non_keyed_children(old, new);
+            // diff_non_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
+        }
+    }
+
+    // Diffing "keyed" children.
+    //
+    // With keyed children, we care about whether we delete, move, or create nodes
+    // versus mutate existing nodes in place. Presumably there is some sort of CSS
+    // transition animation that makes the virtual DOM diffing algorithm
+    // observable. By specifying keys for nodes, we know which virtual DOM nodes
+    // must reuse (or not reuse) the same physical DOM nodes.
+    //
+    // This is loosely based on Inferno's keyed patching implementation. However, we
+    // have to modify the algorithm since we are compiling the diff down into change
+    // list instructions that will be executed later, rather than applying the
+    // changes to the DOM directly as we compare virtual DOMs.
+    //
+    // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
+    //
+    // When entering this function, the parent must be on top of the change list
+    // stack:
+    //
+    //     [... parent]
+    //
+    // Upon exiting, the change list stack is in the same state.
+    fn diff_keyed_children(&mut self, old: &[VNode<'a>], new: &[VNode<'a>]) {
+        // let DiffState { change_list, queue } = &*state;
+
+        if cfg!(debug_assertions) {
+            let mut keys = fxhash::FxHashSet::default();
+            let mut assert_unique_keys = |children: &[VNode]| {
+                keys.clear();
+                for child in children {
+                    let key = child.key();
+                    debug_assert!(
+                        key.is_some(),
+                        "if any sibling is keyed, all siblings must be keyed"
+                    );
+                    keys.insert(key);
+                }
+                debug_assert_eq!(
+                    children.len(),
+                    keys.len(),
+                    "keyed siblings must each have a unique key"
+                );
+            };
+            assert_unique_keys(old);
+            assert_unique_keys(new);
+        }
+
+        // First up, we diff all the nodes with the same key at the beginning of the
+        // children.
+        //
+        // `shared_prefix_count` is the count of how many nodes at the start of
+        // `new` and `old` share the same keys.
+        let shared_prefix_count = match self.diff_keyed_prefix(old, new) {
+            KeyedPrefixResult::Finished => return,
+            KeyedPrefixResult::MoreWorkToDo(count) => count,
+        };
+
+        match self.diff_keyed_prefix(old, new) {
+            KeyedPrefixResult::Finished => return,
+            KeyedPrefixResult::MoreWorkToDo(count) => count,
+        };
+
+        // Next, we find out how many of the nodes at the end of the children have
+        // the same key. We do _not_ diff them yet, since we want to emit the change
+        // list instructions such that they can be applied in a single pass over the
+        // DOM. Instead, we just save this information for later.
+        //
+        // `shared_suffix_count` is the count of how many nodes at the end of `new`
+        // and `old` share the same keys.
+        let shared_suffix_count = old[shared_prefix_count..]
+            .iter()
+            .rev()
+            .zip(new[shared_prefix_count..].iter().rev())
+            .take_while(|&(old, new)| old.key() == new.key())
+            .count();
+
+        let old_shared_suffix_start = old.len() - shared_suffix_count;
+        let new_shared_suffix_start = new.len() - shared_suffix_count;
+
+        // Ok, we now hopefully have a smaller range of children in the middle
+        // within which to re-order nodes with the same keys, remove old nodes with
+        // now-unused keys, and create new nodes with fresh keys.
+        self.diff_keyed_middle(
+            &old[shared_prefix_count..old_shared_suffix_start],
+            &new[shared_prefix_count..new_shared_suffix_start],
+            shared_prefix_count,
+            shared_suffix_count,
+            old_shared_suffix_start,
+        );
+
+        // Finally, diff the nodes at the end of `old` and `new` that share keys.
+        let old_suffix = &old[old_shared_suffix_start..];
+        let new_suffix = &new[new_shared_suffix_start..];
+        debug_assert_eq!(old_suffix.len(), new_suffix.len());
+        if !old_suffix.is_empty() {
+            self.diff_keyed_suffix(old_suffix, new_suffix, new_shared_suffix_start)
+        }
+    }
+
+    // Diff the prefix of children in `new` and `old` that share the same keys in
+    // the same order.
+    //
+    // Upon entry of this function, the change list stack must be:
+    //
+    //     [... parent]
+    //
+    // Upon exit, the change list stack is the same.
+    fn diff_keyed_prefix(&mut self, old: &[VNode<'a>], new: &[VNode<'a>]) -> KeyedPrefixResult {
+        self.change_list.go_down();
+        let mut shared_prefix_count = 0;
+
+        for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
+            if old.key() != new.key() {
+                break;
+            }
+
+            self.change_list.go_to_sibling(i);
+
+            self.diff_node(old, new);
+
+            shared_prefix_count += 1;
+        }
+
+        // If that was all of the old children, then create and append the remaining
+        // new children and we're finished.
+        if shared_prefix_count == old.len() {
+            self.change_list.go_up();
+            self.change_list.commit_traversal();
+            self.create_and_append_children(&new[shared_prefix_count..]);
+            return KeyedPrefixResult::Finished;
+        }
+
+        // And if that was all of the new children, then remove all of the remaining
+        // old children and we're finished.
+        if shared_prefix_count == new.len() {
+            self.change_list.go_to_sibling(shared_prefix_count);
+            self.change_list.commit_traversal();
+            self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
+            return KeyedPrefixResult::Finished;
+        }
+
+        self.change_list.go_up();
+        KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
+    }
+
+    // The most-general, expensive code path for keyed children diffing.
+    //
+    // We find the longest subsequence within `old` of children that are relatively
+    // ordered the same way in `new` (via finding a longest-increasing-subsequence
+    // of the old child's index within `new`). The children that are elements of
+    // this subsequence will remain in place, minimizing the number of DOM moves we
+    // will have to do.
+    //
+    // Upon entry to this function, the change list stack must be:
+    //
+    //     [... parent]
+    //
+    // Upon exit from this function, it will be restored to that same state.
+    fn diff_keyed_middle(
+        &mut self,
+        old: &[VNode<'a>],
+        mut new: &[VNode<'a>],
+        shared_prefix_count: usize,
+        shared_suffix_count: usize,
+        old_shared_suffix_start: usize,
+    ) {
+        // Should have already diffed the shared-key prefixes and suffixes.
+        debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
+        debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
+
+        // The algorithm below relies upon using `u32::MAX` as a sentinel
+        // value, so if we have that many new nodes, it won't work. This
+        // check is a bit academic (hence only enabled in debug), since
+        // wasm32 doesn't have enough address space to hold that many nodes
+        // in memory.
+        debug_assert!(new.len() < u32::MAX as usize);
+
+        // Map from each `old` node's key to its index within `old`.
+        let mut old_key_to_old_index = FxHashMap::default();
+        old_key_to_old_index.reserve(old.len());
+        old_key_to_old_index.extend(old.iter().enumerate().map(|(i, o)| (o.key(), i)));
+
+        // The set of shared keys between `new` and `old`.
+        let mut shared_keys = FxHashSet::default();
+        // Map from each index in `new` to the index of the node in `old` that
+        // has the same key.
+        let mut new_index_to_old_index = Vec::with_capacity(new.len());
+        new_index_to_old_index.extend(new.iter().map(|n| {
+            let key = n.key();
+            if let Some(&i) = old_key_to_old_index.get(&key) {
+                shared_keys.insert(key);
+                i
+            } else {
+                u32::MAX as usize
+            }
+        }));
+
+        // If none of the old keys are reused by the new children, then we
+        // remove all the remaining old children and create the new children
+        // afresh.
+        if shared_suffix_count == 0 && shared_keys.is_empty() {
+            if shared_prefix_count == 0 {
+                self.change_list.commit_traversal();
+                self.remove_all_children(old);
+            } else {
+                self.change_list.go_down_to_child(shared_prefix_count);
+                self.change_list.commit_traversal();
+                self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
+            }
+
+            self.create_and_append_children(new);
+
+            return;
+        }
+
+        // Save each of the old children whose keys are reused in the new
+        // children.
+        let mut old_index_to_temp = vec![u32::MAX; old.len()];
+        let mut start = 0;
+        loop {
+            let end = (start..old.len())
+                .find(|&i| {
+                    let key = old[i].key();
+                    !shared_keys.contains(&key)
+                })
+                .unwrap_or(old.len());
+
+            if end - start > 0 {
+                self.change_list.commit_traversal();
+                let mut t = self.change_list.save_children_to_temporaries(
+                    shared_prefix_count + start,
+                    shared_prefix_count + end,
+                );
+                for i in start..end {
+                    old_index_to_temp[i] = t;
+                    t += 1;
+                }
+            }
+
+            debug_assert!(end <= old.len());
+            if end == old.len() {
+                break;
+            } else {
+                start = end + 1;
+            }
+        }
+
+        // Remove any old children whose keys were not reused in the new
+        // children. Remove from the end first so that we don't mess up indices.
+        let mut removed_count = 0;
+        for (i, old_child) in old.iter().enumerate().rev() {
+            if !shared_keys.contains(&old_child.key()) {
+                // registry.remove_subtree(old_child);
+                // todo
+                self.change_list.commit_traversal();
+                self.change_list.remove_child(i + shared_prefix_count);
+                removed_count += 1;
+            }
+        }
+
+        // If there aren't any more new children, then we are done!
+        if new.is_empty() {
+            return;
+        }
+
+        // The longest increasing subsequence within `new_index_to_old_index`. This
+        // is the longest sequence on DOM nodes in `old` that are relatively ordered
+        // correctly within `new`. We will leave these nodes in place in the DOM,
+        // and only move nodes that are not part of the LIS. This results in the
+        // maximum number of DOM nodes left in place, AKA the minimum number of DOM
+        // nodes moved.
+        let mut new_index_is_in_lis = FxHashSet::default();
+        new_index_is_in_lis.reserve(new_index_to_old_index.len());
+        let mut predecessors = vec![0; new_index_to_old_index.len()];
+        let mut starts = vec![0; new_index_to_old_index.len()];
+        longest_increasing_subsequence::lis_with(
+            &new_index_to_old_index,
+            &mut new_index_is_in_lis,
+            |a, b| a < b,
+            &mut predecessors,
+            &mut starts,
+        );
+
+        // Now we will iterate from the end of the new children back to the
+        // beginning, diffing old children we are reusing and if they aren't in the
+        // LIS moving them to their new destination, or creating new children. Note
+        // that iterating in reverse order lets us use `Node.prototype.insertBefore`
+        // to move/insert children.
+        //
+        // But first, we ensure that we have a child on the change list stack that
+        // we can `insertBefore`. We handle this once before looping over `new`
+        // children, so that we don't have to keep checking on every loop iteration.
+        if shared_suffix_count > 0 {
+            // There is a shared suffix after these middle children. We will be
+            // inserting before that shared suffix, so add the first child of that
+            // shared suffix to the change list stack.
+            //
+            // [... parent]
+            self.change_list
+                .go_down_to_child(old_shared_suffix_start - removed_count);
+        // [... parent first_child_of_shared_suffix]
+        } else {
+            // There is no shared suffix coming after these middle children.
+            // Therefore we have to process the last child in `new` and move it to
+            // the end of the parent's children if it isn't already there.
+            let last_index = new.len() - 1;
+            // uhhhh why an unwrap?
+            let last = new.last().unwrap();
+            // let last = new.last().unwrap_throw();
+            new = &new[..new.len() - 1];
+            if shared_keys.contains(&last.key()) {
+                let old_index = new_index_to_old_index[last_index];
+                let temp = old_index_to_temp[old_index];
+                // [... parent]
+                self.change_list.go_down_to_temp_child(temp);
+                // [... parent last]
+                self.diff_node(&old[old_index], last);
+
+                if new_index_is_in_lis.contains(&last_index) {
+                    // Don't move it, since it is already where it needs to be.
+                } else {
+                    self.change_list.commit_traversal();
+                    // [... parent last]
+                    self.change_list.append_child();
+                    // [... parent]
+                    self.change_list.go_down_to_temp_child(temp);
+                    // [... parent last]
+                }
+            } else {
+                self.change_list.commit_traversal();
+                // [... parent]
+                self.create(last);
+
+                // [... parent last]
+                self.change_list.append_child();
+                // [... parent]
+                self.change_list.go_down_to_reverse_child(0);
+                // [... parent last]
+            }
+        }
+
+        for (new_index, new_child) in new.iter().enumerate().rev() {
+            let old_index = new_index_to_old_index[new_index];
+            if old_index == u32::MAX as usize {
+                debug_assert!(!shared_keys.contains(&new_child.key()));
+                self.change_list.commit_traversal();
+                // [... parent successor]
+                self.create(new_child);
+                // [... parent successor new_child]
+                self.change_list.insert_before();
+            // [... parent new_child]
+            } else {
+                debug_assert!(shared_keys.contains(&new_child.key()));
+                let temp = old_index_to_temp[old_index];
+                debug_assert_ne!(temp, u32::MAX);
+
+                if new_index_is_in_lis.contains(&new_index) {
+                    // [... parent successor]
+                    self.change_list.go_to_temp_sibling(temp);
+                // [... parent new_child]
+                } else {
+                    self.change_list.commit_traversal();
+                    // [... parent successor]
+                    self.change_list.push_temporary(temp);
+                    // [... parent successor new_child]
+                    self.change_list.insert_before();
+                    // [... parent new_child]
+                }
+
+                self.diff_node(&old[old_index], new_child);
+            }
+        }
+
+        // [... parent child]
+        self.change_list.go_up();
+        // [... parent]
+    }
+
+    // Diff the suffix of keyed children that share the same keys in the same order.
+    //
+    // The parent must be on the change list stack when we enter this function:
+    //
+    //     [... parent]
+    //
+    // When this function exits, the change list stack remains the same.
+    fn diff_keyed_suffix(
+        &mut self,
+        old: &[VNode<'a>],
+        new: &[VNode<'a>],
+        new_shared_suffix_start: usize,
+    ) {
+        debug_assert_eq!(old.len(), new.len());
+        debug_assert!(!old.is_empty());
+
+        // [... parent]
+        self.change_list.go_down();
+        // [... parent new_child]
+
+        for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
+            self.change_list.go_to_sibling(new_shared_suffix_start + i);
+
+            self.diff_node(old_child, new_child);
+        }
+
+        // [... parent]
+        self.change_list.go_up();
+    }
+
+    // Diff children that are not keyed.
+    //
+    // The parent must be on the top of the change list stack when entering this
+    // function:
+    //
+    //     [... parent]
+    //
+    // the change list stack is in the same state when this function returns.
+    fn diff_non_keyed_children(&mut self, old: &'a [VNode<'a>], new: &'a [VNode<'a>]) {
+        // Handled these cases in `diff_children` before calling this function.
+        debug_assert!(!new.is_empty());
+        debug_assert!(!old.is_empty());
+
+        //     [... parent]
+        self.change_list.go_down();
+        //     [... parent child]
+
+        for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
+            // [... parent prev_child]
+            self.change_list.go_to_sibling(i);
+            // [... parent this_child]
+            self.diff_node(old_child, new_child);
+        }
+
+        match old.len().cmp(&new.len()) {
+            Ordering::Greater => {
+                // [... parent prev_child]
+                self.change_list.go_to_sibling(new.len());
+                // [... parent first_child_to_remove]
+                self.change_list.commit_traversal();
+                // support::remove_self_and_next_siblings(state, &old[new.len()..]);
+                self.remove_self_and_next_siblings(&old[new.len()..]);
+                // [... parent]
+            }
+            Ordering::Less => {
+                // [... parent last_child]
+                self.change_list.go_up();
+                // [... parent]
+                self.change_list.commit_traversal();
+                self.create_and_append_children(&new[old.len()..]);
+            }
+            Ordering::Equal => {
+                // [... parent child]
+                self.change_list.go_up();
+                // [... parent]
+            }
+        }
+    }
+
+    // ======================
+    // Support methods
+    // ======================
+
+    // Emit instructions to create the given virtual node.
+    //
+    // The change list stack may have any shape upon entering this function:
+    //
+    //     [...]
+    //
+    // When this function returns, the new node is on top of the change list stack:
+    //
+    //     [... node]
+    fn create(&mut self, node: &VNode<'a>) {
+        debug_assert!(self.change_list.traversal_is_committed());
+        match node {
+            VNode::Text(VText { text }) => {
+                self.change_list.create_text_node(text);
+            }
+            VNode::Element(&VElement {
+                key: _,
+                tag_name,
+                listeners,
+                attributes,
+                children,
+                namespace,
+            }) => {
+                if let Some(namespace) = namespace {
+                    self.change_list.create_element_ns(tag_name, namespace);
+                } else {
+                    self.change_list.create_element(tag_name);
+                }
+
+                for l in listeners {
+                    // unsafe {
+                    //     registry.add(l);
+                    // }
+                    self.change_list.new_event_listener(l);
+                }
+
+                for attr in attributes {
+                    self.change_list
+                        .set_attribute(&attr.name, &attr.value, namespace.is_some());
+                }
+
+                // Fast path: if there is a single text child, it is faster to
+                // create-and-append the text node all at once via setting the
+                // parent's `textContent` in a single change list instruction than
+                // to emit three instructions to (1) create a text node, (2) set its
+                // text content, and finally (3) append the text node to this
+                // parent.
+                if children.len() == 1 {
+                    if let VNode::Text(VText { text }) = children[0] {
+                        self.change_list.set_text(text);
+                        return;
+                    }
+                }
+
+                for child in children {
+                    self.create(child);
+                    self.change_list.append_child();
+                }
+            }
+
+            /*
+            todo: integrate re-entrace
+            */
+            // NodeKind::Cached(ref c) => {
+            //     cached_roots.insert(c.id);
+            //     let (node, template) = cached_set.get(c.id);
+            //     if let Some(template) = template {
+            //         create_with_template(
+            //             cached_set,
+            //             self.change_list,
+            //             registry,
+            //             template,
+            //             node,
+            //             cached_roots,
+            //         );
+            //     } else {
+            //         create(cached_set, change_list, registry, node, cached_roots);
+            //     }
+            // }
+            VNode::Suspended => {
+                todo!("Creation of VNode::Suspended not yet supported")
+            }
+            VNode::Component(_) => {
+                todo!("Creation of VNode::Component not yet supported")
+            }
+        }
+    }
+
+    // Remove all of a node's children.
+    //
+    // The change list stack must have this shape upon entry to this function:
+    //
+    //     [... parent]
+    //
+    // When this function returns, the change list stack is in the same state.
+    pub fn remove_all_children(&mut self, old: &[VNode<'a>]) {
+        debug_assert!(self.change_list.traversal_is_committed());
+        for child in old {
+            // registry.remove_subtree(child);
+        }
+        // Fast way to remove all children: set the node's textContent to an empty
+        // string.
+        self.change_list.set_text("");
+    }
+
+    // Create the given children and append them to the parent node.
+    //
+    // The parent node must currently be on top of the change list stack:
+    //
+    //     [... parent]
+    //
+    // When this function returns, the change list stack is in the same state.
+    pub fn create_and_append_children(&mut self, new: &[VNode<'a>]) {
+        debug_assert!(self.change_list.traversal_is_committed());
+        for child in new {
+            self.create(child);
+            self.change_list.append_child();
+        }
+    }
+
+    // Remove the current child and all of its following siblings.
+    //
+    // The change list stack must have this shape upon entry to this function:
+    //
+    //     [... parent child]
+    //
+    // After the function returns, the child is no longer on the change list stack:
+    //
+    //     [... parent]
+    pub fn remove_self_and_next_siblings(&mut self, old: &[VNode<'a>]) {
+        debug_assert!(self.change_list.traversal_is_committed());
+        for child in old {
+            // registry.remove_subtree(child);
+        }
+        self.change_list.remove_self_and_next_siblings();
+    }
+}
+
+enum KeyedPrefixResult {
+    // Fast path: we finished diffing all the children just by looking at the
+    // prefix of shared keys!
+    Finished,
+    // There is more diffing work to do. Here is a count of how many children at
+    // the beginning of `new` and `old` we already processed.
+    MoreWorkToDo(usize),
+}
+
+mod support {
+    use super::*;
+
+    // // Get or create the template.
+    // //
+    // // Upon entering this function the change list stack may be in any shape:
+    // //
+    // //     [...]
+    // //
+    // // When this function returns, it leaves a freshly cloned copy of the template
+    // // on the top of the change list stack:
+    // //
+    // //     [... template]
+    // #[inline]
+    // pub fn get_or_create_template<'a>(// cached_set: &'a CachedSet,
+    //     // change_list: &mut ChangeListBuilder,
+    //     // registry: &mut EventsRegistry,
+    //     // cached_roots: &mut FxHashSet<CacheId>,
+    //     // template_id: CacheId,
+    // ) -> (&'a Node<'a>, bool) {
+    //     let (template, template_template) = cached_set.get(template_id);
+    //     debug_assert!(
+    //         template_template.is_none(),
+    //         "templates should not be templated themselves"
+    //     );
+
+    //     // If we haven't already created and saved the physical DOM subtree for this
+    //     // template, do that now.
+    //     if change_list.has_template(template_id) {
+    //         // Clone the template and push it onto the stack.
+    //         //
+    //         // [...]
+    //         change_list.push_template(template_id);
+    //         // [... template]
+
+    //         (template, true)
+    //     } else {
+    //         // [...]
+    //         create(cached_set, change_list, registry, template, cached_roots);
+    //         // [... template]
+    //         change_list.save_template(template_id);
+    //         // [... template]
+
+    //         (template, false)
+    //     }
+    // }
+
+    // pub fn create_and_replace(
+    //     cached_set: &CachedSet,
+    //     change_list: &mut ChangeListBuilder,
+    //     registry: &mut EventsRegistry,
+    //     new_template: Option<CacheId>,
+    //     old: &Node,
+    //     new: &Node,
+    //     cached_roots: &mut FxHashSet<CacheId>,
+    // ) {
+    //     debug_assert!(change_list.traversal_is_committed());
+
+    //     if let Some(template_id) = new_template {
+    //         let (template, needs_listeners) = get_or_create_template(
+    //             cached_set,
+    //             change_list,
+    //             registry,
+    //             cached_roots,
+    //             template_id,
+    //         );
+    //         change_list.replace_with();
+
+    //         let mut old_forcing = None;
+    //         if needs_listeners {
+    //             old_forcing = Some(change_list.push_force_new_listeners());
+    //         }
+
+    //         diff(
+    //             cached_set,
+    //             change_list,
+    //             registry,
+    //             template,
+    //             new,
+    //             cached_roots,
+    //         );
+
+    //         if let Some(old) = old_forcing {
+    //             change_list.pop_force_new_listeners(old);
+    //         }
+
+    //         change_list.commit_traversal();
+    //     } else {
+    //         create(cached_set, change_list, registry, new, cached_roots);
+    //         change_list.replace_with();
+    //     }
+    //     registry.remove_subtree(old);
+    // }
+
+    // pub fn create_with_template(
+    //     cached_set: &CachedSet,
+    //     change_list: &mut ChangeListBuilder,
+    //     registry: &mut EventsRegistry,
+    //     template_id: CacheId,
+    //     node: &Node,
+    //     cached_roots: &mut FxHashSet<CacheId>,
+    // ) {
+    //     debug_assert!(change_list.traversal_is_committed());
+
+    //     // [...]
+    //     let (template, needs_listeners) =
+    //         get_or_create_template(cached_set, change_list, registry, cached_roots, template_id);
+    //     // [... template]
+
+    //     // Now diff the node with its template.
+    //     //
+    //     // We must force adding new listeners instead of updating existing ones,
+    //     // since listeners don't get cloned in `cloneNode`.
+    //     let mut old_forcing = None;
+    //     if needs_listeners {
+    //         old_forcing = Some(change_list.push_force_new_listeners());
+    //     }
+
+    //     diff(
+    //         cached_set,
+    //         change_list,
+    //         registry,
+    //         template,
+    //         node,
+    //         cached_roots,
+    //     );
+
+    //     if let Some(old) = old_forcing {
+    //         change_list.pop_force_new_listeners(old);
+    //     }
+
+    //     // Make sure that we come back up to the level we were at originally.
+    //     change_list.commit_traversal();
+    // }
+}

+ 6 - 2
packages/core/src/lib.rs

@@ -65,11 +65,13 @@
 //! - dioxus-liveview (SSR + StringRenderer)
 //!
 
-pub mod changelist; // An "edit phase" described by transitions and edit operations
+// pub mod changelist; // An "edit phase" described by transitions and edit operations
 pub mod component; // Logic for extending FC
 pub mod context; // Logic for providing hook + context functionality to user components
 pub mod debug_renderer; // Test harness for validating that lifecycles and diffs work appropriately
-pub mod diff; // The diffing algorithm that builds the ChangeList
+pub mod diff;
+pub mod patch; // The diffing algorithm that builds the ChangeList
+               // pub mod dodriodiff; // The diffing algorithm that builds the ChangeList
 pub mod error; // Error type we expose to the renderers
 pub mod events; // Manages the synthetic event API
 pub mod hooks; // Built-in hooks
@@ -137,5 +139,7 @@ pub mod prelude {
     pub use dioxus_core_macro::fc;
     pub use dioxus_html_2::html;
 
+    pub use crate::diff::DiffMachine;
+
     pub use crate::hooks::*;
 }

+ 3 - 3
packages/core/src/nodebuilder.rs

@@ -366,7 +366,7 @@ where
     /// let my_div = div(&b).attr("id", "my-div").finish();
     /// ```
     #[inline]
-    pub fn attr(mut self, name: &'a str, value: &'a str) -> Self {
+    pub fn attr(mut self, name: &'static str, value: &'a str) -> Self {
         self.attributes.push(Attribute { name, value });
         self
     }
@@ -398,7 +398,7 @@ where
     ///     .bool_attr("hidden", Math::random() >= 0.5)
     ///     .finish();
     /// ```
-    pub fn bool_attr(mut self, name: &'a str, should_add: bool) -> Self {
+    pub fn bool_attr(mut self, name: &'static str, should_add: bool) -> Self {
         if should_add {
             self.attributes.push(Attribute { name, value: "" });
         }
@@ -1049,7 +1049,7 @@ pub fn text<'a>(contents: &'a str) -> VNode<'a> {
 ///
 /// let my_id_attr = attr("id", "my-id");
 /// ```
-pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> {
+pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
     Attribute { name, value }
 }
 

+ 2 - 2
packages/core/src/nodes.rs

@@ -143,8 +143,8 @@ mod velement {
     /// `href="https://example.com"`.
     #[derive(Clone, Debug)]
     pub struct Attribute<'a> {
-        pub(crate) name: &'a str,
-        pub(crate) value: &'a str,
+        pub name: &'static str,
+        pub value: &'a str,
     }
 
     impl<'a> Attribute<'a> {

+ 79 - 0
packages/core/src/patch.rs

@@ -0,0 +1,79 @@
+use fxhash::FxHashMap;
+
+use crate::innerlude::{VNode, VText};
+
+/// A Patch encodes an operation that modifies a real DOM element.
+///
+/// To update the real DOM that a user sees you'll want to first diff your
+/// old virtual dom and new virtual dom.
+///
+/// This diff operation will generate `Vec<Patch>` with zero or more patches that, when
+/// applied to your real DOM, will make your real DOM look like your new virtual dom.
+///
+/// Each Patch has a u32 node index that helps us identify the real DOM node that it applies to.
+///
+/// Our old virtual dom's nodes are indexed depth first, as shown in this illustration
+/// (0 being the root node, 1 being it's first child, 2 being it's first child's first child).
+///
+/// ```text
+///             .─.
+///            ( 0 )
+///             `┬'
+///         ┌────┴──────┐
+///         │           │
+///         ▼           ▼
+///        .─.         .─.
+///       ( 1 )       ( 4 )
+///        `┬'         `─'
+///    ┌────┴───┐       ├─────┬─────┐
+///    │        │       │     │     │
+///    ▼        ▼       ▼     ▼     ▼
+///   .─.      .─.     .─.   .─.   .─.
+///  ( 2 )    ( 3 )   ( 5 ) ( 6 ) ( 7 )
+///   `─'      `─'     `─'   `─'   `─'                  
+/// ```
+///
+/// The patching process is tested in a real browser in crates/virtual-dom-rs/tests/diff_patch.rs
+
+pub enum Patch<'a> {
+    /// Append a vector of child nodes to a parent node id.
+    AppendChildren(NodeIdx, Vec<&'a VNode<'a>>),
+
+    /// For a `node_i32`, remove all children besides the first `len`
+    TruncateChildren(NodeIdx, usize),
+
+    /// Replace a node with another node. This typically happens when a node's tag changes.
+    /// ex: <div> becomes <span>
+    Replace(NodeIdx, &'a VNode<'a>),
+
+    /// Add attributes that the new node has that the old node does not
+    AddAttributes(NodeIdx, FxHashMap<&'a str, &'a str>),
+
+    /// Remove attributes that the old node had that the new node doesn't
+    RemoveAttributes(NodeIdx, Vec<&'a str>),
+
+    /// Change the text of a Text node.
+    ChangeText(NodeIdx, &'a VText<'a>),
+}
+
+type NodeIdx = usize;
+
+impl<'a> Patch<'a> {
+    /// Every Patch is meant to be applied to a specific node within the DOM. Get the
+    /// index of the DOM node that this patch should apply to. DOM nodes are indexed
+    /// depth first with the root node in the tree having index 0.
+    pub fn node_idx(&self) -> usize {
+        match self {
+            Patch::AppendChildren(node_idx, _) => *node_idx,
+            Patch::TruncateChildren(node_idx, _) => *node_idx,
+            Patch::Replace(node_idx, _) => *node_idx,
+            Patch::AddAttributes(node_idx, _) => *node_idx,
+            Patch::RemoveAttributes(node_idx, _) => *node_idx,
+            Patch::ChangeText(node_idx, _) => *node_idx,
+        }
+    }
+}
+
+pub struct PatchList<'a> {
+    patches: Vec<Patch<'a>>,
+}

+ 6 - 4
packages/core/src/virtual_dom.rs

@@ -1,4 +1,5 @@
-use crate::{changelist::EditList, nodes::VNode};
+// use crate::{changelist::EditList, nodes::VNode};
+use crate::nodes::VNode;
 use crate::{events::EventTrigger, innerlude::*};
 use any::Any;
 use bumpalo::Bump;
@@ -121,7 +122,8 @@ impl VirtualDom {
     ///
     ///
     /// ```
-    pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<EditList<'_>> {
+    pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<()> {
+        // pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<EditList<'_>> {
         let EventTrigger {
             component_id,
             listener_id,
@@ -156,8 +158,8 @@ impl VirtualDom {
             process_event(&mut self.components, event)?;
         }
 
-        todo!()
-        // Ok(())
+        // todo!()
+        Ok(())
     }
 
     pub async fn progress_completely(&mut self) -> Result<()> {

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

@@ -17,9 +17,11 @@
 //!
 //! The `WebsysRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
 
+use dioxus::{patch::Patch, prelude::VText};
+pub use dioxus_core as dioxus;
 use dioxus_core::{
     events::EventTrigger,
-    prelude::{Properties, VNode, VirtualDom, FC},
+    prelude::{bumpalo::Bump, html, DiffMachine, Properties, VNode, VirtualDom, FC},
 };
 use futures::{channel::mpsc, future, SinkExt, StreamExt};
 use mpsc::UnboundedSender;
@@ -92,17 +94,432 @@ impl WebsysRenderer {
 
             match internal_dom.progress_with_event(event).await {
                 Err(_) => {}
-                Ok(_) => render_diffs(),
+                Ok(_) => {} // Ok(_) => render_diffs(),
             }
             // waiting for next event to arrive from the external triggers
         }
 
         Ok(())
     }
+
+    fn simple_render(tree: impl for<'a> Fn(&'a Bump) -> VNode<'a>) {
+        let bump = Bump::new();
+
+        let old = html! { <div> </div> }(&bump);
+        let new = tree(&bump);
+
+        let mut machine = DiffMachine::new();
+
+        let patches = machine.diff(&old, &new);
+    }
+}
+
+use std::collections::HashMap;
+use std::collections::HashSet;
+use std::{cmp::min, rc::Rc};
+use wasm_bindgen::JsCast;
+use wasm_bindgen::JsValue;
+use web_sys::{Element, Node, Text};
+
+/// Apply all of the patches to our old root node in order to create the new root node
+/// that we desire.
+/// This is usually used after diffing two virtual nodes.
+pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<(), JsValue> {
+    // pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<ActiveClosures, JsValue> {
+    let root_node: Node = root_node.into();
+
+    let mut cur_node_idx = 0;
+
+    let mut nodes_to_find = HashSet::new();
+
+    for patch in patches {
+        nodes_to_find.insert(patch.node_idx());
+    }
+
+    let mut element_nodes_to_patch = HashMap::new();
+    let mut text_nodes_to_patch = HashMap::new();
+
+    // Closures that were added to the DOM during this patch operation.
+    // let mut active_closures = HashMap::new();
+
+    find_nodes(
+        root_node,
+        &mut cur_node_idx,
+        &mut nodes_to_find,
+        &mut element_nodes_to_patch,
+        &mut text_nodes_to_patch,
+    );
+
+    for patch in patches {
+        let patch_node_idx = patch.node_idx();
+
+        if let Some(element) = element_nodes_to_patch.get(&patch_node_idx) {
+            let new_closures = apply_element_patch(&element, &patch)?;
+            // active_closures.extend(new_closures);
+            continue;
+        }
+
+        if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
+            apply_text_patch(&text_node, &patch)?;
+            continue;
+        }
+
+        unreachable!("Getting here means we didn't find the element or next node that we were supposed to patch.")
+    }
+
+    // Ok(active_closures)
+    Ok(())
+}
+
+fn find_nodes(
+    root_node: Node,
+    cur_node_idx: &mut usize,
+    nodes_to_find: &mut HashSet<usize>,
+    element_nodes_to_patch: &mut HashMap<usize, Element>,
+    text_nodes_to_patch: &mut HashMap<usize, Text>,
+) {
+    if nodes_to_find.len() == 0 {
+        return;
+    }
+
+    // We use child_nodes() instead of children() because children() ignores text nodes
+    let children = root_node.child_nodes();
+    let child_node_count = children.length();
+
+    // If the root node matches, mark it for patching
+    if nodes_to_find.get(&cur_node_idx).is_some() {
+        match root_node.node_type() {
+            Node::ELEMENT_NODE => {
+                element_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
+            }
+            Node::TEXT_NODE => {
+                text_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
+            }
+            other => unimplemented!("Unsupported root node type: {}", other),
+        }
+        nodes_to_find.remove(&cur_node_idx);
+    }
+
+    *cur_node_idx += 1;
+
+    for i in 0..child_node_count {
+        let node = children.item(i).unwrap();
+
+        match node.node_type() {
+            Node::ELEMENT_NODE => {
+                find_nodes(
+                    node,
+                    cur_node_idx,
+                    nodes_to_find,
+                    element_nodes_to_patch,
+                    text_nodes_to_patch,
+                );
+            }
+            Node::TEXT_NODE => {
+                if nodes_to_find.get(&cur_node_idx).is_some() {
+                    text_nodes_to_patch.insert(*cur_node_idx, node.unchecked_into());
+                }
+
+                *cur_node_idx += 1;
+            }
+            Node::COMMENT_NODE => {
+                // At this time we do not support user entered comment nodes, so if we see a comment
+                // then it was a delimiter created by virtual-dom-rs in order to ensure that two
+                // neighboring text nodes did not get merged into one by the browser. So we skip
+                // over this virtual-dom-rs generated comment node.
+            }
+            _other => {
+                // Ignoring unsupported child node type
+                // TODO: What do we do with this situation? Log a warning?
+            }
+        }
+    }
+}
+
+// pub type ActiveClosures = HashMap<u32, Vec<DynClosure>>;
+
+// fn apply_element_patch(node: &Element, patch: &Patch) -> Result<ActiveClosures, JsValue> {
+fn apply_element_patch(node: &Element, patch: &Patch) -> Result<(), JsValue> {
+    // let active_closures = HashMap::new();
+
+    match patch {
+        Patch::AddAttributes(_node_idx, attributes) => {
+            for (attrib_name, attrib_val) in attributes.iter() {
+                node.set_attribute(attrib_name, attrib_val)?;
+            }
+
+            // Ok(active_closures)
+            Ok(())
+        }
+        Patch::RemoveAttributes(_node_idx, attributes) => {
+            for attrib_name in attributes.iter() {
+                node.remove_attribute(attrib_name)?;
+            }
+
+            // Ok(active_closures)
+            Ok(())
+        }
+        Patch::Replace(_node_idx, new_node) => {
+            let created_node = create_dom_node(&new_node);
+
+            node.replace_with_with_node_1(&created_node.node)?;
+
+            Ok(())
+            // Ok(created_node.closures)
+        }
+        Patch::TruncateChildren(_node_idx, num_children_remaining) => {
+            let children = node.child_nodes();
+            let mut child_count = children.length();
+
+            // We skip over any separators that we placed between two text nodes
+            //   -> `<!--ptns-->`
+            //  and trim all children that come after our new desired `num_children_remaining`
+            let mut non_separator_children_found = 0;
+
+            for index in 0 as u32..child_count {
+                let child = children
+                    .get(min(index, child_count - 1))
+                    .expect("Potential child to truncate");
+
+                // If this is a comment node then we know that it is a `<!--ptns-->`
+                // text node separator that was created in virtual_node/mod.rs.
+                if child.node_type() == Node::COMMENT_NODE {
+                    continue;
+                }
+
+                non_separator_children_found += 1;
+
+                if non_separator_children_found <= *num_children_remaining as u32 {
+                    continue;
+                }
+
+                node.remove_child(&child).expect("Truncated children");
+                child_count -= 1;
+            }
+
+            Ok(())
+            // Ok(active_closures)
+        }
+        Patch::AppendChildren(_node_idx, new_nodes) => {
+            let parent = &node;
+
+            let mut active_closures = HashMap::new();
+
+            for new_node in new_nodes {
+                let created_node = create_dom_node(&new_node);
+                // let created_node = new_node.create_dom_node();
+
+                parent.append_child(&created_node.node)?;
+
+                active_closures.extend(created_node.closures);
+            }
+
+            Ok(())
+            // Ok(active_closures)
+        }
+        Patch::ChangeText(_node_idx, _new_node) => {
+            unreachable!("Elements should not receive ChangeText patches.")
+        }
+    }
+}
+
+fn apply_text_patch(node: &Text, patch: &Patch) -> Result<(), JsValue> {
+    match patch {
+        Patch::ChangeText(_node_idx, new_node) => {
+            node.set_node_value(Some(&new_node.text));
+        }
+        Patch::Replace(_node_idx, new_node) => {
+            node.replace_with_with_node_1(&create_dom_node(&new_node).node)?;
+            // node.replace_with_with_node_1(&new_node.create_dom_node().node)?;
+        }
+        other => unreachable!(
+            "Text nodes should only receive ChangeText or Replace patches, not ",
+            // other,
+            // "Text nodes should only receive ChangeText or Replace patches, not {:?}.",
+            // other,
+        ),
+    };
+
+    Ok(())
+}
+
+/// A node along with all of the closures that were created for that
+/// node's events and all of it's child node's events.
+pub struct CreatedNode<T> {
+    /// A `Node` or `Element` that was created from a `VirtualNode`
+    pub node: T,
+    /// A map of a node's unique identifier along with all of the Closures for that node.
+    ///
+    /// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
+    /// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
+    /// memory.
+    pub closures: HashMap<u32, Vec<DynClosure>>,
+}
+
+/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
+/// any Closure regardless of the arguments.
+pub type DynClosure = Rc<dyn AsRef<JsValue>>;
+
+impl<T> CreatedNode<T> {
+    pub fn without_closures<N: Into<T>>(node: N) -> Self {
+        CreatedNode {
+            node: node.into(),
+            closures: HashMap::with_capacity(0),
+        }
+    }
+}
+
+impl<T> std::ops::Deref for CreatedNode<T> {
+    type Target = T;
+    fn deref(&self) -> &Self::Target {
+        &self.node
+    }
+}
+
+impl From<CreatedNode<Element>> for CreatedNode<Node> {
+    fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
+        CreatedNode {
+            node: other.node.into(),
+            closures: other.closures,
+        }
+    }
+}
+fn create_dom_node(node: &VNode<'_>) -> CreatedNode<Node> {
+    match node {
+        VNode::Text(text_node) => CreatedNode::without_closures(create_text_node(text_node)),
+        VNode::Element(element_node) => create_element_node(element_node).into(),
+        // VNode::Element(element_node) => element_node.create_element_node().into(),
+        VNode::Suspended => todo!(" not iimplemented yet"),
+        VNode::Component(_) => todo!(" not iimplemented yet"),
+    }
+}
+
+/// Build a DOM element by recursively creating DOM nodes for this element and it's
+/// children, it's children's children, etc.
+pub fn create_element_node(node: &dioxus_core::nodes::VElement) -> CreatedNode<Element> {
+    let document = web_sys::window().unwrap().document().unwrap();
+
+    // TODO: enable svg again
+    // let element = if html_validation::is_svg_namespace(&node.tag_name) {
+    //     document
+    //         .create_element_ns(Some("http://www.w3.org/2000/svg"), &node.tag_name)
+    //         .unwrap()
+    // } else {
+    let element = document.create_element(&node.tag_name).unwrap();
+    // };
+
+    let mut closures = HashMap::new();
+
+    node.attributes
+        .iter()
+        .map(|f| (f.name, f.value))
+        .for_each(|(name, value)| {
+            if name == "unsafe_inner_html" {
+                element.set_inner_html(value);
+
+                return;
+            }
+
+            element
+                .set_attribute(name, value)
+                .expect("Set element attribute in create element");
+        });
+
+    // if node.events.0.len() > 0 {
+    //     let unique_id = create_unique_identifier();
+
+    //     element
+    //         .set_attribute("data-vdom-id".into(), &unique_id.to_string())
+    //         .expect("Could not set attribute on element");
+
+    //     closures.insert(unique_id, vec![]);
+
+    //     node.events.0.iter().for_each(|(onevent, callback)| {
+    //         // onclick -> click
+    //         let event = &onevent[2..];
+
+    //         let current_elem: &EventTarget = element.dyn_ref().unwrap();
+
+    //         current_elem
+    //             .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
+    //             .unwrap();
+
+    //         closures
+    //             .get_mut(&unique_id)
+    //             .unwrap()
+    //             .push(Rc::clone(callback));
+    //     });
+    // }
+
+    let mut previous_node_was_text = false;
+
+    node.children.iter().for_each(|child| {
+        match child {
+            VNode::Text(text_node) => {
+                let current_node = element.as_ref() as &web_sys::Node;
+
+                // We ensure that the text siblings are patched by preventing the browser from merging
+                // neighboring text nodes. Originally inspired by some of React's work from 2016.
+                //  -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
+                //  -> https://github.com/facebook/react/pull/5753
+                //
+                // `ptns` = Percy text node separator
+                if previous_node_was_text {
+                    let separator = document.create_comment("ptns");
+                    current_node
+                        .append_child(separator.as_ref() as &web_sys::Node)
+                        .unwrap();
+                }
+
+                current_node
+                    .append_child(&create_text_node(&text_node))
+                    .unwrap();
+
+                previous_node_was_text = true;
+            }
+            VNode::Element(element_node) => {
+                previous_node_was_text = false;
+
+                let child = create_element_node(element_node);
+                // let child = element_node.create_element_node();
+                let child_elem: Element = child.node;
+
+                closures.extend(child.closures);
+
+                element.append_child(&child_elem).unwrap();
+            }
+            VNode::Suspended => {
+                todo!("Not yet supported")
+            }
+            VNode::Component(_) => {
+                todo!("Not yet supported")
+            }
+        }
+    });
+
+    // TODO: connect on mount to the event system somehow
+    // if let Some(on_create_elem) = node.events.0.get("on_create_elem") {
+    //     let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
+    //     on_create_elem
+    //         .call1(&wasm_bindgen::JsValue::NULL, &element)
+    //         .unwrap();
+    // }
+
+    CreatedNode {
+        node: element,
+        closures,
+    }
+}
+
+/// Return a `Text` element from a `VirtualNode`, typically right before adding it
+/// into the DOM.
+pub fn create_text_node(node: &VText) -> Text {
+    let document = web_sys::window().unwrap().document().unwrap();
+    document.create_text_node(&node.text)
 }
 
-/// For any listeners in the tree, attach the sender closure.
-/// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu
-fn attach_listeners(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom) {}
+// /// For any listeners in the tree, attach the sender closure.
+// /// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu
+// fn attach_listeners(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom) {}
 
-fn render_diffs() {}
+// fn render_diffs() {}