diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 1b82fe04a..ed92781c9 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -94,8 +94,8 @@ use crate::{ dynamic_template_context::TemplateContext, innerlude::{ - AnyProps, ElementId, GlobalNodeId, Mutations, RendererTemplateId, ScopeArena, ScopeId, - VComponent, VElement, VFragment, VNode, VPlaceholder, VText, + AnyProps, ElementId, Mutations, ScopeArena, ScopeId, VComponent, VElement, VFragment, + VNode, VPlaceholder, VText, }, template::{ Template, TemplateAttribute, TemplateElement, TemplateNode, TemplateNodeId, @@ -111,7 +111,6 @@ pub(crate) struct DiffState<'bump> { pub(crate) scopes: &'bump ScopeArena, pub(crate) mutations: Mutations<'bump>, pub(crate) force_diff: bool, - pub(crate) element_stack: SmallVec<[GlobalNodeId; 10]>, pub(crate) scope_stack: SmallVec<[ScopeId; 5]>, } @@ -121,27 +120,28 @@ impl<'b> DiffState<'b> { scopes, mutations: Mutations::new(), force_diff: false, - element_stack: smallvec![], scope_stack: smallvec![], } } - pub fn diff_scope(&mut self, scopeid: ScopeId) { + pub fn diff_scope(&mut self, parent: ElementId, scopeid: ScopeId) { let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid)); - let scope = self.scopes.get_scope(scopeid).unwrap(); self.scope_stack.push(scopeid); - self.element_stack.push(scope.container); { - self.diff_node(old, new); + self.diff_node(parent, old, new); } - self.element_stack.pop(); self.scope_stack.pop(); self.mutations.mark_dirty_scope(scopeid); } - pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) { + pub fn diff_node( + &mut self, + parent: ElementId, + old_node: &'b VNode<'b>, + new_node: &'b VNode<'b>, + ) { use VNode::{Component, Element, Fragment, Placeholder, TemplateRef, Text}; match (old_node, new_node) { (Text(old), Text(new)) => { @@ -157,50 +157,62 @@ impl<'b> DiffState<'b> { } (Component(old), Component(new)) => { - self.diff_component_nodes(old, new, old_node, new_node); + self.diff_component_nodes(parent, old, new, old_node, new_node); } (Fragment(old), Fragment(new)) => { - self.diff_fragment_nodes(old, new); + self.diff_fragment_nodes(parent, old, new); } (TemplateRef(old), TemplateRef(new)) => { - self.diff_template_ref_nodes(old, new, old_node, new_node); + self.diff_template_ref_nodes(parent, old, new, old_node, new_node); } ( Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_) | TemplateRef(_), Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_) | TemplateRef(_), - ) => self.replace_node(old_node, new_node), + ) => self.replace_node(parent, old_node, new_node), } } - pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize { + pub fn create_node(&mut self, parent: ElementId, node: &'b VNode<'b>, nodes: &mut Vec) { match node { - VNode::Text(vtext) => self.create_text_node(vtext, node), - VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node), - VNode::Element(element) => self.create_element_node(element, node), - VNode::Fragment(frag) => self.create_fragment_node(frag), - VNode::Component(component) => self.create_component_node(component), - VNode::TemplateRef(temp) => self.create_template_ref_node(temp, node), + VNode::Text(vtext) => self.create_text_node(vtext, node, nodes), + VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node, nodes), + VNode::Element(element) => self.create_element_node(parent, element, node, nodes), + VNode::Fragment(frag) => self.create_fragment_node(parent, frag, nodes), + VNode::Component(component) => self.create_component_node(parent, component, nodes), + VNode::TemplateRef(temp) => self.create_template_ref_node(parent, temp, node, nodes), } } - fn create_text_node(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>) -> usize { + fn create_text_node(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>, nodes: &mut Vec) { let real_id = self.scopes.reserve_node(node); text.id.set(Some(real_id)); - self.mutations.create_text_node(text.text, real_id); - 1 + self.mutations + .create_text_node(text.text, Some(real_id.as_u64())); + nodes.push(real_id.0 as u64); } - fn create_anchor_node(&mut self, anchor: &'b VPlaceholder, node: &'b VNode<'b>) -> usize { + fn create_anchor_node( + &mut self, + anchor: &'b VPlaceholder, + node: &'b VNode<'b>, + nodes: &mut Vec, + ) { let real_id = self.scopes.reserve_node(node); anchor.id.set(Some(real_id)); - self.mutations.create_placeholder(real_id); - 1 + self.mutations.create_placeholder(Some(real_id.as_u64())); + nodes.push(real_id.0 as u64); } - fn create_element_node(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize { + fn create_element_node( + &mut self, + parent: ElementId, + element: &'b VElement<'b>, + node: &'b VNode<'b>, + nodes: &mut Vec, + ) { let VElement { tag: tag_name, listeners, @@ -212,43 +224,50 @@ impl<'b> DiffState<'b> { .. } = &element; - parent_id.set(self.element_stack.last().copied()); + parent_id.set(Some(parent)); let real_id = self.scopes.reserve_node(node); dom_id.set(Some(real_id)); - self.element_stack.push(GlobalNodeId::VNodeId(real_id)); { - self.mutations.create_element(tag_name, *namespace, real_id); + self.mutations + .create_element(tag_name, *namespace, Some(real_id.as_u64()), 0); let cur_scope_id = self.current_scope(); for listener in listeners.iter() { - listener - .mounted_node - .set(Some(GlobalNodeId::VNodeId(real_id))); + listener.mounted_node.set(Some(real_id)); self.mutations.new_event_listener(listener, cur_scope_id); } for attr in attributes.iter() { - self.mutations.set_attribute(attr, real_id); + self.mutations.set_attribute(attr, Some(real_id.as_u64())); } if !children.is_empty() { - self.create_and_append_children(children); + self.create_and_append_children(real_id, children); } } - self.element_stack.pop(); - 1 + nodes.push(real_id.0 as u64); } - fn create_fragment_node(&mut self, frag: &'b VFragment<'b>) -> usize { - self.create_children(frag.children) + fn create_fragment_node( + &mut self, + parent: ElementId, + frag: &'b VFragment<'b>, + nodes: &mut Vec, + ) { + self.create_children(parent, frag.children, nodes); } - fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize { + fn create_component_node( + &mut self, + parent: ElementId, + vcomponent: &'b VComponent<'b>, + nodes: &mut Vec, + ) { let parent_idx = self.current_scope(); // the component might already exist - if it does, we need to reuse it @@ -260,12 +279,8 @@ impl<'b> DiffState<'b> { // Insert a new scope into our component list let props: Box = vcomponent.props.borrow_mut().take().unwrap(); let props: Box = unsafe { std::mem::transmute(props) }; - self.scopes.new_with_key( - vcomponent.user_fc, - props, - Some(parent_idx), - self.element_stack.last().copied().unwrap(), - ) + self.scopes + .new_with_key(vcomponent.user_fc, props, Some(parent_idx), parent) }; // Actually initialize the caller's slot with the right address @@ -286,29 +301,27 @@ impl<'b> DiffState<'b> { self.enter_scope(new_idx); - let created = { - // Run the scope for one iteration to initialize it - self.scopes.run_scope(new_idx); - self.mutations.mark_dirty_scope(new_idx); + // Run the scope for one iteration to initialize it + self.scopes.run_scope(new_idx); + self.mutations.mark_dirty_scope(new_idx); - // Take the node that was just generated from running the component - let nextnode = self.scopes.fin_head(new_idx); - self.create_node(nextnode) - }; + // Take the node that was just generated from running the component + let nextnode = self.scopes.fin_head(new_idx); + self.create_node(parent, nextnode, nodes); self.leave_scope(); - - created } pub(crate) fn create_template_ref_node( &mut self, + parent: ElementId, new: &'b VTemplateRef<'b>, - node: &'b VNode<'b>, - ) -> usize { - let (id, created) = { + _node: &'b VNode<'b>, + nodes: &mut Vec, + ) { + let (id, just_created) = { let mut resolver = self.scopes.template_resolver.borrow_mut(); - resolver.get_or_create_client_id(&new.template_id) + resolver.get_or_create_client_id(&new.template_id, self.scopes) }; let template = { @@ -317,19 +330,28 @@ impl<'b> DiffState<'b> { }; let template = template.borrow(); - if created { + if just_created { self.register_template(&template, id); } - let real_id = self.scopes.reserve_node(node); + new.template_ref_id + .set(Some(self.scopes.reserve_template_ref(new))); - new.id.set(Some(real_id)); + let template_ref_id = new.template_ref_id.get().unwrap(); - self.mutations.create_template_ref(real_id, id.into()); + let root_nodes = template.root_nodes(); + nodes.extend(root_nodes.iter().map(|node_id| { + let real_id = self.scopes.reserve_template_node(template_ref_id, *node_id); + new.set_node_id(*node_id, real_id); + real_id.as_u64() + })); - new.hydrate(&template, self); + self.mutations.clone_node_children( + Some(id.as_u64()), + nodes[(nodes.len() - root_nodes.len())..].to_vec(), + ); - 1 + new.hydrate(parent, &template, self); } pub(crate) fn diff_text_nodes( @@ -350,7 +372,7 @@ impl<'b> DiffState<'b> { }; if old.text != new.text { - self.mutations.set_text(new.text, root); + self.mutations.set_text(new.text, Some(root.as_u64())); } self.scopes.update_node(new_node, root); @@ -401,7 +423,7 @@ impl<'b> DiffState<'b> { // // This case is rather rare (typically only in non-keyed lists) if new.tag != old.tag || new.namespace != old.namespace { - self.replace_node(old_node, new_node); + self.replace_node(root, old_node, new_node); return; } @@ -426,15 +448,16 @@ impl<'b> DiffState<'b> { if !old_attr.is_static && old_attr.value != new_attr.value || new_attr.attribute.volatile { - self.mutations.set_attribute(new_attr, root); + self.mutations.set_attribute(new_attr, Some(root.as_u64())); } } } else { for attribute in old.attributes { - self.mutations.remove_attribute(attribute, root); + self.mutations + .remove_attribute(attribute, Some(root.as_u64())); } for attribute in new.attributes { - self.mutations.set_attribute(attribute, root); + self.mutations.set_attribute(attribute, Some(root.as_u64())); } } @@ -452,16 +475,18 @@ impl<'b> DiffState<'b> { for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { new_l.mounted_node.set(old_l.mounted_node.get()); if old_l.event != new_l.event { - self.mutations.remove_event_listener(old_l.event, root); + self.mutations + .remove_event_listener(old_l.event, Some(root.as_u64())); self.mutations.new_event_listener(new_l, cur_scope_id); } } } else { for listener in old.listeners { - self.mutations.remove_event_listener(listener.event, root); + self.mutations + .remove_event_listener(listener.event, Some(root.as_u64())); } for listener in new.listeners { - listener.mounted_node.set(Some(GlobalNodeId::VNodeId(root))); + listener.mounted_node.set(Some(root)); self.mutations.new_event_listener(listener, cur_scope_id); } } @@ -469,17 +494,17 @@ impl<'b> DiffState<'b> { match (old.children.len(), new.children.len()) { (0, 0) => {} (0, _) => { - self.mutations.push_root(root); - let created = self.create_children(new.children); - self.mutations.append_children(created as u32); - self.mutations.pop_root(); + let mut created = Vec::new(); + self.create_children(root, new.children, &mut created); + self.mutations.append_children(Some(root.as_u64()), created); } - (_, _) => self.diff_children(old.children, new.children), + (_, _) => self.diff_children(root, old.children, new.children), }; } fn diff_component_nodes( &mut self, + parent: ElementId, old: &'b VComponent<'b>, new: &'b VComponent<'b>, old_node: &'b VNode<'b>, @@ -538,6 +563,7 @@ impl<'b> DiffState<'b> { self.mutations.mark_dirty_scope(scope_addr); self.diff_node( + parent, self.scopes.wip_head(scope_addr), self.scopes.fin_head(scope_addr), ); @@ -548,11 +574,16 @@ impl<'b> DiffState<'b> { } self.leave_scope(); } else { - self.replace_node(old_node, new_node); + self.replace_node(parent, old_node, new_node); } } - fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) { + fn diff_fragment_nodes( + &mut self, + parent: ElementId, + old: &'b VFragment<'b>, + new: &'b VFragment<'b>, + ) { if std::ptr::eq(old, new) { return; } @@ -561,7 +592,7 @@ impl<'b> DiffState<'b> { // In this case, it's faster to just skip ahead to their diff if old.children.len() == 1 && new.children.len() == 1 { if !std::ptr::eq(old, new) { - self.diff_node(&old.children[0], &new.children[0]); + self.diff_node(parent, &old.children[0], &new.children[0]); } return; } @@ -569,12 +600,13 @@ impl<'b> DiffState<'b> { debug_assert!(!old.children.is_empty()); debug_assert!(!new.children.is_empty()); - self.diff_children(old.children, new.children); + self.diff_children(parent, old.children, new.children); } #[allow(clippy::too_many_lines)] fn diff_template_ref_nodes( &mut self, + parent: ElementId, old: &'b VTemplateRef<'b>, new: &'b VTemplateRef<'b>, old_node: &'b VNode<'b>, @@ -583,9 +615,9 @@ impl<'b> DiffState<'b> { fn diff_attributes<'b, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>( nodes: &Nodes, ctx: ( - &mut Mutations<'b>, + &mut DiffState<'b>, &'b Bump, - &VTemplateRef<'b>, + &'b VTemplateRef<'b>, &Template, usize, ), @@ -598,7 +630,7 @@ impl<'b> DiffState<'b> { TextSegments: AsRef<[TextTemplateSegment]>, Text: AsRef, { - let (mutations, scope_bump, new, template, idx) = ctx; + let (diff_state, scope_bump, new, template, idx) = ctx; for (node_id, attr_idx) in template.get_dynamic_nodes_for_attribute_index(idx) { if let TemplateNodeType::Element(el) = &nodes.as_ref()[node_id.0].node_type { let TemplateElement { attributes, .. } = el; @@ -608,7 +640,10 @@ impl<'b> DiffState<'b> { value: new.dynamic_context.resolve_attribute(idx).clone(), is_static: false, }; - mutations.set_attribute(scope_bump.alloc(attribute), *node_id); + let real_id = new.get_node_id(*node_id); + diff_state + .mutations + .set_attribute(scope_bump.alloc(attribute), Some(real_id.as_u64())); } else { panic!("expected element node"); } @@ -617,7 +652,7 @@ impl<'b> DiffState<'b> { fn set_attribute<'b, Attributes, V, Children, Listeners, TextSegments, Text>( node: &TemplateNode, - ctx: (&mut Mutations<'b>, &'b Bump, &VTemplateRef<'b>, usize), + ctx: (&mut DiffState<'b>, &'b Bump, &'b VTemplateRef<'b>, usize), ) where Attributes: AsRef<[TemplateAttribute]>, V: TemplateValue, @@ -626,7 +661,7 @@ impl<'b> DiffState<'b> { TextSegments: AsRef<[TextTemplateSegment]>, Text: AsRef, { - let (mutations, scope_bump, new, template_attr_idx) = ctx; + let (diff_state, scope_bump, new, template_attr_idx) = ctx; if let TemplateNodeType::Element(el) = &node.node_type { let TemplateElement { attributes, .. } = el; let attr = &attributes.as_ref()[template_attr_idx]; @@ -641,15 +676,22 @@ impl<'b> DiffState<'b> { value, is_static: false, }; - mutations.set_attribute(scope_bump.alloc(attribute), node.id); + let real_id = new.get_node_id(node.id); + diff_state + .mutations + .set_attribute(scope_bump.alloc(attribute), Some(real_id.as_u64())); } else { panic!("expected element node"); } } - fn diff_dynamic_node<'b, Attributes, V, Children, Listeners, TextSegments, Text>( + fn diff_text<'b, Attributes, V, Children, Listeners, TextSegments, Text>( node: &TemplateNode, - ctx: (&mut DiffState<'b>, &'b VNode<'b>, &'b VNode<'b>, ElementId), + ctx: ( + &mut DiffState<'b>, + &'b VTemplateRef<'b>, + &TemplateContext<'b>, + ), ) where Attributes: AsRef<[TemplateAttribute]>, V: TemplateValue, @@ -658,13 +700,16 @@ impl<'b> DiffState<'b> { TextSegments: AsRef<[TextTemplateSegment]>, Text: AsRef, { - let (diff, old_node, new_node, root) = ctx; - if let TemplateNodeType::Element { .. } = node.node_type { - diff.element_stack.push(GlobalNodeId::VNodeId(root)); - diff.diff_node(old_node, new_node); - diff.element_stack.pop(); + let (diff, new, dynamic_context) = ctx; + if let TemplateNodeType::Text(text) = &node.node_type { + let text = dynamic_context.resolve_text(text); + let real_id = new.get_node_id(node.id); + diff.mutations.set_text( + diff.current_scope_bump().alloc(text), + Some(real_id.as_u64()), + ); } else { - diff.diff_node(old_node, new_node); + panic!("expected text node"); } } @@ -680,23 +725,19 @@ impl<'b> DiffState<'b> { .borrow() .is_dirty(&new.template_id) { - self.replace_node(old_node, new_node); + self.replace_node(parent, old_node, new_node); 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, - None => self.scopes.reserve_node(new_node), - }; - - self.scopes.update_node(new_node, root); - - new.id.set(Some(root)); - - self.element_stack.push(GlobalNodeId::VNodeId(root)); - - self.mutations.enter_template_ref(root); + if let Some(template_ref_id) = old.template_ref_id.get() { + self.scopes.update_template_ref(template_ref_id, new); + new.template_ref_id.set(Some(template_ref_id)); + } else { + new.template_ref_id + .set(Some(self.scopes.reserve_template_ref(new))); + } + new.parent.set(Some(parent)); + new.node_ids.replace(old.node_ids.take()); let scope_bump = &self.current_scope_bump(); @@ -718,7 +759,7 @@ impl<'b> DiffState<'b> { template.with_nodes( diff_attributes, diff_attributes, - (&mut self.mutations, scope_bump, new, &template, idx), + (self, scope_bump, new, &template, idx), ); } } @@ -729,26 +770,18 @@ impl<'b> DiffState<'b> { id, set_attribute, set_attribute, - (&mut self.mutations, scope_bump, new, idx), + (self, scope_bump, new, idx), ); } // diff dynmaic nodes - for (idx, (old_node, new_node)) in old + for (old_node, new_node) in old .dynamic_context .nodes .iter() .zip(new.dynamic_context.nodes.iter()) - .enumerate() { - if let Some(id) = template.get_dynamic_nodes_for_node_index(idx) { - template.with_node( - id, - diff_dynamic_node, - diff_dynamic_node, - (self, old_node, new_node, root), - ); - } + self.diff_node(parent, old_node, new_node); } // diff dynamic text @@ -768,31 +801,13 @@ impl<'b> DiffState<'b> { } } for node_id in dirty_text_nodes { - fn diff_text<'b, Attributes, V, Children, Listeners, TextSegments, Text>( - node: &TemplateNode, - ctx: (&mut DiffState<'b>, &TemplateContext<'b>), - ) where - Attributes: AsRef<[TemplateAttribute]>, - V: TemplateValue, - Children: AsRef<[TemplateNodeId]>, - Listeners: AsRef<[usize]>, - TextSegments: AsRef<[TextTemplateSegment]>, - Text: AsRef, - { - let (diff, dynamic_context) = ctx; - if let TemplateNodeType::Text(text) = &node.node_type { - let text = dynamic_context.resolve_text(&text.segments.as_ref()); - diff.mutations - .set_text(diff.current_scope_bump().alloc(text), node.id); - } else { - panic!("expected text node"); - } - } - template.with_node(node_id, diff_text, diff_text, (self, &new.dynamic_context)); + template.with_node( + node_id, + diff_text, + diff_text, + (self, new, &new.dynamic_context), + ); } - - self.mutations.exit_template_ref(); - self.element_stack.pop(); } // Diff the given set of old and new children. @@ -810,7 +825,7 @@ impl<'b> DiffState<'b> { // // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only // to an element, and appending makes sense. - fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { + fn diff_children(&mut self, parent: ElementId, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { if std::ptr::eq(old, new) { return; } @@ -818,7 +833,7 @@ impl<'b> DiffState<'b> { // Remember, fragments can never be empty (they always have a single child) match (old, new) { ([], []) => {} - ([], _) => self.create_and_append_children(new), + ([], _) => self.create_and_append_children(parent, new), (_, []) => self.remove_nodes(old, true), _ => { let new_is_keyed = new[0].key().is_some(); @@ -834,9 +849,9 @@ impl<'b> DiffState<'b> { ); if new_is_keyed && old_is_keyed { - self.diff_keyed_children(old, new); + self.diff_keyed_children(parent, old, new); } else { - self.diff_non_keyed_children(old, new); + self.diff_non_keyed_children(parent, old, new); } } } @@ -850,7 +865,12 @@ impl<'b> DiffState<'b> { // [... parent] // // the change list stack is in the same state when this function returns. - fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { + fn diff_non_keyed_children( + &mut self, + parent: ElementId, + old: &'b [VNode<'b>], + new: &'b [VNode<'b>], + ) { use std::cmp::Ordering; // Handled these cases in `diff_children` before calling this function. @@ -859,12 +879,14 @@ impl<'b> DiffState<'b> { match old.len().cmp(&new.len()) { Ordering::Greater => self.remove_nodes(&old[new.len()..], true), - Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()), + Ordering::Less => { + self.create_and_insert_after(parent, &new[old.len()..], old.last().unwrap()); + } Ordering::Equal => {} } for (new, old) in new.iter().zip(old.iter()) { - self.diff_node(old, new); + self.diff_node(parent, old, new); } } @@ -884,7 +906,12 @@ impl<'b> DiffState<'b> { // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739 // // The stack is empty upon entry. - fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { + fn diff_keyed_children( + &mut self, + parent: ElementId, + old: &'b [VNode<'b>], + new: &'b [VNode<'b>], + ) { if cfg!(debug_assertions) { let mut keys = rustc_hash::FxHashSet::default(); let mut assert_unique_keys = |children: &'b [VNode<'b>]| { @@ -912,7 +939,7 @@ impl<'b> DiffState<'b> { // // `shared_prefix_count` is the count of how many nodes at the start of // `new` and `old` share the same keys. - let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) { + let (left_offset, right_offset) = match self.diff_keyed_ends(parent, old, new) { Some(count) => count, None => return, }; @@ -938,18 +965,18 @@ impl<'b> DiffState<'b> { if left_offset == 0 { // insert at the beginning of the old list let foothold = &old[old.len() - right_offset]; - self.create_and_insert_before(new_middle, foothold); + self.create_and_insert_before(parent, new_middle, foothold); } else if right_offset == 0 { // insert at the end the old list let foothold = old.last().unwrap(); - self.create_and_insert_after(new_middle, foothold); + self.create_and_insert_after(parent, new_middle, foothold); } else { // inserting in the middle let foothold = &old[left_offset - 1]; - self.create_and_insert_after(new_middle, foothold); + self.create_and_insert_after(parent, new_middle, foothold); } } else { - self.diff_keyed_middle(old_middle, new_middle); + self.diff_keyed_middle(parent, old_middle, new_middle); } } @@ -960,6 +987,7 @@ impl<'b> DiffState<'b> { /// If there is no offset, then this function returns None and the diffing is complete. fn diff_keyed_ends( &mut self, + parent: ElementId, old: &'b [VNode<'b>], new: &'b [VNode<'b>], ) -> Option<(usize, usize)> { @@ -970,14 +998,14 @@ impl<'b> DiffState<'b> { if old.key() != new.key() { break; } - self.diff_node(old, new); + self.diff_node(parent, old, new); left_offset += 1; } // If that was all of the old children, then create and append the remaining // new children and we're finished. if left_offset == old.len() { - self.create_and_insert_after(&new[left_offset..], old.last().unwrap()); + self.create_and_insert_after(parent, &new[left_offset..], old.last().unwrap()); return None; } @@ -995,7 +1023,7 @@ impl<'b> DiffState<'b> { if old.key() != new.key() { break; } - self.diff_node(old, new); + self.diff_node(parent, old, new); right_offset += 1; } @@ -1016,7 +1044,7 @@ impl<'b> DiffState<'b> { // // Upon exit from this function, it will be restored to that same self. #[allow(clippy::too_many_lines)] - fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { + fn diff_keyed_middle(&mut self, parent: ElementId, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { /* 1. Map the old keys into a numerical ordering based on indices. 2. Create a map of old key to its index @@ -1074,12 +1102,13 @@ impl<'b> DiffState<'b> { if shared_keys.is_empty() { if let Some(first_old) = old.get(0) { self.remove_nodes(&old[1..], true); - let nodes_created = self.create_children(new); + let mut nodes_created = Vec::new(); + self.create_children(parent, new, &mut nodes_created); self.replace_inner(first_old, nodes_created); } else { // I think this is wrong - why are we appending? // only valid of the if there are no trailing elements - self.create_and_append_children(new); + self.create_and_append_children(parent, new); } return; } @@ -1117,10 +1146,10 @@ impl<'b> DiffState<'b> { } for idx in &lis_sequence { - self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]); + self.diff_node(parent, &old[new_index_to_old_index[*idx]], &new[*idx]); } - let mut nodes_created = 0; + let mut nodes_created = Vec::new(); // add mount instruction for the first items not covered by the lis let last = *lis_sequence.last().unwrap(); @@ -1129,18 +1158,16 @@ impl<'b> DiffState<'b> { let new_idx = idx + last + 1; let old_index = new_index_to_old_index[new_idx]; if old_index == u32::MAX as usize { - nodes_created += self.create_node(new_node); + self.create_node(parent, new_node, &mut nodes_created); } else { - self.diff_node(&old[old_index], new_node); - nodes_created += self.push_all_real_nodes(new_node); + self.diff_node(parent, &old[old_index], new_node); + self.get_all_real_nodes(new_node, &mut nodes_created); } } - self.mutations.insert_after( - self.find_last_element(&new[last]).unwrap(), - nodes_created as u32, - ); - nodes_created = 0; + let last = Some(self.find_last_element(&new[last]).unwrap().as_u64()); + self.mutations.insert_after(last, nodes_created); + nodes_created = Vec::new(); } // for each spacing, generate a mount instruction @@ -1152,19 +1179,17 @@ impl<'b> DiffState<'b> { let new_idx = idx + next + 1; let old_index = new_index_to_old_index[new_idx]; if old_index == u32::MAX as usize { - nodes_created += self.create_node(new_node); + self.create_node(parent, new_node, &mut nodes_created); } else { - self.diff_node(&old[old_index], new_node); - nodes_created += self.push_all_real_nodes(new_node); + self.diff_node(parent, &old[old_index], new_node); + self.get_all_real_nodes(new_node, &mut nodes_created); } } - self.mutations.insert_before( - self.find_first_element(&new[last]).unwrap(), - nodes_created as u32, - ); + let first = Some(self.find_first_element(&new[last]).unwrap().as_u64()); + self.mutations.insert_before(first, nodes_created); - nodes_created = 0; + nodes_created = Vec::new(); } last = *next; } @@ -1175,33 +1200,33 @@ impl<'b> DiffState<'b> { for (idx, new_node) in new[..first_lis].iter().enumerate() { let old_index = new_index_to_old_index[idx]; if old_index == u32::MAX as usize { - nodes_created += self.create_node(new_node); + self.create_node(parent, new_node, &mut nodes_created); } else { - self.diff_node(&old[old_index], new_node); - nodes_created += self.push_all_real_nodes(new_node); + self.diff_node(parent, &old[old_index], new_node); + self.get_all_real_nodes(new_node, &mut nodes_created); } } - self.mutations.insert_before( - self.find_first_element(&new[first_lis]).unwrap(), - nodes_created as u32, - ); + let first = Some(self.find_first_element(&new[first_lis]).unwrap().as_u64()); + self.mutations.insert_before(first, nodes_created); } } - fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) { - let nodes_created = self.create_node(new); - self.replace_inner(old, nodes_created); + pub fn replace_node(&mut self, parent: ElementId, old: &'b VNode<'b>, new: &'b VNode<'b>) { + let mut nodes_vec = Vec::new(); + self.create_node(parent, new, &mut nodes_vec); + self.replace_inner(old, nodes_vec); } - fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: usize) { + fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: Vec) { match old { VNode::Element(el) => { let id = old .try_mounted_id() .unwrap_or_else(|| panic!("broke on {:?}", old)); - self.mutations.replace_with(id, nodes_created as u32); + self.mutations + .replace_with(Some(id.as_u64()), nodes_created); self.remove_nodes(el.children, false); self.scopes.collect_garbage(id); } @@ -1211,7 +1236,8 @@ impl<'b> DiffState<'b> { .try_mounted_id() .unwrap_or_else(|| panic!("broke on {:?}", old)); - self.mutations.replace_with(id, nodes_created as u32); + self.mutations + .replace_with(Some(id.as_u64()), nodes_created); self.scopes.collect_garbage(id); } @@ -1240,14 +1266,29 @@ impl<'b> DiffState<'b> { self.leave_scope(); } - VNode::TemplateRef(t) => { - let id = old - .try_mounted_id() - .unwrap_or_else(|| panic!("broke on {:?}", old)); + VNode::TemplateRef(template_ref) => { + let templates = self.scopes.templates.borrow(); + let template = templates.get(&template_ref.template_id).unwrap().borrow(); + let mut root_iter = template.root_nodes().iter(); + let first_real_id = template_ref.get_node_id(*root_iter.next().unwrap()); + self.mutations + .replace_with(Some(first_real_id.as_u64()), nodes_created); + for id in root_iter { + let real_id = template_ref.get_node_id(*id); + self.mutations.remove(Some(real_id.as_u64())); + } - self.mutations.replace_with(id, nodes_created as u32); - self.remove_nodes(t.dynamic_context.nodes, true); - self.scopes.collect_garbage(id); + self.remove_nodes(template_ref.dynamic_context.nodes, true); + + if let Some(id) = template_ref.template_ref_id.get() { + self.scopes.template_refs.borrow_mut().remove(id.0); + } + + for id in template_ref.node_ids.borrow().iter() { + if let Some(id) = id.get() { + self.scopes.collect_garbage(*id); + } + } } } } @@ -1262,7 +1303,7 @@ impl<'b> DiffState<'b> { t.id.set(None); if gen_muts { - self.mutations.remove(id); + self.mutations.remove(Some(id.as_u64())); } } } @@ -1272,14 +1313,14 @@ impl<'b> DiffState<'b> { a.id.set(None); if gen_muts { - self.mutations.remove(id); + self.mutations.remove(Some(id.as_u64())); } } VNode::Element(e) => { let id = e.id.get().unwrap(); if gen_muts { - self.mutations.remove(id); + self.mutations.remove(Some(id.as_u64())); } self.scopes.collect_garbage(id); @@ -1309,45 +1350,74 @@ impl<'b> DiffState<'b> { self.leave_scope(); } - VNode::TemplateRef(t) => { - let id = t.id.get().unwrap(); - + VNode::TemplateRef(template_ref) => { + let templates = self.scopes.templates.borrow(); + let template = templates.get(&template_ref.template_id).unwrap().borrow(); if gen_muts { - self.mutations.remove(id); + for id in template.root_nodes() { + let real_id = template_ref.get_node_id(*id); + self.mutations.remove(Some(real_id.as_u64())); + } } - self.scopes.collect_garbage(id); - t.id.set(None); + self.remove_nodes(template_ref.dynamic_context.nodes, gen_muts); - self.remove_nodes(t.dynamic_context.nodes, gen_muts); + if let Some(id) = template_ref.template_ref_id.get() { + self.scopes.template_refs.borrow_mut().remove(id.0); + } + + for id in template_ref.node_ids.borrow().iter() { + if let Some(id) = id.get() { + self.scopes.collect_garbage(*id); + } + } } } } } - fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize { - let mut created = 0; + fn create_children( + &mut self, + parent: ElementId, + nodes: &'b [VNode<'b>], + nodes_vec: &mut Vec, + ) { + nodes_vec.reserve(nodes.len()); for node in nodes { - created += self.create_node(node); + self.create_node(parent, node, nodes_vec); } - created } - fn create_and_append_children(&mut self, nodes: &'b [VNode<'b>]) { - let created = self.create_children(nodes); - self.mutations.append_children(created as u32); + fn create_and_append_children(&mut self, parent: ElementId, nodes: &'b [VNode<'b>]) { + let mut nodes_vec = Vec::with_capacity(nodes.len()); + self.create_children(parent, nodes, &mut nodes_vec); + self.mutations + .append_children(Some(parent.as_u64()), nodes_vec); } - fn create_and_insert_after(&mut self, nodes: &'b [VNode<'b>], after: &'b VNode<'b>) { - let created = self.create_children(nodes); + fn create_and_insert_after( + &mut self, + parent: ElementId, + nodes: &'b [VNode<'b>], + after: &'b VNode<'b>, + ) { + let mut nodes_vec = Vec::with_capacity(nodes.len()); + self.create_children(parent, nodes, &mut nodes_vec); let last = self.find_last_element(after).unwrap(); - self.mutations.insert_after(last, created as u32); + self.mutations.insert_after(Some(last.as_u64()), nodes_vec); } - fn create_and_insert_before(&mut self, nodes: &'b [VNode<'b>], before: &'b VNode<'b>) { - let created = self.create_children(nodes); + fn create_and_insert_before( + &mut self, + parent: ElementId, + nodes: &'b [VNode<'b>], + before: &'b VNode<'b>, + ) { + let mut nodes_vec = Vec::with_capacity(nodes.len()); + self.create_children(parent, nodes, &mut nodes_vec); let first = self.find_first_element(before).unwrap(); - self.mutations.insert_before(first, created as u32); + self.mutations + .insert_before(Some(first.as_u64()), nodes_vec); } pub fn current_scope(&self) -> ScopeId { @@ -1362,7 +1432,7 @@ impl<'b> DiffState<'b> { self.scope_stack.pop(); } - fn find_last_element(&self, vnode: &'b VNode<'b>) -> Option { + fn find_last_element(&mut self, vnode: &'b VNode<'b>) -> Option { let mut search_node = Some(vnode); loop { match &search_node.take().unwrap() { @@ -1374,12 +1444,17 @@ impl<'b> DiffState<'b> { let scope_id = el.scope.get().unwrap(); search_node = Some(self.scopes.root_node(scope_id)); } - VNode::TemplateRef(t) => break t.id.get(), + VNode::TemplateRef(t) => { + let templates = self.scopes.templates.borrow(); + let template = templates.get(&t.template_id).unwrap(); + let template = template.borrow(); + break template.root_nodes().last().map(|id| t.get_node_id(*id)); + } } } } - fn find_first_element(&self, vnode: &'b VNode<'b>) -> Option { + fn find_first_element(&mut self, vnode: &'b VNode<'b>) -> Option { let mut search_node = Some(vnode); loop { match &search_node.take().expect("search node to have an ID") { @@ -1391,31 +1466,46 @@ impl<'b> DiffState<'b> { let scope = el.scope.get().expect("element to have a scope assigned"); search_node = Some(self.scopes.root_node(scope)); } - VNode::TemplateRef(t) => break t.id.get(), + VNode::TemplateRef(t) => { + let templates = self.scopes.templates.borrow(); + let template = templates.get(&t.template_id).unwrap(); + let template = template.borrow(); + break template.root_nodes().first().map(|id| t.get_node_id(*id)); + } } } } // 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 { + fn get_all_real_nodes(&mut self, node: &'b VNode<'b>, nodes: &mut Vec) { match node { - VNode::Text(_) | VNode::Placeholder(_) | VNode::Element(_) | VNode::TemplateRef(_) => { - self.mutations.push_root(node.mounted_id()); - 1 + VNode::Text(_) | VNode::Placeholder(_) | VNode::Element(_) => { + nodes.push(node.mounted_id().0 as u64); + } + + VNode::TemplateRef(template_ref) => { + let templates = self.scopes.templates.borrow(); + let template = templates.get(&template_ref.template_id).unwrap(); + let template = template.borrow(); + nodes.extend( + template + .root_nodes() + .iter() + .map(|id| template_ref.get_node_id(*id).as_u64()), + ); } VNode::Fragment(frag) => { - let mut added = 0; + nodes.reserve(frag.children.len()); for child in frag.children { - added += self.push_all_real_nodes(child); + self.get_all_real_nodes(child, nodes); } - added } VNode::Component(c) => { let scope_id = c.scope.get().unwrap(); let root = self.scopes.root_node(scope_id); - self.push_all_real_nodes(root) + self.get_all_real_nodes(root, nodes); } } } @@ -1429,7 +1519,7 @@ impl<'b> DiffState<'b> { .bump } - pub fn register_template(&mut self, template: &Template, id: RendererTemplateId) { + pub fn register_template(&mut self, template: &Template, id: ElementId) { let bump = &self.scopes.template_bump; template.create(&mut self.mutations, bump, id); } diff --git a/packages/core/src/dynamic_template_context.rs b/packages/core/src/dynamic_template_context.rs index 81f58e336..577a738de 100644 --- a/packages/core/src/dynamic_template_context.rs +++ b/packages/core/src/dynamic_template_context.rs @@ -1,10 +1,10 @@ -use std::{marker::PhantomData, ops::Deref}; +use std::{fmt::Write, marker::PhantomData, ops::Deref}; use once_cell::sync::Lazy; use crate::{ template::{TemplateNodeId, TextTemplateSegment}, - AttributeValue, Listener, VNode, + AttributeValue, Listener, TextTemplate, VNode, }; /// A lazily initailized vector @@ -96,28 +96,6 @@ where nodes_with_listeners: listeners, } } - - pub(crate) fn all_dynamic(&self) -> impl Iterator + '_ { - self.nodes - .as_ref() - .iter() - .filter_map(|o| o.as_ref()) - .chain( - self.text - .as_ref() - .iter() - .flat_map(|ids| ids.as_ref().iter()), - ) - .copied() - .chain( - self.attributes - .as_ref() - .iter() - .flat_map(|ids| ids.as_ref().iter()) - .map(|dynamic| dynamic.0), - ) - .chain(self.nodes_with_listeners.as_ref().iter().copied()) - } } /// A dynamic node mapping that is stack allocated @@ -161,19 +139,38 @@ pub struct TemplateContext<'b> { impl<'b> TemplateContext<'b> { /// Resolve text segments to a string - pub fn resolve_text(&self, text: &TextSegments) -> String + pub fn resolve_text( + &self, + text: &TextTemplate, + ) -> String where TextSegments: AsRef<[TextTemplateSegment]>, Text: AsRef, { - let mut result = String::new(); - for seg in text.as_ref() { + let mut result = String::with_capacity(text.min_size); + self.resolve_text_into(text, &mut result); + result + } + + /// Resolve text and writes the result + pub fn resolve_text_into( + &self, + text: &TextTemplate, + result: &mut impl Write, + ) where + TextSegments: AsRef<[TextTemplateSegment]>, + Text: AsRef, + { + for seg in text.segments.as_ref() { match seg { - TextTemplateSegment::Static(s) => result += s.as_ref(), - TextTemplateSegment::Dynamic(idx) => result += self.text_segments[*idx], + TextTemplateSegment::Static(s) => { + let _ = result.write_str(s.as_ref()); + } + TextTemplateSegment::Dynamic(idx) => { + let _ = result.write_str(self.text_segments[*idx]); + } } } - result } /// Resolve an attribute value diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index a42689069..0668f7582 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -3,7 +3,7 @@ //! //! This is all kinda WIP, but the bones are there. -use crate::{nodes::GlobalNodeId, ScopeId}; +use crate::{ElementId, ScopeId}; use std::{any::Any, cell::Cell, fmt::Debug, rc::Rc, sync::Arc}; pub(crate) struct BubbleState { @@ -58,7 +58,7 @@ pub struct UserEvent { pub priority: EventPriority, /// The optional real node associated with the trigger - pub element: Option, + pub element: Option, /// The event type IE "onclick" or "onmouseover" pub name: &'static str, diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index c970ea835..305d2c97c 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -69,19 +69,19 @@ pub(crate) mod innerlude { pub use crate::innerlude::{ AnyEvent, ArbitraryAttributeValue, Attribute, AttributeDiscription, AttributeValue, CodeLocation, Component, DioxusElement, DomEdit, DynamicNodeMapping, Element, ElementId, - ElementIdIterator, EventHandler, EventPriority, GlobalNodeId, IntoAttributeValue, IntoVNode, - LazyNodes, Listener, Mutations, NodeFactory, OwnedAttributeValue, Properties, - RendererTemplateId, SchedulerMsg, Scope, ScopeId, ScopeState, StaticCodeLocation, - StaticDynamicNodeMapping, StaticTemplateNode, StaticTemplateNodes, TaskId, Template, + ElementIdIterator, EventHandler, EventPriority, IntoAttributeValue, IntoVNode, LazyNodes, + Listener, Mutations, NodeFactory, OwnedAttributeValue, PathSeg, Properties, RendererTemplateId, + SchedulerMsg, Scope, ScopeId, ScopeState, StaticCodeLocation, StaticDynamicNodeMapping, + StaticPathSeg, StaticTemplateNode, StaticTemplateNodes, StaticTraverse, TaskId, Template, TemplateAttribute, TemplateAttributeValue, TemplateContext, TemplateElement, TemplateId, TemplateNode, TemplateNodeId, TemplateNodeType, TemplateValue, TextTemplate, - TextTemplateSegment, UiEvent, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder, - VText, VirtualDom, JS_MAX_INT, + TextTemplateSegment, UiEvent, UpdateOp, UserEvent, VComponent, VElement, VFragment, VNode, + VPlaceholder, VText, VirtualDom, }; #[cfg(any(feature = "hot-reload", debug_assertions))] pub use crate::innerlude::{ - OwnedCodeLocation, OwnedDynamicNodeMapping, OwnedTemplateNode, OwnedTemplateNodes, - SetTemplateMsg, + OwnedCodeLocation, OwnedDynamicNodeMapping, OwnedPathSeg, OwnedTemplateNode, + OwnedTemplateNodes, OwnedTraverse, SetTemplateMsg, }; /// The purpose of this module is to alleviate imports of many common types @@ -95,10 +95,10 @@ pub mod prelude { fc_to_builder, AttributeDiscription, AttributeValue, Attributes, CodeLocation, Component, DioxusElement, Element, EventHandler, Fragment, IntoAttributeValue, LazyNodes, LazyStaticVec, NodeFactory, Properties, Scope, ScopeId, ScopeState, StaticAttributeValue, - StaticCodeLocation, StaticDynamicNodeMapping, StaticTemplate, StaticTemplateNodes, - Template, TemplateAttribute, TemplateAttributeValue, TemplateContext, TemplateElement, - TemplateId, TemplateNode, TemplateNodeId, TemplateNodeType, TextTemplate, - TextTemplateSegment, VNode, VirtualDom, + StaticCodeLocation, StaticDynamicNodeMapping, StaticPathSeg, StaticTemplate, + StaticTemplateNodes, StaticTraverse, Template, TemplateAttribute, TemplateAttributeValue, + TemplateContext, TemplateElement, TemplateId, TemplateNode, TemplateNodeId, + TemplateNodeType, TextTemplate, TextTemplateSegment, UpdateOp, VNode, VirtualDom, }; } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 6151a38e7..32f450f22 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -44,57 +44,53 @@ impl Debug for Mutations<'_> { 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: u64, - }, - /// 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, + /// The parent to append nodes to. + root: Option, + + /// The ids of the children to append. + children: Vec, }, /// Replace a given (single) node with a handful of nodes currently on the stack. ReplaceWith { /// The ID of the node to be replaced. - root: u64, + root: Option, - /// How many nodes should be popped from the stack to replace the target node. - m: u32, + /// The ids of the nodes to replace the root with. + nodes: Vec, }, /// Insert a number of nodes after a given node. InsertAfter { /// The ID of the node to insert after. - root: u64, + root: Option, - /// How many nodes should be popped from the stack to insert after the target node. - n: u32, + /// The ids of the nodes to insert after the target node. + nodes: Vec, }, /// Insert a number of nodes before a given node. InsertBefore { /// The ID of the node to insert before. - root: u64, + root: Option, - /// How many nodes should be popped from the stack to insert before the target node. - n: u32, + /// The ids of the nodes to insert before the target node. + nodes: Vec, }, /// Remove a particular node from the DOM Remove { /// The ID of the node to remove. - root: u64, + root: Option, }, /// Create a new purely-text node CreateTextNode { /// The ID the new node should have. - root: u64, + root: Option, /// The textcontent of the node text: &'bump str, @@ -103,82 +99,35 @@ pub enum DomEdit<'bump> { /// Create a new purely-element node CreateElement { /// The ID the new node should have. - root: u64, + root: Option, /// The tagname of the node tag: &'bump str, + + /// The number of children nodes that will follow this message. + children: u32, }, /// Create a new purely-comment node with a given namespace CreateElementNs { /// The ID the new node should have. - root: u64, + root: Option, /// The namespace of the node tag: &'bump str, /// The namespace of the node (like `SVG`) ns: &'static str, + + /// The number of children nodes that will follow this message. + children: u32, }, /// 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: u64, - }, - - /// Create a new purely-text node in a template - CreateTextNodeTemplate { - /// The ID the new node should have. - root: u64, - - /// The textcontent of the noden - text: &'bump str, - - /// If the id of the node must be kept in the refrences - locally_static: bool, - }, - - /// Create a new purely-element node in a template - CreateElementTemplate { - /// The ID the new node should have. - root: u64, - - /// The tagname of the node - tag: &'bump str, - - /// If the id of the node must be kept in the refrences - locally_static: bool, - - /// If any children of this node must be kept in the references - fully_static: bool, - }, - - /// Create a new purely-comment node with a given namespace in a template - CreateElementNsTemplate { - /// The ID the new node should have. - root: u64, - - /// The namespace of the node - tag: &'bump str, - - /// The namespace of the node (like `SVG`) - ns: &'static str, - - /// If the id of the node must be kept in the refrences - locally_static: bool, - - /// If any children of this node must be kept in the references - fully_static: bool, - }, - - /// Create a new placeholder node. - /// In most implementations, this will either be a hidden div or a comment node. in a template - /// Always both locally and fully static - CreatePlaceholderTemplate { - /// The ID the new node should have. - root: u64, + root: Option, }, /// Create a new Event Listener. @@ -190,13 +139,13 @@ pub enum DomEdit<'bump> { scope: ScopeId, /// The ID of the node to attach the listener to. - root: u64, + root: Option, }, /// Remove an existing Event Listener. RemoveEventListener { /// The ID of the node to remove. - root: u64, + root: Option, /// The name of the event to remove. event: &'static str, @@ -205,7 +154,7 @@ pub enum DomEdit<'bump> { /// Set the textcontent of a node. SetText { /// The ID of the node to set the textcontent of. - root: u64, + root: Option, /// The textcontent of the node text: &'bump str, @@ -214,7 +163,7 @@ pub enum DomEdit<'bump> { /// Set the value of a node's attribute. SetAttribute { /// The ID of the node to set the attribute of. - root: u64, + root: Option, /// The name of the attribute to set. field: &'static str, @@ -231,7 +180,7 @@ pub enum DomEdit<'bump> { /// Remove an attribute from a node. RemoveAttribute { /// The ID of the node to remove. - root: u64, + root: Option, /// The name of the attribute to remove. name: &'static str, @@ -240,45 +189,50 @@ pub enum DomEdit<'bump> { ns: Option<&'bump str>, }, - /// Manually pop a root node from the stack. - PopRoot {}, + /// Clones a node. + CloneNode { + /// The ID of the node to clone. + id: Option, - /// Enter a TemplateRef tree - EnterTemplateRef { - /// The ID of the node to enter. - root: u64, + /// The ID of the new node. + new_id: u64, }, - /// Exit a TemplateRef tree - ExitTemplateRef {}, + /// Clones the children of a node. (allows cloning fragments) + CloneNodeChildren { + /// The ID of the node to clone. + id: Option, - /// Create a refrence to a template node. - CreateTemplateRef { - /// The ID of the new template refrence. - id: u64, - - /// The ID of the template the node is refrencing. - template_id: u64, + /// The ID of the new node. + new_ids: Vec, }, - /// Create a new templete. - /// IMPORTANT: When adding nodes to a templete, id's will reset to zero, so they must be allocated on a different stack. - /// It is recommended to use Cow. - CreateTemplate { - /// The ID of the new template. + /// Navigates to the last node to the first child of the current node. + FirstChild {}, + + /// Navigates to the last node to the last child of the current node. + NextSibling {}, + + /// Navigates to the last node to the parent of the current node. + ParentNode {}, + + /// Stores the last node with a new id. + StoreWithId { + /// The ID of the node to store. id: u64, }, - /// Finish a templete - FinishTemplate { - /// The number of root nodes in the template - len: u32, + /// Manually set the last node. + SetLastNode { + /// The ID to set the last node to. + id: u64, }, } use rustc_hash::FxHashSet; use DomEdit::*; +#[allow(unused)] impl<'a> Mutations<'a> { pub(crate) fn new() -> Self { Self { @@ -288,44 +242,29 @@ impl<'a> Mutations<'a> { } } - // Navigation - pub(crate) fn push_root(&mut self, root: impl Into) { - let id = root.into(); - self.edits.push(PushRoot { root: id }); + pub(crate) fn replace_with(&mut self, root: Option, nodes: Vec) { + self.edits.push(ReplaceWith { nodes, root }); } - // Navigation - pub(crate) fn pop_root(&mut self) { - self.edits.push(PopRoot {}); + pub(crate) fn insert_after(&mut self, root: Option, nodes: Vec) { + self.edits.push(InsertAfter { nodes, root }); } - pub(crate) fn replace_with(&mut self, root: impl Into, m: u32) { - let root = root.into(); - self.edits.push(ReplaceWith { m, root }); + pub(crate) fn insert_before(&mut self, root: Option, nodes: Vec) { + self.edits.push(InsertBefore { nodes, root }); } - pub(crate) fn insert_after(&mut self, root: impl Into, n: u32) { - let root = root.into(); - self.edits.push(InsertAfter { n, root }); - } - - pub(crate) fn insert_before(&mut self, root: impl Into, n: u32) { - let root = root.into(); - self.edits.push(InsertBefore { n, root }); - } - - pub(crate) fn append_children(&mut self, n: u32) { - self.edits.push(AppendChildren { many: n }); + pub(crate) fn append_children(&mut self, root: Option, children: Vec) { + self.edits.push(AppendChildren { root, children }); } // Remove Nodes from the dom - pub(crate) fn remove(&mut self, id: impl Into) { - self.edits.push(Remove { root: id.into() }); + pub(crate) fn remove(&mut self, id: Option) { + self.edits.push(Remove { root: id }); } // Create - pub(crate) fn create_text_node(&mut self, text: &'a str, id: impl Into) { - let id = id.into(); + pub(crate) fn create_text_node(&mut self, text: &'a str, id: Option) { self.edits.push(CreateTextNode { text, root: id }); } @@ -333,66 +272,27 @@ impl<'a> Mutations<'a> { &mut self, tag: &'static str, ns: Option<&'static str>, - id: impl Into, + id: Option, + children: u32, ) { - let id = id.into(); 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: impl Into) { - let id = id.into(); - self.edits.push(CreatePlaceholder { root: id }); - } - - // Create - pub(crate) fn create_text_node_template( - &mut self, - text: &'a str, - id: impl Into, - locally_static: bool, - ) { - let id = id.into(); - self.edits.push(CreateTextNodeTemplate { - text, - root: id, - locally_static, - }); - } - - pub(crate) fn create_element_template( - &mut self, - tag: &'static str, - ns: Option<&'static str>, - id: impl Into, - locally_static: bool, - fully_static: bool, - ) { - let id = id.into(); - match ns { - Some(ns) => self.edits.push(CreateElementNsTemplate { + Some(ns) => self.edits.push(CreateElementNs { root: id, ns, tag, - locally_static, - fully_static, + children, }), - None => self.edits.push(CreateElementTemplate { + None => self.edits.push(CreateElement { root: id, tag, - locally_static, - fully_static, + children, }), } } // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom - pub(crate) fn create_placeholder_template(&mut self, id: impl Into) { - let id = id.into(); - self.edits.push(CreatePlaceholderTemplate { root: id }); + pub(crate) fn create_placeholder(&mut self, id: Option) { + self.edits.push(CreatePlaceholder { root: id }); } // events @@ -403,13 +303,7 @@ impl<'a> Mutations<'a> { .. } = listener; - let element_id = match mounted_node.get().unwrap() { - GlobalNodeId::TemplateId { - template_ref_id: _, - template_node_id, - } => template_node_id.into(), - GlobalNodeId::VNodeId(id) => id.into(), - }; + let element_id = Some(mounted_node.get().unwrap().into()); self.edits.push(NewEventListener { scope, @@ -418,21 +312,16 @@ impl<'a> Mutations<'a> { }); } - pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: impl Into) { - self.edits.push(RemoveEventListener { - event, - root: root.into(), - }); + pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: Option) { + self.edits.push(RemoveEventListener { event, root }); } // modify - pub(crate) fn set_text(&mut self, text: &'a str, root: impl Into) { - let root = root.into(); + pub(crate) fn set_text(&mut self, text: &'a str, root: Option) { self.edits.push(SetText { text, root }); } - pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: impl Into) { - let root = root.into(); + pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: Option) { let Attribute { value, attribute, .. } = attribute; @@ -445,8 +334,7 @@ impl<'a> Mutations<'a> { }); } - pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: impl Into) { - let root = root.into(); + pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: Option) { let Attribute { attribute, .. } = attribute; self.edits.push(RemoveAttribute { @@ -460,31 +348,32 @@ impl<'a> Mutations<'a> { self.dirty_scopes.insert(scope); } - pub(crate) fn create_templete(&mut self, id: impl Into) { - self.edits.push(CreateTemplate { id: id.into() }); + pub(crate) fn clone_node(&mut self, id: Option, new_id: u64) { + self.edits.push(CloneNode { id, new_id }); } - pub(crate) fn finish_templete(&mut self, len: u32) { - self.edits.push(FinishTemplate { len }); + pub(crate) fn clone_node_children(&mut self, id: Option, new_ids: Vec) { + self.edits.push(CloneNodeChildren { id, new_ids }); } - pub(crate) fn create_template_ref(&mut self, id: impl Into, template_id: u64) { - self.edits.push(CreateTemplateRef { - id: id.into(), - template_id, - }) + pub(crate) fn first_child(&mut self) { + self.edits.push(FirstChild {}); } - pub(crate) fn enter_template_ref(&mut self, id: impl Into) { - self.edits.push(EnterTemplateRef { root: id.into() }); + pub(crate) fn next_sibling(&mut self) { + self.edits.push(NextSibling {}); } - pub(crate) fn exit_template_ref(&mut self) { - if let Some(&DomEdit::EnterTemplateRef { .. }) = self.edits.last() { - self.edits.pop(); - } else { - self.edits.push(ExitTemplateRef {}); - } + pub(crate) fn parent_node(&mut self) { + self.edits.push(ParentNode {}); + } + + pub(crate) fn store_with_id(&mut self, id: u64) { + self.edits.push(StoreWithId { id }); + } + + pub(crate) fn set_last_node(&mut self, id: u64) { + self.edits.push(SetLastNode { id }); } } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 52d61322e..fed8c548d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -9,64 +9,16 @@ use crate::{ ScopeState, Template, TemplateId, }, lazynodes::LazyNodes, - template::{TemplateNodeId, VTemplateRef}, + template::VTemplateRef, AnyEvent, Component, }; use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ cell::{Cell, RefCell}, fmt::{Arguments, Debug, Formatter}, - num::ParseIntError, rc::Rc, - str::FromStr, }; -/// The ID of a node in the vdom that is either standalone or in a template -#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serialize", serde(untagged))] -pub enum GlobalNodeId { - /// The ID of a node and the template that contains it - TemplateId { - /// The template that contains the node - template_ref_id: ElementId, - /// The ID of the node in the template - template_node_id: TemplateNodeId, - }, - /// The ID of a regular node - VNodeId(ElementId), -} - -impl From for GlobalNodeId { - fn from(id: ElementId) -> GlobalNodeId { - GlobalNodeId::VNodeId(id) - } -} - -impl PartialEq for GlobalNodeId { - fn eq(&self, other: &ElementId) -> bool { - match self { - GlobalNodeId::TemplateId { .. } => false, - GlobalNodeId::VNodeId(id) => id == other, - } - } -} - -impl FromStr for GlobalNodeId { - type Err = ParseIntError; - - fn from_str(s: &str) -> Result { - if let Some((tmpl_id, node_id)) = s.split_once(',') { - Ok(GlobalNodeId::TemplateId { - template_ref_id: ElementId(tmpl_id.parse()?), - template_node_id: TemplateNodeId(node_id.parse()?), - }) - } else { - Ok(GlobalNodeId::VNodeId(ElementId(s.parse()?))) - } - } -} - /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM. /// /// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of: @@ -201,7 +153,7 @@ impl<'src> VNode<'src> { VNode::Placeholder(el) => el.id.get(), VNode::Fragment(_) => None, VNode::Component(_) => None, - VNode::TemplateRef(t) => t.id.get(), + VNode::TemplateRef(_) => None, } } @@ -252,7 +204,6 @@ impl Debug for VNode<'_> { VNode::TemplateRef(temp) => s .debug_struct("VNode::TemplateRef") .field("template_id", &temp.template_id) - .field("id", &temp.id) .finish(), } } @@ -340,7 +291,7 @@ pub struct VElement<'a> { /// The parent of the Element (if any). /// /// Used when bubbling events - pub parent: Cell>, + pub parent: Cell>, /// The Listeners of the VElement. pub listeners: &'a [Listener<'a>], @@ -456,7 +407,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. /// @@ -515,9 +466,11 @@ impl<'a, T> Default for EventHandler<'a, T> { impl EventHandler<'_, T> { /// Call this event handler with the appropriate event type pub fn call(&self, event: T) { + log::trace!("calling event handler"); if let Some(callback) = self.callback.borrow_mut().as_mut() { callback(event); } + log::trace!("done"); } /// Forcibly drop the internal handler callback, releasing memory @@ -870,9 +823,11 @@ impl<'a> NodeFactory<'a> { borrow_mut.insert(id.clone(), Rc::new(RefCell::new(template))); } VNode::TemplateRef(self.bump.alloc(VTemplateRef { - id: empty_cell(), dynamic_context, template_id: id, + node_ids: RefCell::new(Vec::new()), + parent: Cell::new(None), + template_ref_id: Cell::new(None), })) } } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 6078b22e3..f5ec56644 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -1,19 +1,11 @@ -use crate::{ - dynamic_template_context::TemplateContext, - innerlude::*, - template::{ - TemplateAttribute, TemplateElement, TemplateNode, TemplateNodeId, TemplateNodeType, - TemplateValue, TextTemplateSegment, - }, - unsafe_utils::extend_vnode, -}; +use crate::{innerlude::*, template::TemplateNodeId, unsafe_utils::extend_vnode}; use bumpalo::Bump; use futures_channel::mpsc::UnboundedSender; use rustc_hash::FxHashMap; use slab::Slab; use std::{ any::{Any, TypeId}, - cell::{Cell, Ref, RefCell}, + cell::{Cell, RefCell}, collections::{HashMap, HashSet}, future::Future, pin::Pin, @@ -21,6 +13,17 @@ use std::{ sync::Arc, }; +pub(crate) enum NodePtr { + VNode(*const VNode<'static>), + TemplateNode { + template_ref: TemplateRefId, + node_id: TemplateNodeId, + }, + Phantom, +} + +pub(crate) type NodeSlab = Slab; + /// for traceability, we use the raw fn pointer to identify the function /// we also get the component name, but that's not necessarily unique in the app pub(crate) type ComponentPtr = *mut std::os::raw::c_void; @@ -40,12 +43,16 @@ pub(crate) struct ScopeArena { pub scopes: RefCell>, pub heuristics: RefCell>, pub free_scopes: RefCell>, - pub nodes: RefCell>>, + // All nodes are stored here. This mimics the allocations needed on the renderer side. + // Some allocations are needed on the renderer to render nodes in templates that are + // not needed in dioxus, for these allocations None is used so that the id is preseved and passive memory managment is preserved. + pub nodes: RefCell, pub tasks: Rc, pub template_resolver: RefCell, pub templates: Rc>>>>, // this is used to store intermidiate artifacts of creating templates, so that the lifetime aligns with Mutations<'bump>. pub template_bump: Bump, + pub template_refs: RefCell>>, } impl ScopeArena { @@ -68,7 +75,9 @@ impl ScopeArena { let node = bump.alloc(VNode::Element(el)); let mut nodes = Slab::new(); - let root_id = nodes.insert(unsafe { std::mem::transmute(node as *const _) }); + let root_id = nodes.insert(NodePtr::VNode(unsafe { + std::mem::transmute(node as *const _) + })); debug_assert_eq!(root_id, 0); @@ -88,6 +97,7 @@ impl ScopeArena { template_resolver: RefCell::new(TemplateResolver::default()), templates: Rc::new(RefCell::new(FxHashMap::default())), template_bump: Bump::new(), + template_refs: RefCell::new(Slab::new()), } } @@ -107,7 +117,7 @@ impl ScopeArena { fc_ptr: ComponentPtr, vcomp: Box, parent_scope: Option, - container: GlobalNodeId, + container: ElementId, ) -> ScopeId { // Increment the ScopeId system. ScopeIDs are never reused let new_scope_id = ScopeId(self.scope_gen.get()); @@ -217,13 +227,61 @@ impl ScopeArena { let key = entry.key(); let id = ElementId(key); let node = unsafe { extend_vnode(node) }; - entry.insert(node as *const _); + entry.insert(NodePtr::VNode(node as *const _)); + id + } + + pub fn reserve_template_ref<'a>(&self, template_ref: &'a VTemplateRef<'a>) -> TemplateRefId { + let mut refs = self.template_refs.borrow_mut(); + let entry = refs.vacant_entry(); + let key = entry.key(); + let id = TemplateRefId(key); + let static_ref: &VTemplateRef<'static> = unsafe { std::mem::transmute(template_ref) }; + entry.insert(static_ref as *const _); + id + } + + pub fn reserve_template_node( + &self, + template_ref_id: TemplateRefId, + node_id: TemplateNodeId, + ) -> ElementId { + let mut els = self.nodes.borrow_mut(); + let entry = els.vacant_entry(); + let key = entry.key(); + let id = ElementId(key); + entry.insert(NodePtr::TemplateNode { + template_ref: template_ref_id, + node_id, + }); + id + } + + pub fn reserve_phantom_node(&self) -> ElementId { + let mut els = self.nodes.borrow_mut(); + let entry = els.vacant_entry(); + let key = entry.key(); + let id = ElementId(key); + entry.insert(NodePtr::Phantom); id } pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) { let node = unsafe { extend_vnode(node) }; - *self.nodes.borrow_mut().get_mut(id.0).unwrap() = node; + *self.nodes.borrow_mut().get_mut(id.0).unwrap() = NodePtr::VNode(node); + } + + pub fn update_template_ref<'a>( + &self, + template_ref_id: TemplateRefId, + template_ref: &'a VTemplateRef<'a>, + ) { + let template_ref = unsafe { std::mem::transmute(template_ref) }; + *self + .template_refs + .borrow_mut() + .get_mut(template_ref_id.0) + .unwrap() = template_ref; } pub fn collect_garbage(&self, id: ElementId) { @@ -324,7 +382,7 @@ impl ScopeArena { scope.cycle_frame(); } - pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: GlobalNodeId) { + pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: ElementId) { let nodes = self.nodes.borrow(); let mut cur_el = Some(element); @@ -335,39 +393,10 @@ impl ScopeArena { // stop bubbling if canceled return; } - match id { - GlobalNodeId::TemplateId { - template_ref_id, - template_node_id, - } => { - log::trace!( - "looking for listener in {:?} in node {:?}", - template_ref_id, - template_node_id - ); - if let Some(template) = nodes.get(template_ref_id.0) { - let template = unsafe { &**template }; - if let VNode::TemplateRef(template_ref) = template { - let templates = self.templates.borrow(); - let template = templates.get(&template_ref.template_id).unwrap(); - cur_el = template.borrow().with_node( - template_node_id, - bubble_template, - bubble_template, - ( - &nodes, - &template_ref.dynamic_context, - event, - &state, - template_ref_id, - ), - ); - } - } - } - GlobalNodeId::VNodeId(id) => { - if let Some(el) = nodes.get(id.0) { - let real_el = unsafe { &**el }; + if let Some(ptr) = nodes.get(id.0) { + match ptr { + NodePtr::VNode(ptr) => { + let real_el = unsafe { &**ptr }; log::trace!("looking for listener on {:?}", real_el); if let VNode::Element(real_el) = real_el { @@ -393,24 +422,42 @@ impl ScopeArena { cur_el = real_el.parent.get(); } } + NodePtr::TemplateNode { + node_id, + template_ref, + } => { + let template_refs = self.template_refs.borrow(); + let template_ptr = template_refs.get(template_ref.0).unwrap(); + let template_ref = unsafe { &**template_ptr }; + log::trace!("looking for listener in node {:?}", node_id); + let templates = self.templates.borrow(); + let template = templates.get(&template_ref.template_id).unwrap(); + cur_el = template.borrow().with_nodes( + bubble_template, + bubble_template, + (*node_id, template_ref, event, &state), + ); + } + _ => panic!("Expected Real Node"), } } + if !event.bubbles { return; } } - fn bubble_template<'b, Attributes, V, Children, Listeners, TextSegments, Text>( - node: &TemplateNode, + fn bubble_template<'b, Attributes, V, Children, Listeners, TextSegments, Text, Nodes>( + nodes: &Nodes, ctx: ( - &Ref>, - &TemplateContext<'b>, + TemplateNodeId, + &VTemplateRef<'b>, &UserEvent, &Rc, - ElementId, ), - ) -> Option + ) -> Option where + Nodes: AsRef<[TemplateNode]>, Attributes: AsRef<[TemplateAttribute]>, V: TemplateValue, Children: AsRef<[TemplateNodeId]>, @@ -418,46 +465,40 @@ impl ScopeArena { TextSegments: AsRef<[TextTemplateSegment]>, Text: AsRef, { - let (vnodes, dynamic_context, event, state, template_ref_id) = ctx; - if let TemplateNodeType::Element(el) = &node.node_type { - let TemplateElement { listeners, .. } = el; - for listener_idx in listeners.as_ref() { - let listener = dynamic_context.resolve_listener(*listener_idx); - if listener.event == event.name { - log::trace!("calling listener {:?}", listener.event); + let (start, template_ref, event, state) = ctx; + let dynamic_context = &template_ref.dynamic_context; + let mut current = &nodes.as_ref()[start.0]; + loop { + if let TemplateNodeType::Element(el) = ¤t.node_type { + let TemplateElement { listeners, .. } = el; + for listener_idx in listeners.as_ref() { + let listener = dynamic_context.resolve_listener(*listener_idx); + if listener.event == event.name { + log::trace!("calling listener {:?}", listener.event); - let mut cb = listener.callback.borrow_mut(); - if let Some(cb) = cb.as_mut() { - // todo: arcs are pretty heavy to clone - // we really want to convert arc to rc - // unfortunately, the SchedulerMsg must be send/sync to be sent across threads - // we could convert arc to rc internally or something - (cb)(AnyEvent { - bubble_state: state.clone(), - data: event.data.clone(), - }); + let mut cb = listener.callback.borrow_mut(); + if let Some(cb) = cb.as_mut() { + // todo: arcs are pretty heavy to clone + // we really want to convert arc to rc + // unfortunately, the SchedulerMsg must be send/sync to be sent across threads + // we could convert arc to rc internally or something + (cb)(AnyEvent { + bubble_state: state.clone(), + data: event.data.clone(), + }); + } + break; } - break; } - } - if let Some(id) = el.parent { - Some(GlobalNodeId::TemplateId { - template_ref_id, - template_node_id: id, - }) + if let Some(id) = current.parent { + current = &nodes.as_ref()[id.0]; + } else { + return template_ref.parent.get(); + } } else { - vnodes.get(template_ref_id.0).and_then(|el| { - let real_el = unsafe { &**el }; - if let VNode::Element(real_el) = real_el { - real_el.parent.get() - } else { - None - } - }) + return None; } - } else { - None } } } @@ -484,11 +525,10 @@ impl ScopeArena { // this is totally okay since all our nodes are always in a valid state pub fn get_element(&self, id: ElementId) -> Option<&VNode> { - self.nodes - .borrow() - .get(id.0) - .copied() - .map(|ptr| unsafe { extend_vnode(&*ptr) }) + self.nodes.borrow().get(id.0).and_then(|ptr| match ptr { + NodePtr::VNode(ptr) => Some(unsafe { extend_vnode(&**ptr) }), + _ => None, + }) } } @@ -569,7 +609,7 @@ pub struct TaskId { /// use case they might have. pub struct ScopeState { pub(crate) parent_scope: Option<*mut ScopeState>, - pub(crate) container: GlobalNodeId, + pub(crate) container: ElementId, pub(crate) our_arena_idx: ScopeId, pub(crate) height: u32, pub(crate) fnptr: ComponentPtr, diff --git a/packages/core/src/template.rs b/packages/core/src/template.rs index fd12164f7..1788709f6 100644 --- a/packages/core/src/template.rs +++ b/packages/core/src/template.rs @@ -51,26 +51,30 @@ //! To minimize the cost of allowing hot reloading on applications that do not use it there are &'static and owned versions of template nodes, and dynamic node mapping. //! //! Notes: -//! 1) Why does the template need to exist outside of the virtual dom? -//! The main use of the template is skipping diffing on static parts of the dom, but it is also used to make renderes more efficient. Renderers can create a template once and the clone it into place. -//! When the renderers clone the template we could those new nodes as normal vnodes, but that would interfere with the passive memory management of the nodes. This would mean that static nodes memory must be managed by the virtual dom even though those static nodes do not exist in the virtual dom. -//! 2) The template allow diffing to scale with reactivity. +//! 1) The template allow diffing to scale with reactivity. //! With a virtual dom the diffing cost scales with the number of nodes in the dom. With templates the cost scales with the number of dynamic parts of the dom. The dynamic template context links any parts of the template that can change which allows the diffing algorithm to skip traversing the template and find what part to hydrate in constant time. -/// The maxiumum integer in JS -pub const JS_MAX_INT: u64 = 9007199254740991; +use once_cell::unsync::OnceCell; +use std::{ + cell::{Cell, RefCell}, + hash::Hash, + marker::PhantomData, + ptr, +}; use rustc_hash::FxHashMap; -use std::{cell::Cell, hash::Hash, marker::PhantomData, ops::Index}; use bumpalo::Bump; use crate::{ - diff::DiffState, dynamic_template_context::TemplateContext, innerlude::GlobalNodeId, - nodes::AttributeDiscription, Attribute, AttributeValue, ElementId, Mutations, - OwnedAttributeValue, StaticDynamicNodeMapping, + diff::DiffState, dynamic_template_context::TemplateContext, nodes::AttributeDiscription, + scopes::ScopeArena, Attribute, AttributeValue, ElementId, Mutations, OwnedAttributeValue, + OwnedDynamicNodeMapping, StaticDynamicNodeMapping, }; +#[derive(Debug, Clone, Copy)] +pub(crate) struct TemplateRefId(pub usize); + /// The location of a charicter. Used to track the location of rsx calls for hot reloading. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr( @@ -140,10 +144,8 @@ impl Hash for CodeLocation { fn hash(&self, state: &mut H) { match self { CodeLocation::Static(loc) => { - loc.crate_path.hash(state); - loc.file_path.hash(state); - state.write_u32(loc.line); - state.write_u32(loc.column); + let loc: &'static _ = *loc; + state.write_usize((loc as *const _) as usize); } #[cfg(any(feature = "hot-reload", debug_assertions))] CodeLocation::Dynamic(loc) => { @@ -160,7 +162,7 @@ impl Hash for CodeLocation { impl PartialEq for CodeLocation { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Static(l), Self::Static(r)) => l == r, + (Self::Static(l), Self::Static(r)) => ptr::eq(*l, *r), #[cfg(any(feature = "hot-reload", debug_assertions))] (Self::Dynamic(l), Self::Dynamic(r)) => l == r, #[cfg(any(feature = "hot-reload", debug_assertions))] @@ -278,25 +280,161 @@ impl From for u64 { #[cfg_attr(feature = "serialize", serde(transparent))] pub struct TemplateNodeId(pub usize); -impl From for u64 { - fn from(id: TemplateNodeId) -> u64 { - JS_MAX_INT / 2 + id.0 as u64 - } -} - /// A refrence to a template along with any context needed to hydrate it pub struct VTemplateRef<'a> { - pub id: Cell>, + pub(crate) template_ref_id: Cell>, pub template_id: TemplateId, pub dynamic_context: TemplateContext<'a>, + /// The parent of the template + pub(crate) parent: Cell>, + // any nodes that already have ids assigned to them in the renderer + pub node_ids: RefCell>>, } impl<'a> VTemplateRef<'a> { // update the template with content from the dynamic context - pub(crate) fn hydrate<'b>(&self, template: &'b Template, diff_state: &mut DiffState<'a>) { + pub(crate) fn hydrate<'b: 'a>( + &'b self, + parent: ElementId, + template: &Template, + diff_state: &mut DiffState<'a>, + ) { + fn traverse_seg<'b, T, O, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>( + seg: &PathSeg, + nodes: &Nodes, + diff_state: &mut DiffState<'b>, + template_ref: &'b VTemplateRef<'b>, + parent: ElementId, + ) where + Nodes: AsRef<[TemplateNode]>, + Attributes: AsRef<[TemplateAttribute]>, + V: TemplateValue, + Children: AsRef<[TemplateNodeId]>, + Listeners: AsRef<[usize]>, + TextSegments: AsRef<[TextTemplateSegment]>, + Text: AsRef, + T: Traversable, + O: AsRef<[UpdateOp]>, + { + let mut current_node_id = None; + let mut temp_id = false; + for op in seg.ops.as_ref() { + match op { + UpdateOp::StoreNode(id) => { + if let Some(real_id) = template_ref.try_get_node_id(*id) { + current_node_id = Some(real_id); + nodes.as_ref()[id.0].hydrate(real_id, diff_state, template_ref); + } else { + let real_id = diff_state.scopes.reserve_template_node( + template_ref.template_ref_id.get().unwrap(), + *id, + ); + current_node_id = Some(real_id); + template_ref.set_node_id(*id, real_id); + diff_state.mutations.store_with_id(real_id.as_u64()); + nodes.as_ref()[id.0].hydrate(real_id, diff_state, template_ref); + } + } + UpdateOp::AppendChild(id) => { + let node = &nodes.as_ref()[id.0]; + match &node.node_type { + TemplateNodeType::DynamicNode(idx) => { + if current_node_id.is_none() { + // create a temporary node to come back to later + let id = diff_state.scopes.reserve_phantom_node(); + diff_state.mutations.store_with_id(id.as_u64()); + temp_id = true; + current_node_id = Some(id); + } + let id = current_node_id.unwrap(); + let mut created = Vec::new(); + let node = template_ref.dynamic_context.resolve_node(*idx); + diff_state.create_node(id, node, &mut created); + diff_state.mutations.set_last_node(id.as_u64()); + diff_state.mutations.append_children(None, created); + } + _ => panic!("can only insert dynamic nodes"), + } + } + UpdateOp::InsertBefore(id) | UpdateOp::InsertAfter(id) => { + let node = &nodes.as_ref()[id.0]; + match &node.node_type { + TemplateNodeType::DynamicNode(idx) => { + if current_node_id.is_none() { + // create a temporary node to come back to later + let id = diff_state.scopes.reserve_phantom_node(); + diff_state.mutations.store_with_id(id.as_u64()); + temp_id = true; + current_node_id = Some(id); + } + let id = current_node_id.unwrap(); + let mut created = Vec::new(); + let node = template_ref.dynamic_context.resolve_node(*idx); + diff_state.create_node(parent, node, &mut created); + diff_state.mutations.set_last_node(id.as_u64()); + match op { + UpdateOp::InsertBefore(_) => { + diff_state.mutations.insert_before(None, created); + } + UpdateOp::InsertAfter(_) => { + diff_state.mutations.insert_after(None, created); + } + _ => unreachable!(), + } + } + _ => panic!("can only insert dynamic nodes"), + } + } + } + } + match (seg.traverse.first_child(), seg.traverse.next_sibling()) { + (Some(child), Some(sibling)) => { + if current_node_id.is_none() { + // create a temporary node to come back to later + let id = diff_state.scopes.reserve_phantom_node(); + diff_state.mutations.store_with_id(id.as_u64()); + temp_id = true; + current_node_id = Some(id); + } + let id = current_node_id.unwrap(); + diff_state.mutations.first_child(); + traverse_seg(child, nodes, diff_state, template_ref, id); + diff_state.mutations.set_last_node(id.as_u64()); + diff_state.mutations.next_sibling(); + traverse_seg(sibling, nodes, diff_state, template_ref, parent); + } + (Some(seg), None) => { + if current_node_id.is_none() { + let id = diff_state.scopes.reserve_phantom_node(); + diff_state.mutations.store_with_id(id.as_u64()); + temp_id = true; + current_node_id = Some(id); + } + let id = current_node_id.unwrap(); + diff_state.mutations.first_child(); + traverse_seg(seg, nodes, diff_state, template_ref, id); + } + (None, Some(seg)) => { + diff_state.mutations.next_sibling(); + traverse_seg(seg, nodes, diff_state, template_ref, parent); + } + (None, None) => {} + } + if temp_id { + if let Some(id) = current_node_id { + // remove the temporary node + diff_state.scopes.collect_garbage(id); + } + } + } fn hydrate_inner<'b, Nodes, Attributes, V, Children, Listeners, TextSegments, Text>( nodes: &Nodes, - ctx: (&mut DiffState<'b>, &VTemplateRef<'b>, &Template), + ctx: ( + &mut DiffState<'b>, + &'b VTemplateRef<'b>, + &Template, + ElementId, + ), ) where Nodes: AsRef<[TemplateNode]>, Attributes: AsRef<[TemplateAttribute]>, @@ -306,14 +444,50 @@ impl<'a> VTemplateRef<'a> { TextSegments: AsRef<[TextTemplateSegment]>, Text: AsRef, { - let (diff_state, template_ref, template) = ctx; - for id in template.all_dynamic() { - let dynamic_node = &nodes.as_ref()[id.0]; - dynamic_node.hydrate(diff_state, template_ref); + let (diff_state, template_ref, template, parent) = ctx; + + match template { + Template::Static(s) => { + if let Some(seg) = &s.dynamic_path { + diff_state.mutations.set_last_node( + template_ref.get_node_id(template.root_nodes()[0]).as_u64(), + ); + traverse_seg(seg, nodes, diff_state, template_ref, parent); + } + } + Template::Owned(o) => { + if let Some(seg) = &o.dynamic_path { + diff_state.mutations.set_last_node( + template_ref.get_node_id(template.root_nodes()[0]).as_u64(), + ); + traverse_seg(seg, nodes, diff_state, template_ref, parent); + } + } } } - template.with_nodes(hydrate_inner, hydrate_inner, (diff_state, self, template)); + template.with_nodes( + hydrate_inner, + hydrate_inner, + (diff_state, self, template, parent), + ); + } + + pub(crate) fn get_node_id(&self, id: TemplateNodeId) -> ElementId { + self.try_get_node_id(id).unwrap() + } + + pub(crate) fn try_get_node_id(&self, id: TemplateNodeId) -> Option { + let node_ids = self.node_ids.borrow(); + node_ids.get(id.0).and_then(|cell| cell.get().copied()) + } + + pub(crate) fn set_node_id(&self, id: TemplateNodeId, real_id: ElementId) { + let mut ids = self.node_ids.borrow_mut(); + if ids.len() <= id.0 { + ids.resize(id.0 + 1, OnceCell::new()); + } + ids[id.0].set(real_id).unwrap(); } } @@ -326,6 +500,8 @@ pub struct StaticTemplate { pub root_nodes: StaticRootNodes, /// Any nodes that contain dynamic components. This is stored in the tmeplate to avoid traversing the tree every time a template is refrenced. pub dynamic_mapping: StaticDynamicNodeMapping, + /// The path to take to update the template with dynamic content (starts from the first root node) + pub dynamic_path: Option, } /// A template that is created at runtime @@ -341,7 +517,9 @@ pub struct OwnedTemplate { /// The ids of the root nodes in the template pub root_nodes: OwnedRootNodes, /// Any nodes that contain dynamic components. This is stored in the tmeplate to avoid traversing the tree every time a template is refrenced. - pub dynamic_mapping: crate::OwnedDynamicNodeMapping, + pub dynamic_mapping: OwnedDynamicNodeMapping, + /// The path to take to update the template with dynamic content + pub dynamic_path: Option, } /// A template used to skip diffing on some static parts of the rsx @@ -355,19 +533,18 @@ pub enum Template { } impl Template { - pub(crate) fn create<'b>( - &self, - mutations: &mut Mutations<'b>, - bump: &'b Bump, - id: RendererTemplateId, - ) { - mutations.create_templete(id); + pub(crate) fn create<'b>(&self, mutations: &mut Mutations<'b>, bump: &'b Bump, id: ElementId) { + let children = match self { + Template::Static(s) => self.count_real_nodes(s.root_nodes), + #[cfg(any(feature = "hot-reload", debug_assertions))] + Template::Owned(o) => self.count_real_nodes(&o.root_nodes), + }; + mutations.create_element("template", None, Some(id.into()), children as u32); let empty = match self { Template::Static(s) => s.nodes.is_empty(), #[cfg(any(feature = "hot-reload", debug_assertions))] Template::Owned(o) => o.nodes.is_empty(), }; - let mut len = 0; if !empty { let roots = match self { Template::Static(s) => s.root_nodes, @@ -375,11 +552,9 @@ impl Template { Template::Owned(o) => &o.root_nodes, }; for root in roots { - len += 1; self.create_node(mutations, bump, *root); } } - mutations.finish_templete(len as u32); } fn create_node<'b>(&self, mutations: &mut Mutations<'b>, bump: &'b Bump, id: TemplateNodeId) { @@ -395,9 +570,6 @@ impl Template { Text: AsRef, { let (mutations, bump, template) = ctx; - let id = node.id; - let locally_static = node.locally_static; - let fully_static = node.fully_static; match &node.node_type { TemplateNodeType::Element(el) => { let TemplateElement { @@ -407,12 +579,11 @@ impl Template { children, .. } = el; - mutations.create_element_template( + mutations.create_element( tag, *namespace, - id, - locally_static, - fully_static, + None, + template.count_real_nodes(children.as_ref()) as u32, ); for attr in attributes.as_ref() { if let TemplateAttributeValue::Static(val) = &attr.value { @@ -422,34 +593,24 @@ impl Template { is_static: true, value: val, }; - mutations.set_attribute(bump.alloc(attribute), id); + mutations.set_attribute(bump.alloc(attribute), None); } } - let mut children_created = 0; for child in children.as_ref() { template.create_node(mutations, bump, *child); - children_created += 1; } - - mutations.append_children(children_created); } TemplateNodeType::Text(text) => { let mut text_iter = text.segments.as_ref().iter(); if let (Some(TextTemplateSegment::Static(txt)), None) = (text_iter.next(), text_iter.next()) { - mutations.create_text_node_template( - bump.alloc_str(txt.as_ref()), - id, - locally_static, - ); + mutations.create_text_node(bump.alloc_str(txt.as_ref()), None); } else { - mutations.create_text_node_template("", id, locally_static); + mutations.create_text_node("", None); } } - TemplateNodeType::DynamicNode(_) => { - mutations.create_placeholder_template(id); - } + TemplateNodeType::DynamicNode(_) => {} } } self.with_node( @@ -496,10 +657,10 @@ impl Template { } #[cfg(any(feature = "hot-reload", debug_assertions))] - pub(crate) fn with_nodes<'a, F1, F2, Ctx>(&'a self, mut f1: F1, mut f2: F2, ctx: Ctx) + pub(crate) fn with_nodes<'a, F1, F2, Ctx, R>(&'a self, mut f1: F1, mut f2: F2, ctx: Ctx) -> R where - F1: FnMut(&'a &'static [StaticTemplateNode], Ctx), - F2: FnMut(&'a Vec, Ctx), + F1: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R, + F2: FnMut(&'a Vec, Ctx) -> R, { match self { Template::Static(s) => f1(&s.nodes, ctx), @@ -508,22 +669,39 @@ impl Template { } #[cfg(not(any(feature = "hot-reload", debug_assertions)))] - pub(crate) fn with_nodes<'a, F1, F2, Ctx>(&'a self, mut f1: F1, _f2: F2, ctx: Ctx) + pub(crate) fn with_nodes<'a, F1, F2, Ctx, R>(&'a self, mut f1: F1, _f2: F2, ctx: Ctx) -> R where - F1: FnMut(&'a &'static [StaticTemplateNode], Ctx), - F2: FnMut(&'a &'static [StaticTemplateNode], Ctx), + F1: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R, + F2: FnMut(&'a &'static [StaticTemplateNode], Ctx) -> R, { match self { Template::Static(s) => f1(&s.nodes, ctx), } } - pub(crate) fn all_dynamic<'a>(&'a self) -> Box + 'a> { - match self { - Template::Static(s) => Box::new(s.dynamic_mapping.all_dynamic()), - #[cfg(any(feature = "hot-reload", debug_assertions))] - Template::Owned(o) => Box::new(o.dynamic_mapping.all_dynamic()), + fn count_real_nodes(&self, ids: &[TemplateNodeId]) -> usize { + fn count_real_nodes_inner( + nodes: &Nodes, + id: TemplateNodeId, + ) -> usize + where + Nodes: AsRef<[TemplateNode]>, + Attributes: AsRef<[TemplateAttribute]>, + V: TemplateValue, + Children: AsRef<[TemplateNodeId]>, + Listeners: AsRef<[usize]>, + TextSegments: AsRef<[TextTemplateSegment]>, + Text: AsRef, + { + match &nodes.as_ref()[id.0].node_type { + TemplateNodeType::DynamicNode(_) => 0, + TemplateNodeType::Element(_) => 1, + TemplateNodeType::Text(_) => 1, + } } + ids.iter() + .map(|id| self.with_nodes(count_real_nodes_inner, count_real_nodes_inner, *id)) + .sum() } pub(crate) fn volatile_attributes<'a>( @@ -542,14 +720,6 @@ impl Template { } } - pub(crate) fn get_dynamic_nodes_for_node_index(&self, idx: usize) -> Option { - match self { - Template::Static(s) => s.dynamic_mapping.nodes[idx], - #[cfg(any(feature = "hot-reload", debug_assertions))] - Template::Owned(o) => o.dynamic_mapping.nodes[idx], - } - } - pub(crate) fn get_dynamic_nodes_for_text_index(&self, idx: usize) -> &[TemplateNodeId] { match self { Template::Static(s) => s.dynamic_mapping.text[idx], @@ -568,6 +738,14 @@ impl Template { Template::Owned(o) => o.dynamic_mapping.attributes[idx].as_ref(), } } + + pub(crate) fn root_nodes(&self) -> &[TemplateNodeId] { + match self { + Template::Static(s) => s.root_nodes, + #[cfg(any(feature = "hot-reload", debug_assertions))] + Template::Owned(o) => &o.root_nodes, + } + } } /// A array of stack allocated Template nodes @@ -607,7 +785,7 @@ pub type OwnedRootNodes = Vec; /// Templates can only contain a limited subset of VNodes and keys are not needed, as diffing will be skipped. /// Dynamic parts of the Template are inserted into the VNode using the `TemplateContext` by traversing the tree in order and filling in dynamic parts /// This template node is generic over the storage of the nodes to allow for owned and &'static versions. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr( all(feature = "serialize", any(feature = "hot-reload", debug_assertions)), derive(serde::Serialize, serde::Deserialize) @@ -623,12 +801,13 @@ where { /// The ID of the [`TemplateNode`]. Note that this is not an elenemt id, and should be allocated seperately from VNodes on the frontend. pub id: TemplateNodeId, - /// If the id of the node must be kept in the refrences - pub locally_static: bool, - /// If any children of this node must be kept in the references - pub fully_static: bool, + /// The depth of the node in the template node tree + /// Root nodes have a depth of 0 + pub depth: usize, /// The type of the [`TemplateNode`]. pub node_type: TemplateNodeType, + /// The parent of this node. + pub parent: Option, } impl @@ -641,14 +820,12 @@ where TextSegments: AsRef<[TextTemplateSegment]>, Text: AsRef, { - fn hydrate<'b>(&self, diff_state: &mut DiffState<'b>, template_ref: &VTemplateRef<'b>) { - let real_id = template_ref.id.get().unwrap(); - - diff_state.element_stack.push(GlobalNodeId::TemplateId { - template_ref_id: real_id, - template_node_id: self.id, - }); - diff_state.mutations.enter_template_ref(real_id); + fn hydrate<'b>( + &self, + real_node_id: ElementId, + diff_state: &mut DiffState<'b>, + template_ref: &'b VTemplateRef<'b>, + ) { match &self.node_type { TemplateNodeType::Element(el) => { let TemplateElement { @@ -667,41 +844,34 @@ where is_static: false, }; let scope_bump = diff_state.current_scope_bump(); - diff_state - .mutations - .set_attribute(scope_bump.alloc(attribute), self.id); + diff_state.mutations.set_attribute( + scope_bump.alloc(attribute), + Some(real_node_id.as_u64()), + ); } } for listener_idx in listeners.as_ref() { let listener = template_ref.dynamic_context.resolve_listener(*listener_idx); - let global_id = GlobalNodeId::TemplateId { - template_ref_id: real_id, - template_node_id: self.id, - }; - listener.mounted_node.set(Some(global_id)); + listener.mounted_node.set(Some(real_node_id)); diff_state .mutations .new_event_listener(listener, diff_state.current_scope()); } } TemplateNodeType::Text(text) => { - let new_text = template_ref - .dynamic_context - .resolve_text(&text.segments.as_ref()); let scope_bump = diff_state.current_scope_bump(); + let mut bump_str = + bumpalo::collections::String::with_capacity_in(text.min_size, scope_bump); + template_ref + .dynamic_context + .resolve_text_into(text, &mut bump_str); + diff_state .mutations - .set_text(scope_bump.alloc(new_text), self.id) - } - TemplateNodeType::DynamicNode(idx) => { - // this will only be triggered for root elements - let created = - diff_state.create_node(template_ref.dynamic_context.resolve_node(*idx)); - diff_state.mutations.replace_with(self.id, created as u32); + .set_text(bump_str.into_bump_str(), Some(real_node_id.as_u64())); } + TemplateNodeType::DynamicNode(_) => {} } - diff_state.mutations.exit_template_ref(); - diff_state.element_stack.pop(); } } @@ -824,48 +994,6 @@ where DynamicNode(usize), } -impl - TemplateNodeType -where - Attributes: AsRef<[TemplateAttribute]>, - Children: AsRef<[TemplateNodeId]>, - Listeners: AsRef<[usize]>, - V: TemplateValue, - TextSegments: AsRef<[TextTemplateSegment]>, - Text: AsRef, -{ - /// Returns if this node, and its children, are static. - pub fn fully_static>(&self, nodes: &Nodes) -> bool { - self.locally_static() - && match self { - TemplateNodeType::Element(e) => e - .children - .as_ref() - .iter() - .all(|c| nodes[c.0].fully_static(nodes)), - TemplateNodeType::Text(_) => true, - TemplateNodeType::DynamicNode(_) => unreachable!(), - } - } - - /// Returns if this node is static. - pub fn locally_static(&self) -> bool { - match self { - TemplateNodeType::Element(e) => { - e.attributes.as_ref().iter().all(|a| match a.value { - TemplateAttributeValue::Static(_) => true, - TemplateAttributeValue::Dynamic(_) => false, - }) && e.listeners.as_ref().is_empty() - } - TemplateNodeType::Text(t) => t.segments.as_ref().iter().all(|seg| match seg { - TextTemplateSegment::Static(_) => true, - TextTemplateSegment::Dynamic(_) => false, - }), - TemplateNodeType::DynamicNode(_) => false, - } - } -} - type StaticStr = &'static str; /// A element template @@ -899,8 +1027,6 @@ where pub children: Children, /// The ids of the listeners of the element pub listeners: Listeners, - /// The parent of the element - pub parent: Option, value: PhantomData, } @@ -918,7 +1044,6 @@ where attributes: Attributes, children: Children, listeners: Listeners, - parent: Option, ) -> Self { TemplateElement { tag, @@ -926,7 +1051,6 @@ where attributes, children, listeners, - parent, value: PhantomData, } } @@ -945,6 +1069,8 @@ where { /// The segments of the template. pub segments: Segments, + /// The minimum size of the output text. + pub min_size: usize, text: PhantomData, } @@ -954,9 +1080,10 @@ where Text: AsRef, { /// create a new template from the segments it is composed of. - pub const fn new(segments: Segments) -> Self { + pub const fn new(segments: Segments, min_size: usize) -> Self { TextTemplate { segments, + min_size, text: PhantomData, } } @@ -1005,8 +1132,7 @@ pub enum StaticAttributeValue { #[derive(Default)] pub(crate) struct TemplateResolver { // maps a id to the rendererid and if that template needs to be re-created - pub template_id_mapping: FxHashMap, - pub template_count: usize, + pub template_id_mapping: FxHashMap, } impl TemplateResolver { @@ -1028,15 +1154,14 @@ impl TemplateResolver { pub fn get_or_create_client_id( &mut self, template_id: &TemplateId, - ) -> (RendererTemplateId, bool) { + scopes: &ScopeArena, + ) -> (ElementId, bool) { if let Some(id) = self.template_id_mapping.get(template_id) { *id } else { - let id = self.template_count; - let renderer_id = RendererTemplateId(id); + let renderer_id = scopes.reserve_phantom_node(); self.template_id_mapping .insert(template_id.clone(), (renderer_id, false)); - self.template_count += 1; (renderer_id, true) } } @@ -1047,3 +1172,116 @@ impl TemplateResolver { #[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct SetTemplateMsg(pub TemplateId, pub OwnedTemplate); + +#[cfg(any(feature = "hot-reload", debug_assertions))] +/// A path segment that lives on the heap. +pub type OwnedPathSeg = PathSeg>; + +#[cfg(any(feature = "hot-reload", debug_assertions))] +#[cfg_attr( + all(feature = "serialize", any(feature = "hot-reload", debug_assertions)), + derive(serde::Serialize, serde::Deserialize) +)] +#[derive(Debug, Clone, PartialEq)] +/// A traverse message that lives on the heap. +pub enum OwnedTraverse { + /// Halt traversal + Halt, + /// Traverse to the first child of the current node. + FirstChild(Box), + /// Traverse to the next sibling of the current node. + NextSibling(Box), + /// Traverse to the both the first child and next sibling of the current node. + Both(Box<(OwnedPathSeg, OwnedPathSeg)>), +} + +#[cfg(any(feature = "hot-reload", debug_assertions))] +impl Traversable> for OwnedTraverse { + fn first_child(&self) -> Option<&OwnedPathSeg> { + match self { + OwnedTraverse::FirstChild(p) => Some(p), + OwnedTraverse::Both(ps) => Some(&ps.0), + _ => None, + } + } + + fn next_sibling(&self) -> Option<&OwnedPathSeg> { + match self { + OwnedTraverse::NextSibling(p) => Some(p), + OwnedTraverse::Both(ps) => Some(&ps.1), + _ => None, + } + } +} + +/// A path segment that lives on the stack. +pub type StaticPathSeg = PathSeg; + +#[derive(Debug, Clone, PartialEq)] +/// A traverse message that lives on the stack. +pub enum StaticTraverse { + /// Halt traversal + Halt, + /// Traverse to the first child of the current node. + FirstChild(&'static StaticPathSeg), + /// Traverse to the next sibling of the current node. + NextSibling(&'static StaticPathSeg), + /// Traverse to the both the first child and next sibling of the current node. + Both(&'static (StaticPathSeg, StaticPathSeg)), +} + +impl Traversable<&'static [UpdateOp]> for StaticTraverse { + fn first_child(&self) -> Option<&StaticPathSeg> { + match self { + StaticTraverse::FirstChild(p) => Some(p), + StaticTraverse::Both((p, _)) => Some(p), + _ => None, + } + } + + fn next_sibling(&self) -> Option<&StaticPathSeg> { + match self { + StaticTraverse::NextSibling(p) => Some(p), + StaticTraverse::Both((_, p)) => Some(p), + _ => None, + } + } +} + +pub trait Traversable> +where + Self: Sized, +{ + fn first_child(&self) -> Option<&PathSeg>; + fn next_sibling(&self) -> Option<&PathSeg>; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + all(feature = "serialize", any(feature = "hot-reload", debug_assertions)), + derive(serde::Serialize, serde::Deserialize) +)] +/// A path segment that defines a way to traverse a template node and resolve dynamic sections. +pub struct PathSeg, O: AsRef<[UpdateOp]>> { + /// The operation to perform on the current node. + pub ops: O, + /// The next traversal step. + pub traverse: T, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr( + all(feature = "serialize", any(feature = "hot-reload", debug_assertions)), + derive(serde::Serialize, serde::Deserialize) +)] +/// A operation that can be applied to a template node when intially updating it. +pub enum UpdateOp { + /// Store a dynamic node on the renderer + StoreNode(TemplateNodeId), + /// Insert a dynamic node before the current node + InsertBefore(TemplateNodeId), + /// Insert a dynamic node after the current node + InsertAfter(TemplateNodeId), + /// Append a dynamic node to the current node + AppendChild(TemplateNodeId), +} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 85cd44cf4..417bce426 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -103,6 +103,7 @@ use std::{collections::VecDeque, iter::FromIterator, task::Poll}; /// } /// ``` pub struct VirtualDom { + root: ElementId, scopes: ScopeArena, pending_messages: VecDeque, @@ -230,10 +231,11 @@ impl VirtualDom { render_fn: root, }), None, - GlobalNodeId::VNodeId(ElementId(0)), + ElementId(0), ); Self { + root: ElementId(0), scopes, channel, dirty_scopes: IndexSet::from_iter([ScopeId(0)]), @@ -495,7 +497,7 @@ impl VirtualDom { self.scopes.run_scope(scopeid); - diff_state.diff_scope(scopeid); + diff_state.diff_scope(self.root, scopeid); let DiffState { mutations, .. } = diff_state; @@ -551,15 +553,15 @@ impl VirtualDom { let mut diff_state = DiffState::new(&self.scopes); self.scopes.run_scope(scope_id); - diff_state - .element_stack - .push(GlobalNodeId::VNodeId(ElementId(0))); diff_state.scope_stack.push(scope_id); let node = self.scopes.fin_head(scope_id); - let created = diff_state.create_node(node); + let mut created = Vec::new(); + diff_state.create_node(self.root, node, &mut created); - diff_state.mutations.append_children(created as u32); + diff_state + .mutations + .append_children(Some(self.root.as_u64()), created); self.dirty_scopes.clear(); assert!(self.dirty_scopes.is_empty()); @@ -609,9 +611,8 @@ impl VirtualDom { diff_machine.force_diff = true; diff_machine.scope_stack.push(scope_id); let scope = diff_machine.scopes.get_scope(scope_id).unwrap(); - diff_machine.element_stack.push(scope.container); - diff_machine.diff_node(old, new); + diff_machine.diff_node(scope.container, old, new); diff_machine.mutations } @@ -650,11 +651,8 @@ impl VirtualDom { /// ``` pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { let mut machine = DiffState::new(&self.scopes); - machine - .element_stack - .push(GlobalNodeId::VNodeId(ElementId(0))); machine.scope_stack.push(ScopeId(0)); - machine.diff_node(old, new); + machine.diff_node(self.root, old, new); machine.mutations } @@ -675,12 +673,12 @@ impl VirtualDom { pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> { let mut machine = DiffState::new(&self.scopes); machine.scope_stack.push(ScopeId(0)); - machine - .element_stack - .push(GlobalNodeId::VNodeId(ElementId(0))); let node = self.render_vnodes(nodes); - let created = machine.create_node(node); - machine.mutations.append_children(created as u32); + let mut created = Vec::new(); + machine.create_node(self.root, node, &mut created); + machine + .mutations + .append_children(Some(self.root.as_u64()), created); machine.mutations } @@ -706,16 +704,15 @@ impl VirtualDom { let mut create = DiffState::new(&self.scopes); create.scope_stack.push(ScopeId(0)); + let mut created = Vec::new(); + create.create_node(self.root, old, &mut created); create - .element_stack - .push(GlobalNodeId::VNodeId(ElementId(0))); - let created = create.create_node(old); - create.mutations.append_children(created as u32); + .mutations + .append_children(Some(self.root.as_u64()), created); let mut edit = DiffState::new(&self.scopes); edit.scope_stack.push(ScopeId(0)); - edit.element_stack.push(GlobalNodeId::VNodeId(ElementId(0))); - edit.diff_node(old, new); + edit.diff_node(self.root, old, new); (create.mutations, edit.mutations) } diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index 2fab85a21..cf2c49727 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -3,7 +3,8 @@ use std::any::Any; use std::sync::Arc; -use dioxus_core::{EventPriority, GlobalNodeId, UserEvent}; +use dioxus_core::ElementId; +use dioxus_core::{EventPriority, UserEvent}; use dioxus_html::event_bubbles; use dioxus_html::on::*; use serde::{Deserialize, Serialize}; @@ -37,7 +38,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option { #[derive(Deserialize, Serialize)] struct ImEvent { event: String, - mounted_dom_id: GlobalNodeId, + mounted_dom_id: ElementId, contents: serde_json::Value, } diff --git a/packages/dioxus/tests/create_dom.rs b/packages/dioxus/tests/create_dom.rs index 2508d6418..a88bfce89 100644 --- a/packages/dioxus/tests/create_dom.rs +++ b/packages/dioxus/tests/create_dom.rs @@ -30,29 +30,15 @@ fn test_original_diff() { assert_eq!( mutations.edits, [ - CreateTemplate { id: 0 }, - CreateElementTemplate { - root: 4503599627370495, - tag: "div", - locally_static: true, - fully_static: true - }, - CreateElementTemplate { - root: 4503599627370496, - tag: "div", - locally_static: true, - fully_static: true - }, - CreateTextNodeTemplate { - root: 4503599627370497, - text: "Hello, world!", - locally_static: true - }, - AppendChildren { many: 1 }, - AppendChildren { many: 1 }, - FinishTemplate { len: 1 }, - CreateTemplateRef { id: 1, template_id: 0 }, - AppendChildren { many: 1 } + // create template + CreateElement { root: Some(1), tag: "template", children: 1 }, + CreateElement { root: None, tag: "div", children: 1 }, + CreateElement { root: None, tag: "div", children: 1 }, + CreateTextNode { root: None, text: "Hello, world!" }, + // clone template + CloneNodeChildren { id: Some(1), new_ids: vec![2] }, + // add to root + AppendChildren { root: Some(0), children: vec![2] }, ] ); } @@ -83,49 +69,27 @@ fn create() { assert_eq!( mutations.edits, [ - CreateTemplate { id: 0 }, - CreateElementTemplate { - root: 4503599627370495, - tag: "div", - locally_static: true, - fully_static: false - }, - CreateElementTemplate { - root: 4503599627370496, - tag: "div", - locally_static: true, - fully_static: false - }, - CreateTextNodeTemplate { - root: 4503599627370497, - text: "Hello, world!", - locally_static: true - }, - CreateElementTemplate { - root: 4503599627370498, - tag: "div", - locally_static: true, - fully_static: false - }, - CreateElementTemplate { - root: 4503599627370499, - tag: "div", - locally_static: true, - fully_static: false - }, - CreatePlaceholderTemplate { root: 4503599627370500 }, - AppendChildren { many: 1 }, - AppendChildren { many: 1 }, - AppendChildren { many: 2 }, - AppendChildren { many: 1 }, - FinishTemplate { len: 1 }, - CreateTemplateRef { id: 1, template_id: 0 }, - EnterTemplateRef { root: 1 }, - CreateTextNode { root: 2, text: "hello" }, - CreateTextNode { root: 3, text: "world" }, - ReplaceWith { root: 4503599627370500, m: 2 }, - ExitTemplateRef {}, - AppendChildren { many: 1 } + // create template + CreateElement { root: Some(1), tag: "template", children: 1 }, + CreateElement { root: None, tag: "div", children: 1 }, + CreateElement { root: None, tag: "div", children: 2 }, + CreateTextNode { root: None, text: "Hello, world!" }, + CreateElement { root: None, tag: "div", children: 1 }, + CreateElement { root: None, tag: "div", children: 1 }, + CreatePlaceholder { root: None }, + // clone template + CloneNodeChildren { id: Some(1), new_ids: vec![2] }, + CreateTextNode { root: Some(3), text: "hello" }, + CreateTextNode { root: Some(4), text: "world" }, + // update template + SetLastNode { id: 2 }, + FirstChild {}, + FirstChild {}, + NextSibling {}, + FirstChild {}, + FirstChild {}, + ReplaceWith { root: None, nodes: vec![3, 4] }, + AppendChildren { root: Some(0), children: vec![2] } ] ); } @@ -143,24 +107,19 @@ fn create_list() { let mut dom = new_dom(APP, ()); let mutations = dom.rebuild(); - // copilot wrote this test :P assert_eq!( mutations.edits, [ - CreateTemplate { id: 0 }, - CreateElementTemplate { - root: 4503599627370495, - tag: "div", - locally_static: true, - fully_static: true - }, - CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true }, - AppendChildren { many: 1 }, - FinishTemplate { len: 1 }, - CreateTemplateRef { id: 1, template_id: 0 }, - CreateTemplateRef { id: 2, template_id: 0 }, - CreateTemplateRef { id: 3, template_id: 0 }, - AppendChildren { many: 3 } + // create template + CreateElement { root: Some(1), tag: "template", children: 1 }, + CreateElement { root: None, tag: "div", children: 1 }, + CreateTextNode { root: None, text: "hello" }, + // clone template + CloneNodeChildren { id: Some(1), new_ids: vec![2] }, + CloneNodeChildren { id: Some(1), new_ids: vec![3] }, + CloneNodeChildren { id: Some(1), new_ids: vec![4] }, + // add to root + AppendChildren { root: Some(0), children: vec![2, 3, 4] }, ] ); } @@ -179,42 +138,19 @@ fn create_simple() { let mut dom = new_dom(APP, ()); let mutations = dom.rebuild(); - // copilot wrote this test :P assert_eq!( mutations.edits, [ - CreateTemplate { id: 0 }, - CreateElementTemplate { - root: 4503599627370495, - tag: "div", - locally_static: true, - fully_static: true - }, - AppendChildren { many: 0 }, - CreateElementTemplate { - root: 4503599627370496, - tag: "div", - locally_static: true, - fully_static: true - }, - AppendChildren { many: 0 }, - CreateElementTemplate { - root: 4503599627370497, - tag: "div", - locally_static: true, - fully_static: true - }, - AppendChildren { many: 0 }, - CreateElementTemplate { - root: 4503599627370498, - tag: "div", - locally_static: true, - fully_static: true - }, - AppendChildren { many: 0 }, - FinishTemplate { len: 4 }, - CreateTemplateRef { id: 1, template_id: 0 }, - AppendChildren { many: 1 } + // create template + CreateElement { root: Some(1), tag: "template", children: 4 }, + CreateElement { root: None, tag: "div", children: 0 }, + CreateElement { root: None, tag: "div", children: 0 }, + CreateElement { root: None, tag: "div", children: 0 }, + CreateElement { root: None, tag: "div", children: 0 }, + // clone template + CloneNodeChildren { id: Some(1), new_ids: vec![2, 3, 4, 5] }, + // add to root + AppendChildren { root: Some(0), children: vec![2, 3, 4, 5] }, ] ); } @@ -247,46 +183,35 @@ fn create_components() { assert_eq!( mutations.edits, [ - CreateTemplate { id: 0 }, - CreateElementTemplate { - root: 4503599627370495, - tag: "h1", - locally_static: true, - fully_static: true - }, - AppendChildren { many: 0 }, - CreateElementTemplate { - root: 4503599627370496, - tag: "div", - locally_static: true, - fully_static: false - }, - CreatePlaceholderTemplate { root: 4503599627370497 }, - AppendChildren { many: 1 }, - CreateElementTemplate { - root: 4503599627370498, - tag: "p", - locally_static: true, - fully_static: true - }, - AppendChildren { many: 0 }, - FinishTemplate { len: 3 }, - CreateTemplateRef { id: 1, template_id: 0 }, - EnterTemplateRef { root: 1 }, - CreateTextNode { root: 2, text: "abc1" }, - ReplaceWith { root: 4503599627370497, m: 1 }, - ExitTemplateRef {}, - CreateTemplateRef { id: 3, template_id: 0 }, - EnterTemplateRef { root: 3 }, - CreateTextNode { root: 4, text: "abc2" }, - ReplaceWith { root: 4503599627370497, m: 1 }, - ExitTemplateRef {}, - CreateTemplateRef { id: 5, template_id: 0 }, - EnterTemplateRef { root: 5 }, - CreateTextNode { root: 6, text: "abc3" }, - ReplaceWith { root: 4503599627370497, m: 1 }, - ExitTemplateRef {}, - AppendChildren { many: 3 } + // create template + CreateElement { root: Some(1), tag: "template", children: 3 }, + CreateElement { root: None, tag: "h1", children: 0 }, + CreateElement { root: None, tag: "div", children: 1 }, + CreatePlaceholder { root: None }, + CreateElement { root: None, tag: "p", children: 0 }, + // clone template + CloneNodeChildren { id: Some(1), new_ids: vec![2, 3, 4] }, + // update template + CreateTextNode { root: Some(5), text: "abc1" }, + SetLastNode { id: 3 }, + FirstChild {}, + ReplaceWith { root: None, nodes: vec![5] }, + // clone template + CloneNodeChildren { id: Some(1), new_ids: vec![6, 7, 8] }, + // update template + CreateTextNode { root: Some(9), text: "abc2" }, + SetLastNode { id: 7 }, + FirstChild {}, + ReplaceWith { root: None, nodes: vec![9] }, + // clone template + CloneNodeChildren { id: Some(1), new_ids: vec![10, 11, 12] }, + // update template + CreateTextNode { root: Some(13), text: "abc3" }, + SetLastNode { id: 11 }, + FirstChild {}, + ReplaceWith { root: None, nodes: vec![13] }, + // add to root + AppendChildren { root: Some(0), children: vec![2, 3, 4, 6, 7, 8, 10, 11, 12] } ] ); } @@ -302,22 +227,19 @@ fn anchors() { let mut dom = new_dom(App, ()); let mutations = dom.rebuild(); + assert_eq!( mutations.edits, [ - CreateTemplate { id: 0 }, - CreateElementTemplate { - root: 4503599627370495, - tag: "div", - locally_static: true, - fully_static: true - }, - CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true }, - AppendChildren { many: 1 }, - FinishTemplate { len: 1 }, - CreateTemplateRef { id: 1, template_id: 0 }, - CreatePlaceholder { root: 2 }, - AppendChildren { many: 2 } + // create template + CreateElement { root: Some(1), tag: "template", children: 1 }, + CreateElement { root: None, tag: "div", children: 1 }, + CreateTextNode { root: None, text: "hello" }, + // clone template + CloneNodeChildren { id: Some(1), new_ids: vec![2] }, + CreatePlaceholder { root: Some(3) }, + // add to root + AppendChildren { root: Some(0), children: vec![2, 3] }, ] ); } diff --git a/packages/dioxus/tests/diffing.rs b/packages/dioxus/tests/diffing.rs index d4298dd9d..0f807151f 100644 --- a/packages/dioxus/tests/diffing.rs +++ b/packages/dioxus/tests/diffing.rs @@ -25,14 +25,17 @@ fn html_and_rsx_generate_the_same_output() { assert_eq!( create.edits, [ - CreateElement { root: 1, tag: "div" }, - CreateTextNode { root: 2, text: "Hello world" }, - AppendChildren { many: 1 }, - AppendChildren { many: 1 }, + CreateElement { root: Some(1,), tag: "div", children: 0 }, + CreateTextNode { root: Some(2,), text: "Hello world" }, + AppendChildren { root: Some(1,), children: vec![2] }, + AppendChildren { root: Some(0,), children: vec![1] }, ] ); - assert_eq!(change.edits, [SetText { text: "Goodbye world", root: 2 },]); + assert_eq!( + change.edits, + [SetText { root: Some(2,), text: "Goodbye world" },] + ); } /// Should result in 3 elements on the stack @@ -49,16 +52,16 @@ fn fragments_create_properly() { assert_eq!( create.edits, [ - CreateElement { root: 1, tag: "div" }, - CreateTextNode { root: 2, text: "Hello a" }, - AppendChildren { many: 1 }, - CreateElement { root: 3, tag: "div" }, - CreateTextNode { root: 4, text: "Hello b" }, - AppendChildren { many: 1 }, - CreateElement { root: 5, tag: "div" }, - CreateTextNode { root: 6, text: "Hello c" }, - AppendChildren { many: 1 }, - AppendChildren { many: 3 }, + CreateElement { root: Some(1,), tag: "div", children: 0 }, + CreateTextNode { root: Some(2,), text: "Hello a" }, + AppendChildren { root: Some(1,), children: vec![2,] }, + CreateElement { root: Some(3,), tag: "div", children: 0 }, + CreateTextNode { root: Some(4,), text: "Hello b" }, + AppendChildren { root: Some(3,), children: vec![4,] }, + CreateElement { root: Some(5,), tag: "div", children: 0 }, + CreateTextNode { root: Some(6,), text: "Hello c" }, + AppendChildren { root: Some(5,), children: vec![6,] }, + AppendChildren { root: Some(0,), children: vec![1, 3, 5,] }, ] ); } @@ -75,13 +78,16 @@ fn empty_fragments_create_anchors() { assert_eq!( create.edits, - [CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }] + [ + CreatePlaceholder { root: Some(1,) }, + AppendChildren { root: Some(0,), children: vec![1,] }, + ] ); assert_eq!( change.edits, [ - CreateElement { root: 2, tag: "div" }, - ReplaceWith { m: 1, root: 1 } + CreateElement { root: Some(2,), tag: "div", children: 0 }, + ReplaceWith { root: Some(1,), nodes: vec![2,] }, ] ); } @@ -95,20 +101,24 @@ fn empty_fragments_create_many_anchors() { let right = rsx_without_templates!({ (0..5).map(|_f| rsx_without_templates! { div {}}) }); let (create, change) = dom.diff_lazynodes(left, right); + assert_eq!( create.edits, - [CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }] + [ + CreatePlaceholder { root: Some(1,) }, + AppendChildren { root: Some(0,), children: vec![1,] }, + ] ); assert_eq!( change.edits, [ - CreateElement { root: 2, tag: "div" }, - CreateElement { root: 3, tag: "div" }, - CreateElement { root: 4, tag: "div" }, - CreateElement { root: 5, tag: "div" }, - CreateElement { root: 6, tag: "div" }, - ReplaceWith { m: 5, root: 1 } + CreateElement { root: Some(2,), tag: "div", children: 0 }, + CreateElement { root: Some(3,), tag: "div", children: 0 }, + CreateElement { root: Some(4,), tag: "div", children: 0 }, + CreateElement { root: Some(5,), tag: "div", children: 0 }, + CreateElement { root: Some(6,), tag: "div", children: 0 }, + ReplaceWith { root: Some(1,), nodes: vec![2, 3, 4, 5, 6,] }, ] ); } @@ -127,24 +137,28 @@ fn empty_fragments_create_anchors_with_many_children() { }); let (create, change) = dom.diff_lazynodes(left, right); + assert_eq!( create.edits, - [CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }] + [ + CreatePlaceholder { root: Some(1,) }, + AppendChildren { root: Some(0,), children: vec![1,] }, + ] ); assert_eq!( change.edits, [ - CreateElement { tag: "div", root: 2 }, - CreateTextNode { text: "hello: 0", root: 3 }, - AppendChildren { many: 1 }, - CreateElement { tag: "div", root: 4 }, - CreateTextNode { text: "hello: 1", root: 5 }, - AppendChildren { many: 1 }, - CreateElement { tag: "div", root: 6 }, - CreateTextNode { text: "hello: 2", root: 7 }, - AppendChildren { many: 1 }, - ReplaceWith { root: 1, m: 3 } + CreateElement { root: Some(2,), tag: "div", children: 0 }, + CreateTextNode { root: Some(3,), text: "hello: 0" }, + AppendChildren { root: Some(2,), children: vec![3,] }, + CreateElement { root: Some(4,), tag: "div", children: 0 }, + CreateTextNode { root: Some(5,), text: "hello: 1" }, + AppendChildren { root: Some(4,), children: vec![5,] }, + CreateElement { root: Some(6,), tag: "div", children: 0 }, + CreateTextNode { root: Some(7,), text: "hello: 2" }, + AppendChildren { root: Some(6,), children: vec![7,] }, + ReplaceWith { root: Some(1,), nodes: vec![2, 4, 6,] }, ] ); } @@ -162,25 +176,26 @@ fn many_items_become_fragment() { let right = rsx_without_templates!({ (0..0).map(|_| rsx_without_templates! { div {} }) }); let (create, change) = dom.diff_lazynodes(left, right); + assert_eq!( create.edits, [ - CreateElement { root: 1, tag: "div" }, - CreateTextNode { text: "hello", root: 2 }, - AppendChildren { many: 1 }, - CreateElement { root: 3, tag: "div" }, - CreateTextNode { text: "hello", root: 4 }, - AppendChildren { many: 1 }, - AppendChildren { many: 2 }, + CreateElement { root: Some(1,), tag: "div", children: 0 }, + CreateTextNode { root: Some(2,), text: "hello" }, + AppendChildren { root: Some(1,), children: vec![2,] }, + CreateElement { root: Some(3,), tag: "div", children: 0 }, + CreateTextNode { root: Some(4,), text: "hello" }, + AppendChildren { root: Some(3,), children: vec![4,] }, + AppendChildren { root: Some(0,), children: vec![1, 3,] }, ] ); assert_eq!( change.edits, [ - CreatePlaceholder { root: 5 }, - ReplaceWith { root: 1, m: 1 }, - Remove { root: 3 }, + CreatePlaceholder { root: Some(5,) }, + ReplaceWith { root: Some(1,), nodes: vec![5,] }, + Remove { root: Some(3,) }, ] ); } @@ -220,20 +235,17 @@ fn two_fragments_with_differrent_elements_are_differet() { ); let (_create, changes) = dom.diff_lazynodes(left, right); - println!("{:#?}", &changes); assert_eq!( changes.edits, [ - // create the new h1s - CreateElement { tag: "h1", root: 4 }, - CreateElement { tag: "h1", root: 5 }, - CreateElement { tag: "h1", root: 6 }, - InsertAfter { root: 2, n: 3 }, - // replace the divs with new h1s - CreateElement { tag: "h1", root: 7 }, - ReplaceWith { root: 1, m: 1 }, - CreateElement { tag: "h1", root: 1 }, // notice how 1 gets re-used - ReplaceWith { root: 2, m: 1 }, + CreateElement { root: Some(4,), tag: "h1", children: 0 }, + CreateElement { root: Some(5,), tag: "h1", children: 0 }, + CreateElement { root: Some(6,), tag: "h1", children: 0 }, + InsertAfter { root: Some(2,), nodes: vec![4, 5, 6,] }, + CreateElement { root: Some(7,), tag: "h1", children: 0 }, + ReplaceWith { root: Some(1,), nodes: vec![7,] }, // notice how 1 gets re-used + CreateElement { root: Some(1,), tag: "h1", children: 0 }, + ReplaceWith { root: Some(2,), nodes: vec![1,] }, ] ); } @@ -253,16 +265,17 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() { ); let (create, change) = dom.diff_lazynodes(left, right); + assert_eq!( create.edits, [ - CreateElement { root: 1, tag: "div" }, - CreateElement { root: 2, tag: "div" }, - CreateElement { root: 3, tag: "div" }, - CreateElement { root: 4, tag: "div" }, - CreateElement { root: 5, tag: "div" }, - CreateElement { root: 6, tag: "p" }, - AppendChildren { many: 6 }, + CreateElement { root: Some(1,), tag: "div", children: 0 }, + CreateElement { root: Some(2,), tag: "div", children: 0 }, + CreateElement { root: Some(3,), tag: "div", children: 0 }, + CreateElement { root: Some(4,), tag: "div", children: 0 }, + CreateElement { root: Some(5,), tag: "div", children: 0 }, + CreateElement { root: Some(6,), tag: "p", children: 0 }, + AppendChildren { root: Some(0,), children: vec![1, 2, 3, 4, 5, 6,] }, ] ); @@ -271,13 +284,13 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() { assert_eq!( change.edits, [ - Remove { root: 3 }, - Remove { root: 4 }, - Remove { root: 5 }, - CreateElement { root: 5, tag: "h1" }, // 3 gets reused - ReplaceWith { root: 1, m: 1 }, // 1 gets deleted - CreateElement { root: 1, tag: "h1" }, // 1 gets reused - ReplaceWith { root: 2, m: 1 }, + Remove { root: Some(3,) }, + Remove { root: Some(4,) }, + Remove { root: Some(5,) }, + CreateElement { root: Some(5,), tag: "h1", children: 0 }, // 5 gets reused + ReplaceWith { root: Some(1,), nodes: vec![5,] }, // 1 gets deleted + CreateElement { root: Some(1,), tag: "h1", children: 0 }, // 1 gets reused + ReplaceWith { root: Some(2,), nodes: vec![1,] }, ] ); } @@ -297,22 +310,23 @@ fn two_fragments_with_same_elements_are_differet() { ); let (create, change) = dom.diff_lazynodes(left, right); + assert_eq!( create.edits, [ - CreateElement { root: 1, tag: "div" }, - CreateElement { root: 2, tag: "div" }, - CreateElement { root: 3, tag: "p" }, - AppendChildren { many: 3 }, + CreateElement { root: Some(1,), tag: "div", children: 0 }, + CreateElement { root: Some(2,), tag: "div", children: 0 }, + CreateElement { root: Some(3,), tag: "p", children: 0 }, + AppendChildren { root: Some(0,), children: vec![1, 2, 3,] }, ] ); assert_eq!( change.edits, [ - CreateElement { root: 4, tag: "div" }, - CreateElement { root: 5, tag: "div" }, - CreateElement { root: 6, tag: "div" }, - InsertAfter { root: 2, n: 3 }, + CreateElement { root: Some(4,), tag: "div", children: 0 }, + CreateElement { root: Some(5,), tag: "div", children: 0 }, + CreateElement { root: Some(6,), tag: "div", children: 0 }, + InsertAfter { root: Some(2,), nodes: vec![4, 5, 6,] }, ] ); } @@ -332,9 +346,14 @@ fn keyed_diffing_order() { ); let (create, change) = dom.diff_lazynodes(left, right); + assert_eq!( change.edits, - [Remove { root: 3 }, Remove { root: 4 }, Remove { root: 5 },] + [ + Remove { root: Some(3,) }, + Remove { root: Some(4,) }, + Remove { root: Some(5,) }, + ] ); } @@ -356,10 +375,10 @@ fn keyed_diffing_out_of_order() { }); let (_, changes) = dom.diff_lazynodes(left, right); - println!("{:?}", &changes); + assert_eq!( changes.edits, - [PushRoot { root: 7 }, InsertBefore { root: 5, n: 1 }] + [InsertBefore { root: Some(5,), nodes: vec![7,] },] ); } @@ -381,16 +400,13 @@ fn keyed_diffing_out_of_order_adds() { }); let (_, change) = dom.diff_lazynodes(left, right); + assert_eq!( change.edits, - [ - PushRoot { root: 5 }, - PushRoot { root: 4 }, - InsertBefore { n: 2, root: 1 } - ] + [InsertBefore { root: Some(1,), nodes: vec![5, 4,] },] ); } -/// Should result in moves onl +/// Should result in moves only #[test] fn keyed_diffing_out_of_order_adds_2() { let dom = new_dom(); @@ -408,13 +424,10 @@ fn keyed_diffing_out_of_order_adds_2() { }); let (_, change) = dom.diff_lazynodes(left, right); + assert_eq!( change.edits, - [ - PushRoot { root: 4 }, - PushRoot { root: 5 }, - InsertBefore { n: 2, root: 1 } - ] + [InsertBefore { root: Some(1,), nodes: vec![4, 5,] },] ); } @@ -436,13 +449,10 @@ fn keyed_diffing_out_of_order_adds_3() { }); let (_, change) = dom.diff_lazynodes(left, right); + assert_eq!( change.edits, - [ - PushRoot { root: 5 }, - PushRoot { root: 4 }, - InsertBefore { n: 2, root: 2 } - ] + [InsertBefore { root: Some(2,), nodes: vec![5, 4,] },] ); } @@ -464,13 +474,10 @@ fn keyed_diffing_out_of_order_adds_4() { }); let (_, change) = dom.diff_lazynodes(left, right); + assert_eq!( change.edits, - [ - PushRoot { root: 5 }, - PushRoot { root: 4 }, - InsertBefore { n: 2, root: 3 } - ] + [InsertBefore { root: Some(3), nodes: vec![5, 4,] },] ); } @@ -494,7 +501,7 @@ fn keyed_diffing_out_of_order_adds_5() { let (_, change) = dom.diff_lazynodes(left, right); assert_eq!( change.edits, - [PushRoot { root: 5 }, InsertBefore { n: 1, root: 4 }] + [InsertBefore { root: Some(4), nodes: vec![5] }] ); } @@ -515,12 +522,13 @@ fn keyed_diffing_additions() { }); let (_, change) = dom.diff_lazynodes(left, right); + assert_eq!( change.edits, [ - CreateElement { root: 6, tag: "div" }, - CreateElement { root: 7, tag: "div" }, - InsertAfter { n: 2, root: 5 } + CreateElement { root: Some(6,), tag: "div", children: 0 }, + CreateElement { root: Some(7,), tag: "div", children: 0 }, + InsertAfter { root: Some(5,), nodes: vec![6, 7,] }, ] ); } @@ -547,12 +555,11 @@ fn keyed_diffing_additions_and_moves_on_ends() { change.edits, [ // create 11, 12 - CreateElement { tag: "div", root: 5 }, - CreateElement { tag: "div", root: 6 }, - InsertAfter { root: 3, n: 2 }, - // move 7 to the front - PushRoot { root: 4 }, - InsertBefore { root: 1, n: 1 } + CreateElement { root: Some(5), tag: "div", children: 0 }, + CreateElement { root: Some(6), tag: "div", children: 0 }, + InsertAfter { root: Some(3), nodes: vec![5, 6] }, + // // move 7 to the front + InsertBefore { root: Some(1), nodes: vec![4] } ] ); } @@ -580,16 +587,15 @@ fn keyed_diffing_additions_and_moves_in_middle() { change.edits, [ // create 5, 6 - CreateElement { tag: "div", root: 5 }, - CreateElement { tag: "div", root: 6 }, - InsertBefore { root: 3, n: 2 }, + CreateElement { root: Some(5,), tag: "div", children: 0 }, + CreateElement { root: Some(6,), tag: "div", children: 0 }, + InsertBefore { root: Some(3,), nodes: vec![5, 6,] }, // create 7, 8 - CreateElement { tag: "div", root: 7 }, - CreateElement { tag: "div", root: 8 }, - InsertBefore { root: 2, n: 2 }, + CreateElement { root: Some(7,), tag: "div", children: 0 }, + CreateElement { root: Some(8,), tag: "div", children: 0 }, + InsertBefore { root: Some(2,), nodes: vec![7, 8,] }, // move 7 - PushRoot { root: 4 }, - InsertBefore { root: 1, n: 1 } + InsertBefore { root: Some(1,), nodes: vec![4,] }, ] ); } @@ -616,18 +622,16 @@ fn controlled_keyed_diffing_out_of_order() { assert_eq!( changes.edits, [ - Remove { root: 4 }, - // move 4 to after 6 - PushRoot { root: 1 }, - InsertAfter { n: 1, root: 3 }, // remove 7 - + Remove { root: Some(4,) }, + // move 4 to after 6 + InsertAfter { root: Some(3,), nodes: vec![1,] }, // create 9 and insert before 6 - CreateElement { root: 4, tag: "div" }, - InsertBefore { n: 1, root: 3 }, + CreateElement { root: Some(4,), tag: "div", children: 0 }, + InsertBefore { root: Some(3,), nodes: vec![4,] }, // create 0 and insert before 5 - CreateElement { root: 5, tag: "div" }, - InsertBefore { n: 1, root: 2 }, + CreateElement { root: Some(5,), tag: "div", children: 0 }, + InsertBefore { root: Some(2,), nodes: vec![5,] }, ] ); } @@ -653,11 +657,10 @@ fn controlled_keyed_diffing_out_of_order_max_test() { assert_eq!( changes.edits, [ - Remove { root: 5 }, - CreateElement { root: 5, tag: "div" }, - InsertBefore { n: 1, root: 3 }, - PushRoot { root: 4 }, - InsertBefore { n: 1, root: 1 }, + Remove { root: Some(5,) }, + CreateElement { root: Some(5,), tag: "div", children: 0 }, + InsertBefore { root: Some(3,), nodes: vec![5,] }, + InsertBefore { root: Some(1,), nodes: vec![4,] }, ] ); } @@ -687,11 +690,11 @@ fn remove_list() { assert_eq!( changes.edits, + // remove 5, 4, 3 [ - // remove 5, 4, 3 - Remove { root: 3 }, - Remove { root: 4 }, - Remove { root: 5 }, + Remove { root: Some(3) }, + Remove { root: Some(4) }, + Remove { root: Some(5) } ] ); } @@ -720,9 +723,9 @@ fn remove_list_nokeyed() { changes.edits, [ // remove 5, 4, 3 - Remove { root: 3 }, - Remove { root: 4 }, - Remove { root: 5 }, + Remove { root: Some(3) }, + Remove { root: Some(4) }, + Remove { root: Some(5) }, ] ); } @@ -745,10 +748,8 @@ fn add_nested_elements() { assert_eq!( change.edits, [ - PushRoot { root: 1 }, - CreateElement { root: 2, tag: "div" }, - AppendChildren { many: 1 }, - PopRoot {}, + CreateElement { root: Some(2), tag: "div", children: 0 }, + AppendChildren { root: Some(1), children: vec![2] }, ] ); } @@ -772,8 +773,8 @@ fn add_listeners() { assert_eq!( change.edits, [ - NewEventListener { event_name: "keyup", scope: ScopeId(0), root: 1 }, - NewEventListener { event_name: "keydown", scope: ScopeId(0), root: 1 }, + NewEventListener { event_name: "keyup", scope: ScopeId(0), root: Some(1) }, + NewEventListener { event_name: "keydown", scope: ScopeId(0), root: Some(1) }, ] ); } @@ -797,8 +798,8 @@ fn remove_listeners() { assert_eq!( change.edits, [ - RemoveEventListener { event: "keyup", root: 1 }, - RemoveEventListener { event: "keydown", root: 1 }, + RemoveEventListener { event: "keyup", root: Some(1) }, + RemoveEventListener { event: "keydown", root: Some(1) }, ] ); } @@ -823,8 +824,8 @@ fn diff_listeners() { assert_eq!( change.edits, [ - RemoveEventListener { root: 1, event: "keydown" }, - NewEventListener { event_name: "keyup", scope: ScopeId(0), root: 1 } + RemoveEventListener { root: Some(1), event: "keydown" }, + NewEventListener { event_name: "keyup", scope: ScopeId(0), root: Some(1) } ] ); } diff --git a/packages/dioxus/tests/earlyabort.rs b/packages/dioxus/tests/earlyabort.rs index 4ac5bc356..11bf3e2ca 100644 --- a/packages/dioxus/tests/earlyabort.rs +++ b/packages/dioxus/tests/earlyabort.rs @@ -37,37 +37,31 @@ fn test_early_abort() { assert_eq!( edits.edits, [ - CreateTemplate { id: 0 }, - CreateElementTemplate { - root: 4503599627370495, - tag: "div", - locally_static: true, - fully_static: true - }, - CreateTextNodeTemplate { - root: 4503599627370496, - text: "Hello, world!", - locally_static: true - }, - AppendChildren { many: 1 }, - FinishTemplate { len: 1 }, - CreateTemplateRef { id: 1, template_id: 0 }, - AppendChildren { many: 1 } + // create template + CreateElement { root: Some(1), tag: "template", children: 1 }, + CreateElement { root: None, tag: "div", children: 1 }, + CreateTextNode { root: None, text: "Hello, world!" }, + // clone template + CloneNodeChildren { id: Some(1), new_ids: vec![2] }, + AppendChildren { root: Some(0), children: vec![2] } ] ); - let edits = dom.hard_diff(ScopeId(0)); - assert_eq!( - edits.edits, - [CreatePlaceholder { root: 2 }, ReplaceWith { root: 1, m: 1 },], - ); - let edits = dom.hard_diff(ScopeId(0)); assert_eq!( edits.edits, [ - CreateTemplateRef { id: 1, template_id: 0 }, // gets reused - ReplaceWith { root: 2, m: 1 } + CreatePlaceholder { root: Some(3) }, + ReplaceWith { root: Some(2), nodes: vec![3] } + ] + ); + + let edits = dom.hard_diff(ScopeId(0)); + assert_eq!( + edits.edits, + [ + CloneNodeChildren { id: Some(1), new_ids: vec![2] }, + ReplaceWith { root: Some(3), nodes: vec![2] } ] ); } diff --git a/packages/dioxus/tests/lifecycle.rs b/packages/dioxus/tests/lifecycle.rs index 9ff541a7f..17d198597 100644 --- a/packages/dioxus/tests/lifecycle.rs +++ b/packages/dioxus/tests/lifecycle.rs @@ -62,14 +62,14 @@ fn events_generate() { assert_eq!( edits.edits, [ - CreateElement { tag: "div", root: 1 }, - NewEventListener { event_name: "click", scope: ScopeId(0), root: 1 }, - CreateElement { tag: "div", root: 2 }, - CreateTextNode { text: "nested", root: 3 }, - AppendChildren { many: 1 }, - CreateTextNode { text: "Click me!", root: 4 }, - AppendChildren { many: 2 }, - AppendChildren { many: 1 }, + CreateElement { root: Some(1), tag: "div", children: 0 }, + NewEventListener { event_name: "click", scope: ScopeId(0), root: Some(1) }, + CreateElement { root: Some(2), tag: "div", children: 0 }, + CreateTextNode { root: Some(3), text: "nested" }, + AppendChildren { root: Some(2), children: vec![3] }, + CreateTextNode { root: Some(4), text: "Click me!" }, + AppendChildren { root: Some(1), children: vec![2, 4] }, + AppendChildren { root: Some(0), children: vec![1] } ] ) } @@ -105,24 +105,24 @@ fn components_generate() { assert_eq!( edits.edits, [ - CreateTextNode { text: "Text0", root: 1 }, - AppendChildren { many: 1 }, + CreateTextNode { root: Some(1), text: "Text0" }, + AppendChildren { root: Some(0), children: vec![1] } ] ); assert_eq!( dom.hard_diff(ScopeId(0)).edits, [ - CreateElement { tag: "div", root: 2 }, - ReplaceWith { root: 1, m: 1 }, + CreateElement { root: Some(2), tag: "div", children: 0 }, + ReplaceWith { root: Some(1), nodes: vec![2] } ] ); assert_eq!( dom.hard_diff(ScopeId(0)).edits, [ - CreateTextNode { text: "Text2", root: 1 }, - ReplaceWith { root: 2, m: 1 }, + CreateTextNode { root: Some(1), text: "Text2" }, + ReplaceWith { root: Some(2), nodes: vec![1] } ] ); @@ -130,40 +130,43 @@ fn components_generate() { assert_eq!( dom.hard_diff(ScopeId(0)).edits, [ - CreateElement { tag: "h1", root: 2 }, - ReplaceWith { root: 1, m: 1 }, + CreateElement { root: Some(2), tag: "h1", children: 0 }, + ReplaceWith { root: Some(1), nodes: vec![2] } ] ); // placeholder - assert_eq!( - dom.hard_diff(ScopeId(0)).edits, - [CreatePlaceholder { root: 1 }, ReplaceWith { root: 2, m: 1 },] - ); - assert_eq!( dom.hard_diff(ScopeId(0)).edits, [ - CreateTextNode { text: "text 3", root: 2 }, - ReplaceWith { root: 1, m: 1 }, + CreatePlaceholder { root: Some(1) }, + ReplaceWith { root: Some(2), nodes: vec![1] } ] ); assert_eq!( dom.hard_diff(ScopeId(0)).edits, [ - CreateTextNode { text: "text 0", root: 1 }, - CreateTextNode { text: "text 1", root: 3 }, - ReplaceWith { root: 2, m: 2 }, + CreateTextNode { root: Some(2), text: "text 3" }, + ReplaceWith { root: Some(1), nodes: vec![2] } ] ); assert_eq!( dom.hard_diff(ScopeId(0)).edits, [ - CreateElement { tag: "h1", root: 2 }, - ReplaceWith { root: 1, m: 1 }, - Remove { root: 3 }, + CreateTextNode { text: "text 0", root: Some(1) }, + CreateTextNode { text: "text 1", root: Some(3) }, + ReplaceWith { root: Some(2), nodes: vec![1, 3] }, + ] + ); + + assert_eq!( + dom.hard_diff(ScopeId(0)).edits, + [ + CreateElement { tag: "h1", root: Some(2), children: 0 }, + ReplaceWith { root: Some(1), nodes: vec![2] }, + Remove { root: Some(3) }, ] ); } diff --git a/packages/dioxus/tests/passthru.rs b/packages/dioxus/tests/passthru.rs index 87d9f8545..ee9667c97 100644 --- a/packages/dioxus/tests/passthru.rs +++ b/packages/dioxus/tests/passthru.rs @@ -45,10 +45,10 @@ fn nested_passthru_creates() { assert_eq!( edits.edits, [ - CreateElement { tag: "div", root: 1 }, - CreateTextNode { text: "hi", root: 2 }, - AppendChildren { many: 1 }, - AppendChildren { many: 1 }, + CreateElement { tag: "div", root: Some(1), children: 0 }, + CreateTextNode { text: "hi", root: Some(2) }, + AppendChildren { root: Some(1), children: vec![2] }, + AppendChildren { root: Some(0), children: vec![1] }, ] ) } @@ -88,13 +88,13 @@ fn nested_passthru_creates_add() { assert_eq!( edits.edits, [ - CreateTextNode { text: "1", root: 1 }, - CreateTextNode { text: "2", root: 2 }, - CreateTextNode { text: "3", root: 3 }, - CreateElement { tag: "div", root: 4 }, - CreateTextNode { text: "hi", root: 5 }, - AppendChildren { many: 1 }, - AppendChildren { many: 4 }, + CreateTextNode { text: "1", root: Some(1) }, + CreateTextNode { text: "2", root: Some(2) }, + CreateTextNode { text: "3", root: Some(3) }, + CreateElement { tag: "div", root: Some(4), children: 0 }, + CreateTextNode { text: "hi", root: Some(5) }, + AppendChildren { root: Some(4), children: vec![5] }, + AppendChildren { root: Some(0), children: vec![1, 2, 3, 4] }, ] ) } diff --git a/packages/dioxus/tests/sharedstate.rs b/packages/dioxus/tests/sharedstate.rs index 43c4755d3..3b6f3559c 100644 --- a/packages/dioxus/tests/sharedstate.rs +++ b/packages/dioxus/tests/sharedstate.rs @@ -25,8 +25,8 @@ fn shared_state_test() { assert_eq!( edits, [ - CreateTextNode { root: 1, text: "Hello, world!" }, - AppendChildren { many: 1 }, + CreateTextNode { root: Some(1), text: "Hello, world!" }, + AppendChildren { root: Some(0), children: vec![1] }, ] ); } diff --git a/packages/dioxus/tests/vdom_rebuild.rs b/packages/dioxus/tests/vdom_rebuild.rs index f19513fd4..059c36d2c 100644 --- a/packages/dioxus/tests/vdom_rebuild.rs +++ b/packages/dioxus/tests/vdom_rebuild.rs @@ -63,38 +63,22 @@ fn conditional_rendering() { assert_eq!( mutations.edits, [ - CreateTemplate { id: 0 }, - CreateElementTemplate { - root: 4503599627370495, - tag: "h1", - locally_static: true, - fully_static: true - }, - CreateTextNodeTemplate { root: 4503599627370496, text: "hello", locally_static: true }, - AppendChildren { many: 1 }, - CreatePlaceholderTemplate { root: 4503599627370497 }, - CreatePlaceholderTemplate { root: 4503599627370498 }, - FinishTemplate { len: 3 }, - CreateTemplateRef { id: 1, template_id: 0 }, - EnterTemplateRef { root: 1 }, - CreateTemplate { id: 1 }, - CreateElementTemplate { - root: 4503599627370495, - tag: "span", - locally_static: true, - fully_static: true - }, - CreateTextNodeTemplate { root: 4503599627370496, text: "a", locally_static: true }, - AppendChildren { many: 1 }, - FinishTemplate { len: 1 }, - CreateTemplateRef { id: 2, template_id: 1 }, - ReplaceWith { root: 4503599627370497, m: 1 }, - ExitTemplateRef {}, - EnterTemplateRef { root: 1 }, - CreatePlaceholder { root: 3 }, - ReplaceWith { root: 4503599627370498, m: 1 }, - ExitTemplateRef {}, - AppendChildren { many: 1 } + CreateElement { root: Some(1), tag: "template", children: 3 }, + CreateElement { root: None, tag: "h1", children: 1 }, + CreateTextNode { root: None, text: "hello" }, + CreatePlaceholder { root: None }, + CreatePlaceholder { root: None }, + CloneNodeChildren { id: Some(1), new_ids: vec![2, 3, 4] }, + CreateElement { root: Some(5), tag: "template", children: 1 }, + CreateElement { root: None, tag: "span", children: 1 }, + CreateTextNode { root: None, text: "a" }, + CloneNodeChildren { id: Some(5), new_ids: vec![6] }, + SetLastNode { id: 3 }, + ReplaceWith { root: None, nodes: vec![6] }, + CreatePlaceholder { root: Some(7) }, + SetLastNode { id: 4 }, + ReplaceWith { root: None, nodes: vec![7] }, + AppendChildren { root: Some(0), children: vec![2, 3, 4] } ] ) } diff --git a/packages/interpreter/src/bindings.rs b/packages/interpreter/src/bindings.rs index 5a3dda28e..0587368b5 100644 --- a/packages/interpreter/src/bindings.rs +++ b/packages/interpreter/src/bindings.rs @@ -15,102 +15,83 @@ extern "C" { pub fn SetNode(this: &Interpreter, id: usize, node: Node); #[wasm_bindgen(method)] - pub fn PushRoot(this: &Interpreter, root: u64); + pub fn AppendChildren(this: &Interpreter, root: Option, children: Vec); #[wasm_bindgen(method)] - pub fn PopRoot(this: &Interpreter); + pub fn ReplaceWith(this: &Interpreter, root: Option, nodes: Vec); #[wasm_bindgen(method)] - pub fn AppendChildren(this: &Interpreter, many: u32); + pub fn InsertAfter(this: &Interpreter, root: Option, nodes: Vec); #[wasm_bindgen(method)] - pub fn ReplaceWith(this: &Interpreter, root: u64, m: u32); + pub fn InsertBefore(this: &Interpreter, root: Option, nodes: Vec); #[wasm_bindgen(method)] - pub fn InsertAfter(this: &Interpreter, root: u64, n: u32); + pub fn Remove(this: &Interpreter, root: Option); #[wasm_bindgen(method)] - pub fn InsertBefore(this: &Interpreter, root: u64, n: u32); + pub fn CreateTextNode(this: &Interpreter, text: JsValue, root: Option); #[wasm_bindgen(method)] - pub fn Remove(this: &Interpreter, root: u64); + pub fn CreateElement(this: &Interpreter, tag: &str, root: Option, children: u32); #[wasm_bindgen(method)] - pub fn CreateTextNode(this: &Interpreter, text: JsValue, root: u64); + pub fn CreateElementNs( + this: &Interpreter, + tag: &str, + root: Option, + ns: &str, + children: u32, + ); #[wasm_bindgen(method)] - pub fn CreateElement(this: &Interpreter, tag: &str, root: u64); - - #[wasm_bindgen(method)] - pub fn CreateElementNs(this: &Interpreter, tag: &str, root: u64, ns: &str); - - #[wasm_bindgen(method)] - pub fn CreatePlaceholder(this: &Interpreter, root: u64); + pub fn CreatePlaceholder(this: &Interpreter, root: Option); #[wasm_bindgen(method)] pub fn NewEventListener( this: &Interpreter, name: &str, - root: u64, + root: Option, handler: &Function, bubbles: bool, ); #[wasm_bindgen(method)] - pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str, bubbles: bool); + pub fn RemoveEventListener(this: &Interpreter, root: Option, name: &str, bubbles: bool); #[wasm_bindgen(method)] - pub fn SetText(this: &Interpreter, root: u64, text: JsValue); + pub fn SetText(this: &Interpreter, root: Option, text: JsValue); #[wasm_bindgen(method)] pub fn SetAttribute( this: &Interpreter, - root: u64, + root: Option, field: &str, value: JsValue, ns: Option<&str>, ); #[wasm_bindgen(method)] - pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str, ns: Option<&str>); + pub fn RemoveAttribute(this: &Interpreter, root: Option, field: &str, ns: Option<&str>); #[wasm_bindgen(method)] - pub fn CreateTemplateRef(this: &Interpreter, id: u64, template_id: u64); + pub fn CloneNode(this: &Interpreter, root: Option, new_id: u64); #[wasm_bindgen(method)] - pub fn CreateTemplate(this: &Interpreter, id: u64); + pub fn CloneNodeChildren(this: &Interpreter, root: Option, new_ids: Vec); #[wasm_bindgen(method)] - pub fn FinishTemplate(this: &Interpreter, len: u32); + pub fn FirstChild(this: &Interpreter); #[wasm_bindgen(method)] - pub fn EnterTemplateRef(this: &Interpreter, id: u64); + pub fn NextSibling(this: &Interpreter); #[wasm_bindgen(method)] - pub fn ExitTemplateRef(this: &Interpreter); + pub fn ParentNode(this: &Interpreter); #[wasm_bindgen(method)] - pub fn CreateElementTemplate( - this: &Interpreter, - tag: &str, - root: u64, - locally_static: bool, - fully_static: bool, - ); + pub fn StoreWithId(this: &Interpreter, id: u64); #[wasm_bindgen(method)] - pub fn CreateElementNsTemplate( - this: &Interpreter, - tag: &str, - id: u64, - ns: &str, - locally_static: bool, - fully_static: bool, - ); - - #[wasm_bindgen(method)] - pub fn CreateTextNodeTemplate(this: &Interpreter, text: &str, root: u64, locally_static: bool); - - #[wasm_bindgen(method)] - pub fn CreatePlaceholderTemplate(this: &Interpreter, root: u64); + pub fn SetLastNode(this: &Interpreter, id: u64); } diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 9c01afc89..9e95fc991 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -1,7 +1,3 @@ -// id > Number.MAX_SAFE_INTEGER/2 in template ref -// id <= Number.MAX_SAFE_INTEGER/2 in global nodes -const templateIdLimit = BigInt((Number.MAX_SAFE_INTEGER - 1) / 2); - export function main() { let root = window.document.getElementById("main"); if (root != null) { @@ -10,129 +6,6 @@ export function main() { } } -class TemplateRef { - constructor(fragment, dynamicNodePaths, roots, id) { - this.fragment = fragment; - this.dynamicNodePaths = dynamicNodePaths; - this.roots = roots; - this.id = id; - this.placed = false; - this.nodes = []; - } - - build(id) { - if (!this.nodes[id]) { - let current = this.fragment; - const path = this.dynamicNodePaths[id]; - for (let i = 0; i < path.length; i++) { - const idx = path[i]; - current = current.firstChild; - for (let i2 = 0; i2 < idx; i2++) { - current = current.nextSibling; - } - } - this.nodes[id] = current; - } - } - - get(id) { - this.build(id); - return this.nodes[id]; - } - - parent() { - return this.roots[0].parentNode; - } - - first() { - return this.roots[0]; - } - - last() { - return this.roots[this.roots.length - 1]; - } - - move() { - // move the root nodes into a new template - this.fragment = new DocumentFragment(); - for (let n of this.roots) { - this.fragment.appendChild(n); - } - } - - getFragment() { - if (!this.placed) { - this.placed = true; - } - else { - this.move(); - } - return this.fragment; - } -} - -class Template { - constructor(template_id, id) { - this.nodes = []; - this.dynamicNodePaths = []; - this.template_id = template_id; - this.id = id; - this.template = document.createElement("template"); - } - - finalize(roots) { - for (let i = 0; i < roots.length; i++) { - let node = roots[i]; - let path = [i]; - const is_element = node.nodeType == 1; - const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic"); - if (!locally_static) { - this.dynamicNodePaths[node.tmplId] = [...path]; - } - const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static"); - if (traverse_children) { - this.createIds(path, node); - } - this.template.content.appendChild(node); - } - document.head.appendChild(this.template); - } - - createIds(path, root) { - let i = 0; - for (let node = root.firstChild; node != null; node = node.nextSibling) { - let new_path = [...path, i]; - const is_element = node.nodeType == 1; - const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic"); - if (!locally_static) { - this.dynamicNodePaths[node.tmplId] = [...new_path]; - } - const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static"); - if (traverse_children) { - this.createIds(new_path, node); - } - i++; - } - } - - ref(id) { - const template = this.template.content.cloneNode(true); - let roots = []; - this.reconstructingRefrencesIndex = 0; - for (let node = template.firstChild; node != null; node = node.nextSibling) { - roots.push(node); - } - let ref = new TemplateRef(template, this.dynamicNodePaths, roots, id); - // resolve ids for any nodes that can change - for (let i = 0; i < this.dynamicNodePaths.length; i++) { - if (this.dynamicNodePaths[i]) { - ref.build(i); - } - } - return ref; - } -} - class ListenerMap { constructor(root) { // bubbling events can listen at the root element @@ -185,188 +58,155 @@ class ListenerMap { export class Interpreter { constructor(root) { this.root = root; - this.stack = [root]; - this.templateInProgress = null; - this.insideTemplateRef = []; + this.lastNode = root; this.listeners = new ListenerMap(root); this.handlers = {}; this.nodes = [root]; - this.templates = []; + this.parents = []; } - top() { - return this.stack[this.stack.length - 1]; - } - pop() { - return this.stack.pop(); - } - currentTemplateId() { - if (this.insideTemplateRef.length) { - return this.insideTemplateRef[this.insideTemplateRef.length - 1].id; - } - else { - return null; - } - } - getId(id) { - if (this.templateInProgress !== null) { - return this.templates[this.templateInProgress].nodes[id - templateIdLimit]; - } - else if (this.insideTemplateRef.length && id >= templateIdLimit) { - return this.insideTemplateRef[this.insideTemplateRef.length - 1].get(id - templateIdLimit); - } - else { - return this.nodes[id]; - } - } - SetNode(id, node) { - if (this.templateInProgress !== null) { - id -= templateIdLimit; - node.tmplId = id; - this.templates[this.templateInProgress].nodes[id] = node; - } - else if (this.insideTemplateRef.length && id >= templateIdLimit) { - id -= templateIdLimit; - let last = this.insideTemplateRef[this.insideTemplateRef.length - 1]; - last.childNodes[id] = node; - if (last.nodeCache[id]) { - last.nodeCache[id] = node; + checkAppendParent() { + if (this.parents.length > 0) { + const lastParent = this.parents[this.parents.length - 1]; + lastParent[1]--; + if (lastParent[1] === 0) { + this.parents.pop(); } - } - else { - this.nodes[id] = node; + lastParent[0].appendChild(this.lastNode); } } - PushRoot(root) { - const node = this.getId(root); - this.stack.push(node); - } - PopRoot() { - this.stack.pop(); - } - AppendChildren(many) { - let root = this.stack[this.stack.length - (1 + many)]; - let to_add = this.stack.splice(this.stack.length - many); - for (let i = 0; i < many; i++) { - const child = to_add[i]; - if (child instanceof TemplateRef) { - root.appendChild(child.getFragment()); - } - else { - root.appendChild(child); - } + AppendChildren(root, children) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + for (let i = 0; i < children.length; i++) { + node.appendChild(this.nodes[children[i]]); } } - ReplaceWith(root_id, m) { - let root = this.getId(root_id); - if (root instanceof TemplateRef) { - this.InsertBefore(root_id, m); - this.Remove(root_id); + ReplaceWith(root, nodes) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; } - else { - let els = this.stack.splice(this.stack.length - m).map(function (el) { - if (el instanceof TemplateRef) { - return el.getFragment(); - } - else { - return el; - } - }); - root.replaceWith(...els); + let els = []; + for (let i = 0; i < nodes.length; i++) { + els.push(this.nodes[nodes[i]]) } + node.replaceWith(...els); } - InsertAfter(root, n) { - const old = this.getId(root); - const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) { - if (el instanceof TemplateRef) { - return el.getFragment(); - } - else { - return el; - } - }); - if (old instanceof TemplateRef) { - const last = old.last(); - last.after(...new_nodes); + InsertAfter(root, nodes) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; } - else { - old.after(...new_nodes); + let els = []; + for (let i = 0; i < nodes.length; i++) { + els.push(this.nodes[nodes[i]]) } + node.after(...els); } - InsertBefore(root, n) { - const old = this.getId(root); - const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) { - if (el instanceof TemplateRef) { - return el.getFragment(); - } - else { - return el; - } - }); - if (old instanceof TemplateRef) { - const first = old.first(); - first.before(...new_nodes); + InsertBefore(root, nodes) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; } - else { - old.before(...new_nodes); + let els = []; + for (let i = 0; i < nodes.length; i++) { + els.push(this.nodes[nodes[i]]) } + node.before(...els); } Remove(root) { - let node = this.getId(root); + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } if (node !== undefined) { - if (node instanceof TemplateRef) { - for (let child of node.roots) { - child.remove(); - } - } - else { - node.remove(); - } + node.remove(); } } CreateTextNode(text, root) { - const node = document.createTextNode(text); - this.stack.push(node); - this.SetNode(root, node); + this.lastNode = document.createTextNode(text); + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } } - CreateElement(tag, root) { - const el = document.createElement(tag); - this.stack.push(el); - this.SetNode(root, el); + CreateElement(tag, root, children) { + this.lastNode = document.createElement(tag); + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } + if (children > 0) { + this.parents.push([this.lastNode, children]); + } } - CreateElementNs(tag, root, ns) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.SetNode(root, el); + CreateElementNs(tag, root, ns, children) { + this.lastNode = document.createElementNS(ns, tag); + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } + if (children > 0) { + this.parents.push([this.lastNode, children]); + } } CreatePlaceholder(root) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.SetNode(root, el); + this.lastNode = document.createElement("pre"); + this.lastNode.hidden = true; + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } } NewEventListener(event_name, root, handler, bubbles) { - const element = this.getId(root); - if (root >= templateIdLimit) { - let currentTemplateRefId = this.currentTemplateId(); - root -= templateIdLimit; - element.setAttribute("data-dioxus-id", `${currentTemplateRefId},${root}`); + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; } - else { - element.setAttribute("data-dioxus-id", `${root}`); - } - this.listeners.create(event_name, element, handler, bubbles); + node.setAttribute("data-dioxus-id", `${root}`); + this.listeners.create(event_name, node, handler, bubbles); } RemoveEventListener(root, event_name, bubbles) { - const element = this.getId(root); - element.removeAttribute(`data-dioxus-id`); - this.listeners.remove(element, event_name, bubbles); + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + node.removeAttribute(`data-dioxus-id`); + this.listeners.remove(node, event_name, bubbles); } SetText(root, text) { - this.getId(root).data = text; + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + node.data = text; } SetAttribute(root, field, value, ns) { const name = field; - const node = this.getId(root); + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } if (ns === "style") { // @ts-ignore node.style[name] = value; @@ -400,7 +240,12 @@ export class Interpreter { } RemoveAttribute(root, field, ns) { const name = field; - const node = this.getId(root); + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } if (ns == "style") { node.style.removeProperty(name); } else if (ns !== null || ns !== undefined) { @@ -417,94 +262,82 @@ export class Interpreter { node.removeAttribute(name); } } - CreateTemplateRef(id, template_id) { - const el = this.templates[template_id].ref(id); - this.nodes[id] = el; - this.stack.push(el); + CloneNode(old, new_id) { + let node; + if (old === null) { + node = this.lastNode; + } else { + node = this.nodes[old]; + } + this.nodes[new_id] = node.cloneNode(true); } - CreateTemplate(template_id) { - this.templateInProgress = template_id; - this.templates[template_id] = new Template(template_id, 0); + CloneNodeChildren(old, new_ids) { + let node; + if (old === null) { + node = this.lastNode; + } else { + node = this.nodes[old]; + } + const old_node = node.cloneNode(true); + let i = 0; + for (let node = old_node.firstChild; i < new_ids.length; node = node.nextSibling) { + this.nodes[new_ids[i++]] = node; + } } - FinishTemplate(many) { - this.templates[this.templateInProgress].finalize(this.stack.splice(this.stack.length - many)); - this.templateInProgress = null; + FirstChild() { + this.lastNode = this.lastNode.firstChild; } - EnterTemplateRef(id) { - this.insideTemplateRef.push(this.nodes[id]); + NextSibling() { + this.lastNode = this.lastNode.nextSibling; } - ExitTemplateRef() { - this.insideTemplateRef.pop(); + ParentNode() { + this.lastNode = this.lastNode.parentNode; + } + StoreWithId(id) { + this.nodes[id] = this.lastNode; + } + SetLastNode(root) { + this.lastNode = this.nodes[root]; } handleEdits(edits) { for (let edit of edits) { this.handleEdit(edit); } } - CreateElementTemplate(tag, root, locally_static, fully_static) { - const el = document.createElement(tag); - this.stack.push(el); - this.SetNode(root, el); - if (!locally_static) - el.setAttribute("data-dioxus-dynamic", "true"); - if (fully_static) - el.setAttribute("data-dioxus-fully-static", fully_static); - } - CreateElementNsTemplate(tag, root, ns, locally_static, fully_static) { - const el = document.createElementNS(ns, tag); - this.stack.push(el); - this.SetNode(root, el); - if (!locally_static) - el.setAttribute("data-dioxus-dynamic", "true"); - if (fully_static) - el.setAttribute("data-dioxus-fully-static", fully_static); - } - CreateTextNodeTemplate(text, root, locally_static) { - const node = document.createTextNode(text); - this.stack.push(node); - this.SetNode(root, node); - } - CreatePlaceholderTemplate(root) { - const el = document.createElement("pre"); - el.setAttribute("data-dioxus-dynamic", "true"); - el.hidden = true; - this.stack.push(el); - this.SetNode(root, el); - } handleEdit(edit) { switch (edit.type) { case "PushRoot": - this.PushRoot(BigInt(edit.root)); + this.PushRoot(edit.root); break; case "AppendChildren": - this.AppendChildren(edit.many); + this.AppendChildren(edit.root, edit.children); break; case "ReplaceWith": - this.ReplaceWith(BigInt(edit.root), edit.m); + this.ReplaceWith(edit.root, edit.nodes); break; case "InsertAfter": - this.InsertAfter(BigInt(edit.root), edit.n); + this.InsertAfter(edit.root, edit.nodes); break; case "InsertBefore": - this.InsertBefore(BigInt(edit.root), edit.n); + this.InsertBefore(edit.root, edit.nodes); break; case "Remove": - this.Remove(BigInt(edit.root)); + this.Remove(edit.root); break; case "CreateTextNode": - this.CreateTextNode(edit.text, BigInt(edit.root)); + this.CreateTextNode(edit.text, edit.root); break; case "CreateElement": - this.CreateElement(edit.tag, BigInt(edit.root)); + this.CreateElement(edit.tag, edit.root, edit.children); break; case "CreateElementNs": - this.CreateElementNs(edit.tag, BigInt(edit.root), edit.ns); + this.CreateElementNs(edit.tag, edit.root, edit.ns, edit.children); break; case "CreatePlaceholder": - this.CreatePlaceholder(BigInt(edit.root)); + this.CreatePlaceholder(edit.root); break; case "RemoveEventListener": - this.RemoveEventListener(BigInt(edit.root), edit.event_name); + this.RemoveEventListener(edit.root, edit.event_name); break; case "NewEventListener": // this handler is only provided on desktop implementations since this @@ -588,16 +421,7 @@ export class Interpreter { if (realId === null) { return; } - if (realId.includes(",")) { - realId = realId.split(','); - realId = { - template_ref_id: parseInt(realId[0]), - template_node_id: parseInt(realId[1]), - }; - } - else { - realId = parseInt(realId); - } + realId = parseInt(realId); window.ipc.postMessage( serializeIpcMessage("user_event", { event: edit.event_name, @@ -607,47 +431,38 @@ export class Interpreter { ); } }; - this.NewEventListener(edit.event_name, BigInt(edit.root), handler, event_bubbles(edit.event_name)); + this.NewEventListener(edit.event_name, edit.root, handler, event_bubbles(edit.event_name)); break; case "SetText": - this.SetText(BigInt(edit.root), edit.text); + this.SetText(edit.root, edit.text); break; case "SetAttribute": - this.SetAttribute(BigInt(edit.root), edit.field, edit.value, edit.ns); + this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); break; case "RemoveAttribute": - this.RemoveAttribute(BigInt(edit.root), edit.name, edit.ns); + this.RemoveAttribute(edit.root, edit.name, edit.ns); break; - case "PopRoot": - this.PopRoot(); + case "CloneNode": + this.CloneNode(edit.id, edit.new_id); break; - case "CreateTemplateRef": - this.CreateTemplateRef(BigInt(edit.id), edit.template_id); + case "CloneNodeChildren": + this.CloneNodeChildren(edit.id, edit.new_ids); break; - case "CreateTemplate": - this.CreateTemplate(BigInt(edit.id)); + case "FirstChild": + this.FirstChild(); break; - case "FinishTemplate": - this.FinishTemplate(edit.len); + case "NextSibling": + this.NextSibling(); break; - case "EnterTemplateRef": - this.EnterTemplateRef(BigInt(edit.root)); + case "ParentNode": + this.ParentNode(); break; - case "ExitTemplateRef": - this.ExitTemplateRef(); + case "StoreWithId": + this.StoreWithId(BigInt(edit.id)); break; - case "CreateElementTemplate": - this.CreateElementTemplate(edit.tag, BigInt(edit.root), edit.locally_static, edit.fully_static); - break; - case "CreateElementNsTemplate": - this.CreateElementNsTemplate(edit.tag, BigInt(edit.root), edit.ns, edit.locally_static, edit.fully_static); - break; - case "CreateTextNodeTemplate": - this.CreateTextNodeTemplate(edit.text, BigInt(edit.root), edit.locally_static); - break; - case "CreatePlaceholderTemplate": - this.CreatePlaceholderTemplate(BigInt(edit.root)); + case "SetLastNode": + this.SetLastNode(BigInt(edit.id)); break; } } diff --git a/packages/liveview/src/events.rs b/packages/liveview/src/events.rs index 29ca9bb50..841bdeeff 100644 --- a/packages/liveview/src/events.rs +++ b/packages/liveview/src/events.rs @@ -5,7 +5,7 @@ use std::any::Any; use std::sync::Arc; -use dioxus_core::GlobalNodeId; +use dioxus_core::ElementId; use dioxus_core::{EventPriority, UserEvent}; use dioxus_html::event_bubbles; use dioxus_html::on::*; @@ -26,7 +26,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option { #[derive(serde::Serialize, serde::Deserialize)] struct ImEvent { event: String, - mounted_dom_id: GlobalNodeId, + mounted_dom_id: ElementId, contents: serde_json::Value, } diff --git a/packages/liveview/src/interpreter.js b/packages/liveview/src/interpreter.js index 91f89f8fd..7e99745f2 100644 --- a/packages/liveview/src/interpreter.js +++ b/packages/liveview/src/interpreter.js @@ -35,127 +35,6 @@ class IPC { } } -// id > Number.MAX_SAFE_INTEGER/2 in template ref -// id <= Number.MAX_SAFE_INTEGER/2 in global nodes -const templateIdLimit = BigInt((Number.MAX_SAFE_INTEGER - 1) / 2); - -class TemplateRef { - constructor(fragment, dynamicNodePaths, roots, id) { - this.fragment = fragment; - this.dynamicNodePaths = dynamicNodePaths; - this.roots = roots; - this.id = id; - this.placed = false; - this.nodes = []; - } - - build(id) { - if (!this.nodes[id]) { - let current = this.fragment; - const path = this.dynamicNodePaths[id]; - for (let i = 0; i < path.length; i++) { - const idx = path[i]; - current = current.firstChild; - for (let i2 = 0; i2 < idx; i2++) { - current = current.nextSibling; - } - } - this.nodes[id] = current; - } - } - - get(id) { - this.build(id); - return this.nodes[id]; - } - - parent() { - return this.roots[0].parentNode; - } - - first() { - return this.roots[0]; - } - - last() { - return this.roots[this.roots.length - 1]; - } - - move() { - // move the root nodes into a new template - this.fragment = new DocumentFragment(); - for (let n of this.roots) { - this.fragment.appendChild(n); - } - } - - getFragment() { - if (!this.placed) { - this.placed = true; - } - else { - this.move(); - } - return this.fragment; - } -} - -class Template { - constructor(template_id, id) { - this.nodes = []; - this.dynamicNodePaths = []; - this.template_id = template_id; - this.id = id; - this.template = document.createElement("template"); - this.reconstructingRefrencesIndex = null; - } - - finalize(roots) { - for (let i = 0; i < roots.length; i++) { - let node = roots[i]; - let path = [i]; - const is_element = node.nodeType == 1; - const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic"); - if (!locally_static) { - this.dynamicNodePaths[node.tmplId] = [...path]; - } - const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static"); - if (traverse_children) { - this.createIds(path, node); - } - this.template.content.appendChild(node); - } - document.head.appendChild(this.template); - } - - createIds(path, root) { - let i = 0; - for (let node = root.firstChild; node != null; node = node.nextSibling) { - let new_path = [...path, i]; - const is_element = node.nodeType == 1; - const locally_static = is_element && !node.hasAttribute("data-dioxus-dynamic"); - if (!locally_static) { - this.dynamicNodePaths[node.tmplId] = [...new_path]; - } - const traverse_children = is_element && !node.hasAttribute("data-dioxus-fully-static"); - if (traverse_children) { - this.createIds(new_path, node); - } - i++; - } - } - - ref(id) { - const template = this.template.content.cloneNode(true); - let roots = []; - this.reconstructingRefrencesIndex = 0; - for (let node = template.firstChild; node != null; node = node.nextSibling) { - roots.push(node); - } - return new TemplateRef(template, this.dynamicNodePaths, roots, id); - } -} - class ListenerMap { constructor(root) { // bubbling events can listen at the root element @@ -208,188 +87,155 @@ class ListenerMap { class Interpreter { constructor(root) { this.root = root; - this.stack = [root]; - this.templateInProgress = null; - this.insideTemplateRef = []; + this.lastNode = root; this.listeners = new ListenerMap(root); this.handlers = {}; this.nodes = [root]; - this.templates = []; + this.parents = []; } - top() { - return this.stack[this.stack.length - 1]; - } - pop() { - return this.stack.pop(); - } - currentTemplateId() { - if (this.insideTemplateRef.length) { - return this.insideTemplateRef[this.insideTemplateRef.length - 1].id; - } - else { - return null; - } - } - getId(id) { - if (this.templateInProgress !== null) { - return this.templates[this.templateInProgress].nodes[id - templateIdLimit]; - } - else if (this.insideTemplateRef.length && id >= templateIdLimit) { - return this.insideTemplateRef[this.insideTemplateRef.length - 1].get(id - templateIdLimit); - } - else { - return this.nodes[id]; - } - } - SetNode(id, node) { - if (this.templateInProgress !== null) { - id -= templateIdLimit; - node.tmplId = id; - this.templates[this.templateInProgress].nodes[id] = node; - } - else if (this.insideTemplateRef.length && id >= templateIdLimit) { - id -= templateIdLimit; - let last = this.insideTemplateRef[this.insideTemplateRef.length - 1]; - last.childNodes[id] = node; - if (last.nodeCache[id]) { - last.nodeCache[id] = node; + checkAppendParent() { + if (this.parents.length > 0) { + const lastParent = this.parents[this.parents.length - 1]; + lastParent[1]--; + if (lastParent[1] === 0) { + this.parents.pop(); } - } - else { - this.nodes[id] = node; + lastParent[0].appendChild(this.lastNode); } } - PushRoot(root) { - const node = this.getId(root); - this.stack.push(node); - } - PopRoot() { - this.stack.pop(); - } - AppendChildren(many) { - let root = this.stack[this.stack.length - (1 + many)]; - let to_add = this.stack.splice(this.stack.length - many); - for (let i = 0; i < many; i++) { - const child = to_add[i]; - if (child instanceof TemplateRef) { - root.appendChild(child.getFragment()); - } - else { - root.appendChild(child); - } + AppendChildren(root, children) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + for (let i = 0; i < children.length; i++) { + node.appendChild(this.nodes[children[i]]); } } - ReplaceWith(root_id, m) { - let root = this.getId(root_id); - if (root instanceof TemplateRef) { - this.InsertBefore(root_id, m); - this.Remove(root_id); + ReplaceWith(root, nodes) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; } - else { - let els = this.stack.splice(this.stack.length - m).map(function (el) { - if (el instanceof TemplateRef) { - return el.getFragment(); - } - else { - return el; - } - }); - root.replaceWith(...els); + let els = []; + for (let i = 0; i < nodes.length; i++) { + els.push(this.nodes[nodes[i]]) } + node.replaceWith(...els); } - InsertAfter(root, n) { - const old = this.getId(root); - const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) { - if (el instanceof TemplateRef) { - return el.getFragment(); - } - else { - return el; - } - }); - if (old instanceof TemplateRef) { - const last = old.last(); - last.after(...new_nodes); + InsertAfter(root, nodes) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; } - else { - old.after(...new_nodes); + let els = []; + for (let i = 0; i < nodes.length; i++) { + els.push(this.nodes[nodes[i]]) } + node.after(...els); } - InsertBefore(root, n) { - const old = this.getId(root); - const new_nodes = this.stack.splice(this.stack.length - n).map(function (el) { - if (el instanceof TemplateRef) { - return el.getFragment(); - } - else { - return el; - } - }); - if (old instanceof TemplateRef) { - const first = old.first(); - first.before(...new_nodes); + InsertBefore(root, nodes) { + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; } - else { - old.before(...new_nodes); + let els = []; + for (let i = 0; i < nodes.length; i++) { + els.push(this.nodes[nodes[i]]) } + node.before(...els); } Remove(root) { - let node = this.getId(root); + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } if (node !== undefined) { - if (node instanceof TemplateRef) { - for (let child of node.roots) { - child.remove(); - } - } - else { - node.remove(); - } + node.remove(); } } CreateTextNode(text, root) { - const node = document.createTextNode(text); - this.stack.push(node); - this.SetNode(root, node); + this.lastNode = document.createTextNode(text); + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } } - CreateElement(tag, root) { - const el = document.createElement(tag); - this.stack.push(el); - this.SetNode(root, el); + CreateElement(tag, root, children) { + this.lastNode = document.createElement(tag); + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } + if (children > 0) { + this.parents.push([this.lastNode, children]); + } } - CreateElementNs(tag, root, ns) { - let el = document.createElementNS(ns, tag); - this.stack.push(el); - this.SetNode(root, el); + CreateElementNs(tag, root, ns, children) { + this.lastNode = document.createElementNS(ns, tag); + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } + if (children > 0) { + this.parents.push([this.lastNode, children]); + } } CreatePlaceholder(root) { - let el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.SetNode(root, el); + this.lastNode = document.createElement("pre"); + this.lastNode.hidden = true; + this.checkAppendParent(); + if (root != null) { + this.nodes[root] = this.lastNode; + } } NewEventListener(event_name, root, handler, bubbles) { - const element = this.getId(root); - if (root >= templateIdLimit) { - let currentTemplateRefId = this.currentTemplateId(); - root -= templateIdLimit; - element.setAttribute("data-dioxus-id", `${currentTemplateRefId},${root}`); + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; } - else { - element.setAttribute("data-dioxus-id", `${root}`); - } - this.listeners.create(event_name, element, handler, bubbles); + node.setAttribute("data-dioxus-id", `${root}`); + this.listeners.create(event_name, node, handler, bubbles); } RemoveEventListener(root, event_name, bubbles) { - const element = this.getId(root); - element.removeAttribute(`data-dioxus-id`); - this.listeners.remove(element, event_name, bubbles); + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + node.removeAttribute(`data-dioxus-id`); + this.listeners.remove(node, event_name, bubbles); } SetText(root, text) { - this.getId(root).data = text; + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } + node.data = text; } SetAttribute(root, field, value, ns) { const name = field; - const node = this.getId(root); + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } if (ns === "style") { // @ts-ignore node.style[name] = value; @@ -423,7 +269,12 @@ class Interpreter { } RemoveAttribute(root, field, ns) { const name = field; - const node = this.getId(root); + let node; + if (root == null) { + node = this.lastNode; + } else { + node = this.nodes[root]; + } if (ns == "style") { node.style.removeProperty(name); } else if (ns !== null || ns !== undefined) { @@ -440,94 +291,82 @@ class Interpreter { node.removeAttribute(name); } } - CreateTemplateRef(id, template_id) { - const el = this.templates[template_id].ref(id); - this.nodes[id] = el; - this.stack.push(el); + CloneNode(old, new_id) { + let node; + if (old === null) { + node = this.lastNode; + } else { + node = this.nodes[old]; + } + this.nodes[new_id] = node.cloneNode(true); } - CreateTemplate(template_id) { - this.templateInProgress = template_id; - this.templates[template_id] = new Template(template_id, 0); + CloneNodeChildren(old, new_ids) { + let node; + if (old === null) { + node = this.lastNode; + } else { + node = this.nodes[old]; + } + const old_node = node.cloneNode(true); + let i = 0; + for (let node = old_node.firstChild; i < new_ids.length; node = node.nextSibling) { + this.nodes[new_ids[i++]] = node; + } } - FinishTemplate(many) { - this.templates[this.templateInProgress].finalize(this.stack.splice(this.stack.length - many)); - this.templateInProgress = null; + FirstChild() { + this.lastNode = this.lastNode.firstChild; } - EnterTemplateRef(id) { - this.insideTemplateRef.push(this.nodes[id]); + NextSibling() { + this.lastNode = this.lastNode.nextSibling; } - ExitTemplateRef() { - this.insideTemplateRef.pop(); + ParentNode() { + this.lastNode = this.lastNode.parentNode; + } + StoreWithId(id) { + this.nodes[id] = this.lastNode; + } + SetLastNode(root) { + this.lastNode = this.nodes[root]; } handleEdits(edits) { for (let edit of edits) { this.handleEdit(edit); } } - CreateElementTemplate(tag, root, locally_static, fully_static) { - const el = document.createElement(tag); - this.stack.push(el); - this.SetNode(root, el); - if (!locally_static) - el.setAttribute("data-dioxus-dynamic", "true"); - if (fully_static) - el.setAttribute("data-dioxus-fully-static", fully_static); - } - CreateElementNsTemplate(tag, root, ns, locally_static, fully_static) { - const el = document.createElementNS(ns, tag); - this.stack.push(el); - this.SetNode(root, el); - if (!locally_static) - el.setAttribute("data-dioxus-dynamic", "true"); - if (fully_static) - el.setAttribute("data-dioxus-fully-static", fully_static); - } - CreateTextNodeTemplate(text, root, locally_static) { - const node = document.createTextNode(text); - this.stack.push(node); - this.SetNode(root, node); - } - CreatePlaceholderTemplate(root) { - const el = document.createElement("pre"); - el.setAttribute("data-dioxus-dynamic", "true"); - el.hidden = true; - this.stack.push(el); - this.SetNode(root, el); - } handleEdit(edit) { switch (edit.type) { case "PushRoot": - this.PushRoot(BigInt(edit.root)); + this.PushRoot(edit.root); break; case "AppendChildren": - this.AppendChildren(edit.many); + this.AppendChildren(edit.root, edit.children); break; case "ReplaceWith": - this.ReplaceWith(BigInt(edit.root), edit.m); + this.ReplaceWith(edit.root, edit.nodes); break; case "InsertAfter": - this.InsertAfter(BigInt(edit.root), edit.n); + this.InsertAfter(edit.root, edit.nodes); break; case "InsertBefore": - this.InsertBefore(BigInt(edit.root), edit.n); + this.InsertBefore(edit.root, edit.nodes); break; case "Remove": - this.Remove(BigInt(edit.root)); + this.Remove(edit.root); break; case "CreateTextNode": - this.CreateTextNode(edit.text, BigInt(edit.root)); + this.CreateTextNode(edit.text, edit.root); break; case "CreateElement": - this.CreateElement(edit.tag, BigInt(edit.root)); + this.CreateElement(edit.tag, edit.root, edit.children); break; case "CreateElementNs": - this.CreateElementNs(edit.tag, BigInt(edit.root), edit.ns); + this.CreateElementNs(edit.tag, edit.root, edit.ns, edit.children); break; case "CreatePlaceholder": - this.CreatePlaceholder(BigInt(edit.root)); + this.CreatePlaceholder(edit.root); break; case "RemoveEventListener": - this.RemoveEventListener(BigInt(edit.root), edit.event_name); + this.RemoveEventListener(edit.root, edit.event_name); break; case "NewEventListener": // this handler is only provided on desktop implementations since this @@ -547,7 +386,7 @@ class Interpreter { event.preventDefault(); const href = target.getAttribute("href"); if (href !== "" && href !== null && href !== undefined) { - window.ipc.send( + window.ipc.postMessage( serializeIpcMessage("browser_open", { href }) ); } @@ -611,16 +450,7 @@ class Interpreter { if (realId === null) { return; } - if (realId.includes(",")) { - realId = realId.split(','); - realId = { - template_ref_id: parseInt(realId[0]), - template_node_id: parseInt(realId[1]), - }; - } - else { - realId = parseInt(realId); - } + realId = parseInt(realId); window.ipc.send( serializeIpcMessage("user_event", { event: edit.event_name, @@ -630,47 +460,38 @@ class Interpreter { ); } }; - this.NewEventListener(edit.event_name, BigInt(edit.root), handler, event_bubbles(edit.event_name)); + this.NewEventListener(edit.event_name, edit.root, handler, event_bubbles(edit.event_name)); break; case "SetText": - this.SetText(BigInt(edit.root), edit.text); + this.SetText(edit.root, edit.text); break; case "SetAttribute": - this.SetAttribute(BigInt(edit.root), edit.field, edit.value, edit.ns); + this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); break; case "RemoveAttribute": - this.RemoveAttribute(BigInt(edit.root), edit.name, edit.ns); + this.RemoveAttribute(edit.root, edit.name, edit.ns); break; - case "PopRoot": - this.PopRoot(); + case "CloneNode": + this.CloneNode(edit.id, edit.new_id); break; - case "CreateTemplateRef": - this.CreateTemplateRef(BigInt(edit.id), edit.template_id); + case "CloneNodeChildren": + this.CloneNodeChildren(edit.id, edit.new_ids); break; - case "CreateTemplate": - this.CreateTemplate(BigInt(edit.id)); + case "FirstChild": + this.FirstChild(); break; - case "FinishTemplate": - this.FinishTemplate(edit.len); + case "NextSibling": + this.NextSibling(); break; - case "EnterTemplateRef": - this.EnterTemplateRef(BigInt(edit.root)); + case "ParentNode": + this.ParentNode(); break; - case "ExitTemplateRef": - this.ExitTemplateRef(); + case "StoreWithId": + this.StoreWithId(BigInt(edit.id)); break; - case "CreateElementTemplate": - this.CreateElementTemplate(edit.tag, BigInt(edit.root), edit.locally_static, edit.fully_static); - break; - case "CreateElementNsTemplate": - this.CreateElementNsTemplate(edit.tag, BigInt(edit.root), edit.ns, edit.locally_static, edit.fully_static); - break; - case "CreateTextNodeTemplate": - this.CreateTextNodeTemplate(edit.text, BigInt(edit.root), edit.locally_static); - break; - case "CreatePlaceholderTemplate": - this.CreatePlaceholderTemplate(BigInt(edit.root)); + case "SetLastNode": + this.SetLastNode(BigInt(edit.id)); break; } } diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index dd1eb8dad..852477fc8 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -164,59 +164,12 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let gen = quote! { impl State for #type_name { - fn update<'a, T: dioxus_native_core::traversable::Traversable,T2: dioxus_native_core::traversable::Traversable>( - dirty: &[(dioxus_core::GlobalNodeId, dioxus_native_core::node_ref::NodeMask)], + fn update<'a, T: dioxus_native_core::traversable::Traversable,T2: dioxus_native_core::traversable::Traversable>( + dirty: &[(dioxus_native_core::RealNodeId, dioxus_native_core::node_ref::NodeMask)], state_tree: &'a mut T, rdom: &'a T2, ctx: &anymap::AnyMap, - ) -> rustc_hash::FxHashSet{ - #[derive(Eq, PartialEq)] - struct HeightOrdering { - height: u16, - id: dioxus_core::GlobalNodeId, - } - - impl HeightOrdering { - fn new(height: u16, id: dioxus_core::GlobalNodeId) -> Self { - HeightOrdering { - height, - id, - } - } - } - - // not the ordering after height is just for deduplication it can be any ordering as long as it is consistent - impl Ord for HeightOrdering { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.height.cmp(&other.height).then(match (self.id, other.id){ - ( - dioxus_core::GlobalNodeId::TemplateId { - template_ref_id, - template_node_id, - }, - dioxus_core::GlobalNodeId::TemplateId { - template_ref_id: o_template_ref_id, - template_node_id: o_template_node_id, - }, - ) => template_ref_id - .0 - .cmp(&o_template_ref_id.0) - .then(template_node_id.0.cmp(&o_template_node_id.0)), - (dioxus_core::GlobalNodeId::TemplateId { .. }, dioxus_core::GlobalNodeId::VNodeId(_)) => std::cmp::Ordering::Less, - (dioxus_core::GlobalNodeId::VNodeId(_), dioxus_core::GlobalNodeId::TemplateId { .. }) => { - std::cmp::Ordering::Greater - } - (dioxus_core::GlobalNodeId::VNodeId(s_id), dioxus_core::GlobalNodeId::VNodeId(o_id)) => s_id.0.cmp(&o_id.0), - }) - } - } - - impl PartialOrd for HeightOrdering { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(&other)) - } - } - + ) -> rustc_hash::FxHashSet{ #[derive(Clone, Copy)] struct MembersDirty { #(#members: bool, )* @@ -238,7 +191,7 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream { let mut dirty_elements = rustc_hash::FxHashSet::default(); // the states of any elements that are dirty - let mut states: rustc_hash::FxHashMap = rustc_hash::FxHashMap::default(); + let mut states: rustc_hash::FxHashMap = rustc_hash::FxHashMap::default(); for (id, mask) in dirty { let members_dirty = MembersDirty { @@ -408,7 +361,7 @@ impl<'a> StateStruct<'a> { let insert = dep.child.iter().map(|d|{ if *d == mem { quote! { - let seeking = HeightOrdering::new(state_tree.height(parent_id).unwrap(), parent_id); + let seeking = dioxus_native_core::HeightOrdering::new(state_tree.height(parent_id).unwrap(), parent_id); if let Err(idx) = resolution_order .binary_search_by(|ordering| ordering.cmp(&seeking).reverse()){ resolution_order.insert( @@ -453,7 +406,7 @@ impl<'a> StateStruct<'a> { let insert = dep.parent.iter().map(|d| { if *d == mem { quote! { - let seeking = HeightOrdering::new(state_tree.height(*child_id).unwrap(), *child_id); + let seeking = dioxus_native_core::HeightOrdering::new(state_tree.height(*child_id).unwrap(), *child_id); if let Err(idx) = resolution_order .binary_search(&seeking){ resolution_order.insert( @@ -508,7 +461,7 @@ impl<'a> StateStruct<'a> { DependencyKind::Parent => { quote! { // resolve parent dependant state - let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::>(); + let mut resolution_order = states.keys().copied().map(|id| dioxus_native_core::HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::>(); resolution_order.sort(); let mut i = 0; while i < resolution_order.len(){ @@ -528,7 +481,7 @@ impl<'a> StateStruct<'a> { DependencyKind::Child => { quote! { // resolve child dependant state - let mut resolution_order = states.keys().copied().map(|id| HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::>(); + let mut resolution_order = states.keys().copied().map(|id| dioxus_native_core::HeightOrdering::new(state_tree.height(id).unwrap(), id)).collect::>(); resolution_order.sort_by(|height_ordering1, height_ordering2| { height_ordering1.cmp(&height_ordering2).reverse() }); diff --git a/packages/native-core-macro/tests/called_minimally_on_build.rs b/packages/native-core-macro/tests/called_minimally_on_build.rs index e318ccff0..7063574aa 100644 --- a/packages/native-core-macro/tests/called_minimally_on_build.rs +++ b/packages/native-core-macro/tests/called_minimally_on_build.rs @@ -1,5 +1,4 @@ use anymap::AnyMap; -use dioxus::core as dioxus_core; use dioxus::prelude::*; use dioxus_native_core::node_ref::*; use dioxus_native_core::real_dom::*; diff --git a/packages/native-core-macro/tests/change_nodes.rs b/packages/native-core-macro/tests/change_nodes.rs index 59e0d37a1..2197cdbce 100644 --- a/packages/native-core-macro/tests/change_nodes.rs +++ b/packages/native-core-macro/tests/change_nodes.rs @@ -1,7 +1,8 @@ -use dioxus::core::{self as dioxus_core, GlobalNodeId}; +use dioxus::core::ElementId; use dioxus::prelude::*; use dioxus_native_core::real_dom::RealDom; use dioxus_native_core::state::State; +use dioxus_native_core::RealNodeId; use dioxus_native_core_macro::State; #[derive(State, Default, Clone)] @@ -33,37 +34,13 @@ fn remove_node() { let _to_update = dom.apply_mutations(vec![create]); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(0), - }] - .node_data - .height, - 1 - ); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(1), - }] - .node_data - .height, - 2 - ); + assert_eq!(dom[RealNodeId::ElementId(ElementId(0))].node_data.height, 0); + assert_eq!(dom[RealNodeId::UnaccessableId(0)].node_data.height, 1); dom.apply_mutations(vec![edit]); - assert_eq!(dom.size(), 1); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(2), - template_node_id: dioxus::prelude::TemplateNodeId(0), - }] - .node_data - .height, - 1 - ); + assert_eq!(dom.size(), 3); + assert_eq!(dom[RealNodeId::ElementId(ElementId(0))].node_data.height, 0); } #[test] @@ -90,36 +67,12 @@ fn add_node() { let _to_update = dom.apply_mutations(vec![create]); - assert_eq!(dom.size(), 1); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(0), - }] - .node_data - .height, - 1 - ); + assert_eq!(dom.size(), 2); + assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1); dom.apply_mutations(vec![update]); - assert_eq!(dom.size(), 1); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(2), - template_node_id: dioxus::prelude::TemplateNodeId(0), - }] - .node_data - .height, - 1 - ); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(2), - template_node_id: dioxus::prelude::TemplateNodeId(1), - }] - .node_data - .height, - 2 - ); + assert_eq!(dom.size(), 3); + assert_eq!(dom[RealNodeId::ElementId(ElementId(3))].node_data.height, 0); + assert_eq!(dom[RealNodeId::UnaccessableId(0)].node_data.height, 1); } diff --git a/packages/native-core-macro/tests/initial_build.rs b/packages/native-core-macro/tests/initial_build.rs index c99e45a1c..d9b35f37a 100644 --- a/packages/native-core-macro/tests/initial_build.rs +++ b/packages/native-core-macro/tests/initial_build.rs @@ -1,7 +1,8 @@ -use dioxus::core::{self as dioxus_core, GlobalNodeId}; +use dioxus::core::ElementId; use dioxus::prelude::*; use dioxus_native_core::real_dom::RealDom; use dioxus_native_core::state::State; +use dioxus_native_core::RealNodeId; use dioxus_native_core_macro::State; #[derive(Default, Clone, State)] @@ -24,16 +25,8 @@ fn initial_build_simple() { let _to_update = dom.apply_mutations(vec![mutations]); - assert_eq!(dom.size(), 1); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(0), - }] - .node_data - .height, - 1 - ); + assert_eq!(dom.size(), 2); + assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1); } #[test] @@ -60,59 +53,11 @@ fn initial_build_with_children() { let mut dom: RealDom = RealDom::new(); let _to_update = dom.apply_mutations(vec![mutations]); - assert_eq!(dom.size(), 1); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(0), - }] - .node_data - .height, - 1 - ); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(1), - }] - .node_data - .height, - 2 - ); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(2), - }] - .node_data - .height, - 3 - ); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(3), - }] - .node_data - .height, - 3 - ); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(4), - }] - .node_data - .height, - 4 - ); - assert_eq!( - dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(5), - }] - .node_data - .height, - 3 - ); + assert_eq!(dom.size(), 2); + assert_eq!(dom[RealNodeId::ElementId(ElementId(2))].node_data.height, 1); + assert_eq!(dom[RealNodeId::UnaccessableId(6)].node_data.height, 2); + assert_eq!(dom[RealNodeId::UnaccessableId(5)].node_data.height, 3); + assert_eq!(dom[RealNodeId::UnaccessableId(8)].node_data.height, 3); + assert_eq!(dom[RealNodeId::UnaccessableId(10)].node_data.height, 3); + assert_eq!(dom[RealNodeId::UnaccessableId(9)].node_data.height, 4); } diff --git a/packages/native-core-macro/tests/peristant_iterator.rs b/packages/native-core-macro/tests/peristant_iterator.rs index 1d79f0915..b999b503c 100644 --- a/packages/native-core-macro/tests/peristant_iterator.rs +++ b/packages/native-core-macro/tests/peristant_iterator.rs @@ -1,4 +1,3 @@ -use dioxus::core as dioxus_core; use dioxus::core_macro::rsx_without_templates; use dioxus::prelude::*; use dioxus_native_core::{ diff --git a/packages/native-core-macro/tests/update_state.rs b/packages/native-core-macro/tests/update_state.rs index 4f0d18b29..2123ee1f4 100644 --- a/packages/native-core-macro/tests/update_state.rs +++ b/packages/native-core-macro/tests/update_state.rs @@ -1,12 +1,11 @@ use anymap::AnyMap; use dioxus::core::ElementId; -use dioxus::core::{self as dioxus_core, GlobalNodeId}; use dioxus::core::{AttributeValue, DomEdit, Mutations}; use dioxus::core_macro::rsx_without_templates; use dioxus::prelude::*; -use dioxus_native_core::node_ref::*; use dioxus_native_core::real_dom::*; use dioxus_native_core::state::{ChildDepState, NodeDepState, ParentDepState, State}; +use dioxus_native_core::{node_ref::*, RealNodeId}; use dioxus_native_core_macro::State; #[derive(Debug, Clone, Default, State)] @@ -195,10 +194,7 @@ fn state_initial() { ctx.insert(42u32); let _to_rerender = dom.update_state(nodes_updated, ctx); - let root_div = &dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(0), - }]; + let root_div = &dom[RealNodeId::ElementId(ElementId(2))]; assert_eq!(root_div.state.bubbled.0, Some("div".to_string())); assert_eq!( root_div.state.bubbled.1, @@ -218,10 +214,7 @@ fn state_initial() { assert_eq!(root_div.state.node.0, Some("div".to_string())); assert_eq!(root_div.state.node.1, vec![]); - let child_p = &dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(1), - }]; + let child_p = &dom[RealNodeId::UnaccessableId(3)]; assert_eq!(child_p.state.bubbled.0, Some("p".to_string())); assert_eq!(child_p.state.bubbled.1, Vec::new()); assert_eq!(child_p.state.pushed.0, Some("p".to_string())); @@ -241,10 +234,7 @@ fn state_initial() { vec![("color".to_string(), "red".to_string())] ); - let child_h1 = &dom[GlobalNodeId::TemplateId { - template_ref_id: dioxus_core::ElementId(1), - template_node_id: dioxus::prelude::TemplateNodeId(2), - }]; + let child_h1 = &dom[RealNodeId::UnaccessableId(4)]; assert_eq!(child_h1.state.bubbled.0, Some("h1".to_string())); assert_eq!(child_h1.state.bubbled.1, Vec::new()); assert_eq!(child_h1.state.pushed.0, Some("h1".to_string())); @@ -313,7 +303,7 @@ fn state_reduce_parent_called_minimally_on_update() { let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); let nodes_updated = dom.apply_mutations(vec![Mutations { edits: vec![DomEdit::SetAttribute { - root: 1, + root: Some(1), field: "width", value: AttributeValue::Text("99%"), ns: Some("style"), @@ -382,7 +372,7 @@ fn state_reduce_child_called_minimally_on_update() { let _to_rerender = dom.update_state(nodes_updated, AnyMap::new()); let nodes_updated = dom.apply_mutations(vec![Mutations { edits: vec![DomEdit::SetAttribute { - root: 4, + root: Some(4), field: "width", value: AttributeValue::Text("99%"), ns: Some("style"), @@ -395,7 +385,7 @@ fn state_reduce_child_called_minimally_on_update() { dom.traverse_depth_first(|n| { assert_eq!( n.state.part1.child_counter.0, - if let GlobalNodeId::VNodeId(ElementId(id)) = n.node_data.id { + if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id { if id > 4 { 1 } else { @@ -407,7 +397,7 @@ fn state_reduce_child_called_minimally_on_update() { ); assert_eq!( n.state.child_counter.0, - if let GlobalNodeId::VNodeId(ElementId(id)) = n.node_data.id { + if let Some(RealNodeId::ElementId(ElementId(id))) = n.node_data.id { if id > 4 { 1 } else { diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index c9f050741..76270671a 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -19,6 +19,7 @@ taffy = "0.1.0" smallvec = "1.6" rustc-hash = "1.1.0" anymap = "0.12.1" +slab = "0.4" [dev-dependencies] rand = "0.8.5" diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 71172b4bd..bf24096a3 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -1,8 +1,78 @@ +use std::cmp::Ordering; + +use dioxus_core::ElementId; + pub mod layout_attributes; pub mod node_ref; pub mod real_dom; pub mod state; -pub mod template; #[doc(hidden)] pub mod traversable; pub mod utils; + +/// A id for a node that lives in the real dom. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum RealNodeId { + ElementId(ElementId), + UnaccessableId(usize), +} + +impl RealNodeId { + pub fn as_element_id(&self) -> ElementId { + match self { + RealNodeId::ElementId(id) => *id, + RealNodeId::UnaccessableId(_) => panic!("Expected element id"), + } + } + + pub fn as_unaccessable_id(&self) -> usize { + match self { + RealNodeId::ElementId(_) => panic!("Expected unaccessable id"), + RealNodeId::UnaccessableId(id) => *id, + } + } +} + +impl Ord for RealNodeId { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} + +impl PartialOrd for RealNodeId { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Self::ElementId(a), Self::ElementId(b)) => a.0.partial_cmp(&b.0), + (Self::UnaccessableId(a), Self::UnaccessableId(b)) => a.partial_cmp(b), + (Self::ElementId(_), Self::UnaccessableId(_)) => Some(Ordering::Greater), + (Self::UnaccessableId(_), Self::ElementId(_)) => Some(Ordering::Less), + } + } +} + +/// Used in derived state macros +#[derive(Eq, PartialEq)] +#[doc(hidden)] +pub struct HeightOrdering { + pub height: u16, + pub id: RealNodeId, +} + +impl HeightOrdering { + pub fn new(height: u16, id: RealNodeId) -> Self { + HeightOrdering { height, id } + } +} + +// not the ordering after height is just for deduplication it can be any ordering as long as it is consistent +impl Ord for HeightOrdering { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.height.cmp(&other.height).then(self.id.cmp(&other.id)) + } +} + +impl PartialOrd for HeightOrdering { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/packages/native-core/src/node_ref.rs b/packages/native-core/src/node_ref.rs index 4557cbee2..edbcfa696 100644 --- a/packages/native-core/src/node_ref.rs +++ b/packages/native-core/src/node_ref.rs @@ -1,8 +1,7 @@ -use dioxus_core::*; - use crate::{ real_dom::{NodeData, NodeType, OwnedAttributeView}, state::union_ordered_iter, + RealNodeId, }; /// A view into a [VNode] with limited access. @@ -22,8 +21,8 @@ impl<'a> NodeView<'a> { } /// Get the id of the node - pub fn id(&self) -> GlobalNodeId { - self.inner.id + pub fn id(&self) -> RealNodeId { + self.inner.id.unwrap() } /// Get the tag of the node if the tag is enabled in the mask diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index e5dae0da1..20a055778 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -1,18 +1,13 @@ use anymap::AnyMap; +use dioxus_core::{AttributeDiscription, ElementId, Mutations, OwnedAttributeValue, VNode}; use rustc_hash::{FxHashMap, FxHashSet}; +use slab::Slab; use std::ops::{Index, IndexMut}; -use dioxus_core::{ - AttributeDiscription, ElementId, GlobalNodeId, Mutations, OwnedAttributeValue, - RendererTemplateId, TemplateNodeId, VNode, JS_MAX_INT, -}; - use crate::node_ref::{AttributeMask, NodeMask}; use crate::state::State; -use crate::template::{NativeTemplate, TemplateRefOrNode}; use crate::traversable::Traversable; - -pub(crate) type TemplateMapping = FxHashMap>; +use crate::RealNodeId; /// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. /// The render state passes from parent to children and or accumulates state from children to parents. @@ -20,12 +15,13 @@ pub(crate) type TemplateMapping = FxHashMap { root: usize, - nodes: Vec>>>, - nodes_listening: FxHashMap<&'static str, FxHashSet>, - templates: TemplateMapping, - template_stack: smallvec::SmallVec<[ElementId; 5]>, - template_in_progress: Option, - node_stack: smallvec::SmallVec<[GlobalNodeId; 10]>, + nodes: Vec>>>, + // some nodes do not have an ElementId immediately, those node are stored here + internal_nodes: Slab>>, + nodes_listening: FxHashMap<&'static str, FxHashSet>, + last: Option, + // any nodes that have children queued to be added in the form (parent, children remaining) + parents_queued: Vec<(RealNodeId, u32)>, } impl Default for RealDom { @@ -36,26 +32,30 @@ impl Default for RealDom { impl RealDom { pub fn new() -> RealDom { + let mut root = Node::new(NodeType::Element { + tag: "Root".to_string(), + namespace: Some("Root"), + attributes: FxHashMap::default(), + listeners: FxHashSet::default(), + children: Vec::new(), + }); + root.node_data.id = Some(RealNodeId::ElementId(ElementId(0))); + RealDom { root: 0, - nodes: { - let v = vec![Some(Box::new(TemplateRefOrNode::Node(Node::new( - GlobalNodeId::VNodeId(ElementId(0)), - NodeType::Element { - tag: "Root".to_string(), - namespace: Some("Root"), - attributes: FxHashMap::default(), - listeners: FxHashSet::default(), - children: Vec::new(), - }, - ))))]; - v - }, + nodes: vec![Some(Box::new(root))], + internal_nodes: Slab::new(), nodes_listening: FxHashMap::default(), - node_stack: smallvec::SmallVec::new(), - templates: FxHashMap::default(), - template_stack: smallvec::SmallVec::new(), - template_in_progress: None, + last: None, + parents_queued: Vec::new(), + } + } + + pub fn resolve_maybe_id(&self, id: Option) -> RealNodeId { + if let Some(id) = id { + RealNodeId::ElementId(ElementId(id as usize)) + } else { + self.last.unwrap() } } @@ -63,189 +63,147 @@ impl RealDom { pub fn apply_mutations( &mut self, mutations_vec: Vec, - ) -> Vec<(GlobalNodeId, NodeMask)> { + ) -> Vec<(RealNodeId, NodeMask)> { let mut nodes_updated = Vec::new(); - nodes_updated.push((GlobalNodeId::VNodeId(ElementId(0)), NodeMask::ALL)); + nodes_updated.push((RealNodeId::ElementId(ElementId(0)), NodeMask::ALL)); for mutations in mutations_vec { for e in mutations.edits { use dioxus_core::DomEdit::*; match e { - PushRoot { root } => self.node_stack.push(self.decode_id(root)), - AppendChildren { many } => { - let target = if self.node_stack.len() > many as usize { - *self - .node_stack - .get(self.node_stack.len() - (many as usize + 1)) - .unwrap() - } else { - GlobalNodeId::VNodeId(ElementId(0)) - }; - let drained: Vec<_> = self - .node_stack - .drain(self.node_stack.len() - many as usize..) - .collect(); - for id in drained { + AppendChildren { root, children } => { + let target = self.resolve_maybe_id(root); + for id in children { + let id = RealNodeId::ElementId(ElementId(id as usize)); self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); self.link_child(id, target).unwrap(); } } - ReplaceWith { root, m } => { - let id = self.decode_id(root); - let root = self.remove(id).unwrap(); - let target = root.parent().unwrap(); - let drained: Vec<_> = self - .node_stack - .drain(self.node_stack.len() - m as usize..) - .collect(); - for id in drained { + ReplaceWith { root, nodes } => { + let id_to_replace = self.resolve_maybe_id(root); + let target = self[id_to_replace].node_data.parent.unwrap(); + for id in nodes { + let id = RealNodeId::ElementId(ElementId(id as usize)); self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - self.link_child(id, target).unwrap(); + self.link_child_before(id, target, id_to_replace).unwrap(); + } + self.remove(id_to_replace).unwrap(); + } + InsertAfter { root, nodes } => { + let root = self.resolve_maybe_id(root); + let target = self.parent(root).unwrap(); + for id in nodes { + let id = RealNodeId::ElementId(ElementId(id as usize)); + self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); + self.link_child_after(id, target, root).unwrap(); } } - InsertAfter { root, n } => { - let target = self.parent(self.decode_id(root)).unwrap(); - let drained: Vec<_> = self - .node_stack - .drain(self.node_stack.len() - n as usize..) - .collect(); - for id in drained { + InsertBefore { root, nodes } => { + let root = self.resolve_maybe_id(root); + let target = self.parent(root).unwrap(); + for id in nodes { + let id = RealNodeId::ElementId(ElementId(id as usize)); self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - self.link_child(id, target).unwrap(); - } - } - InsertBefore { root, n } => { - let target = self.parent(self.decode_id(root)).unwrap(); - let drained: Vec<_> = self - .node_stack - .drain(self.node_stack.len() - n as usize..) - .collect(); - for id in drained { - self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); - self.link_child(id, target).unwrap(); + self.link_child_before(id, target, root).unwrap(); } } Remove { root } => { - if let Some(parent) = self.parent(self.decode_id(root)) { + let id = self.resolve_maybe_id(root); + if let Some(parent) = self.parent(id) { self.mark_dirty(parent, NodeMask::NONE, &mut nodes_updated); } - let id = self.decode_id(root); self.remove(id).unwrap(); } CreateTextNode { root, text } => { - let root = self.decode_id(root); - let n = Node::new( - root, - NodeType::Text { - text: text.to_string(), - }, - ); - self.insert(n); - self.node_stack.push(root) + let n = Node::new(NodeType::Text { + text: text.to_string(), + }); + let id = self.insert(n, root, &mut nodes_updated); + self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); + if let Some((parent, remaining)) = self.parents_queued.last_mut() { + *remaining -= 1; + let parent = *parent; + if *remaining == 0 { + self.parents_queued.pop(); + } + self.link_child(id, parent).unwrap(); + } + self.last = Some(id); } - CreateTextNodeTemplate { - root, - text, - locally_static: _, - } => { - let root = self.decode_id(root); - let n = Node::new( - root, - NodeType::Text { - text: text.to_string(), - }, - ); - self.current_template_mut().unwrap().insert(n); - self.node_stack.push(root) - } - CreateElement { root, tag } => { - let root = self.decode_id(root); - let n = Node::new( - root, - NodeType::Element { - tag: tag.to_string(), - namespace: None, - attributes: FxHashMap::default(), - listeners: FxHashSet::default(), - children: Vec::new(), - }, - ); - self.insert(n); - self.node_stack.push(root) - } - CreateElementTemplate { + CreateElement { root, tag, - locally_static: _, - fully_static: _, + children, } => { - let root = self.decode_id(root); - let n = Node::new( - root, - NodeType::Element { - tag: tag.to_string(), - namespace: None, - attributes: FxHashMap::default(), - listeners: FxHashSet::default(), - children: Vec::new(), - }, - ); - self.current_template_mut().unwrap().insert(n); - self.node_stack.push(root) + let n = Node::new(NodeType::Element { + tag: tag.to_string(), + namespace: None, + attributes: FxHashMap::default(), + listeners: FxHashSet::default(), + children: Vec::new(), + }); + let id = self.insert(n, root, &mut nodes_updated); + self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); + if let Some((parent, remaining)) = self.parents_queued.last_mut() { + *remaining -= 1; + let parent = *parent; + if *remaining == 0 { + self.parents_queued.pop(); + } + self.link_child(id, parent).unwrap(); + } + self.last = Some(id); + if children > 0 { + self.parents_queued.push((id, children)); + } } - CreateElementNs { root, tag, ns } => { - let root = self.decode_id(root); - let n = Node::new( - root, - NodeType::Element { - tag: tag.to_string(), - namespace: Some(ns), - attributes: FxHashMap::default(), - listeners: FxHashSet::default(), - children: Vec::new(), - }, - ); - self.insert(n); - self.node_stack.push(root) - } - CreateElementNsTemplate { + CreateElementNs { root, tag, ns, - locally_static: _, - fully_static: _, + children, } => { - let root = self.decode_id(root); - let n = Node::new( - root, - NodeType::Element { - tag: tag.to_string(), - namespace: Some(ns), - attributes: FxHashMap::default(), - listeners: FxHashSet::default(), - children: Vec::new(), - }, - ); - self.current_template_mut().unwrap().insert(n); - self.node_stack.push(root) + let n = Node::new(NodeType::Element { + tag: tag.to_string(), + namespace: Some(ns), + attributes: FxHashMap::default(), + listeners: FxHashSet::default(), + children: Vec::new(), + }); + let id = self.insert(n, root, &mut nodes_updated); + self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); + if let Some((parent, remaining)) = self.parents_queued.last_mut() { + *remaining -= 1; + let parent = *parent; + if *remaining == 0 { + self.parents_queued.pop(); + } + self.link_child(id, parent).unwrap(); + } + self.last = Some(id); + if children > 0 { + self.parents_queued.push((id, children)); + } } CreatePlaceholder { root } => { - let root = self.decode_id(root); - let n = Node::new(root, NodeType::Placeholder); - self.insert(n); - self.node_stack.push(root) - } - CreatePlaceholderTemplate { root } => { - let root = self.decode_id(root); - let n = Node::new(root, NodeType::Placeholder); - self.current_template_mut().unwrap().insert(n); - self.node_stack.push(root) + let n = Node::new(NodeType::Placeholder); + let id = self.insert(n, root, &mut nodes_updated); + self.mark_dirty(id, NodeMask::ALL, &mut nodes_updated); + if let Some((parent, remaining)) = self.parents_queued.last_mut() { + *remaining -= 1; + let parent = *parent; + if *remaining == 0 { + self.parents_queued.pop(); + } + self.link_child(id, parent).unwrap(); + } + self.last = Some(id); } NewEventListener { event_name, scope: _, root, } => { - let id = self.decode_id(root); + let id = self.resolve_maybe_id(root); self.mark_dirty(id, NodeMask::new().with_listeners(), &mut nodes_updated); match &mut self[id].node_data.node_type { NodeType::Text { .. } => panic!("Text nodes cannot have listeners"), @@ -263,7 +221,7 @@ impl RealDom { } } RemoveEventListener { root, event } => { - let id = self.decode_id(root); + let id = self.resolve_maybe_id(root); self.mark_dirty(id, NodeMask::new().with_listeners(), &mut nodes_updated); let v = self.nodes_listening.get_mut(event).unwrap(); v.remove(&id); @@ -272,7 +230,7 @@ impl RealDom { root, text: new_text, } => { - let id = self.decode_id(root); + let id = self.resolve_maybe_id(root); self.mark_dirty(id, NodeMask::new().with_text(), &mut nodes_updated); let target = &mut self[id]; match &mut target.node_data.node_type { @@ -288,7 +246,7 @@ impl RealDom { ns, value, } => { - let id = self.decode_id(root); + let id = self.resolve_maybe_id(root); if let NodeType::Element { attributes, .. } = &mut self[id].node_data.node_type { @@ -312,395 +270,237 @@ impl RealDom { RemoveAttribute { root, name: field, .. } => { - let id = self.decode_id(root); + let id = self.resolve_maybe_id(root); self.mark_dirty( id, NodeMask::new_with_attrs(AttributeMask::single(field)), &mut nodes_updated, ); } - PopRoot {} => { - self.node_stack.pop(); + CloneNode { id, new_id } => { + let id = self.resolve_maybe_id(id); + self.clone_node_into(id, &mut nodes_updated, Some(new_id)); } - CreateTemplateRef { id, template_id } => { - let template_id = RendererTemplateId(template_id as usize); - let template = self.templates.get(&template_id).unwrap(); - let nodes = template.nodes.clone(); - let id = ElementId(id as usize); - fn update_refrences( - real_dom: &mut RealDom, - nodes_updated: &mut Vec<(GlobalNodeId, NodeMask)>, - node_id: GlobalNodeId, - template_id: ElementId, - ) { - nodes_updated.push((node_id, NodeMask::ALL)); - let node_id = if let GlobalNodeId::TemplateId { - template_node_id, .. - } = node_id - { - GlobalNodeId::TemplateId { - template_ref_id: template_id, - template_node_id, - } - } else { - node_id - }; - let n = real_dom.get_mut(node_id).unwrap(); - if let GlobalNodeId::TemplateId { - template_node_id, .. - } = n.node_data.id - { - n.node_data.id = GlobalNodeId::TemplateId { - template_ref_id: template_id, - template_node_id, - }; - if let Some(GlobalNodeId::TemplateId { - template_ref_id: ElementId(0), - template_node_id, - }) = n.node_data.parent - { - n.node_data.parent = Some(GlobalNodeId::TemplateId { - template_ref_id: template_id, - template_node_id, - }); - } - } - if let NodeType::Element { children, .. } = &mut n.node_data.node_type { - for c in children.iter_mut() { - if let GlobalNodeId::TemplateId { - template_node_id, .. - } = c - { - *c = GlobalNodeId::TemplateId { - template_ref_id: template_id, - template_node_id: *template_node_id, - }; - } else { - panic!("non-template node in template"); - } - } - for c in children.clone() { - update_refrences(real_dom, nodes_updated, c, template_id); - } + CloneNodeChildren { id, new_ids } => { + let id = self.resolve_maybe_id(id); + let bounded_self: &mut Self = self; + let unbounded_self: &mut Self = + unsafe { std::mem::transmute(bounded_self) }; + if let NodeType::Element { children, .. } = &self[id].node_data.node_type { + for (old_id, new_id) in children.iter().zip(new_ids) { + let child_id = unbounded_self.clone_node_into( + *old_id, + &mut nodes_updated, + Some(new_id), + ); + unbounded_self[child_id].node_data.parent = None; } } - let template = self.templates.get(&template_id).unwrap(); - let roots: Vec<_> = template - .roots - .iter() - .map(|n| GlobalNodeId::TemplateId { - template_ref_id: id, - template_node_id: TemplateNodeId(*n), - }) - .collect(); - let template_ref = TemplateRefOrNode::Ref { - nodes, - roots: roots.clone(), - parent: None, - }; - self.resize_to(id.0); - self.nodes[id.0] = Some(Box::new(template_ref)); - for node_id in roots { - update_refrences(self, &mut nodes_updated, node_id, id); + } + FirstChild {} => { + if let NodeType::Element { children, .. } = + &self[self.last.unwrap()].node_data.node_type + { + self.last = Some(children[0]); + } else { + panic!("tried to call first child on a non element"); } - self.node_stack.push(dioxus_core::GlobalNodeId::VNodeId(id)); } - CreateTemplate { id } => { - let id = RendererTemplateId(id as usize); - self.templates.insert(id, NativeTemplate::default()); - self.template_in_progress = Some(id); + NextSibling {} => { + let id = self.last.unwrap(); + if let Some(parent) = self.parent(id) { + if let NodeType::Element { children, .. } = + &self[parent].node_data.node_type + { + let index = children.iter().position(|a| *a == id).unwrap(); + self.last = Some(children[index + 1]); + } + } else { + panic!("tried to call next sibling on a non element"); + } } - FinishTemplate { len } => { - let len = len as usize; - let roots = self - .node_stack - .drain((self.node_stack.len() - len)..) - .map(|id| { - if let GlobalNodeId::TemplateId { - template_node_id, .. - } = id - { - template_node_id.0 - } else { - panic!("tried to add a non-template node to a template") - } - }) - .collect(); - let current_template = self.current_template_mut(); - current_template.unwrap().roots = roots; - self.template_in_progress = None; + ParentNode {} => { + if let Some(parent) = self.parent(self.last.unwrap()) { + self.last = Some(parent); + } else { + panic!("tried to call parent node on a non element"); + } } - EnterTemplateRef { root } => self.template_stack.push(ElementId(root as usize)), - ExitTemplateRef {} => { - self.template_stack.pop(); + StoreWithId { id } => { + let old_id = self.last.unwrap(); + let node = self.internal_nodes.remove(old_id.as_unaccessable_id()); + let new_id = self.insert(*node, Some(id), &mut nodes_updated); + self.update_id(old_id, new_id, &mut nodes_updated); + } + SetLastNode { id } => { + self.last = + Some(RealNodeId::ElementId(dioxus_core::ElementId(id as usize))); } } } } - debug_assert!(self.template_stack.is_empty()); - debug_assert_eq!(self.template_in_progress, None); - // remove any nodes that were created and then removed in the same mutations from the dirty nodes list - nodes_updated.retain(|n| match &n.0 { - GlobalNodeId::TemplateId { - template_ref_id, - template_node_id, - } => self - .nodes - .get(template_ref_id.0) - .and_then(|o| o.as_ref()) - .and_then(|t| match &**t { - TemplateRefOrNode::Ref { nodes, .. } => { - nodes.get(template_node_id.0).and_then(|o| o.as_ref()) - } - TemplateRefOrNode::Node(_) => None, - }) - .is_some(), - GlobalNodeId::VNodeId(n) => self - .nodes - .get(n.0) - .and_then(|o| o.as_ref()) - .and_then(|n| match &**n { - TemplateRefOrNode::Ref { .. } => None, - TemplateRefOrNode::Node(_) => Some(n), - }) - .is_some(), + nodes_updated.retain(|n| match n.0 { + RealNodeId::ElementId(id) => self.nodes.get(id.0).and_then(|o| o.as_ref()).is_some(), + RealNodeId::UnaccessableId(id) => self.internal_nodes.get(id).is_some(), }); nodes_updated } + /// Update refrences to an old node id to a new node id + fn update_id( + &mut self, + old_id: RealNodeId, + new_id: RealNodeId, + nodes_updated: &mut Vec<(RealNodeId, NodeMask)>, + ) { + // this is safe because a node cannot have itself as a child or parent + let unbouned_self = unsafe { &mut *(self as *mut Self) }; + // update parent's link to child id + if let Some(parent) = self[new_id].node_data.parent { + if let NodeType::Element { children, .. } = &mut self[parent].node_data.node_type { + for c in children { + if *c == old_id { + *c = new_id; + break; + } + } + } + } + // update child's link to parent id + if let NodeType::Element { children, .. } = &self[new_id].node_data.node_type { + for child_id in children { + unbouned_self[*child_id].node_data.parent = Some(new_id); + } + } + // update dirty nodes + for (node, _) in nodes_updated { + if *node == old_id { + *node = new_id; + } + } + // update nodes listening + for v in self.nodes_listening.values_mut() { + if v.contains(&old_id) { + v.remove(&old_id); + v.insert(new_id); + } + } + // update last + if let Some(last) = self.last { + if last == old_id { + self.last = Some(new_id); + } + } + } + fn mark_dirty( &self, - gid: GlobalNodeId, + gid: RealNodeId, mask: NodeMask, - dirty_nodes: &mut Vec<(GlobalNodeId, NodeMask)>, + dirty_nodes: &mut Vec<(RealNodeId, NodeMask)>, ) { - if self.template_in_progress.is_some() { - return; - } - if let GlobalNodeId::VNodeId(id) = gid { - if let TemplateRefOrNode::Ref { roots, .. } = &**self.nodes[id.0].as_ref().unwrap() { - for r in roots { - dirty_nodes.push((*r, mask.clone())); - } - } else { - dirty_nodes.push((gid, mask)); - } - } else { - dirty_nodes.push((gid, mask)); - } - } - - fn current_template_mut(&mut self) -> Option<&mut NativeTemplate> { - self.templates.get_mut(self.template_in_progress.as_ref()?) - } - - fn current_template(&self) -> Option<&NativeTemplate> { - self.templates.get(self.template_in_progress.as_ref()?) + dirty_nodes.push((gid, mask)); } /// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts. pub fn update_state( &mut self, - nodes_updated: Vec<(GlobalNodeId, NodeMask)>, + nodes_updated: Vec<(RealNodeId, NodeMask)>, ctx: AnyMap, - ) -> FxHashSet { + ) -> FxHashSet { let (mut state_tree, node_tree) = self.split(); S::update(&nodes_updated, &mut state_tree, &node_tree, &ctx) } /// Link a child and parent together - fn link_child(&mut self, child_id: GlobalNodeId, parent_id: GlobalNodeId) -> Option<()> { - if let GlobalNodeId::VNodeId(id) = parent_id { - if let TemplateRefOrNode::Ref { .. } = &**self.nodes[id.0].as_ref().unwrap() { - return Some(()); - } - } - let mut created = false; - if let GlobalNodeId::VNodeId(id) = child_id { - #[allow(clippy::transmute_ptr_to_ref)] - let unbounded_self: &mut Self = unsafe { std::mem::transmute(&*self as *const Self) }; - if let TemplateRefOrNode::Ref { roots, .. } = &**self.nodes[id.0].as_mut()? { - // this is safe because we know that no parent will be it's own child - let parent = &mut unbounded_self[parent_id]; - for r in roots { - parent.add_child(*r); - } - created = true; - } - } + fn link_child(&mut self, child_id: RealNodeId, parent_id: RealNodeId) -> Option<()> { let parent = &mut self[parent_id]; - if !created { - parent.add_child(child_id); - } - let parent_height = parent.node_data.height + 1; - match child_id { - GlobalNodeId::VNodeId(id) => { - match &mut **self.nodes.get_mut(id.0).unwrap().as_mut().unwrap() { - TemplateRefOrNode::Ref { roots, parent, .. } => { - *parent = Some(parent_id); - for r in roots.clone() { - self[r].node_data.parent = Some(parent_id); - } - } - TemplateRefOrNode::Node(n) => n.node_data.parent = Some(parent_id), - } - } - GlobalNodeId::TemplateId { - template_ref_id, - template_node_id, - } => { - let n = if let Some(template) = self.current_template_mut() { - &mut **template.nodes[template_node_id.0].as_mut().unwrap() - } else { - let nodes = match self - .nodes - .get_mut(template_ref_id.0) - .unwrap() - .as_mut() - .unwrap() - .as_mut() - { - TemplateRefOrNode::Ref { nodes, .. } => nodes, - TemplateRefOrNode::Node(_) => panic!("Expected template ref"), - }; - nodes - .get_mut(template_node_id.0) - .and_then(|n| n.as_mut()) - .map(|n| n.as_mut()) - .unwrap() - }; + parent.add_child(child_id); + let parent_height = parent.node_data.height; + self[child_id].set_parent(parent_id); + self.set_height(child_id, parent_height + 1); - n.set_parent(parent_id); - } + Some(()) + } + + /// Link a child and parent together with the child inserted before a marker + fn link_child_before( + &mut self, + child_id: RealNodeId, + parent_id: RealNodeId, + marker: RealNodeId, + ) -> Option<()> { + let parent = &mut self[parent_id]; + if let NodeType::Element { children, .. } = &mut parent.node_data.node_type { + let index = children.iter().position(|a| *a == marker)?; + children.insert(index, child_id); } - self.set_height(child_id, parent_height); + let parent_height = parent.node_data.height; + self[child_id].set_parent(parent_id); + self.set_height(child_id, parent_height + 1); + + Some(()) + } + + /// Link a child and parent together with the child inserted after a marker + fn link_child_after( + &mut self, + child_id: RealNodeId, + parent_id: RealNodeId, + marker: RealNodeId, + ) -> Option<()> { + let parent = &mut self[parent_id]; + if let NodeType::Element { children, .. } = &mut parent.node_data.node_type { + let index = children.iter().position(|a| *a == marker)?; + children.insert(index + 1, child_id); + } + let parent_height = parent.node_data.height; + self[child_id].set_parent(parent_id); + self.set_height(child_id, parent_height + 1); Some(()) } /// Recursively increase the height of a node and its children - fn set_height(&mut self, id: GlobalNodeId, height: u16) { - match id { - GlobalNodeId::VNodeId(id) => { - let n = &mut **self.nodes.get_mut(id.0).unwrap().as_mut().unwrap(); - match n { - TemplateRefOrNode::Ref { roots, .. } => { - for root in roots.clone() { - self.set_height(root, height); - } - } - TemplateRefOrNode::Node(n) => { - n.node_data.height = height; - if let NodeType::Element { children, .. } = &n.node_data.node_type { - for c in children.clone() { - self.set_height(c, height + 1); - } - } - } - } - } - GlobalNodeId::TemplateId { - template_ref_id, - template_node_id, - } => { - let n = if let Some(template) = self.current_template_mut() { - &mut **template.nodes[template_node_id.0].as_mut().unwrap() - } else { - let nodes = match self - .nodes - .get_mut(template_ref_id.0) - .unwrap() - .as_mut() - .unwrap() - .as_mut() - { - TemplateRefOrNode::Ref { nodes, .. } => nodes, - TemplateRefOrNode::Node(_) => panic!("Expected template ref"), - }; - nodes - .get_mut(template_node_id.0) - .and_then(|n| n.as_mut()) - .map(|n| n.as_mut()) - .unwrap() - }; - - n.node_data.height = height; - if let NodeType::Element { children, .. } = &n.node_data.node_type { - for c in children.clone() { - self.set_height(c, height + 1); - } - } + fn set_height(&mut self, id: RealNodeId, height: u16) { + let node = &mut self[id]; + node.node_data.height = height; + if let NodeType::Element { children, .. } = &node.node_data.node_type { + for c in children.clone() { + self.set_height(c, height + 1); } } } // remove a node and it's children from the dom. - fn remove(&mut self, id: GlobalNodeId) -> Option> { + fn remove(&mut self, id: RealNodeId) -> Option> { // We do not need to remove the node from the parent's children list for children. - fn inner(dom: &mut RealDom, id: GlobalNodeId) -> Option> { - for nodes_listeners in dom.nodes_listening.values_mut() { - nodes_listeners.remove(&id); - } - let mut either = match id { - GlobalNodeId::VNodeId(id) => *dom.nodes[id.0].take()?, - GlobalNodeId::TemplateId { - template_ref_id, - template_node_id, - } => { - let template_ref = &mut dom.nodes[template_ref_id.0].as_mut().unwrap(); - if let TemplateRefOrNode::Ref { nodes, roots, .. } = template_ref.as_mut() { - roots.retain(|r| *r != id); - TemplateRefOrNode::Node(*nodes[template_node_id.0].take().unwrap()) - } else { - unreachable!() - } - } + fn inner(dom: &mut RealDom, id: RealNodeId) -> Option> { + let mut node = match id { + RealNodeId::ElementId(id) => *dom.nodes[id.0].take()?, + RealNodeId::UnaccessableId(id) => *dom.internal_nodes.remove(id), }; - match &mut either { - TemplateRefOrNode::Node(node) => { - if let NodeType::Element { children, .. } = &mut node.node_data.node_type { - for c in children { - inner(dom, *c); - } - } - Some(either) + if let NodeType::Element { children, .. } = &mut node.node_data.node_type { + for c in children { + inner(dom, *c); } - TemplateRefOrNode::Ref { .. } => Some(either), } + Some(node) } let mut node = match id { - GlobalNodeId::VNodeId(id) => *self.nodes[id.0].take()?, - GlobalNodeId::TemplateId { - template_ref_id, - template_node_id, - } => { - let template_ref = &mut self.nodes[template_ref_id.0].as_mut().unwrap(); - if let TemplateRefOrNode::Ref { nodes, roots, .. } = template_ref.as_mut() { - roots.retain(|r| *r != id); - TemplateRefOrNode::Node(*nodes[template_node_id.0].take().unwrap()) - } else { - unreachable!() - } - } + RealNodeId::ElementId(id) => *self.nodes[id.0].take()?, + RealNodeId::UnaccessableId(id) => *self.internal_nodes.remove(id), }; - for nodes_listeners in self.nodes_listening.values_mut() { - nodes_listeners.remove(&id); - } - if let Some(parent) = node.parent() { + if let Some(parent) = node.node_data.parent { let parent = &mut self[parent]; parent.remove_child(id); } - match &mut node { - TemplateRefOrNode::Ref { .. } => {} - TemplateRefOrNode::Node(node) => { - if let NodeType::Element { children, .. } = &mut node.node_data.node_type { - for c in children { - inner(self, *c)?; - } - } + if let NodeType::Element { children, .. } = &mut node.node_data.node_type { + for c in children { + inner(self, *c)?; } } Some(node) @@ -713,12 +513,38 @@ impl RealDom { } } - fn insert(&mut self, node: Node) { - match node.node_data.id { - GlobalNodeId::TemplateId { .. } => panic!("cannot insert into template"), - GlobalNodeId::VNodeId(id) => { - self.resize_to(id.0); - self.nodes[id.0] = Some(Box::new(TemplateRefOrNode::Node(node))); + fn insert( + &mut self, + mut node: Node, + id: Option, + nodes_updated: &mut Vec<(RealNodeId, NodeMask)>, + ) -> RealNodeId { + match id { + Some(id) => { + let id = id as usize; + self.resize_to(id); + let real_id = RealNodeId::ElementId(ElementId(id)); + node.node_data.id = Some(real_id); + // move the old node to a new unaccessable id + if let Some(mut old) = self.nodes[id].take() { + let old_id = old.node_data.id.unwrap(); + let entry = self.internal_nodes.vacant_entry(); + let id = entry.key(); + let new_id = RealNodeId::UnaccessableId(id); + old.node_data.id = Some(real_id); + entry.insert(old); + self.update_id(old_id, new_id, nodes_updated); + } + self.nodes[id] = Some(Box::new(node)); + real_id + } + None => { + let entry = self.internal_nodes.vacant_entry(); + let id = entry.key(); + let real_id = RealNodeId::UnaccessableId(id); + node.node_data.id = Some(real_id); + entry.insert(Box::new(node)); + real_id } } } @@ -759,15 +585,15 @@ impl RealDom { .zip( e.children .iter() - .map(|c| GlobalNodeId::VNodeId(c.mounted_id())), + .map(|c| RealNodeId::ElementId(c.mounted_id())), ) .all(|(c1, c2)| *c1 == c2) && e.children.iter().all(|c| { self.contains_node(c) - && self[GlobalNodeId::VNodeId(c.mounted_id())] + && self[RealNodeId::ElementId(c.mounted_id())] .node_data .parent - == e.id.get().map(GlobalNodeId::VNodeId) + == e.id.get().map(RealNodeId::ElementId) }) && attributes .iter() @@ -788,7 +614,7 @@ impl RealDom { VNode::Placeholder(_) => true, VNode::Text(t) => { if let Some(id) = t.id.get() { - let dom_node = &self[GlobalNodeId::VNodeId(id)]; + let dom_node = &self[RealNodeId::ElementId(id)]; match &dom_node.node_data.node_type { NodeType::Text { text } => t.text == text, _ => false, @@ -814,7 +640,7 @@ impl RealDom { /// Call a function for each node in the dom, depth first. pub fn traverse_depth_first(&self, mut f: impl FnMut(&Node)) { - fn inner(dom: &RealDom, id: GlobalNodeId, f: &mut impl FnMut(&Node)) { + fn inner(dom: &RealDom, id: RealNodeId, f: &mut impl FnMut(&Node)) { let node = &dom[id]; f(node); if let NodeType::Element { children, .. } = &node.node_data.node_type { @@ -824,7 +650,7 @@ impl RealDom { } } if let NodeType::Element { children, .. } = &self - [GlobalNodeId::VNodeId(ElementId(self.root))] + [RealNodeId::ElementId(ElementId(self.root))] .node_data .node_type { @@ -836,11 +662,7 @@ impl RealDom { /// Call a function for each node in the dom, depth first. pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(&mut Node)) { - fn inner( - dom: &mut RealDom, - id: GlobalNodeId, - f: &mut impl FnMut(&mut Node), - ) { + fn inner(dom: &mut RealDom, id: RealNodeId, f: &mut impl FnMut(&mut Node)) { let node = &mut dom[id]; f(node); if let NodeType::Element { children, .. } = &mut node.node_data.node_type { @@ -851,7 +673,7 @@ impl RealDom { } let root = self.root; if let NodeType::Element { children, .. } = &mut self - [GlobalNodeId::VNodeId(ElementId(root))] + [RealNodeId::ElementId(ElementId(root))] .node_data .node_type { @@ -861,33 +683,11 @@ impl RealDom { } } - pub fn decode_id(&self, id: impl Into) -> GlobalNodeId { - let mut id = id.into(); - if id >= JS_MAX_INT / 2 { - id -= JS_MAX_INT / 2; - if self.current_template().is_some() { - GlobalNodeId::TemplateId { - template_ref_id: ElementId(0), - template_node_id: TemplateNodeId(id as usize), - } - } else { - let template_ref_id = *self.template_stack.last().unwrap(); - let template_node_id = TemplateNodeId(id as usize); - GlobalNodeId::TemplateId { - template_ref_id, - template_node_id, - } - } - } else { - GlobalNodeId::VNodeId(ElementId(id as usize)) - } - } - pub fn split( &mut self, ) -> ( - impl Traversable + '_, - impl Traversable + '_, + impl Traversable + '_, + impl Traversable + '_, ) { let raw = self as *mut Self; // this is safe beacuse the traversable trait does not allow mutation of the position of elements, and within elements the access is disjoint. @@ -896,64 +696,61 @@ impl RealDom { unsafe { &mut *raw }.map(|n| &n.node_data, |n| &mut n.node_data), ) } + + /// Recurively clones a node and marks it and it's children as dirty. + fn clone_node_into( + &mut self, + id: RealNodeId, + nodes_updated: &mut Vec<(RealNodeId, NodeMask)>, + new_id: Option, + ) -> RealNodeId { + let new_id = self.insert(self[id].clone(), new_id, nodes_updated); + nodes_updated.push((new_id, NodeMask::ALL)); + // this is safe because no node has itself as a child. + let unbounded_self = unsafe { &mut *(self as *mut Self) }; + let mut node = &mut self[new_id]; + node.node_data.height = 0; + if let NodeType::Element { children, .. } = &mut node.node_data.node_type { + for c in children { + let child_id = unbounded_self.clone_node_into(*c, nodes_updated, None); + *c = child_id; + let parent_height = node.node_data.height; + unbounded_self[child_id].set_parent(new_id); + unbounded_self.set_height(child_id, parent_height + 1); + } + } + new_id + } } impl Index for RealDom { type Output = Node; fn index(&self, idx: ElementId) -> &Self::Output { - self.get(GlobalNodeId::VNodeId(idx)).unwrap() + self.get(RealNodeId::ElementId(idx)).unwrap() } } -impl Index for RealDom { +impl Index for RealDom { type Output = Node; - fn index(&self, idx: GlobalNodeId) -> &Self::Output { + fn index(&self, idx: RealNodeId) -> &Self::Output { self.get(idx).unwrap() } } -impl Index for RealDom { - type Output = Node; - - fn index(&self, idx: usize) -> &Self::Output { - if let Some(template) = self.current_template() { - template.nodes[idx].as_ref().unwrap() - } else { - &self[GlobalNodeId::VNodeId(dioxus_core::ElementId(idx))] - } - } -} - impl IndexMut for RealDom { fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output { - self.get_mut(GlobalNodeId::VNodeId(idx)).unwrap() + self.get_mut(RealNodeId::ElementId(idx)).unwrap() } } -impl IndexMut for RealDom { - fn index_mut(&mut self, idx: GlobalNodeId) -> &mut Self::Output { +impl IndexMut for RealDom { + fn index_mut(&mut self, idx: RealNodeId) -> &mut Self::Output { self.get_mut(idx).unwrap() } } -impl IndexMut for RealDom { - fn index_mut(&mut self, idx: usize) -> &mut Self::Output { - if self.template_stack.is_empty() { - &mut self[GlobalNodeId::VNodeId(dioxus_core::ElementId(idx))] - } else { - self.current_template_mut() - .unwrap() - .nodes - .get_mut(idx) - .unwrap() - .as_mut() - .unwrap() - } - } -} - /// The node is stored client side and stores only basic data about the node. #[derive(Debug, Clone)] pub struct Node { @@ -966,9 +763,9 @@ pub struct Node { #[derive(Debug, Clone)] pub struct NodeData { /// The id of the node this node was created from. - pub id: GlobalNodeId, + pub id: Option, /// The parent id of the node. - pub parent: Option, + pub parent: Option, /// Additional inforation specific to the node type pub node_type: NodeType, /// The number of parents before the root node. The root node has height 1. @@ -986,17 +783,17 @@ pub enum NodeType { namespace: Option<&'static str>, attributes: FxHashMap, listeners: FxHashSet, - children: Vec, + children: Vec, }, Placeholder, } impl Node { - fn new(id: GlobalNodeId, node_type: NodeType) -> Self { + fn new(node_type: NodeType) -> Self { Node { state: S::default(), node_data: NodeData { - id, + id: None, parent: None, node_type, height: 0, @@ -1005,27 +802,32 @@ impl Node { } /// link a child node - fn add_child(&mut self, child: GlobalNodeId) { + fn add_child(&mut self, child: RealNodeId) { if let NodeType::Element { children, .. } = &mut self.node_data.node_type { children.push(child); } } /// remove a child node - fn remove_child(&mut self, child: GlobalNodeId) { + fn remove_child(&mut self, child: RealNodeId) { if let NodeType::Element { children, .. } = &mut self.node_data.node_type { children.retain(|c| c != &child); } } /// link the parent node - fn set_parent(&mut self, parent: GlobalNodeId) { + fn set_parent(&mut self, parent: RealNodeId) { self.node_data.parent = Some(parent); } + + /// get the mounted id of the node + pub fn mounted_id(&self) -> RealNodeId { + self.node_data.id.unwrap() + } } impl Traversable for RealDom { - type Id = GlobalNodeId; + type Id = RealNodeId; type Node = Node; fn height(&self, id: Self::Id) -> Option { @@ -1035,63 +837,20 @@ impl Traversable for RealDom { fn get(&self, id: Self::Id) -> Option<&Self::Node> { match id { - GlobalNodeId::VNodeId(id) => match self.nodes.get(id.0)?.as_ref()?.as_ref() { - TemplateRefOrNode::Ref { .. } => panic!("Template nodes should not be indexable"), - TemplateRefOrNode::Node(n) => Some(n), - }, - GlobalNodeId::TemplateId { - template_ref_id, - template_node_id, - } => { - if self.template_in_progress.is_some() { - let template = self.current_template().unwrap(); - template.nodes[template_node_id.0] - .as_ref() - .map(|n| n.as_ref()) - } else { - let nodes = match self.nodes.get(template_ref_id.0)?.as_ref()?.as_ref() { - TemplateRefOrNode::Ref { nodes, .. } => nodes, - TemplateRefOrNode::Node(_) => { - panic!("Expected template ref") - } - }; - - nodes - .get(template_node_id.0) - .and_then(|n| n.as_ref()) - .map(|n| n.as_ref()) - } + RealNodeId::ElementId(id) => { + self.nodes.get(id.0).and_then(|b| b.as_ref().map(|b| &**b)) } + RealNodeId::UnaccessableId(id) => self.internal_nodes.get(id).map(|b| &**b), } } fn get_mut(&mut self, id: Self::Id) -> Option<&mut Self::Node> { match id { - GlobalNodeId::VNodeId(id) => match self.nodes.get_mut(id.0)?.as_mut()?.as_mut() { - TemplateRefOrNode::Ref { .. } => panic!("Template nodes should not be indexable"), - TemplateRefOrNode::Node(n) => Some(n), - }, - GlobalNodeId::TemplateId { - template_ref_id, - template_node_id, - } => { - if self.template_in_progress.is_some() { - let template = self.current_template_mut().unwrap(); - template.nodes[template_node_id.0] - .as_mut() - .map(|n| n.as_mut()) - } else { - let nodes = match self.nodes.get_mut(template_ref_id.0)?.as_mut()?.as_mut() { - TemplateRefOrNode::Ref { nodes, .. } => nodes, - TemplateRefOrNode::Node(_) => panic!("Expected template ref"), - }; - - nodes - .get_mut(template_node_id.0) - .and_then(|n| n.as_mut()) - .map(|n| n.as_mut()) - } - } + RealNodeId::ElementId(id) => self + .nodes + .get_mut(id.0) + .and_then(|b| b.as_mut().map(|b| &mut **b)), + RealNodeId::UnaccessableId(id) => self.internal_nodes.get_mut(id).map(|b| &mut **b), } } @@ -1107,29 +866,7 @@ impl Traversable for RealDom { } fn parent(&self, id: Self::Id) -> Option { - match id { - GlobalNodeId::VNodeId(id) => self.nodes.get(id.0).as_ref()?.as_ref()?.parent(), - GlobalNodeId::TemplateId { - template_ref_id, - template_node_id, - } => { - if self.template_in_progress.is_some() { - let template = self.current_template().unwrap(); - template.nodes[template_node_id.0] - .as_ref() - .map(|n| n.as_ref()) - } else { - let nodes = match self.nodes.get(template_ref_id.0)?.as_ref()?.as_ref() { - TemplateRefOrNode::Ref { nodes, .. } => nodes, - TemplateRefOrNode::Node(_) => panic!("Expected template ref"), - }; - - nodes.get(template_node_id.0).and_then(|n| n.as_deref()) - }? - .node_data - .parent - } - } + self.get(id).and_then(|n| n.node_data.parent) } } diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index e0132872a..af84410cd 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -1,12 +1,11 @@ use std::{cmp::Ordering, fmt::Debug}; -use anymap::AnyMap; -use dioxus_core::GlobalNodeId; -use rustc_hash::FxHashSet; - use crate::node_ref::{NodeMask, NodeView}; use crate::real_dom::NodeData; use crate::traversable::Traversable; +use crate::RealNodeId; +use anymap::AnyMap; +use rustc_hash::FxHashSet; /// Join two sorted iterators pub(crate) fn union_ordered_iter( @@ -209,14 +208,14 @@ pub trait State: Default + Clone { #[doc(hidden)] fn update< 'a, - T: Traversable, - T2: Traversable, + T: Traversable, + T2: Traversable, >( - dirty: &[(GlobalNodeId, NodeMask)], + dirty: &[(RealNodeId, NodeMask)], state_tree: &'a mut T, rdom: &'a T2, ctx: &AnyMap, - ) -> FxHashSet; + ) -> FxHashSet; } impl ChildDepState for () { diff --git a/packages/native-core/src/template.rs b/packages/native-core/src/template.rs deleted file mode 100644 index ecd036d92..000000000 --- a/packages/native-core/src/template.rs +++ /dev/null @@ -1,44 +0,0 @@ -use dioxus_core::{GlobalNodeId, TemplateNodeId}; - -use crate::{real_dom::Node, state::State}; - -#[derive(Debug, Default)] -pub struct NativeTemplate { - pub(crate) nodes: Vec>>>, - pub(crate) roots: Vec, -} - -impl NativeTemplate { - pub fn insert(&mut self, node: Node) { - let id = node.node_data.id; - match id { - GlobalNodeId::TemplateId { - template_node_id: TemplateNodeId(id), - .. - } => { - self.nodes.resize(id + 1, None); - self.nodes[id] = Some(Box::new(node)); - } - GlobalNodeId::VNodeId(_) => panic!("Cannot insert a VNode into a template"), - } - } -} - -#[derive(Debug)] -pub(crate) enum TemplateRefOrNode { - Ref { - nodes: Vec>>>, - roots: Vec, - parent: Option, - }, - Node(Node), -} - -impl TemplateRefOrNode { - pub fn parent(&self) -> Option { - match self { - TemplateRefOrNode::Ref { parent, .. } => *parent, - TemplateRefOrNode::Node(node) => node.node_data.parent, - } - } -} diff --git a/packages/native-core/src/utils.rs b/packages/native-core/src/utils.rs index 4d378dbb1..aa61807a4 100644 --- a/packages/native-core/src/utils.rs +++ b/packages/native-core/src/utils.rs @@ -1,17 +1,18 @@ use crate::{ real_dom::{NodeType, RealDom}, state::State, + RealNodeId, }; -use dioxus_core::{DomEdit, ElementId, GlobalNodeId, Mutations}; +use dioxus_core::{DomEdit, ElementId, Mutations}; pub enum ElementProduced { /// The iterator produced an element by progressing to the next node in a depth first order. - Progressed(GlobalNodeId), + Progressed(RealNodeId), /// The iterator reached the end of the tree and looped back to the root - Looped(GlobalNodeId), + Looped(RealNodeId), } impl ElementProduced { - pub fn id(&self) -> GlobalNodeId { + pub fn id(&self) -> RealNodeId { match self { ElementProduced::Progressed(id) => *id, ElementProduced::Looped(id) => *id, @@ -50,16 +51,13 @@ impl NodePosition { /// The iterator loops around when it reaches the end or the beginning. pub struct PersistantElementIter { // stack of elements and fragments - stack: smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>, + stack: smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, } impl Default for PersistantElementIter { fn default() -> Self { PersistantElementIter { - stack: smallvec::smallvec![( - GlobalNodeId::VNodeId(dioxus_core::ElementId(0)), - NodePosition::AtNode - )], + stack: smallvec::smallvec![(RealNodeId::ElementId(ElementId(0)), NodePosition::AtNode)], } } } @@ -79,7 +77,7 @@ impl PersistantElementIter { .filter_map(|e| { // nodes within templates will never be removed if let DomEdit::Remove { root } = e { - let id = rdom.decode_id(*root); + let id = rdom.resolve_maybe_id(*root); Some(id) } else { None @@ -102,21 +100,23 @@ impl PersistantElementIter { for m in &mutations.edits { match m { DomEdit::Remove { root } => { - let id = rdom.decode_id(*root); + let id = rdom.resolve_maybe_id(*root); if children.iter().take(*child_idx + 1).any(|c| *c == id) { *child_idx -= 1; } } - DomEdit::InsertBefore { root, n } => { - let id = rdom.decode_id(*root); + DomEdit::InsertBefore { root, nodes } => { + let id = rdom.resolve_maybe_id(*root); + let n = nodes.len(); if children.iter().take(*child_idx + 1).any(|c| *c == id) { - *child_idx += *n as usize; + *child_idx += n as usize; } } - DomEdit::InsertAfter { root, n } => { - let id = rdom.decode_id(*root); + DomEdit::InsertAfter { root, nodes } => { + let id = rdom.resolve_maybe_id(*root); + let n = nodes.len(); if children.iter().take(*child_idx).any(|c| *c == id) { - *child_idx += *n as usize; + *child_idx += n as usize; } } _ => (), @@ -131,7 +131,7 @@ impl PersistantElementIter { /// get the next element pub fn next(&mut self, rdom: &RealDom) -> ElementProduced { if self.stack.is_empty() { - let id = GlobalNodeId::VNodeId(ElementId(0)); + let id = RealNodeId::ElementId(ElementId(0)); let new = (id, NodePosition::AtNode); self.stack.push(new); ElementProduced::Looped(id) @@ -167,10 +167,10 @@ impl PersistantElementIter { pub fn prev(&mut self, rdom: &RealDom) -> ElementProduced { // recursively add the last child element to the stack fn push_back( - stack: &mut smallvec::SmallVec<[(GlobalNodeId, NodePosition); 5]>, - new_node: GlobalNodeId, + stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>, + new_node: RealNodeId, rdom: &RealDom, - ) -> GlobalNodeId { + ) -> RealNodeId { match &rdom[new_node].node_data.node_type { NodeType::Element { children, .. } => { if children.is_empty() { @@ -184,7 +184,7 @@ impl PersistantElementIter { } } if self.stack.is_empty() { - let new_node = GlobalNodeId::VNodeId(ElementId(0)); + let new_node = RealNodeId::ElementId(ElementId(0)); ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom)) } else { let (last, o_child_idx) = self.stack.last_mut().unwrap(); @@ -223,7 +223,7 @@ impl PersistantElementIter { } } - fn pop(&mut self) -> GlobalNodeId { + fn pop(&mut self) -> RealNodeId { self.stack.pop().unwrap().0 } } diff --git a/packages/rsx/src/template.rs b/packages/rsx/src/template.rs index 4e3c742a3..693f29b05 100644 --- a/packages/rsx/src/template.rs +++ b/packages/rsx/src/template.rs @@ -1,5 +1,6 @@ use dioxus_core::{ - OwnedAttributeValue, TemplateAttributeValue, TemplateNodeId, TextTemplate, TextTemplateSegment, + OwnedAttributeValue, OwnedPathSeg, OwnedTraverse, TemplateAttributeValue, TemplateNodeId, + TextTemplate, TextTemplateSegment, UpdateOp, }; use proc_macro2::TokenStream; use quote::TokenStreamExt; @@ -54,7 +55,7 @@ struct TemplateElementBuilder { attributes: Vec, children: Vec, listeners: Vec, - parent: Option, + locally_static: bool, } #[cfg(any(feature = "hot-reload", debug_assertions))] @@ -73,7 +74,7 @@ impl TemplateElementBuilder { attributes, children, listeners, - parent, + .. } = self; let (element_tag, element_ns) = element_to_static_str(&tag.to_string()).ok_or_else(|| { @@ -94,7 +95,6 @@ impl TemplateElementBuilder { owned_attributes, children, listeners, - parent, )) } } @@ -106,19 +106,12 @@ impl ToTokens for TemplateElementBuilder { attributes, children, listeners, - parent, + .. } = self; let children = children.iter().map(|id| { let raw = id.0; quote! {TemplateNodeId(#raw)} }); - let parent = match parent { - Some(id) => { - let raw = id.0; - quote! {Some(TemplateNodeId(#raw))} - } - None => quote! {None}, - }; tokens.append_all(quote! { TemplateElement::new( dioxus_elements::#tag::TAG_NAME, @@ -126,7 +119,6 @@ impl ToTokens for TemplateElementBuilder { &[#(#attributes),*], &[#(#children),*], &[#(#listeners),*], - #parent, ) }) } @@ -279,12 +271,18 @@ impl ToTokens for TemplateNodeTypeBuilder { TemplateNodeType::Element(#el) }), TemplateNodeTypeBuilder::Text(txt) => { + let mut length = 0; + let segments = txt.segments.iter().map(|seg| match seg { - TextTemplateSegment::Static(s) => quote!(TextTemplateSegment::Static(#s)), + TextTemplateSegment::Static(s) => { + length += s.len(); + quote!(TextTemplateSegment::Static(#s)) + } TextTemplateSegment::Dynamic(idx) => quote!(TextTemplateSegment::Dynamic(#idx)), }); + tokens.append_all(quote! { - TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*])) + TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*], #length)) }); } TemplateNodeTypeBuilder::DynamicNode(idx) => tokens.append_all(quote! { @@ -296,62 +294,54 @@ impl ToTokens for TemplateNodeTypeBuilder { struct TemplateNodeBuilder { id: TemplateNodeId, + depth: usize, + parent: Option, node_type: TemplateNodeTypeBuilder, + fully_static: bool, } impl TemplateNodeBuilder { #[cfg(any(feature = "hot-reload", debug_assertions))] fn try_into_owned(self, location: &OwnedCodeLocation) -> Result { - let TemplateNodeBuilder { id, node_type } = self; + let TemplateNodeBuilder { + id, + node_type, + parent, + depth, + .. + } = self; let node_type = node_type.try_into_owned(location)?; Ok(OwnedTemplateNode { id, node_type, - locally_static: false, - fully_static: false, + parent, + depth, }) } - fn is_fully_static(&self, nodes: &Vec) -> bool { - self.is_locally_static() - && match &self.node_type { - TemplateNodeTypeBuilder::Element(el) => el - .children - .iter() - .all(|child| nodes[child.0].is_fully_static(nodes)), - TemplateNodeTypeBuilder::Text(_) => true, - TemplateNodeTypeBuilder::DynamicNode(_) => unreachable!(), - } - } - - fn is_locally_static(&self) -> bool { - match &self.node_type { - TemplateNodeTypeBuilder::Element(el) => { - el.attributes.iter().all(|attr| match &attr.value { - TemplateAttributeValue::Static(_) => true, - TemplateAttributeValue::Dynamic(_) => false, - }) && el.listeners.is_empty() - } - TemplateNodeTypeBuilder::Text(txt) => txt.segments.iter().all(|seg| match seg { - TextTemplateSegment::Static(_) => true, - TextTemplateSegment::Dynamic(_) => false, - }), - TemplateNodeTypeBuilder::DynamicNode(_) => false, - } - } - - fn to_tokens(&self, tokens: &mut TokenStream, nodes: &Vec) { - let Self { id, node_type } = self; + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + id, + node_type, + parent, + depth, + .. + } = self; let raw_id = id.0; - let fully_static = self.is_fully_static(nodes); - let locally_static = self.is_locally_static(); + let parent = match parent { + Some(id) => { + let id = id.0; + quote! {Some(TemplateNodeId(#id))} + } + None => quote! {None}, + }; tokens.append_all(quote! { TemplateNode { id: TemplateNodeId(#raw_id), node_type: #node_type, - locally_static: #locally_static, - fully_static: #fully_static, + parent: #parent, + depth: #depth, } }) } @@ -369,8 +359,8 @@ impl TemplateBuilder { pub fn from_roots(roots: Vec) -> Option { let mut builder = Self::default(); - for root in roots { - let id = builder.build_node(root, None); + for (i, root) in roots.into_iter().enumerate() { + let id = builder.build_node(root, None, vec![i], 0); builder.root_nodes.push(id); } @@ -391,18 +381,25 @@ impl TemplateBuilder { fn from_roots_always(roots: Vec) -> Self { let mut builder = Self::default(); - for root in roots { - let id = builder.build_node(root, None); + for (i, root) in roots.into_iter().enumerate() { + let id = builder.build_node(root, None, vec![i], 0); builder.root_nodes.push(id); } builder } - fn build_node(&mut self, node: BodyNode, parent: Option) -> TemplateNodeId { + fn build_node( + &mut self, + node: BodyNode, + parent: Option, + path: Vec, + depth: usize, + ) -> TemplateNodeId { let id = TemplateNodeId(self.nodes.len()); match node { BodyNode::Element(el) => { + let mut locally_static = true; let mut attributes = Vec::new(); let mut listeners = Vec::new(); for attr in el.attributes { @@ -417,6 +414,7 @@ impl TemplateBuilder { ), }) } else { + locally_static = false; attributes.push(TemplateAttributeBuilder { element_tag: el.name.clone(), name: AttributeName::Ident(name), @@ -436,6 +434,7 @@ impl TemplateBuilder { ), }) } else { + locally_static = false; attributes.push(TemplateAttributeBuilder { element_tag: el.name.clone(), name: AttributeName::Str(name), @@ -446,6 +445,7 @@ impl TemplateBuilder { } } ElementAttr::AttrExpression { name, value } => { + locally_static = false; attributes.push(TemplateAttributeBuilder { element_tag: el.name.clone(), name: AttributeName::Ident(name), @@ -455,6 +455,7 @@ impl TemplateBuilder { }) } ElementAttr::CustomAttrExpression { name, value } => { + locally_static = false; attributes.push(TemplateAttributeBuilder { element_tag: el.name.clone(), name: AttributeName::Str(name), @@ -464,6 +465,7 @@ impl TemplateBuilder { }) } ElementAttr::EventTokens { name, tokens } => { + locally_static = false; listeners.push(self.dynamic_context.add_listener(name, tokens)) } } @@ -481,16 +483,25 @@ impl TemplateBuilder { attributes, children: Vec::new(), listeners, - parent, + locally_static, }), + parent, + depth, + fully_static: false, }); - let children: Vec<_> = el .children .into_iter() - .map(|child| self.build_node(child, Some(id))) + .enumerate() + .map(|(i, child)| { + let mut new_path = path.clone(); + new_path.push(i); + self.build_node(child, Some(id), new_path, depth + 1) + }) .collect(); + let children_fully_static = children.iter().all(|c| self.nodes[c.0].fully_static); let parent = &mut self.nodes[id.0]; + parent.fully_static = locally_static && children_fully_static; if let TemplateNodeTypeBuilder::Element(element) = &mut parent.node_type { element.children = children; } @@ -502,16 +513,25 @@ impl TemplateBuilder { node_type: TemplateNodeTypeBuilder::DynamicNode( self.dynamic_context.add_node(BodyNode::Component(comp)), ), + parent, + depth, + fully_static: false, }); } BodyNode::Text(txt) => { let mut segments = Vec::new(); + let mut length = 0; + let mut fully_static = true; for segment in txt.segments { segments.push(match segment { - Segment::Literal(lit) => TextTemplateSegment::Static(lit), + Segment::Literal(lit) => { + length += lit.len(); + TextTemplateSegment::Static(lit) + } Segment::Formatted(fmted) => { + fully_static = false; TextTemplateSegment::Dynamic(self.dynamic_context.add_text(fmted)) } }) @@ -519,7 +539,10 @@ impl TemplateBuilder { self.nodes.push(TemplateNodeBuilder { id, - node_type: TemplateNodeTypeBuilder::Text(TextTemplate::new(segments)), + node_type: TemplateNodeTypeBuilder::Text(TextTemplate::new(segments, length)), + parent, + depth, + fully_static, }); } @@ -529,6 +552,9 @@ impl TemplateBuilder { node_type: TemplateNodeTypeBuilder::DynamicNode( self.dynamic_context.add_node(BodyNode::RawExpr(expr)), ), + parent, + depth, + fully_static: false, }); } } @@ -608,6 +634,7 @@ impl TemplateBuilder { pub fn try_into_owned(self, location: &OwnedCodeLocation) -> Result { let mut nodes = Vec::new(); let dynamic_mapping = self.dynamic_mapping(&nodes); + let dynamic_path = self.dynamic_path(); for node in self.nodes { nodes.push(node.try_into_owned(location)?); } @@ -616,6 +643,7 @@ impl TemplateBuilder { nodes, root_nodes: self.root_nodes, dynamic_mapping, + dynamic_path, }) } @@ -684,6 +712,114 @@ impl TemplateBuilder { listener_mapping, ) } + + fn dynamic_path(&self) -> Option { + let mut last_seg: Option = None; + let mut nodes_to_insert_after = Vec::new(); + // iterating from the last root to the first + for root in self.root_nodes.iter().rev() { + let root_node = &self.nodes[root.0]; + if let TemplateNodeTypeBuilder::DynamicNode(_) = root_node.node_type { + match &mut last_seg { + // if there has been no static nodes, we can append the child to the parent node + None => nodes_to_insert_after.push(*root), + // otherwise we insert the child before the last static node + Some(seg) => { + seg.ops.push(UpdateOp::InsertBefore(*root)); + } + } + } else if let Some(mut new) = self.construct_path_segment(*root) { + if let Some(last) = last_seg.take() { + match new.traverse { + OwnedTraverse::Halt => { + new.traverse = OwnedTraverse::NextSibling(Box::new(last)); + } + OwnedTraverse::FirstChild(b) => { + new.traverse = OwnedTraverse::Both(Box::new((*b, last))); + } + _ => unreachable!(), + } + } else { + for node in nodes_to_insert_after.drain(..) { + new.ops.push(UpdateOp::InsertAfter(node)); + } + } + last_seg = Some(new); + } else if let Some(last) = last_seg.take() { + last_seg = Some(OwnedPathSeg { + ops: Vec::new(), + traverse: OwnedTraverse::NextSibling(Box::new(last)), + }); + } + } + last_seg + } + + fn construct_path_segment(&self, node_id: TemplateNodeId) -> Option { + let n = &self.nodes[node_id.0]; + if n.fully_static { + return None; + } + match &n.node_type { + TemplateNodeTypeBuilder::Element(el) => { + let mut last_seg: Option = None; + let mut children_to_append = Vec::new(); + // iterating from the last child to the first + for child in el.children.iter().rev() { + let child_node = &self.nodes[child.0]; + if let TemplateNodeTypeBuilder::DynamicNode(_) = child_node.node_type { + match &mut last_seg { + // if there has been no static nodes, we can append the child to the parent node + None => children_to_append.push(*child), + // otherwise we insert the child before the last static node + Some(seg) => { + seg.ops.push(UpdateOp::InsertBefore(*child)); + } + } + } else if let Some(mut new) = self.construct_path_segment(*child) { + if let Some(last) = last_seg.take() { + match new.traverse { + OwnedTraverse::Halt => { + new.traverse = OwnedTraverse::NextSibling(Box::new(last)); + } + OwnedTraverse::FirstChild(b) => { + new.traverse = OwnedTraverse::Both(Box::new((*b, last))); + } + _ => unreachable!(), + } + } + last_seg = Some(new); + } else if let Some(last) = last_seg.take() { + last_seg = Some(OwnedPathSeg { + ops: Vec::new(), + traverse: OwnedTraverse::NextSibling(Box::new(last)), + }); + } + } + let mut ops = Vec::new(); + if !el.locally_static || n.parent.is_none() { + ops.push(UpdateOp::StoreNode(node_id)); + } + for child in children_to_append.into_iter().rev() { + ops.push(UpdateOp::AppendChild(child)); + } + Some(OwnedPathSeg { + ops, + traverse: match last_seg { + Some(last) => OwnedTraverse::FirstChild(Box::new(last)), + None => OwnedTraverse::Halt, + }, + }) + } + TemplateNodeTypeBuilder::Text(_) => Some(OwnedPathSeg { + ops: vec![UpdateOp::StoreNode(n.id)], + traverse: dioxus_core::OwnedTraverse::Halt, + }), + TemplateNodeTypeBuilder::DynamicNode(_) => unreachable!( + "constructing path segment for dynamic nodes is handled in the parent node" + ), + } + } } impl ToTokens for TemplateBuilder { @@ -759,10 +895,18 @@ impl ToTokens for TemplateBuilder { }); let mut nodes_quoted = TokenStream::new(); for n in nodes { - n.to_tokens(&mut nodes_quoted, nodes); + n.to_tokens(&mut nodes_quoted); quote! {,}.to_tokens(&mut nodes_quoted); } + let dynamic_path = match self.dynamic_path() { + Some(seg) => { + let seg = quote_owned_segment(seg); + quote! {Some(#seg)} + } + None => quote! {None}, + }; + let quoted = quote! { { const __NODES: dioxus::prelude::StaticTemplateNodes = &[#nodes_quoted]; @@ -791,6 +935,7 @@ impl ToTokens for TemplateBuilder { nodes: __NODES, root_nodes: __ROOT_NODES, dynamic_mapping: StaticDynamicNodeMapping::new(__NODE_MAPPING, __TEXT_MAPPING, __ATTRIBUTE_MAPPING, __STATIC_VOLITALE_MAPPING, __NODES_WITH_LISTENERS), + dynamic_path: #dynamic_path, }); let __bump = __cx.bump(); @@ -874,3 +1019,67 @@ impl ToTokens for DynamicTemplateContextBuilder { }) } } + +fn quote_owned_segment(seg: OwnedPathSeg) -> proc_macro2::TokenStream { + let OwnedPathSeg { ops, traverse } = seg; + + let ops = ops + .into_iter() + .map(|op| match op { + UpdateOp::StoreNode(id) => { + let id = quote_template_node_id(id); + quote!(UpdateOp::StoreNode(#id)) + } + UpdateOp::InsertBefore(id) => { + let id = quote_template_node_id(id); + quote!(UpdateOp::InsertBefore(#id)) + } + UpdateOp::InsertAfter(id) => { + let id = quote_template_node_id(id); + quote!(UpdateOp::InsertAfter(#id)) + } + UpdateOp::AppendChild(id) => { + let id = quote_template_node_id(id); + quote!(UpdateOp::AppendChild(#id)) + } + }) + .collect::>(); + + let traverse = quote_owned_traverse(traverse); + + quote! { + StaticPathSeg { + ops: &[#(#ops),*], + traverse: #traverse, + } + } +} + +fn quote_owned_traverse(traverse: OwnedTraverse) -> proc_macro2::TokenStream { + match traverse { + OwnedTraverse::Halt => { + quote! {StaticTraverse::Halt} + } + OwnedTraverse::FirstChild(seg) => { + let seg = quote_owned_segment(*seg); + quote! {StaticTraverse::FirstChild(&#seg)} + } + OwnedTraverse::NextSibling(seg) => { + let seg = quote_owned_segment(*seg); + quote! {StaticTraverse::NextSibling(&#seg)} + } + OwnedTraverse::Both(b) => { + let (child, sibling) = *b; + let child = quote_owned_segment(child); + let sibling = quote_owned_segment(sibling); + quote! {StaticTraverse::Both(&(#child, #sibling))} + } + } +} + +fn quote_template_node_id(id: TemplateNodeId) -> proc_macro2::TokenStream { + let raw = id.0; + quote! { + TemplateNodeId(#raw) + } +} diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index d405fd9c0..a793a636e 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -433,7 +433,7 @@ impl<'a: 'c, 'c> TextRenderer<'a, '_, 'c> { *last_node_was_text = true; - let text = dynamic_context.resolve_text(&txt.segments); + let text = dynamic_context.resolve_text(txt); write!(f, "{}", text)? } diff --git a/packages/tui/examples/tui_colorpicker.rs b/packages/tui/examples/tui_colorpicker.rs index 354eb1dc3..553a37aa4 100644 --- a/packages/tui/examples/tui_colorpicker.rs +++ b/packages/tui/examples/tui_colorpicker.rs @@ -1,3 +1,4 @@ +use dioxus::core_macro::rsx_without_templates; use dioxus::prelude::*; use dioxus_tui::query::Query; use dioxus_tui::Size; @@ -10,7 +11,8 @@ fn app(cx: Scope) -> Element { let hue = use_state(&cx, || 0.0); let brightness = use_state(&cx, || 0.0); let tui_query: Query = cx.consume_context().unwrap(); - cx.render(rsx! { + // disable templates so that every node has an id and can be queried + cx.render(rsx_without_templates! { div{ width: "100%", background_color: "hsl({hue}, 70%, {brightness}%)", diff --git a/packages/tui/examples/tui_components.rs b/packages/tui/examples/tui_components.rs index 651b9edd8..45ed56338 100644 --- a/packages/tui/examples/tui_components.rs +++ b/packages/tui/examples/tui_components.rs @@ -1,9 +1,10 @@ #![allow(non_snake_case)] use dioxus::prelude::*; +use dioxus_tui::Config; fn main() { - dioxus_tui::launch(app); + dioxus_tui::launch_cfg(app, Config::default()); } #[derive(Props, PartialEq)] diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index fe9770e50..9c070bc00 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -1,7 +1,9 @@ use crate::{node::PreventDefault, Dom}; -use dioxus_core::GlobalNodeId; -use dioxus_native_core::utils::{ElementProduced, PersistantElementIter}; +use dioxus_native_core::{ + utils::{ElementProduced, PersistantElementIter}, + RealNodeId, +}; use dioxus_native_core_macro::sorted_str_slice; use std::{cmp::Ordering, num::NonZeroU16}; @@ -90,7 +92,7 @@ impl NodeDepState<()> for Focus { } } else if node .listeners() - .map(|mut listeners| { + .and_then(|mut listeners| { listeners .any(|l| FOCUS_EVENTS.binary_search(&l).is_ok()) .then_some(()) @@ -117,7 +119,7 @@ const FOCUS_ATTRIBUTES: &[&str] = &sorted_str_slice!(["tabindex"]); #[derive(Default)] pub(crate) struct FocusState { pub(crate) focus_iter: PersistantElementIter, - pub(crate) last_focused_id: Option, + pub(crate) last_focused_id: Option, pub(crate) focus_level: FocusLevel, pub(crate) dirty: bool, } @@ -216,6 +218,9 @@ impl FocusState { } if let Some(id) = next_focus { + if !rdom[id].state.focus.level.focusable() { + panic!() + } rdom[id].state.focused = true; if let Some(old) = self.last_focused_id.replace(id) { rdom[old].state.focused = false; @@ -231,9 +236,9 @@ impl FocusState { pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) { fn remove_children( - to_prune: &mut [&mut Option], + to_prune: &mut [&mut Option], rdom: &Dom, - removed: GlobalNodeId, + removed: RealNodeId, ) { for opt in to_prune.iter_mut() { if let Some(id) = opt { @@ -256,19 +261,19 @@ impl FocusState { dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children( &mut [&mut self.last_focused_id], rdom, - rdom.decode_id(*root), + rdom.resolve_maybe_id(*root), ), dioxus_core::DomEdit::Remove { root } => remove_children( &mut [&mut self.last_focused_id], rdom, - rdom.decode_id(*root), + rdom.resolve_maybe_id(*root), ), _ => (), } } } - pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: GlobalNodeId) { + pub(crate) fn set_focus(&mut self, rdom: &mut Dom, id: RealNodeId) { if let Some(old) = self.last_focused_id.replace(id) { rdom[old].state.focused = false; } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 7fbc84257..44ac276cc 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -2,6 +2,7 @@ use crossterm::event::{ Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind, }; use dioxus_core::*; +use dioxus_native_core::RealNodeId; use rustc_hash::{FxHashMap, FxHashSet}; use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D}; @@ -190,7 +191,8 @@ impl InnerInputState { self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom); if old_focus != self.focus_state.last_focused_id { - if let Some(id) = self.focus_state.last_focused_id { + // elements with listeners will always have a element id + if let Some(RealNodeId::ElementId(id)) = self.focus_state.last_focused_id { resolved_events.push(UserEvent { scope_id: None, priority: EventPriority::Medium, @@ -208,7 +210,7 @@ impl InnerInputState { bubbles: event_bubbles("focusin"), }); } - if let Some(id) = old_focus { + if let Some(RealNodeId::ElementId(id)) = old_focus { resolved_events.push(UserEvent { scope_id: None, priority: EventPriority::Medium, @@ -243,13 +245,13 @@ impl InnerInputState { fn try_create_event( name: &'static str, data: Arc, - will_bubble: &mut FxHashSet, + will_bubble: &mut FxHashSet, resolved_events: &mut Vec, node: &Node, dom: &Dom, ) { // only trigger event if the event was not triggered already by a child - let id = node.node_data.id; + let id = node.mounted_id(); if will_bubble.insert(id) { let mut parent = node.node_data.parent; while let Some(parent_id) = parent { @@ -260,7 +262,7 @@ impl InnerInputState { scope_id: None, priority: EventPriority::Medium, name, - element: Some(id), + element: Some(id.as_element_id()), data, bubbles: event_bubbles(name), }) @@ -547,7 +549,7 @@ impl InnerInputState { let currently_contains = layout_contains_point(node_layout, new_pos); if currently_contains && node.state.focus.level.focusable() { - focus_id = Some(node.node_data.id); + focus_id = Some(node.mounted_id()); } }); if let Some(id) = focus_id { @@ -665,7 +667,7 @@ impl RinkInputHandler { scope_id: None, priority: EventPriority::Medium, name: event, - element: Some(node.node_data.id), + element: Some(node.mounted_id().as_element_id()), data: data.clone(), bubbles: event_bubbles(event), }); diff --git a/packages/tui/src/layout.rs b/packages/tui/src/layout.rs index e7791943f..e65e5b2df 100644 --- a/packages/tui/src/layout.rs +++ b/packages/tui/src/layout.rs @@ -6,6 +6,7 @@ use dioxus_native_core::layout_attributes::apply_layout_attributes; use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView}; use dioxus_native_core::real_dom::OwnedAttributeView; use dioxus_native_core::state::ChildDepState; +use dioxus_native_core::RealNodeId; use dioxus_native_core_macro::sorted_str_slice; use taffy::prelude::*; @@ -100,7 +101,7 @@ impl ChildDepState for TaffyLayout { } // the root node fills the entire area - if node.id() == ElementId(0) { + if node.id() == RealNodeId::ElementId(ElementId(0)) { apply_layout_attributes("width", "100%", &mut style); apply_layout_attributes("height", "100%", &mut style); } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 7208bd20f..70d90ef0a 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -7,7 +7,7 @@ use crossterm::{ }; use dioxus_core::exports::futures_channel::mpsc::unbounded; use dioxus_core::*; -use dioxus_native_core::real_dom::RealDom; +use dioxus_native_core::{real_dom::RealDom, RealNodeId}; use focus::FocusState; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, @@ -133,8 +133,8 @@ fn render_vdom( terminal.clear().unwrap(); } - let mut to_rerender: rustc_hash::FxHashSet = - vec![GlobalNodeId::VNodeId(ElementId(0))] + let mut to_rerender: rustc_hash::FxHashSet = + vec![RealNodeId::ElementId(ElementId(0))] .into_iter() .collect(); let mut updated = true; diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index a1bd33eeb..6e6137fde 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -7,7 +7,7 @@ //! - tests to ensure dyn_into works for various event types. //! - Partial delegation?> -use dioxus_core::{DomEdit, SchedulerMsg, UserEvent}; +use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent}; use dioxus_html::event_bubbles; use dioxus_interpreter_js::Interpreter; use js_sys::Function; @@ -43,7 +43,7 @@ impl WebsysDom { break Ok(UserEvent { name: event_name_from_typ(&typ), data: virtual_event_from_websys_event(event.clone(), target.clone()), - element: Some(id), + element: Some(ElementId(id)), scope_id: None, priority: dioxus_core::EventPriority::Medium, bubbles: event.bubbles(), @@ -107,18 +107,25 @@ impl WebsysDom { pub fn apply_edits(&mut self, mut edits: Vec) { for edit in edits.drain(..) { match edit { - DomEdit::PushRoot { root } => self.interpreter.PushRoot(root), - DomEdit::PopRoot {} => self.interpreter.PopRoot(), - DomEdit::AppendChildren { many } => self.interpreter.AppendChildren(many), - DomEdit::ReplaceWith { root, m } => self.interpreter.ReplaceWith(root, m), - DomEdit::InsertAfter { root, n } => self.interpreter.InsertAfter(root, n), - DomEdit::InsertBefore { root, n } => self.interpreter.InsertBefore(root, n), + DomEdit::AppendChildren { root, children } => { + self.interpreter.AppendChildren(root, children); + } + DomEdit::ReplaceWith { root, nodes } => self.interpreter.ReplaceWith(root, nodes), + DomEdit::InsertAfter { root, nodes } => self.interpreter.InsertAfter(root, nodes), + DomEdit::InsertBefore { root, nodes } => self.interpreter.InsertBefore(root, nodes), DomEdit::Remove { root } => self.interpreter.Remove(root), - DomEdit::CreateElement { tag, root } => self.interpreter.CreateElement(tag, root), - DomEdit::CreateElementNs { tag, root, ns } => { - self.interpreter.CreateElementNs(tag, root, ns) - } + DomEdit::CreateElement { + root, + tag, + children, + } => self.interpreter.CreateElement(tag, root, children), + DomEdit::CreateElementNs { + root, + tag, + ns, + children, + } => self.interpreter.CreateElementNs(tag, root, ns, children), DomEdit::CreatePlaceholder { root } => self.interpreter.CreatePlaceholder(root), DomEdit::NewEventListener { event_name, root, .. @@ -157,45 +164,15 @@ impl WebsysDom { let value = serde_wasm_bindgen::to_value(&value).unwrap(); self.interpreter.SetAttribute(root, field, value, ns) } - DomEdit::CreateTemplateRef { id, template_id } => { - self.interpreter.CreateTemplateRef(id, template_id) - } - DomEdit::CreateTemplate { id } => self.interpreter.CreateTemplate(id), - DomEdit::FinishTemplate { len } => self.interpreter.FinishTemplate(len), - DomEdit::EnterTemplateRef { root } => self.interpreter.EnterTemplateRef(root), - DomEdit::ExitTemplateRef {} => self.interpreter.ExitTemplateRef(), - DomEdit::CreateTextNodeTemplate { - root, - text, - locally_static, - } => self - .interpreter - .CreateTextNodeTemplate(text, root, locally_static), - DomEdit::CreateElementTemplate { - root, - tag, - locally_static, - fully_static, - } => { - self.interpreter - .CreateElementTemplate(tag, root, locally_static, fully_static) - } - DomEdit::CreateElementNsTemplate { - root, - tag, - ns, - locally_static, - fully_static, - } => self.interpreter.CreateElementNsTemplate( - tag, - root, - ns, - locally_static, - fully_static, - ), - DomEdit::CreatePlaceholderTemplate { root } => { - self.interpreter.CreatePlaceholderTemplate(root) + DomEdit::CloneNode { id, new_id } => self.interpreter.CloneNode(id, new_id), + DomEdit::CloneNodeChildren { id, new_ids } => { + self.interpreter.CloneNodeChildren(id, new_ids) } + DomEdit::FirstChild {} => self.interpreter.FirstChild(), + DomEdit::NextSibling {} => self.interpreter.NextSibling(), + DomEdit::ParentNode {} => self.interpreter.ParentNode(), + DomEdit::StoreWithId { id } => self.interpreter.StoreWithId(id), + DomEdit::SetLastNode { id } => self.interpreter.SetLastNode(id), } } } diff --git a/packages/web/src/rehydrate.rs b/packages/web/src/rehydrate.rs index 7b49c7319..cab43bacd 100644 --- a/packages/web/src/rehydrate.rs +++ b/packages/web/src/rehydrate.rs @@ -108,30 +108,13 @@ impl WebsysDom { } for listener in vel.listeners { - let global_id = listener.mounted_node.get().unwrap(); - match global_id { - dioxus_core::GlobalNodeId::TemplateId { - template_ref_id, - template_node_id: id, - } => { - self.interpreter.EnterTemplateRef(template_ref_id.into()); - self.interpreter.NewEventListener( - listener.event, - id.into(), - self.handler.as_ref().unchecked_ref(), - event_bubbles(listener.event), - ); - self.interpreter.ExitTemplateRef(); - } - dioxus_core::GlobalNodeId::VNodeId(id) => { - self.interpreter.NewEventListener( - listener.event, - id.into(), - self.handler.as_ref().unchecked_ref(), - event_bubbles(listener.event), - ); - } - } + let id = listener.mounted_node.get().unwrap(); + self.interpreter.NewEventListener( + listener.event, + Some(id.as_u64()), + self.handler.as_ref().unchecked_ref(), + event_bubbles(listener.event), + ); } if !vel.listeners.is_empty() {