Explorar el Código

wip: broken, but solved

Jonathan Kelley hace 4 años
padre
commit
cb74d70

+ 70 - 0
notes/log.md

@@ -0,0 +1,70 @@
+# March 3, 2021
+
+Still TODO:
+- Wire up Nodebuilder to track listeners as they are added.                     (easyish)
+- Wire up attrs on nodes to track listeners properly
+  - Could be done in the nodebuilder where the attrs are added automatically    (easyish)
+  - Could just inject context into the diffing algorithm                        (hardish)
+- Wire up component syntax                                                      (easy)
+- Wire up component calling approach                                            (easyish)
+- Wire up component diffing                                                     (hardish)
+
+
+Approach:
+- move listeners out of vnode diffing
+- move listeners onto scope via nodebuilder
+- instead of a listeners list, store a list of listeners and their IDs
+  - this way means the diffing algorithm doesn't need to know that context
+- This should fix our listener approach
+- The only thing from here is child component
+
+
+Thoughts:
+- the macros should generate a static set of attrs into a [attr] array (faster, more predictable, no allocs)
+- children should be generated as a static set if no parans are detected
+  - More complex in the macro sized, unfortunately, not *too* hard
+- Listeners should also be a static set (dynamic listeners don't make too much sense) 
+  - use the builder syntax if you're doing something wild and need this granular control
+- Tags should also be &'static str - no reason to generate them on the fly
+
+Major milestones going forward:
+- Scheduled updates
+- String renderer (and methods for accessing vdom directly as a tree of nodes)
+  - good existing work on this in some places
+- Suspense
+- Child support, nested diffing
+- State management
+- Tests tests tests
+  
+Done so far:
+- websys 
+- webview
+- rsx! macro
+- html! macro
+- lifecycles
+- scopes
+- hooks
+- context API
+- bump
+
+
+## Solutions from today's thinking session...
+
+### To solve children:
+
+- maintain a map of `ScopeIdx` to `Node` in the renderer
+- Add new patch commands
+    - traverse_to_known (idx)
+        - Pop known component onto stack (super easy)
+    - add_known (idx)
+        - Save top of stack as root associated with idx
+    - remove_known (idx)
+        - Remove node on top of stack from known roots
+    - ... Something like this
+- Continue with BFS exploration of child components, DFS of VNodes
+    - Easier to write, easier to reason about
+
+### To solve listeners:
+
+- Map listeners directly as attrs before diffing via a listenerhandle
+- Evaluation of nodes is now stateful where we track listeners as they are added

+ 5 - 1
packages/core-macro/src/htm.rs

@@ -58,7 +58,11 @@ impl ToTokens for HtmlRender {
 
         // create a lazy tree that accepts a bump allocator
         let final_tokens = quote! {
-            move |bump| { #new_toks }
+            move |ctx| {
+                let bump = ctx.bump();
+
+                #new_toks
+            }
         };
 
         final_tokens.to_tokens(out_tokens);

+ 4 - 1
packages/core-macro/src/rsxt.rs

@@ -72,7 +72,10 @@ impl ToTokens for RsxRender {
 
         // create a lazy tree that accepts a bump allocator
         let final_tokens = quote! {
-            move |bump: &Bump| { #new_toks }
+            move |ctx| {
+                let bump = ctx.bump();
+                #new_toks
+            }
         };
 
         final_tokens.to_tokens(out_tokens);

+ 1 - 1
packages/core/Cargo.toml

@@ -18,7 +18,7 @@ dioxus-core-macro = { path = "../core-macro", version = "0.1.1" }
 once_cell = "1.5.2"
 
 # Backs the scope creation and reutilization
-generational-arena = "0.2.8"
+generational-arena = { version = "0.2.8", features = ["serde"] }
 # Bumpalo backs the VNode creation
 bumpalo = { version = "3.6.0", features = ["collections"] }
 

+ 16 - 15
packages/core/examples/rsx_usage.rs

@@ -1,3 +1,4 @@
+use bumpalo::Bump;
 use dioxus_core::prelude::*;
 
 fn main() {}
@@ -12,21 +13,21 @@ struct ButtonProps<'a> {
 fn CustomButton(ctx: Context, props: ButtonProps) -> DomTree {
     let onfocus = move |evt: ()| log::debug!("Focused");
 
-    todo!()
-    // ctx.render(rsx! {
-    //     // button {
-    //     //     // ..props.attrs,
-    //     //     class: "abc123",
-    //     //     // style: { a: 2, b: 3, c: 4 },
-    //     //     onclick: {move |evt| {
-    //     //         log::info("hello world");
-    //     //     }},
-    //     //     // Custom1 { a: 123 }
-    //     //     // Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} }
-    //     //     // Custom3 { a: "sometext goes here" }
-    //     //     // Custom4 { onclick: |evt| log::info("click") }
-    //     // }
-    // })
+    // todo!()
+    ctx.render(rsx! {
+        button {
+            // ..props.attrs,
+            class: "abc123",
+            // style: { a: 2, b: 3, c: 4 },
+            onclick: move |evt| {
+                // log::info("hello world");
+            },
+            // Custom1 { a: 123 }
+            // Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} }
+            // Custom3 { a: "sometext goes here" }
+            // Custom4 { onclick: |evt| log::info("click") }
+        }
+    })
 }
 
 // h1 {

+ 80 - 0
packages/core/old/patch.rs

@@ -0,0 +1,80 @@
+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
+
+// #[derive(serde::Serialize, serde::Deserialize)]
+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>>,
+}

+ 329 - 0
packages/core/old/percydiff.rs

@@ -0,0 +1,329 @@
+//! 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;
+
+pub struct DiffMachine {
+    immediate_queue: Vec<Index>,
+    diffed: FxHashSet<Index>,
+    need_to_diff: FxHashSet<Index>,
+    marked_for_removal: Vec<Index>,
+}
+
+impl DiffMachine {
+    pub fn new() -> Self {
+        Self {
+            immediate_queue: vec![],
+            diffed: FxHashSet::default(),
+            need_to_diff: FxHashSet::default(),
+            marked_for_removal: vec![],
+        }
+    }
+
+    /// 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;
+        }
+
+        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;
+            }
+
+            // 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);
+                }
+            }
+            return patches;
+        }
+
+        // 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));
+                }
+            }
+
+            // 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);
+                            }
+                        }
+                        None => {
+                            add_attributes.insert(new_attr_name, new_attr_val);
+                        }
+                    };
+                }
+
+                // 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);
+                        }
+                    };
+                }
+
+                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));
+                }
+
+                let old_child_count = old_element.children.len();
+                let new_child_count = new_element.children.len();
+
+                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))
+                }
+
+                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);
+                    }
+                }
+            }
+
+            (VNode::Suspended, _)
+            | (_, VNode::Suspended)
+            | (VNode::Component(_), _)
+            | (_, VNode::Component(_)) => {
+                todo!("cant yet handle these two")
+            }
+
+            (VNode::Text(_), VNode::Element(_))
+            | (VirtualNode::Element(_), VirtualNode::Text(_)) => {
+                unreachable!("Unequal variant discriminants should already have been handled");
+            }
+        };
+
+        //    new_root.create_element()
+        patches
+    }
+}
+
+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);
+        }
+    }
+}
+
+// #[cfg(test)]
+mod tests {
+    use bumpalo::Bump;
+
+    use super::*;
+
+    fn test_diff(
+        tree1: impl Fn(&Bump) -> VNode<'_>,
+        tree2: impl Fn(&Bump) -> VNode<'_>,
+        expected_patches: Vec<Patch>,
+        description: &'static str,
+    ) {
+        let bump = Bump::new();
+
+        let nodes1 = tree1(&bump);
+        let nodes2 = tree1(&bump);
+
+        let mut machine = DiffMachine::new();
+
+        let patches = machine.diff(&nodes1, &nodes2);
+
+        patches
+            .iter()
+            .zip(expected_patches.iter())
+            .for_each(|f| assert_eq!(compare_patch(f.0, f.1), true, "{}", description));
+    }
+
+    // todo: make this actually perform real comparisons
+    // by default, nothing is derived for vnodes or patches
+    fn compare_patch(patch1: &Patch, patch2: &Patch) -> bool {
+        match (patch1, patch2) {
+            (Patch::AppendChildren(_, _), Patch::AppendChildren(_, _)) => true,
+            (Patch::AppendChildren(_, _), _) => false,
+
+            (Patch::TruncateChildren(_, _), Patch::TruncateChildren(_, _)) => true,
+            (Patch::TruncateChildren(_, _), _) => false,
+
+            (Patch::Replace(_, _), Patch::Replace(_, _)) => true,
+            (Patch::Replace(_, _), _) => false,
+
+            (Patch::AddAttributes(_, _), Patch::AddAttributes(_, _)) => true,
+            (Patch::AddAttributes(_, _), _) => false,
+
+            (Patch::RemoveAttributes(_, _), Patch::RemoveAttributes(_, _)) => true,
+            (Patch::RemoveAttributes(_, _), _) => false,
+
+            (Patch::ChangeText(_, _), Patch::ChangeText(_, _)) => true,
+            (Patch::ChangeText(_, _), _) => false,
+        }
+    }
+
+    fn printdiff(
+        tree1: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
+        tree2: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
+        desc: &'static str,
+    ) {
+        let bump = Bump::new();
+
+        let nodes1 = tree1(&bump);
+        let nodes2 = tree2(&bump);
+
+        let mut machine = DiffMachine::new();
+
+        let patches = machine.diff(&nodes1, &nodes2);
+
+        patches.iter().for_each(|f| match f {
+            Patch::AppendChildren(idx, a) => {
+                println!("AppendChildren");
+            }
+            Patch::TruncateChildren(idx, a) => {
+                println!("TruncateChildren");
+            }
+            Patch::Replace(idx, a) => {
+                println!("Replace");
+            }
+            Patch::AddAttributes(idx, a) => {
+                println!("AddAttributes");
+            }
+            Patch::RemoveAttributes(idx, a) => {
+                println!("RemoveAttributes");
+            }
+            Patch::ChangeText(idx, a) => {
+                println!("ChangeText");
+            }
+        });
+    }
+
+    #[test]
+    fn example_diff() {
+        printdiff(
+            html! { <div> </div> },
+            html! { <div>"Hello world!" </div> },
+            "demo the difference between two simple dom tree",
+        );
+
+        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",
+        );
+    }
+}

+ 0 - 0
packages/core/src/validation.rs → packages/core/old/validation.rs


+ 0 - 753
packages/core/src/changelist.rs

@@ -1,753 +0,0 @@
-//! Changelist
-//! ----------
-//!
-//! This module exposes the "changelist" object which allows 3rd party implementors to handle diffs to the virtual dom.
-//!
-//! # Design
-//! ---
-//! In essence, the changelist object connects a diff of two vdoms to the actual edits required to update the output renderer.
-//!
-//! This abstraction relies on the assumption that the final renderer accepts a tree of elements. For most target platforms,
-//! this is an appropriate abstraction .
-//!
-//! During the diff phase, the change list is built. Once the diff phase is over, the change list is finished and returned back
-//! to the renderer. The renderer is responsible for propogating the updates (a stream of u32) to the final display.
-//!
-//! Because the change list references data internal to the vdom, it needs to be consumed by the renderer before the vdom
-//! can continue to work. This means once a change list is generated, it should be consumed as fast as possible, otherwise the
-//! dom will be blocked from progressing. This is enforced by lifetimes on the returend changelist object.
-//!
-//!
-
-use bumpalo::Bump;
-
-use crate::innerlude::Listener;
-use serde::{Deserialize, Serialize};
-/// The `Edit` represents a single modifcation of the renderer tree.
-///
-///
-///
-///
-///
-///
-///
-///
-/// todo@ jon: allow serde to be optional
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(tag = "type")]
-pub enum Edit<'d> {
-    SetText { text: &'d str },
-    RemoveSelfAndNextSiblings {},
-    ReplaceWith,
-    SetAttribute { name: &'d str, value: &'d str },
-    RemoveAttribute { name: &'d str },
-    PushReverseChild { n: u32 },
-    PopPushChild { n: u32 },
-    Pop,
-    AppendChild,
-    CreateTextNode { text: &'d str },
-    CreateElement { tag_name: &'d str },
-    NewEventListener { event_type: &'d str, idx: CbIdx },
-    UpdateEventListener { event_type: &'d str, idx: CbIdx },
-    RemoveEventListener { event_type: &'d str },
-    CreateElementNs { tag_name: &'d str, ns: &'d str },
-    SaveChildrenToTemporaries { temp: u32, start: u32, end: u32 },
-    PushChild { n: u32 },
-    PushTemporary { temp: u32 },
-    InsertBefore,
-    PopPushReverseChild { n: u32 },
-    RemoveChild { n: u32 },
-    SetClass { class_name: &'d str },
-}
-
-/// Re-export a cover over generational ID for libraries that don't need it
-/// We can go back and forth between the two via methods on GI
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
-pub struct CbIdx {
-    pub gi_id: usize,
-    pub gi_gen: u64,
-    pub listener_idx: usize,
-}
-
-impl CbIdx {
-    pub fn from_gi_index(index: generational_arena::Index, listener_idx: usize) -> Self {
-        let (gi_id, gi_gen) = index.into_raw_parts();
-        Self {
-            gi_id,
-            gi_gen,
-            listener_idx,
-        }
-    }
-}
-
-pub type EditList<'src> = Vec<Edit<'src>>;
-
-pub struct EditMachine<'src> {
-    pub traversal: Traversal,
-    next_temporary: u32,
-    forcing_new_listeners: bool,
-
-    pub emitter: EditList<'src>,
-}
-
-impl<'b> EditMachine<'b> {
-    pub fn new(_bump: &'b Bump) -> Self {
-        Self {
-            traversal: Traversal::new(),
-            next_temporary: 0,
-            forcing_new_listeners: false,
-            emitter: EditList::default(),
-        }
-    }
-
-    /// Traversal methods.
-    pub fn go_down(&mut self) {
-        self.traversal.down();
-    }
-
-    pub fn go_down_to_child(&mut self, index: usize) {
-        self.traversal.down();
-        self.traversal.sibling(index);
-    }
-
-    pub fn go_down_to_reverse_child(&mut self, index: usize) {
-        self.traversal.down();
-        self.traversal.reverse_sibling(index);
-    }
-
-    pub fn go_up(&mut self) {
-        self.traversal.up();
-    }
-
-    pub fn go_to_sibling(&mut self, index: usize) {
-        self.traversal.sibling(index);
-    }
-
-    pub fn go_to_temp_sibling(&mut self, temp: u32) {
-        self.traversal.up();
-        self.traversal.down_to_temp(temp);
-    }
-
-    pub fn go_down_to_temp_child(&mut self, temp: u32) {
-        self.traversal.down_to_temp(temp);
-    }
-
-    pub fn commit_traversal(&mut self) {
-        if self.traversal.is_committed() {
-            log::debug!("Traversal already committed");
-            return;
-        }
-
-        for mv in self.traversal.commit() {
-            match mv {
-                MoveTo::Parent => {
-                    log::debug!("emit: pop");
-                    self.emitter.push(Edit::Pop {});
-                    // self.emitter.pop();
-                }
-                MoveTo::Child(n) => {
-                    log::debug!("emit: push_child({})", n);
-                    self.emitter.push(Edit::PushChild { n });
-                }
-                MoveTo::ReverseChild(n) => {
-                    log::debug!("emit: push_reverse_child({})", n);
-                    self.emitter.push(Edit::PushReverseChild { n });
-                    // self.emitter.push_reverse_child(n);
-                }
-                MoveTo::Sibling(n) => {
-                    log::debug!("emit: pop_push_child({})", n);
-                    self.emitter.push(Edit::PopPushChild { n });
-                    // self.emitter.pop_push_child(n);
-                }
-                MoveTo::ReverseSibling(n) => {
-                    log::debug!("emit: pop_push_reverse_child({})", n);
-                    self.emitter.push(Edit::PopPushReverseChild { n });
-                }
-                MoveTo::TempChild(temp) => {
-                    log::debug!("emit: push_temporary({})", temp);
-                    self.emitter.push(Edit::PushTemporary { temp });
-                    // self.emitter.push_temporary(temp);
-                }
-            }
-        }
-    }
-
-    pub fn traversal_is_committed(&self) -> bool {
-        self.traversal.is_committed()
-    }
-}
-
-impl<'a> EditMachine<'a> {
-    pub fn next_temporary(&self) -> u32 {
-        self.next_temporary
-    }
-
-    pub fn set_next_temporary(&mut self, next_temporary: u32) {
-        self.next_temporary = next_temporary;
-    }
-
-    pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
-        debug_assert!(self.traversal_is_committed());
-        debug_assert!(start < end);
-        let temp_base = self.next_temporary;
-        // debug!(
-        //     "emit: save_children_to_temporaries({}, {}, {})",
-        //     temp_base, start, end
-        // );
-        self.next_temporary = temp_base + (end - start) as u32;
-        self.emitter.push(Edit::SaveChildrenToTemporaries {
-            temp: temp_base,
-            start: start as u32,
-            end: end as u32,
-        });
-        temp_base
-    }
-
-    pub fn push_temporary(&mut self, temp: u32) {
-        debug_assert!(self.traversal_is_committed());
-        // debug!("emit: push_temporary({})", temp);
-        self.emitter.push(Edit::PushTemporary { temp });
-        // self.emitter.push_temporary(temp);
-    }
-
-    pub fn remove_child(&mut self, child: usize) {
-        debug_assert!(self.traversal_is_committed());
-        // debug!("emit: remove_child({})", child);
-        // self.emitter.remove_child(child as u32);
-        self.emitter.push(Edit::RemoveChild { n: child as u32 })
-    }
-
-    pub fn insert_before(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-        // debug!("emit: insert_before()");
-        // self.emitter.insert_before();
-        self.emitter.push(Edit::InsertBefore {})
-    }
-
-    pub fn ensure_string(&mut self, _string: &str) -> StringKey {
-        todo!()
-        // self.strings.ensure_string(string, &self.emitter)
-    }
-
-    pub fn set_text(&mut self, text: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        // debug!("emit: set_text({:?})", text);
-        // self.emitter.set_text(text);
-        self.emitter.push(Edit::SetText { text });
-        // .set_text(text.as_ptr() as u32, text.len() as u32);
-    }
-
-    pub fn remove_self_and_next_siblings(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-        // debug!("emit: remove_self_and_next_siblings()");
-        self.emitter.push(Edit::RemoveSelfAndNextSiblings {});
-        // self.emitter.remove_self_and_next_siblings();
-    }
-
-    pub fn replace_with(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-        // debug!("emit: replace_with()");
-        self.emitter.push(Edit::ReplaceWith {});
-        // self.emitter.replace_with();
-    }
-
-    pub fn set_attribute(&mut self, name: &'a str, value: &'a str, is_namespaced: bool) {
-        debug_assert!(self.traversal_is_committed());
-        // todo!()
-        if name == "class" && !is_namespaced {
-            // let class_id = self.ensure_string(value);
-            // let class_id = self.ensure_string(value);
-            // debug!("emit: set_class({:?})", value);
-            // self.emitter.set_class(class_id.into());
-            self.emitter.push(Edit::SetClass { class_name: value });
-        } else {
-            self.emitter.push(Edit::SetAttribute { name, value });
-            // let name_id = self.ensure_string(name);
-            // let value_id = self.ensure_string(value);
-            // debug!("emit: set_attribute({:?}, {:?})", name, value);
-            // self.state
-            //     .emitter
-            //     .set_attribute(name_id.into(), value_id.into());
-        }
-    }
-
-    pub fn remove_attribute(&mut self, name: &'a str) {
-        // todo!("figure out how to get this working with ensure string");
-        self.emitter.push(Edit::RemoveAttribute { name });
-        // self.emitter.remove_attribute(name);
-        // debug_assert!(self.traversal_is_committed());
-        // // debug!("emit: remove_attribute({:?})", name);
-        // let name_id = self.ensure_string(name);
-        // self.emitter.remove_attribute(name_id.into());
-    }
-
-    pub fn append_child(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-        // debug!("emit: append_child()");
-        self.emitter.push(Edit::AppendChild {});
-        // self.emitter.append_child();
-    }
-
-    pub fn create_text_node(&mut self, text: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        // debug!("emit: create_text_node({:?})", text);
-        // self.emitter.create_text_node(text);
-        self.emitter.push(Edit::CreateTextNode { text });
-    }
-
-    pub fn create_element(&mut self, tag_name: &'a str) {
-        // debug_assert!(self.traversal_is_committed());
-        // debug!("emit: create_element({:?})", tag_name);
-        // let tag_name_id = self.ensure_string(tag_name);
-        self.emitter.push(Edit::CreateElement { tag_name });
-        // self.emitter.create_element(tag_name);
-        // self.emitter.create_element(tag_name_id.into());
-    }
-
-    pub fn create_element_ns(&mut self, tag_name: &'a str, ns: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        // debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
-        // let tag_name_id = self.ensure_string(tag_name);
-        // let ns_id = self.ensure_string(ns);
-        // self.emitter.create_element_ns(tag_name, ns);
-        self.emitter.push(Edit::CreateElementNs { tag_name, ns });
-        // self.emitter
-        //     .create_element_ns(tag_name_id.into(), ns_id.into());
-    }
-
-    pub fn push_force_new_listeners(&mut self) -> bool {
-        let old = self.forcing_new_listeners;
-        self.forcing_new_listeners = true;
-        old
-    }
-
-    pub fn pop_force_new_listeners(&mut self, previous: bool) {
-        debug_assert!(self.forcing_new_listeners);
-        self.forcing_new_listeners = previous;
-    }
-
-    pub fn new_event_listener(&mut self, event: &'a str, idx: CbIdx) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::NewEventListener {
-            event_type: event,
-            idx,
-        });
-        // todo!("Event listener not wired up yet");
-        // log::debug!("emit: new_event_listener({:?})", listener);
-        // let (a, b) = listener.get_callback_parts();
-        // debug_assert!(a != 0);
-        // // let event_id = self.ensure_string(listener.event);
-        // self.emitter.new_event_listener(listener.event.into(), a, b);
-    }
-
-    pub fn update_event_listener(&mut self, event: &'a str, idx: CbIdx) {
-        debug_assert!(self.traversal_is_committed());
-        if self.forcing_new_listeners {
-            self.new_event_listener(event, idx);
-            return;
-        }
-
-        self.emitter.push(Edit::NewEventListener {
-            event_type: event,
-            idx,
-        });
-
-        // log::debug!("emit: update_event_listener({:?})", listener);
-        // // todo!("Event listener not wired up yet");
-        // let (a, b) = listener.get_callback_parts();
-        // debug_assert!(a != 0);
-        // self.emitter.push(Edit::UpdateEventListener {
-        //     event_type: listener.event.into(),
-        //     a,
-        //     b,
-        // });
-        // self.emitter.update_event_listener(event_id.into(), a, b);
-    }
-
-    pub fn remove_event_listener(&mut self, event: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter
-            .push(Edit::RemoveEventListener { event_type: event });
-        // debug!("emit: remove_event_listener({:?})", event);
-        // let _event_id = self.ensure_string(event);
-        // todo!("Event listener not wired up yet");
-        // self.emitter.remove_event_listener(event_id.into());
-    }
-
-    // #[inline]
-    // pub fn has_template(&mut self, id: CacheId) -> bool {
-    //     self.templates.contains(&id)
-    // }
-
-    // pub fn save_template(&mut self, id: CacheId) {
-    //     debug_assert!(self.traversal_is_committed());
-    //     debug_assert!(!self.has_template(id));
-    //     // debug!("emit: save_template({:?})", id);
-    //     self.templates.insert(id);
-    //     self.emitter.save_template(id.into());
-    // }
-
-    // pub fn push_template(&mut self, id: CacheId) {
-    //     debug_assert!(self.traversal_is_committed());
-    //     debug_assert!(self.has_template(id));
-    //     // debug!("emit: push_template({:?})", id);
-    //     self.emitter.push_template(id.into());
-    // }
-}
-
-// Keeps track of where we are moving in a DOM tree, and shortens traversal
-// paths between mutations to their minimal number of operations.
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum MoveTo {
-    /// Move from the current node up to its parent.
-    Parent,
-
-    /// Move to the current node's n^th child.
-    Child(u32),
-
-    /// Move to the current node's n^th from last child.
-    ReverseChild(u32),
-
-    /// Move to the n^th sibling. Not relative from the current
-    /// location. Absolute indexed within all of the current siblings.
-    Sibling(u32),
-
-    /// Move to the n^th from last sibling. Not relative from the current
-    /// location. Absolute indexed within all of the current siblings.
-    ReverseSibling(u32),
-
-    /// Move down to the given saved temporary child.
-    TempChild(u32),
-}
-
-#[derive(Debug)]
-pub struct Traversal {
-    uncommitted: Vec<MoveTo>,
-}
-
-impl Traversal {
-    /// Construct a new `Traversal` with its internal storage backed by the
-    /// given bump arena.
-    pub fn new() -> Traversal {
-        Traversal {
-            uncommitted: Vec::with_capacity(32),
-        }
-    }
-
-    /// Move the traversal up in the tree.
-    pub fn up(&mut self) {
-        match self.uncommitted.last() {
-            Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::Parent);
-            }
-            Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) | Some(MoveTo::ReverseChild(_)) => {
-                self.uncommitted.pop();
-                // And we're back at the parent.
-            }
-            _ => {
-                self.uncommitted.push(MoveTo::Parent);
-            }
-        }
-    }
-
-    /// Move the traversal down in the tree to the first child of the current
-    /// node.
-    pub fn down(&mut self) {
-        if let Some(&MoveTo::Parent) = self.uncommitted.last() {
-            self.uncommitted.pop();
-            self.sibling(0);
-        } else {
-            self.uncommitted.push(MoveTo::Child(0));
-        }
-    }
-
-    /// Move the traversal to the n^th sibling.
-    pub fn sibling(&mut self, index: usize) {
-        let index = index as u32;
-        match self.uncommitted.last_mut() {
-            Some(MoveTo::Sibling(ref mut n)) | Some(MoveTo::Child(ref mut n)) => {
-                *n = index;
-            }
-            Some(MoveTo::ReverseSibling(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::Sibling(index));
-            }
-            Some(MoveTo::TempChild(_)) | Some(MoveTo::ReverseChild(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::Child(index))
-            }
-            _ => {
-                self.uncommitted.push(MoveTo::Sibling(index));
-            }
-        }
-    }
-
-    /// Move the the n^th from last sibling.
-    pub fn reverse_sibling(&mut self, index: usize) {
-        let index = index as u32;
-        match self.uncommitted.last_mut() {
-            Some(MoveTo::ReverseSibling(ref mut n)) | Some(MoveTo::ReverseChild(ref mut n)) => {
-                *n = index;
-            }
-            Some(MoveTo::Sibling(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::ReverseSibling(index));
-            }
-            Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::ReverseChild(index))
-            }
-            _ => {
-                self.uncommitted.push(MoveTo::ReverseSibling(index));
-            }
-        }
-    }
-
-    /// Go to the given saved temporary.
-    pub fn down_to_temp(&mut self, temp: u32) {
-        match self.uncommitted.last() {
-            Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
-                self.uncommitted.pop();
-            }
-            Some(MoveTo::Parent)
-            | Some(MoveTo::TempChild(_))
-            | Some(MoveTo::Child(_))
-            | Some(MoveTo::ReverseChild(_))
-            | None => {
-                // Can't remove moves to parents since we rely on their stack
-                // pops.
-            }
-        }
-        self.uncommitted.push(MoveTo::TempChild(temp));
-    }
-
-    /// Are all the traversal's moves committed? That is, are there no moves
-    /// that have *not* been committed yet?
-    #[inline]
-    pub fn is_committed(&self) -> bool {
-        // is_empty is not inlined?
-        self.uncommitted.is_empty()
-        // self.uncommitted.len() == 0
-    }
-
-    /// Commit this traversals moves and return the optimized path from the last
-    /// commit.
-    #[inline]
-    pub fn commit(&mut self) -> Moves {
-        Moves {
-            inner: self.uncommitted.drain(..),
-        }
-    }
-
-    #[inline]
-    pub fn reset(&mut self) {
-        self.uncommitted.clear();
-    }
-}
-
-pub struct Moves<'a> {
-    inner: std::vec::Drain<'a, MoveTo>,
-}
-
-impl Iterator for Moves<'_> {
-    type Item = MoveTo;
-
-    #[inline]
-    fn next(&mut self) -> Option<MoveTo> {
-        self.inner.next()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_traversal() {
-        fn t<F>(f: F) -> Box<dyn FnMut(&mut Traversal)>
-        where
-            F: 'static + FnMut(&mut Traversal),
-        {
-            Box::new(f) as _
-        }
-
-        for (mut traverse, expected_moves) in vec![
-            (
-                t(|t| {
-                    t.down();
-                }),
-                vec![MoveTo::Child(0)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                }),
-                vec![MoveTo::Parent],
-            ),
-            (
-                t(|t| {
-                    t.sibling(42);
-                }),
-                vec![MoveTo::Sibling(42)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.sibling(2);
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.sibling(3);
-                }),
-                vec![MoveTo::Child(3)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.sibling(4);
-                    t.sibling(8);
-                }),
-                vec![MoveTo::Child(8)],
-            ),
-            (
-                t(|t| {
-                    t.sibling(1);
-                    t.sibling(1);
-                }),
-                vec![MoveTo::Sibling(1)],
-            ),
-            (
-                t(|t| {
-                    t.reverse_sibling(3);
-                }),
-                vec![MoveTo::ReverseSibling(3)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.reverse_sibling(3);
-                }),
-                vec![MoveTo::ReverseChild(3)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.reverse_sibling(3);
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.reverse_sibling(3);
-                    t.reverse_sibling(6);
-                }),
-                vec![MoveTo::ReverseChild(6)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                    t.reverse_sibling(3);
-                    t.reverse_sibling(6);
-                }),
-                vec![MoveTo::Parent, MoveTo::ReverseSibling(6)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                    t.sibling(3);
-                    t.sibling(6);
-                }),
-                vec![MoveTo::Parent, MoveTo::Sibling(6)],
-            ),
-            (
-                t(|t| {
-                    t.sibling(3);
-                    t.sibling(6);
-                    t.up();
-                }),
-                vec![MoveTo::Parent],
-            ),
-            (
-                t(|t| {
-                    t.reverse_sibling(3);
-                    t.reverse_sibling(6);
-                    t.up();
-                }),
-                vec![MoveTo::Parent],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.down_to_temp(3);
-                }),
-                vec![MoveTo::Child(0), MoveTo::TempChild(3)],
-            ),
-            (
-                t(|t| {
-                    t.down_to_temp(3);
-                    t.sibling(5);
-                }),
-                vec![MoveTo::Child(5)],
-            ),
-            (
-                t(|t| {
-                    t.down_to_temp(3);
-                    t.reverse_sibling(5);
-                }),
-                vec![MoveTo::ReverseChild(5)],
-            ),
-            (
-                t(|t| {
-                    t.down_to_temp(3);
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.sibling(2);
-                    t.up();
-                    t.down_to_temp(3);
-                }),
-                vec![MoveTo::Parent, MoveTo::TempChild(3)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                    t.down_to_temp(3);
-                }),
-                vec![MoveTo::Parent, MoveTo::TempChild(3)],
-            ),
-        ] {
-            let mut traversal = Traversal::new();
-            traverse(&mut traversal);
-            let actual_moves: Vec<_> = traversal.commit().collect();
-            assert_eq!(actual_moves, expected_moves);
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub struct StringKey(u32);
-
-impl From<StringKey> for u32 {
-    #[inline]
-    fn from(key: StringKey) -> u32 {
-        key.0
-    }
-}

+ 4 - 2
packages/core/src/component.rs

@@ -1,6 +1,7 @@
 //! This file handles the supporting infrastructure for the `Component` trait and `Properties` which makes it possible
 //! for components to be used within Nodes.
-//!
+
+pub type ScopeIdx = generational_arena::Index;
 
 /// The `Component` trait refers to any struct or funciton that can be used as a component
 /// We automatically implement Component for FC<T>
@@ -44,6 +45,7 @@ mod tests {
 
     static TestComponent: FC<()> = |ctx, props| {
         //
+
         ctx.render(html! {
             <div>
             </div>
@@ -52,7 +54,7 @@ mod tests {
 
     static TestComponent2: FC<()> = |ctx, props| {
         //
-        ctx.render(|bump: &Bump| VNode::text("blah"))
+        ctx.render(|ctx| VNode::text("blah"))
     };
 
     #[test]

+ 18 - 3
packages/core/src/context.rs

@@ -76,8 +76,10 @@ impl<'a> Context<'a> {
     ///     ctx.render(lazy_tree)
     /// }
     ///```
-    pub fn render(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> DomTree {
-        let safe_nodes = lazy_nodes(self.bump);
+    pub fn render(self, lazy_nodes: impl FnOnce(NodeCtx<'a>) -> VNode<'a> + 'a) -> DomTree {
+        let ctx = NodeCtx { bump: self.bump };
+        let safe_nodes = lazy_nodes(ctx);
+
         let unsafe_nodes = unsafe { std::mem::transmute::<VNode<'a>, VNode<'static>>(safe_nodes) };
         self.final_nodes.deref().borrow_mut().replace(unsafe_nodes);
         DomTree {}
@@ -90,12 +92,25 @@ impl<'a> Context<'a> {
     /// When the future completes, the component will be renderered
     pub fn suspend(
         &self,
-        _fut: impl Future<Output = impl FnOnce(&'a Bump) -> VNode<'a>>,
+        _fut: impl Future<Output = impl FnOnce(&'a NodeCtx<'a>) -> VNode<'a>>,
     ) -> VNode<'a> {
         todo!()
     }
 }
 
+// NodeCtx is used to build VNodes in the component's memory space.
+// This struct adds metadata to the final DomTree about listeners, attributes, and children
+pub struct NodeCtx<'a> {
+    bump: &'a Bump,
+}
+
+impl NodeCtx<'_> {
+    #[inline]
+    pub fn bump(&self) -> &Bump {
+        self.bump
+    }
+}
+
 pub mod hooks {
     //! This module provides internal state management functionality for Dioxus components
     //!

+ 1073 - 247
packages/core/src/diff.rs

@@ -1,329 +1,1155 @@
-//! A primitive diffing algorithm
+//! 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.
 //!
+//! ----
 //!
-
-use std::{collections::HashMap, mem};
-
+//! 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 crate::innerlude::*;
-use crate::patch::Patch;
-use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
-use generational_arena::Index;
-
-pub struct DiffMachine {
-    immediate_queue: Vec<Index>,
-    diffed: FxHashSet<Index>,
-    need_to_diff: FxHashSet<Index>,
-    marked_for_removal: Vec<Index>,
+use bumpalo::Bump;
+use fxhash::{FxHashMap, FxHashSet};
+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.
+pub struct DiffMachine<'a> {
+    pub change_list: EditMachine<'a>,
+    immediate_queue: Vec<ScopeIdx>,
+    diffed: FxHashSet<ScopeIdx>,
+    need_to_diff: FxHashSet<ScopeIdx>,
 }
 
-impl DiffMachine {
-    pub fn new() -> Self {
+impl<'a> DiffMachine<'a> {
+    pub fn new(bump: &'a Bump) -> Self {
+        // log::debug!("starting diff machine");
         Self {
-            immediate_queue: vec![],
+            change_list: EditMachine::new(bump),
+            immediate_queue: Vec::new(),
             diffed: FxHashSet::default(),
             need_to_diff: FxHashSet::default(),
-            marked_for_removal: vec![],
+            // current_idx: None,
         }
     }
 
-    /// 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 consume(self) -> EditList<'a> {
+        self.change_list.emitter
     }
 
-    pub fn diff_recursive<'a, 'b>(
+    pub fn diff_node(
         &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;
-        }
+        old: &VNode<'a>,
+        new: &VNode<'a>,
+        // scope: Option<generational_arena::Index>,
+    ) {
+        log::debug!("old {:#?}", old);
+        log::debug!("new {:#?}", new);
 
-        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;
+        // Set it while diffing
+        // Reset it when finished diffing
+        // self.current_idx = scope;
+        /*
+        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);
+                }
             }
 
-            // 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;
-            // }
-        }
+            // 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);
 
-        // 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);
+                // 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)) => {
+                // log::debug!("elements are different");
+                // 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")
             }
-            return patches;
         }
+        // self.current_idx = None;
+    }
 
-        // 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));
+    // 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 (l_idx, new_l) in new.iter().enumerate() {
+            // 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);
+            // }
+            let event_type = new_l.event;
+
+            for old_l in old {
+                if new_l.event == old_l.event {
+                    // if let Some(scope) = self.current_idx {
+                    //     let cb = CbIdx::from_gi_index(scope, l_idx);
+                    self.change_list.update_event_listener(event_type, cb);
+                    // }
+
+                    continue 'outer1;
                 }
             }
 
-            // 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);
-                            }
-                        }
-                        None => {
-                            add_attributes.insert(new_attr_name, new_attr_val);
-                        }
-                    };
+            if let Some(scope) = self.current_idx {
+                let cb = CbIdx::from_gi_index(scope, l_idx);
+                self.change_list.new_event_listener(event_type, cb);
+            }
+        }
+
+        '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);
+        }
+    }
 
-                // 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);
+    // 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;
+                    }
                 }
 
-                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));
+                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);
+        }
+    }
 
-                let old_child_count = old_element.children.len();
-                let new_child_count = new_element.children.len();
+    // 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_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))
+        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 {
-                    patches.push(Patch::TruncateChildren(*cur_node_idx, new_child_count))
+                (_, &VNode::Text(VText { text })) => {
+                    self.change_list.commit_traversal();
+                    self.change_list.set_text(text);
+                    // for o in old {
+                    //     registry.remove_subtree(o);
+                    // }
+                    return;
                 }
 
-                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);
-                    }
-                }
+                (_, _) => {}
             }
+        }
 
-            (VNode::Suspended, _)
-            | (_, VNode::Suspended)
-            | (VNode::Component(_), _)
-            | (_, VNode::Component(_)) => {
-                todo!("cant yet handle these two")
+        if old.is_empty() {
+            if !new.is_empty() {
+                self.change_list.commit_traversal();
+                self.create_and_append_children(new);
             }
+            return;
+        }
 
-            (VNode::Text(_), VNode::Element(_))
-            | (VirtualNode::Element(_), VirtualNode::Text(_)) => {
-                unreachable!("Unequal variant discriminants should already have been handled");
-            }
-        };
+        let new_is_keyed = new[0].key().is_some();
+        let old_is_keyed = old[0].key().is_some();
 
-        //    new_root.create_element()
-        patches
+        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);
+        }
     }
-}
 
-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);
+    // 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)
         }
     }
-}
 
-// #[cfg(test)]
-mod tests {
-    use bumpalo::Bump;
+    // 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;
 
-    use super::*;
+        for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
+            if old.key() != new.key() {
+                break;
+            }
 
-    fn test_diff(
-        tree1: impl Fn(&Bump) -> VNode<'_>,
-        tree2: impl Fn(&Bump) -> VNode<'_>,
-        expected_patches: Vec<Patch>,
-        description: &'static str,
-    ) {
-        let bump = Bump::new();
+            self.change_list.go_to_sibling(i);
+
+            self.diff_node(old, new);
 
-        let nodes1 = tree1(&bump);
-        let nodes2 = tree1(&bump);
+            shared_prefix_count += 1;
+        }
 
-        let mut machine = DiffMachine::new();
+        // 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;
+        }
 
-        let patches = machine.diff(&nodes1, &nodes2);
+        // 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;
+        }
 
-        patches
-            .iter()
-            .zip(expected_patches.iter())
-            .for_each(|f| assert_eq!(compare_patch(f.0, f.1), true, "{}", description));
+        self.change_list.go_up();
+        KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
     }
 
-    // todo: make this actually perform real comparisons
-    // by default, nothing is derived for vnodes or patches
-    fn compare_patch(patch1: &Patch, patch2: &Patch) -> bool {
-        match (patch1, patch2) {
-            (Patch::AppendChildren(_, _), Patch::AppendChildren(_, _)) => true,
-            (Patch::AppendChildren(_, _), _) => false,
+    // 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);
 
-            (Patch::TruncateChildren(_, _), Patch::TruncateChildren(_, _)) => true,
-            (Patch::TruncateChildren(_, _), _) => false,
+        // 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)));
 
-            (Patch::Replace(_, _), Patch::Replace(_, _)) => true,
-            (Patch::Replace(_, _), _) => false,
+        // 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
+            }
+        }));
 
-            (Patch::AddAttributes(_, _), Patch::AddAttributes(_, _)) => true,
-            (Patch::AddAttributes(_, _), _) => false,
+        // 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..]);
+            }
 
-            (Patch::RemoveAttributes(_, _), Patch::RemoveAttributes(_, _)) => true,
-            (Patch::RemoveAttributes(_, _), _) => false,
+            self.create_and_append_children(new);
 
-            (Patch::ChangeText(_, _), Patch::ChangeText(_, _)) => true,
-            (Patch::ChangeText(_, _), _) => false,
+            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]
     }
 
-    fn printdiff(
-        tree1: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
-        tree2: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
-        desc: &'static str,
+    // 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,
     ) {
-        let bump = Bump::new();
+        debug_assert_eq!(old.len(), new.len());
+        debug_assert!(!old.is_empty());
 
-        let nodes1 = tree1(&bump);
-        let nodes2 = tree2(&bump);
+        // [... parent]
+        self.change_list.go_down();
+        // [... parent new_child]
 
-        let mut machine = DiffMachine::new();
+        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();
+    }
 
-        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());
 
-        patches.iter().for_each(|f| match f {
-            Patch::AppendChildren(idx, a) => {
-                println!("AppendChildren");
+        //     [... 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]
             }
-            Patch::TruncateChildren(idx, a) => {
-                println!("TruncateChildren");
+            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::Replace(idx, a) => {
-                println!("Replace");
+            Ordering::Equal => {
+                // [... parent child]
+                self.change_list.go_up();
+                // [... parent]
             }
-            Patch::AddAttributes(idx, a) => {
-                println!("AddAttributes");
+        }
+    }
+
+    // ======================
+    // 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,
+            }) => {
+                // log::info!("Creating {:#?}", node);
+                if let Some(namespace) = namespace {
+                    self.change_list.create_element_ns(tag_name, namespace);
+                } else {
+                    self.change_list.create_element(tag_name);
+                }
+
+                listeners.iter().enumerate().for_each(|(id, listener)| {
+                    if let Some(index) = self.current_idx {
+                        self.change_list
+                            .new_event_listener(listener.event, CbIdx::from_gi_index(index, id));
+                    } else {
+                        // Don't panic
+                        // Used for testing
+                        log::trace!("Failed to set listener, create was not called in the context of the virtual dom");
+                    }
+                });
+                // for l in listeners {
+                // unsafe {
+                //     registry.add(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();
+                }
             }
-            Patch::RemoveAttributes(idx, a) => {
-                println!("RemoveAttributes");
+
+            /*
+            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::ChangeText(idx, a) => {
-                println!("ChangeText");
+            VNode::Component(_) => {
+                todo!("Creation of VNode::Component not yet supported")
             }
-        });
+        }
     }
 
-    #[test]
-    fn example_diff() {
-        printdiff(
-            html! { <div> </div> },
-            html! { <div>"Hello world!" </div> },
-            "demo the difference between two simple dom tree",
-        );
+    // 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("");
+    }
 
-        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",
-        );
+    // 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 {
+
+    // // 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();
+    // }
+}

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

@@ -1,1165 +0,0 @@
-use bumpalo::Bump;
-/// 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::{CbIdx, Edit, EditList, EditMachine},
-    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.
-pub struct DiffMachine<'a> {
-    pub change_list: EditMachine<'a>,
-    immediate_queue: Vec<Index>,
-    diffed: FxHashSet<Index>,
-    need_to_diff: FxHashSet<Index>,
-
-    // Current scopes we're comparing
-    current_idx: Option<generational_arena::Index>,
-}
-
-impl<'a> DiffMachine<'a> {
-    pub fn new(bump: &'a Bump) -> Self {
-        // log::debug!("starting diff machine");
-        Self {
-            change_list: EditMachine::new(bump),
-            immediate_queue: Vec::new(),
-            diffed: FxHashSet::default(),
-            need_to_diff: FxHashSet::default(),
-            current_idx: None,
-        }
-    }
-
-    pub fn consume(self) -> EditList<'a> {
-        self.change_list.emitter
-    }
-
-    pub fn diff_node(
-        &mut self,
-        old: &VNode<'a>,
-        new: &VNode<'a>,
-        scope: Option<generational_arena::Index>,
-    ) {
-        log::debug!("old {:#?}", old);
-        log::debug!("new {:#?}", new);
-
-        // Set it while diffing
-        // Reset it when finished diffing
-        self.current_idx = scope;
-        /*
-        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)) => {
-                // log::debug!("elements are different");
-                // 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")
-            }
-        }
-        self.current_idx = None;
-    }
-
-    // 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 (l_idx, new_l) in new.iter().enumerate() {
-            // 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);
-            // }
-            let event_type = new_l.event;
-
-            for old_l in old {
-                if new_l.event == old_l.event {
-                    if let Some(scope) = self.current_idx {
-                        let cb = CbIdx::from_gi_index(scope, l_idx);
-                        self.change_list.update_event_listener(event_type, cb);
-                    }
-
-                    continue 'outer1;
-                }
-            }
-
-            if let Some(scope) = self.current_idx {
-                let cb = CbIdx::from_gi_index(scope, l_idx);
-                self.change_list.new_event_listener(event_type, cb);
-            }
-        }
-
-        '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, self.current_idx);
-
-            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, self.current_idx);
-
-                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, self.current_idx);
-            }
-        }
-
-        // [... 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, self.current_idx);
-        }
-
-        // [... 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, self.current_idx);
-        }
-
-        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,
-            }) => {
-                // log::info!("Creating {:#?}", node);
-                if let Some(namespace) = namespace {
-                    self.change_list.create_element_ns(tag_name, namespace);
-                } else {
-                    self.change_list.create_element(tag_name);
-                }
-
-                listeners.iter().enumerate().for_each(|(id, listener)| {
-                    if let Some(index) = self.current_idx {
-                        self.change_list
-                            .new_event_listener(listener.event, CbIdx::from_gi_index(index, id));
-                    } else {
-                        // Don't panic
-                        // Used for testing
-                        log::trace!("Failed to set listener, create was not called in the context of the virtual dom");
-                    }
-                });
-                // for l in listeners {
-                // unsafe {
-                //     registry.add(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 {
-
-    // // 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();
-    // }
-}

+ 5 - 13
packages/core/src/events.rs

@@ -4,29 +4,21 @@
 //! 3rd party renderers are responsible for forming this virtual events from events
 //!
 //! The goal here is to provide a consistent event interface across all renderer types
-use generational_arena::Index;
 
-use crate::innerlude::CbIdx;
+use crate::innerlude::ScopeIdx;
 
 #[derive(Debug)]
 pub struct EventTrigger {
-    pub component_id: Index,
+    pub component_id: ScopeIdx,
     pub listener_id: usize,
     pub event: VirtualEvent,
 }
 
 impl EventTrigger {
-    pub fn new(event: VirtualEvent, cb: CbIdx) -> Self {
-        let CbIdx {
-            gi_id,
-            gi_gen,
-            listener_idx,
-        } = cb;
-
-        let component_id = Index::from_raw_parts(gi_id, gi_gen);
+    pub fn new(event: VirtualEvent, scope: ScopeIdx, id: usize) -> Self {
         Self {
-            component_id,
-            listener_id: listener_idx,
+            component_id: scope,
+            listener_id: id,
             event,
         }
     }

+ 16 - 9
packages/core/src/lib.rs

@@ -65,20 +65,21 @@
 //! - dioxus-liveview (SSR + StringRenderer)
 //!
 
-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;
-                        // pub mod patch; // The diffing algorithm that builds the ChangeList
-pub mod dodriodiff; // The diffing algorithm that builds the ChangeList
+pub mod debug_renderer;
+pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
+               // pub mod diff;
+               // pub mod patch; // The diffing algorithm that builds the ChangeList
+pub mod diff;
+// 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
 pub mod nodebuilder; // Logic for building VNodes with a direct syntax
 pub mod nodes; // Logic for the VNodes
 pub mod scope; // Logic for single components
-pub mod validation; //  Logic for validating trees
+               // pub mod validation; //  Logic for validating trees
 pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and suspense
 
 pub mod builder {
@@ -96,7 +97,12 @@ pub(crate) mod innerlude {
     pub(crate) use crate::virtual_dom::VirtualDom;
     pub(crate) use nodes::*;
 
-    pub use crate::changelist::CbIdx;
+    pub use crate::component::ScopeIdx;
+    pub use crate::diff::DiffMachine;
+    pub use crate::events::EventTrigger;
+    pub use crate::patch::{EditList, EditMachine};
+    // pub use crate::patchdx;
+    // pub use crate::patchtList;
     // pub use nodes::iterables::IterableNodes;
     /// This type alias is an internal way of abstracting over the static functions that represent components.
 
@@ -138,6 +144,7 @@ pub mod prelude {
 
     // expose our bumpalo type
     pub use bumpalo;
+    pub use bumpalo::Bump;
 
     // Re-export the FC macro
     pub use crate as dioxus;
@@ -146,8 +153,8 @@ pub mod prelude {
     pub use dioxus_core_macro::format_args_f;
     pub use dioxus_core_macro::{fc, html, rsx};
 
-    // pub use crate::diff::DiffMachine;
-    pub use crate::dodriodiff::DiffMachine;
+    pub use crate::component::ScopeIdx;
+    pub use crate::diff::DiffMachine;
 
     pub use crate::hooks::*;
 }

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

@@ -333,7 +333,7 @@ where
     ///
     /// // A button that does something when clicked!
     /// let my_button = button(&b)
-    ///     .on("click", |root, vdom, event| {
+    ///     .on("click", |event| {
     ///         // ...
     ///     })
     ///     .finish();

+ 7 - 1
packages/core/src/nodes.rs

@@ -94,7 +94,8 @@ mod vnode {
 }
 
 mod velement {
-    use crate::events::VirtualEvent;
+
+    // use crate::{events::VirtualEvent, innerlude::CbIdx};
 
     use super::*;
     use std::fmt::Debug;
@@ -179,6 +180,11 @@ mod velement {
         }
     }
 
+    pub struct ListenerHandle {
+        pub event: &'static str,
+        pub idx: CbIdx,
+    }
+
     /// An event listener.
     pub struct Listener<'bump> {
         /// The type of event to listen for.

+ 713 - 59
packages/core/src/patch.rs

@@ -1,80 +1,734 @@
-use fxhash::FxHashMap;
+//! Changelist
+//! ----------
+//!
+//! This module exposes the "changelist" object which allows 3rd party implementors to handle diffs to the virtual dom.
+//!
+//! # Design
+//! ---
+//! In essence, the changelist object connects a diff of two vdoms to the actual edits required to update the output renderer.
+//!
+//! This abstraction relies on the assumption that the final renderer accepts a tree of elements. For most target platforms,
+//! this is an appropriate abstraction .
+//!
+//! During the diff phase, the change list is built. Once the diff phase is over, the change list is finished and returned back
+//! to the renderer. The renderer is responsible for propogating the updates to the final display.
+//!
+//! Because the change list references data internal to the vdom, it needs to be consumed by the renderer before the vdom
+//! can continue to work. This means once a change list is generated, it should be consumed as fast as possible, otherwise the
+//! dom will be blocked from progressing. This is enforced by lifetimes on the returend changelist object.
+//!
+//!
 
-use crate::innerlude::{VNode, VText};
+use bumpalo::Bump;
 
-/// A Patch encodes an operation that modifies a real DOM element.
+use crate::innerlude::{Listener, ScopeIdx};
+use serde::{Deserialize, Serialize};
+/// The `Edit` represents a single modifcation of the renderer tree.
 ///
-/// 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
+///
+///
+/// todo@ jon: allow serde to be optional
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum Edit<'d> {
+    SetText { text: &'d str },
+    RemoveSelfAndNextSiblings {},
+    ReplaceWith,
+    SetAttribute { name: &'d str, value: &'d str },
+    RemoveAttribute { name: &'d str },
+    PushReverseChild { n: u32 },
+    PopPushChild { n: u32 },
+    Pop,
+    AppendChild,
+    CreateTextNode { text: &'d str },
+    CreateElement { tag_name: &'d str },
+    NewEventListener { event_type: &'d str, s: ScopeIdx },
+    UpdateEventListener { event_type: &'d str, s: ScopeIdx },
+    RemoveEventListener { event_type: &'d str },
+    CreateElementNs { tag_name: &'d str, ns: &'d str },
+    SaveChildrenToTemporaries { temp: u32, start: u32, end: u32 },
+    PushChild { n: u32 },
+    PushTemporary { temp: u32 },
+    InsertBefore,
+    PopPushReverseChild { n: u32 },
+    RemoveChild { n: u32 },
+    SetClass { class_name: &'d str },
+    PushKnown { node: ScopeIdx },
+}
+
+pub type EditList<'src> = Vec<Edit<'src>>;
+
+pub struct EditMachine<'src> {
+    pub traversal: Traversal,
+    next_temporary: u32,
+    forcing_new_listeners: bool,
+
+    pub emitter: EditList<'src>,
+}
+
+impl<'b> EditMachine<'b> {
+    pub fn new(_bump: &'b Bump) -> Self {
+        Self {
+            traversal: Traversal::new(),
+            next_temporary: 0,
+            forcing_new_listeners: false,
+            emitter: EditList::default(),
+        }
+    }
+
+    /// Traversal methods.
+    pub fn go_down(&mut self) {
+        self.traversal.down();
+    }
+
+    pub fn go_down_to_child(&mut self, index: usize) {
+        self.traversal.down();
+        self.traversal.sibling(index);
+    }
+
+    pub fn go_down_to_reverse_child(&mut self, index: usize) {
+        self.traversal.down();
+        self.traversal.reverse_sibling(index);
+    }
+
+    pub fn go_up(&mut self) {
+        self.traversal.up();
+    }
+
+    pub fn go_to_sibling(&mut self, index: usize) {
+        self.traversal.sibling(index);
+    }
+
+    pub fn go_to_temp_sibling(&mut self, temp: u32) {
+        self.traversal.up();
+        self.traversal.down_to_temp(temp);
+    }
+
+    pub fn go_down_to_temp_child(&mut self, temp: u32) {
+        self.traversal.down_to_temp(temp);
+    }
+
+    pub fn commit_traversal(&mut self) {
+        if self.traversal.is_committed() {
+            log::debug!("Traversal already committed");
+            return;
+        }
+
+        for mv in self.traversal.commit() {
+            match mv {
+                MoveTo::Parent => {
+                    log::debug!("emit: pop");
+                    self.emitter.push(Edit::Pop {});
+                    // self.emitter.pop();
+                }
+                MoveTo::Child(n) => {
+                    log::debug!("emit: push_child({})", n);
+                    self.emitter.push(Edit::PushChild { n });
+                }
+                MoveTo::ReverseChild(n) => {
+                    log::debug!("emit: push_reverse_child({})", n);
+                    self.emitter.push(Edit::PushReverseChild { n });
+                    // self.emitter.push_reverse_child(n);
+                }
+                MoveTo::Sibling(n) => {
+                    log::debug!("emit: pop_push_child({})", n);
+                    self.emitter.push(Edit::PopPushChild { n });
+                    // self.emitter.pop_push_child(n);
+                }
+                MoveTo::ReverseSibling(n) => {
+                    log::debug!("emit: pop_push_reverse_child({})", n);
+                    self.emitter.push(Edit::PopPushReverseChild { n });
+                }
+                MoveTo::TempChild(temp) => {
+                    log::debug!("emit: push_temporary({})", temp);
+                    self.emitter.push(Edit::PushTemporary { temp });
+                    // self.emitter.push_temporary(temp);
+                }
+            }
+        }
+    }
+
+    pub fn traversal_is_committed(&self) -> bool {
+        self.traversal.is_committed()
+    }
+}
+
+impl<'a> EditMachine<'a> {
+    pub fn next_temporary(&self) -> u32 {
+        self.next_temporary
+    }
+
+    pub fn set_next_temporary(&mut self, next_temporary: u32) {
+        self.next_temporary = next_temporary;
+    }
+
+    pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
+        debug_assert!(self.traversal_is_committed());
+        debug_assert!(start < end);
+        let temp_base = self.next_temporary;
+        // debug!(
+        //     "emit: save_children_to_temporaries({}, {}, {})",
+        //     temp_base, start, end
+        // );
+        self.next_temporary = temp_base + (end - start) as u32;
+        self.emitter.push(Edit::SaveChildrenToTemporaries {
+            temp: temp_base,
+            start: start as u32,
+            end: end as u32,
+        });
+        temp_base
+    }
+
+    pub fn push_temporary(&mut self, temp: u32) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: push_temporary({})", temp);
+        self.emitter.push(Edit::PushTemporary { temp });
+        // self.emitter.push_temporary(temp);
+    }
+
+    pub fn remove_child(&mut self, child: usize) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: remove_child({})", child);
+        // self.emitter.remove_child(child as u32);
+        self.emitter.push(Edit::RemoveChild { n: child as u32 })
+    }
+
+    pub fn insert_before(&mut self) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: insert_before()");
+        // self.emitter.insert_before();
+        self.emitter.push(Edit::InsertBefore {})
+    }
+
+    pub fn ensure_string(&mut self, _string: &str) -> StringKey {
+        todo!()
+        // self.strings.ensure_string(string, &self.emitter)
+    }
+
+    pub fn set_text(&mut self, text: &'a str) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: set_text({:?})", text);
+        // self.emitter.set_text(text);
+        self.emitter.push(Edit::SetText { text });
+        // .set_text(text.as_ptr() as u32, text.len() as u32);
+    }
+
+    pub fn remove_self_and_next_siblings(&mut self) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: remove_self_and_next_siblings()");
+        self.emitter.push(Edit::RemoveSelfAndNextSiblings {});
+        // self.emitter.remove_self_and_next_siblings();
+    }
+
+    pub fn replace_with(&mut self) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: replace_with()");
+        self.emitter.push(Edit::ReplaceWith {});
+        // self.emitter.replace_with();
+    }
+
+    pub fn set_attribute(&mut self, name: &'a str, value: &'a str, is_namespaced: bool) {
+        debug_assert!(self.traversal_is_committed());
+        // todo!()
+        if name == "class" && !is_namespaced {
+            // let class_id = self.ensure_string(value);
+            // let class_id = self.ensure_string(value);
+            // debug!("emit: set_class({:?})", value);
+            // self.emitter.set_class(class_id.into());
+            self.emitter.push(Edit::SetClass { class_name: value });
+        } else {
+            self.emitter.push(Edit::SetAttribute { name, value });
+            // let name_id = self.ensure_string(name);
+            // let value_id = self.ensure_string(value);
+            // debug!("emit: set_attribute({:?}, {:?})", name, value);
+            // self.state
+            //     .emitter
+            //     .set_attribute(name_id.into(), value_id.into());
+        }
+    }
+
+    pub fn remove_attribute(&mut self, name: &'a str) {
+        // todo!("figure out how to get this working with ensure string");
+        self.emitter.push(Edit::RemoveAttribute { name });
+        // self.emitter.remove_attribute(name);
+        // debug_assert!(self.traversal_is_committed());
+        // // debug!("emit: remove_attribute({:?})", name);
+        // let name_id = self.ensure_string(name);
+        // self.emitter.remove_attribute(name_id.into());
+    }
+
+    pub fn append_child(&mut self) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: append_child()");
+        self.emitter.push(Edit::AppendChild {});
+        // self.emitter.append_child();
+    }
+
+    pub fn create_text_node(&mut self, text: &'a str) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: create_text_node({:?})", text);
+        // self.emitter.create_text_node(text);
+        self.emitter.push(Edit::CreateTextNode { text });
+    }
 
-// #[derive(serde::Serialize, serde::Deserialize)]
-pub enum Patch<'a> {
-    /// Append a vector of child nodes to a parent node id.
-    AppendChildren(NodeIdx, Vec<&'a VNode<'a>>),
+    pub fn create_element(&mut self, tag_name: &'a str) {
+        // debug_assert!(self.traversal_is_committed());
+        // debug!("emit: create_element({:?})", tag_name);
+        // let tag_name_id = self.ensure_string(tag_name);
+        self.emitter.push(Edit::CreateElement { tag_name });
+        // self.emitter.create_element(tag_name);
+        // self.emitter.create_element(tag_name_id.into());
+    }
+
+    pub fn create_element_ns(&mut self, tag_name: &'a str, ns: &'a str) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
+        // let tag_name_id = self.ensure_string(tag_name);
+        // let ns_id = self.ensure_string(ns);
+        // self.emitter.create_element_ns(tag_name, ns);
+        self.emitter.push(Edit::CreateElementNs { tag_name, ns });
+        // self.emitter
+        //     .create_element_ns(tag_name_id.into(), ns_id.into());
+    }
+
+    pub fn push_force_new_listeners(&mut self) -> bool {
+        let old = self.forcing_new_listeners;
+        self.forcing_new_listeners = true;
+        old
+    }
+
+    pub fn pop_force_new_listeners(&mut self, previous: bool) {
+        debug_assert!(self.forcing_new_listeners);
+        self.forcing_new_listeners = previous;
+    }
+
+    pub fn new_event_listener(&mut self, event: &'a str, idx: CbIdx) {
+        debug_assert!(self.traversal_is_committed());
+        self.emitter.push(Edit::NewEventListener {
+            event_type: event,
+            s: idx,
+        });
+        // todo!("Event listener not wired up yet");
+        // log::debug!("emit: new_event_listener({:?})", listener);
+        // let (a, b) = listener.get_callback_parts();
+        // debug_assert!(a != 0);
+        // // let event_id = self.ensure_string(listener.event);
+        // self.emitter.new_event_listener(listener.event.into(), a, b);
+    }
+
+    pub fn update_event_listener(&mut self, event: &'a str, idx: CbIdx) {
+        debug_assert!(self.traversal_is_committed());
+        if self.forcing_new_listeners {
+            self.new_event_listener(event, idx);
+            return;
+        }
+
+        self.emitter.push(Edit::NewEventListener {
+            event_type: event,
+            s: idx,
+        });
 
-    /// For a `node_i32`, remove all children besides the first `len`
-    TruncateChildren(NodeIdx, usize),
+        // log::debug!("emit: update_event_listener({:?})", listener);
+        // // todo!("Event listener not wired up yet");
+        // let (a, b) = listener.get_callback_parts();
+        // debug_assert!(a != 0);
+        // self.emitter.push(Edit::UpdateEventListener {
+        //     event_type: listener.event.into(),
+        //     a,
+        //     b,
+        // });
+        // self.emitter.update_event_listener(event_id.into(), a, b);
+    }
 
-    /// Replace a node with another node. This typically happens when a node's tag changes.
-    /// ex: <div> becomes <span>
-    Replace(NodeIdx, &'a VNode<'a>),
+    pub fn remove_event_listener(&mut self, event: &'a str) {
+        debug_assert!(self.traversal_is_committed());
+        self.emitter
+            .push(Edit::RemoveEventListener { event_type: event });
+        // debug!("emit: remove_event_listener({:?})", event);
+        // let _event_id = self.ensure_string(event);
+        // todo!("Event listener not wired up yet");
+        // self.emitter.remove_event_listener(event_id.into());
+    }
 
-    /// Add attributes that the new node has that the old node does not
-    AddAttributes(NodeIdx, FxHashMap<&'a str, &'a str>),
+    // #[inline]
+    // pub fn has_template(&mut self, id: CacheId) -> bool {
+    //     self.templates.contains(&id)
+    // }
 
-    /// Remove attributes that the old node had that the new node doesn't
-    RemoveAttributes(NodeIdx, Vec<&'a str>),
+    // pub fn save_template(&mut self, id: CacheId) {
+    //     debug_assert!(self.traversal_is_committed());
+    //     debug_assert!(!self.has_template(id));
+    //     // debug!("emit: save_template({:?})", id);
+    //     self.templates.insert(id);
+    //     self.emitter.save_template(id.into());
+    // }
 
-    /// Change the text of a Text node.
-    ChangeText(NodeIdx, &'a VText<'a>),
+    // pub fn push_template(&mut self, id: CacheId) {
+    //     debug_assert!(self.traversal_is_committed());
+    //     debug_assert!(self.has_template(id));
+    //     // debug!("emit: push_template({:?})", id);
+    //     self.emitter.push_template(id.into());
+    // }
 }
 
-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,
+// Keeps track of where we are moving in a DOM tree, and shortens traversal
+// paths between mutations to their minimal number of operations.
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum MoveTo {
+    /// Move from the current node up to its parent.
+    Parent,
+
+    /// Move to the current node's n^th child.
+    Child(u32),
+
+    /// Move to the current node's n^th from last child.
+    ReverseChild(u32),
+
+    /// Move to the n^th sibling. Not relative from the current
+    /// location. Absolute indexed within all of the current siblings.
+    Sibling(u32),
+
+    /// Move to the n^th from last sibling. Not relative from the current
+    /// location. Absolute indexed within all of the current siblings.
+    ReverseSibling(u32),
+
+    /// Move down to the given saved temporary child.
+    TempChild(u32),
+}
+
+#[derive(Debug)]
+pub struct Traversal {
+    uncommitted: Vec<MoveTo>,
+}
+
+impl Traversal {
+    /// Construct a new `Traversal` with its internal storage backed by the
+    /// given bump arena.
+    pub fn new() -> Traversal {
+        Traversal {
+            uncommitted: Vec::with_capacity(32),
         }
     }
+
+    /// Move the traversal up in the tree.
+    pub fn up(&mut self) {
+        match self.uncommitted.last() {
+            Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
+                self.uncommitted.pop();
+                self.uncommitted.push(MoveTo::Parent);
+            }
+            Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) | Some(MoveTo::ReverseChild(_)) => {
+                self.uncommitted.pop();
+                // And we're back at the parent.
+            }
+            _ => {
+                self.uncommitted.push(MoveTo::Parent);
+            }
+        }
+    }
+
+    /// Move the traversal down in the tree to the first child of the current
+    /// node.
+    pub fn down(&mut self) {
+        if let Some(&MoveTo::Parent) = self.uncommitted.last() {
+            self.uncommitted.pop();
+            self.sibling(0);
+        } else {
+            self.uncommitted.push(MoveTo::Child(0));
+        }
+    }
+
+    /// Move the traversal to the n^th sibling.
+    pub fn sibling(&mut self, index: usize) {
+        let index = index as u32;
+        match self.uncommitted.last_mut() {
+            Some(MoveTo::Sibling(ref mut n)) | Some(MoveTo::Child(ref mut n)) => {
+                *n = index;
+            }
+            Some(MoveTo::ReverseSibling(_)) => {
+                self.uncommitted.pop();
+                self.uncommitted.push(MoveTo::Sibling(index));
+            }
+            Some(MoveTo::TempChild(_)) | Some(MoveTo::ReverseChild(_)) => {
+                self.uncommitted.pop();
+                self.uncommitted.push(MoveTo::Child(index))
+            }
+            _ => {
+                self.uncommitted.push(MoveTo::Sibling(index));
+            }
+        }
+    }
+
+    /// Move the the n^th from last sibling.
+    pub fn reverse_sibling(&mut self, index: usize) {
+        let index = index as u32;
+        match self.uncommitted.last_mut() {
+            Some(MoveTo::ReverseSibling(ref mut n)) | Some(MoveTo::ReverseChild(ref mut n)) => {
+                *n = index;
+            }
+            Some(MoveTo::Sibling(_)) => {
+                self.uncommitted.pop();
+                self.uncommitted.push(MoveTo::ReverseSibling(index));
+            }
+            Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) => {
+                self.uncommitted.pop();
+                self.uncommitted.push(MoveTo::ReverseChild(index))
+            }
+            _ => {
+                self.uncommitted.push(MoveTo::ReverseSibling(index));
+            }
+        }
+    }
+
+    /// Go to the given saved temporary.
+    pub fn down_to_temp(&mut self, temp: u32) {
+        match self.uncommitted.last() {
+            Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
+                self.uncommitted.pop();
+            }
+            Some(MoveTo::Parent)
+            | Some(MoveTo::TempChild(_))
+            | Some(MoveTo::Child(_))
+            | Some(MoveTo::ReverseChild(_))
+            | None => {
+                // Can't remove moves to parents since we rely on their stack
+                // pops.
+            }
+        }
+        self.uncommitted.push(MoveTo::TempChild(temp));
+    }
+
+    /// Are all the traversal's moves committed? That is, are there no moves
+    /// that have *not* been committed yet?
+    #[inline]
+    pub fn is_committed(&self) -> bool {
+        // is_empty is not inlined?
+        self.uncommitted.is_empty()
+        // self.uncommitted.len() == 0
+    }
+
+    /// Commit this traversals moves and return the optimized path from the last
+    /// commit.
+    #[inline]
+    pub fn commit(&mut self) -> Moves {
+        Moves {
+            inner: self.uncommitted.drain(..),
+        }
+    }
+
+    #[inline]
+    pub fn reset(&mut self) {
+        self.uncommitted.clear();
+    }
 }
 
-pub struct PatchList<'a> {
-    patches: Vec<Patch<'a>>,
+pub struct Moves<'a> {
+    inner: std::vec::Drain<'a, MoveTo>,
+}
+
+impl Iterator for Moves<'_> {
+    type Item = MoveTo;
+
+    #[inline]
+    fn next(&mut self) -> Option<MoveTo> {
+        self.inner.next()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_traversal() {
+        fn t<F>(f: F) -> Box<dyn FnMut(&mut Traversal)>
+        where
+            F: 'static + FnMut(&mut Traversal),
+        {
+            Box::new(f) as _
+        }
+
+        for (mut traverse, expected_moves) in vec![
+            (
+                t(|t| {
+                    t.down();
+                }),
+                vec![MoveTo::Child(0)],
+            ),
+            (
+                t(|t| {
+                    t.up();
+                }),
+                vec![MoveTo::Parent],
+            ),
+            (
+                t(|t| {
+                    t.sibling(42);
+                }),
+                vec![MoveTo::Sibling(42)],
+            ),
+            (
+                t(|t| {
+                    t.down();
+                    t.up();
+                }),
+                vec![],
+            ),
+            (
+                t(|t| {
+                    t.down();
+                    t.sibling(2);
+                    t.up();
+                }),
+                vec![],
+            ),
+            (
+                t(|t| {
+                    t.down();
+                    t.sibling(3);
+                }),
+                vec![MoveTo::Child(3)],
+            ),
+            (
+                t(|t| {
+                    t.down();
+                    t.sibling(4);
+                    t.sibling(8);
+                }),
+                vec![MoveTo::Child(8)],
+            ),
+            (
+                t(|t| {
+                    t.sibling(1);
+                    t.sibling(1);
+                }),
+                vec![MoveTo::Sibling(1)],
+            ),
+            (
+                t(|t| {
+                    t.reverse_sibling(3);
+                }),
+                vec![MoveTo::ReverseSibling(3)],
+            ),
+            (
+                t(|t| {
+                    t.down();
+                    t.reverse_sibling(3);
+                }),
+                vec![MoveTo::ReverseChild(3)],
+            ),
+            (
+                t(|t| {
+                    t.down();
+                    t.reverse_sibling(3);
+                    t.up();
+                }),
+                vec![],
+            ),
+            (
+                t(|t| {
+                    t.down();
+                    t.reverse_sibling(3);
+                    t.reverse_sibling(6);
+                }),
+                vec![MoveTo::ReverseChild(6)],
+            ),
+            (
+                t(|t| {
+                    t.up();
+                    t.reverse_sibling(3);
+                    t.reverse_sibling(6);
+                }),
+                vec![MoveTo::Parent, MoveTo::ReverseSibling(6)],
+            ),
+            (
+                t(|t| {
+                    t.up();
+                    t.sibling(3);
+                    t.sibling(6);
+                }),
+                vec![MoveTo::Parent, MoveTo::Sibling(6)],
+            ),
+            (
+                t(|t| {
+                    t.sibling(3);
+                    t.sibling(6);
+                    t.up();
+                }),
+                vec![MoveTo::Parent],
+            ),
+            (
+                t(|t| {
+                    t.reverse_sibling(3);
+                    t.reverse_sibling(6);
+                    t.up();
+                }),
+                vec![MoveTo::Parent],
+            ),
+            (
+                t(|t| {
+                    t.down();
+                    t.down_to_temp(3);
+                }),
+                vec![MoveTo::Child(0), MoveTo::TempChild(3)],
+            ),
+            (
+                t(|t| {
+                    t.down_to_temp(3);
+                    t.sibling(5);
+                }),
+                vec![MoveTo::Child(5)],
+            ),
+            (
+                t(|t| {
+                    t.down_to_temp(3);
+                    t.reverse_sibling(5);
+                }),
+                vec![MoveTo::ReverseChild(5)],
+            ),
+            (
+                t(|t| {
+                    t.down_to_temp(3);
+                    t.up();
+                }),
+                vec![],
+            ),
+            (
+                t(|t| {
+                    t.sibling(2);
+                    t.up();
+                    t.down_to_temp(3);
+                }),
+                vec![MoveTo::Parent, MoveTo::TempChild(3)],
+            ),
+            (
+                t(|t| {
+                    t.up();
+                    t.down_to_temp(3);
+                }),
+                vec![MoveTo::Parent, MoveTo::TempChild(3)],
+            ),
+        ] {
+            let mut traversal = Traversal::new();
+            traverse(&mut traversal);
+            let actual_moves: Vec<_> = traversal.commit().collect();
+            assert_eq!(actual_moves, expected_moves);
+        }
+    }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct StringKey(u32);
+
+impl From<StringKey> for u32 {
+    #[inline]
+    fn from(key: StringKey) -> u32 {
+        key.0
+    }
 }

+ 11 - 9
packages/core/src/scope.rs

@@ -1,8 +1,9 @@
+use crate::component::ScopeIdx;
 use crate::context::hooks::Hook;
 use crate::innerlude::*;
 use crate::nodes::VNode;
 use bumpalo::Bump;
-use generational_arena::Index;
+// use generational_arena::ScopeIdx;
 
 use std::{
     any::TypeId,
@@ -32,7 +33,7 @@ pub struct Scope {
     pub hook_arena: typed_arena::Arena<Hook>,
 
     // Map to the parent
-    pub parent: Option<Index>,
+    pub parent: Option<ScopeIdx>,
 
     pub frames: ActiveFrame,
 
@@ -50,7 +51,7 @@ pub struct Scope {
 
 impl Scope {
     // create a new scope from a function
-    pub fn new<'a, P1, P2: 'static>(f: FC<P1>, props: P1, parent: Option<Index>) -> Self {
+    pub fn new<'a, P1, P2: 'static>(f: FC<P1>, props: P1, parent: Option<ScopeIdx>) -> Self {
         let hook_arena = typed_arena::Arena::new();
         let hooks = RefCell::new(Vec::new());
 
@@ -288,7 +289,7 @@ mod tests {
     fn test_scope() {
         let example: FC<()> = |ctx, props| {
             use crate::builder::*;
-            ctx.render(|b| div(b).child(text("a")).finish())
+            ctx.render(|ctx| div(ctx.bump()).child(text("a")).finish())
         };
 
         let props = ();
@@ -318,7 +319,8 @@ mod tests {
 
         let childprops: ExampleProps<'a> = ExampleProps { name: content };
         // let childprops: ExampleProps<'a> = ExampleProps { name: content };
-        ctx.render(move |b: &'a Bump| {
+        ctx.render(move |ctx| {
+            let b = ctx.bump();
             div(b)
                 .child(text(props.name))
                 // .child(text(props.name))
@@ -336,8 +338,8 @@ mod tests {
     }
 
     fn child_example<'b>(ctx: Context<'b>, props: &'b ExampleProps) -> DomTree {
-        ctx.render(move |b| {
-            div(b)
+        ctx.render(move |ctx| {
+            div(ctx.bump())
                 .child(text(props.name))
                 //
                 .finish()
@@ -346,8 +348,8 @@ mod tests {
 
     static CHILD: FC<ExampleProps> = |ctx, props: &'_ ExampleProps| {
         // todo!()
-        ctx.render(move |b| {
-            div(b)
+        ctx.render(move |ctx| {
+            div(ctx.bump())
                 .child(text(props.name))
                 //
                 .finish()

+ 12 - 24
packages/core/src/virtual_dom.rs

@@ -1,15 +1,10 @@
 // use crate::{changelist::EditList, nodes::VNode};
-use crate::{
-    changelist::{self, EditList},
-    dodriodiff::DiffMachine,
-};
-use crate::{events::EventTrigger, innerlude::*};
 
+use crate::innerlude::*;
 use bumpalo::Bump;
-
-use generational_arena::{Arena, Index};
+use generational_arena::Arena;
 use std::{
-    any::{self, TypeId},
+    any::TypeId,
     borrow::BorrowMut,
     cell::{RefCell, UnsafeCell},
     collections::{vec_deque, VecDeque},
@@ -28,7 +23,7 @@ pub struct VirtualDom {
 
     /// The index of the root component.
     /// Will not be ready if the dom is fresh
-    base_scope: Index,
+    base_scope: ScopeIdx,
 
     event_queue: Rc<RefCell<VecDeque<LifecycleEvent>>>,
 
@@ -98,11 +93,7 @@ impl VirtualDom {
 
         component.run::<()>();
 
-        diff_machine.diff_node(
-            component.old_frame(),
-            component.new_frame(),
-            Some(self.base_scope),
-        );
+        diff_machine.diff_node(component.old_frame(), component.new_frame());
 
         Ok(diff_machine.consume())
     }
@@ -173,11 +164,7 @@ impl VirtualDom {
 
         component.run::<()>();
 
-        diff_machine.diff_node(
-            component.old_frame(),
-            component.new_frame(),
-            Some(self.base_scope),
-        );
+        diff_machine.diff_node(component.old_frame(), component.new_frame());
         // diff_machine.diff_node(
         //     component.old_frame(),
         //     component.new_frame(),
@@ -252,7 +239,7 @@ pub struct LifecycleEvent {
 pub enum LifecycleType {
     // Component needs to be mounted, but its scope doesn't exist yet
     Mount {
-        to: Index,
+        to: ScopeIdx,
         under: usize,
         props: Box<dyn std::any::Any>,
     },
@@ -260,17 +247,17 @@ pub enum LifecycleType {
     // Parent was evalauted causing new props to generate
     PropsChanged {
         props: Box<dyn std::any::Any>,
-        component: Index,
+        component: ScopeIdx,
     },
 
     // Hook for the subscription API
     Callback {
-        component: Index,
+        component: ScopeIdx,
     },
 }
 
 impl LifecycleEvent {
-    fn index(&self) -> Option<Index> {
+    fn index(&self) -> Option<ScopeIdx> {
         match &self.event_type {
             LifecycleType::Mount {
                 to: _,
@@ -290,8 +277,9 @@ mod tests {
     #[test]
     fn start_dom() {
         let mut dom = VirtualDom::new(|ctx, props| {
-            ctx.render(|bump| {
+            ctx.render(|ctx| {
                 use crate::builder::*;
+                let bump = ctx.bump();
                 div(bump).child(text("hello,    world")).finish()
             })
         });

+ 1 - 5
packages/web/src/interpreter.rs

@@ -1,9 +1,6 @@
 use std::{borrow::Borrow, fmt::Debug, sync::Arc};
 
-use dioxus_core::{
-    changelist::{CbIdx, Edit},
-    events::{EventTrigger, MouseEvent, VirtualEvent},
-};
+use dioxus_core::events::{EventTrigger, MouseEvent, VirtualEvent};
 use fxhash::FxHashMap;
 use log::debug;
 use wasm_bindgen::{closure::Closure, JsCast};
@@ -91,7 +88,6 @@ impl EventDelegater {
                     .and_then(|v| v.parse().ok());
 
                 if let (Some(gi_id), Some(gi_gen), Some(li_idx)) = (gi_id, gi_gen, li_idx) {
-                    
                     // Call the trigger
                     trigger.0.as_ref()(EventTrigger::new(
                         virtual_event_from_websys_event(event),