From e337aff0a443297c7d4f14b1bb5e1ad23963f10e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 23 Aug 2023 10:48:10 -0500 Subject: [PATCH] WIP split element from element ref --- packages/core/src/arena.rs | 86 +++++++------ packages/core/src/create.rs | 80 ++++++------- packages/core/src/diff.rs | 200 ++++++++++++++++++++----------- packages/core/src/fragment.rs | 2 +- packages/core/src/nodes.rs | 7 +- packages/core/src/virtual_dom.rs | 137 +++++++++++---------- packages/rsx/src/lib.rs | 2 +- 7 files changed, 291 insertions(+), 223 deletions(-) diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index c8f7ce3c9..c41454b50 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -13,62 +13,52 @@ use crate::{ #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct ElementId(pub usize); -pub(crate) struct ElementRef { +/// An Element that can be bubbled to's unique identifier. +/// +/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is +/// unmounted, then the `ElementId` will be reused for a new component. +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct BubbleId(pub usize); + +#[derive(Debug, Clone)] +pub struct ElementRef { // the pathway of the real element inside the template - pub path: ElementPath, + pub(crate) path: ElementPath, // The actual template - pub template: Option>>, + pub(crate) template: NonNull>, // The scope the element belongs to - pub scope: ScopeId, + pub(crate) scope: ScopeId, } #[derive(Clone, Copy, Debug)] -pub enum ElementPath { - Deep(&'static [u8]), - Root(usize), -} - -impl ElementRef { - pub(crate) fn none() -> Self { - Self { - template: None, - path: ElementPath::Root(0), - scope: ScopeId(0), - } - } +pub struct ElementPath { + path: &'static [u8], } impl VirtualDom { - pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId { - self.next_reference(template, ElementPath::Deep(path)) + pub(crate) fn next_element(&mut self) -> ElementId { + ElementId(self.elements.insert(None)) } - pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId { - self.next_reference(template, ElementPath::Root(path)) + pub(crate) fn next_element_ref(&mut self, template: &VNode, path: &'static [u8]) -> BubbleId { + self.next_reference(template, ElementPath { path }) } - pub(crate) fn next_null(&mut self) -> ElementId { - let entry = self.elements.vacant_entry(); - let id = entry.key(); - - entry.insert(ElementRef::none()); - ElementId(id) - } - - fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId { - let entry = self.elements.vacant_entry(); + fn next_reference(&mut self, template: &VNode, path: ElementPath) -> BubbleId { + let entry = self.element_refs.vacant_entry(); let id = entry.key(); let scope = self.runtime.current_scope_id().unwrap_or(ScopeId(0)); entry.insert(ElementRef { // We know this is non-null because it comes from a reference - template: Some(unsafe { NonNull::new_unchecked(template as *const _ as *mut _) }), + template: unsafe { NonNull::new_unchecked(template as *const _ as *mut _) }, path, scope, }); - ElementId(id) + BubbleId(id) } pub(crate) fn reclaim(&mut self, el: ElementId) { @@ -76,7 +66,7 @@ impl VirtualDom { .unwrap_or_else(|| panic!("cannot reclaim {:?}", el)); } - pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option { + pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<()> { if el.0 == 0 { panic!( "Cannot reclaim the root element - {:#?}", @@ -84,12 +74,26 @@ impl VirtualDom { ); } - self.elements.try_remove(el.0) + self.elements.try_remove(el.0).map(|_| ()) + } + + pub(crate) fn set_template(&mut self, el: ElementId, node: &VNode, path: &'static [u8]) { + match self.elements[el.0] { + Some(bubble_id) => { + self.element_refs[bubble_id.0].path = ElementPath { path }; + self.element_refs[bubble_id.0].template = + unsafe { NonNull::new_unchecked(node as *const _ as *mut _) }; + } + None => { + self.elements[el.0] = Some(self.next_reference(node, ElementPath { path })); + } + } } pub(crate) fn update_template(&mut self, el: ElementId, node: &VNode) { + let bubble_id = self.elements[el.0].unwrap(); let node: *const VNode = node as *const _; - self.elements[el.0].template = unsafe { std::mem::transmute(node) }; + self.element_refs[bubble_id.0].template = unsafe { std::mem::transmute(node) }; } // Drop a scope and all its children @@ -182,18 +186,12 @@ impl VirtualDom { impl ElementPath { pub(crate) fn is_decendant(&self, small: &&[u8]) -> bool { - match *self { - ElementPath::Deep(big) => small.len() <= big.len() && *small == &big[..small.len()], - ElementPath::Root(r) => small.len() == 1 && small[0] == r as u8, - } + small.len() <= self.path.len() && *small == &self.path[..small.len()] } } impl PartialEq<&[u8]> for ElementPath { fn eq(&self, other: &&[u8]) -> bool { - match *self { - ElementPath::Deep(deep) => deep.eq(*other), - ElementPath::Root(r) => other.len() == 1 && other[0] == r as u8, - } + self.path.eq(*other) } } diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index a61a8cace..f8ae3add3 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,4 +1,5 @@ use crate::any_props::AnyProps; +use crate::diff::BoundaryRef; use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText}; use crate::mutations::Mutation; use crate::mutations::Mutation::*; @@ -181,15 +182,15 @@ impl<'b> VirtualDom { use DynamicNode::*; match &template.dynamic_nodes[idx] { node @ Component { .. } | node @ Fragment(_) => { - self.create_dynamic_node(template, node, idx) + self.create_dynamic_node(template, idx, node, idx) } Placeholder(VPlaceholder { id }) => { - let id = self.set_slot(template, id, idx); + let id = self.set_slot(id); self.mutations.push(CreatePlaceholder { id }); 1 } Text(VText { id, value }) => { - let id = self.set_slot(template, id, idx); + let id = self.set_slot(id); self.create_static_text(value, id); 1 } @@ -265,7 +266,7 @@ impl<'b> VirtualDom { .map(|sorted_index| dynamic_nodes[sorted_index].0); for idx in reversed_iter { - let m = self.create_dynamic_node(template, &template.dynamic_nodes[idx], idx); + let m = self.create_dynamic_node(template, idx, &template.dynamic_nodes[idx], idx); if m > 0 { // The path is one shorter because the top node is the root let path = &template.template.get().node_paths[idx][1..]; @@ -279,15 +280,15 @@ impl<'b> VirtualDom { attrs: &mut Peekable>, root_idx: u8, root: ElementId, - node: &VNode, + node: &'b VNode<'b>, ) { while let Some((mut attr_id, path)) = attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx)) { - let id = self.assign_static_node_as_dynamic(path, root, node, attr_id); + let id = self.assign_static_node_as_dynamic(path, root); loop { - self.write_attribute(&node.dynamic_attrs[attr_id], id); + self.write_attribute(node, attr_id, &node.dynamic_attrs[attr_id], id); // Only push the dynamic attributes forward if they match the current path (same element) match attrs.next_if(|(_, p)| *p == path) { @@ -298,7 +299,13 @@ impl<'b> VirtualDom { } } - fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) { + fn write_attribute( + &mut self, + template: &'b VNode<'b>, + idx: usize, + attribute: &'b crate::Attribute<'b>, + id: ElementId, + ) { // Make sure we set the attribute's associated id attribute.mounted_element.set(id); @@ -307,6 +314,8 @@ impl<'b> VirtualDom { match &attribute.value { AttributeValue::Listener(_) => { + let path = &template.template.get().attr_paths[idx]; + self.set_template(id, template, path); self.mutations.push(NewEventListener { // all listeners start with "on" name: &unbounded_name[2..], @@ -330,7 +339,7 @@ impl<'b> VirtualDom { fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId { // Get an ID for this root since it's a real root - let this_id = self.next_root(template, root_idx); + let this_id = self.next_element(); template.root_ids.borrow_mut()[root_idx] = this_id; self.mutations.push(LoadTemplate { @@ -353,8 +362,6 @@ impl<'b> VirtualDom { &mut self, path: &'static [u8], this_id: ElementId, - template: &VNode, - attr_id: usize, ) -> ElementId { if path.len() == 1 { return this_id; @@ -362,7 +369,7 @@ impl<'b> VirtualDom { // if attribute is on a root node, then we've already created the element // Else, it's deep in the template and we should create a new id for it - let id = self.next_element(template, template.template.get().attr_paths[attr_id]); + let id = self.next_element(); self.mutations.push(Mutation::AssignId { path: &path[1..], @@ -439,27 +446,21 @@ impl<'b> VirtualDom { pub(crate) fn create_dynamic_node( &mut self, - template: &'b VNode<'b>, + parent: BoundaryRef<'b>, node: &'b DynamicNode<'b>, - idx: usize, ) -> usize { use DynamicNode::*; match node { - Text(text) => self.create_dynamic_text(template, text, idx), - Placeholder(place) => self.create_placeholder(place, template, idx), - Component(component) => self.create_component_node(template, component), + Text(text) => self.create_dynamic_text(parent, text), + Placeholder(place) => self.create_placeholder(place, parent), + Component(component) => self.create_component_node(parent, component), Fragment(frag) => frag.iter().map(|child| self.create(child)).sum(), } } - fn create_dynamic_text( - &mut self, - template: &'b VNode<'b>, - text: &'b VText<'b>, - idx: usize, - ) -> usize { + fn create_dynamic_text(&mut self, parent: BoundaryRef<'b>, text: &'b VText<'b>) -> usize { // Allocate a dynamic element reference for this text node - let new_id = self.next_element(template, template.template.get().node_paths[idx]); + let new_id = self.next_element(); // Make sure the text node is assigned to the correct element text.id.set(Some(new_id)); @@ -470,7 +471,7 @@ impl<'b> VirtualDom { // Add the mutation to the list self.mutations.push(HydrateText { id: new_id, - path: &template.template.get().node_paths[idx][1..], + path: &parent.parent.template.get().node_paths[parent.dyn_node_idx][1..], value, }); @@ -481,18 +482,17 @@ impl<'b> VirtualDom { pub(crate) fn create_placeholder( &mut self, placeholder: &VPlaceholder, - template: &'b VNode<'b>, - idx: usize, + parent: BoundaryRef<'b>, ) -> usize { // Allocate a dynamic element reference for this text node - let id = self.next_element(template, template.template.get().node_paths[idx]); + let id = self.next_element(); // Make sure the text node is assigned to the correct element placeholder.id.set(Some(id)); // Assign the ID to the existing node in the template self.mutations.push(AssignId { - path: &template.template.get().node_paths[idx][1..], + path: &parent.parent.template.get().node_paths[parent.dyn_node_idx][1..], id, }); @@ -502,7 +502,7 @@ impl<'b> VirtualDom { pub(super) fn create_component_node( &mut self, - template: &'b VNode<'b>, + parent: BoundaryRef<'b>, component: &'b VComponent<'b>, ) -> usize { use RenderReturn::*; @@ -514,8 +514,11 @@ impl<'b> VirtualDom { match unsafe { self.run_scope(scope).extend_lifetime_ref() } { // Create the component's root element - Ready(t) => self.create_scope(scope, t), - Aborted(t) => self.mount_aborted(template, t), + Ready(t) => { + self.assign_boundary_ref(parent, t); + self.create_scope(scope, t) + } + Aborted(t) => self.mount_aborted(t), } } @@ -531,20 +534,15 @@ impl<'b> VirtualDom { .unwrap_or_else(|| component.scope.get().unwrap()) } - fn mount_aborted(&mut self, parent: &'b VNode<'b>, placeholder: &VPlaceholder) -> usize { - let id = self.next_element(parent, &[]); + fn mount_aborted(&mut self, placeholder: &VPlaceholder) -> usize { + let id = self.next_element(); self.mutations.push(Mutation::CreatePlaceholder { id }); placeholder.id.set(Some(id)); 1 } - fn set_slot( - &mut self, - template: &'b VNode<'b>, - slot: &'b Cell>, - id: usize, - ) -> ElementId { - let id = self.next_element(template, template.template.get().node_paths[id]); + fn set_slot(&mut self, slot: &'b Cell>) -> ElementId { + let id = self.next_element(); slot.set(Some(id)); id } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index dd4cf8147..80ccbc315 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -7,7 +7,7 @@ use crate::{ nodes::{DynamicNode, VNode}, scopes::ScopeId, virtual_dom::VirtualDom, - Attribute, TemplateNode, + Attribute, AttributeValue, TemplateNode, }; use rustc_hash::{FxHashMap, FxHashSet}; @@ -43,14 +43,14 @@ impl<'b> VirtualDom { // Placeholder becomes something // We should also clear the error now - (Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]), + (Aborted(l), Ready(r)) => self.replace_placeholder(l, [r], todo!()), }; } self.runtime.scope_stack.borrow_mut().pop(); } fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) { - let id = self.next_null(); + let id = self.next_element(); p.id.set(Some(id)); self.mutations.push(Mutation::CreatePlaceholder { id }); @@ -81,12 +81,17 @@ impl<'b> VirtualDom { if let Some(&template) = map.get(&byte_index) { right_template.template.set(template); if template != left_template.template.get() { - return self.replace(left_template, [right_template]); + return self.replace(left_template, [right_template], todo!()); } } } } + // Copy over the parent + { + right_template.parent.set(left_template.parent.get()); + } + // If the templates are the same, we don't need to do anything, nor do we want to if templates_are_the_same(left_template, right_template) { return; @@ -94,7 +99,7 @@ impl<'b> VirtualDom { // If the templates are different by name, we need to replace the entire template if templates_are_different(left_template, right_template) { - return self.light_diff_templates(left_template, right_template); + return self.light_diff_templates(left_template, right_template, todo!()); } // If the templates are the same, we can diff the attributes and children @@ -105,15 +110,14 @@ impl<'b> VirtualDom { .zip(right_template.dynamic_attrs.iter()) .for_each(|(left_attr, right_attr)| { // Move over the ID from the old to the new - right_attr - .mounted_element - .set(left_attr.mounted_element.get()); - - // We want to make sure anything that gets pulled is valid - self.update_template(left_attr.mounted_element.get(), right_template); + let mounted_element = left_attr.mounted_element.get(); + right_attr.mounted_element.set(mounted_element); // If the attributes are different (or volatile), we need to update them - if left_attr.value != right_attr.value || left_attr.volatile { + if let AttributeValue::Listener(_) = left_attr.value { + // Nodes with Listeners always need their pointers updated + self.update_template(mounted_element, right_template); + } else if left_attr.value != right_attr.value || left_attr.volatile { self.update_attribute(right_attr, left_attr); } }); @@ -123,8 +127,13 @@ impl<'b> VirtualDom { .dynamic_nodes .iter() .zip(right_template.dynamic_nodes.iter()) - .for_each(|(left_node, right_node)| { - self.diff_dynamic_node(left_node, right_node, right_template); + .enumerate() + .for_each(|(dyn_node_idx, (left_node, right_node))| { + let current_ref = BoundaryRef { + parent: right_template, + dyn_node_idx, + }; + self.diff_dynamic_node(left_node, right_node, current_ref); }); // Make sure the roots get transferred over while we're here @@ -135,29 +144,20 @@ impl<'b> VirtualDom { right.push(element); } } - - let root_ids = right_template.root_ids.borrow(); - - // Update the node refs - for i in 0..root_ids.len() { - if let Some(root_id) = root_ids.get(i) { - self.update_template(*root_id, right_template); - } - } } fn diff_dynamic_node( &mut self, left_node: &'b DynamicNode<'b>, right_node: &'b DynamicNode<'b>, - node: &'b VNode<'b>, + parent: BoundaryRef<'b>, ) { match (left_node, right_node) { - (Text(left), Text(right)) => self.diff_vtext(left, right, node), - (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right), + (Text(left), Text(right)) => self.diff_vtext(left, right), + (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right, parent), (Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()), - (Component(left), Component(right)) => self.diff_vcomponent(left, right, node), - (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right), + (Component(left), Component(right)) => self.diff_vcomponent(left, right, parent), + (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right, parent), (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right), _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."), }; @@ -179,7 +179,7 @@ impl<'b> VirtualDom { &mut self, left: &'b VComponent<'b>, right: &'b VComponent<'b>, - right_template: &'b VNode<'b>, + parent: BoundaryRef<'b>, ) { if std::ptr::eq(left, right) { return; @@ -187,7 +187,7 @@ impl<'b> VirtualDom { // Replace components that have different render fns if left.render_fn != right.render_fn { - return self.replace_vcomponent(right_template, right, left); + return self.replace_vcomponent(right, left, parent); } // Make sure the new vcomponent has the right scopeid associated to it @@ -222,11 +222,11 @@ impl<'b> VirtualDom { fn replace_vcomponent( &mut self, - right_template: &'b VNode<'b>, right: &'b VComponent<'b>, left: &'b VComponent<'b>, + parent: BoundaryRef<'b>, ) { - let m = self.create_component_node(right_template, right); + let m = self.create_component_node(parent, right); let pre_edits = self.mutations.edits.len(); @@ -280,12 +280,17 @@ impl<'b> VirtualDom { /// Component { ..props } /// } /// ``` - fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) { + fn light_diff_templates( + &mut self, + left: &'b VNode<'b>, + right: &'b VNode<'b>, + parent: BoundaryRef<'b>, + ) { match matching_components(left, right) { - None => self.replace(left, [right]), + None => self.replace(left, [right], parent), Some(components) => components .into_iter() - .for_each(|(l, r)| self.diff_vcomponent(l, r, right)), + .for_each(|(idx, l, r)| self.diff_vcomponent(l, r, parent)), } } @@ -293,11 +298,8 @@ impl<'b> VirtualDom { /// /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's /// different. - fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>, node: &'b VNode<'b>) { - let id = left - .id - .get() - .unwrap_or_else(|| self.next_element(node, &[0])); + fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) { + let id = left.id.get().unwrap_or_else(|| self.next_element()); right.id.set(Some(id)); if left.value != right.value { @@ -306,7 +308,12 @@ impl<'b> VirtualDom { } } - fn diff_non_empty_fragment(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) { + fn diff_non_empty_fragment( + &mut self, + old: &'b [VNode<'b>], + new: &'b [VNode<'b>], + parent: BoundaryRef<'b>, + ) { let new_is_keyed = new[0].key.is_some(); let old_is_keyed = old[0].key.is_some(); debug_assert!( @@ -319,9 +326,9 @@ impl<'b> VirtualDom { ); if new_is_keyed && old_is_keyed { - self.diff_keyed_children(old, new); + self.diff_keyed_children(old, new, parent); } else { - self.diff_non_keyed_children(old, new); + self.diff_non_keyed_children(old, new, parent); } } @@ -333,7 +340,12 @@ impl<'b> VirtualDom { // [... 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, + old: &'b [VNode<'b>], + new: &'b [VNode<'b>], + parent: BoundaryRef<'b>, + ) { use std::cmp::Ordering; // Handled these cases in `diff_children` before calling this function. @@ -342,7 +354,9 @@ impl<'b> VirtualDom { match old.len().cmp(&new.len()) { Ordering::Greater => self.remove_nodes(&old[new.len()..]), - Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()), + Ordering::Less => { + self.create_and_insert_after(&new[old.len()..], old.last().unwrap(), parent) + } Ordering::Equal => {} } @@ -367,7 +381,12 @@ impl<'b> VirtualDom { // 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, + old: &'b [VNode<'b>], + new: &'b [VNode<'b>], + parent: BoundaryRef<'b>, + ) { if cfg!(debug_assertions) { let mut keys = rustc_hash::FxHashSet::default(); let mut assert_unique_keys = |children: &'b [VNode<'b>]| { @@ -395,7 +414,7 @@ impl<'b> VirtualDom { // // `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(old, new, parent) { Some(count) => count, None => return, }; @@ -421,18 +440,18 @@ impl<'b> VirtualDom { 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(new_middle, foothold, parent); } 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(new_middle, foothold, parent); } else { // inserting in the middle let foothold = &old[left_offset - 1]; - self.create_and_insert_after(new_middle, foothold); + self.create_and_insert_after(new_middle, foothold, parent); } } else { - self.diff_keyed_middle(old_middle, new_middle); + self.diff_keyed_middle(old_middle, new_middle, parent); } } @@ -445,6 +464,7 @@ impl<'b> VirtualDom { &mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>], + parent: BoundaryRef<'b>, ) -> Option<(usize, usize)> { let mut left_offset = 0; @@ -460,7 +480,7 @@ impl<'b> VirtualDom { // 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(&new[left_offset..], old.last().unwrap(), parent); return None; } @@ -499,7 +519,12 @@ impl<'b> VirtualDom { // // 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, + old: &'b [VNode<'b>], + new: &'b [VNode<'b>], + parent: BoundaryRef<'b>, + ) { /* 1. Map the old keys into a numerical ordering based on indices. 2. Create a map of old key to its index @@ -556,7 +581,7 @@ impl<'b> VirtualDom { if shared_keys.is_empty() { if old.get(0).is_some() { self.remove_nodes(&old[1..]); - self.replace(&old[0], new); + self.replace(&old[0], new, parent); } else { // I think this is wrong - why are we appending? // only valid of the if there are no trailing elements @@ -734,20 +759,35 @@ impl<'b> VirtualDom { .sum() } - fn create_children(&mut self, nodes: impl IntoIterator>) -> usize { - nodes - .into_iter() - .fold(0, |acc, child| acc + self.create(child)) + fn create_children( + &mut self, + nodes: impl IntoIterator>, + parent: BoundaryRef<'b>, + ) -> usize { + nodes.into_iter().fold(0, |acc, child| { + self.assign_boundary_ref(parent, child); + acc + self.create(child) + }) } - fn create_and_insert_before(&mut self, new: &'b [VNode<'b>], before: &'b VNode<'b>) { - let m = self.create_children(new); + fn create_and_insert_before( + &mut self, + new: &'b [VNode<'b>], + before: &'b VNode<'b>, + parent: BoundaryRef<'b>, + ) { + let m = self.create_children(new, parent); let id = self.find_first_element(before); self.mutations.push(Mutation::InsertBefore { id, m }) } - fn create_and_insert_after(&mut self, new: &'b [VNode<'b>], after: &'b VNode<'b>) { - let m = self.create_children(new); + fn create_and_insert_after( + &mut self, + new: &'b [VNode<'b>], + after: &'b VNode<'b>, + parent: BoundaryRef<'b>, + ) { + let m = self.create_children(new, parent); let id = self.find_last_element(after); self.mutations.push(Mutation::InsertAfter { id, m }) } @@ -757,15 +797,21 @@ impl<'b> VirtualDom { &mut self, l: &'b VPlaceholder, r: impl IntoIterator>, + parent: BoundaryRef<'b>, ) { - let m = self.create_children(r); + let m = self.create_children(r, parent); let id = l.id.get().unwrap(); self.mutations.push(Mutation::ReplaceWith { id, m }); self.reclaim(id); } - fn replace(&mut self, left: &'b VNode<'b>, right: impl IntoIterator>) { - let m = self.create_children(right); + fn replace( + &mut self, + left: &'b VNode<'b>, + right: impl IntoIterator>, + parent: BoundaryRef<'b>, + ) { + let m = self.create_children(right, parent); let pre_edits = self.mutations.edits.len(); @@ -786,7 +832,7 @@ impl<'b> VirtualDom { fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder) { // Create the placeholder first, ensuring we get a dedicated ID for the placeholder - let placeholder = self.next_element(&l[0], &[]); + let placeholder = self.next_element(); r.id.set(Some(placeholder)); @@ -984,6 +1030,15 @@ impl<'b> VirtualDom { } } } + + pub(crate) fn assign_boundary_ref(&mut self, parent: BoundaryRef, child: &VNode<'_>) { + // assign the parent + let path = parent.parent.template.get().node_paths[parent.dyn_node_idx]; + + child + .parent + .set(Some(self.next_element_ref(parent.parent, path))); + } } /// Are the templates the same? @@ -1005,7 +1060,7 @@ fn templates_are_different(left_template: &VNode, right_template: &VNode) -> boo fn matching_components<'a>( left: &'a VNode<'a>, right: &'a VNode<'a>, -) -> Option, &'a VComponent<'a>)>> { +) -> Option, &'a VComponent<'a>)>> { let left_template = left.template.get(); let right_template = right.template.get(); if left_template.roots.len() != right_template.roots.len() { @@ -1017,7 +1072,8 @@ fn matching_components<'a>( .roots .iter() .zip(right_template.roots.iter()) - .map(|(l, r)| { + .enumerate() + .map(|(idx, (l, r))| { let (l, r) = match (l, r) { (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r), _ => return None, @@ -1028,7 +1084,7 @@ fn matching_components<'a>( _ => return None, }; - Some((l, r)) + Some((idx, l, r)) }) .collect() } @@ -1060,3 +1116,9 @@ fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool { _ => false, } } + +// A parent that can be used for bubbling +pub(crate) struct BoundaryRef<'b> { + pub parent: &'b VNode<'b>, + pub dyn_node_idx: usize, +} diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index a9e919bfa..0e4bf09e3 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -30,7 +30,7 @@ pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element { let children = cx.props.0.as_ref()?; Some(VNode { key: children.key, - parent: children.parent, + parent: children.parent.clone(), template: children.template.clone(), root_ids: children.root_ids.clone(), dynamic_nodes: children.dynamic_nodes, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 2399c16b8..9232b2961 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,3 +1,4 @@ +use crate::innerlude::BubbleId; use crate::{ any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState, }; @@ -47,7 +48,7 @@ pub struct VNode<'a> { pub key: Option<&'a str>, /// When rendered, this template will be linked to its parent manually - pub parent: Option, + pub parent: Cell>, /// The static nodes and static descriptor of the template pub template: Cell>, @@ -68,7 +69,7 @@ impl<'a> VNode<'a> { pub fn empty(cx: &'a ScopeState) -> Element<'a> { Some(VNode { key: None, - parent: None, + parent: Default::default(), root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())), dynamic_nodes: &[], dynamic_attrs: &[], @@ -722,7 +723,7 @@ impl<'b> IntoDynNode<'b> for Arguments<'_> { impl<'a> IntoDynNode<'a> for &'a VNode<'a> { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { DynamicNode::Fragment(_cx.bump().alloc([VNode { - parent: self.parent, + parent: self.parent.clone(), template: self.template.clone(), root_ids: self.root_ids.clone(), key: self.key, diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a1def71d2..faf1ecf44 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -5,7 +5,7 @@ use crate::{ any_props::VProps, arena::{ElementId, ElementRef}, - innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, + innerlude::{BubbleId, DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, mutations::Mutation, nodes::RenderReturn, nodes::{Template, TemplateId}, @@ -183,7 +183,10 @@ pub struct VirtualDom { pub(crate) templates: FxHashMap>>, // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template - pub(crate) elements: Slab, + pub(crate) element_refs: Slab, + + // The element ids that are used in the renderer + pub(crate) elements: Slab>, pub(crate) mutations: Mutations<'static>, @@ -260,6 +263,7 @@ impl VirtualDom { dirty_scopes: Default::default(), templates: Default::default(), elements: Default::default(), + element_refs: Default::default(), mutations: Mutations::default(), suspended_scopes: Default::default(), }; @@ -273,7 +277,7 @@ impl VirtualDom { root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0)))); // the root element is always given element ID 0 since it's the container for the entire tree - dom.elements.insert(ElementRef::none()); + dom.elements.insert(None); dom } @@ -349,7 +353,13 @@ impl VirtualDom { | | | <-- no, broke early | <-- no, broke early */ - let mut parent_path = self.elements.get(element.0); + println!("calling {:?}", element); + let element = match self.elements.get(element.0) { + Some(Some(el)) => el, + _ => return, + }; + let mut parent_path = self.element_refs.get(element.0).cloned(); + println!("parent path {:?}", parent_path); let mut listeners = vec![]; // We will clone this later. The data itself is wrapped in RC to be used in callbacks if required @@ -363,33 +373,69 @@ impl VirtualDom { // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent. while let Some(el_ref) = parent_path { // safety: we maintain references of all vnodes in the element slab - if let Some(template) = el_ref.template { - let template = unsafe { template.as_ref() }; - let node_template = template.template.get(); - let target_path = el_ref.path; + let template = el_ref.template; + let template = unsafe { template.as_ref() }; + let node_template = template.template.get(); + let target_path = el_ref.path; - for (idx, attr) in template.dynamic_attrs.iter().enumerate() { - let this_path = node_template.attr_paths[idx]; + for (idx, attr) in template.dynamic_attrs.iter().enumerate() { + let this_path = node_template.attr_paths[idx]; - // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one - if attr.name.trim_start_matches("on") == name - && target_path.is_decendant(&this_path) - { - listeners.push(&attr.value); + // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one + if attr.name.trim_start_matches("on") == name + && target_path.is_decendant(&this_path) + { + listeners.push(&attr.value); - // Break if this is the exact target element. - // This means we won't call two listeners with the same name on the same element. This should be - // documented, or be rejected from the rsx! macro outright - if target_path == this_path { - break; - } + // Break if this is the exact target element. + // This means we won't call two listeners with the same name on the same element. This should be + // documented, or be rejected from the rsx! macro outright + if target_path == this_path { + break; } } + } - // Now that we've accumulated all the parent attributes for the target element, call them in reverse order - // We check the bubble state between each call to see if the event has been stopped from bubbling - for listener in listeners.drain(..).rev() { - if let AttributeValue::Listener(listener) = listener { + // Now that we've accumulated all the parent attributes for the target element, call them in reverse order + // We check the bubble state between each call to see if the event has been stopped from bubbling + for listener in listeners.drain(..).rev() { + if let AttributeValue::Listener(listener) = listener { + let origin = el_ref.scope; + self.runtime.scope_stack.borrow_mut().push(origin); + self.runtime.rendering.set(false); + if let Some(cb) = listener.borrow_mut().as_deref_mut() { + cb(uievent.clone()); + } + self.runtime.scope_stack.borrow_mut().pop(); + self.runtime.rendering.set(true); + + if !uievent.propagates.get() { + return; + } + } + } + + parent_path = template + .parent + .get() + .and_then(|id| self.element_refs.get(id.0).cloned()); + } + } else { + // Otherwise, we just call the listener on the target element + if let Some(el_ref) = parent_path { + // safety: we maintain references of all vnodes in the element slab + let template = el_ref.template; + let template = unsafe { template.as_ref() }; + let node_template = template.template.get(); + let target_path = el_ref.path; + + for (idx, attr) in template.dynamic_attrs.iter().enumerate() { + let this_path = node_template.attr_paths[idx]; + + // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one + // Only call the listener if this is the exact target element. + if attr.name.trim_start_matches("on") == name && target_path == this_path { + if let AttributeValue::Listener(listener) = &attr.value { let origin = el_ref.scope; self.runtime.scope_stack.borrow_mut().push(origin); self.runtime.rendering.set(false); @@ -399,44 +445,7 @@ impl VirtualDom { self.runtime.scope_stack.borrow_mut().pop(); self.runtime.rendering.set(true); - if !uievent.propagates.get() { - return; - } - } - } - - parent_path = template.parent.and_then(|id| self.elements.get(id.0)); - } else { - break; - } - } - } else { - // Otherwise, we just call the listener on the target element - if let Some(el_ref) = parent_path { - // safety: we maintain references of all vnodes in the element slab - if let Some(template) = el_ref.template { - let template = unsafe { template.as_ref() }; - let node_template = template.template.get(); - let target_path = el_ref.path; - - for (idx, attr) in template.dynamic_attrs.iter().enumerate() { - let this_path = node_template.attr_paths[idx]; - - // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one - // Only call the listener if this is the exact target element. - if attr.name.trim_start_matches("on") == name && target_path == this_path { - if let AttributeValue::Listener(listener) = &attr.value { - let origin = el_ref.scope; - self.runtime.scope_stack.borrow_mut().push(origin); - self.runtime.rendering.set(false); - if let Some(cb) = listener.borrow_mut().as_deref_mut() { - cb(uievent.clone()); - } - self.runtime.scope_stack.borrow_mut().pop(); - self.runtime.rendering.set(true); - - break; - } + break; } } } @@ -559,7 +568,7 @@ impl VirtualDom { // If an error occurs, we should try to render the default error component and context where the error occured RenderReturn::Aborted(placeholder) => { log::info!("Ran into suspended or aborted scope during rebuild"); - let id = self.next_null(); + let id = self.next_element(); placeholder.id.set(Some(id)); self.mutations.push(Mutation::CreatePlaceholder { id }); } diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index f52ecad0e..d5e5b2009 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -245,7 +245,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> { attr_paths: &[ #(#attr_paths),* ], }; ::dioxus::core::VNode { - parent: None, + parent: Default::default(), key: #key_tokens, template: std::cell::Cell::new(TEMPLATE), root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()).into(),