فهرست منبع

feat: use walking pattern

Jonathan Kelley 2 سال پیش
والد
کامیت
22d4bf7346

+ 0 - 6
packages/core/Cargo.toml

@@ -34,17 +34,11 @@ slab = "0.4"
 
 futures-channel = "0.3.21"
 
-# used for noderefs
-once_cell = "1.8"
-
 indexmap = "1.7"
 
 # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
 
-# todo: I want to get rid of this
-backtrace = "0.3"
-
 [features]
 default = []
 serialize = ["serde"]

+ 5 - 0
packages/core/architecture.md

@@ -114,6 +114,11 @@ Some essential reading:
 - https://web.dev/rail/
 - https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
 
+# Templates
+
+If everything is a template, then we'll have the idea that the only children can b Templates
+
+
 # What's going on?
 
 Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI

+ 124 - 197
packages/core/src/diff.rs

@@ -112,9 +112,7 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
 
         self.scope_stack.push(scopeid);
         self.element_stack.push(scope.container);
-        {
-            self.diff_node(old, new);
-        }
+        self.diff_node(old, new);
         self.element_stack.pop();
         self.scope_stack.pop();
 
@@ -122,58 +120,45 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
     }
 
     pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) {
-        use VNode::{Component, Element, Fragment, Placeholder, Template, Text};
-        match (old_node, new_node) {
-            (Text(old), Text(new)) => {
-                self.diff_text_nodes(old, new, old_node, new_node);
-            }
-
-            (Element(old), Element(new)) => {
-                self.diff_element_nodes(old, new, old_node, new_node);
-            }
-
-            (Component(old), Component(new)) => {
-                self.diff_component_nodes(old_node, new_node, *old, *new);
-            }
-
-            (Fragment(old), Fragment(new)) => {
-                self.diff_fragment_nodes(old, new);
-            }
+        use VNode::{Component, Element, Fragment, Template, Text};
 
-            (Template(old), Template(new)) => {
-                self.diff_templates(old, new, old_node, new_node);
-            }
+        // Same node by ref, no need to diff.
+        if std::ptr::eq(old_node, new_node) {
+            return;
+        }
 
-            (Placeholder(_), Placeholder(_)) => {
-                self.diff_placeholder_nodes(old_node, new_node);
-            }
+        match (old_node, new_node) {
+            (Text(old), Text(new)) => self.diff_text(old, new, old_node, new_node),
+            (Element(old), Element(new)) => self.diff_element(old, new, old_node, new_node),
+            (Component(old), Component(new)) => self.diff_component(old_node, new_node, *old, *new),
+            (Fragment(old), Fragment(new)) => self.diff_fragment(old, new),
+            (Template(old), Template(new)) => self.diff_templates(old, new, old_node, new_node),
 
             (
-                Component(_) | Text(_) | Element(_) | Template(_) | Fragment(_) | Placeholder(_),
-                Component(_) | Text(_) | Element(_) | Template(_) | Fragment(_) | Placeholder(_),
+                Component(_) | Text(_) | Element(_) | Template(_) | Fragment(_),
+                Component(_) | Text(_) | Element(_) | Template(_) | Fragment(_),
             ) => self.replace_node(old_node, new_node),
         }
     }
 
     pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize {
         match node {
-            VNode::Text(vtext) => self.create_text_node(vtext, node),
-            VNode::Element(element) => self.create_element_node(element, node),
-            VNode::Fragment(frag) => self.create_fragment_node(*frag),
+            VNode::Text(vtext) => self.create_text(vtext, node),
+            VNode::Element(element) => self.create_element(element, node),
+            VNode::Fragment(frag) => self.create_fragment(frag),
             VNode::Component(component) => self.create_component_node(*component),
             VNode::Template(template) => self.create_template_node(template, node),
-            VNode::Placeholder(placeholder) => todo!(),
         }
     }
 
-    fn create_text_node(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>) -> usize {
+    fn create_text(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>) -> usize {
         let real_id = self.scopes.reserve_node(node);
         text.id.set(Some(real_id));
         self.mutations.create_text_node(text.text, real_id);
         1
     }
 
-    fn create_element_node(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize {
+    fn create_element(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize {
         let VElement {
             tag: tag_name,
             listeners,
@@ -198,7 +183,7 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
             let cur_scope_id = self.current_scope();
 
             for listener in listeners.iter() {
-                listener.mounted_node.set(Some(real_id));
+                listener.mounted_node.set(real_id);
                 self.mutations.new_event_listener(listener, cur_scope_id);
             }
 
@@ -216,7 +201,7 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
         1
     }
 
-    fn create_fragment_node(&mut self, frag: &'b VFragment<'b>) -> usize {
+    fn create_fragment(&mut self, frag: &'b VFragment<'b>) -> usize {
         self.create_children(frag.children)
     }
 
@@ -274,17 +259,13 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
         created
     }
 
-    pub(crate) fn diff_text_nodes(
+    pub(crate) fn diff_text(
         &mut self,
         old: &'b VText<'b>,
         new: &'b VText<'b>,
         _old_node: &'b VNode<'b>,
         new_node: &'b VNode<'b>,
     ) {
-        if std::ptr::eq(old, new) {
-            return;
-        }
-
         // if the node is comming back not assigned, that means it was borrowed but removed
         let root = match old.id.get() {
             Some(id) => id,
@@ -307,183 +288,138 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
         old_node: &'b VNode<'b>,
         new_node: &'b VNode<'b>,
     ) {
-        if old.template.id == new.template.id {
-            // if they're the same, just diff the dynamic nodes directly
-            for (left, right) in old.dynamic_nodes.iter().zip(new.dynamic_nodes.iter()) {
-                self.diff_node(left, right);
-            }
+        if old.template.id != new.template.id {
+            return self.replace_node(old_node, new_node);
+        }
 
-            // todo: need a way to load up the element bound to these attributes
-            for (left, right) in old.dynamic_attrs.iter().zip(new.dynamic_attrs.iter()) {
-                //
+        // if they're the same, just diff the dynamic nodes directly
+        for (left, right) in old.dynamic_nodes.iter().zip(new.dynamic_nodes.iter()) {
+            self.diff_node(&left.node, &right.node);
+        }
+
+        for (left, right) in old.dynamic_attrs.iter().zip(new.dynamic_attrs.iter()) {
+            let id = left.mounted_element.get();
+            right.mounted_element.set(id);
+
+            for (left, right) in right.attrs.iter().zip(left.attrs.iter()) {
+                if right.value != left.value || right.volatile {
+                    self.mutations
+                        .set_attribute(right.name, right.value, right.namespace, id);
+                }
             }
 
-            // hmm, what we do here?
-            for (left, right) in old.listeners.iter().zip(new.listeners.iter()) {
-                //
+            // There's not really any diffing that needs to happen for listeners
+            for listener in right.listeners {
+                listener.mounted_node.set(id);
             }
-        } else {
-            // else, diff them manually, taking the slow path
-            self.replace_node(old_node, new_node);
         }
     }
 
-    fn create_static_template_nodes(&mut self, node: &'b TemplateNode) {
-        let id = ElementId(999999);
-        match node {
+    fn create_static_template_nodes(&mut self, node: &'b TemplateNode, id: ElementId) {
+        match *node {
             TemplateNode::Element {
                 tag,
                 attrs,
                 children,
+                namespace,
             } => {
-                self.mutations.create_element(tag, None, id);
-
-                for attr in *attrs {
-                    match attr {
-                        crate::TemplateAttribute::Dynamic(_) => todo!(),
-                        crate::TemplateAttribute::Static { name, value } => {
-                            self.mutations.set_attribute(
-                                name,
-                                AttributeValue::Text(value),
-                                None,
-                                id,
-                            );
-                        }
-                    }
+                self.mutations.create_element(tag, namespace, id);
+                for attr in attrs {
+                    self.mutations.set_attribute(
+                        attr.name,
+                        AttributeValue::Text(attr.value),
+                        attr.namespace,
+                        id,
+                    );
                 }
-
                 for child in children.iter() {
-                    self.create_static_template_nodes(child);
+                    self.create_static_template_nodes(child, id);
                 }
                 self.mutations.append_children(children.len() as u32);
             }
-            TemplateNode::Text(ref text) => self.mutations.create_text_node(text, id),
+            TemplateNode::Text(text) => self.mutations.create_text_node(text, id),
             TemplateNode::Dynamic(_) => self.mutations.create_placeholder(id),
         }
     }
 
     /// Create the template from scratch using instructions, cache it, and then use the instructions to build it
+    ///
+    /// This would be way easier if the ID could just be unique *after* cloning
+    ///
+    /// If we traversed the template
     fn create_template_node(&mut self, template: &'b VTemplate<'b>, node: &'b VNode<'b>) -> usize {
-        let template_id = template.template.id;
-        let templates = self.scopes.template_cache.borrow_mut();
+        // Reserve a single node for all the template nodes to reuse
+        template.node_id.set(self.scopes.reserve_node(node));
 
-        // create and insert the template if it doesn't exist within the VirtualDom (it won't exist on the renderer either)
+        // Save the template if it doesn't exist
+        // todo: use &mut cache instead of borrowed cache
+        let mut templates = self.scopes.template_cache.borrow_mut();
         if !templates.contains(&template.template) {
             template
                 .template
                 .roots
                 .into_iter()
-                .for_each(|node| self.create_static_template_nodes(node));
+                .for_each(|node| self.create_static_template_nodes(node, template.node_id.get()));
 
             self.mutations
-                .save(template_id, template.template.roots.len() as u32);
-        }
-
-        self.mutations.load(template_id);
-
-        let mut created = 0;
-
-        // create the dynamic nodes
-        for node in template.dynamic_nodes.iter() {
-            created += self.create_node(node);
-        }
+                .save(template.template.id, template.template.roots.len() as u32);
 
-        // set any dynamic attributes
-        for attr in template.dynamic_attrs.iter() {
-            // assign an ID to the element
-            let id = self.scopes.reserve_node(node);
-
-            self.mutations
-                .set_attribute(attr.name, attr.value, attr.namespace, id);
+            templates.insert(template.template);
         }
 
-        todo!()
-
-        // let def = template.template;
-
-        // let mut templates = self.scopes.template_cache.borrow_mut();
+        // Walk the roots backwards, creating nodes and assigning IDs
+        let mut dynamic_attrs = template.dynamic_attrs.iter().peekable();
+        let mut dynamic_nodes = template.dynamic_nodes.iter().peekable();
 
-        // if !templates.contains(&def) {
-        //     // self.create_template_def(def);
-        //     templates.insert(def);
-        // }
-
-        // let mut nodes_created = 0;
-        // let mut dynamic_nodes = template.dynamic_nodes.iter().enumerate().rev();
-
-        // for node in template.rendered_nodes {
-        //     match node {
-        //         // Give it an ID
-        //         crate::innerlude::TemplateRoots::Static(_) => todo!(),
+        let mut on_stack = 0;
+        for (root_idx, root) in template.template.roots.iter().enumerate() {
+            on_stack += match root {
+                TemplateNode::Dynamic(id) => self.create_node(&template.dynamic_nodes[*id].node),
+                TemplateNode::Element { .. } | TemplateNode::Text(_) => 1,
+            };
 
-        //         // let the creation step give it an ID
-        //         crate::innerlude::TemplateRoots::Runtime(_) => todo!(),
-        //     }
-        // }
+            // we're on top of a node that has a dynamic attribute for a descndent
+            // Set that attribute now before the stack gets in a weird state
+            // Roots may not be more than 255 nodes long, enforced by the macro
+            while let Some(loc) = dynamic_attrs.next_if(|attr| attr.pathway[0] == root_idx as u8) {
+                // Attach all the elementIDs to the nodes with dynamic content
+                let id = self.scopes.reserve_node(node);
+                self.mutations.assign_id(&loc.pathway[1..], id);
+                loc.mounted_element.set(id);
+
+                for attr in loc.attrs {
+                    self.mutations
+                        .set_attribute(attr.name, attr.value, attr.namespace, id);
+                }
 
-        // // Create all the dynamic nodes and merge them into the template
-        // for (index, node) in dynamic_nodes {
-        //     let new_created = self.create_node(node);
-        //     self.mutations.edits.push(DomEdit::MergeTemplate {
-        //         index: index as u32,
-        //         num_children: new_created as u32,
-        //     })
-        // }
+                for listener in loc.listeners {
+                    listener.mounted_node.set(id);
+                }
+            }
 
-        // nodes_created
-    }
+            while let Some(dyn_node) = dynamic_nodes.next_if(|f| f.pathway[0] == root_idx as u8) {
+                // we're on top of a node that has a dynamic node for a descndent
+                // Set that node now
+                // Roots may not be more than 255 nodes long, enforced by the macro
+                if dyn_node.pathway[0] == root_idx as u8 {
+                    let created = self.create_node(&dyn_node.node);
 
-    fn create_template_static_node(&mut self, nodes: &'static [VNode<'static>]) -> usize {
-        todo!()
-        // let mut created = 0;
-        // for node in nodes {
-        //     match *node {
-        //         TemplateNode::Element(el) => {
-        //             for attr in el.attributes {
-        //                 match attr {
-        //                     crate::template::TemplateAttribute::Dynamic => todo!(),
-        //                     crate::template::TemplateAttribute::Static { attr } => {
-        //                         self.mutations.set_attribute(attr, 0);
-        //                     }
-        //                 }
-        //             }
-
-        //             self.mutations.create_element(el.tag, None, ElementId(0));
-
-        //             if !nodes.is_empty() {
-        //                 let res = self.create_template_static_node(nodes);
-        //                 self.mutations.append_children(res as u32);
-        //             }
-
-        //             created += 1;
-        //         }
-        //         TemplateNode::StaticText(text) => {
-        //             self.mutations.create_text_node(text, ElementId(0));
-        //             created += 1;
-        //         }
-        //         TemplateNode::DynamicText(_)
-        //         | TemplateNode::DynamicExpr(_)
-        //         | TemplateNode::DynamicComponent(_) => {
-        //             self.mutations.create_placeholder(ElementId(0));
-        //             created += 1;
-        //         }
-        //     }
-        // }
+                    self.mutations
+                        .replace_descendant(&dyn_node.pathway[1..], created as u32);
+                }
+            }
+        }
 
-        // created
+        on_stack
     }
 
-    fn diff_element_nodes(
+    fn diff_element(
         &mut self,
         old: &'b VElement<'b>,
         new: &'b VElement<'b>,
         old_node: &'b VNode<'b>,
         new_node: &'b VNode<'b>,
     ) {
-        if std::ptr::eq(old, new) {
-            return;
-        }
-
         // if the node is comming back not assigned, that means it was borrowed but removed
         let root = match old.id.get() {
             Some(id) => id,
@@ -563,7 +499,7 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
                 self.mutations.remove_event_listener(listener.event, root);
             }
             for listener in new.listeners {
-                listener.mounted_node.set(Some(root));
+                listener.mounted_node.set(root);
                 self.mutations.new_event_listener(listener, cur_scope_id);
             }
         }
@@ -580,7 +516,7 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
         };
     }
 
-    fn diff_component_nodes(
+    fn diff_component(
         &mut self,
         old_node: &'b VNode<'b>,
         new_node: &'b VNode<'b>,
@@ -592,10 +528,6 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
             .get()
             .expect("existing component nodes should have a scope");
 
-        if std::ptr::eq(old, new) {
-            return;
-        }
-
         // Make sure we're dealing with the same component (by function pointer)
         if old.user_fc == new.user_fc {
             self.enter_scope(scope_addr);
@@ -654,11 +586,7 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
         }
     }
 
-    fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) {
-        if std::ptr::eq(old, new) {
-            return;
-        }
-
+    fn diff_fragment(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) {
         todo!()
         // // This is the case where options or direct vnodes might be used.
         // // In this case, it's faster to just skip ahead to their diff
@@ -1086,7 +1014,8 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
                 self.scopes.collect_garbage(id);
             }
 
-            VNode::Text(_) | VNode::Placeholder(_) => {
+            // | VNode::Placeholder(_)
+            VNode::Text(_) => {
                 let id = old
                     .try_mounted_id()
                     .unwrap_or_else(|| panic!("broke on {:?}", old));
@@ -1145,15 +1074,15 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
                         }
                     }
                 }
-                VNode::Placeholder(a) => {
-                    let id = a.id.get().unwrap();
-                    self.scopes.collect_garbage(id);
-                    a.id.set(None);
-
-                    if gen_muts {
-                        self.mutations.remove(id);
-                    }
-                }
+                // VNode::Placeholder(a) => {
+                //     let id = a.id.get().unwrap();
+                //     self.scopes.collect_garbage(id);
+                //     a.id.set(None);
+
+                //     if gen_muts {
+                //         self.mutations.remove(id);
+                //     }
+                // }
                 VNode::Element(e) => {
                     let id = e.id.get().unwrap();
 
@@ -1242,8 +1171,7 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
                 }
                 VNode::Template(c) => {
                     todo!()
-                }
-                VNode::Placeholder(_) => todo!(),
+                } // VNode::Placeholder(_) => todo!(),
             }
         }
     }
@@ -1261,8 +1189,7 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
                 }
                 VNode::Template(t) => {
                     todo!()
-                }
-                VNode::Placeholder(_) => todo!(),
+                } // VNode::Placeholder(_) => todo!(),
             }
         }
     }
@@ -1270,7 +1197,7 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
     // recursively push all the nodes of a tree onto the stack and return how many are there
     fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
         match node {
-            VNode::Text(_) | VNode::Element(_) | VNode::Placeholder(_) => {
+            VNode::Text(_) | VNode::Element(_) => {
                 self.mutations.push_root(node.mounted_id());
                 1
             }
@@ -1294,7 +1221,7 @@ impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
         }
     }
 
-    pub(crate) fn diff_placeholder_nodes(&self, old_node: &VNode, new_node: &VNode) {
+    pub(crate) fn diff_placeholder(&self, old_node: &VNode, new_node: &VNode) {
         todo!()
     }
 }

+ 35 - 7
packages/core/src/mutations.rs

@@ -20,6 +20,7 @@ pub trait Renderer<'a> {
     fn pop_root(&mut self);
     /// Replace the given element with the next m elements on the stack
     fn replace_with(&mut self, root: ElementId, m: u32);
+
     /// Insert the next m elements on the stack after the given element
     fn insert_after(&mut self, root: ElementId, n: u32);
     /// Insert the next m elements on the stack before the given element
@@ -41,6 +42,8 @@ pub trait Renderer<'a> {
     fn remove(&mut self, root: ElementId);
     /// Remove an attribute from an existing element
     fn remove_attribute(&mut self, attribute: &Attribute, root: ElementId);
+    /// Remove all the children of the given element
+    fn remove_children(&mut self, root: ElementId);
 
     /// Attach a new listener to the dom
     fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId);
@@ -58,13 +61,38 @@ pub trait Renderer<'a> {
         root: ElementId,
     );
 
-    /// Save the current n nodes to the ID to be loaded later
-    fn save(&mut self, id: &str, num: u32);
-    /// Loads a set of saved nodes from the ID
-    fn load(&mut self, id: &str);
+    /// General statistics for doing things that extend outside of the renderer
+    fn mark_dirty_scope(&mut self, scope: ScopeId);
 
-    // General statistics for doing things that extend outside of the renderer
+    /// Save the current n nodes to the ID to be loaded later
+    fn save(&mut self, id: &'static str, num: u32);
+    /// Loads a set of saved nodes from the ID into a scratch space
+    fn load(&mut self, id: &'static str, index: u32);
+    /// Assign the element on the stack's descendent the given ID
+    fn assign_id(&mut self, descendent: &'static [u8], id: ElementId);
+    /// Replace the given element of the topmost element with the next m elements on the stack
+    /// Is essentially a combination of assign_id and replace_with
+    fn replace_descendant(&mut self, descendent: &'static [u8], m: u32);
+}
 
-    ///
-    fn mark_dirty_scope(&mut self, scope: ScopeId);
+/*
+div {
+    div {
+        div {
+            div {}
+        }
+    }
 }
+
+push_child(0)
+push_child(1)
+push_child(3)
+push_child(4)
+pop
+pop
+
+clone_node(0)
+set_node(el, [1,2,3,4])
+set_attribute("class", "foo")
+append_child(1)
+*/

+ 72 - 54
packages/core/src/nodes/arbitrary_value.rs

@@ -1,6 +1,6 @@
 use std::{
     any::Any,
-    fmt::{Arguments, Display, Formatter},
+    fmt::{Arguments, Formatter},
 };
 
 use bumpalo::Bump;
@@ -8,41 +8,40 @@ use bumpalo::Bump;
 /// Possible values for an attribute
 #[derive(Clone, Copy)]
 pub enum AttributeValue<'a> {
+    /// Reference strs, most common
     Text(&'a str),
-    Float32(f32),
+    /// Basic float values
+    Float(f32),
+    /// Basic Int values
+    Int(i32),
+    /// Basic bool values
     Bool(bool),
+    /// Everything else
     Any(&'a dyn AnyAttributeValue),
 }
 
-// #[cfg(feature = "serialize")]
-
 impl<'a> PartialEq for AttributeValue<'a> {
     fn eq(&self, other: &Self) -> bool {
         match (self, other) {
             (Self::Text(l0), Self::Text(r0)) => l0 == r0,
-            (Self::Float32(l0), Self::Float32(r0)) => l0 == r0,
+            (Self::Float(l0), Self::Float(r0)) => l0 == r0,
+            (Self::Int(l0), Self::Int(r0)) => l0 == r0,
             (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
-            // (Self::Any(l0), Self::Any(r0)) => l0.cmp(r0),
+            (Self::Any(l0), Self::Any(r0)) => (*l0).cmp_any(*r0),
             _ => false,
         }
     }
 }
 
-impl<'a> Display for AttributeValue<'a> {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        todo!()
-    }
-}
-
 impl std::fmt::Debug for AttributeValue<'_> {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        todo!()
-        // match self {
-        //     AttributeValue::Text(s) => write!(f, "AttributeValue::Text({:?})", s),
-        //     AttributeValue::Float32(f) => write!(f, "AttributeValue::Float32({:?})", f),
-        //     AttributeValue::Bool(b) => write!(f, "AttributeValue::Bool({:?})", b),
-        //     AttributeValue::Any(a) => write!(f, "AttributeValue::Any({:?})", a),
-        // }
+        match self {
+            AttributeValue::Text(s) => write!(f, "AttributeValue::Text({:?})", s),
+            AttributeValue::Float(v) => write!(f, "AttributeValue::Float({:?})", v),
+            AttributeValue::Int(v) => write!(f, "AttributeValue::Int({:?})", v),
+            AttributeValue::Bool(b) => write!(f, "AttributeValue::Bool({:?})", b),
+            AttributeValue::Any(_) => write!(f, "AttributeValue::Any()"),
+        }
     }
 }
 
@@ -51,24 +50,27 @@ pub trait IntoAttributeValue<'a> {
     /// Convert into an attribute value
     fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
 }
+
+impl<'a> IntoAttributeValue<'a> for &'a str {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Text(self)
+    }
+}
 impl<'a> IntoAttributeValue<'a> for f32 {
     fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Float32(self)
+        AttributeValue::Float(self)
     }
 }
-
-impl<'a> IntoAttributeValue<'a> for bool {
+impl<'a> IntoAttributeValue<'a> for i32 {
     fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Bool(self)
+        AttributeValue::Int(self)
     }
 }
-
-impl<'a> IntoAttributeValue<'a> for &'a str {
+impl<'a> IntoAttributeValue<'a> for bool {
     fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        AttributeValue::Text(self)
+        AttributeValue::Bool(self)
     }
 }
-
 impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
     fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
         use bumpalo::core_alloc::fmt::Write;
@@ -78,29 +80,6 @@ impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
     }
 }
 
-impl<'a, T> IntoAttributeValue<'a> for &'a T
-where
-    T: PartialEq,
-{
-    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
-        todo!()
-        // AttributeValue::Any(ArbitraryAttributeValue {
-        //     value: self,
-        //     cmp: |a, b| {
-        //         if let Some(a) = a.as_any().downcast_ref::<T>() {
-        //             if let Some(b) = b.as_any().downcast_ref::<T>() {
-        //                 a == b
-        //             } else {
-        //                 false
-        //             }
-        //         } else {
-        //             false
-        //         }
-        //     },
-        // })
-    }
-}
-
 // todo
 #[allow(missing_docs)]
 impl<'a> AttributeValue<'a> {
@@ -130,16 +109,55 @@ impl<'a> AttributeValue<'a> {
 /// If you want to override the default behavior, you should implement PartialEq through a wrapper type
 pub trait AnyAttributeValue: Any {
     /// Perform a comparison between two values
-    fn cmp_any(&self, _other: &dyn Any) -> bool {
+    fn cmp_any<'a>(&'a self, _other: &'a dyn AnyAttributeValue) -> bool {
         false
     }
 }
 
 impl<T: Any + PartialEq> AnyAttributeValue for T {
-    fn cmp_any(&self, other: &dyn Any) -> bool {
-        match other.downcast_ref::<T>() {
-            Some(t) => self == t,
+    fn cmp_any(&self, other: &dyn AnyAttributeValue) -> bool {
+        // we can't, for whatever reason use other as &dyn Any
+        let right: &dyn Any = unsafe { std::mem::transmute(other) };
+
+        match right.downcast_ref::<T>() {
+            Some(right) => self == right,
             None => false,
         }
     }
 }
+
+#[test]
+fn cmp_any_works_even_though_it_transmutes() {
+    // same type, same value
+    let a = 2;
+    let b = 2;
+    assert!(a.cmp_any(&b as &dyn AnyAttributeValue));
+
+    // same type, different value
+    let a = "asds";
+    let b = "asdsasd";
+    assert!(!a.cmp_any(&b as &dyn AnyAttributeValue));
+
+    // different type, different values
+    let a = 123;
+    let b = "asdsasd";
+    assert!(!a.cmp_any(&b as &dyn AnyAttributeValue));
+
+    // Custom structs
+    #[derive(PartialEq)]
+    struct CustomStruct {
+        a: i32,
+    }
+
+    let a = CustomStruct { a: 1 };
+    let b = CustomStruct { a: 1 };
+    assert!(a.cmp_any(&b as &dyn AnyAttributeValue));
+
+    let a = CustomStruct { a: 1 };
+    let b = CustomStruct { a: 123 };
+    assert!(!a.cmp_any(&b as &dyn AnyAttributeValue));
+
+    let a = CustomStruct { a: 1 };
+    let b = "asdasd";
+    assert!(!a.cmp_any(&b as &dyn AnyAttributeValue));
+}

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

@@ -70,10 +70,6 @@ pub struct Attribute<'a> {
     /// Used in controlled components to ensure changes are propagated.
     pub volatile: bool,
 
-    /// A reverse lookup for tracking down attributes for templates
-    /// Not used for anything else
-    pub mounted_node: Cell<Option<ElementId>>,
-
     /// The value of the attribute.
     pub value: AttributeValue<'a>,
 }
@@ -83,7 +79,7 @@ pub struct Attribute<'a> {
 pub struct Listener<'bump> {
     /// The ID of the node that this listener is mounted to
     /// Used to generate the event listener's ID on the DOM
-    pub mounted_node: Cell<Option<ElementId>>,
+    pub mounted_node: Cell<ElementId>,
 
     /// The type of event to listen for.
     ///

+ 10 - 19
packages/core/src/nodes/factory.rs

@@ -108,7 +108,6 @@ impl<'a> NodeFactory<'a> {
             name,
             namespace,
             volatile: is_volatile,
-            mounted_node: Default::default(),
             value: val.into_value(self.bump),
         }
     }
@@ -125,7 +124,6 @@ impl<'a> NodeFactory<'a> {
             name,
             namespace,
             volatile: is_volatile,
-            mounted_node: Default::default(),
             value,
         }
     }
@@ -172,7 +170,7 @@ impl<'a> NodeFactory<'a> {
     pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> {
         Listener {
             event,
-            mounted_node: Cell::new(None),
+            mounted_node: Cell::new(ElementId(0)),
             callback,
         }
     }
@@ -234,27 +232,20 @@ impl<'a> NodeFactory<'a> {
     /// Create a refrence to a template
     pub fn template_ref(
         &self,
-        template: fn() -> Template<'static>,
+        template: Template<'static>,
         nodes: &'a [VNode<'a>],
         attributes: &'a [Attribute<'a>],
         listeners: &'a [Listener<'a>],
         key: Option<Arguments>,
     ) -> VNode<'a> {
-        // let borrow_ref = self.scope.templates.borrow();
-        // // We only create the template if it doesn't already exist to allow for hot reloading
-        // if !borrow_ref.contains_key(&id) {
-        //     drop(borrow_ref);
-        //     let mut borrow_mut = self.scope.templates.borrow_mut();
-        //     borrow_mut.insert(id.clone(), Rc::new(RefCell::new(template)));
-        // }
-        todo!()
-        // VNode::TemplateRef(self.bump.alloc(VTemplate {
-        //     dynamic_context,
-        //     template_id: id,
-        //     node_ids: RefCell::new(Vec::new()),
-        //     parent: Cell::new(None),
-        //     template_ref_id: Cell::new(None),
-        // }))
+        VNode::Template(self.bump.alloc(VTemplate {
+            key: None,
+            node_id: Cell::new(ElementId(0)),
+            template,
+            dynamic_nodes: self.bump.alloc([]),
+            dynamic_attrs: self.bump.alloc([]),
+            listeners,
+        }))
     }
 }
 

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

@@ -6,7 +6,7 @@ pub struct VFragment<'src> {
     /// The key of the fragment to be used during keyed diffing.
     pub key: Option<&'src str>,
 
-    /// The [`ElementId`] of the placeholder.
+    /// The [`ElementId`] of the placeholder if it exists
     pub placeholder: Cell<Option<ElementId>>,
 
     /// Fragments can never have zero children. Enforced by NodeFactory.

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

@@ -5,7 +5,6 @@
 
 use std::fmt::{Debug, Formatter};
 
-mod Placeholder;
 mod arbitrary_value;
 mod component;
 mod element;
@@ -25,7 +24,7 @@ pub use suspense::*;
 pub use template::*;
 pub use text::*;
 
-use self::Placeholder::VPlaceholder;
+use self::placeholder::VPlaceholder;
 
 /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
 ///
@@ -117,9 +116,6 @@ pub enum VNode<'src> {
     ///
     ///
     Template(&'src VTemplate<'src>),
-
-    ///
-    Placeholder(&'src VPlaceholder),
 }
 
 /// An Element's unique identifier.
@@ -140,7 +136,6 @@ impl<'src> VNode<'src> {
             VNode::Fragment(f) => f.key,
             VNode::Template(t) => t.key,
             VNode::Text(_t) => None,
-            VNode::Placeholder(_p) => None,
         }
     }
 
@@ -161,7 +156,6 @@ impl<'src> VNode<'src> {
             VNode::Fragment(_) => None,
             VNode::Component(_) => None,
             VNode::Template(_) => None,
-            VNode::Placeholder(el) => el.id.get(),
         }
     }
 
@@ -173,7 +167,6 @@ impl<'src> VNode<'src> {
             VNode::Component(c) => VNode::Component(c),
             VNode::Fragment(f) => VNode::Fragment(f),
             VNode::Template(t) => VNode::Template(t),
-            VNode::Placeholder(p) => VNode::Placeholder(p),
         }
     }
 }
@@ -209,10 +202,6 @@ impl Debug for VNode<'_> {
                 .debug_struct("VNode::Templates")
                 .field("template_id", &temp.template.id)
                 .finish(),
-            VNode::Placeholder(place) => s
-                .debug_struct("VNode::Placeholder")
-                .field("id", &place.id)
-                .finish(),
         }
     }
 }

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

@@ -1,7 +1,7 @@
-use std::cell::Cell;
-
 use crate::ElementId;
+use std::cell::Cell;
 
+/// A placeholder node
 pub struct VPlaceholder {
     pub id: Cell<Option<ElementId>>,
     pub dynamic_index: Option<usize>,

+ 32 - 10
packages/core/src/nodes/template.rs

@@ -1,16 +1,25 @@
-use std::hash::Hash;
+use std::{cell::Cell, hash::Hash};
 
-use crate::{Attribute, Listener, VNode};
+use crate::{Attribute, ElementId, Listener, VNode};
 
 /// A reference to a template along with any context needed to hydrate it
 pub struct VTemplate<'a> {
     pub key: Option<&'a str>,
 
+    // The ID assigned for all nodes in this template
+    pub node_id: Cell<ElementId>,
+
+    // Position this template for fragments and stuff
+    pub head_id: Cell<ElementId>,
+
+    pub tail_id: Cell<ElementId>,
+
     pub template: Template<'static>,
 
-    pub dynamic_nodes: &'a [VNode<'a>],
+    /// All the non-root dynamic nodes
+    pub dynamic_nodes: &'a [NodeLocation<'a>],
 
-    pub dynamic_attrs: &'a [Attribute<'a>],
+    pub dynamic_attrs: &'a [AttributeLocation<'a>],
 
     pub listeners: &'a [Listener<'a>],
 }
@@ -26,13 +35,11 @@ pub struct Template<'a> {
 }
 
 impl<'a> Eq for Template<'a> {}
-
 impl<'a> PartialEq for Template<'a> {
     fn eq(&self, other: &Self) -> bool {
         self.id == other.id
     }
 }
-
 impl<'a> Hash for Template<'a> {
     fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
         self.id.hash(state);
@@ -41,8 +48,10 @@ impl<'a> Hash for Template<'a> {
 
 /// A weird-ish variant of VNodes with way more limited types
 pub enum TemplateNode<'a> {
+    /// A simple element
     Element {
         tag: &'static str,
+        namespace: Option<&'static str>,
         attrs: &'a [TemplateAttribute<'a>],
         children: &'a [TemplateNode<'a>],
     },
@@ -50,8 +59,21 @@ pub enum TemplateNode<'a> {
     Dynamic(usize),
 }
 
-pub enum TemplateAttribute<'a> {
-    // todo: more values
-    Static { name: &'static str, value: &'a str },
-    Dynamic(usize),
+pub struct TemplateAttribute<'a> {
+    pub name: &'static str,
+    pub value: &'a str,
+    pub namespace: Option<&'static str>,
+    pub volatile: bool,
+}
+
+pub struct AttributeLocation<'a> {
+    pub pathway: &'static [u8],
+    pub mounted_element: Cell<ElementId>,
+    pub attrs: &'a [Attribute<'a>],
+    pub listeners: &'a [Listener<'a>],
+}
+
+pub struct NodeLocation<'a> {
+    pub pathway: &'static [u8],
+    pub node: VNode<'a>,
 }

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

@@ -1,15 +1,5 @@
-use crate::{
-    innerlude::{
-        AttributeValue, ComponentPtr, Element, IntoAttributeValue, Properties, Scope, ScopeId,
-        ScopeState, Template,
-    },
-    AnyEvent, Component, ElementId,
-};
-use bumpalo::{boxed::Box as BumpBox, Bump};
-use std::{
-    cell::{Cell, RefCell},
-    fmt::{Arguments, Debug, Formatter},
-};
+use crate::ElementId;
+use std::cell::Cell;
 
 /// A bump-allocated string slice and metadata.
 pub struct VText<'src> {

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

@@ -12,7 +12,6 @@ use std::{collections::VecDeque, iter::FromIterator, task::Poll};
 
 /// A virtual node system that progresses user events and diffs UI trees.
 ///
-///
 /// ## Guide
 ///
 /// Components are defined as simple functions that take [`Scope`] and return an [`Element`].
@@ -505,6 +504,13 @@ impl VirtualDom {
         }
     }
 
+    /// Run the virtualdom, waiting for all async components to finish rendering
+    ///
+    /// As they finish rendering, the virtualdom will apply the mutations to the renderer.
+    pub async fn render(&mut self, renderer: &mut impl Renderer<'_>) {
+        //
+    }
+
     /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
     ///
     /// The diff machine expects the RealDom's stack to be the root of the application.

+ 33 - 76
packages/dioxus/tests/rsx_syntax.rs

@@ -1,87 +1,44 @@
 use dioxus::prelude::*;
 use dioxus_core::{Attribute, TemplateAttribute};
+use dioxus_edit_stream::*;
 
 fn basic_syntax_is_a_template(cx: Scope) -> Element {
     let asd = 123;
-
-    let g = rsx! {
-        div {
-            class: "asd",
-            // class: "{asd}",
-            // onclick: move |_| {},
-            // div { "{var}" }
+    let var = 123;
+
+    cx.render(rsx! {
+        div { class: "asd", class: "{asd}",
+            onclick: move |_| {},
+            div { "{var}" }
+            div {
+                h1 { "var" }
+                p { "you're great!" }
+                div {
+                    background_color: "red",
+                    h1 { "var" }
+                    div {
+                        b { "asd" }
+                        "not great"
+                    }
+                }
+                p { "you're great!" }
+            }
         }
-    };
-
-    let __cx = NodeFactory::new(&cx);
-
-    static attrs: &'static [TemplateAttribute<'static>] =
-        &[::dioxus::core::TemplateAttribute::Static(
-            ::dioxus::core::Attribute {
-                name: "class",
-                namespace: None,
-                volatile: false,
-                mounted_node: Default::default(),
-                value: ::dioxus::core::AttributeValue::Text("asd"),
-            },
-        )];
-
-    __cx . template_ref (
-        || :: dioxus :: core :: Template {
-            id : "packages/dioxus/tests/rsx_syntax.rs:7:13:/Users/jonkelley/Development/dioxus/packages/dioxus" ,
-            roots : &[
-                :: dioxus :: core :: TemplateNode :: Element {
-                    tag : dioxus_elements :: div :: TAG_NAME ,
-                    attrs : attrs,
-                    children : & [] ,
-            }] ,
-            } ,
-         __cx . bump () . alloc ([]) , __cx . bump () . alloc ([]) , __cx . bump () . alloc ([]) ,
-        None
-    );
-
-    // let static_attr = ::dioxus::core::TemplateAttribute::Static(::dioxus::core::Attribute {
-    //     name: "class",
-    //     namespace: None,
-    //     volatile: false,
-    //     mounted_node: Default::default(),
-    //     value: ::dioxus::core::AttributeValue::Text("asd"),
-    // });
-
-    // __cx . template_ref (|| :: dioxus :: core :: Template { id : "packages/dioxus/tests/rsx_syntax.rs:7:13:/Users/jonkelley/Development/dioxus/packages/dioxus" , roots : & [:: dioxus :: core :: TemplateNode :: Element { tag : dioxus_elements :: div :: TAG_NAME , attrs : & [static_attr , :: dioxus :: core :: TemplateAttribute :: Dynamic (0usize)] , children : & [] , }] , } , __cx . bump () . alloc ([]) , __cx . bump () . alloc ([__cx . attr (dioxus_elements :: div :: class . 0 , :: core :: fmt :: Arguments :: new_v1 (& [""] , & [:: core :: fmt :: ArgumentV1 :: new_display (& asd)]) , None , false)]) , __cx . bump () . alloc ([]) , None);
+    })
+}
 
-    cx.render(g)
+fn basic_template(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div {"hi!"}
+    })
+}
 
-    // let __cx = NodeFactory::new(&cx);
+#[test]
+fn basic_prints() {
+    let dom = VirtualDom::new(basic_template);
 
-    // let t = __cx.template_ref (
-    //         || :: dioxus :: core :: Template {
-    //             id : "packages/dioxus/tests/rsx_syntax.rs:8:13:/Users/jonkelley/Development/dioxus/packages/dioxus" ,
-    //             roots : & [
-    //                 :: dioxus :: core :: TemplateNode :: Element {
-    //                     tag : dioxus_elements :: div :: TAG_NAME ,
-    //                     attrs : & [:: dioxus :: core :: TemplateAttribute :: Dynamic (0usize)] ,
-    //                     children : & [] ,
-    //                 }
-    //             ],
-    //         },
-    //         &[] ,
-    //         {
-    //             let mut arr = dioxus_core::exports::bumpalo::vec![in __cx.bump()];
-    //             arr.push(Attribute {
-    //                 name: "asd",
-    //                 namespace: None,
-    //                 volatile: false,
-    //                 mounted_node: Default::default(),
-    //                 value: dioxus_core::AttributeValue::Text(
-    //                     __cx.raw_text(format_args!("{asd}")).0
-    //                 ),
-    //             });
-    //             arr.into_bump_slice() as &[::dioxus::core::Attribute]
-    //         },
-    //         & [] ,
-    //         None
-    //     );
+    let renderer = dioxus_edit_stream::Mutations::default();
+    dom.rebuild(&mut renderer);
 
-    // Some(t)
+    dbg!(renderer.edits);
 }

+ 356 - 376
packages/edit-stream/src/lib.rs

@@ -1,378 +1,358 @@
 use dioxus_core::*;
 
-// /// ## Mutations
-// ///
-// /// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
-// /// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
-// /// applied the edits.
-// ///
-// /// Mutations are the only link between the RealDOM and the VirtualDOM.
-// pub struct Mutations<'a> {
-//     /// The list of edits that need to be applied for the RealDOM to match the VirtualDOM.
-//     pub edits: Vec<DomEdit<'a>>,
-
-//     /// The list of Scopes that were diffed, created, and removed during the Diff process.
-//     pub dirty_scopes: FxHashSet<ScopeId>,
-
-//     /// The list of nodes to connect to the RealDOM.
-//     pub refs: Vec<NodeRefMutation<'a>>,
-// }
-
-// impl Debug for Mutations<'_> {
-//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-//         f.debug_struct("Mutations")
-//             .field("edits", &self.edits)
-//             .field("noderefs", &self.refs)
-//             .finish()
-//     }
-// }
-
-// /// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
-// /// network or through FFI boundaries.
-// #[derive(Debug, PartialEq)]
-// #[cfg_attr(
-//     feature = "serialize",
-//     derive(serde::Serialize, serde::Deserialize),
-//     serde(tag = "type")
-// )]
-// pub enum DomEdit<'bump> {
-//     /// Push the given root node onto our stack.
-//     PushRoot {
-//         /// The ID of the root node to push.
-//         root: ElementId,
-//     },
-
-//     /// Pop the topmost node from our stack and append them to the node
-//     /// at the top of the stack.
-//     AppendChildren {
-//         /// How many nodes should be popped from the stack.
-//         /// The node remaining on the stack will be the target for the append.
-//         many: u32,
-//     },
-
-//     /// Replace a given (single) node with a handful of nodes currently on the stack.
-//     ReplaceWith {
-//         /// The ID of the node to be replaced.
-//         root: ElementId,
-
-//         /// How many nodes should be popped from the stack to replace the target node.
-//         m: u32,
-//     },
-
-//     /// Insert a number of nodes after a given node.
-//     InsertAfter {
-//         /// The ID of the node to insert after.
-//         root: ElementId,
-
-//         /// How many nodes should be popped from the stack to insert after the target node.
-//         n: u32,
-//     },
-
-//     /// Insert a number of nodes before a given node.
-//     InsertBefore {
-//         /// The ID of the node to insert before.
-//         root: ElementId,
-
-//         /// How many nodes should be popped from the stack to insert before the target node.
-//         n: u32,
-//     },
-
-//     /// Remove a particular node from the DOM
-//     Remove {
-//         /// The ID of the node to remove.
-//         root: ElementId,
-//     },
-
-//     /// Create a new purely-text node
-//     CreateTextNode {
-//         /// The ID the new node should have.
-//         root: ElementId,
-
-//         /// The textcontent of the node
-//         text: &'bump str,
-//     },
-
-//     /// Create a new purely-element node
-//     CreateElement {
-//         /// The ID the new node should have.
-//         root: ElementId,
-
-//         /// The tagname of the node
-//         tag: &'bump str,
-//     },
-
-//     /// Create a new purely-comment node with a given namespace
-//     CreateElementNs {
-//         /// The ID the new node should have.
-//         root: ElementId,
-
-//         /// The namespace of the node
-//         tag: &'bump str,
-
-//         /// The namespace of the node (like `SVG`)
-//         ns: &'static str,
-//     },
-
-//     /// Create a new placeholder node.
-//     /// In most implementations, this will either be a hidden div or a comment node.
-//     CreatePlaceholder {
-//         /// The ID the new node should have.
-//         root: ElementId,
-//     },
-
-//     /// Create a new Event Listener.
-//     NewEventListener {
-//         /// The name of the event to listen for.
-//         event_name: &'static str,
-
-//         /// The ID of the node to attach the listener to.
-//         scope: ScopeId,
-
-//         /// The ID of the node to attach the listener to.
-//         root: ElementId,
-//     },
-
-//     /// Remove an existing Event Listener.
-//     RemoveEventListener {
-//         /// The ID of the node to remove.
-//         root: ElementId,
-
-//         /// The name of the event to remove.
-//         event: &'static str,
-//     },
-
-//     /// Set the textcontent of a node.
-//     SetText {
-//         /// The ID of the node to set the textcontent of.
-//         root: ElementId,
-
-//         /// The textcontent of the node
-//         text: &'bump str,
-//     },
-
-//     /// Set the value of a node's attribute.
-//     SetAttribute {
-//         /// The ID of the node to set the attribute of.
-//         root: ElementId,
-
-//         /// The name of the attribute to set.
-//         field: &'static str,
-
-//         /// The value of the attribute.
-//         value: AttributeValue<'bump>,
-
-//         // value: &'bump str,
-//         /// The (optional) namespace of the attribute.
-//         /// For instance, "style" is in the "style" namespace.
-//         ns: Option<&'bump str>,
-//     },
-
-//     /// Remove an attribute from a node.
-//     RemoveAttribute {
-//         /// The ID of the node to remove.
-//         root: ElementId,
-
-//         /// The name of the attribute to remove.
-//         name: &'static str,
-
-//         /// The namespace of the attribute.
-//         ns: Option<&'bump str>,
-//     },
-
-//     /// Manually pop a root node from the stack.
-//     PopRoot {
-//         /// The amount of nodes to pop
-//         count: u32,
-//     },
-
-//     /// Remove all the children of an element
-//     RemoveChildren {
-//         /// The root
-//         root: ElementId,
-//     },
-
-//     /// Create a template using the nodes on the stack
-//     StoreTemplate {
-//         /// The ID of the template
-//         name: &'static str,
-
-//         /// The amount of nodes to pop from the stack into the template
-//         num_children: u32,
-
-//         /// Indicies for the nodes to pop from the stack into the template
-//         dynamic_nodes: &'static [&'static [u32]],
-//     },
-
-//     /// Load the template onto the stack
-//     LoadTemplate {
-//         /// The ID of the template
-//         name: &'static str,
-
-//         /// The index of the template body
-//         index: u32,
-
-//         /// Give the node a new ID to remove it later
-//         root: ElementId,
-//     },
-
-//     /// Load n nodes into the kth dynamic node of the template
-//     ///
-//     /// Assumes that the template is already on the stack
-//     MergeTemplate {
-//         /// The index of the dynamic node to merge into
-//         index: u32,
-
-//         /// The amount of nodes to pop from the stack into the template
-//         num_children: u32,
-//     },
-// }
-
-// use DomEdit::*;
-
-// impl<'a> Mutations<'a> {
-//     pub(crate) fn new() -> Self {
-//         Self {
-//             edits: Vec::new(),
-//             refs: Vec::new(),
-//             dirty_scopes: Default::default(),
-//         }
-//     }
-
-//     // Navigation
-//     pub(crate) fn push_root(&mut self, root: ElementId) {
-//         self.edits.push(PushRoot { root });
-//     }
-
-//     // Navigation
-//     pub(crate) fn pop_root(&mut self) {
-//         self.edits.push(PopRoot { count: 1 });
-//     }
-
-//     pub(crate) fn replace_with(&mut self, root: ElementId, m: u32) {
-//         self.edits.push(ReplaceWith { m, root });
-//     }
-
-//     pub(crate) fn insert_after(&mut self, root: ElementId, n: u32) {
-//         self.edits.push(InsertAfter { n, root });
-//     }
-
-//     pub(crate) fn insert_before(&mut self, root: ElementId, n: u32) {
-//         self.edits.push(InsertBefore { n, root });
-//     }
-
-//     pub(crate) fn append_children(&mut self, n: u32) {
-//         self.edits.push(AppendChildren { many: n });
-//     }
-
-//     // Remove Nodes from the dom
-//     pub(crate) fn remove(&mut self, root: ElementId) {
-//         self.edits.push(Remove { root });
-//     }
-
-//     // Create
-//     pub(crate) fn create_text_node(&mut self, text: &'a str, root: ElementId) {
-//         self.edits.push(CreateTextNode { text, root });
-//     }
-
-//     pub(crate) fn create_element(
-//         &mut self,
-//         tag: &'static str,
-//         ns: Option<&'static str>,
-//         id: ElementId,
-//     ) {
-//         match ns {
-//             Some(ns) => self.edits.push(CreateElementNs { root: id, ns, tag }),
-//             None => self.edits.push(CreateElement { root: id, tag }),
-//         }
-//     }
-//     // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
-//     pub(crate) fn create_placeholder(&mut self, id: ElementId) {
-//         self.edits.push(CreatePlaceholder { root: id });
-//     }
-
-//     // events
-//     pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) {
-//         let Listener {
-//             event,
-//             mounted_node,
-//             ..
-//         } = listener;
-
-//         let element_id = mounted_node.get().unwrap();
-
-//         self.edits.push(NewEventListener {
-//             scope,
-//             event_name: event,
-//             root: element_id,
-//         });
-//     }
-//     pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: ElementId) {
-//         self.edits.push(RemoveEventListener { event, root });
-//     }
-
-//     // modify
-//     pub(crate) fn set_text(&mut self, text: &'a str, root: ElementId) {
-//         self.edits.push(SetText { text, root });
-//     }
-
-//     pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: ElementId) {
-//         let Attribute {
-//             name,
-//             value,
-//             namespace,
-//             ..
-//         } = attribute;
-
-//         self.edits.push(SetAttribute {
-//             field: name,
-//             value: value.clone(),
-//             ns: *namespace,
-//             root,
-//         });
-//     }
-
-//     pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: ElementId) {
-//         let Attribute {
-//             name, namespace, ..
-//         } = attribute;
-
-//         self.edits.push(RemoveAttribute {
-//             name,
-//             ns: *namespace,
-//             root,
-//         });
-//     }
-
-//     pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
-//         self.dirty_scopes.insert(scope);
-//     }
-// }
-
-// // refs are only assigned once
-// pub struct NodeRefMutation<'a> {
-//     pub element: &'a mut Option<once_cell::sync::OnceCell<Box<dyn Any>>>,
-//     pub element_id: ElementId,
-// }
-
-// impl<'a> std::fmt::Debug for NodeRefMutation<'a> {
-//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-//         f.debug_struct("NodeRefMutation")
-//             .field("element_id", &self.element_id)
-//             .finish()
-//     }
-// }
-
-// impl<'a> NodeRefMutation<'a> {
-//     pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
-//         self.element
-//             .as_ref()
-//             .and_then(|f| f.get())
-//             .and_then(|f| f.downcast_ref::<T>())
-//     }
-//     pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
-//         self.element
-//             .as_mut()
-//             .and_then(|f| f.get_mut())
-//             .and_then(|f| f.downcast_mut::<T>())
-//     }
-// }
+/// ## Mutations
+///
+/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
+/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
+/// applied the edits.
+///
+/// Mutations are the only link between the RealDOM and the VirtualDOM.
+#[derive(Default)]
+pub struct Mutations<'a> {
+    /// The list of edits that need to be applied for the RealDOM to match the VirtualDOM.
+    pub edits: Vec<DomEdit<'a>>,
+
+    /// The list of Scopes that were diffed, created, and removed during the Diff process.
+    pub dirty_scopes: Vec<ScopeId>,
+}
+
+/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
+/// network or through FFI boundaries.
+#[derive(Debug, PartialEq)]
+#[cfg_attr(
+    feature = "serialize",
+    derive(serde::Serialize, serde::Deserialize),
+    serde(tag = "type")
+)]
+pub enum DomEdit<'bump> {
+    /// Push the given root node onto our stack.
+    PushRoot {
+        /// The ID of the root node to push.
+        root: ElementId,
+    },
+
+    /// Pop the topmost node from our stack and append them to the node
+    /// at the top of the stack.
+    AppendChildren {
+        /// How many nodes should be popped from the stack.
+        /// The node remaining on the stack will be the target for the append.
+        many: u32,
+    },
+
+    /// Replace a given (single) node with a handful of nodes currently on the stack.
+    ReplaceWith {
+        /// The ID of the node to be replaced.
+        root: ElementId,
+
+        /// How many nodes should be popped from the stack to replace the target node.
+        m: u32,
+    },
+
+    /// Insert a number of nodes after a given node.
+    InsertAfter {
+        /// The ID of the node to insert after.
+        root: ElementId,
+
+        /// How many nodes should be popped from the stack to insert after the target node.
+        n: u32,
+    },
+
+    /// Insert a number of nodes before a given node.
+    InsertBefore {
+        /// The ID of the node to insert before.
+        root: ElementId,
+
+        /// How many nodes should be popped from the stack to insert before the target node.
+        n: u32,
+    },
+
+    /// Remove a particular node from the DOM
+    Remove {
+        /// The ID of the node to remove.
+        root: ElementId,
+    },
+
+    /// Create a new purely-text node
+    CreateTextNode {
+        /// The ID the new node should have.
+        root: ElementId,
+
+        /// The textcontent of the node
+        text: &'bump str,
+    },
+
+    /// Create a new purely-element node
+    CreateElement {
+        /// The ID the new node should have.
+        root: ElementId,
+
+        /// The tagname of the node
+        tag: &'bump str,
+    },
+
+    /// Create a new purely-comment node with a given namespace
+    CreateElementNs {
+        /// The ID the new node should have.
+        root: ElementId,
+
+        /// The namespace of the node
+        tag: &'bump str,
+
+        /// The namespace of the node (like `SVG`)
+        ns: &'static str,
+    },
+
+    /// Create a new placeholder node.
+    /// In most implementations, this will either be a hidden div or a comment node.
+    CreatePlaceholder {
+        /// The ID the new node should have.
+        root: ElementId,
+    },
+
+    /// Create a new Event Listener.
+    NewEventListener {
+        /// The name of the event to listen for.
+        event_name: &'static str,
+
+        /// The ID of the node to attach the listener to.
+        scope: ScopeId,
+
+        /// The ID of the node to attach the listener to.
+        root: ElementId,
+    },
+
+    /// Remove an existing Event Listener.
+    RemoveEventListener {
+        /// The ID of the node to remove.
+        root: ElementId,
+
+        /// The name of the event to remove.
+        event: &'static str,
+    },
+
+    /// Set the textcontent of a node.
+    SetText {
+        /// The ID of the node to set the textcontent of.
+        root: ElementId,
+
+        /// The textcontent of the node
+        text: &'bump str,
+    },
+
+    /// Set the value of a node's attribute.
+    SetAttribute {
+        /// The ID of the node to set the attribute of.
+        root: ElementId,
+
+        /// The name of the attribute to set.
+        field: &'static str,
+
+        /// The value of the attribute.
+        value: AttributeValue<'bump>,
+
+        // value: &'bump str,
+        /// The (optional) namespace of the attribute.
+        /// For instance, "style" is in the "style" namespace.
+        ns: Option<&'bump str>,
+    },
+
+    /// Remove an attribute from a node.
+    RemoveAttribute {
+        /// The ID of the node to remove.
+        root: ElementId,
+
+        /// The name of the attribute to remove.
+        name: &'static str,
+
+        /// The namespace of the attribute.
+        ns: Option<&'bump str>,
+    },
+
+    /// Manually pop a root node from the stack.
+    PopRoot {
+        /// The amount of nodes to pop
+        count: u32,
+    },
+
+    /// Remove all the children of an element
+    RemoveChildren {
+        /// The root
+        root: ElementId,
+    },
+
+    /*
+
+    Template stuff
+
+    - load into scratch space
+    - dump nodes into stack
+    - assign ids of nodes in template
+
+    */
+    /// Create a template using the nodes on the stack
+    Save {
+        /// The ID of the template
+        name: &'static str,
+
+        /// The amount of nodes to pop from the stack into the template
+        num_children: u32,
+    },
+
+    /// Load the template into a scratch space on the stack
+    ///
+    /// The template body now lives on the stack, but needs to be finished before its nodes can be appended to the DOM.
+    Load {
+        /// The ID of the template
+        name: &'static str,
+
+        id: u32,
+    },
+
+    AssignId {
+        index: &'static str,
+        id: ElementId,
+    },
+
+    ReplaceDescendant {
+        index: &'static str,
+        m: u32,
+    },
+}
+
+use DomEdit::*;
+
+impl<'a> dioxus_core::Renderer<'a> for Mutations<'a> {
+    // Navigation
+    fn push_root(&mut self, root: ElementId) {
+        self.edits.push(PushRoot { root });
+    }
+
+    // Navigation
+    fn pop_root(&mut self) {
+        self.edits.push(PopRoot { count: 1 });
+    }
+
+    fn replace_with(&mut self, root: ElementId, m: u32) {
+        self.edits.push(ReplaceWith { m, root });
+    }
+
+    fn replace_descendant(&mut self, descendent: &'static [u8], m: u32) {
+        self.edits.push(ReplaceDescendant {
+            // serializing is just hijacking ascii
+            index: unsafe { std::str::from_utf8_unchecked(descendent) },
+            m,
+        });
+    }
+
+    fn insert_after(&mut self, root: ElementId, n: u32) {
+        self.edits.push(InsertAfter { n, root });
+    }
+
+    fn insert_before(&mut self, root: ElementId, n: u32) {
+        self.edits.push(InsertBefore { n, root });
+    }
+
+    fn append_children(&mut self, n: u32) {
+        self.edits.push(AppendChildren { many: n });
+    }
+
+    // Create
+    fn create_text_node(&mut self, text: &'a str, root: ElementId) {
+        self.edits.push(CreateTextNode { text, root });
+    }
+
+    fn create_element(&mut self, tag: &'static str, ns: Option<&'static str>, id: ElementId) {
+        match ns {
+            Some(ns) => self.edits.push(CreateElementNs { root: id, ns, tag }),
+            None => self.edits.push(CreateElement { root: id, tag }),
+        }
+    }
+
+    // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
+    fn create_placeholder(&mut self, id: ElementId) {
+        self.edits.push(CreatePlaceholder { root: id });
+    }
+
+    fn assign_id(&mut self, descendent: &'static [u8], id: ElementId) {
+        self.edits.push(AssignId {
+            index: unsafe { std::str::from_utf8_unchecked(descendent) },
+            id,
+        });
+    }
+
+    // Remove Nodes from the dom
+    fn remove(&mut self, root: ElementId) {
+        self.edits.push(Remove { root });
+    }
+
+    fn remove_attribute(&mut self, attribute: &Attribute, root: ElementId) {
+        self.edits.push(RemoveAttribute {
+            name: attribute.name,
+            ns: attribute.namespace,
+            root,
+        });
+    }
+
+    // events
+    fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) {
+        let Listener {
+            event,
+            mounted_node,
+            ..
+        } = listener;
+
+        let element_id = mounted_node.get();
+
+        self.edits.push(NewEventListener {
+            scope,
+            event_name: event,
+            root: element_id,
+        });
+    }
+
+    fn remove_event_listener(&mut self, event: &'static str, root: ElementId) {
+        self.edits.push(RemoveEventListener { event, root });
+    }
+
+    // modify
+    fn set_text(&mut self, text: &'a str, root: ElementId) {
+        self.edits.push(SetText { text, root });
+    }
+
+    fn save(&mut self, id: &'static str, num: u32) {
+        self.edits.push(Save {
+            name: id,
+            num_children: num,
+        });
+    }
+
+    fn load(&mut self, id: &'static str, index: u32) {
+        self.edits.push(Load {
+            name: id,
+            id: index,
+        });
+    }
+
+    fn mark_dirty_scope(&mut self, scope: ScopeId) {
+        self.dirty_scopes.push(scope);
+    }
+
+    fn set_attribute(
+        &mut self,
+        name: &'static str,
+        value: AttributeValue<'a>,
+        namespace: Option<&'a str>,
+        root: ElementId,
+    ) {
+        self.edits.push(SetAttribute {
+            field: name,
+            value: value.clone(),
+            ns: namespace,
+            root,
+        });
+    }
+
+    fn remove_children(&mut self, element: ElementId) {
+        todo!()
+    }
+}

+ 3 - 0
packages/html/src/global_attributes.rs

@@ -10,6 +10,7 @@ macro_rules! no_namespace_trait_methods {
         )*
     ) => {
         $(
+            $(#[$attr])*
             const $name: AttributeDiscription = (
                 stringify!($name),
                 None,
@@ -26,6 +27,7 @@ macro_rules! style_trait_methods {
         )*
     ) => {
         $(
+            $(#[$attr])*
             const $name: AttributeDiscription = (
                 $lit,
                 Some("style"),
@@ -42,6 +44,7 @@ macro_rules! aria_trait_methods {
         )*
     ) => {
         $(
+            $(#[$attr])*
             const $name: AttributeDiscription = (
                 $lit,
                 None,

+ 29 - 27
packages/rsx/src/lib.rs

@@ -83,41 +83,39 @@ impl ToTokens for CallBody {
         fn render_static_node<'a>(root: &'a BodyNode, cx: &mut DynamicContext<'a>) -> TokenStream2 {
             match root {
                 BodyNode::Element(el) => {
-                    let name = &el.name;
+                    let el_name = &el.name;
 
                     let children = {
                         let children = el.children.iter().map(|root| render_static_node(root, cx));
                         quote! { #(#children),* }
                     };
 
-                    let attrs = el.attributes.iter().map(|attr| {
+                    let attrs = el.attributes.iter().filter_map(|attr| {
                         //
                         match &attr.attr {
                             ElementAttr::AttrText { name, value } if value.is_static() => {
                                 let value = value.source.as_ref().unwrap();
-                                quote! {
-                                    ::dioxus::core::TemplateAttribute::Static(::dioxus::core::Attribute {
-                                        name: stringify!(#name),
-                                        namespace: None,
-                                        volatile: false,
-                                        mounted_node: Default::default(),
-                                        value: ::dioxus::core::AttributeValue::Text(#value),
-                                    })
-                                }
+                                Some(quote! {
+                                    ::dioxus::core::TemplateAttribute {
+                                        name: dioxus_elements::#el_name::#name.0,
+                                        namespace: dioxus_elements::#el_name::#name.1,
+                                        volatile: dioxus_elements::#el_name::#name.2,
+                                        value: #value,
+                                    }
+                                })
                             }
 
                             ElementAttr::CustomAttrText { name, value } if value.is_static() => {
                                 let value = value.source.as_ref().unwrap();
-                                quote! {
-                                    ::dioxus::core::TemplateAttribute::Static(::dioxus::core::Attribute {
-                                        name: stringify!(#name),
-                                        namespace: None,
-                                        volatile: false,
-                                        mounted_node: Default::default(),
-                                        value: ::dioxus::core::AttributeValue::Text(#value),
-                                    })
-                                }
-                            },
+                                Some(quote! {
+                                    ::dioxus::core::TemplateAttribute {
+                                        name: dioxus_elements::#el_name::#name.0,
+                                        namespace: dioxus_elements::#el_name::#name.1,
+                                        volatile: dioxus_elements::#el_name::#name.2,
+                                        value: #value,
+                                    }
+                                })
+                            }
 
                             ElementAttr::AttrExpression { .. }
                             | ElementAttr::AttrText { .. }
@@ -125,20 +123,24 @@ impl ToTokens for CallBody {
                             | ElementAttr::CustomAttrExpression { .. } => {
                                 let ct = cx.dynamic_attributes.len();
                                 cx.dynamic_attributes.push(attr);
-                                quote! { ::dioxus::core::TemplateAttribute::Dynamic(#ct) }
+                                // quote! {}
+                                None
+                                // quote! { ::dioxus::core::TemplateAttribute::Dynamic(#ct) }
                             }
 
                             ElementAttr::EventTokens { .. } => {
                                 let ct = cx.dynamic_listeners.len();
                                 cx.dynamic_listeners.push(attr);
-                                quote! { ::dioxus::core::TemplateAttribute::Dynamic(#ct) }
+                                // quote! {}
+                                None
                             }
                         }
                     });
 
                     quote! {
                         ::dioxus::core::TemplateNode::Element {
-                            tag: dioxus_elements::#name::TAG_NAME,
+                            tag: dioxus_elements::#el_name::TAG_NAME,
+                            namespace: dioxus_elements::#el_name::NAME_SPACE,
                             attrs: &[ #(#attrs),* ],
                             children: &[ #children ],
                         }
@@ -166,19 +168,19 @@ impl ToTokens for CallBody {
         // Render and release the mutable borrow on context
         let roots = quote! { #( #root_printer ),* };
 
-        let dyn_printer = &context.dynamic_nodes;
+        let node_printer = &context.dynamic_nodes;
         let attr_printer = context.dynamic_attributes.iter();
         let listener_printer = context.dynamic_listeners.iter();
 
         out_tokens.append_all(quote! {
             LazyNodes::new(move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
                 __cx.template_ref(
-                    || ::dioxus::core::Template {
+                    ::dioxus::core::Template {
                         id: ::dioxus::core::get_line_num!(),
                         roots: &[ #roots ]
                     },
                     __cx.bump().alloc([
-                       #( #dyn_printer ),*
+                       #( #node_printer ),*
                     ]),
                     __cx.bump().alloc([
                        #( #attr_printer ),*

+ 30 - 30
packages/ssr/src/lib.rs

@@ -410,36 +410,36 @@ fn render_attributes<'a, 'b: 'a>(
     let mut inner_html = None;
     let mut attr_iter = attrs.peekable();
 
-    while let Some(attr) = attr_iter.next() {
-        match attr.namespace {
-            None => {
-                if attr.name == "dangerous_inner_html" {
-                    inner_html = Some(attr.value.as_text().unwrap())
-                } else {
-                    if is_boolean_attribute(attr.name) && !attr.value.is_truthy() {
-                        continue;
-                    }
-                    write!(f, " {}=\"{}\"", attr.name, attr.value)?
-                }
-            }
-            Some(ns) => {
-                // write the opening tag
-                write!(f, " {}=\"", ns)?;
-                let mut cur_ns_el = attr;
-                loop {
-                    write!(f, "{}:{};", cur_ns_el.name, cur_ns_el.value)?;
-                    match attr_iter.peek() {
-                        Some(next_attr) if next_attr.namespace == Some(ns) => {
-                            cur_ns_el = attr_iter.next().unwrap();
-                        }
-                        _ => break,
-                    }
-                }
-                // write the closing tag
-                write!(f, "\"")?;
-            }
-        }
-    }
+    // while let Some(attr) = attr_iter.next() {
+    //     match attr.namespace {
+    //         None => {
+    //             if attr.name == "dangerous_inner_html" {
+    //                 inner_html = Some(attr.value.as_text().unwrap())
+    //             } else {
+    //                 if is_boolean_attribute(attr.name) && !attr.value.is_truthy() {
+    //                     continue;
+    //                 }
+    //                 write!(f, " {}=\"{}\"", attr.name, attr.value)?
+    //             }
+    //         }
+    //         Some(ns) => {
+    //             // write the opening tag
+    //             write!(f, " {}=\"", ns)?;
+    //             let mut cur_ns_el = attr;
+    //             loop {
+    //                 write!(f, "{}:{};", cur_ns_el.name, cur_ns_el.value)?;
+    //                 match attr_iter.peek() {
+    //                     Some(next_attr) if next_attr.namespace == Some(ns) => {
+    //                         cur_ns_el = attr_iter.next().unwrap();
+    //                     }
+    //                     _ => break,
+    //                 }
+    //             }
+    //             // write the closing tag
+    //             write!(f, "\"")?;
+    //         }
+    //     }
+    // }
     Ok(inner_html)
 }