Bladeren bron

Feat: wire up some of the changelist for diff

Jonathan Kelley 4 jaren geleden
bovenliggende
commit
d063a19

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

@@ -0,0 +1,671 @@
+//! 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 crate::innerlude::Listener;
+
+/// Renderers need to implement the interpreter trait
+///
+///
+///
+///
+///
+trait Inrerpreter {
+    fn set_text(&mut self, text: &str);
+    fn remove_self_and_next_siblings(&mut self);
+    fn replace_with(&mut self);
+    fn set_attribute(&mut self, name: &str, value: &str);
+    fn remove_attribute(&mut self, name: &str);
+    fn push_reverse_child(&mut self, n: u32);
+    fn pop_push_child(&mut self, n: u32);
+    fn pop(&mut self);
+    fn append_child(&mut self);
+    fn create_text_node(&mut self, text: &str);
+    fn create_element(&mut self, tag_name: &str);
+    fn new_event_listener(&mut self, event_type: &str, a: u32, b: u32);
+    fn update_event_listener(&mut self, event_type: &str, a: u32, b: u32);
+    fn remove_event_listener(&mut self, event_type: &str);
+    fn create_element_ns(&mut self, tag_name: &str, ns: &str);
+    fn save_children_to_temporaries(&mut self, temp: u32, start: u32, end: u32);
+    // fn save_children_to_temporaries(&mut self, mut temp: u32, start: u32, end: u32);
+    fn push_child(&mut self, n: u32);
+    fn push_temporary(&mut self, temp: u32);
+    fn insert_before(&mut self);
+    fn pop_push_reverse_child(&mut self, n: u32);
+    fn remove_child(&mut self, n: u32);
+    fn set_class(&mut self, class_name: &str);
+    // fn save_template(&mut self, id: CacheId);
+    // fn push_template(&mut self, id: CacheId);
+}
+
+pub struct ChangeList<'src> {
+    traversal: Traversal,
+    next_temporary: u32,
+    forcing_new_listeners: bool,
+    emitter: &'src mut dyn Inrerpreter,
+}
+
+/// Traversal methods.
+impl ChangeList<'_> {
+    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() {
+            return;
+        }
+
+        for mv in self.traversal.commit() {
+            // match mv {
+            //     MoveTo::Parent => {
+            //         // debug!("emit: pop");
+            //         self.emitter.pop();
+            //     }
+            //     MoveTo::Child(n) => {
+            //         // debug!("emit: push_child({})", n);
+            //         self.emitter.push_child(n);
+            //     }
+            //     MoveTo::ReverseChild(n) => {
+            //         // debug!("emit: push_reverse_child({})", n);
+            //         self.emitter.push_reverse_child(n);
+            //     }
+            //     MoveTo::Sibling(n) => {
+            //         // debug!("emit: pop_push_child({})", n);
+            //         self.emitter.pop_push_child(n);
+            //     }
+            //     MoveTo::ReverseSibling(n) => {
+            //         // debug!("emit: pop_push_reverse_child({})", n);
+            //         self.emitter.pop_push_reverse_child(n);
+            //     }
+            //     MoveTo::TempChild(temp) => {
+            //         // debug!("emit: push_temporary({})", temp);
+            //         self.emitter.push_temporary(temp);
+            //     }
+            // }
+        }
+    }
+
+    pub fn traversal_is_committed(&self) -> bool {
+        self.traversal.is_committed()
+    }
+}
+
+impl ChangeList<'_> {
+    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
+            .save_children_to_temporaries(temp_base, start as u32, end as u32);
+        temp_base
+    }
+
+    pub fn push_temporary(&self, temp: u32) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: push_temporary({})", temp);
+        self.emitter.push_temporary(temp);
+    }
+
+    pub fn remove_child(&self, child: usize) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: remove_child({})", child);
+        self.emitter.remove_child(child as u32);
+    }
+
+    pub fn insert_before(&self) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: insert_before()");
+        self.emitter.insert_before();
+    }
+
+    pub fn ensure_string(&mut self, string: &str) -> StringKey {
+        todo!()
+        // self.strings.ensure_string(string, &self.emitter)
+    }
+
+    pub fn set_text(&self, text: &str) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: set_text({:?})", text);
+        self.emitter.set_text(text);
+        // .set_text(text.as_ptr() as u32, text.len() as u32);
+    }
+
+    pub fn remove_self_and_next_siblings(&self) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: remove_self_and_next_siblings()");
+        self.emitter.remove_self_and_next_siblings();
+    }
+
+    pub fn replace_with(&self) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: replace_with()");
+        self.emitter.replace_with();
+    }
+
+    pub fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) {
+        debug_assert!(self.traversal_is_committed());
+        todo!()
+        // if name == "class" && !is_namespaced {
+        //     let class_id = self.ensure_string(value);
+        //     // debug!("emit: set_class({:?})", value);
+        //     self.emitter.set_class(class_id.into());
+        // } else {
+        //     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: &str) {
+        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(&self) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: append_child()");
+        self.emitter.append_child();
+    }
+
+    pub fn create_text_node(&self, text: &str) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: create_text_node({:?})", text);
+        self.emitter.create_text_node(text);
+    }
+
+    pub fn create_element(&mut self, tag_name: &str) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: create_element({:?})", tag_name);
+        let tag_name_id = self.ensure_string(tag_name);
+        self.emitter.create_element(tag_name_id.into());
+    }
+
+    pub fn create_element_ns(&mut self, tag_name: &str, ns: &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_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, listener: &Listener) {
+        debug_assert!(self.traversal_is_committed());
+        // 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(event_id.into(), a, b);
+    }
+
+    pub fn update_event_listener(&mut self, listener: &Listener) {
+        debug_assert!(self.traversal_is_committed());
+
+        if self.forcing_new_listeners {
+            self.new_event_listener(listener);
+            return;
+        }
+
+        // debug!("emit: update_event_listener({:?})", listener);
+        let (a, b) = listener.get_callback_parts();
+        debug_assert!(a != 0);
+        let event_id = self.ensure_string(listener.event);
+        self.emitter.update_event_listener(event_id.into(), a, b);
+    }
+
+    pub fn remove_event_listener(&mut self, event: &str) {
+        debug_assert!(self.traversal_is_committed());
+        // debug!("emit: remove_event_listener({:?})", event);
+        let event_id = self.ensure_string(event);
+        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 {
+        self.uncommitted.is_empty()
+    }
+
+    /// 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
+    }
+}

+ 1 - 0
packages/core/src/lib.rs

@@ -65,6 +65,7 @@
 //! - dioxus-liveview (SSR + StringRenderer)
 //!
 
+pub mod changelist;
 pub mod component;
 pub mod context;
 pub mod debug_renderer;

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

@@ -8,6 +8,7 @@ use std::{
     cell::{RefCell, UnsafeCell},
     future::Future,
     marker::PhantomData,
+    rc::Rc,
     sync::atomic::AtomicUsize,
 };
 
@@ -21,11 +22,14 @@ pub struct VirtualDom<P: Properties> {
     /// The index of the root component.
     base_scope: Index,
 
-    /// Components generate lifecycle events
-    event_queue: Vec<LifecycleEvent>, // wrap in a lock or something so
+    ///
+    ///
+    ///
+    ///
+    event_queue: Rc<RefCell<Vec<LifecycleEvent>>>,
 
-    // mark the root props with P, even though they're held by the root component
-    _p: PhantomData<P>,
+    #[doc(hidden)] // mark the root props with P, even though they're held by the root component
+    _root_props: PhantomData<P>,
 }
 
 impl VirtualDom<()> {
@@ -61,19 +65,19 @@ impl<P: Properties + 'static> VirtualDom<P> {
         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 = vec![first_event];
+        let event_queue = Rc::new(RefCell::new(vec![first_event]));
 
         Self {
             components,
             base_scope,
             event_queue,
-            _p: PhantomData {},
+            _root_props: PhantomData {},
         }
     }
 
     /// With access to the virtual dom, schedule an update to the Root component's props
     pub fn update_props(&mut self, new_props: P) {
-        self.event_queue.push(LifecycleEvent {
+        self.event_queue.borrow_mut().push(LifecycleEvent {
             event_type: LifecycleType::PropsChanged {
                 props: Box::new(new_props),
             },
@@ -86,8 +90,8 @@ impl<P: Properties + 'static> VirtualDom<P> {
     /// 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.pop().ok_or(Error::NoEvent)?;
-        process_event(self, event)
+        let event = self.event_queue.borrow_mut().pop().ok_or(Error::NoEvent)?;
+        process_event(&mut self.components, event)
     }
 
     /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
@@ -125,9 +129,21 @@ impl<P: Properties + 'static> VirtualDom<P> {
             .as_ref();
 
         // Run the callback
-        // Theoretically, this should
+        // This should cause internal state to progress, dumping events into the event queue
+        // todo: integrate this with a tracing mechanism exposed to a dev tool
         listener();
 
+        // Run through our events, tagging which Indexes are receiving updates
+        // 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)?;
+        }
+
         Ok(())
     }
 
@@ -143,11 +159,12 @@ impl<P: Properties + 'static> VirtualDom<P> {
 ///
 ///
 ///
-fn process_event<P: Properties>(
-    dom: &mut VirtualDom<P>,
+fn process_event(
+    // dom: &mut VirtualDom<P>,
+    components: &mut Arena<Scope>,
     LifecycleEvent { index, event_type }: LifecycleEvent,
 ) -> Result<()> {
-    let scope = dom.components.get(index).ok_or(Error::NoEvent)?;
+    let scope = components.get(index).ok_or(Error::NoEvent)?;
 
     match event_type {
         // Component needs to be mounted to the virtual dom
@@ -176,7 +193,8 @@ fn process_event<P: Properties>(
         // Component was removed from the DOM
         // Run any destructors and cleanup for the hooks and the dump the component
         LifecycleType::Removed {} => {
-            let f = dom.components.remove(index);
+            let f = components.remove(index);
+            // let f = dom.components.remove(index);
         }
 
         // Component was messaged via the internal subscription service

+ 2 - 0
packages/web/Cargo.toml

@@ -13,6 +13,8 @@ wasm-bindgen = "0.2.70"
 lazy_static = "1.4.0"
 wasm-bindgen-futures = "0.4.20"
 futures = "0.3.12"
+wasm-logger = "0.2.0"
+log = "0.4.14"
 # html-validation = { path = "../html-validation", version = "0.1.1" }
 
 [dependencies.web-sys]

+ 381 - 0
packages/web/src/interpreter.rs

@@ -0,0 +1,381 @@
+use crate::cached_set::CacheId;
+use crate::{Element, EventsTrampoline};
+use fxhash::FxHashMap;
+use log::{debug, info, log};
+use wasm_bindgen::{closure::Closure, JsCast};
+use web_sys::{window, Document, Event, Node};
+
+#[derive(Debug)]
+pub(crate) struct ChangeListInterpreter {
+    container: Element,
+    stack: Stack,
+    temporaries: FxHashMap<u32, Node>,
+    templates: FxHashMap<CacheId, Node>,
+    callback: Option<Closure<dyn FnMut(&Event)>>,
+    document: Document,
+}
+
+#[derive(Debug, Default)]
+struct Stack {
+    list: Vec<Node>,
+}
+
+impl Stack {
+    pub fn with_capacity(cap: usize) -> Self {
+        Stack {
+            list: Vec::with_capacity(cap),
+        }
+    }
+
+    pub fn push(&mut self, node: Node) {
+        debug!("stack-push: {:?}", node);
+        self.list.push(node);
+    }
+
+    pub fn pop(&mut self) -> Node {
+        let res = self.list.pop().unwrap();
+        debug!("stack-pop: {:?}", res);
+
+        res
+    }
+
+    pub fn clear(&mut self) {
+        self.list.clear();
+    }
+
+    pub fn top(&self) -> &Node {
+        &self.list[self.list.len() - 1]
+    }
+}
+
+impl ChangeListInterpreter {
+    pub fn new(container: Element) -> Self {
+        let document = window()
+            .expect("must have access to the window")
+            .document()
+            .expect("must have access to the Document");
+
+        Self {
+            container,
+            stack: Stack::with_capacity(20),
+            temporaries: Default::default(),
+            templates: Default::default(),
+            callback: None,
+            document,
+        }
+    }
+
+    pub fn unmount(&mut self) {
+        self.stack.clear();
+        self.temporaries.clear();
+        self.templates.clear();
+    }
+
+    pub fn start(&mut self) {
+        if let Some(child) = self.container.first_child() {
+            self.stack.push(child);
+        }
+    }
+
+    pub fn reset(&mut self) {
+        self.stack.clear();
+        self.temporaries.clear();
+    }
+
+    pub fn get_template(&self, id: CacheId) -> Option<&Node> {
+        self.templates.get(&id)
+    }
+
+    pub fn init_events_trampoline(&mut self, mut trampoline: EventsTrampoline) {
+        self.callback = Some(Closure::wrap(Box::new(move |event: &web_sys::Event| {
+            let target = event
+                .target()
+                .expect("missing target")
+                .dyn_into::<Element>()
+                .expect("not a valid element");
+            let typ = event.type_();
+            let a: u32 = target
+                .get_attribute(&format!("dodrio-a-{}", typ))
+                .and_then(|v| v.parse().ok())
+                .unwrap_or_default();
+
+            let b: u32 = target
+                .get_attribute(&format!("dodrio-b-{}", typ))
+                .and_then(|v| v.parse().ok())
+                .unwrap_or_default();
+
+            // get a and b from the target
+            trampoline(event.clone(), a, b);
+        }) as Box<dyn FnMut(&Event)>));
+    }
+
+    // 0
+    pub fn set_text(&mut self, text: &str) {
+        self.stack.top().set_text_content(Some(text));
+    }
+
+    // 1
+    pub fn remove_self_and_next_siblings(&mut self) {
+        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
+    pub fn replace_with(&mut self) {
+        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
+    pub fn set_attribute(&mut self, name: &str, value: &str) {
+        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
+    pub fn remove_attribute(&mut self, name: &str) {
+        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
+    pub fn push_reverse_child(&mut self, n: u32) {
+        let parent = self.stack.top();
+        let children = parent.child_nodes();
+        let child = children.get(children.length() - n - 1).unwrap();
+        self.stack.push(child);
+    }
+
+    // 6
+    pub fn pop_push_child(&mut self, n: u32) {
+        self.stack.pop();
+        let parent = self.stack.top();
+        let children = parent.child_nodes();
+        let child = children.get(n).unwrap();
+        self.stack.push(child);
+    }
+
+    // 7
+    pub fn pop(&mut self) {
+        self.stack.pop();
+    }
+
+    // 8
+    pub fn append_child(&mut self) {
+        let child = self.stack.pop();
+        self.stack.top().append_child(&child).unwrap();
+    }
+
+    // 9
+    pub fn create_text_node(&mut self, text: &str) {
+        self.stack.push(
+            self.document
+                .create_text_node(text)
+                .dyn_into::<Node>()
+                .unwrap(),
+        );
+    }
+
+    // 10
+    pub fn create_element(&mut self, tag_name: &str) {
+        let el = self
+            .document
+            .create_element(tag_name)
+            .unwrap()
+            .dyn_into::<Node>()
+            .unwrap();
+        self.stack.push(el);
+    }
+
+    // 11
+    pub fn new_event_listener(&mut self, event_type: &str, a: u32, b: u32) {
+        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
+    pub fn update_event_listener(&mut self, event_type: &str, a: u32, b: u32) {
+        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
+    pub fn remove_event_listener(&mut self, event_type: &str) {
+        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();
+        }
+    }
+
+    // 16
+    pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
+        let el = self
+            .document
+            .create_element_ns(Some(ns), tag_name)
+            .unwrap()
+            .dyn_into::<Node>()
+            .unwrap();
+        self.stack.push(el);
+    }
+
+    // 17
+    pub fn save_children_to_temporaries(&mut self, mut temp: u32, start: u32, end: u32) {
+        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;
+        }
+    }
+
+    // 18
+    pub fn push_child(&mut self, n: u32) {
+        let parent = self.stack.top();
+        let child = parent.child_nodes().get(n).unwrap();
+        self.stack.push(child);
+    }
+
+    // 19
+    pub fn push_temporary(&mut self, temp: u32) {
+        let t = self.temporaries.get(&temp).unwrap().clone();
+        self.stack.push(t);
+    }
+
+    // 20
+    pub fn insert_before(&mut self) {
+        let before = self.stack.pop();
+        let after = self.stack.pop();
+        after
+            .parent_node()
+            .unwrap()
+            .insert_before(&before, Some(&after))
+            .unwrap();
+        self.stack.push(before);
+    }
+
+    // 21
+    pub fn pop_push_reverse_child(&mut self, n: u32) {
+        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);
+    }
+
+    // 22
+    pub fn remove_child(&mut self, n: u32) {
+        let parent = self.stack.top();
+        if let Some(child) = parent.child_nodes().get(n).unwrap().dyn_ref::<Element>() {
+            child.remove();
+        }
+    }
+
+    // 23
+    pub fn set_class(&mut self, class_name: &str) {
+        if let Some(el) = self.stack.top().dyn_ref::<Element>() {
+            el.set_class_name(class_name);
+        }
+    }
+
+    // 24
+    pub fn save_template(&mut self, id: CacheId) {
+        let template = self.stack.top();
+        let t = template.clone_node_with_deep(true).unwrap();
+        self.templates.insert(id, t);
+    }
+
+    // 25
+    pub fn push_template(&mut self, id: CacheId) {
+        let template = self.get_template(id).unwrap();
+        let t = template.clone_node_with_deep(true).unwrap();
+        self.stack.push(t);
+    }
+
+    pub fn has_template(&self, id: CacheId) -> bool {
+        self.templates.contains_key(&id)
+    }
+}

+ 1 - 1
packages/web/src/lib.rs

@@ -24,7 +24,7 @@ use dioxus_core::{
 };
 use futures::{channel::mpsc, future, SinkExt, StreamExt};
 use mpsc::UnboundedSender;
-
+pub mod interpreter;
 /// 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