Bläddra i källkod

Feat: re-enable stack machine approach

Jonathan Kelley 4 år sedan
förälder
incheckning
e3ede7f

+ 1 - 0
.vscode/spellright.dict

@@ -25,3 +25,4 @@ Derefing
 rei
 RefMut
 diffed
+datafetching

+ 4 - 2
packages/cli/src/logging.rs

@@ -31,12 +31,14 @@ pub fn set_up_logging() {
         })
         // set the default log level. to filter out verbose log messages from dependencies, set
         // this to Warn and overwrite the log level for your crate.
-        .level(log::LevelFilter::Warn)
+        .level(log::LevelFilter::Info)
+        // .level(log::LevelFilter::Warn)
         // change log levels for individual modules. Note: This looks for the record's target
         // field which defaults to the module path but can be overwritten with the `target`
         // parameter:
         // `info!(target="special_target", "This log message is about special_target");`
-        .level_for("dioxus", log::LevelFilter::Info)
+        // .level_for("dioxus", log::LevelFilter::Debug)
+        // .level_for("dioxus", log::LevelFilter::Info)
         // .level_for("pretty_colored", log::LevelFilter::Trace)
         // output to stdout
         .chain(std::io::stdout())

+ 4 - 1
packages/core/Cargo.toml

@@ -31,5 +31,8 @@ id-arena = "2.2.1"
 thiserror = "1.0.23"
 fxhash = "0.2.1"
 longest-increasing-subsequence = "0.1.0"
-serde = "1.0.123"
+serde = { version = "1.0.123", features = ["derive"] }
+log = "0.4.14"
+pretty_env_logger = "0.4.0"
+ouroboros = "0.8.0"
 # hashbrown = { version = "0.9.1", features = ["bumpalo"] }

+ 25 - 12
packages/core/src/changelist.rs

@@ -19,6 +19,8 @@
 //!
 //!
 
+use bumpalo::Bump;
+
 use crate::innerlude::{Listener, VirtualDom};
 
 /// The `Edit` represents a single modifcation of the renderer tree.
@@ -31,6 +33,7 @@ use crate::innerlude::{Listener, VirtualDom};
 ///
 ///
 ///
+#[derive(Debug)]
 pub enum Edit<'d> {
     SetText { text: &'d str },
     RemoveSelfAndNextSiblings {},
@@ -57,14 +60,23 @@ pub enum Edit<'d> {
 }
 
 pub struct EditList<'src> {
-    traversal: Traversal,
+    pub traversal: Traversal,
     next_temporary: u32,
     forcing_new_listeners: bool,
-    emitter: Vec<Edit<'src>>,
+    pub emitter: Vec<Edit<'src>>,
 }
 
-/// Traversal methods.
-impl EditList<'_> {
+impl<'b> EditList<'b> {
+    pub fn new(bump: &'b Bump) -> Self {
+        Self {
+            traversal: Traversal::new(),
+            next_temporary: 0,
+            forcing_new_listeners: false,
+            emitter: Vec::new(),
+        }
+    }
+
+    /// Traversal methods.
     pub fn go_down(&mut self) {
         self.traversal.down();
     }
@@ -98,36 +110,37 @@ impl EditList<'_> {
 
     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 => {
-                    // debug!("emit: pop");
+                    log::debug!("emit: pop");
                     self.emitter.push(Edit::Pop {});
                     // self.emitter.pop();
                 }
                 MoveTo::Child(n) => {
-                    // debug!("emit: push_child({})", n);
+                    log::debug!("emit: push_child({})", n);
                     self.emitter.push(Edit::PushChild { n });
                 }
                 MoveTo::ReverseChild(n) => {
-                    // debug!("emit: push_reverse_child({})", n);
+                    log::debug!("emit: push_reverse_child({})", n);
                     self.emitter.push(Edit::PushReverseChild { n });
                     // self.emitter.push_reverse_child(n);
                 }
                 MoveTo::Sibling(n) => {
-                    // debug!("emit: pop_push_child({})", n);
+                    log::debug!("emit: pop_push_child({})", n);
                     self.emitter.push(Edit::PopPushChild { n });
                     // self.emitter.pop_push_child(n);
                 }
                 MoveTo::ReverseSibling(n) => {
-                    // debug!("emit: pop_push_reverse_child({})", n);
+                    log::debug!("emit: pop_push_reverse_child({})", n);
                     self.emitter.push(Edit::PopPushReverseChild { n });
                 }
                 MoveTo::TempChild(temp) => {
-                    // debug!("emit: push_temporary({})", temp);
+                    log::debug!("emit: push_temporary({})", temp);
                     self.emitter.push(Edit::PushTemporary { temp });
                     // self.emitter.push_temporary(temp);
                 }
@@ -477,8 +490,8 @@ impl Traversal {
     #[inline]
     pub fn is_committed(&self) -> bool {
         // is_empty is not inlined?
-        // self.uncommitted.is_empty()
-        self.uncommitted.len() == 0
+        self.uncommitted.is_empty()
+        // self.uncommitted.len() == 0
     }
 
     /// Commit this traversals moves and return the optimized path from the last

+ 2 - 0
packages/core/src/diff.rs

@@ -238,6 +238,8 @@ mod tests {
             .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,

+ 20 - 5
packages/core/src/dodriodiff.rs

@@ -1,3 +1,4 @@
+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`.
 ///
@@ -36,7 +37,7 @@ use fxhash::{FxHashMap, FxHashSet};
 use generational_arena::Index;
 
 use crate::{
-    changelist::EditList,
+    changelist::{Edit, EditList},
     innerlude::{Attribute, Listener, Scope, VElement, VNode, VText},
     virtual_dom::LifecycleEvent,
 };
@@ -55,8 +56,8 @@ use std::cmp::Ordering;
 /// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components
 /// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via
 /// subscriptions and props changes.
-struct DiffingMachine<'a> {
-    change_list: &'a mut EditList<'a>,
+pub struct DiffMachine<'a> {
+    pub change_list: EditList<'a>,
     immediate_queue: Vec<Index>,
     diffed: FxHashSet<Index>,
     need_to_diff: FxHashSet<Index>,
@@ -67,8 +68,21 @@ enum NeedToDiff {
     Subscription,
 }
 
-impl<'a> DiffingMachine<'a> {
-    fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
+impl<'a> DiffMachine<'a> {
+    pub fn new(bump: &'a Bump) -> Self {
+        Self {
+            change_list: EditList::new(bump),
+            immediate_queue: Vec::new(),
+            diffed: FxHashSet::default(),
+            need_to_diff: FxHashSet::default(),
+        }
+    }
+
+    pub fn consume(self) -> Vec<Edit<'a>> {
+        self.change_list.emitter
+    }
+
+    pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
         /*
         For each valid case, we "commit traversal", meaning we save this current position in the tree.
         Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
@@ -107,6 +121,7 @@ impl<'a> DiffingMachine<'a> {
             // 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();

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

@@ -65,13 +65,13 @@
 //! - dioxus-liveview (SSR + StringRenderer)
 //!
 
-// pub mod changelist; // An "edit phase" described by transitions and edit operations
+pub mod changelist; // An "edit phase" described by transitions and edit operations
 pub mod component; // Logic for extending FC
 pub mod context; // Logic for providing hook + context functionality to user components
 pub mod debug_renderer; // Test harness for validating that lifecycles and diffs work appropriately
-pub mod diff;
-pub mod patch; // The diffing algorithm that builds the ChangeList
-               // pub mod dodriodiff; // The diffing algorithm that builds the ChangeList
+                        // pub mod diff;
+                        // pub mod patch; // The diffing algorithm that builds the ChangeList
+pub mod dodriodiff; // The diffing algorithm that builds the ChangeList
 pub mod error; // Error type we expose to the renderers
 pub mod events; // Manages the synthetic event API
 pub mod hooks; // Built-in hooks
@@ -99,7 +99,7 @@ pub(crate) mod innerlude {
     // pub use nodes::iterables::IterableNodes;
     /// This type alias is an internal way of abstracting over the static functions that represent components.
 
-    pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> VNode<'a>;
+    pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> &'a VNode<'a>;
     // pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
 
     // TODO @Jon, fix this
@@ -139,7 +139,8 @@ pub mod prelude {
     pub use dioxus_core_macro::fc;
     pub use dioxus_html_2::html;
 
-    pub use crate::diff::DiffMachine;
+    // pub use crate::diff::DiffMachine;
+    pub use crate::dodriodiff::DiffMachine;
 
     pub use crate::hooks::*;
 }

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

@@ -192,7 +192,7 @@ mod velement {
     /// Keys must be unique among siblings.
     ///
     /// If any sibling is keyed, then they all must be keyed.
-    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
+    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
     pub struct NodeKey(pub(crate) u32);
 
     impl Default for NodeKey {

+ 142 - 57
packages/core/src/scope.rs

@@ -3,11 +3,82 @@ use crate::innerlude::*;
 use crate::nodes::VNode;
 use bumpalo::Bump;
 use generational_arena::Index;
+use owning_ref::StableAddress;
 use std::{
-    any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData,
-    sync::atomic::AtomicUsize, todo,
+    any::TypeId,
+    borrow::{Borrow, BorrowMut},
+    cell::{RefCell, UnsafeCell},
+    future::Future,
+    marker::PhantomData,
+    ops::{Deref, DerefMut},
+    sync::atomic::AtomicUsize,
+    todo,
 };
 
+pub struct BumpContainer(pub UnsafeCell<Bump>);
+impl BumpContainer {
+    fn new() -> Self {
+        Self(UnsafeCell::new(Bump::new()))
+    }
+}
+
+impl Deref for BumpContainer {
+    type Target = Bump;
+
+    fn deref(&self) -> &Self::Target {
+        todo!()
+        // self.0.borrow()
+    }
+}
+impl DerefMut for BumpContainer {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        todo!()
+        // self.0.borrow_mut()
+    }
+}
+unsafe impl StableAddress for BumpContainer {}
+
+#[ouroboros::self_referencing]
+pub struct BumpFrame {
+    pub bump: BumpContainer,
+
+    #[covariant]
+    #[borrows(bump)]
+    pub head_node: &'this VNode<'this>,
+}
+
+pub struct ActiveFrame {
+    pub idx: AtomicUsize,
+    pub frames: [BumpFrame; 2],
+}
+
+impl ActiveFrame {
+    fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
+        Self {
+            idx: 0.into(),
+            frames: [a, b],
+        }
+    }
+
+    fn next(&self) -> &BumpFrame {
+        self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
+        let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed);
+        match cur % 1 {
+            1 => &self.frames[1],
+            0 => &self.frames[0],
+            _ => unreachable!("mod cannot by non-zero"),
+        }
+    }
+    // fn next(&self) -> &BumpFrame {
+    //     self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
+    //     let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed);
+    //     match cur % 2_usize {
+    //         1 => &self.frames[1],
+    //         0 => &self.frames[0],
+    //     }
+    // }
+}
+
 /// Every component in Dioxus is represented by a `Scope`.
 ///
 /// Scopes contain the state for hooks, the component's props, and other lifecycle information.
@@ -26,16 +97,12 @@ pub struct Scope {
     pub parent: Option<Index>,
 
     // todo, do better with the active frame stuff
-    pub frames: [Bump; 2],
-
     // somehow build this vnode with a lifetime tied to self
     // This root node has  "static" lifetime, but it's really not static.
     // It's goverened by the oldest of the two frames and is switched every time a new render occurs
     // Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
     // ! do not copy this reference are things WILL break !
-    pub root_node: *mut VNode<'static>,
-
-    pub active_frame: ActiveFrame,
+    pub frames: ActiveFrame,
 
     // IE Which listeners need to be woken up?
     pub listeners: Vec<Box<dyn Fn()>>,
@@ -45,19 +112,19 @@ pub struct Scope {
     pub caller: *const i32,
 }
 
-pub enum ActiveFrame {
-    First,
-    Second,
-}
+// pub enum ActiveFrame {
+//     First,
+//     Second,
+// }
 
-impl ActiveFrame {
-    fn next(&mut self) {
-        match self {
-            ActiveFrame::First => *self = ActiveFrame::Second,
-            ActiveFrame::Second => *self = ActiveFrame::First,
-        }
-    }
-}
+// impl ActiveFrame {
+//     fn next(&mut self) {
+//         match self {
+//             ActiveFrame::First => *self = ActiveFrame::Second,
+//             ActiveFrame::Second => *self = ActiveFrame::First,
+//         }
+//     }
+// }
 
 impl Scope {
     // create a new scope from a function
@@ -72,26 +139,31 @@ impl Scope {
 
         // Create the two buffers the componetn will render into
         // There will always be an "old" and "new"
-        let frames = [Bump::new(), Bump::new()];
 
         let listeners = Vec::new();
 
-        let active_frame = ActiveFrame::First;
+        let new_frame = BumpFrameBuilder {
+            bump: BumpContainer::new(),
+            head_node_builder: |bump| bump.alloc(VNode::text("")),
+        }
+        .build();
 
-        let new = frames[0].alloc(VNode::Text(VText::new("")));
+        let old_frame = BumpFrameBuilder {
+            bump: BumpContainer::new(),
+            head_node_builder: |bump| bump.alloc(VNode::text("")),
+        }
+        .build();
 
-        let cur_node = new as *mut _;
+        let frames = ActiveFrame::from_frames(old_frame, new_frame);
 
         Self {
             hook_arena,
             hooks,
             props_type,
             caller,
-            active_frame,
+            frames,
             listeners,
             parent,
-            frames,
-            root_node: cur_node,
         }
     }
 
@@ -99,37 +171,50 @@ impl Scope {
     /// This function downcasts the function pointer based on the stored props_type
     ///
     /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
-    pub(crate) fn run<'a, P: Properties + ?Sized>(&self, props: &'a P) {
-        let bump = match self.active_frame {
-            // If the active frame is the first, then we need to bump into the second
-            ActiveFrame::First => &self.frames[1],
-            // If the active frame is the second, then we need to bump into the first
-            ActiveFrame::Second => &self.frames[0],
-        }; // n.b, there might be a better way of doing this active frame stuff - perhaps swapping
-
-        let ctx = Context {
-            scope: &*self,
-            _p: PhantomData {},
-            arena: &self.hook_arena,
-            hooks: &self.hooks,
-            idx: 0.into(),
-            bump,
-        };
-
-        /*
-        SAFETY ALERT
-
-        This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
-        We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
-        we transmute the function back using the props as reference.
-
-        we could do a better check to make sure that the TypeID is correct before casting
-        --
-        This is safe because we check that the generic type matches before casting.
-        */
-        let caller = unsafe { std::mem::transmute::<*const i32, FC<P>>(self.caller) };
-        let new_nodes = caller(ctx, props);
-        let old_nodes: &mut VNode<'static> = unsafe { &mut *self.root_node };
+    pub(crate) fn run<'a, 'bump, P: Properties + ?Sized>(&'bump mut self, props: &'a P) {
+        // I really wanted to do this safely, but I don't think we can.
+        // We want to reset the bump before writing into it. This requires &mut to the bump
+        // Ouroborous lets us borrow with self, but the heads (IE the source) cannot be changed while the ref is live
+
+        // n.b, there might be a better way of doing this active frame stuff - perhaps swapping
+        let frame = self.frames.next();
+
+        frame.with_bump(|bump_container| {
+            let bump: &mut Bump = unsafe { &mut *bump_container.0.get() };
+            bump.reset();
+
+            let bump = &*bump;
+
+            let ctx: Context<'bump> = Context {
+                scope: &*self,
+                _p: PhantomData {},
+                arena: &self.hook_arena,
+                hooks: &self.hooks,
+                idx: 0.into(),
+                bump,
+            };
+
+            /*
+            SAFETY ALERT
+
+            This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
+            We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
+            we transmute the function back using the props as reference.
+
+            we could do a better check to make sure that the TypeID is correct before casting
+            --
+            This is safe because we check that the generic type matches before casting.
+            */
+            let caller = unsafe { std::mem::transmute::<*const i32, FC<P>>(self.caller) };
+            let nodes: &'bump VNode  = caller(ctx, props);
+        });
+
+        // let new_nodes = caller(ctx, props);
+        // let r = new_nodes as *const _;
+        // self.old_root = self.new_root;
+        // self.new_root = new_nodes as *const _;
+
+        // let old_nodes: &mut VNode<'static> = unsafe { &mut *self.root_node };
 
         // TODO: Iterate through the new nodes
         // move any listeners into ourself

+ 87 - 69
packages/core/src/virtual_dom.rs

@@ -1,12 +1,14 @@
 // use crate::{changelist::EditList, nodes::VNode};
-use crate::nodes::VNode;
+use crate::{dodriodiff::DiffMachine, nodes::VNode};
 use crate::{events::EventTrigger, innerlude::*};
 use any::Any;
 use bumpalo::Bump;
 use generational_arena::{Arena, Index};
 use std::{
     any::{self, TypeId},
+    borrow::BorrowMut,
     cell::{RefCell, UnsafeCell},
+    collections::{vec_deque, VecDeque},
     future::Future,
     marker::PhantomData,
     rc::Rc,
@@ -24,7 +26,7 @@ pub struct VirtualDom {
     /// The index of the root component.
     base_scope: Index,
 
-    event_queue: Rc<RefCell<Vec<LifecycleEvent>>>,
+    event_queue: Rc<RefCell<VecDeque<LifecycleEvent>>>,
 
     // Mark the root props with P, even though they're held by the root component
     // This is done so we don't have a "generic" vdom, making it easier to hold references to it, especially when the holders
@@ -65,7 +67,7 @@ impl VirtualDom {
         let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props);
 
         // Create an event queue with a mount for the base scope
-        let event_queue = Rc::new(RefCell::new(vec![first_event]));
+        let event_queue = Rc::new(RefCell::new(vec![first_event].into_iter().collect()));
 
         let _root_prop_type = TypeId::of::<P>();
 
@@ -84,12 +86,15 @@ impl VirtualDom {
             return Err(Error::WrongProps);
         }
 
-        self.event_queue.borrow_mut().push(LifecycleEvent {
-            event_type: LifecycleType::PropsChanged {
-                props: Box::new(new_props),
-            },
-            index: self.base_scope,
-        });
+        self.event_queue
+            .as_ref()
+            .borrow_mut()
+            .push_back(LifecycleEvent {
+                event_type: LifecycleType::PropsChanged {
+                    props: Box::new(new_props),
+                },
+                component_index: self.base_scope,
+            });
 
         Ok(())
     }
@@ -103,8 +108,13 @@ impl VirtualDom {
     /// Update the root props, and progress
     /// Takes a bump arena to allocate into, making the diff phase as fast as possible
     pub fn progress(&mut self) -> Result<()> {
-        let event = self.event_queue.borrow_mut().pop().ok_or(Error::NoEvent)?;
-        process_event(&mut self.components, event)
+        let event = self
+            .event_queue
+            .as_ref()
+            .borrow_mut()
+            .pop_front()
+            .ok_or(Error::NoEvent)?;
+        self.process_event(event)
     }
 
     /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
@@ -151,85 +161,93 @@ impl VirtualDom {
         // Prop updates take prescedence over subscription updates
         // Run all prop updates *first* as they will cascade into children.
         // *then* run the non-prop updates that were not already covered by props
-        let mut events = self.event_queue.borrow_mut();
 
-        // for now, just naively process each event in the queue
-        for event in events.drain(..) {
-            process_event(&mut self.components, event)?;
+        let mut affected_components = Vec::new();
+        // It's essentially draining the vec, but with some dancing to release the RefMut
+        // We also want to be able to push events into the queue from processing the event
+        while let Some(event) = {
+            let new_evt = self.event_queue.as_ref().borrow_mut().pop_front();
+            new_evt
+        } {
+            affected_components.push(event.component_index);
+            self.process_event(event)?;
         }
 
-        // todo!()
+        let diff_bump = Bump::new();
+        let diff_machine = DiffMachine::new(&diff_bump);
+
         Ok(())
     }
 
     pub async fn progress_completely(&mut self) -> Result<()> {
         Ok(())
     }
-}
-
-/// Using mutable access to the Virtual Dom, progress a given lifecycle event
-///
-///
-///
-///
-///
-///
-fn process_event(
-    // dom: &mut VirtualDom<P>,
-    components: &mut Arena<Scope>,
-    LifecycleEvent { index, event_type }: LifecycleEvent,
-) -> Result<()> {
-    let scope = components.get(index).ok_or(Error::NoEvent)?;
-
-    match event_type {
-        // Component needs to be mounted to the virtual dom
-        LifecycleType::Mount { to, under, props } => {
-            if let Some(other) = to {
-                // mount to another component
-            } else {
-                // mount to the root
+    /// Using mutable access to the Virtual Dom, progress a given lifecycle event
+    ///
+    ///
+    ///
+    ///
+    ///
+    ///
+    fn process_event(
+        &mut self,
+        LifecycleEvent {
+            component_index: index,
+            event_type,
+        }: LifecycleEvent,
+    ) -> Result<()> {
+        let scope = self.components.get_mut(index).ok_or(Error::NoEvent)?;
+
+        match event_type {
+            // Component needs to be mounted to the virtual dom
+            LifecycleType::Mount { to, under, props } => {
+                if let Some(other) = to {
+                    // mount to another component
+                } else {
+                    // mount to the root
+                }
+
+                let g = props.as_ref();
+                scope.run(g);
+                // scope.run(runner, props, dom);
             }
 
-            let g = props.as_ref();
-            scope.run(g);
-            // scope.run(runner, props, dom);
-        }
+            // The parent for this component generated new props and the component needs update
+            LifecycleType::PropsChanged { props } => {
+                //
+            }
 
-        // The parent for this component generated new props and the component needs update
-        LifecycleType::PropsChanged { props } => {
-            //
-        }
+            // Component was successfully mounted to the dom
+            LifecycleType::Mounted {} => {
+                //
+            }
 
-        // Component was successfully mounted to the dom
-        LifecycleType::Mounted {} => {
-            //
-        }
+            // Component was removed from the DOM
+            // Run any destructors and cleanup for the hooks and the dump the component
+            LifecycleType::Removed {} => {
+                let f = self.components.remove(index);
+                // let f = dom.components.remove(index);
+            }
 
-        // Component was removed from the DOM
-        // Run any destructors and cleanup for the hooks and the dump the component
-        LifecycleType::Removed {} => {
-            let f = components.remove(index);
-            // let f = dom.components.remove(index);
-        }
+            // Component was messaged via the internal subscription service
+            LifecycleType::Messaged => {
+                //
+            }
 
-        // Component was messaged via the internal subscription service
-        LifecycleType::Messaged => {
+            // Event from renderer was fired with a given listener ID
             //
-        }
+            LifecycleType::Callback { listener_id } => {}
 
-        // Event from renderer was fired with a given listener ID
-        //
-        LifecycleType::Callback { listener_id } => {}
+            // Run any post-render callbacks on a component
+            LifecycleType::Rendered => {}
+        }
 
-        // Run any post-render callbacks on a component
-        LifecycleType::Rendered => {}
+        Ok(())
     }
-
-    Ok(())
 }
 
 pub struct LifecycleEvent {
-    pub index: Index,
+    pub component_index: Index,
     pub event_type: LifecycleType,
 }
 
@@ -265,7 +283,7 @@ impl LifecycleEvent {
         props: P,
     ) -> Self {
         Self {
-            index: which,
+            component_index: which,
             event_type: LifecycleType::Mount {
                 to,
                 under,

+ 3 - 0
packages/web/Cargo.toml

@@ -17,6 +17,8 @@ futures = "0.3.12"
 wasm-logger = "0.2.0"
 log = "0.4.14"
 fxhash = "0.2.1"
+pretty_env_logger = "0.4.0"
+console_error_panic_hook = "0.1.6"
 # html-validation = { path = "../html-validation", version = "0.1.1" }
 
 [dependencies.web-sys]
@@ -36,6 +38,7 @@ features = [
     "MouseEvent",
     "InputEvent",
     "DocumentType",
+    "CharacterData",
 ]
 
 [profile.release]

+ 39 - 34
packages/web/examples/basic.rs

@@ -8,42 +8,47 @@ use dioxus_web::*;
 
 fn main() {
     wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
-    // log::debug!("Hello world, from the app");
+    console_error_panic_hook::set_once();
     WebsysRenderer::simple_render(html! {
-
-        // Body
-        <div class="flex items-center justify-center flex-col">
-            <div class="flex items-center justify-center">
-                <div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
-                    // Title
-                    <div class="font-bold text-xl">
-                        // {format!("Fibonacci Calculator: n = {}",n)}
-                        "Jon's awesome site!!11"
-                    </div>
-
-                    // Subtext / description
-                    <div class="text-sm text-gray-500">
-                        // {format!("Calculated in {} nanoseconds",duration)}
-                        // {format!("Calculated in {} nanoseconds",duration)}
-                        "He worked so hard on it :)"
-                    </div>
-
-                    <div class="flex flex-row items-center justify-center mt-6">
-                        // Main number
-                        <div class="font-medium text-6xl">
-                            "1337"
-                        </div>
-                    </div>
-
-                    // Try another
-                    <div class="flex flex-row justify-between mt-6">
-                        // <a href=format!("http://localhost:8080/fib/{}", other_fib_to_try) class="underline">
-                            "Legit made my own React"
-                        // </a>
-                    </div>
-                </div>
+        <div>
+            <div class="flex items-center justify-center flex-col">
+                <div class="font-bold text-xl"> "Count is {}" </div>
+                <button onclick={move |_| log::info!("button1 clicked!")}> "increment" </button>
+                <button onclick={move |_| log::info!("button2 clicked!")}> "decrement" </button>
             </div>
         </div>
-
     });
+    // WebsysRenderer::simple_render(html! {
+    //     <div>
+    //         <div class="flex items-center justify-center flex-col">
+    //             <div class="flex items-center justify-center">
+    //                 <div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
+    //                     // Title
+    //                     <div class="font-bold text-xl">
+    //                         "Jon's awesome site!!11"
+    //                     </div>
+
+    //                     // Subtext / description
+    //                     <div class="text-sm text-gray-500">
+    //                         "He worked so hard on it :)"
+    //                     </div>
+
+    //                     <div class="flex flex-row items-center justify-center mt-6">
+    //                         // Main number
+    //                         <div class="font-medium text-6xl">
+    //                             "1337"
+    //                         </div>
+    //                     </div>
+
+    //                     // Try another
+    //                     <div class="flex flex-row justify-between mt-6">
+    //                         // <a href=format!("http://localhost:8080/fib/{}", other_fib_to_try) class="underline">
+    //                             "Legit made my own React"
+    //                         // </a>
+    //                     </div>
+    //                 </div>
+    //             </div>
+    //         </div>
+    //     </div>
+    // });
 }

+ 274 - 8
packages/web/src/interpreter.rs

@@ -1,17 +1,16 @@
-// use crate::cached_set::CacheId;
-// use crate::{Element, EventsTrampoline};
+use dioxus_core::changelist::Edit;
 use fxhash::FxHashMap;
 use log::{debug, info, log};
 use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{window, Document, Element, Event, Node};
 
 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
-pub(crate) struct CacheId(u32);
+pub struct CacheId(u32);
 
 #[derive(Debug)]
-pub(crate) struct ChangeListInterpreter {
+pub struct PatchMachine {
     container: Element,
-    stack: Stack,
+    pub stack: Stack,
     temporaries: FxHashMap<u32, Node>,
     templates: FxHashMap<CacheId, Node>,
     callback: Option<Closure<dyn FnMut(&Event)>>,
@@ -19,7 +18,7 @@ pub(crate) struct ChangeListInterpreter {
 }
 
 #[derive(Debug, Default)]
-struct Stack {
+pub struct Stack {
     list: Vec<Node>,
 }
 
@@ -47,11 +46,19 @@ impl Stack {
     }
 
     pub fn top(&self) -> &Node {
-        &self.list[self.list.len() - 1]
+        log::info!(
+            "Called top of stack with {} items remaining",
+            self.list.len()
+        );
+        match self.list.last() {
+            Some(a) => a,
+            None => panic!("should not happen"),
+        }
+        // &self.list[self.list.len() - 1]
     }
 }
 
-impl ChangeListInterpreter {
+impl PatchMachine {
     pub fn new(container: Element) -> Self {
         let document = window()
             .expect("must have access to the window")
@@ -114,6 +121,265 @@ impl ChangeListInterpreter {
         // }) as Box<dyn FnMut(&Event)>));
     }
 
+    pub fn handle_edit(&mut self, edit: &Edit) {
+        match *edit {
+            // 0
+            Edit::SetText { text } => {
+                //
+                self.stack.top().set_text_content(Some(text))
+            }
+
+            // 1
+            Edit::RemoveSelfAndNextSiblings {} => {
+                let node = self.stack.pop();
+                let mut sibling = node.next_sibling();
+
+                while let Some(inner) = sibling {
+                    let temp = inner.next_sibling();
+                    if let Some(sibling) = inner.dyn_ref::<Element>() {
+                        sibling.remove();
+                    }
+                    sibling = temp;
+                }
+                if let Some(node) = node.dyn_ref::<Element>() {
+                    node.remove();
+                }
+            }
+
+            // 2
+            Edit::ReplaceWith => {
+                let new_node = self.stack.pop();
+                let old_node = self.stack.pop();
+
+                if old_node.has_type::<Element>() {
+                    old_node
+                        .dyn_ref::<Element>()
+                        .unwrap()
+                        .replace_with_with_node_1(&new_node)
+                        .unwrap();
+                } else if old_node.has_type::<web_sys::CharacterData>() {
+                    old_node
+                        .dyn_ref::<web_sys::CharacterData>()
+                        .unwrap()
+                        .replace_with_with_node_1(&new_node)
+                        .unwrap();
+                } else if old_node.has_type::<web_sys::DocumentType>() {
+                    old_node
+                        .dyn_ref::<web_sys::DocumentType>()
+                        .unwrap()
+                        .replace_with_with_node_1(&new_node)
+                        .unwrap();
+                } else {
+                    panic!("Cannot replace node: {:?}", old_node);
+                }
+
+                self.stack.push(new_node);
+            }
+
+            // 3
+            Edit::SetAttribute { name, value } => {
+                let node = self.stack.top();
+
+                if let Some(node) = node.dyn_ref::<Element>() {
+                    node.set_attribute(name, value).unwrap();
+
+                    // Some attributes are "volatile" and don't work through `setAttribute`.
+                    // TODO:
+                    // if name == "value" {
+                    //     node.set_value(value);
+                    // }
+                    // if name == "checked" {
+                    //     node.set_checked(true);
+                    // }
+                    // if name == "selected" {
+                    //     node.set_selected(true);
+                    // }
+                }
+            }
+
+            // 4
+            Edit::RemoveAttribute { name } => {
+                let node = self.stack.top();
+                if let Some(node) = node.dyn_ref::<Element>() {
+                    node.remove_attribute(name).unwrap();
+
+                    // Some attributes are "volatile" and don't work through `removeAttribute`.
+                    // TODO:
+                    // if name == "value" {
+                    //     node.set_value("");
+                    // }
+                    // if name == "checked" {
+                    //     node.set_checked(false);
+                    // }
+                    // if name == "selected" {
+                    //     node.set_selected(false);
+                    // }
+                }
+            }
+
+            // 5
+            Edit::PushReverseChild { n } => {
+                let parent = self.stack.top();
+                let children = parent.child_nodes();
+                let child = children.get(children.length() - n - 1).unwrap();
+                self.stack.push(child);
+            }
+
+            // 6
+            Edit::PopPushChild { n } => {
+                self.stack.pop();
+                let parent = self.stack.top();
+                let children = parent.child_nodes();
+                let child = children.get(n).unwrap();
+                self.stack.push(child);
+            }
+
+            // 7
+            Edit::Pop => {
+                self.stack.pop();
+            }
+
+            // 8
+            Edit::AppendChild => {
+                let child = self.stack.pop();
+                self.stack.top().append_child(&child).unwrap();
+            }
+
+            // 9
+            Edit::CreateTextNode { text } => self.stack.push(
+                self.document
+                    .create_text_node(text)
+                    .dyn_into::<Node>()
+                    .unwrap(),
+            ),
+
+            // 10
+            Edit::CreateElement { tag_name } => {
+                let el = self
+                    .document
+                    .create_element(tag_name)
+                    .unwrap()
+                    .dyn_into::<Node>()
+                    .unwrap();
+                self.stack.push(el);
+            }
+
+            // 11
+            Edit::NewEventListener { event_type, a, b } => {
+                let el = self.stack.top();
+
+                let el = el
+                    .dyn_ref::<Element>()
+                    .expect(&format!("not an element: {:?}", el));
+                el.add_event_listener_with_callback(
+                    event_type,
+                    self.callback.as_ref().unwrap().as_ref().unchecked_ref(),
+                )
+                .unwrap();
+                debug!("adding attributes: {}, {}", a, b);
+                el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string())
+                    .unwrap();
+                el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string())
+                    .unwrap();
+            }
+
+            // 12
+            Edit::UpdateEventListener { event_type, a, b } => {
+                if let Some(el) = self.stack.top().dyn_ref::<Element>() {
+                    el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string())
+                        .unwrap();
+                    el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string())
+                        .unwrap();
+                }
+            }
+
+            // 13
+            Edit::RemoveEventListener { event_type } => {
+                if let Some(el) = self.stack.top().dyn_ref::<Element>() {
+                    el.remove_event_listener_with_callback(
+                        event_type,
+                        self.callback.as_ref().unwrap().as_ref().unchecked_ref(),
+                    )
+                    .unwrap();
+                }
+            }
+
+            // 14
+            Edit::CreateElementNs { tag_name, ns } => {
+                let el = self
+                    .document
+                    .create_element_ns(Some(ns), tag_name)
+                    .unwrap()
+                    .dyn_into::<Node>()
+                    .unwrap();
+                self.stack.push(el);
+            }
+
+            // 15
+            Edit::SaveChildrenToTemporaries {
+                mut temp,
+                start,
+                end,
+            } => {
+                let parent = self.stack.top();
+                let children = parent.child_nodes();
+                for i in start..end {
+                    self.temporaries.insert(temp, children.get(i).unwrap());
+                    temp += 1;
+                }
+            }
+
+            // 16
+            Edit::PushChild { n } => {
+                let parent = self.stack.top();
+                let child = parent.child_nodes().get(n).unwrap();
+                self.stack.push(child);
+            }
+
+            // 17
+            Edit::PushTemporary { temp } => {
+                let t = self.temporaries.get(&temp).unwrap().clone();
+                self.stack.push(t);
+            }
+
+            // 18
+            Edit::InsertBefore => {
+                let before = self.stack.pop();
+                let after = self.stack.pop();
+                after
+                    .parent_node()
+                    .unwrap()
+                    .insert_before(&before, Some(&after))
+                    .unwrap();
+                self.stack.push(before);
+            }
+
+            // 19
+            Edit::PopPushReverseChild { n } => {
+                self.stack.pop();
+                let parent = self.stack.top();
+                let children = parent.child_nodes();
+                let child = children.get(children.length() - n - 1).unwrap();
+                self.stack.push(child);
+            }
+
+            // 20
+            Edit::RemoveChild { n } => {
+                let parent = self.stack.top();
+                if let Some(child) = parent.child_nodes().get(n).unwrap().dyn_ref::<Element>() {
+                    child.remove();
+                }
+            }
+
+            // 21
+            Edit::SetClass { class_name } => {
+                if let Some(el) = self.stack.top().dyn_ref::<Element>() {
+                    el.set_class_name(class_name);
+                }
+            }
+        }
+    }
+
     // 0
     pub fn set_text(&mut self, text: &str) {
         self.stack.top().set_text_content(Some(text));

+ 111 - 424
packages/web/src/lib.rs

@@ -1,5 +1,5 @@
 //! Dioxus WebSys
-//!
+//! --------------
 //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser.
 //!
 //! While it is possible to render a single component directly, it is not possible to render component trees. For these,
@@ -16,8 +16,11 @@
 //! ```
 //!
 //! The `WebsysRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
+use web_sys::{window, Document, Element, Event, Node};
 
-use dioxus::{patch::Patch, prelude::VText};
+use dioxus::prelude::VElement;
+// use dioxus::{patch::Patch, prelude::VText};
+// use dioxus::{patch::Patch, prelude::VText};
 pub use dioxus_core as dioxus;
 use dioxus_core::{
     events::EventTrigger,
@@ -26,6 +29,7 @@ use dioxus_core::{
 use futures::{channel::mpsc, future, SinkExt, StreamExt};
 use mpsc::UnboundedSender;
 pub mod interpreter;
+use interpreter::PatchMachine;
 /// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM.
 /// Under the hood, we leverage WebSys and interact directly with the DOM
 
@@ -71,18 +75,18 @@ impl WebsysRenderer {
         let (sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
 
         // Iterate through the nodes, attaching the closure and sender to the listener
-        {
-            let mut remote_sender = sender.clone();
-            let listener = move || {
-                let event = EventTrigger::new();
-                wasm_bindgen_futures::spawn_local(async move {
-                    remote_sender
-                        .send(event)
-                        .await
-                        .expect("Updating receiver failed");
-                })
-            };
-        }
+        // {
+        //     let mut remote_sender = sender.clone();
+        //     let listener = move || {
+        //         let event = EventTrigger::new();
+        //         wasm_bindgen_futures::spawn_local(async move {
+        //             remote_sender
+        //                 .send(event)
+        //                 .await
+        //                 .expect("Updating receiver failed");
+        //         })
+        //     };
+        // }
 
         // Event loop waits for the receiver to finish up
         // TODO! Connect the sender to the virtual dom's suspense system
@@ -105,433 +109,116 @@ impl WebsysRenderer {
     pub fn simple_render(tree: impl for<'a> Fn(&'a Bump) -> VNode<'a>) {
         let bump = Bump::new();
 
+        // Choose the body to render the app into
+        let window = web_sys::window().expect("should have access to the Window");
+        let document = window
+            .document()
+            .expect("should have access to the Document");
+        let body = document.body().unwrap();
+
+        // Build a dummy div
+        let container: &Element = body.as_ref();
+        container.set_inner_html("");
+        container
+            .append_child(
+                document
+                    .create_element("div")
+                    .expect("should create element OK")
+                    .as_ref(),
+            )
+            .expect("should append child OK");
+
+        // Create the old dom and the new dom
+        // The old is just an empty div, like the one we made above
         let old = html! { <div> </div> }(&bump);
-
-        let created = create_dom_node(&old);
-        let root_node = created.node;
-
         let new = tree(&bump);
 
-        let mut machine = DiffMachine::new();
-
-        let patches = machine.diff(&old, &new);
-        // log::info!("There are {:?} patches", patches.len());
-
-        let root2 = root_node.clone();
-        patch(root_node, &patches).expect("Failed to simple render");
-        let document = web_sys::window().unwrap().document().unwrap();
-
-        document.body().unwrap().append_child(&root2);
-        // log::info!("Succesfully patched the dom");
-    }
-}
-
-use std::collections::HashMap;
-use std::collections::HashSet;
-use std::{cmp::min, rc::Rc};
-use wasm_bindgen::JsCast;
-use wasm_bindgen::JsValue;
-use web_sys::{Element, Node, Text};
-
-/// Apply all of the patches to our old root node in order to create the new root node
-/// that we desire.
-/// This is usually used after diffing two virtual nodes.
-pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<(), JsValue> {
-    // pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<ActiveClosures, JsValue> {
-    let root_node: Node = root_node.into();
-
-    let mut cur_node_idx = 0;
-
-    let mut nodes_to_find = HashSet::new();
-
-    for patch in patches {
-        nodes_to_find.insert(patch.node_idx());
-    }
-
-    let mut element_nodes_to_patch = HashMap::new();
-    let mut text_nodes_to_patch = HashMap::new();
-
-    // Closures that were added to the DOM during this patch operation.
-    // let mut active_closures = HashMap::new();
-
-    find_nodes(
-        root_node,
-        &mut cur_node_idx,
-        &mut nodes_to_find,
-        &mut element_nodes_to_patch,
-        &mut text_nodes_to_patch,
-    );
-
-    for patch in patches {
-        let patch_node_idx = patch.node_idx();
-
-        if let Some(element) = element_nodes_to_patch.get(&patch_node_idx) {
-            let new_closures = apply_element_patch(&element, &patch)?;
-            // active_closures.extend(new_closures);
-            continue;
-        }
-
-        if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
-            apply_text_patch(&text_node, &patch)?;
-            continue;
-        }
-
-        unreachable!("Getting here means we didn't find the element or next node that we were supposed to patch.")
-    }
-
-    // Ok(active_closures)
-    Ok(())
-}
-
-fn find_nodes(
-    root_node: Node,
-    cur_node_idx: &mut usize,
-    nodes_to_find: &mut HashSet<usize>,
-    element_nodes_to_patch: &mut HashMap<usize, Element>,
-    text_nodes_to_patch: &mut HashMap<usize, Text>,
-) {
-    if nodes_to_find.len() == 0 {
-        return;
-    }
-
-    // We use child_nodes() instead of children() because children() ignores text nodes
-    let children = root_node.child_nodes();
-    let child_node_count = children.length();
+        // Build a machine that diffs doms
+        let mut diff_machine = DiffMachine::new(&bump);
+        diff_machine.diff_node(&old, &new);
 
-    // If the root node matches, mark it for patching
-    if nodes_to_find.get(&cur_node_idx).is_some() {
-        match root_node.node_type() {
-            Node::ELEMENT_NODE => {
-                element_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
-            }
-            Node::TEXT_NODE => {
-                text_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
-            }
-            other => unimplemented!("Unsupported root node type: {}", other),
-        }
-        nodes_to_find.remove(&cur_node_idx);
-    }
+        // Build a machine that patches doms
+        // In practice, the diff machine might be on a different computer, sending us patches
+        let mut patch_machine = PatchMachine::new(body.clone().into());
 
-    *cur_node_idx += 1;
+        // need to make sure we push the root node onto the stack before trying to run anything
+        // this provides an entrance for the diffing machine to do its work
+        // Here, we grab the div out of the container (the body) to connect with the dummy div we made above
+        // This is because we don't support fragments (yet)
+        let root_node = container.first_child().unwrap();
+        patch_machine.stack.push(root_node);
 
-    for i in 0..child_node_count {
-        let node = children.item(i).unwrap();
-
-        match node.node_type() {
-            Node::ELEMENT_NODE => {
-                find_nodes(
-                    node,
-                    cur_node_idx,
-                    nodes_to_find,
-                    element_nodes_to_patch,
-                    text_nodes_to_patch,
-                );
-            }
-            Node::TEXT_NODE => {
-                if nodes_to_find.get(&cur_node_idx).is_some() {
-                    text_nodes_to_patch.insert(*cur_node_idx, node.unchecked_into());
-                }
-
-                *cur_node_idx += 1;
-            }
-            Node::COMMENT_NODE => {
-                // At this time we do not support user entered comment nodes, so if we see a comment
-                // then it was a delimiter created by virtual-dom-rs in order to ensure that two
-                // neighboring text nodes did not get merged into one by the browser. So we skip
-                // over this virtual-dom-rs generated comment node.
-            }
-            _other => {
-                // Ignoring unsupported child node type
-                // TODO: What do we do with this situation? Log a warning?
-            }
+        // Consume the diff machine, generating the patch list
+        for patch in diff_machine.consume() {
+            patch_machine.handle_edit(&patch);
+            log::info!("Patch is {:?}", patch);
         }
     }
-}
-
-// pub type ActiveClosures = HashMap<u32, Vec<DynClosure>>;
-
-// fn apply_element_patch(node: &Element, patch: &Patch) -> Result<ActiveClosures, JsValue> {
-fn apply_element_patch(node: &Element, patch: &Patch) -> Result<(), JsValue> {
-    // let active_closures = HashMap::new();
-
-    match patch {
-        Patch::AddAttributes(_node_idx, attributes) => {
-            for (attrib_name, attrib_val) in attributes.iter() {
-                node.set_attribute(attrib_name, attrib_val)?;
-            }
-
-            // Ok(active_closures)
-            Ok(())
-        }
-        Patch::RemoveAttributes(_node_idx, attributes) => {
-            for attrib_name in attributes.iter() {
-                node.remove_attribute(attrib_name)?;
-            }
-
-            // Ok(active_closures)
-            Ok(())
-        }
-        Patch::Replace(_node_idx, new_node) => {
-            let created_node = create_dom_node(&new_node);
-
-            node.replace_with_with_node_1(&created_node.node)?;
-
-            Ok(())
-            // Ok(created_node.closures)
-        }
-        Patch::TruncateChildren(_node_idx, num_children_remaining) => {
-            let children = node.child_nodes();
-            let mut child_count = children.length();
-
-            // We skip over any separators that we placed between two text nodes
-            //   -> `<!--ptns-->`
-            //  and trim all children that come after our new desired `num_children_remaining`
-            let mut non_separator_children_found = 0;
-
-            for index in 0 as u32..child_count {
-                let child = children
-                    .get(min(index, child_count - 1))
-                    .expect("Potential child to truncate");
-
-                // If this is a comment node then we know that it is a `<!--ptns-->`
-                // text node separator that was created in virtual_node/mod.rs.
-                if child.node_type() == Node::COMMENT_NODE {
-                    continue;
-                }
-
-                non_separator_children_found += 1;
-
-                if non_separator_children_found <= *num_children_remaining as u32 {
-                    continue;
-                }
-
-                node.remove_child(&child).expect("Truncated children");
-                child_count -= 1;
-            }
 
-            Ok(())
-            // Ok(active_closures)
-        }
-        Patch::AppendChildren(_node_idx, new_nodes) => {
-            let parent = &node;
-
-            let mut active_closures = HashMap::new();
-
-            for new_node in new_nodes {
-                let created_node = create_dom_node(&new_node);
-                // let created_node = new_node.create_dom_node();
-
-                parent.append_child(&created_node.node)?;
-
-                active_closures.extend(created_node.closures);
-            }
-
-            Ok(())
-            // Ok(active_closures)
-        }
-        Patch::ChangeText(_node_idx, _new_node) => {
-            unreachable!("Elements should not receive ChangeText patches.")
-        }
-    }
-}
-
-fn apply_text_patch(node: &Text, patch: &Patch) -> Result<(), JsValue> {
-    match patch {
-        Patch::ChangeText(_node_idx, new_node) => {
-            node.set_node_value(Some(&new_node.text));
-        }
-        Patch::Replace(_node_idx, new_node) => {
-            node.replace_with_with_node_1(&create_dom_node(&new_node).node)?;
-            // node.replace_with_with_node_1(&new_node.create_dom_node().node)?;
-        }
-        other => unreachable!(
-            "Text nodes should only receive ChangeText or Replace patches, not ",
-            // other,
-            // "Text nodes should only receive ChangeText or Replace patches, not {:?}.",
-            // other,
-        ),
-    };
-
-    Ok(())
-}
+    pub fn complex_render(
+        tree1: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
+        tree2: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
+    ) {
+        let bump = Bump::new();
 
-/// A node along with all of the closures that were created for that
-/// node's events and all of it's child node's events.
-pub struct CreatedNode<T> {
-    /// A `Node` or `Element` that was created from a `VirtualNode`
-    pub node: T,
-    /// A map of a node's unique identifier along with all of the Closures for that node.
-    ///
-    /// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
-    /// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
-    /// memory.
-    pub closures: HashMap<u32, Vec<DynClosure>>,
-}
+        let old = tree1(&bump);
+        let new = tree2(&bump);
 
-/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
-/// any Closure regardless of the arguments.
-pub type DynClosure = Rc<dyn AsRef<JsValue>>;
+        let mut machine = DiffMachine::new(&bump);
+        machine.diff_node(&old, &new);
 
-impl<T> CreatedNode<T> {
-    pub fn without_closures<N: Into<T>>(node: N) -> Self {
-        CreatedNode {
-            node: node.into(),
-            closures: HashMap::with_capacity(0),
+        for patch in machine.consume() {
+            println!("Patch is {:?}", patch);
         }
     }
 }
 
-impl<T> std::ops::Deref for CreatedNode<T> {
-    type Target = T;
-    fn deref(&self) -> &Self::Target {
-        &self.node
-    }
-}
-
-impl From<CreatedNode<Element>> for CreatedNode<Node> {
-    fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
-        CreatedNode {
-            node: other.node.into(),
-            closures: other.closures,
-        }
-    }
-}
-fn create_dom_node(node: &VNode<'_>) -> CreatedNode<Node> {
-    match node {
-        VNode::Text(text_node) => CreatedNode::without_closures(create_text_node(text_node)),
-        VNode::Element(element_node) => create_element_node(element_node).into(),
-        // VNode::Element(element_node) => element_node.create_element_node().into(),
-        VNode::Suspended => todo!(" not iimplemented yet"),
-        VNode::Component(_) => todo!(" not iimplemented yet"),
-    }
-}
-
-/// Build a DOM element by recursively creating DOM nodes for this element and it's
-/// children, it's children's children, etc.
-pub fn create_element_node(node: &dioxus_core::nodes::VElement) -> CreatedNode<Element> {
-    let document = web_sys::window().unwrap().document().unwrap();
-
-    // TODO: enable svg again
-    // let element = if html_validation::is_svg_namespace(&node.tag_name) {
-    //     document
-    //         .create_element_ns(Some("http://www.w3.org/2000/svg"), &node.tag_name)
-    //         .unwrap()
-    // } else {
-    let element = document.create_element(&node.tag_name).unwrap();
-    // };
-
-    let mut closures = HashMap::new();
-
-    node.attributes
-        .iter()
-        .map(|f| (f.name, f.value))
-        .for_each(|(name, value)| {
-            if name == "unsafe_inner_html" {
-                element.set_inner_html(value);
-
-                return;
-            }
-
-            element
-                .set_attribute(name, value)
-                .expect("Set element attribute in create element");
+#[cfg(test)]
+mod tests {
+    use std::env;
+
+    use super::*;
+    use dioxus_core as dioxus;
+    use dioxus_core::prelude::html;
+
+    #[test]
+    fn simple_patch() {
+        env::set_var("RUST_LOG", "trace");
+        pretty_env_logger::init();
+        log::info!("Hello!");
+        let renderer = WebsysRenderer::simple_render(html! {
+            <div>
+                "Hello world"
+                <button onclick={move |_| log::info!("button1 clicked!")}> "click me" </button>
+                <button onclick={move |_| log::info!("button2 clicked!")}> "click me" </button>
+            </div>
         });
-
-    // if node.events.0.len() > 0 {
-    //     let unique_id = create_unique_identifier();
-
-    //     element
-    //         .set_attribute("data-vdom-id".into(), &unique_id.to_string())
-    //         .expect("Could not set attribute on element");
-
-    //     closures.insert(unique_id, vec![]);
-
-    //     node.events.0.iter().for_each(|(onevent, callback)| {
-    //         // onclick -> click
-    //         let event = &onevent[2..];
-
-    //         let current_elem: &EventTarget = element.dyn_ref().unwrap();
-
-    //         current_elem
-    //             .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
-    //             .unwrap();
-
-    //         closures
-    //             .get_mut(&unique_id)
-    //             .unwrap()
-    //             .push(Rc::clone(callback));
-    //     });
-    // }
-
-    let mut previous_node_was_text = false;
-
-    node.children.iter().for_each(|child| {
-        // log::info!("Patching child");
-        match child {
-            VNode::Text(text_node) => {
-                let current_node = element.as_ref() as &web_sys::Node;
-
-                // We ensure that the text siblings are patched by preventing the browser from merging
-                // neighboring text nodes. Originally inspired by some of React's work from 2016.
-                //  -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
-                //  -> https://github.com/facebook/react/pull/5753
-                //
-                // `ptns` = Percy text node separator
-                if previous_node_was_text {
-                    let separator = document.create_comment("ptns");
-                    current_node
-                        .append_child(separator.as_ref() as &web_sys::Node)
-                        .unwrap();
-                }
-
-                current_node
-                    .append_child(&create_text_node(&text_node))
-                    .unwrap();
-
-                previous_node_was_text = true;
-            }
-            VNode::Element(element_node) => {
-                previous_node_was_text = false;
-
-                let child = create_element_node(element_node);
-                // let child = element_node.create_element_node();
-                let child_elem: Element = child.node;
-
-                closures.extend(child.closures);
-
-                element.append_child(&child_elem).unwrap();
-            }
-            VNode::Suspended => {
-                todo!("Not yet supported")
-            }
-            VNode::Component(_) => {
-                todo!("Not yet supported")
-            }
-        }
-    });
-
-    // TODO: connect on mount to the event system somehow
-    // if let Some(on_create_elem) = node.events.0.get("on_create_elem") {
-    //     let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
-    //     on_create_elem
-    //         .call1(&wasm_bindgen::JsValue::NULL, &element)
-    //         .unwrap();
-    // }
-
-    CreatedNode {
-        node: element,
-        closures,
     }
-}
 
-/// Return a `Text` element from a `VirtualNode`, typically right before adding it
-/// into the DOM.
-pub fn create_text_node(node: &VText) -> Text {
-    let document = web_sys::window().unwrap().document().unwrap();
-    document.create_text_node(&node.text)
+    #[test]
+    fn complex_patch() {
+        env::set_var("RUST_LOG", "trace");
+        pretty_env_logger::init();
+        log::info!("Hello!");
+        let renderer = WebsysRenderer::complex_render(
+            html! {
+                <div>
+                    "Hello world"
+                    <div>
+                        <h1> "Heading" </h1>
+                    </div>
+                </div>
+            },
+            html! {
+                <div>
+                    "Hello world"
+                    "Hello world"
+                    "Hello world"
+                    <div>
+                        <h1> "Heading" </h1>
+                    </div>
+                </div>
+            },
+        );
+    }
 }
-
-// /// For any listeners in the tree, attach the sender closure.
-// /// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu
-// fn attach_listeners(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom) {}
-// fn render_diffs() {}

+ 409 - 0
packages/web/src/percypatch.rs

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