diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 0a3ed1202..9c145f53b 100644 --- a/packages/core/Cargo.toml +++ b/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"] diff --git a/packages/core/architecture.md b/packages/core/architecture.md index 87c3cf4a6..84fea3917 100644 --- a/packages/core/architecture.md +++ b/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 diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 6f9277aa1..d5598322b 100644 --- a/packages/core/src/diff.rs +++ b/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}; + use VNode::{Component, Element, Fragment, Template, Text}; + + // Same node by ref, no need to diff. + if std::ptr::eq(old_node, new_node) { + return; + } + 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); - } - - (Template(old), Template(new)) => { - self.diff_templates(old, new, old_node, new_node); - } - - (Placeholder(_), Placeholder(_)) => { - self.diff_placeholder_nodes(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); + } + + // 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); + } } - // 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()) { - // + // There's not really any diffing that needs to happen for listeners + for listener in right.listeners { + listener.mounted_node.set(id); } - - // hmm, what we do here? - for (left, right) in old.listeners.iter().zip(new.listeners.iter()) { - // - } - } 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); + .save(template.template.id, template.template.roots.len() as u32); + + templates.insert(template.template); } - self.mutations.load(template_id); + // 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(); - let mut created = 0; + 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, + }; - // create the dynamic nodes - for node in template.dynamic_nodes.iter() { - created += self.create_node(node); + // 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); + } + + for listener in loc.listeners { + listener.mounted_node.set(id); + } + } + + 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); + + self.mutations + .replace_descendant(&dyn_node.pathway[1..], created 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); - } - - todo!() - - // let def = template.template; - - // let mut templates = self.scopes.template_cache.borrow_mut(); - - // 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 the creation step give it an ID - // crate::innerlude::TemplateRoots::Runtime(_) => todo!(), - // } - // } - - // // 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, - // }) - // } - - // nodes_created + on_stack } - 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; - // } - // } - // } - - // created - } - - 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); + // 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); - } - } + // 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!() } } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 2c347f24d..1bd88ad65 100644 --- a/packages/core/src/mutations.rs +++ b/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 - - /// + /// General statistics for doing things that extend outside of the renderer fn mark_dirty_scope(&mut self, scope: ScopeId); + + /// 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); } + +/* +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) +*/ diff --git a/packages/core/src/nodes/arbitrary_value.rs b/packages/core/src/nodes/arbitrary_value.rs index f9590f208..f890936ac 100644 --- a/packages/core/src/nodes/arbitrary_value.rs +++ b/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 f32 { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { - AttributeValue::Float32(self) - } -} - -impl<'a> IntoAttributeValue<'a> for bool { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { - AttributeValue::Bool(self) - } -} 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::Float(self) + } +} +impl<'a> IntoAttributeValue<'a> for i32 { + fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { + AttributeValue::Int(self) + } +} +impl<'a> IntoAttributeValue<'a> for bool { + fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { + 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::() { - // if let Some(b) = b.as_any().downcast_ref::() { - // 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 AnyAttributeValue for T { - fn cmp_any(&self, other: &dyn Any) -> bool { - match other.downcast_ref::() { - 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::() { + 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)); +} diff --git a/packages/core/src/nodes/element.rs b/packages/core/src/nodes/element.rs index 11aeb7327..bbe42f91e 100644 --- a/packages/core/src/nodes/element.rs +++ b/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>, - /// 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>, + pub mounted_node: Cell, /// The type of event to listen for. /// diff --git a/packages/core/src/nodes/factory.rs b/packages/core/src/nodes/factory.rs index 06694a706..d286c3154 100644 --- a/packages/core/src/nodes/factory.rs +++ b/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, ) -> 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, + })) } } diff --git a/packages/core/src/nodes/fragment.rs b/packages/core/src/nodes/fragment.rs index 2732acab3..15317fb42 100644 --- a/packages/core/src/nodes/fragment.rs +++ b/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>, /// Fragments can never have zero children. Enforced by NodeFactory. diff --git a/packages/core/src/nodes/mod.rs b/packages/core/src/nodes/mod.rs index f049ca142..7ff6a9136 100644 --- a/packages/core/src/nodes/mod.rs +++ b/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(), } } } diff --git a/packages/core/src/nodes/placeholder.rs b/packages/core/src/nodes/placeholder.rs index d4967cf48..36bb6c048 100644 --- a/packages/core/src/nodes/placeholder.rs +++ b/packages/core/src/nodes/placeholder.rs @@ -1,7 +1,7 @@ +use crate::ElementId; use std::cell::Cell; -use crate::ElementId; - +/// A placeholder node pub struct VPlaceholder { pub id: Cell>, pub dynamic_index: Option, diff --git a/packages/core/src/nodes/template.rs b/packages/core/src/nodes/template.rs index d98102c39..8268a40bd 100644 --- a/packages/core/src/nodes/template.rs +++ b/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, + + // Position this template for fragments and stuff + pub head_id: Cell, + + pub tail_id: Cell, + 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(&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, + pub attrs: &'a [Attribute<'a>], + pub listeners: &'a [Listener<'a>], +} + +pub struct NodeLocation<'a> { + pub pathway: &'static [u8], + pub node: VNode<'a>, } diff --git a/packages/core/src/nodes/text.rs b/packages/core/src/nodes/text.rs index e1302eded..63d19c0b9 100644 --- a/packages/core/src/nodes/text.rs +++ b/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> { diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index e6e4b6ae6..688fdd137 100644 --- a/packages/core/src/virtual_dom.rs +++ b/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. diff --git a/packages/dioxus/tests/rsx_syntax.rs b/packages/dioxus/tests/rsx_syntax.rs index 88ce1ba65..4efa722eb 100644 --- a/packages/dioxus/tests/rsx_syntax.rs +++ b/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 var = 123; - let g = rsx! { - div { - class: "asd", - // class: "{asd}", - // onclick: move |_| {}, - // div { "{var}" } + 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); +fn basic_template(cx: Scope) -> Element { + cx.render(rsx! { + div {"hi!"} + }) +} - 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"), - }, - )]; +#[test] +fn basic_prints() { + let dom = VirtualDom::new(basic_template); - __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 renderer = dioxus_edit_stream::Mutations::default(); + dom.rebuild(&mut renderer); - // 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) - - // let __cx = NodeFactory::new(&cx); - - // 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 - // ); - - // Some(t) + dbg!(renderer.edits); } diff --git a/packages/edit-stream/src/lib.rs b/packages/edit-stream/src/lib.rs index 31564f2df..45c8ca3c5 100644 --- a/packages/edit-stream/src/lib.rs +++ b/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>, - -// /// The list of Scopes that were diffed, created, and removed during the Diff process. -// pub dirty_scopes: FxHashSet, - -// /// The list of nodes to connect to the RealDOM. -// pub refs: Vec>, -// } - -// 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>>, -// 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(&self) -> Option<&T> { -// self.element -// .as_ref() -// .and_then(|f| f.get()) -// .and_then(|f| f.downcast_ref::()) -// } -// pub fn downcast_mut(&mut self) -> Option<&mut T> { -// self.element -// .as_mut() -// .and_then(|f| f.get_mut()) -// .and_then(|f| f.downcast_mut::()) -// } -// } +/// ## 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>, + + /// The list of Scopes that were diffed, created, and removed during the Diff process. + pub dirty_scopes: Vec, +} + +/// 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!() + } +} diff --git a/packages/html/src/global_attributes.rs b/packages/html/src/global_attributes.rs index 1b1ce86d0..5c4ac42e8 100644 --- a/packages/html/src/global_attributes.rs +++ b/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, diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 87bb348ea..7e1751df9 100644 --- a/packages/rsx/src/lib.rs +++ b/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 ),* diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index f4558b4e7..2f9e88992 100644 --- a/packages/ssr/src/lib.rs +++ b/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) }