WIP split element from element ref

This commit is contained in:
Evan Almloff committed 2023-08-23 10:48:10 -05:00
1 parent 882c3c9dfb
commit e337aff0a4
7 files changed
+291 -223

No files matched your search

+42 -44
View File
@@ -13,62 +13,52 @@ use crate::{
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ElementId(pub usize); 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 // the pathway of the real element inside the template
pub path: ElementPath, pub(crate) path: ElementPath,
// The actual template // The actual template
pub template: Option<NonNull<VNode<'static>>>, pub(crate) template: NonNull<VNode<'static>>,
// The scope the element belongs to // The scope the element belongs to
pub scope: ScopeId, pub(crate) scope: ScopeId,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum ElementPath { pub struct ElementPath {
Deep(&'static [u8]), path: &'static [u8],
Root(usize),
}
impl ElementRef {
pub(crate) fn none() -> Self {
Self {
template: None,
path: ElementPath::Root(0),
scope: ScopeId(0),
}
}
} }
impl VirtualDom { impl VirtualDom {
pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId { pub(crate) fn next_element(&mut self) -> ElementId {
self.next_reference(template, ElementPath::Deep(path)) ElementId(self.elements.insert(None))
} }
pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId { pub(crate) fn next_element_ref(&mut self, template: &VNode, path: &'static [u8]) -> BubbleId {
self.next_reference(template, ElementPath::Root(path)) self.next_reference(template, ElementPath { path })
} }
pub(crate) fn next_null(&mut self) -> ElementId { fn next_reference(&mut self, template: &VNode, path: ElementPath) -> BubbleId {
let entry = self.elements.vacant_entry(); let entry = self.element_refs.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();
let id = entry.key(); let id = entry.key();
let scope = self.runtime.current_scope_id().unwrap_or(ScopeId(0)); let scope = self.runtime.current_scope_id().unwrap_or(ScopeId(0));
entry.insert(ElementRef { entry.insert(ElementRef {
// We know this is non-null because it comes from a reference // 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, path,
scope, scope,
}); });
ElementId(id) BubbleId(id)
} }
pub(crate) fn reclaim(&mut self, el: ElementId) { pub(crate) fn reclaim(&mut self, el: ElementId) {
@@ -76,7 +66,7 @@ impl VirtualDom {
.unwrap_or_else(|| panic!("cannot reclaim {:?}", el)); .unwrap_or_else(|| panic!("cannot reclaim {:?}", el));
} }
pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<ElementRef> { pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<()> {
if el.0 == 0 { if el.0 == 0 {
panic!( panic!(
"Cannot reclaim the root element - {:#?}", "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) { 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 _; 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 // Drop a scope and all its children
@@ -182,18 +186,12 @@ impl VirtualDom {
impl ElementPath { impl ElementPath {
pub(crate) fn is_decendant(&self, small: &&[u8]) -> bool { pub(crate) fn is_decendant(&self, small: &&[u8]) -> bool {
match *self { small.len() <= self.path.len() && *small == &self.path[..small.len()]
ElementPath::Deep(big) => small.len() <= big.len() && *small == &big[..small.len()],
ElementPath::Root(r) => small.len() == 1 && small[0] == r as u8,
}
} }
} }
impl PartialEq<&[u8]> for ElementPath { impl PartialEq<&[u8]> for ElementPath {
fn eq(&self, other: &&[u8]) -> bool { fn eq(&self, other: &&[u8]) -> bool {
match *self { self.path.eq(*other)
ElementPath::Deep(deep) => deep.eq(*other),
ElementPath::Root(r) => other.len() == 1 && other[0] == r as u8,
}
} }
} }
+39 -41
View File
@@ -1,4 +1,5 @@
use crate::any_props::AnyProps; use crate::any_props::AnyProps;
use crate::diff::BoundaryRef;
use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText}; use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText};
use crate::mutations::Mutation; use crate::mutations::Mutation;
use crate::mutations::Mutation::*; use crate::mutations::Mutation::*;
@@ -181,15 +182,15 @@ impl<'b> VirtualDom {
use DynamicNode::*; use DynamicNode::*;
match &template.dynamic_nodes[idx] { match &template.dynamic_nodes[idx] {
node @ Component { .. } | node @ Fragment(_) => { node @ Component { .. } | node @ Fragment(_) => {
self.create_dynamic_node(template, node, idx) self.create_dynamic_node(template, idx, node, idx)
} }
Placeholder(VPlaceholder { id }) => { Placeholder(VPlaceholder { id }) => {
let id = self.set_slot(template, id, idx); let id = self.set_slot(id);
self.mutations.push(CreatePlaceholder { id }); self.mutations.push(CreatePlaceholder { id });
1 1
} }
Text(VText { id, value }) => { Text(VText { id, value }) => {
let id = self.set_slot(template, id, idx); let id = self.set_slot(id);
self.create_static_text(value, id); self.create_static_text(value, id);
1 1
} }
@@ -265,7 +266,7 @@ impl<'b> VirtualDom {
.map(|sorted_index| dynamic_nodes[sorted_index].0); .map(|sorted_index| dynamic_nodes[sorted_index].0);
for idx in reversed_iter { 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 { if m > 0 {
// The path is one shorter because the top node is the root // The path is one shorter because the top node is the root
let path = &template.template.get().node_paths[idx][1..]; let path = &template.template.get().node_paths[idx][1..];
@@ -279,15 +280,15 @@ impl<'b> VirtualDom {
attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>, attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
root_idx: u8, root_idx: u8,
root: ElementId, root: ElementId,
node: &VNode, node: &'b VNode<'b>,
) { ) {
while let Some((mut attr_id, path)) = while let Some((mut attr_id, path)) =
attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx)) 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 { 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) // Only push the dynamic attributes forward if they match the current path (same element)
match attrs.next_if(|(_, p)| *p == path) { 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 // Make sure we set the attribute's associated id
attribute.mounted_element.set(id); attribute.mounted_element.set(id);
@@ -307,6 +314,8 @@ impl<'b> VirtualDom {
match &attribute.value { match &attribute.value {
AttributeValue::Listener(_) => { AttributeValue::Listener(_) => {
let path = &template.template.get().attr_paths[idx];
self.set_template(id, template, path);
self.mutations.push(NewEventListener { self.mutations.push(NewEventListener {
// all listeners start with "on" // all listeners start with "on"
name: &unbounded_name[2..], name: &unbounded_name[2..],
@@ -330,7 +339,7 @@ impl<'b> VirtualDom {
fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId { fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId {
// Get an ID for this root since it's a real root // 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; template.root_ids.borrow_mut()[root_idx] = this_id;
self.mutations.push(LoadTemplate { self.mutations.push(LoadTemplate {
@@ -353,8 +362,6 @@ impl<'b> VirtualDom {
&mut self, &mut self,
path: &'static [u8], path: &'static [u8],
this_id: ElementId, this_id: ElementId,
template: &VNode,
attr_id: usize,
) -> ElementId { ) -> ElementId {
if path.len() == 1 { if path.len() == 1 {
return this_id; return this_id;
@@ -362,7 +369,7 @@ impl<'b> VirtualDom {
// if attribute is on a root node, then we've already created the element // 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 // 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 { self.mutations.push(Mutation::AssignId {
path: &path[1..], path: &path[1..],
@@ -439,27 +446,21 @@ impl<'b> VirtualDom {
pub(crate) fn create_dynamic_node( pub(crate) fn create_dynamic_node(
&mut self, &mut self,
template: &'b VNode<'b>, parent: BoundaryRef<'b>,
node: &'b DynamicNode<'b>, node: &'b DynamicNode<'b>,
idx: usize,
) -> usize { ) -> usize {
use DynamicNode::*; use DynamicNode::*;
match node { match node {
Text(text) => self.create_dynamic_text(template, text, idx), Text(text) => self.create_dynamic_text(parent, text),
Placeholder(place) => self.create_placeholder(place, template, idx), Placeholder(place) => self.create_placeholder(place, parent),
Component(component) => self.create_component_node(template, component), Component(component) => self.create_component_node(parent, component),
Fragment(frag) => frag.iter().map(|child| self.create(child)).sum(), Fragment(frag) => frag.iter().map(|child| self.create(child)).sum(),
} }
} }
fn create_dynamic_text( fn create_dynamic_text(&mut self, parent: BoundaryRef<'b>, text: &'b VText<'b>) -> usize {
&mut self,
template: &'b VNode<'b>,
text: &'b VText<'b>,
idx: usize,
) -> usize {
// Allocate a dynamic element reference for this text node // 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 // Make sure the text node is assigned to the correct element
text.id.set(Some(new_id)); text.id.set(Some(new_id));
@@ -470,7 +471,7 @@ impl<'b> VirtualDom {
// Add the mutation to the list // Add the mutation to the list
self.mutations.push(HydrateText { self.mutations.push(HydrateText {
id: new_id, id: new_id,
path: &template.template.get().node_paths[idx][1..], path: &parent.parent.template.get().node_paths[parent.dyn_node_idx][1..],
value, value,
}); });
@@ -481,18 +482,17 @@ impl<'b> VirtualDom {
pub(crate) fn create_placeholder( pub(crate) fn create_placeholder(
&mut self, &mut self,
placeholder: &VPlaceholder, placeholder: &VPlaceholder,
template: &'b VNode<'b>, parent: BoundaryRef<'b>,
idx: usize,
) -> usize { ) -> usize {
// Allocate a dynamic element reference for this text node // 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 // Make sure the text node is assigned to the correct element
placeholder.id.set(Some(id)); placeholder.id.set(Some(id));
// Assign the ID to the existing node in the template // Assign the ID to the existing node in the template
self.mutations.push(AssignId { 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, id,
}); });
@@ -502,7 +502,7 @@ impl<'b> VirtualDom {
pub(super) fn create_component_node( pub(super) fn create_component_node(
&mut self, &mut self,
template: &'b VNode<'b>, parent: BoundaryRef<'b>,
component: &'b VComponent<'b>, component: &'b VComponent<'b>,
) -> usize { ) -> usize {
use RenderReturn::*; use RenderReturn::*;
@@ -514,8 +514,11 @@ impl<'b> VirtualDom {
match unsafe { self.run_scope(scope).extend_lifetime_ref() } { match unsafe { self.run_scope(scope).extend_lifetime_ref() } {
// Create the component's root element // Create the component's root element
Ready(t) => self.create_scope(scope, t), Ready(t) => {
Aborted(t) => self.mount_aborted(template, 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()) .unwrap_or_else(|| component.scope.get().unwrap())
} }
fn mount_aborted(&mut self, parent: &'b VNode<'b>, placeholder: &VPlaceholder) -> usize { fn mount_aborted(&mut self, placeholder: &VPlaceholder) -> usize {
let id = self.next_element(parent, &[]); let id = self.next_element();
self.mutations.push(Mutation::CreatePlaceholder { id }); self.mutations.push(Mutation::CreatePlaceholder { id });
placeholder.id.set(Some(id)); placeholder.id.set(Some(id));
1 1
} }
fn set_slot( fn set_slot(&mut self, slot: &'b Cell<Option<ElementId>>) -> ElementId {
&mut self, let id = self.next_element();
template: &'b VNode<'b>,
slot: &'b Cell<Option<ElementId>>,
id: usize,
) -> ElementId {
let id = self.next_element(template, template.template.get().node_paths[id]);
slot.set(Some(id)); slot.set(Some(id));
id id
} }
+131 -69
View File
@@ -7,7 +7,7 @@ use crate::{
nodes::{DynamicNode, VNode}, nodes::{DynamicNode, VNode},
scopes::ScopeId, scopes::ScopeId,
virtual_dom::VirtualDom, virtual_dom::VirtualDom,
Attribute, TemplateNode, Attribute, AttributeValue, TemplateNode,
}; };
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
@@ -43,14 +43,14 @@ impl<'b> VirtualDom {
// Placeholder becomes something // Placeholder becomes something
// We should also clear the error now // 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(); self.runtime.scope_stack.borrow_mut().pop();
} }
fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) { 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.id.set(Some(id));
self.mutations.push(Mutation::CreatePlaceholder { id }); self.mutations.push(Mutation::CreatePlaceholder { id });
@@ -81,12 +81,17 @@ impl<'b> VirtualDom {
if let Some(&template) = map.get(&byte_index) { if let Some(&template) = map.get(&byte_index) {
right_template.template.set(template); right_template.template.set(template);
if template != left_template.template.get() { 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 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) { if templates_are_the_same(left_template, right_template) {
return; return;
@@ -94,7 +99,7 @@ impl<'b> VirtualDom {
// If the templates are different by name, we need to replace the entire template // If the templates are different by name, we need to replace the entire template
if templates_are_different(left_template, right_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 // 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()) .zip(right_template.dynamic_attrs.iter())
.for_each(|(left_attr, right_attr)| { .for_each(|(left_attr, right_attr)| {
// Move over the ID from the old to the new // Move over the ID from the old to the new
right_attr let mounted_element = left_attr.mounted_element.get();
.mounted_element right_attr.mounted_element.set(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);
// If the attributes are different (or volatile), we need to update them // 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); self.update_attribute(right_attr, left_attr);
} }
}); });
@@ -123,8 +127,13 @@ impl<'b> VirtualDom {
.dynamic_nodes .dynamic_nodes
.iter() .iter()
.zip(right_template.dynamic_nodes.iter()) .zip(right_template.dynamic_nodes.iter())
.for_each(|(left_node, right_node)| { .enumerate()
self.diff_dynamic_node(left_node, right_node, right_template); .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 // Make sure the roots get transferred over while we're here
@@ -135,29 +144,20 @@ impl<'b> VirtualDom {
right.push(element); 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( fn diff_dynamic_node(
&mut self, &mut self,
left_node: &'b DynamicNode<'b>, left_node: &'b DynamicNode<'b>,
right_node: &'b DynamicNode<'b>, right_node: &'b DynamicNode<'b>,
node: &'b VNode<'b>, parent: BoundaryRef<'b>,
) { ) {
match (left_node, right_node) { match (left_node, right_node) {
(Text(left), Text(right)) => self.diff_vtext(left, right, node), (Text(left), Text(right)) => self.diff_vtext(left, right),
(Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right), (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right, parent),
(Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()), (Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()),
(Component(left), Component(right)) => self.diff_vcomponent(left, right, node), (Component(left), Component(right)) => self.diff_vcomponent(left, right, parent),
(Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right), (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right, parent),
(Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right), (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."), _ => 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, &mut self,
left: &'b VComponent<'b>, left: &'b VComponent<'b>,
right: &'b VComponent<'b>, right: &'b VComponent<'b>,
right_template: &'b VNode<'b>, parent: BoundaryRef<'b>,
) { ) {
if std::ptr::eq(left, right) { if std::ptr::eq(left, right) {
return; return;
@@ -187,7 +187,7 @@ impl<'b> VirtualDom {
// Replace components that have different render fns // Replace components that have different render fns
if left.render_fn != right.render_fn { 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 // Make sure the new vcomponent has the right scopeid associated to it
@@ -222,11 +222,11 @@ impl<'b> VirtualDom {
fn replace_vcomponent( fn replace_vcomponent(
&mut self, &mut self,
right_template: &'b VNode<'b>,
right: &'b VComponent<'b>, right: &'b VComponent<'b>,
left: &'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(); let pre_edits = self.mutations.edits.len();
@@ -280,12 +280,17 @@ impl<'b> VirtualDom {
/// Component { ..props } /// 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) { match matching_components(left, right) {
None => self.replace(left, [right]), None => self.replace(left, [right], parent),
Some(components) => components Some(components) => components
.into_iter() .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 /// 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. /// different.
fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>, node: &'b VNode<'b>) { fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) {
let id = left let id = left.id.get().unwrap_or_else(|| self.next_element());
.id
.get()
.unwrap_or_else(|| self.next_element(node, &[0]));
right.id.set(Some(id)); right.id.set(Some(id));
if left.value != right.value { 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 new_is_keyed = new[0].key.is_some();
let old_is_keyed = old[0].key.is_some(); let old_is_keyed = old[0].key.is_some();
debug_assert!( debug_assert!(
@@ -319,9 +326,9 @@ impl<'b> VirtualDom {
); );
if new_is_keyed && old_is_keyed { if new_is_keyed && old_is_keyed {
self.diff_keyed_children(old, new); self.diff_keyed_children(old, new, parent);
} else { } else {
self.diff_non_keyed_children(old, new); self.diff_non_keyed_children(old, new, parent);
} }
} }
@@ -333,7 +340,12 @@ impl<'b> VirtualDom {
// [... parent] // [... parent]
// //
// the change list stack is in the same state when this function returns. // 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; use std::cmp::Ordering;
// Handled these cases in `diff_children` before calling this function. // Handled these cases in `diff_children` before calling this function.
@@ -342,7 +354,9 @@ impl<'b> VirtualDom {
match old.len().cmp(&new.len()) { match old.len().cmp(&new.len()) {
Ordering::Greater => self.remove_nodes(&old[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 => {} Ordering::Equal => {}
} }
@@ -367,7 +381,12 @@ impl<'b> VirtualDom {
// https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739 // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
// //
// The stack is empty upon entry. // 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) { if cfg!(debug_assertions) {
let mut keys = rustc_hash::FxHashSet::default(); let mut keys = rustc_hash::FxHashSet::default();
let mut assert_unique_keys = |children: &'b [VNode<'b>]| { 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 // `shared_prefix_count` is the count of how many nodes at the start of
// `new` and `old` share the same keys. // `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, Some(count) => count,
None => return, None => return,
}; };
@@ -421,18 +440,18 @@ impl<'b> VirtualDom {
if left_offset == 0 { if left_offset == 0 {
// insert at the beginning of the old list // insert at the beginning of the old list
let foothold = &old[old.len() - right_offset]; 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 { } else if right_offset == 0 {
// insert at the end the old list // insert at the end the old list
let foothold = old.last().unwrap(); let foothold = old.last().unwrap();
self.create_and_insert_after(new_middle, foothold); self.create_and_insert_after(new_middle, foothold, parent);
} else { } else {
// inserting in the middle // inserting in the middle
let foothold = &old[left_offset - 1]; let foothold = &old[left_offset - 1];
self.create_and_insert_after(new_middle, foothold); self.create_and_insert_after(new_middle, foothold, parent);
} }
} else { } 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, &mut self,
old: &'b [VNode<'b>], old: &'b [VNode<'b>],
new: &'b [VNode<'b>], new: &'b [VNode<'b>],
parent: BoundaryRef<'b>,
) -> Option<(usize, usize)> { ) -> Option<(usize, usize)> {
let mut left_offset = 0; 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 // If that was all of the old children, then create and append the remaining
// new children and we're finished. // new children and we're finished.
if left_offset == old.len() { 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; return None;
} }
@@ -499,7 +519,12 @@ impl<'b> VirtualDom {
// //
// Upon exit from this function, it will be restored to that same self. // Upon exit from this function, it will be restored to that same self.
#[allow(clippy::too_many_lines)] #[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. 1. Map the old keys into a numerical ordering based on indices.
2. Create a map of old key to its index 2. Create a map of old key to its index
@@ -556,7 +581,7 @@ impl<'b> VirtualDom {
if shared_keys.is_empty() { if shared_keys.is_empty() {
if old.get(0).is_some() { if old.get(0).is_some() {
self.remove_nodes(&old[1..]); self.remove_nodes(&old[1..]);
self.replace(&old[0], new); self.replace(&old[0], new, parent);
} else { } else {
// I think this is wrong - why are we appending? // I think this is wrong - why are we appending?
// only valid of the if there are no trailing elements // only valid of the if there are no trailing elements
@@ -734,20 +759,35 @@ impl<'b> VirtualDom {
.sum() .sum()
} }
fn create_children(&mut self, nodes: impl IntoIterator<Item = &'b VNode<'b>>) -> usize { fn create_children(
nodes &mut self,
.into_iter() nodes: impl IntoIterator<Item = &'b VNode<'b>>,
.fold(0, |acc, child| acc + self.create(child)) 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>) { fn create_and_insert_before(
let m = self.create_children(new); &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); let id = self.find_first_element(before);
self.mutations.push(Mutation::InsertBefore { id, m }) self.mutations.push(Mutation::InsertBefore { id, m })
} }
fn create_and_insert_after(&mut self, new: &'b [VNode<'b>], after: &'b VNode<'b>) { fn create_and_insert_after(
let m = self.create_children(new); &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); let id = self.find_last_element(after);
self.mutations.push(Mutation::InsertAfter { id, m }) self.mutations.push(Mutation::InsertAfter { id, m })
} }
@@ -757,15 +797,21 @@ impl<'b> VirtualDom {
&mut self, &mut self,
l: &'b VPlaceholder, l: &'b VPlaceholder,
r: impl IntoIterator<Item = &'b VNode<'b>>, r: impl IntoIterator<Item = &'b VNode<'b>>,
parent: BoundaryRef<'b>,
) { ) {
let m = self.create_children(r); let m = self.create_children(r, parent);
let id = l.id.get().unwrap(); let id = l.id.get().unwrap();
self.mutations.push(Mutation::ReplaceWith { id, m }); self.mutations.push(Mutation::ReplaceWith { id, m });
self.reclaim(id); self.reclaim(id);
} }
fn replace(&mut self, left: &'b VNode<'b>, right: impl IntoIterator<Item = &'b VNode<'b>>) { fn replace(
let m = self.create_children(right); &mut self,
left: &'b VNode<'b>,
right: impl IntoIterator<Item = &'b VNode<'b>>,
parent: BoundaryRef<'b>,
) {
let m = self.create_children(right, parent);
let pre_edits = self.mutations.edits.len(); 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) { 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 // 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.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? /// Are the templates the same?
@@ -1005,7 +1060,7 @@ fn templates_are_different(left_template: &VNode, right_template: &VNode) -> boo
fn matching_components<'a>( fn matching_components<'a>(
left: &'a VNode<'a>, left: &'a VNode<'a>,
right: &'a VNode<'a>, right: &'a VNode<'a>,
) -> Option<Vec<(&'a VComponent<'a>, &'a VComponent<'a>)>> { ) -> Option<Vec<(usize, &'a VComponent<'a>, &'a VComponent<'a>)>> {
let left_template = left.template.get(); let left_template = left.template.get();
let right_template = right.template.get(); let right_template = right.template.get();
if left_template.roots.len() != right_template.roots.len() { if left_template.roots.len() != right_template.roots.len() {
@@ -1017,7 +1072,8 @@ fn matching_components<'a>(
.roots .roots
.iter() .iter()
.zip(right_template.roots.iter()) .zip(right_template.roots.iter())
.map(|(l, r)| { .enumerate()
.map(|(idx, (l, r))| {
let (l, r) = match (l, r) { let (l, r) = match (l, r) {
(TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r), (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
_ => return None, _ => return None,
@@ -1028,7 +1084,7 @@ fn matching_components<'a>(
_ => return None, _ => return None,
}; };
Some((l, r)) Some((idx, l, r))
}) })
.collect() .collect()
} }
@@ -1060,3 +1116,9 @@ fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
_ => false, _ => false,
} }
} }
// A parent that can be used for bubbling
pub(crate) struct BoundaryRef<'b> {
pub parent: &'b VNode<'b>,
pub dyn_node_idx: usize,
}
+1 -1
View File
@@ -30,7 +30,7 @@ pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
let children = cx.props.0.as_ref()?; let children = cx.props.0.as_ref()?;
Some(VNode { Some(VNode {
key: children.key, key: children.key,
parent: children.parent, parent: children.parent.clone(),
template: children.template.clone(), template: children.template.clone(),
root_ids: children.root_ids.clone(), root_ids: children.root_ids.clone(),
dynamic_nodes: children.dynamic_nodes, dynamic_nodes: children.dynamic_nodes,
+4 -3
View File
@@ -1,3 +1,4 @@
use crate::innerlude::BubbleId;
use crate::{ use crate::{
any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState, any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
}; };
@@ -47,7 +48,7 @@ pub struct VNode<'a> {
pub key: Option<&'a str>, pub key: Option<&'a str>,
/// When rendered, this template will be linked to its parent manually /// When rendered, this template will be linked to its parent manually
pub parent: Option<ElementId>, pub parent: Cell<Option<BubbleId>>,
/// The static nodes and static descriptor of the template /// The static nodes and static descriptor of the template
pub template: Cell<Template<'static>>, pub template: Cell<Template<'static>>,
@@ -68,7 +69,7 @@ impl<'a> VNode<'a> {
pub fn empty(cx: &'a ScopeState) -> Element<'a> { pub fn empty(cx: &'a ScopeState) -> Element<'a> {
Some(VNode { Some(VNode {
key: None, key: None,
parent: None, parent: Default::default(),
root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())), root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())),
dynamic_nodes: &[], dynamic_nodes: &[],
dynamic_attrs: &[], dynamic_attrs: &[],
@@ -722,7 +723,7 @@ impl<'b> IntoDynNode<'b> for Arguments<'_> {
impl<'a> IntoDynNode<'a> for &'a VNode<'a> { impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
DynamicNode::Fragment(_cx.bump().alloc([VNode { DynamicNode::Fragment(_cx.bump().alloc([VNode {
parent: self.parent, parent: self.parent.clone(),
template: self.template.clone(), template: self.template.clone(),
root_ids: self.root_ids.clone(), root_ids: self.root_ids.clone(),
key: self.key, key: self.key,
+73 -64
View File
@@ -5,7 +5,7 @@
use crate::{ use crate::{
any_props::VProps, any_props::VProps,
arena::{ElementId, ElementRef}, arena::{ElementId, ElementRef},
innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, innerlude::{BubbleId, DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
mutations::Mutation, mutations::Mutation,
nodes::RenderReturn, nodes::RenderReturn,
nodes::{Template, TemplateId}, nodes::{Template, TemplateId},
@@ -183,7 +183,10 @@ pub struct VirtualDom {
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>, pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
// Every element is actually a dual reference - one to the template and the other to the dynamic node in that template // 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<ElementRef>, pub(crate) element_refs: Slab<ElementRef>,
// The element ids that are used in the renderer
pub(crate) elements: Slab<Option<BubbleId>>,
pub(crate) mutations: Mutations<'static>, pub(crate) mutations: Mutations<'static>,
@@ -260,6 +263,7 @@ impl VirtualDom {
dirty_scopes: Default::default(), dirty_scopes: Default::default(),
templates: Default::default(), templates: Default::default(),
elements: Default::default(), elements: Default::default(),
element_refs: Default::default(),
mutations: Mutations::default(), mutations: Mutations::default(),
suspended_scopes: Default::default(), suspended_scopes: Default::default(),
}; };
@@ -273,7 +277,7 @@ impl VirtualDom {
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0)))); 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 // 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 dom
} }
@@ -349,7 +353,13 @@ impl VirtualDom {
| | | <-- no, broke early | | | <-- no, broke early
| <-- 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![]; let mut listeners = vec![];
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required // 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. // 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(el_ref) = parent_path {
// safety: we maintain references of all vnodes in the element slab // safety: we maintain references of all vnodes in the element slab
if let Some(template) = el_ref.template { let template = el_ref.template;
let template = unsafe { template.as_ref() }; let template = unsafe { template.as_ref() };
let node_template = template.template.get(); let node_template = template.template.get();
let target_path = el_ref.path; let target_path = el_ref.path;
for (idx, attr) in template.dynamic_attrs.iter().enumerate() { for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
let this_path = node_template.attr_paths[idx]; let this_path = node_template.attr_paths[idx];
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
if attr.name.trim_start_matches("on") == name if attr.name.trim_start_matches("on") == name
&& target_path.is_decendant(&this_path) && target_path.is_decendant(&this_path)
{ {
listeners.push(&attr.value); listeners.push(&attr.value);
// Break if this is the exact target element. // 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 // 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 // documented, or be rejected from the rsx! macro outright
if target_path == this_path { if target_path == this_path {
break; break;
}
} }
} }
}
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order // 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 // We check the bubble state between each call to see if the event has been stopped from bubbling
for listener in listeners.drain(..).rev() { for listener in listeners.drain(..).rev() {
if let AttributeValue::Listener(listener) = listener { 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; let origin = el_ref.scope;
self.runtime.scope_stack.borrow_mut().push(origin); self.runtime.scope_stack.borrow_mut().push(origin);
self.runtime.rendering.set(false); self.runtime.rendering.set(false);
@@ -399,44 +445,7 @@ impl VirtualDom {
self.runtime.scope_stack.borrow_mut().pop(); self.runtime.scope_stack.borrow_mut().pop();
self.runtime.rendering.set(true); self.runtime.rendering.set(true);
if !uievent.propagates.get() { break;
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;
}
} }
} }
} }
@@ -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 // If an error occurs, we should try to render the default error component and context where the error occured
RenderReturn::Aborted(placeholder) => { RenderReturn::Aborted(placeholder) => {
log::info!("Ran into suspended or aborted scope during rebuild"); 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)); placeholder.id.set(Some(id));
self.mutations.push(Mutation::CreatePlaceholder { id }); self.mutations.push(Mutation::CreatePlaceholder { id });
} }
+1 -1
View File
@@ -245,7 +245,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
attr_paths: &[ #(#attr_paths),* ], attr_paths: &[ #(#attr_paths),* ],
}; };
::dioxus::core::VNode { ::dioxus::core::VNode {
parent: None, parent: Default::default(),
key: #key_tokens, key: #key_tokens,
template: std::cell::Cell::new(TEMPLATE), template: std::cell::Cell::new(TEMPLATE),
root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()).into(), root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()).into(),