diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 785d4b174..ffdd4656b 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -13,62 +13,50 @@ 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. +/// +/// `BubbleId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is +/// unmounted, then the `BubbleId` 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 VNodeId(pub usize); + +#[derive(Debug, Clone, Copy)] +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: VNodeId, // 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::ROOT, - } - } +pub struct ElementPath { + pub(crate) 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_vnode_ref(&mut self, vnode: &VNode) -> VNodeId { + let new_id = VNodeId(self.element_refs.insert(Some(unsafe { + std::mem::transmute::, _>(vnode.into()) + }))); - pub(crate) fn next_null(&mut self) -> ElementId { - let entry = self.elements.vacant_entry(); - let id = entry.key(); + // Set this id to be dropped when the scope is rerun + if let Some(scope) = self.runtime.current_scope_id() { + self.scopes[scope.0] + .element_refs_to_drop + .borrow_mut() + .push(new_id); + } - entry.insert(ElementRef::none()); - ElementId(id) - } - - fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId { - let entry = self.elements.vacant_entry(); - let id = entry.key(); - let scope = self.runtime.current_scope_id().unwrap_or(ScopeId::ROOT); - - 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 _) }), - path, - scope, - }); - ElementId(id) + new_id } pub(crate) fn reclaim(&mut self, el: ElementId) { @@ -76,7 +64,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 +72,12 @@ impl VirtualDom { ); } - self.elements.try_remove(el.0) + self.elements.try_remove(el.0).map(|_| ()) } - pub(crate) fn update_template(&mut self, el: ElementId, node: &VNode) { - let node: *const VNode = node as *const _; - self.elements[el.0].template = unsafe { std::mem::transmute(node) }; + pub(crate) fn set_template(&mut self, id: VNodeId, vnode: &VNode) { + self.element_refs[id.0] = + Some(unsafe { std::mem::transmute::, _>(vnode.into()) }); } // Drop a scope and all its children @@ -101,6 +89,15 @@ impl VirtualDom { id, }); + // Remove all VNode ids from the scope + for id in self.scopes[id.0] + .element_refs_to_drop + .borrow_mut() + .drain(..) + { + self.element_refs.try_remove(id.0); + } + self.ensure_drop_safety(id); if recursive { @@ -145,14 +142,25 @@ impl VirtualDom { } /// Descend through the tree, removing any borrowed props and listeners - pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) { + pub(crate) fn ensure_drop_safety(&mut self, scope_id: ScopeId) { let scope = &self.scopes[scope_id.0]; + { + // Drop all element refs that could be invalidated when the component was rerun + let mut element_refs = self.scopes[scope_id.0].element_refs_to_drop.borrow_mut(); + let element_refs_slab = &mut self.element_refs; + for element_ref in element_refs.drain(..) { + if let Some(element_ref) = element_refs_slab.get_mut(element_ref.0) { + *element_ref = None; + } + } + } + // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we // run the hooks (which hold an &mut Reference) // recursively call ensure_drop_safety on all children - let mut props = scope.borrowed_props.borrow_mut(); - props.drain(..).for_each(|comp| { + let props = { scope.borrowed_props.borrow_mut().clone() }; + for comp in props { let comp = unsafe { &*comp }; match comp.scope.get() { Some(child) if child != scope_id => self.ensure_drop_safety(child), @@ -161,7 +169,9 @@ impl VirtualDom { if let Ok(mut props) = comp.props.try_borrow_mut() { *props = None; } - }); + } + let scope = &self.scopes[scope_id.0]; + scope.borrowed_props.borrow_mut().clear(); // Now that all the references are gone, we can safely drop our own references in our listeners. let mut listeners = scope.attributes_to_drop_before_render.borrow_mut(); @@ -176,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 733a796ce..f3b89ae9e 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,5 +1,7 @@ use crate::any_props::AnyProps; -use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText}; +use crate::innerlude::{ + BorrowedAttributeValue, ElementPath, ElementRef, VComponent, VPlaceholder, VText, +}; use crate::mutations::Mutation; use crate::mutations::Mutation::*; use crate::nodes::VNode; @@ -94,6 +96,9 @@ impl<'b> VirtualDom { nodes_mut.resize(len, ElementId::default()); }; + // Set this node id + node.stable_id.set(Some(self.next_vnode_ref(node))); + // The best renderers will have templates prehydrated and registered // Just in case, let's create the template using instructions anyways self.register_template(node.template.get()); @@ -181,15 +186,30 @@ impl<'b> VirtualDom { use DynamicNode::*; match &template.dynamic_nodes[idx] { node @ Component { .. } | node @ Fragment(_) => { - self.create_dynamic_node(template, node, idx) + let template_ref = ElementRef { + path: ElementPath { + path: template.template.get().node_paths[idx], + }, + template: template.stable_id().unwrap(), + scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), + }; + self.create_dynamic_node(template_ref, node) } - Placeholder(VPlaceholder { id }) => { - let id = self.set_slot(template, id, idx); + Placeholder(VPlaceholder { id, parent }) => { + let template_ref = ElementRef { + path: ElementPath { + path: template.template.get().node_paths[idx], + }, + template: template.stable_id().unwrap(), + scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), + }; + parent.set(Some(template_ref)); + 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 +285,14 @@ 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 boundary_ref = ElementRef { + path: ElementPath { + path: template.template.get().node_paths[idx], + }, + template: template.stable_id().unwrap(), + scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), + }; + let m = self.create_dynamic_node(boundary_ref, &template.dynamic_nodes[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 +306,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 +325,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 +340,13 @@ impl<'b> VirtualDom { match &attribute.value { AttributeValue::Listener(_) => { + let path = &template.template.get().attr_paths[idx]; + let element_ref = ElementRef { + path: ElementPath { path }, + template: template.stable_id().unwrap(), + scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), + }; + self.elements[id.0] = Some(element_ref); self.mutations.push(NewEventListener { // all listeners start with "on" name: &unbounded_name[2..], @@ -330,7 +370,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 +393,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 +400,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..], @@ -440,27 +478,21 @@ impl<'b> VirtualDom { pub(crate) fn create_dynamic_node( &mut self, - template: &'b VNode<'b>, + parent: ElementRef, 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), - Fragment(frag) => frag.iter().map(|child| self.create(child)).sum(), + Text(text) => self.create_dynamic_text(parent, text), + Placeholder(place) => self.create_placeholder(place, parent), + Component(component) => self.create_component_node(Some(parent), component), + Fragment(frag) => self.create_children(*frag, Some(parent)), } } - fn create_dynamic_text( - &mut self, - template: &'b VNode<'b>, - text: &'b VText<'b>, - idx: usize, - ) -> usize { + fn create_dynamic_text(&mut self, parent: ElementRef, 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)); @@ -471,7 +503,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.path.path[1..], value, }); @@ -482,18 +514,20 @@ impl<'b> VirtualDom { pub(crate) fn create_placeholder( &mut self, placeholder: &VPlaceholder, - template: &'b VNode<'b>, - idx: usize, + parent: ElementRef, ) -> 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 placeholder's parent + placeholder.parent.set(Some(parent)); + // Assign the ID to the existing node in the template self.mutations.push(AssignId { - path: &template.template.get().node_paths[idx][1..], + path: &parent.path.path[1..], id, }); @@ -503,7 +537,7 @@ impl<'b> VirtualDom { pub(super) fn create_component_node( &mut self, - template: &'b VNode<'b>, + parent: Option, component: &'b VComponent<'b>, ) -> usize { use RenderReturn::*; @@ -515,8 +549,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, parent), } } @@ -532,20 +569,17 @@ 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, parent: Option) -> usize { + let id = self.next_element(); self.mutations.push(Mutation::CreatePlaceholder { id }); placeholder.id.set(Some(id)); + placeholder.parent.set(parent); + 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 2de862218..58bd934b9 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,7 +1,10 @@ use crate::{ any_props::AnyProps, arena::ElementId, - innerlude::{BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText}, + innerlude::{ + BorrowedAttributeValue, DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, + VText, + }, mutations::Mutation, nodes::RenderReturn, nodes::{DynamicNode, VNode}, @@ -39,19 +42,27 @@ impl<'b> VirtualDom { (Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p), // Just move over the placeholder - (Aborted(l), Aborted(r)) => r.id.set(l.id.get()), + (Aborted(l), Aborted(r)) => { + r.id.set(l.id.get()); + r.parent.set(l.parent.get()) + } // 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], + l.parent.get().expect("root node should not be none"), + ), }; } 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)); + p.parent.set(l.parent.get()); self.mutations.push(Mutation::CreatePlaceholder { id }); let pre_edits = self.mutations.edits.len(); @@ -81,12 +92,24 @@ 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]); + let parent = left_template.parent.take(); + return self.replace(left_template, [right_template], parent); } } } } + // Copy over the parent + { + right_template.parent.set(left_template.parent.get()); + } + + // Update the bubble id pointer + right_template.stable_id.set(left_template.stable_id.get()); + if let Some(bubble_id) = right_template.stable_id.get() { + self.set_template(bubble_id, right_template); + } + // 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; @@ -105,12 +128,8 @@ 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 { @@ -123,8 +142,16 @@ 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 = ElementRef { + template: right_template.stable_id().unwrap(), + path: ElementPath { + path: left_template.template.get().node_paths[dyn_node_idx], + }, + scope: self.runtime.scope_stack.borrow().last().copied().unwrap(), + }; + self.diff_dynamic_node(left_node, right_node, current_ref); }); // Make sure the roots get transferred over while we're here @@ -135,30 +162,24 @@ 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: ElementRef, ) { 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), - (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), - (Fragment(left), Placeholder(right)) => self.node_to_placeholder(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()); + right.parent.set(left.parent.get()); + }, + (Component(left), Component(right)) => self.diff_vcomponent(left, right, Some(parent)), + (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right, parent), + (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right, parent), _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."), }; } @@ -179,7 +200,7 @@ impl<'b> VirtualDom { &mut self, left: &'b VComponent<'b>, right: &'b VComponent<'b>, - right_template: &'b VNode<'b>, + parent: Option, ) { if std::ptr::eq(left, right) { return; @@ -187,7 +208,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 @@ -228,11 +249,11 @@ impl<'b> VirtualDom { fn replace_vcomponent( &mut self, - right_template: &'b VNode<'b>, right: &'b VComponent<'b>, left: &'b VComponent<'b>, + parent: Option, ) { - let m = self.create_component_node(right_template, right); + let m = self.create_component_node(parent, right); let pre_edits = self.mutations.edits.len(); @@ -287,11 +308,12 @@ impl<'b> VirtualDom { /// } /// ``` fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) { + let parent = left.parent.take(); 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(|(l, r)| self.diff_vcomponent(l, r, parent)), } } @@ -299,11 +321,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 { @@ -312,7 +331,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: ElementRef, + ) { let new_is_keyed = new[0].key.is_some(); let old_is_keyed = old[0].key.is_some(); debug_assert!( @@ -325,9 +349,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); } } @@ -339,7 +363,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: ElementRef, + ) { use std::cmp::Ordering; // Handled these cases in `diff_children` before calling this function. @@ -348,7 +377,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 => {} } @@ -373,7 +404,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: ElementRef, + ) { if cfg!(debug_assertions) { let mut keys = rustc_hash::FxHashSet::default(); let mut assert_unique_keys = |children: &'b [VNode<'b>]| { @@ -401,7 +437,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, }; @@ -427,18 +463,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); } } @@ -451,6 +487,7 @@ impl<'b> VirtualDom { &mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>], + parent: ElementRef, ) -> Option<(usize, usize)> { let mut left_offset = 0; @@ -466,7 +503,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; } @@ -505,7 +542,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: ElementRef, + ) { /* 1. Map the old keys into a numerical ordering based on indices. 2. Create a map of old key to its index @@ -562,7 +604,7 @@ impl<'b> VirtualDom { if shared_keys.is_empty() { if old.first().is_some() { self.remove_nodes(&old[1..]); - self.replace(&old[0], new); + self.replace(&old[0], new, Some(parent)); } else { // I think this is wrong - why are we appending? // only valid of the if there are no trailing elements @@ -739,20 +781,38 @@ impl<'b> VirtualDom { .sum() } - fn create_children(&mut self, nodes: impl IntoIterator>) -> usize { + pub(crate) fn create_children( + &mut self, + nodes: impl IntoIterator>, + parent: Option, + ) -> usize { nodes .into_iter() - .fold(0, |acc, child| acc + self.create(child)) + .map(|child| { + self.assign_boundary_ref(parent, child); + self.create(child) + }) + .sum() } - 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: ElementRef, + ) { + let m = self.create_children(new, Some(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: ElementRef, + ) { + let m = self.create_children(new, Some(parent)); let id = self.find_last_element(after); self.mutations.push(Mutation::InsertAfter { id, m }) } @@ -762,15 +822,21 @@ impl<'b> VirtualDom { &mut self, l: &'b VPlaceholder, r: impl IntoIterator>, + parent: ElementRef, ) { - let m = self.create_children(r); + let m = self.create_children(r, Some(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: Option, + ) { + let m = self.create_children(right, parent); let pre_edits = self.mutations.edits.len(); @@ -789,11 +855,12 @@ impl<'b> VirtualDom { }; } - fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder) { + fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder, parent: ElementRef) { // 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)); + r.parent.set(Some(parent)); self.mutations .push(Mutation::CreatePlaceholder { id: placeholder }); @@ -831,6 +898,16 @@ impl<'b> VirtualDom { // Clean up the roots, assuming we need to generate mutations for these // This is done last in order to preserve Node ID reclaim order (reclaim in reverse order of claim) self.reclaim_roots(node, gen_muts); + + // Clean up the vnode id + self.reclaim_vnode_id(node); + } + + fn reclaim_vnode_id(&mut self, node: &'b VNode<'b>) { + // Clean up the vnode id + if let Some(id) = node.stable_id() { + self.element_refs.remove(id.0); + } } fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) { @@ -989,6 +1066,13 @@ impl<'b> VirtualDom { } } } + + pub(crate) fn assign_boundary_ref(&mut self, parent: Option, child: &'b VNode<'b>) { + if let Some(parent) = parent { + // assign the parent of the child + child.parent.set(Some(parent)); + } + } } /// Are the templates the same? diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index a9e919bfa..dbc8e8131 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -30,7 +30,8 @@ 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(), + stable_id: children.stable_id.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 78868d29f..9f3ce04a2 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,3 +1,4 @@ +use crate::innerlude::{ElementRef, VNodeId}; use crate::{ any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState, }; @@ -47,7 +48,10 @@ pub struct VNode<'a> { pub key: Option<&'a str>, /// When rendered, this template will be linked to its parent manually - pub parent: Option, + pub(crate) parent: Cell>, + + /// The bubble id assigned to the child that we need to update and drop when diffing happens + pub(crate) stable_id: Cell>, /// The static nodes and static descriptor of the template pub template: Cell>, @@ -68,7 +72,8 @@ impl<'a> VNode<'a> { pub fn empty(cx: &'a ScopeState) -> Element<'a> { Some(VNode { key: None, - parent: None, + parent: Default::default(), + stable_id: Default::default(), root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())), dynamic_nodes: &[], dynamic_attrs: &[], @@ -81,6 +86,30 @@ impl<'a> VNode<'a> { }) } + /// Create a new VNode + pub fn new( + key: Option<&'a str>, + template: Template<'static>, + root_ids: bumpalo::collections::Vec<'a, ElementId>, + dynamic_nodes: &'a [DynamicNode<'a>], + dynamic_attrs: &'a [Attribute<'a>], + ) -> Self { + Self { + key, + parent: Cell::new(None), + stable_id: Cell::new(None), + template: Cell::new(template), + root_ids: RefCell::new(root_ids), + dynamic_nodes, + dynamic_attrs, + } + } + + /// Get the stable id of this node used for bubbling events + pub(crate) fn stable_id(&self) -> Option { + self.stable_id.get() + } + /// Load a dynamic root at the given index /// /// Returns [`None`] if the root is actually a static node (Element/Text) @@ -319,7 +348,7 @@ pub struct VComponent<'a> { /// The function pointer of the component, known at compile time /// - /// It is possible that components get folded at comppile time, so these shouldn't be really used as a key + /// It is possible that components get folded at compile time, so these shouldn't be really used as a key pub(crate) render_fn: *const (), pub(crate) props: RefCell + 'a>>>, @@ -372,6 +401,8 @@ impl<'a> VText<'a> { pub struct VPlaceholder { /// The ID of this node in the real DOM pub(crate) id: Cell>, + /// The parent of this node + pub(crate) parent: Cell>, } impl VPlaceholder { @@ -722,7 +753,8 @@ 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(), + stable_id: self.stable_id.clone(), template: self.template.clone(), root_ids: self.root_ids.clone(), key: self.key, diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 397f5328e..962a55e86 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -36,6 +36,7 @@ impl VirtualDom { borrowed_props: Default::default(), attributes_to_drop_before_render: Default::default(), + element_refs_to_drop: Default::default(), })); let context = diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index f0bbd776c..40e6a91de 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -3,7 +3,7 @@ use crate::{ any_props::VProps, bump_frame::BumpFrame, innerlude::ErrorBoundary, - innerlude::{DynamicNode, EventHandler, VComponent, VText}, + innerlude::{DynamicNode, EventHandler, VComponent, VNodeId, VText}, lazynodes::LazyNodes, nodes::{IntoAttributeValue, IntoDynNode, RenderReturn}, runtime::Runtime, @@ -94,6 +94,7 @@ pub struct ScopeState { pub(crate) hook_idx: Cell, pub(crate) borrowed_props: RefCell>>, + pub(crate) element_refs_to_drop: RefCell>, pub(crate) attributes_to_drop_before_render: RefCell>>, pub(crate) props: Option>>, @@ -466,7 +467,7 @@ impl<'src> ScopeState { render_fn: component as *const (), static_props: P::IS_STATIC, props: RefCell::new(Some(extended)), - scope: Cell::new(None), + scope: Default::default(), }) } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 2d83ba63a..d1951b388 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -4,19 +4,19 @@ use crate::{ any_props::VProps, - arena::{ElementId, ElementRef}, - innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, + arena::ElementId, + innerlude::{DirtyScope, ElementRef, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, mutations::Mutation, nodes::RenderReturn, nodes::{Template, TemplateId}, runtime::{Runtime, RuntimeGuard}, scopes::{ScopeId, ScopeState}, - AttributeValue, Element, Event, Scope, + AttributeValue, Element, Event, Scope, VNode, }; use futures_util::{pin_mut, StreamExt}; use rustc_hash::{FxHashMap, FxHashSet}; use slab::Slab; -use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc}; +use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, ptr::NonNull, rc::Rc}; /// A virtual node system that progresses user events and diffs UI trees. /// @@ -186,7 +186,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>, @@ -263,6 +266,7 @@ impl VirtualDom { dirty_scopes: Default::default(), templates: Default::default(), elements: Default::default(), + element_refs: Default::default(), mutations: Mutations::default(), suspended_scopes: Default::default(), }; @@ -276,7 +280,7 @@ impl VirtualDom { root.provide_context(Rc::new(ErrorBoundary::new(ScopeId::ROOT))); // 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 } @@ -314,9 +318,9 @@ impl VirtualDom { } } - /// Call a listener inside the VirtualDom with data from outside the VirtualDom. + /// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an dynamic element, not a static node or a text node.** /// - /// This method will identify the appropriate element. The data must match up with the listener delcared. Note that + /// This method will identify the appropriate element. The data must match up with the listener declared. Note that /// this method does not give any indication as to the success of the listener call. If the listener is not found, /// nothing will happen. /// @@ -353,7 +357,15 @@ impl VirtualDom { | | | <-- no, broke early | <-- no, broke early */ - let mut parent_path = self.elements.get(element.0); + let parent_path = match self.elements.get(element.0) { + Some(Some(el)) => el, + _ => return, + }; + let mut parent_node = self + .element_refs + .get(parent_path.template.0) + .cloned() + .map(|el| (*parent_path, el)); let mut listeners = vec![]; // We will clone this later. The data itself is wrapped in RC to be used in callbacks if required @@ -365,36 +377,72 @@ impl VirtualDom { // If the event bubbles, we traverse through the tree until we find the target element. if bubbles { // 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 { + while let Some((path, el_ref)) = parent_node { // 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 = unsafe { el_ref.unwrap().as_ref() }; + let node_template = template.template.get(); + let target_path = path.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 { - let origin = el_ref.scope; + // 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 = path.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_node = template.parent.get().and_then(|element_ref| { + self.element_refs + .get(element_ref.template.0) + .cloned() + .map(|el| (element_ref, el)) + }); + } + } else { + // Otherwise, we just call the listener on the target element + if let Some((path, el_ref)) = parent_node { + // safety: we maintain references of all vnodes in the element slab + let template = unsafe { el_ref.unwrap().as_ref() }; + let node_template = template.template.get(); + let target_path = path.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 = path.scope; self.runtime.scope_stack.borrow_mut().push(origin); self.runtime.rendering.set(false); if let Some(cb) = listener.borrow_mut().as_deref_mut() { @@ -403,44 +451,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; } } } @@ -563,7 +574,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) => { tracing::debug!("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 }); } @@ -595,15 +606,12 @@ impl VirtualDom { /// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content pub async fn wait_for_suspense(&mut self) { loop { - // println!("waiting for suspense {:?}", self.suspended_scopes); if self.suspended_scopes.is_empty() { return; } - // println!("waiting for suspense"); self.wait_for_work().await; - // println!("Rendered immediately"); _ = self.render_immediate(); } } diff --git a/packages/core/tests/event_propagation.rs b/packages/core/tests/event_propagation.rs new file mode 100644 index 000000000..d694b1ff1 --- /dev/null +++ b/packages/core/tests/event_propagation.rs @@ -0,0 +1,69 @@ +use dioxus::prelude::*; +use dioxus_core::ElementId; +use std::{rc::Rc, sync::Mutex}; + +static CLICKS: Mutex = Mutex::new(0); + +#[test] +fn events_propagate() { + let mut dom = VirtualDom::new(app); + _ = dom.rebuild(); + + // Top-level click is registered + dom.handle_event("click", Rc::new(MouseData::default()), ElementId(1), true); + assert_eq!(*CLICKS.lock().unwrap(), 1); + + // break reference.... + for _ in 0..5 { + dom.mark_dirty(ScopeId(0)); + _ = dom.render_immediate(); + } + + // Lower click is registered + dom.handle_event("click", Rc::new(MouseData::default()), ElementId(2), true); + assert_eq!(*CLICKS.lock().unwrap(), 3); + + // break reference.... + for _ in 0..5 { + dom.mark_dirty(ScopeId(0)); + _ = dom.render_immediate(); + } + + // Stop propagation occurs + dom.handle_event("click", Rc::new(MouseData::default()), ElementId(2), true); + assert_eq!(*CLICKS.lock().unwrap(), 3); +} + +fn app(cx: Scope) -> Element { + render! { + div { + onclick: move |_| { + println!("top clicked"); + *CLICKS.lock().unwrap() += 1; + }, + + vec![ + render! { + problematic_child {} + } + ].into_iter() + } + } +} + +fn problematic_child(cx: Scope) -> Element { + render! { + button { + onclick: move |evt| { + println!("bottom clicked"); + let mut clicks = CLICKS.lock().unwrap(); + + if *clicks == 3 { + evt.stop_propagation(); + } else { + *clicks += 1; + } + } + } + } +} diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs index 1523c4e22..65d131eb8 100644 --- a/packages/core/tests/fuzzing.rs +++ b/packages/core/tests/fuzzing.rs @@ -2,7 +2,7 @@ use dioxus::prelude::Props; use dioxus_core::*; -use std::{cell::Cell, collections::HashSet}; +use std::{cfg, collections::HashSet}; fn random_ns() -> Option<&'static str> { let namespace = rand::random::() % 2; @@ -170,22 +170,23 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode { let range = if depth > 5 { 1 } else { 4 }; match rand::random::() % range { 0 => DynamicNode::Placeholder(Default::default()), - 1 => cx.make_node((0..(rand::random::() % 5)).map(|_| VNode { - key: None, - parent: Default::default(), - template: Cell::new(Template { - name: concat!(file!(), ":", line!(), ":", column!(), ":0"), - roots: &[TemplateNode::Dynamic { id: 0 }], - node_paths: &[&[0]], - attr_paths: &[], - }), - root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(), - dynamic_nodes: cx.bump().alloc([cx.component( - create_random_element, - DepthProps { depth, root: false }, - "create_random_element", - )]), - dynamic_attrs: &[], + 1 => cx.make_node((0..(rand::random::() % 5)).map(|_| { + VNode::new( + None, + Template { + name: concat!(file!(), ":", line!(), ":", column!(), ":0"), + roots: &[TemplateNode::Dynamic { id: 0 }], + node_paths: &[&[0]], + attr_paths: &[], + }, + bumpalo::collections::Vec::new_in(cx.bump()), + cx.bump().alloc([cx.component( + create_random_element, + DepthProps { depth, root: false }, + "create_random_element", + )]), + &[], + ) })), 2 => cx.component( create_random_element, @@ -271,13 +272,11 @@ fn create_random_element(cx: Scope) -> Element { ) .into_boxed_str(), )); - // println!("{template:#?}"); - let node = VNode { - key: None, - parent: None, - template: Cell::new(template), - root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(), - dynamic_nodes: { + let node = VNode::new( + None, + template, + bumpalo::collections::Vec::new_in(cx.bump()), + { let dynamic_nodes: Vec<_> = dynamic_node_types .iter() .map(|ty| match ty { @@ -291,12 +290,12 @@ fn create_random_element(cx: Scope) -> Element { .collect(); cx.bump().alloc(dynamic_nodes) }, - dynamic_attrs: cx.bump().alloc( + cx.bump().alloc( (0..template.attr_paths.len()) .map(|_| create_random_dynamic_attr(cx)) .collect::>(), ), - }; + ); Some(node) } _ => None, @@ -306,10 +305,10 @@ fn create_random_element(cx: Scope) -> Element { } // test for panics when creating random nodes and templates -#[cfg(not(miri))] #[test] fn create() { - for _ in 0..1000 { + let repeat_count = if cfg!(miri) { 100 } else { 1000 }; + for _ in 0..repeat_count { let mut vdom = VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true }); let _ = vdom.rebuild(); @@ -318,10 +317,10 @@ fn create() { // test for panics when diffing random nodes // This test will change the template every render which is not very realistic, but it helps stress the system -#[cfg(not(miri))] #[test] fn diff() { - for _ in 0..100000 { + let repeat_count = if cfg!(miri) { 100 } else { 1000 }; + for _ in 0..repeat_count { let mut vdom = VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true }); let _ = vdom.rebuild(); diff --git a/packages/fullstack/src/hooks/server_cached.rs b/packages/fullstack/src/hooks/server_cached.rs index b3f9113b5..82e755fc3 100644 --- a/packages/fullstack/src/hooks/server_cached.rs +++ b/packages/fullstack/src/hooks/server_cached.rs @@ -15,7 +15,6 @@ use serde::{de::DeserializeOwned, Serialize}; /// let state1 = use_state(cx, || server_cached(|| { /// 1234 /// })); -/// /// todo!() /// } /// ``` diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index 4f0a34b70..29c4fef9e 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -3,7 +3,6 @@ use dioxus_core::*; use dioxus_native_core::prelude::*; use dioxus_native_core_macro::partial_derive_state; use shipyard::Component; -use std::cell::Cell; fn random_ns() -> Option<&'static str> { let namespace = rand::random::() % 2; @@ -178,22 +177,23 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode { let range = if depth > 3 { 1 } else { 3 }; match rand::random::() % range { 0 => DynamicNode::Placeholder(Default::default()), - 1 => cx.make_node((0..(rand::random::() % 5)).map(|_| VNode { - key: None, - parent: Default::default(), - template: Cell::new(Template { - name: concat!(file!(), ":", line!(), ":", column!(), ":0"), - roots: &[TemplateNode::Dynamic { id: 0 }], - node_paths: &[&[0]], - attr_paths: &[], - }), - root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()).into(), - dynamic_nodes: cx.bump().alloc([cx.component( - create_random_element, - DepthProps { depth, root: false }, - "create_random_element", - )]), - dynamic_attrs: &[], + 1 => cx.make_node((0..(rand::random::() % 5)).map(|_| { + VNode::new( + None, + Template { + name: concat!(file!(), ":", line!(), ":", column!(), ":0"), + roots: &[TemplateNode::Dynamic { id: 0 }], + node_paths: &[&[0]], + attr_paths: &[], + }, + dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()), + cx.bump().alloc([cx.component( + create_random_element, + DepthProps { depth, root: false }, + "create_random_element", + )]), + &[], + ) })), 2 => cx.component( create_random_element, @@ -253,13 +253,11 @@ fn create_random_element(cx: Scope) -> Element { .into_boxed_str(), )); println!("{template:#?}"); - let node = VNode { - key: None, - parent: None, - template: Cell::new(template), - root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()) - .into(), - dynamic_nodes: { + let node = VNode::new( + None, + template, + dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()), + { let dynamic_nodes: Vec<_> = dynamic_node_types .iter() .map(|ty| match ty { @@ -273,12 +271,12 @@ fn create_random_element(cx: Scope) -> Element { .collect(); cx.bump().alloc(dynamic_nodes) }, - dynamic_attrs: cx.bump().alloc( + cx.bump().alloc( (0..template.attr_paths.len()) .map(|_| create_random_dynamic_attr(cx)) .collect::>(), ), - }; + ); Some(node) } _ => None, diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 458075b63..af188f907 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -252,14 +252,13 @@ impl<'a> ToTokens for TemplateRenderer<'a> { node_paths: &[ #(#node_paths),* ], attr_paths: &[ #(#attr_paths),* ], }; - ::dioxus::core::VNode { - parent: None, - 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(), - dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]), - dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]), - } + ::dioxus::core::VNode::new( + #key_tokens, + TEMPLATE, + dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()), + __cx.bump().alloc([ #( #node_printer ),* ]), + __cx.bump().alloc([ #( #dyn_attr_printer ),* ]), + ) }); } }