From aed29b1decdec51e6dec8c074a087861bc60d9d0 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 9 Jan 2024 10:09:08 -0600 Subject: [PATCH] separate mount information from VNodes --- packages/core/src/create.rs | 615 --------------- packages/core/src/diff.rs | 1119 --------------------------- packages/core/src/diff/component.rs | 149 ++++ packages/core/src/diff/iterator.rs | 429 ++++++++++ packages/core/src/diff/mod.rs | 213 +++++ packages/core/src/diff/node.rs | 988 +++++++++++++++++++++++ packages/core/src/error_boundary.rs | 2 - packages/core/src/lib.rs | 1 - packages/core/src/nodes.rs | 212 ++--- packages/core/src/virtual_dom.rs | 29 +- 10 files changed, 1913 insertions(+), 1844 deletions(-) delete mode 100644 packages/core/src/create.rs delete mode 100644 packages/core/src/diff.rs create mode 100644 packages/core/src/diff/component.rs create mode 100644 packages/core/src/diff/iterator.rs create mode 100644 packages/core/src/diff/mod.rs create mode 100644 packages/core/src/diff/node.rs diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs deleted file mode 100644 index 050b50283..000000000 --- a/packages/core/src/create.rs +++ /dev/null @@ -1,615 +0,0 @@ -use crate::innerlude::{ElementPath, ElementRef, VComponent, VPlaceholder, VText, WriteMutations}; -use crate::nodes::VNode; -use crate::nodes::{DynamicNode, TemplateNode}; -use crate::virtual_dom::VirtualDom; -use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, Template}; -use std::cell::Cell; -use std::iter::Peekable; -use TemplateNode::*; - -#[cfg(debug_assertions)] -fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> { - let mut with_indecies = paths.iter().copied().enumerate().collect::>(); - with_indecies.sort_unstable_by(|(_, a), (_, b)| { - let mut a = a.iter(); - let mut b = b.iter(); - loop { - match (a.next(), b.next()) { - (Some(a), Some(b)) => { - if a != b { - return a.cmp(b); - } - } - // The shorter path goes first - (None, Some(_)) => return std::cmp::Ordering::Less, - (Some(_), None) => return std::cmp::Ordering::Greater, - (None, None) => return std::cmp::Ordering::Equal, - } - } - }); - with_indecies -} - -#[test] -#[cfg(debug_assertions)] -fn sorting() { - let r: [(usize, &[u8]); 5] = [ - (0, &[0, 1]), - (1, &[0, 2]), - (2, &[1, 0]), - (3, &[1, 0, 1]), - (4, &[1, 2]), - ]; - assert_eq!( - sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 0, 1,], &[1, 2,],]), - r - ); - let r: [(usize, &[u8]); 6] = [ - (0, &[0]), - (1, &[0, 1]), - (2, &[0, 1, 2]), - (3, &[1]), - (4, &[1, 2]), - (5, &[2]), - ]; - assert_eq!( - sort_bfs(&[&[0], &[0, 1], &[0, 1, 2], &[1], &[1, 2], &[2],]), - r - ); -} - -impl VirtualDom { - /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer. - /// - /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created. - pub(crate) fn create_scope( - &mut self, - scope: ScopeId, - template: &VNode, - to: &mut impl WriteMutations, - ) -> usize { - self.runtime.scope_stack.borrow_mut().push(scope); - let nodes = self.create(template, to); - self.runtime.scope_stack.borrow_mut().pop(); - nodes - } - - /// Create this template and write its mutations - pub(crate) fn create(&mut self, node: &VNode, to: &mut impl WriteMutations) -> usize { - // check for a overridden template - #[cfg(debug_assertions)] - { - let (path, byte_index) = node.template.get().name.rsplit_once(':').unwrap(); - if let Some(template) = self - .templates - .get(path) - .and_then(|map| map.get(&byte_index.parse().unwrap())) - { - node.template.set(*template); - - // Initialize the root nodes slice if it was changed - let mut nodes_mut = node.root_ids.borrow_mut(); - let len = node.template.get().roots.len(); - if nodes_mut.len() != len { - *nodes_mut = vec![ElementId::default(); len].into_boxed_slice(); - }; - } - }; - - // 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(), to); - - // Walk the roots, creating nodes and assigning IDs - // nodes in an iterator of ((dynamic_node_index, sorted_index), path) - // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS) - #[cfg(not(debug_assertions))] - let (mut attrs, mut nodes) = ( - node.template - .get() - .attr_paths - .iter() - .copied() - .enumerate() - .peekable(), - node.template - .get() - .node_paths - .iter() - .copied() - .enumerate() - .map(|(i, path)| ((i, i), path)) - .peekable(), - ); - // If this is a debug build, we need to check that the paths are in the correct order because hot reloading can cause scrambled states - - #[cfg(debug_assertions)] - let (attrs_sorted, nodes_sorted) = { - ( - sort_bfs(node.template.get().attr_paths), - sort_bfs(node.template.get().node_paths), - ) - }; - #[cfg(debug_assertions)] - let (mut attrs, mut nodes) = { - ( - attrs_sorted.into_iter().peekable(), - nodes_sorted - .iter() - .copied() - .enumerate() - .map(|(i, (id, path))| ((id, i), path)) - .peekable(), - ) - }; - - node.template - .get() - .roots - .iter() - .enumerate() - .map(|(idx, root)| match root { - DynamicText { id } | Dynamic { id } => { - nodes.next().unwrap(); - self.write_dynamic_root(node, *id, to) - } - Element { .. } => { - #[cfg(not(debug_assertions))] - let id = self.write_element_root(node, idx, &mut attrs, &mut nodes, &[], to); - #[cfg(debug_assertions)] - let id = self.write_element_root( - node, - idx, - &mut attrs, - &mut nodes, - &nodes_sorted, - to, - ); - id - } - Text { .. } => self.write_static_text_root(node, idx, to), - }) - .sum() - } - - fn write_static_text_root( - &mut self, - node: &VNode, - idx: usize, - to: &mut impl WriteMutations, - ) -> usize { - // Simply just load the template root, no modifications needed - self.load_template_root(node, idx, to); - - // Text producs just one node on the stack - 1 - } - - fn write_dynamic_root( - &mut self, - template: &VNode, - idx: usize, - to: &mut impl WriteMutations, - ) -> usize { - use DynamicNode::*; - match &template.dynamic_nodes[idx] { - node @ Component { .. } | node @ Fragment(_) => { - let template_ref = ElementRef { - path: ElementPath { - path: template.template.get().node_paths[idx], - }, - element: template.clone(), - }; - self.create_dynamic_node(&template_ref, node, to) - } - Placeholder(VPlaceholder { id, parent }) => { - let template_ref = ElementRef { - path: ElementPath { - path: template.template.get().node_paths[idx], - }, - element: template.clone(), - }; - *parent.borrow_mut() = Some(template_ref); - let id = self.set_slot(id); - to.create_placeholder(id); - 1 - } - Text(VText { id, value }) => { - let id = self.set_slot(id); - to.create_text_node(value, id); - 1 - } - } - } - - /// We write all the descendent data for this element - /// - /// Elements can contain other nodes - and those nodes can be dynamic or static - /// - /// We want to make sure we write these nodes while on top of the root - fn write_element_root( - &mut self, - template: &VNode, - root_idx: usize, - dynamic_attrs: &mut Peekable>, - dynamic_nodes_iter: &mut Peekable>, - dynamic_nodes: &[(usize, &'static [u8])], - to: &mut impl WriteMutations, - ) -> usize { - // Load the template root and get the ID for the node on the stack - let root_on_stack = self.load_template_root(template, root_idx, to); - - // Write all the attributes below this root - self.write_attrs_on_root(dynamic_attrs, root_idx as u8, root_on_stack, template, to); - - // Load in all of the placeholder or dynamic content under this root too - self.load_placeholders( - dynamic_nodes_iter, - dynamic_nodes, - root_idx as u8, - template, - to, - ); - - 1 - } - - /// Load all of the placeholder nodes for descendents of this root node - /// - /// ```rust, ignore - /// rsx! { - /// div { - /// // This is a placeholder - /// some_value, - /// - /// // Load this too - /// "{some_text}" - /// } - /// } - /// ``` - #[allow(unused)] - fn load_placeholders( - &mut self, - dynamic_nodes_iter: &mut Peekable>, - dynamic_nodes: &[(usize, &'static [u8])], - root_idx: u8, - template: &VNode, - to: &mut impl WriteMutations, - ) { - let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) { - Some((a, b)) => (a, b), - None => return, - }; - - // If hot reloading is enabled, we need to map the sorted index to the original index of the dynamic node. If it is disabled, we can just use the sorted index - #[cfg(not(debug_assertions))] - let reversed_iter = (start..=end).rev(); - #[cfg(debug_assertions)] - let reversed_iter = (start..=end) - .rev() - .map(|sorted_index| dynamic_nodes[sorted_index].0); - - for idx in reversed_iter { - let boundary_ref = ElementRef { - path: ElementPath { - path: template.template.get().node_paths[idx], - }, - element: template.clone(), - }; - let m = self.create_dynamic_node(&boundary_ref, &template.dynamic_nodes[idx], to); - if m > 0 { - // The path is one shorter because the top node is the root - let path = &template.template.get().node_paths[idx][1..]; - to.replace_placeholder_with_nodes(path, m); - } - } - } - - fn write_attrs_on_root( - &mut self, - attrs: &mut Peekable>, - root_idx: u8, - root: ElementId, - node: &VNode, - to: &mut impl WriteMutations, - ) { - 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, to); - - loop { - self.write_attribute(node, attr_id, &node.dynamic_attrs[attr_id], id, to); - - // Only push the dynamic attributes forward if they match the current path (same element) - match attrs.next_if(|(_, p)| *p == path) { - Some((next_attr_id, _)) => attr_id = next_attr_id, - None => break, - } - } - } - } - - fn write_attribute( - &mut self, - template: &VNode, - idx: usize, - attribute: &crate::Attribute, - id: ElementId, - to: &mut impl WriteMutations, - ) { - // Make sure we set the attribute's associated id - attribute.mounted_element.set(id); - - // Safety: we promise not to re-alias this text later on after committing it to the mutation - let unbounded_name: &str = attribute.name; - - match &attribute.value { - AttributeValue::Listener(_) => { - let path = &template.template.get().attr_paths[idx]; - let element_ref = ElementRef { - path: ElementPath { path }, - element: template.clone(), - }; - self.elements[id.0] = Some(element_ref); - to.create_event_listener(&unbounded_name[2..], id); - } - _ => { - let unbounded_value = &attribute.value; - - to.set_attribute(unbounded_name, attribute.namespace, unbounded_value, id); - } - } - } - - fn load_template_root( - &mut self, - template: &VNode, - root_idx: usize, - to: &mut impl WriteMutations, - ) -> ElementId { - // Get an ID for this root since it's a real root - let this_id = self.next_element(); - template.root_ids.borrow_mut()[root_idx] = this_id; - - to.load_template(template.template.get().name, root_idx, this_id); - - this_id - } - - /// We have some dynamic attributes attached to a some node - /// - /// That node needs to be loaded at runtime, so we need to give it an ID - /// - /// If the node in question is on the stack, we just return that ID - /// - /// If the node is not on the stack, we create a new ID for it and assign it - fn assign_static_node_as_dynamic( - &mut self, - path: &'static [u8], - this_id: ElementId, - to: &mut impl WriteMutations, - ) -> ElementId { - if path.len() == 1 { - return this_id; - } - - // 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(); - - to.assign_node_id(&path[1..], id); - - id - } - - /// Insert a new template into the VirtualDom's template registry - pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template) { - // First, make sure we mark the template as seen, regardless if we process it - let (path, _) = template.name.rsplit_once(':').unwrap(); - if let Some((_, old_template)) = self - .templates - .entry(path) - .or_default() - .iter_mut() - .min_by_key(|(byte_index, _)| **byte_index) - { - // the byte index of the hot reloaded template could be different - template.name = old_template.name; - *old_template = template; - } else { - // This is a template without any current instances - self.templates - .entry(path) - .or_default() - .insert(usize::MAX, template); - } - - // If it's all dynamic nodes, then we don't need to register it - if !template.is_completely_dynamic() { - self.queued_templates.push(template); - } - } - - /// Insert a new template into the VirtualDom's template registry - // used in conditional compilation - #[allow(unused_mut)] - pub(crate) fn register_template( - &mut self, - mut template: Template, - to: &mut impl WriteMutations, - ) { - let (path, byte_index) = template.name.rsplit_once(':').unwrap(); - - let byte_index = byte_index.parse::().unwrap(); - // First, check if we've already seen this template - if self - .templates - .get(&path) - .filter(|set| set.contains_key(&byte_index)) - .is_none() - { - // if hot reloading is enabled, then we need to check for a template that has overriten this one - #[cfg(debug_assertions)] - if let Some(mut new_template) = self - .templates - .get_mut(path) - .and_then(|map| map.remove(&usize::MAX)) - { - // the byte index of the hot reloaded template could be different - new_template.name = template.name; - template = new_template; - } - - self.templates - .entry(path) - .or_default() - .insert(byte_index, template); - - // If it's all dynamic nodes, then we don't need to register it - if !template.is_completely_dynamic() { - to.register_template(template) - } - } - } - - pub(crate) fn create_dynamic_node( - &mut self, - parent: &ElementRef, - node: &DynamicNode, - to: &mut impl WriteMutations, - ) -> usize { - use DynamicNode::*; - match node { - Text(text) => self.create_dynamic_text(parent, text, to), - Placeholder(place) => self.create_placeholder(place, parent, to), - Component(component) => self.create_component_node(Some(parent), component, to), - Fragment(frag) => self.create_children(frag, Some(parent), to), - } - } - - fn create_dynamic_text( - &mut self, - parent: &ElementRef, - text: &VText, - to: &mut impl WriteMutations, - ) -> usize { - // Allocate a dynamic element reference for this text node - let new_id = self.next_element(); - - // Make sure the text node is assigned to the correct element - text.id.set(Some(new_id)); - - // Add the mutation to the list - to.hydrate_text_node(&parent.path.path[1..], &text.value, new_id); - - // Since we're hydrating an existing node, we don't create any new nodes - 0 - } - - pub(crate) fn create_placeholder( - &mut self, - placeholder: &VPlaceholder, - parent: &ElementRef, - to: &mut impl WriteMutations, - ) -> usize { - // Allocate a dynamic element reference for this text node - 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.borrow_mut() = Some(parent.clone()); - - // Assign the ID to the existing node in the template - to.assign_node_id(&parent.path.path[1..], id); - - // Since the placeholder is already in the DOM, we don't create any new nodes - 0 - } - - pub(super) fn create_component_node( - &mut self, - parent: Option<&ElementRef>, - component: &VComponent, - to: &mut impl WriteMutations, - ) -> usize { - use RenderReturn::*; - - // Load up a ScopeId for this vcomponent - let scope = self.load_scope_from_vcomponent(component, to); - - component.scope.set(Some(scope)); - - let new = self.run_scope(scope); - - self.scopes[scope.0].last_rendered_node = Some(new.clone()); - - match &new { - // Create the component's root element - Ready(t) => { - self.assign_boundary_ref(parent, t); - self.create_scope(scope, t, to) - } - Aborted(t) => self.mount_aborted(t, parent, to), - } - } - - /// Load a scope from a vcomponent. If the scope id doesn't exist, that means the component is currently "live" - fn load_scope_from_vcomponent( - &mut self, - component: &VComponent, - _to: &mut impl WriteMutations, - ) -> ScopeId { - component.scope.get().unwrap_or_else(|| { - self.new_scope(component.props.clone(), component.name) - .context() - .id - }) - } - - fn mount_aborted( - &mut self, - placeholder: &VPlaceholder, - parent: Option<&ElementRef>, - to: &mut impl WriteMutations, - ) -> usize { - let id = self.next_element(); - to.create_placeholder(id); - placeholder.id.set(Some(id)); - *placeholder.parent.borrow_mut() = parent.cloned(); - - 1 - } - - fn set_slot(&mut self, slot: &Cell>) -> ElementId { - let id = self.next_element(); - slot.set(Some(id)); - id - } -} - -fn collect_dyn_node_range( - dynamic_nodes: &mut Peekable>, - root_idx: u8, -) -> Option<(usize, usize)> { - let start = match dynamic_nodes.peek() { - Some(((_, idx), [first, ..])) if *first == root_idx => *idx, - _ => return None, - }; - - let mut end = start; - - while let Some(((_, idx), p)) = - dynamic_nodes.next_if(|(_, p)| matches!(p, [idx, ..] if *idx == root_idx)) - { - if p.len() == 1 { - continue; - } - - end = idx; - } - - Some((start, end)) -} diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs deleted file mode 100644 index b3d65cd9a..000000000 --- a/packages/core/src/diff.rs +++ /dev/null @@ -1,1119 +0,0 @@ -use std::ops::Deref; - -use crate::{ - any_props::AnyProps, - arena::ElementId, - innerlude::{ - DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, VText, WriteMutations, - }, - nodes::RenderReturn, - nodes::{DynamicNode, VNode}, - scopes::ScopeId, - virtual_dom::VirtualDom, - Attribute, TemplateNode, -}; - -use rustc_hash::{FxHashMap, FxHashSet}; -use DynamicNode::*; - -impl VirtualDom { - pub(super) fn diff_scope( - &mut self, - scope: ScopeId, - new_nodes: RenderReturn, - to: &mut impl WriteMutations, - ) { - self.runtime.scope_stack.borrow_mut().push(scope); - let scope_state = &mut self.scopes[scope.0]; - // Load the old and new bump arenas - let new = &new_nodes; - let old = scope_state.last_rendered_node.take().unwrap(); - - use RenderReturn::{Aborted, Ready}; - - match (&old, new) { - // Normal pathway - (Ready(l), Ready(r)) => self.diff_node(l, r, to), - - // Unwind the mutations if need be - (Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p, to), - - // Just move over the placeholder - (Aborted(l), Aborted(r)) => { - r.id.set(l.id.get()); - *r.parent.borrow_mut() = l.parent.borrow().clone(); - } - - // Placeholder becomes something - // We should also clear the error now - (Aborted(l), Ready(r)) => { - let parent = l.parent.take(); - self.replace_placeholder( - l, - [r], - parent.as_ref().expect("root node should not be none"), - to, - ) - } - }; - - let scope_state = &mut self.scopes[scope.0]; - scope_state.last_rendered_node = Some(new_nodes); - - self.runtime.scope_stack.borrow_mut().pop(); - } - - fn diff_ok_to_err(&mut self, l: &VNode, p: &VPlaceholder, to: &mut impl WriteMutations) { - let id = self.next_element(); - p.id.set(Some(id)); - *p.parent.borrow_mut() = l.parent.borrow().clone(); - to.create_placeholder(id); - - to.insert_nodes_before(id, 1); - - // TODO: Instead of *just* removing it, we can use the replace mutation - self.remove_node(l, true, to); - } - - fn diff_node( - &mut self, - left_template: &VNode, - right_template: &VNode, - to: &mut impl WriteMutations, - ) { - // If hot reloading is enabled, we need to make sure we're using the latest template - #[cfg(debug_assertions)] - { - let (path, byte_index) = right_template.template.get().name.rsplit_once(':').unwrap(); - if let Some(map) = self.templates.get(path) { - let byte_index = byte_index.parse::().unwrap(); - if let Some(&template) = map.get(&byte_index) { - right_template.template.set(template); - if template != left_template.template.get() { - let parent = left_template.parent.take(); - let parent = parent.as_ref(); - return self.replace(left_template, [right_template], parent, to); - } - } - } - } - - // Copy over the parent - { - *right_template.parent.borrow_mut() = left_template.parent.borrow().clone(); - } - - // 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; - } - - // If the templates are different by name, we need to replace the entire template - if templates_are_different(left_template, right_template) { - return self.light_diff_templates(left_template, right_template, to); - } - - // If the templates are the same, we can diff the attributes and children - // Start with the attributes - left_template - .dynamic_attrs - .iter() - .zip(right_template.dynamic_attrs.iter()) - .for_each(|(left_attr, right_attr)| { - // Move over the ID from the old to the new - 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 { - self.update_attribute(right_attr, left_attr, to); - } - }); - - // Now diff the dynamic nodes - left_template - .dynamic_nodes - .iter() - .zip(right_template.dynamic_nodes.iter()) - .enumerate() - .for_each(|(dyn_node_idx, (left_node, right_node))| { - let current_ref = ElementRef { - element: right_template.clone(), - path: ElementPath { - path: left_template.template.get().node_paths[dyn_node_idx], - }, - }; - self.diff_dynamic_node(left_node, right_node, ¤t_ref, to); - }); - - // Make sure the roots get transferred over while we're here - { - let mut right = right_template.root_ids.borrow_mut(); - let left = left_template.root_ids.borrow(); - for (from, into) in left.iter().zip(right.iter_mut()) { - *into = *from; - } - } - } - - fn diff_dynamic_node( - &mut self, - left_node: &DynamicNode, - right_node: &DynamicNode, - parent: &ElementRef, - to: &mut impl WriteMutations, - ) { - match (left_node, right_node) { - (Text(left), Text(right)) => self.diff_vtext(left, right, to), - (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right, parent, to), - (Placeholder(left), Placeholder(right)) => { - right.id.set(left.id.get()); - *right.parent.borrow_mut() = left.parent.borrow().clone(); - }, - (Component(left), Component(right)) => self.diff_vcomponent(left, right, Some(parent), to), - (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, right, parent, to), - (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right, parent, to), - _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."), - }; - } - - fn update_attribute( - &mut self, - right_attr: &Attribute, - left_attr: &Attribute, - to: &mut impl WriteMutations, - ) { - let name = &left_attr.name; - let value = &right_attr.value; - to.set_attribute( - name, - right_attr.namespace, - value, - left_attr.mounted_element.get(), - ); - } - - fn diff_vcomponent( - &mut self, - left: &VComponent, - right: &VComponent, - parent: Option<&ElementRef>, - to: &mut impl WriteMutations, - ) { - if std::ptr::eq(left, right) { - return; - } - - // Replace components that have different render fns - if left.render_fn != right.render_fn { - return self.replace_vcomponent(right, left, parent, to); - } - - // Make sure the new vcomponent has the right scopeid associated to it - let scope_id = left.scope.get().unwrap(); - - right.scope.set(Some(scope_id)); - - // copy out the box for both - let old_scope = &self.scopes[scope_id.0]; - let old = old_scope.props.deref(); - let new: &dyn AnyProps = right.props.deref(); - - // If the props are static, then we try to memoize by setting the new with the old - // The target scopestate still has the reference to the old props, so there's no need to update anything - // This also implicitly drops the new props since they're not used - if old.memoize(new.props()) { - tracing::trace!( - "Memoized props for component {:#?} ({})", - scope_id, - old_scope.context().name - ); - return; - } - - // First, move over the props from the old to the new, dropping old props in the process - self.scopes[scope_id.0].props = right.props.clone(); - - // Now run the component and diff it - let new = self.run_scope(scope_id); - self.diff_scope(scope_id, new, to); - - self.dirty_scopes.remove(&DirtyScope { - height: self.runtime.get_context(scope_id).unwrap().height, - id: scope_id, - }); - } - - fn replace_vcomponent( - &mut self, - right: &VComponent, - left: &VComponent, - parent: Option<&ElementRef>, - to: &mut impl WriteMutations, - ) { - let _m = self.create_component_node(parent, right, to); - - // TODO: Instead of *just* removing it, we can use the replace mutation - self.remove_component_node(left, true, to); - - todo!() - } - - /// Lightly diff the two templates, checking only their roots. - /// - /// The goal here is to preserve any existing component state that might exist. This is to preserve some React-like - /// behavior where the component state is preserved when the component is re-rendered. - /// - /// This is implemented by iterating each root, checking if the component is the same, if it is, then diff it. - /// - /// We then pass the new template through "create" which should be smart enough to skip roots. - /// - /// Currently, we only handle the case where the roots are the same component list. If there's any sort of deviation, - /// IE more nodes, less nodes, different nodes, or expressions, then we just replace the whole thing. - /// - /// This is mostly implemented to help solve the issue where the same component is rendered under two different - /// conditions: - /// - /// ```rust, ignore - /// if enabled { - /// rsx!{ Component { enabled_sign: "abc" } } - /// } else { - /// rsx!{ Component { enabled_sign: "xyz" } } - /// } - /// ``` - /// - /// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state, - /// then you should be passing in separate props instead. - /// - /// ```rust, ignore - /// let props = if enabled { - /// ComponentProps { enabled_sign: "abc" } - /// } else { - /// ComponentProps { enabled_sign: "xyz" } - /// }; - /// - /// rsx! { - /// Component { ..props } - /// } - /// ``` - fn light_diff_templates(&mut self, left: &VNode, right: &VNode, to: &mut impl WriteMutations) { - let parent = left.parent.take(); - let parent = parent.as_ref(); - match matching_components(left, right) { - None => self.replace(left, [right], parent, to), - Some(components) => components - .into_iter() - .for_each(|(l, r)| self.diff_vcomponent(l, r, parent, to)), - } - } - - /// Diff the two text nodes - /// - /// 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: &VText, right: &VText, to: &mut impl WriteMutations) { - let id = left.id.get().unwrap_or_else(|| self.next_element()); - - right.id.set(Some(id)); - if left.value != right.value { - to.set_node_text(&right.value, id); - } - } - - fn diff_non_empty_fragment( - &mut self, - old: &[VNode], - new: &[VNode], - parent: &ElementRef, - to: &mut impl WriteMutations, - ) { - let new_is_keyed = new[0].key.is_some(); - let old_is_keyed = old[0].key.is_some(); - debug_assert!( - new.iter().all(|n| n.key.is_some() == new_is_keyed), - "all siblings must be keyed or all siblings must be non-keyed" - ); - debug_assert!( - old.iter().all(|o| o.key.is_some() == old_is_keyed), - "all siblings must be keyed or all siblings must be non-keyed" - ); - - if new_is_keyed && old_is_keyed { - self.diff_keyed_children(old, new, parent, to); - } else { - self.diff_non_keyed_children(old, new, parent, to); - } - } - - // Diff children that are not keyed. - // - // The parent must be on the top of the change list stack when entering this - // function: - // - // [... parent] - // - // the change list stack is in the same state when this function returns. - fn diff_non_keyed_children( - &mut self, - old: &[VNode], - new: &[VNode], - parent: &ElementRef, - to: &mut impl WriteMutations, - ) { - use std::cmp::Ordering; - - // Handled these cases in `diff_children` before calling this function. - debug_assert!(!new.is_empty()); - debug_assert!(!old.is_empty()); - - match old.len().cmp(&new.len()) { - Ordering::Greater => self.remove_nodes(&old[new.len()..], to), - Ordering::Less => { - self.create_and_insert_after(&new[old.len()..], old.last().unwrap(), parent, to) - } - Ordering::Equal => {} - } - - for (new, old) in new.iter().zip(old.iter()) { - self.diff_node(old, new, to); - } - } - - // Diffing "keyed" children. - // - // With keyed children, we care about whether we delete, move, or create nodes - // versus mutate existing nodes in place. Presumably there is some sort of CSS - // transition animation that makes the virtual DOM diffing algorithm - // observable. By specifying keys for nodes, we know which virtual DOM nodes - // must reuse (or not reuse) the same physical DOM nodes. - // - // This is loosely based on Inferno's keyed patching implementation. However, we - // have to modify the algorithm since we are compiling the diff down into change - // list instructions that will be executed later, rather than applying the - // changes to the DOM directly as we compare virtual DOMs. - // - // 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: &[VNode], - new: &[VNode], - parent: &ElementRef, - to: &mut impl WriteMutations, - ) { - if cfg!(debug_assertions) { - let mut keys = rustc_hash::FxHashSet::default(); - let mut assert_unique_keys = |children: &[VNode]| { - keys.clear(); - for child in children { - let key = child.key.clone(); - debug_assert!( - key.is_some(), - "if any sibling is keyed, all siblings must be keyed" - ); - keys.insert(key); - } - debug_assert_eq!( - children.len(), - keys.len(), - "keyed siblings must each have a unique key" - ); - }; - assert_unique_keys(old); - assert_unique_keys(new); - } - - // First up, we diff all the nodes with the same key at the beginning of the - // children. - // - // `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, parent, to) { - Some(count) => count, - None => return, - }; - - // Ok, we now hopefully have a smaller range of children in the middle - // within which to re-order nodes with the same keys, remove old nodes with - // now-unused keys, and create new nodes with fresh keys. - - let old_middle = &old[left_offset..(old.len() - right_offset)]; - let new_middle = &new[left_offset..(new.len() - right_offset)]; - - debug_assert!( - !((old_middle.len() == new_middle.len()) && old_middle.is_empty()), - "keyed children must have the same number of children" - ); - - if new_middle.is_empty() { - // remove the old elements - self.remove_nodes(old_middle, to); - } else if old_middle.is_empty() { - // there were no old elements, so just create the new elements - // we need to find the right "foothold" though - we shouldn't use the "append" at all - 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, parent, to); - } 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, parent, to); - } else { - // inserting in the middle - let foothold = &old[left_offset - 1]; - self.create_and_insert_after(new_middle, foothold, parent, to); - } - } else { - self.diff_keyed_middle(old_middle, new_middle, parent, to); - } - } - - /// Diff both ends of the children that share keys. - /// - /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing. - /// - /// If there is no offset, then this function returns None and the diffing is complete. - fn diff_keyed_ends( - &mut self, - old: &[VNode], - new: &[VNode], - parent: &ElementRef, - to: &mut impl WriteMutations, - ) -> Option<(usize, usize)> { - let mut left_offset = 0; - - for (old, new) in old.iter().zip(new.iter()) { - // abort early if we finally run into nodes with different keys - if old.key != new.key { - break; - } - self.diff_node(old, new, to); - left_offset += 1; - } - - // If that was all of the old children, then create and append the remaining - // new children and we're finished. - if left_offset == old.len() { - self.create_and_insert_after(&new[left_offset..], old.last().unwrap(), parent, to); - return None; - } - - // And if that was all of the new children, then remove all of the remaining - // old children and we're finished. - if left_offset == new.len() { - self.remove_nodes(&old[left_offset..], to); - return None; - } - - // if the shared prefix is less than either length, then we need to walk backwards - let mut right_offset = 0; - for (old, new) in old.iter().rev().zip(new.iter().rev()) { - // abort early if we finally run into nodes with different keys - if old.key != new.key { - break; - } - self.diff_node(old, new, to); - right_offset += 1; - } - - Some((left_offset, right_offset)) - } - - // The most-general, expensive code path for keyed children diffing. - // - // We find the longest subsequence within `old` of children that are relatively - // ordered the same way in `new` (via finding a longest-increasing-subsequence - // of the old child's index within `new`). The children that are elements of - // this subsequence will remain in place, minimizing the number of DOM moves we - // will have to do. - // - // Upon entry to this function, the change list stack must be empty. - // - // This function will load the appropriate nodes onto the stack and do diffing in place. - // - // 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: &[VNode], - new: &[VNode], - parent: &ElementRef, - to: &mut impl WriteMutations, - ) { - /* - 1. Map the old keys into a numerical ordering based on indices. - 2. Create a map of old key to its index - 3. Map each new key to the old key, carrying over the old index. - - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3 - - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist - - now, we should have a list of integers that indicates where in the old list the new items map to. - - 4. Compute the LIS of this list - - this indicates the longest list of new children that won't need to be moved. - - 5. Identify which nodes need to be removed - 6. Identify which nodes will need to be diffed - - 7. Going along each item in the new list, create it and insert it before the next closest item in the LIS. - - if the item already existed, just move it to the right place. - - 8. Finally, generate instructions to remove any old children. - 9. Generate instructions to finally diff children that are the same between both - */ - // 0. Debug sanity checks - // Should have already diffed the shared-key prefixes and suffixes. - debug_assert_ne!(new.first().map(|i| &i.key), old.first().map(|i| &i.key)); - debug_assert_ne!(new.last().map(|i| &i.key), old.last().map(|i| &i.key)); - - // 1. Map the old keys into a numerical ordering based on indices. - // 2. Create a map of old key to its index - // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3). - let old_key_to_old_index = old - .iter() - .enumerate() - .map(|(i, o)| (o.key.as_ref().unwrap(), i)) - .collect::>(); - - let mut shared_keys = FxHashSet::default(); - - // 3. Map each new key to the old key, carrying over the old index. - let new_index_to_old_index = new - .iter() - .map(|node| { - let key = node.key.as_ref().unwrap(); - if let Some(&index) = old_key_to_old_index.get(&key) { - shared_keys.insert(key); - index - } else { - u32::MAX as usize - } - }) - .collect::>(); - - // If none of the old keys are reused by the new children, then we remove all the remaining old children and - // create the new children afresh. - if shared_keys.is_empty() { - if !old.is_empty() { - self.remove_nodes(&old[1..], to); - self.replace(&old[0], new, Some(parent), to); - } else { - // I think this is wrong - why are we appending? - // only valid of the if there are no trailing elements - // self.create_and_append_children(new); - - todo!("we should never be appending - just creating N"); - } - return; - } - - // remove any old children that are not shared - // todo: make this an iterator - for child in old { - let key = child.key.as_ref().unwrap(); - if !shared_keys.contains(&key) { - self.remove_node(child, true, to); - } - } - - // 4. Compute the LIS of this list - let mut lis_sequence = Vec::with_capacity(new_index_to_old_index.len()); - - let mut predecessors = vec![0; new_index_to_old_index.len()]; - let mut starts = vec![0; new_index_to_old_index.len()]; - - longest_increasing_subsequence::lis_with( - &new_index_to_old_index, - &mut lis_sequence, - |a, b| a < b, - &mut predecessors, - &mut starts, - ); - - // the lis comes out backwards, I think. can't quite tell. - lis_sequence.sort_unstable(); - - // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS) - if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) { - lis_sequence.pop(); - } - - for idx in &lis_sequence { - self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx], to); - } - - let mut nodes_created = 0; - - // add mount instruction for the first items not covered by the lis - let last = *lis_sequence.last().unwrap(); - if last < (new.len() - 1) { - for (idx, new_node) in new[(last + 1)..].iter().enumerate() { - let new_idx = idx + last + 1; - let old_index = new_index_to_old_index[new_idx]; - if old_index == u32::MAX as usize { - nodes_created += self.create(new_node, to); - } else { - self.diff_node(&old[old_index], new_node, to); - nodes_created += self.push_all_real_nodes(new_node, to); - } - } - - let id = self.find_last_element(&new[last]); - if nodes_created > 0 { - to.insert_nodes_after(id, nodes_created) - } - nodes_created = 0; - } - - // for each spacing, generate a mount instruction - let mut lis_iter = lis_sequence.iter().rev(); - let mut last = *lis_iter.next().unwrap(); - for next in lis_iter { - if last - next > 1 { - for (idx, new_node) in new[(next + 1)..last].iter().enumerate() { - let new_idx = idx + next + 1; - let old_index = new_index_to_old_index[new_idx]; - if old_index == u32::MAX as usize { - nodes_created += self.create(new_node, to); - } else { - self.diff_node(&old[old_index], new_node, to); - nodes_created += self.push_all_real_nodes(new_node, to); - } - } - - let id = self.find_first_element(&new[last]); - if nodes_created > 0 { - to.insert_nodes_before(id, nodes_created); - } - - nodes_created = 0; - } - last = *next; - } - - // add mount instruction for the last items not covered by the lis - let first_lis = *lis_sequence.first().unwrap(); - if first_lis > 0 { - for (idx, new_node) in new[..first_lis].iter().enumerate() { - let old_index = new_index_to_old_index[idx]; - if old_index == u32::MAX as usize { - nodes_created += self.create(new_node, to); - } else { - self.diff_node(&old[old_index], new_node, to); - nodes_created += self.push_all_real_nodes(new_node, to); - } - } - - let id = self.find_first_element(&new[first_lis]); - if nodes_created > 0 { - to.insert_nodes_before(id, nodes_created); - } - } - } - - /// Push all the real nodes on the stack - fn push_all_real_nodes(&self, node: &VNode, to: &mut impl WriteMutations) -> usize { - node.template - .get() - .roots - .iter() - .enumerate() - .map(|(idx, _)| { - let node = match node.dynamic_root(idx) { - Some(node) => node, - None => { - to.push_root(node.root_ids.borrow()[idx]); - return 1; - } - }; - - match node { - Text(t) => { - to.push_root(t.id.get().unwrap()); - 1 - } - Placeholder(t) => { - to.push_root(t.id.get().unwrap()); - 1 - } - Fragment(nodes) => nodes - .iter() - .map(|node| self.push_all_real_nodes(node, to)) - .sum(), - - Component(comp) => { - let scope = comp.scope.get().unwrap(); - match self.get_scope(scope).unwrap().root_node() { - RenderReturn::Ready(node) => self.push_all_real_nodes(node, to), - RenderReturn::Aborted(_node) => todo!(), - } - } - } - }) - .sum() - } - - pub(crate) fn create_children<'a>( - &mut self, - nodes: impl IntoIterator, - parent: Option<&ElementRef>, - to: &mut impl WriteMutations, - ) -> usize { - nodes - .into_iter() - .map(|child| { - self.assign_boundary_ref(parent, child); - self.create(child, to) - }) - .sum() - } - - fn create_and_insert_before( - &mut self, - new: &[VNode], - before: &VNode, - parent: &ElementRef, - to: &mut impl WriteMutations, - ) { - let m = self.create_children(new, Some(parent), to); - let id = self.find_first_element(before); - to.insert_nodes_before(id, m); - } - - fn create_and_insert_after( - &mut self, - new: &[VNode], - after: &VNode, - parent: &ElementRef, - to: &mut impl WriteMutations, - ) { - let m = self.create_children(new, Some(parent), to); - let id = self.find_last_element(after); - to.insert_nodes_after(id, m); - } - - /// Simply replace a placeholder with a list of nodes - fn replace_placeholder<'a>( - &mut self, - l: &VPlaceholder, - r: impl IntoIterator, - parent: &ElementRef, - to: &mut impl WriteMutations, - ) { - let m = self.create_children(r, Some(parent), to); - let id = l.id.get().unwrap(); - to.replace_node_with(id, m); - self.reclaim(id); - } - - fn replace<'a>( - &mut self, - left: &VNode, - right: impl IntoIterator, - parent: Option<&ElementRef>, - to: &mut impl WriteMutations, - ) { - let m = self.create_children(right, parent, to); - - // TODO: Instead of *just* removing it, we can use the replace mutation - to.insert_nodes_before(self.find_first_element(left), m); - - self.remove_node(left, true, to); - } - - fn node_to_placeholder( - &mut self, - l: &[VNode], - r: &VPlaceholder, - parent: &ElementRef, - to: &mut impl WriteMutations, - ) { - // Create the placeholder first, ensuring we get a dedicated ID for the placeholder - let placeholder = self.next_element(); - - r.id.set(Some(placeholder)); - r.parent.borrow_mut().replace(parent.clone()); - - to.create_placeholder(placeholder); - - self.replace_nodes(l, 1, to); - } - - /// Replace many nodes with a number of nodes on the stack - fn replace_nodes(&mut self, nodes: &[VNode], m: usize, to: &mut impl WriteMutations) { - // We want to optimize the replace case to use one less mutation if possible - // Since mutations are done in reverse, the last node removed will be the first in the stack - // TODO: Instead of *just* removing it, we can use the replace mutation - to.insert_nodes_before(self.find_first_element(&nodes[0]), m); - - debug_assert!( - !nodes.is_empty(), - "replace_nodes must have at least one node" - ); - - self.remove_nodes(nodes, to); - } - - /// Remove these nodes from the dom - /// Wont generate mutations for the inner nodes - fn remove_nodes(&mut self, nodes: &[VNode], to: &mut impl WriteMutations) { - nodes - .iter() - .rev() - .for_each(|node| self.remove_node(node, true, to)); - } - - fn remove_node(&mut self, node: &VNode, gen_muts: bool, to: &mut impl WriteMutations) { - // Clean up any attributes that have claimed a static node as dynamic for mount/unmounta - // Will not generate mutations! - self.reclaim_attributes(node); - - // Remove the nested dynamic nodes - // We don't generate mutations for these, as they will be removed by the parent (in the next line) - // But we still need to make sure to reclaim them from the arena and drop their hooks, etc - self.remove_nested_dyn_nodes(node, to); - - // 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, to); - } - - fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool, to: &mut impl WriteMutations) { - for (idx, _) in node.template.get().roots.iter().enumerate() { - if let Some(dy) = node.dynamic_root(idx) { - self.remove_dynamic_node(dy, gen_muts, to); - } else { - let id = node.root_ids.borrow()[idx]; - if gen_muts { - to.remove_node(id); - } - self.reclaim(id); - } - } - } - - fn reclaim_attributes(&mut self, node: &VNode) { - let mut id = None; - for (idx, attr) in node.dynamic_attrs.iter().enumerate() { - // We'll clean up the root nodes either way, so don't worry - let path_len = node - .template - .get() - .attr_paths - .get(idx) - .map(|path| path.len()); - // if the path is 1 the attribute is in the root, so we don't need to clean it up - // if the path is 0, the attribute is a not attached at all, so we don't need to clean it up - - if let Some(len) = path_len { - if (..=1).contains(&len) { - continue; - } - } - - let next_id = attr.mounted_element.get(); - - if id == Some(next_id) { - continue; - } - - id = Some(next_id); - - self.reclaim(next_id); - } - } - - fn remove_nested_dyn_nodes(&mut self, node: &VNode, to: &mut impl WriteMutations) { - for (idx, dyn_node) in node.dynamic_nodes.iter().enumerate() { - let path_len = node - .template - .get() - .node_paths - .get(idx) - .map(|path| path.len()); - // Roots are cleaned up automatically above and nodes with a empty path are placeholders - if let Some(2..) = path_len { - self.remove_dynamic_node(dyn_node, false, to) - } - } - } - - fn remove_dynamic_node( - &mut self, - node: &DynamicNode, - gen_muts: bool, - to: &mut impl WriteMutations, - ) { - match node { - Component(comp) => self.remove_component_node(comp, gen_muts, to), - Text(t) => self.remove_text_node(t, gen_muts, to), - Placeholder(t) => self.remove_placeholder(t, gen_muts, to), - Fragment(nodes) => nodes - .iter() - .for_each(|node| self.remove_node(node, gen_muts, to)), - }; - } - - fn remove_placeholder( - &mut self, - t: &VPlaceholder, - gen_muts: bool, - to: &mut impl WriteMutations, - ) { - if let Some(id) = t.id.take() { - if gen_muts { - to.remove_node(id); - } - self.reclaim(id) - } - } - - fn remove_text_node(&mut self, t: &VText, gen_muts: bool, to: &mut impl WriteMutations) { - if let Some(id) = t.id.take() { - if gen_muts { - to.remove_node(id); - } - self.reclaim(id) - } - } - - fn remove_component_node( - &mut self, - comp: &VComponent, - gen_muts: bool, - to: &mut impl WriteMutations, - ) { - // Remove the component reference from the vcomponent so they're not tied together - let scope = comp - .scope - .take() - .expect("VComponents to always have a scope"); - - // Remove the component from the dom - match self.scopes[scope.0].last_rendered_node.take().unwrap() { - RenderReturn::Ready(t) => self.remove_node(&t, gen_muts, to), - RenderReturn::Aborted(placeholder) => { - self.remove_placeholder(&placeholder, gen_muts, to) - } - }; - - // Now drop all the resources - self.drop_scope(scope); - } - - fn find_first_element(&self, node: &VNode) -> ElementId { - match node.dynamic_root(0) { - None => node.root_ids.borrow()[0], - Some(Text(t)) => t.id.get().unwrap(), - Some(Fragment(t)) => self.find_first_element(&t[0]), - Some(Placeholder(t)) => t.id.get().unwrap(), - Some(Component(comp)) => { - let scope = comp.scope.get().unwrap(); - match self.get_scope(scope).unwrap().root_node() { - RenderReturn::Ready(t) => self.find_first_element(t), - _ => todo!("cannot handle nonstandard nodes"), - } - } - } - } - - fn find_last_element(&self, node: &VNode) -> ElementId { - match node.dynamic_root(node.template.get().roots.len() - 1) { - None => *node.root_ids.borrow().last().unwrap(), - Some(Text(t)) => t.id.get().unwrap(), - Some(Fragment(t)) => self.find_last_element(t.last().unwrap()), - Some(Placeholder(t)) => t.id.get().unwrap(), - Some(Component(comp)) => { - let scope = comp.scope.get().unwrap(); - match self.get_scope(scope).unwrap().root_node() { - RenderReturn::Ready(t) => self.find_last_element(t), - _ => todo!("cannot handle nonstandard nodes"), - } - } - } - } - - pub(crate) fn assign_boundary_ref(&mut self, parent: Option<&ElementRef>, child: &VNode) { - if let Some(parent) = parent { - // assign the parent of the child - child.parent.borrow_mut().replace(parent.clone()); - } - } -} - -/// Are the templates the same? -/// -/// We need to check for the obvious case, and the non-obvious case where the template as cloned -/// -/// We use the pointer of the dynamic_node list in this case -fn templates_are_the_same(left_template: &VNode, right_template: &VNode) -> bool { - std::ptr::eq(left_template, right_template) -} - -fn templates_are_different(left_template: &VNode, right_template: &VNode) -> bool { - let left_template_name = left_template.template.get().name; - let right_template_name = right_template.template.get().name; - // we want to re-create the node if the template name is different by pointer even if the value is the same so that we can detect when hot reloading changes the template - !std::ptr::eq(left_template_name, right_template_name) -} - -fn matching_components<'a>( - left: &'a VNode, - right: &'a VNode, -) -> Option> { - let left_template = left.template.get(); - let right_template = right.template.get(); - if left_template.roots.len() != right_template.roots.len() { - return None; - } - - // run through the components, ensuring they're the same - left_template - .roots - .iter() - .zip(right_template.roots.iter()) - .map(|(l, r)| { - let (l, r) = match (l, r) { - (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r), - _ => return None, - }; - - let (l, r) = match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) { - (Component(l), Component(r)) => (l, r), - _ => return None, - }; - - Some((l, r)) - }) - .collect() -} - -/// We can apply various optimizations to dynamic nodes that are the single child of their parent. -/// -/// IE -/// - for text - we can use SetTextContent -/// - for clearning children we can use RemoveChildren -/// - for appending children we can use AppendChildren -#[allow(dead_code)] -fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool { - let template = node.template.get(); - let path = template.node_paths[idx]; - - // use a loop to index every static node's children until the path has run out - // only break if the last path index is a dynamic node - let mut static_node = &template.roots[path[0] as usize]; - - for i in 1..path.len() - 1 { - match static_node { - TemplateNode::Element { children, .. } => static_node = &children[path[i] as usize], - _ => return false, - } - } - - match static_node { - TemplateNode::Element { children, .. } => children.len() == 1, - _ => false, - } -} diff --git a/packages/core/src/diff/component.rs b/packages/core/src/diff/component.rs new file mode 100644 index 000000000..72c5328ee --- /dev/null +++ b/packages/core/src/diff/component.rs @@ -0,0 +1,149 @@ +use std::{ + cell::RefMut, + ops::{Deref, DerefMut}, +}; + +use crate::{ + any_props::AnyProps, + arena::ElementId, + innerlude::{ + DirtyScope, ElementPath, ElementRef, VComponent, VNodeMount, VText, WriteMutations, + }, + nodes::RenderReturn, + nodes::{DynamicNode, VNode}, + scopes::ScopeId, + virtual_dom::VirtualDom, + TemplateNode, +}; + +use DynamicNode::*; + +impl VirtualDom { + pub(crate) fn diff_scope( + &mut self, + to: &mut impl WriteMutations, + scope: ScopeId, + new_nodes: RenderReturn, + ) { + self.runtime.scope_stack.borrow_mut().push(scope); + let scope_state = &mut self.scopes[scope.0]; + // Load the old and new bump arenas + let new = &new_nodes; + let old = scope_state.last_rendered_node.take().unwrap(); + + use RenderReturn::{Aborted, Ready}; + + let (Ready(old) | Aborted(old), Ready(new) | Aborted(new)) = (&old, new); + old.diff_node(new, self, to); + + let scope_state = &mut self.scopes[scope.0]; + scope_state.last_rendered_node = Some(new_nodes); + + self.runtime.scope_stack.borrow_mut().pop(); + } + + /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer. + /// + /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created. + pub(crate) fn create_scope( + &mut self, + to: &mut impl WriteMutations, + scope: ScopeId, + new_node: &VNode, + parent: Option, + ) -> usize { + self.runtime.scope_stack.borrow_mut().push(scope); + let nodes = new_node.create(self, to, parent); + self.runtime.scope_stack.borrow_mut().pop(); + nodes + } +} + +impl VNode { + pub(crate) fn diff_vcomponent( + &self, + mount: &mut VNodeMount, + idx: usize, + new: &VComponent, + old: &VComponent, + scope_id: ScopeId, + parent: Option, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) { + // Replace components that have different render fns + if old.render_fn != new.render_fn { + return self.replace_vcomponent(mount, idx, new, parent, dom, to); + } + + // copy out the box for both + let old_scope = &mut dom.scopes[scope_id.0]; + let old_props = old_scope.props.deref(); + let new_props: &dyn AnyProps = new.props.deref(); + + // If the props are static, then we try to memoize by setting the new with the old + // The target scopestate still has the reference to the old props, so there's no need to update anything + // This also implicitly drops the new props since they're not used + if old_props.memoize(new_props.props()) { + tracing::trace!( + "Memoized props for component {:#?} ({})", + scope_id, + old_scope.context().name + ); + return; + } + + // First, move over the props from the old to the new, dropping old props in the process + dom.scopes[scope_id.0].props = new.props.clone(); + + // Now run the component and diff it + let new = dom.run_scope(scope_id); + dom.diff_scope(to, scope_id, new); + + let height = dom.runtime.get_context(scope_id).unwrap().height; + dom.dirty_scopes.remove(&DirtyScope { + height, + id: scope_id, + }); + } + fn replace_vcomponent( + &self, + mount: &mut VNodeMount, + idx: usize, + new: &VComponent, + parent: Option, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) { + let scope = ScopeId(mount.mounted_dynamic_nodes[idx]); + + let _m = self.create_component_node(mount, idx, new, parent, dom, to); + + // TODO: Instead of *just* removing it, we can use the replace mutation + dom.remove_component_node(to, scope, true); + + todo!() + } + + pub(super) fn create_component_node( + &self, + mount: &mut VNodeMount, + idx: usize, + component: &VComponent, + parent: Option, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) -> usize { + // Load up a ScopeId for this vcomponent. If it's already mounted, then we can just use that + let scope = dom + .new_scope(component.props.clone(), component.name) + .context() + .id; + + let mut new = dom.run_scope(scope); + + dom.scopes[scope.0].last_rendered_node = Some(new.clone()); + + dom.create_scope(to, scope, &new, parent) + } +} diff --git a/packages/core/src/diff/iterator.rs b/packages/core/src/diff/iterator.rs new file mode 100644 index 000000000..0374a8af1 --- /dev/null +++ b/packages/core/src/diff/iterator.rs @@ -0,0 +1,429 @@ +use crate::{ + innerlude::{ElementRef, WriteMutations}, + nodes::VNode, + VirtualDom, +}; + +use rustc_hash::{FxHashMap, FxHashSet}; + +impl VirtualDom { + pub(crate) fn diff_non_empty_fragment( + &mut self, + to: &mut impl WriteMutations, + old: &[VNode], + new: &[VNode], + parent: Option, + ) { + let new_is_keyed = new[0].key.is_some(); + let old_is_keyed = old[0].key.is_some(); + debug_assert!( + new.iter().all(|n| n.key.is_some() == new_is_keyed), + "all siblings must be keyed or all siblings must be non-keyed" + ); + debug_assert!( + old.iter().all(|o| o.key.is_some() == old_is_keyed), + "all siblings must be keyed or all siblings must be non-keyed" + ); + + if new_is_keyed && old_is_keyed { + self.diff_keyed_children(to, old, new, parent); + } else { + self.diff_non_keyed_children(to, old, new, parent); + } + } + + // Diff children that are not keyed. + // + // The parent must be on the top of the change list stack when entering this + // function: + // + // [... parent] + // + // the change list stack is in the same state when this function returns. + fn diff_non_keyed_children( + &mut self, + to: &mut impl WriteMutations, + old: &[VNode], + new: &[VNode], + parent: Option, + ) { + use std::cmp::Ordering; + + // Handled these cases in `diff_children` before calling this function. + debug_assert!(!new.is_empty()); + debug_assert!(!old.is_empty()); + + match old.len().cmp(&new.len()) { + Ordering::Greater => self.remove_nodes(to, &old[new.len()..]), + Ordering::Less => self.create_and_insert_after( + to, + &new[old.len()..], + &mut old.last().unwrap(), + parent, + ), + Ordering::Equal => {} + } + + for (new, old) in new.iter().zip(old.iter()) { + old.diff_node(new, self, to); + } + } + + // Diffing "keyed" children. + // + // With keyed children, we care about whether we delete, move, or create nodes + // versus mutate existing nodes in place. Presumably there is some sort of CSS + // transition animation that makes the virtual DOM diffing algorithm + // observable. By specifying keys for nodes, we know which virtual DOM nodes + // must reuse (or not reuse) the same physical DOM nodes. + // + // This is loosely based on Inferno's keyed patching implementation. However, we + // have to modify the algorithm since we are compiling the diff down into change + // list instructions that will be executed later, rather than applying the + // changes to the DOM directly as we compare virtual DOMs. + // + // 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, + to: &mut impl WriteMutations, + old: &[VNode], + new: &[VNode], + parent: Option, + ) { + if cfg!(debug_assertions) { + let mut keys = rustc_hash::FxHashSet::default(); + let mut assert_unique_keys = |children: &[VNode]| { + keys.clear(); + for child in children { + let key = child.key.clone(); + debug_assert!( + key.is_some(), + "if any sibling is keyed, all siblings must be keyed" + ); + keys.insert(key); + } + debug_assert_eq!( + children.len(), + keys.len(), + "keyed siblings must each have a unique key" + ); + }; + assert_unique_keys(old); + assert_unique_keys(new); + } + + // First up, we diff all the nodes with the same key at the beginning of the + // children. + // + // `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(to, old, new, parent.clone()) { + Some(count) => count, + None => return, + }; + + // Ok, we now hopefully have a smaller range of children in the middle + // within which to re-order nodes with the same keys, remove old nodes with + // now-unused keys, and create new nodes with fresh keys. + + let old_middle = &old[left_offset..(old.len() - right_offset)]; + let new_middle = &new[left_offset..(new.len() - right_offset)]; + + debug_assert!( + !((old_middle.len() == new_middle.len()) && old_middle.is_empty()), + "keyed children must have the same number of children" + ); + + if new_middle.is_empty() { + // remove the old elements + self.remove_nodes(to, old_middle); + } else if old_middle.is_empty() { + // there were no old elements, so just create the new elements + // we need to find the right "foothold" though - we shouldn't use the "append" at all + if left_offset == 0 { + // insert at the beginning of the old list + let mut foothold = &old[old.len() - right_offset]; + self.create_and_insert_before(to, new_middle, &mut foothold, parent); + } else if right_offset == 0 { + // insert at the end the old list + let mut foothold = old.last().unwrap(); + self.create_and_insert_after(to, new_middle, &mut foothold, parent); + } else { + // inserting in the middle + let mut foothold = &old[left_offset - 1]; + self.create_and_insert_after(to, new_middle, &mut foothold, parent); + } + } else { + self.diff_keyed_middle(to, old_middle, new_middle, parent); + } + } + + /// Diff both ends of the children that share keys. + /// + /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing. + /// + /// If there is no offset, then this function returns None and the diffing is complete. + fn diff_keyed_ends( + &mut self, + to: &mut impl WriteMutations, + old: &[VNode], + new: &[VNode], + parent: Option, + ) -> Option<(usize, usize)> { + let mut left_offset = 0; + + for (old, new) in old.iter().zip(new.iter()) { + // abort early if we finally run into nodes with different keys + if old.key != new.key { + break; + } + old.diff_node(new, self, to); + left_offset += 1; + } + + // If that was all of the old children, then create and append the remaining + // new children and we're finished. + if left_offset == old.len() { + self.create_and_insert_after(to, &new[left_offset..], &mut old.last().unwrap(), parent); + return None; + } + + // And if that was all of the new children, then remove all of the remaining + // old children and we're finished. + if left_offset == new.len() { + self.remove_nodes(to, &old[left_offset..]); + return None; + } + + // if the shared prefix is less than either length, then we need to walk backwards + let mut right_offset = 0; + for (old, new) in old.iter().rev().zip(new.iter().rev()) { + // abort early if we finally run into nodes with different keys + if old.key != new.key { + break; + } + old.diff_node(new, self, to); + right_offset += 1; + } + + Some((left_offset, right_offset)) + } + + // The most-general, expensive code path for keyed children diffing. + // + // We find the longest subsequence within `old` of children that are relatively + // ordered the same way in `new` (via finding a longest-increasing-subsequence + // of the old child's index within `new`). The children that are elements of + // this subsequence will remain in place, minimizing the number of DOM moves we + // will have to do. + // + // Upon entry to this function, the change list stack must be empty. + // + // This function will load the appropriate nodes onto the stack and do diffing in place. + // + // Upon exit from this function, it will be restored to that same self. + #[allow(clippy::too_many_lines)] + fn diff_keyed_middle( + &mut self, + to: &mut impl WriteMutations, + old: &[VNode], + new: &[VNode], + parent: Option, + ) { + /* + 1. Map the old keys into a numerical ordering based on indices. + 2. Create a map of old key to its index + 3. Map each new key to the old key, carrying over the old index. + - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3 + - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist + + now, we should have a list of integers that indicates where in the old list the new items mapto. + + 4. Compute the LIS of this list + - this indicates the longest list of new children that won't need to be moved. + + 5. Identify which nodes need to be removed + 6. Identify which nodes will need to be diffed + + 7. Going along each item in the new list, create it and insert it before the next closest item in the LIS. + - if the item already existed, just move it to the right place. + + 8. Finally, generate instructions to remove any old children. + 9. Generate instructions to finally diff children that are the same between both + */ + // 0. Debug sanity checks + // Should have already diffed the shared-key prefixes and suffixes. + debug_assert_ne!(new.first().map(|i| &i.key), old.first().map(|i| &i.key)); + debug_assert_ne!(new.last().map(|i| &i.key), old.last().map(|i| &i.key)); + + // 1. Map the old keys into a numerical ordering based on indices. + // 2. Create a map of old key to its index + // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3). + let old_key_to_old_index = old + .iter() + .enumerate() + .map(|(i, o)| (o.key.as_ref().unwrap(), i)) + .collect::>(); + + let mut shared_keys = FxHashSet::default(); + + // 3. Map each new key to the old key, carrying over the old index. + let new_index_to_old_index = new + .iter() + .map(|node| { + let key = node.key.as_ref().unwrap(); + if let Some(&index) = old_key_to_old_index.get(&key) { + shared_keys.insert(key); + index + } else { + u32::MAX as usize + } + }) + .collect::>(); + + // If none of the old keys are reused by the new children, then we remove all the remaining old children and + // create the new children afresh. + if shared_keys.is_empty() { + if !old.is_empty() { + self.remove_nodes(to, &old[1..]); + old[0].replace(new, parent, self, to); + } else { + // I think this is wrong - why are we appending? + // only valid of the if there are no trailing elements + // self.create_and_append_children(new); + + todo!("we should never be appending - just creating N"); + } + return; + } + + // remove any old children that are not shared + // todo: make this an iterator + for child in old { + let key = child.key.as_ref().unwrap(); + if !shared_keys.contains(&key) { + child.remove_node(self, to, true); + } + } + + // 4. Compute the LIS of this list + let mut lis_sequence = Vec::with_capacity(new_index_to_old_index.len()); + + let mut predecessors = vec![0; new_index_to_old_index.len()]; + let mut starts = vec![0; new_index_to_old_index.len()]; + + longest_increasing_subsequence::lis_with( + &new_index_to_old_index, + &mut lis_sequence, + |a, b| a < b, + &mut predecessors, + &mut starts, + ); + + // the lis comes out backwards, I think. can't quite tell. + lis_sequence.sort_unstable(); + + // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS) + if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) { + lis_sequence.pop(); + } + + for idx in &lis_sequence { + old[new_index_to_old_index[*idx]].diff_node(&new[*idx], self, to); + } + + let mut nodes_created = 0; + + // add mount instruction for the first items not covered by the lis + let last = *lis_sequence.last().unwrap(); + if last < (new.len() - 1) { + for (idx, new_node) in new[(last + 1)..].iter().enumerate() { + let new_idx = idx + last + 1; + let old_index = new_index_to_old_index[new_idx]; + if old_index == u32::MAX as usize { + nodes_created += new_node.create(self, to, parent.clone()); + } else { + old[old_index].diff_node(new_node, self, to); + nodes_created += new_node.push_all_real_nodes(self, to); + } + } + + let id = new[last].find_last_element(self); + if nodes_created > 0 { + to.insert_nodes_after(id, nodes_created) + } + nodes_created = 0; + } + + // for each spacing, generate a mount instruction + let mut lis_iter = lis_sequence.iter().rev(); + let mut last = *lis_iter.next().unwrap(); + for next in lis_iter { + if last - next > 1 { + for (idx, new_node) in new[(next + 1)..last].iter().enumerate() { + let new_idx = idx + next + 1; + let old_index = new_index_to_old_index[new_idx]; + if old_index == u32::MAX as usize { + nodes_created += new_node.create(self, to, parent.clone()); + } else { + old[old_index].diff_node(new_node, self, to); + nodes_created += new_node.push_all_real_nodes(self, to); + } + } + + let id = new[last].find_first_element(self); + if nodes_created > 0 { + to.insert_nodes_before(id, nodes_created); + } + + nodes_created = 0; + } + last = *next; + } + + // add mount instruction for the last items not covered by the lis + let first_lis = *lis_sequence.first().unwrap(); + if first_lis > 0 { + for (idx, new_node) in new[..first_lis].iter().enumerate() { + let old_index = new_index_to_old_index[idx]; + if old_index == u32::MAX as usize { + nodes_created += new_node.create(self, to, parent.clone()); + } else { + old[old_index].diff_node(new_node, self, to); + nodes_created += new_node.push_all_real_nodes(self, to); + } + } + + let id = new[first_lis].find_first_element(self); + if nodes_created > 0 { + to.insert_nodes_before(id, nodes_created); + } + } + } + + fn create_and_insert_before( + &mut self, + to: &mut impl WriteMutations, + new: &[VNode], + before: &VNode, + parent: Option, + ) { + let m = self.create_children(to, new, parent); + let id = before.find_first_element(self); + to.insert_nodes_before(id, m); + } + + fn create_and_insert_after( + &mut self, + to: &mut impl WriteMutations, + new: &[VNode], + after: &VNode, + parent: Option, + ) { + let m = self.create_children(to, new, parent); + let id = after.find_last_element(self); + to.insert_nodes_after(id, m); + } +} diff --git a/packages/core/src/diff/mod.rs b/packages/core/src/diff/mod.rs new file mode 100644 index 000000000..0c706e3fe --- /dev/null +++ b/packages/core/src/diff/mod.rs @@ -0,0 +1,213 @@ +use std::{ + cell::RefMut, + ops::{Deref, DerefMut}, +}; + +use crate::{ + any_props::AnyProps, + arena::ElementId, + innerlude::{ + DirtyScope, ElementPath, ElementRef, VComponent, VNodeMount, VText, WriteMutations, + }, + nodes::RenderReturn, + nodes::{DynamicNode, VNode}, + scopes::ScopeId, + virtual_dom::VirtualDom, + Template, TemplateNode, +}; + +use DynamicNode::*; + +mod component; +mod iterator; +mod node; + +impl VirtualDom { + pub(crate) fn create_children<'a>( + &mut self, + to: &mut impl WriteMutations, + nodes: impl IntoIterator, + parent: Option, + ) -> usize { + nodes + .into_iter() + .map(|child| child.create(self, to, parent.clone())) + .sum() + } + + fn remove_element_id(&mut self, to: &mut impl WriteMutations, id: ElementId, gen_muts: bool) { + if gen_muts { + to.remove_node(id); + } + self.reclaim(id) + } + + /// Simply replace a placeholder with a list of nodes + fn replace_placeholder<'a>( + &mut self, + to: &mut impl WriteMutations, + placeholder_id: ElementId, + r: impl IntoIterator, + parent: Option, + ) { + let m = self.create_children(to, r, parent); + to.replace_node_with(placeholder_id, m); + self.reclaim(placeholder_id); + } + + fn nodes_to_placeholder( + &mut self, + to: &mut impl WriteMutations, + mount: &mut VNodeMount, + dyn_node_idx: usize, + old_nodes: &[VNode], + ) { + // Create the placeholder first, ensuring we get a dedicated ID for the placeholder + let placeholder = self.next_element(); + + // Set the id of the placeholder + mount.mounted_dynamic_nodes[dyn_node_idx] = placeholder.0; + + to.create_placeholder(placeholder); + + self.replace_nodes(to, old_nodes, 1); + } + + /// Replace many nodes with a number of nodes on the stack + fn replace_nodes(&mut self, to: &mut impl WriteMutations, nodes: &[VNode], m: usize) { + // We want to optimize the replace case to use one less mutation if possible + // Since mutations are done in reverse, the last node removed will be the first in the stack + // TODO: Instead of *just* removing it, we can use the replace mutation + let first_element = nodes[0].find_first_element(self); + to.insert_nodes_before(first_element, m); + + debug_assert!( + !nodes.is_empty(), + "replace_nodes must have at least one node" + ); + + self.remove_nodes(to, nodes); + } + + /// Remove these nodes from the dom + /// Wont generate mutations for the inner nodes + fn remove_nodes(&mut self, to: &mut impl WriteMutations, nodes: &[VNode]) { + nodes + .iter() + .rev() + .for_each(|node| node.remove_node(self, to, true)); + } + + pub(crate) fn remove_component_node( + &mut self, + to: &mut impl WriteMutations, + scope: ScopeId, + gen_muts: bool, + ) { + // Remove the component from the dom + if let Some(mut node) = self.scopes[scope.0].last_rendered_node.take() { + node.remove_node(self, to, gen_muts) + }; + + // Now drop all the resources + self.drop_scope(scope); + } + + /// Insert a new template into the VirtualDom's template registry + // used in conditional compilation + #[allow(unused_mut)] + pub(crate) fn register_template( + &mut self, + to: &mut impl WriteMutations, + mut template: Template, + ) { + let (path, byte_index) = template.name.rsplit_once(':').unwrap(); + + let byte_index = byte_index.parse::().unwrap(); + // First, check if we've already seen this template + if self + .templates + .get(&path) + .filter(|set| set.contains_key(&byte_index)) + .is_none() + { + // if hot reloading is enabled, then we need to check for a template that has overriten this one + #[cfg(debug_assertions)] + if let Some(mut new_template) = self + .templates + .get_mut(path) + .and_then(|map| map.remove(&usize::MAX)) + { + // the byte index of the hot reloaded template could be different + new_template.name = template.name; + template = new_template; + } + + self.templates + .entry(path) + .or_default() + .insert(byte_index, template); + + // If it's all dynamic nodes, then we don't need to register it + if !template.is_completely_dynamic() { + to.register_template(template) + } + } + } + + /// Insert a new template into the VirtualDom's template registry + pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template) { + // First, make sure we mark the template as seen, regardless if we process it + let (path, _) = template.name.rsplit_once(':').unwrap(); + if let Some((_, old_template)) = self + .templates + .entry(path) + .or_default() + .iter_mut() + .min_by_key(|(byte_index, _)| **byte_index) + { + // the byte index of the hot reloaded template could be different + template.name = old_template.name; + *old_template = template; + } else { + // This is a template without any current instances + self.templates + .entry(path) + .or_default() + .insert(usize::MAX, template); + } + + // If it's all dynamic nodes, then we don't need to register it + if !template.is_completely_dynamic() { + self.queued_templates.push(template); + } + } +} + +/// We can apply various optimizations to dynamic nodes that are the single child of their parent. +/// +/// IE +/// - for text - we can use SetTextContent +/// - for clearing children we can use RemoveChildren +/// - for appending children we can use AppendChildren +#[allow(dead_code)] +fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool { + let template = node.template.get(); + let path = template.node_paths[idx]; + + // use a loop to index every static node's children until the path has run out + // only break if the last path index is a dynamic node + let mut static_node = &template.roots[path[0] as usize]; + + for i in 1..path.len() - 1 { + match static_node { + TemplateNode::Element { children, .. } => static_node = &children[path[i] as usize], + _ => return false, + } + } + + match static_node { + TemplateNode::Element { children, .. } => children.len() == 1, + _ => false, + } +} diff --git a/packages/core/src/diff/node.rs b/packages/core/src/diff/node.rs new file mode 100644 index 000000000..7b6642c6b --- /dev/null +++ b/packages/core/src/diff/node.rs @@ -0,0 +1,988 @@ +use crate::{Attribute, AttributeValue, DynamicNode::*, VPlaceholder}; +use crate::{VNode, VirtualDom, WriteMutations}; +use core::iter::Peekable; +use std::cell::Ref; +use std::{ + cell::RefMut, + ops::{Deref, DerefMut}, +}; + +use crate::{ + any_props::AnyProps, + arena::ElementId, + innerlude::{DirtyScope, ElementPath, ElementRef, VComponent, VNodeMount, VText}, + nodes::DynamicNode, + nodes::RenderReturn, + scopes::ScopeId, + TemplateNode, + TemplateNode::*, +}; + +impl VNode { + pub(crate) fn diff_node( + &self, + new: &VNode, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) { + // If hot reloading is enabled, we need to make sure we're using the latest template + #[cfg(debug_assertions)] + { + let (path, byte_index) = new.template.get().name.rsplit_once(':').unwrap(); + if let Some(map) = dom.templates.get(path) { + let byte_index = byte_index.parse::().unwrap(); + if let Some(&template) = map.get(&byte_index) { + new.template.set(template); + if template != self.template.get() { + let parent = self.parent(); + return self.replace([new], parent, dom, to); + } + } + } + } + + { + // Copy over the mount information + *new.mount.borrow_mut() = self.mount.borrow_mut().take(); + } + + // If the templates are the same, we don't need to do anything, except copy over the mount information + if self == new { + return; + } + + // If the templates are different by name, we need to replace the entire template + if self.templates_are_different(new) { + return self.light_diff_templates(new, dom, to); + } + + let mut mount = self.assert_mounted_mut(); + let mount = &mut *mount; + + // If the templates are the same, we can diff the attributes and children + // Start with the attributes + self.dynamic_attrs + .iter() + .zip(new.dynamic_attrs.iter()) + .enumerate() + .for_each(|(idx, (old_attr, new_attr))| { + // If the attributes are different (or volatile), we need to update them + if old_attr.value != new_attr.value || new_attr.volatile { + self.update_attribute(mount, idx, new_attr, to); + } + }); + + // Now diff the dynamic nodes + self.dynamic_nodes + .iter() + .zip(new.dynamic_nodes.iter()) + .enumerate() + .for_each(|(dyn_node_idx, (old, new))| { + self.diff_dynamic_node(mount, dyn_node_idx, old, new, dom, to) + }); + } + + /// Push all the real nodes on the stack + pub(crate) fn push_all_real_nodes( + &self, + dom: &VirtualDom, + to: &mut impl WriteMutations, + ) -> usize { + let template = self.template.get(); + + let mount = self.assert_mounted(); + + template + .roots + .iter() + .enumerate() + .map(|(root_idx, _)| match &self.template.get().roots[root_idx] { + Dynamic { id: idx } => match &self.dynamic_nodes[*idx] { + Placeholder(_) | Text(_) => { + to.push_root(mount.root_ids[root_idx]); + 1 + } + Fragment(nodes) => { + let mut accumulated = 0; + for node in nodes { + accumulated += node.push_all_real_nodes(dom, to); + } + accumulated + } + + Component(_) => { + let scope = ScopeId(mount.mounted_dynamic_nodes[*idx]); + let node = dom.get_scope(scope).unwrap().root_node(); + node.push_all_real_nodes(dom, to) + } + }, + _ => { + to.push_root(mount.root_ids[root_idx]); + 1 + } + }) + .sum() + } + + fn diff_dynamic_node( + &self, + mount: &mut VNodeMount, + idx: usize, + old_node: &DynamicNode, + new_node: &DynamicNode, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) { + let parent = || ElementRef { + element: self.clone_with_parent(), + path: ElementPath { + path: self.template.get().node_paths[idx], + }, + }; + match (old_node, new_node) { + (Text(old), Text(new)) => self.diff_vtext( to, mount, idx, old, new), + (Placeholder(_), Placeholder(_)) => {}, + (Fragment(old), Fragment(new)) => dom.diff_non_empty_fragment(to, old, new, Some(parent())), + (Component(old), Component(new)) => { + let scope_id = ScopeId(mount.mounted_dynamic_nodes[idx]); + self.diff_vcomponent(mount, idx, new, old, scope_id, Some(parent()), dom, to) + }, + (Placeholder(_), Fragment(right)) => { + let placeholder_id = ElementId(mount.mounted_dynamic_nodes[idx]); + dom.replace_placeholder(to, placeholder_id, right, Some(parent()))}, + (Fragment(left), Placeholder(_)) => { + dom.nodes_to_placeholder(to, mount, idx, left,) + }, + _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."), + }; + } + + pub(crate) fn find_first_element(&self, dom: &VirtualDom) -> ElementId { + let mount = self.assert_mounted(); + match &self.template.get().roots[0] { + TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => mount.root_ids[0], + TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => { + match &self.dynamic_nodes[*id] { + Placeholder(_) | Text(_) => ElementId(mount.mounted_dynamic_nodes[*id]), + Fragment(children) => { + let child = children.first().unwrap(); + child.find_first_element(dom) + } + Component(comp) => { + let scope = ScopeId(mount.mounted_dynamic_nodes[*id]); + match dom.get_scope(scope).unwrap().root_node() { + RenderReturn::Ready(child) => child.find_first_element(dom), + _ => todo!("cannot handle nonstandard nodes"), + } + } + } + } + } + } + + pub(crate) fn find_last_element(&self, dom: &VirtualDom) -> ElementId { + let mount = self.assert_mounted(); + match &self.template.get().roots.last().unwrap() { + TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => { + *mount.root_ids.last().unwrap() + } + TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => { + match &self.dynamic_nodes[*id] { + Placeholder(_) | Text(_) => ElementId(mount.mounted_dynamic_nodes[*id]), + Fragment(t) => t.last().unwrap().find_last_element(dom), + Component(comp) => { + let scope = ScopeId(mount.mounted_dynamic_nodes[*id]); + match dom.get_scope(scope).unwrap().root_node() { + RenderReturn::Ready(node) => node.find_last_element(dom), + _ => todo!("cannot handle nonstandard nodes"), + } + } + } + } + } + } + + /// Diff the two text nodes + /// + /// This just sets the text of the node if it's different. + fn diff_vtext( + &self, + to: &mut impl WriteMutations, + mount: &VNodeMount, + idx: usize, + left: &VText, + right: &VText, + ) { + if left.value != right.value { + let id = ElementId(mount.mounted_dynamic_nodes[idx]); + to.set_node_text(&right.value, id); + } + } + + pub(crate) fn replace<'a>( + &self, + right: impl IntoIterator, + parent: Option, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) { + let m = dom.create_children(to, right, parent); + + // TODO: Instead of *just* removing it, we can use the replace mutation + let first_element = self.find_first_element(dom); + to.insert_nodes_before(first_element, m); + + self.remove_node(dom, to, true) + } + + pub(crate) fn remove_node( + &self, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + gen_muts: bool, + ) { + let mount = self.assert_mounted(); + + // Clean up any attributes that have claimed a static node as dynamic for mount/unmounts + // Will not generate mutations! + self.reclaim_attributes(&mount, dom, to); + + // Remove the nested dynamic nodes + // We don't generate mutations for these, as they will be removed by the parent (in the next line) + // But we still need to make sure to reclaim them from the arena and drop their hooks, etc + self.remove_nested_dyn_nodes(&mount, dom, to); + + // 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(&mount, dom, to, gen_muts); + } + + fn reclaim_roots( + &self, + mount: &VNodeMount, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + gen_muts: bool, + ) { + for (idx, node) in self.template.get().roots.iter().enumerate() { + if let Some(id) = node.dynamic_id() { + let dynamic_node = &self.dynamic_nodes[id]; + self.remove_dynamic_node(mount, dom, to, idx, dynamic_node, gen_muts); + } else { + let id = ElementId(mount.mounted_dynamic_nodes[idx]); + if gen_muts { + to.remove_node(id); + } + dom.reclaim(id); + } + } + } + + fn remove_nested_dyn_nodes( + &self, + mount: &VNodeMount, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) { + let template = self.template.get(); + for (idx, dyn_node) in self.dynamic_nodes.iter().enumerate() { + let path_len = template.node_paths.get(idx).map(|path| path.len()); + // Roots are cleaned up automatically above and nodes with a empty path are placeholders + if let Some(2..) = path_len { + self.remove_dynamic_node(mount, dom, to, idx, dyn_node, false) + } + } + } + + fn remove_dynamic_node( + &self, + mount: &VNodeMount, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + idx: usize, + node: &DynamicNode, + gen_muts: bool, + ) { + match node { + Component(comp) => { + let scope_id = ScopeId(mount.mounted_dynamic_nodes[idx]); + dom.remove_component_node(to, scope_id, gen_muts); + } + Text(_) | Placeholder(_) => { + let id = ElementId(mount.mounted_dynamic_nodes[idx]); + dom.remove_element_id(to, id, gen_muts) + } + Fragment(nodes) => nodes + .iter() + .for_each(|node| self.remove_node(dom, to, gen_muts)), + }; + } + + fn assert_mounted(&self) -> Ref<'_, VNodeMount> { + Ref::map(self.mount.borrow(), |mount| { + mount.as_ref().expect("to be mounted") + }) + } + + fn assert_mounted_mut(&self) -> RefMut<'_, VNodeMount> { + RefMut::map(self.mount.borrow_mut(), |mount| { + mount.as_mut().expect("to be mounted") + }) + } + + fn templates_are_different(&self, other: &VNode) -> bool { + let self_node_name = self.template.get().name; + let other_node_name = other.template.get().name; + // we want to re-create the node if the template name is different by pointer even if the value is the same so that we can detect when hot reloading changes the template + !std::ptr::eq(self_node_name, other_node_name) + } + + pub(super) fn reclaim_attributes( + &self, + mount: &VNodeMount, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) { + let mut id = None; + + for (idx, path) in self.template.get().attr_paths.iter().enumerate() { + let attr = &self.dynamic_attrs[idx]; + + // We clean up the roots in the next step, so don't worry about them here + if path.len() <= 1 { + continue; + } + + let next_id = mount.mounted_attributes[idx]; + + // only reclaim the new element if it's different from the previous one + if id != Some(next_id) { + dom.reclaim(next_id); + } + } + } + + pub(super) fn update_attribute( + &self, + mount: &VNodeMount, + idx: usize, + new_attr: &Attribute, + to: &mut impl WriteMutations, + ) { + let name = &new_attr.name; + let value = &new_attr.value; + let id = mount.mounted_attributes[idx]; + to.set_attribute(name, new_attr.namespace, value, id); + } + + /// Lightly diff the two templates, checking only their roots. + /// + /// The goal here is to preserve any existing component state that might exist. This is to preserve some React-like + /// behavior where the component state is preserved when the component is re-rendered. + /// + /// This is implemented by iterating each root, checking if the component is the same, if it is, then diff it. + /// + /// We then pass the new template through "create" which should be smart enough to skip roots. + /// + /// Currently, we only handle the case where the roots are the same component list. If there's any sort of deviation, + /// IE more nodes, less nodes, different nodes, or expressions, then we just replace the whole thing. + /// + /// This is mostly implemented to help solve the issue where the same component is rendered under two different + /// conditions: + /// + /// ```rust, ignore + /// if enabled { + /// rsx!{ Component { enabled_sign: "abc" } } + /// } else { + /// rsx!{ Component { enabled_sign: "xyz" } } + /// } + /// ``` + /// + /// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state, + /// then you should be passing in separate props instead. + /// + /// ```rust, ignore + /// let props = if enabled { + /// ComponentProps { enabled_sign: "abc" } + /// } else { + /// ComponentProps { enabled_sign: "xyz" } + /// }; + /// + /// rsx! { + /// Component { ..props } + /// } + /// ``` + pub(crate) fn light_diff_templates( + &self, + new: &VNode, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) { + let parent = new.parent(); + match matching_components(self, new) { + None => self.replace([new], parent, dom, to), + Some(components) => { + let mut mount = self.assert_mounted_mut(); + for (idx, (old_component, new_component)) in components.into_iter().enumerate() { + let scope_id = ScopeId(self.assert_mounted().mounted_dynamic_nodes[idx]); + self.diff_vcomponent( + &mut *mount, + idx, + old_component, + new_component, + scope_id, + parent.clone(), + dom, + to, + ) + } + } + } + } + + /// Create this template and write its mutations + pub(crate) fn create( + &self, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + parent: Option, + ) -> usize { + // check for a overridden template + #[cfg(debug_assertions)] + { + let template = self.template.get(); + let (path, byte_index) = template.name.rsplit_once(':').unwrap(); + if let Some(new_template) = dom + .templates + .get(path) + .and_then(|map| map.get(&byte_index.parse().unwrap())) + { + self.template.set(*new_template); + } + }; + + let template = self.template.get(); + + // The best renderers will have templates pre-hydrated and registered + // Just in case, let's create the template using instructions anyways + dom.register_template(to, template); + + // Walk the roots, creating nodes and assigning IDs + // nodes in an iterator of ((dynamic_node_index, sorted_index), path) + // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS) + #[cfg(not(debug_assertions))] + let (mut attrs, mut nodes) = ( + template.attr_paths.iter().copied().enumerate().peekable(), + template + .node_paths + .iter() + .copied() + .enumerate() + .map(|(i, path)| ((i, i), path)) + .peekable(), + ); + // If this is a debug build, we need to check that the paths are in the correct order because hot reloading can cause scrambled states + + #[cfg(debug_assertions)] + let (attrs_sorted, nodes_sorted) = + { (sort_bfs(template.attr_paths), sort_bfs(template.node_paths)) }; + #[cfg(debug_assertions)] + let (mut attrs, mut nodes) = { + ( + attrs_sorted.into_iter().peekable(), + nodes_sorted + .iter() + .copied() + .enumerate() + .map(|(i, (id, path))| ((id, i), path)) + .peekable(), + ) + }; + + let mut node = self.mount.borrow_mut(); + + // Initialize the mount information for this template + *node = Some(VNodeMount { + parent, + root_ids: vec![ElementId(0); template.roots.len()].into_boxed_slice(), + mounted_attributes: vec![ElementId(0); template.attr_paths.len()].into_boxed_slice(), + mounted_dynamic_nodes: vec![0; template.node_paths.len()].into_boxed_slice(), + }); + + let mount = node.as_mut().unwrap(); + + template + .roots + .iter() + .enumerate() + .map(|(idx, root)| match root { + DynamicText { id } | Dynamic { id } => { + nodes.next().unwrap(); + self.write_dynamic_root(mount, *id, dom, to) + } + Element { .. } => { + #[cfg(not(debug_assertions))] + let id = self.write_element_root( + mount, + node, + idx, + &mut attrs, + &mut nodes, + &[], + dom, + to, + ); + #[cfg(debug_assertions)] + let id = self.write_element_root( + mount, + idx, + &mut attrs, + &mut nodes, + &nodes_sorted, + dom, + to, + ); + id + } + TemplateNode::Text { .. } => self.write_static_text_root(mount, idx, dom, to), + }) + .sum() + } +} + +impl VNode { + fn write_static_text_root( + &self, + mount: &mut VNodeMount, + idx: usize, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) -> usize { + // Simply just load the template root, no modifications needed + self.load_template_root(mount, idx, dom, to); + + // Text producs just one node on the stack + 1 + } + + fn write_dynamic_root( + &self, + mount: &mut VNodeMount, + idx: usize, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) -> usize { + use DynamicNode::*; + match &self.dynamic_nodes[idx] { + Component(component) => { + let parent = Some(ElementRef { + path: ElementPath { + path: self.template.get().node_paths[idx], + }, + element: self.clone_with_parent(), + }); + self.create_component_node(mount, idx, component, parent, dom, to) + } + Fragment(frag) => { + let parent = Some(ElementRef { + path: ElementPath { + path: self.template.get().node_paths[idx], + }, + element: self.clone_with_parent(), + }); + dom.create_children(to, frag, parent) + } + Placeholder(_) => { + let id = mount.mount_node(idx, dom); + to.create_placeholder(id); + 1 + } + Text(VText { value }) => { + let id = mount.mount_node(idx, dom); + to.create_text_node(value, id); + 1 + } + } + } + + /// We write all the descendent data for this element + /// + /// Elements can contain other nodes - and those nodes can be dynamic or static + /// + /// We want to make sure we write these nodes while on top of the root + fn write_element_root( + &self, + mount: &mut VNodeMount, + root_idx: usize, + dynamic_attrs: &mut Peekable>, + dynamic_nodes_iter: &mut Peekable>, + dynamic_nodes: &[(usize, &'static [u8])], + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) -> usize { + // Load the template root and get the ID for the node on the stack + let root_on_stack = self.load_template_root(mount, root_idx, dom, to); + + // Write all the attributes below this root + self.write_attrs_on_root(mount, dynamic_attrs, root_idx as u8, root_on_stack, dom, to); + + // Load in all of the placeholder or dynamic content under this root too + self.load_placeholders( + mount, + dynamic_nodes_iter, + dynamic_nodes, + root_idx as u8, + dom, + to, + ); + + 1 + } + + /// Load all of the placeholder nodes for descendents of this root node + /// + /// ```rust, ignore + /// rsx! { + /// div { + /// // This is a placeholder + /// some_value, + /// + /// // Load this too + /// "{some_text}" + /// } + /// } + /// ``` + #[allow(unused)] + fn load_placeholders( + &self, + mount: &mut VNodeMount, + dynamic_nodes_iter: &mut Peekable>, + dynamic_nodes: &[(usize, &'static [u8])], + root_idx: u8, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) { + let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) { + Some((a, b)) => (a, b), + None => return, + }; + + // If hot reloading is enabled, we need to map the sorted index to the original index of the dynamic node. If it is disabled, we can just use the sorted index + #[cfg(not(debug_assertions))] + let reversed_iter = (start..=end).rev(); + #[cfg(debug_assertions)] + let reversed_iter = (start..=end) + .rev() + .map(|sorted_index| dynamic_nodes[sorted_index].0); + + for idx in reversed_iter { + let m = self.create_dynamic_node(mount, idx, dom, to); + if m > 0 { + // The path is one shorter because the top node is the root + let path = &self.template.get().node_paths[idx][1..]; + to.replace_placeholder_with_nodes(path, m); + } + } + } + + fn write_attrs_on_root( + &self, + mount: &mut VNodeMount, + attrs: &mut Peekable>, + root_idx: u8, + root: ElementId, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) { + 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, dom, to); + + loop { + self.write_attribute(mount, attr_id, id, dom, to); + + // Only push the dynamic attributes forward if they match the current path (same element) + match attrs.next_if(|(_, p)| *p == path) { + Some((next_attr_id, _)) => attr_id = next_attr_id, + None => break, + } + } + } + } + + fn write_attribute( + &self, + mount: &mut VNodeMount, + idx: usize, + id: ElementId, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) { + // Make sure we set the attribute's associated id + mount.mounted_attributes[idx] = id; + + let attribute = &self.dynamic_attrs[idx]; + + match &attribute.value { + AttributeValue::Listener(_) => { + // If this is a listener, we need to create an element reference for it so that when we receive an event, we can find the element + let path = &self.template.get().attr_paths[idx]; + let element_ref = ElementRef { + path: ElementPath { path }, + element: self.clone_with_parent(), + }; + dom.elements[id.0] = Some(element_ref); + to.create_event_listener(&attribute.name[2..], id); + } + _ => { + to.set_attribute(&attribute.name, attribute.namespace, &attribute.value, id); + } + } + } + + fn load_template_root( + &self, + mount: &mut VNodeMount, + root_idx: usize, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) -> ElementId { + // Get an ID for this root since it's a real root + let this_id = dom.next_element(); + mount.root_ids[root_idx] = this_id; + + to.load_template(self.template.get().name, root_idx, this_id); + + this_id + } + + /// We have some dynamic attributes attached to a some node + /// + /// That node needs to be loaded at runtime, so we need to give it an ID + /// + /// If the node in question is on the stack, we just return that ID + /// + /// If the node is not on the stack, we create a new ID for it and assign it + fn assign_static_node_as_dynamic( + &self, + path: &'static [u8], + this_id: ElementId, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) -> ElementId { + if path.len() == 1 { + return this_id; + } + + // 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 = dom.next_element(); + + to.assign_node_id(&path[1..], id); + + id + } + + pub(crate) fn create_dynamic_node( + &self, + mount: &mut VNodeMount, + index: usize, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) -> usize { + use DynamicNode::*; + let node = &self.dynamic_nodes[index]; + match node { + Text(text) => self.create_dynamic_text(mount, index, text, dom, to), + Placeholder(_) => self.create_placeholder(mount, index, dom, to), + Component(component) => { + let parent = Some(ElementRef { + path: ElementPath { + path: self.template.get().node_paths[index], + }, + element: self.clone_with_parent(), + }); + self.create_component_node(mount, index, component, parent, dom, to) + } + Fragment(frag) => { + let parent = Some(ElementRef { + path: ElementPath { + path: self.template.get().node_paths[index], + }, + element: self.clone_with_parent(), + }); + dom.create_children(to, frag, parent) + } + } + } + + /// Mount a root node and return its ID and the path to the node + fn mount_dynamic_node_with_path( + &self, + mount: &mut VNodeMount, + idx: usize, + dom: &mut VirtualDom, + ) -> (ElementId, &'static [u8]) { + // Add the mutation to the list + let path = self.template.get().node_paths[idx]; + + // Allocate a dynamic element reference for this text node + let new_id = mount.mount_node(idx, dom); + + (new_id, &path[1..]) + } + + fn create_dynamic_text( + &self, + mount: &mut VNodeMount, + idx: usize, + text: &VText, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) -> usize { + let (new_id, path) = self.mount_dynamic_node_with_path(mount, idx, dom); + + // Hydrate the text node + to.hydrate_text_node(path, &text.value, new_id); + + // Since we're hydrating an existing node, we don't create any new nodes + 0 + } + + pub(crate) fn create_placeholder( + &self, + mount: &mut VNodeMount, + idx: usize, + dom: &mut VirtualDom, + to: &mut impl WriteMutations, + ) -> usize { + let (id, path) = self.mount_dynamic_node_with_path(mount, idx, dom); + + // Assign the ID to the existing node in the template + to.assign_node_id(path, id); + + // Since the placeholder is already in the DOM, we don't create any new nodes + 0 + } +} + +impl VNodeMount { + fn mount_attribute(&mut self, attribute_index: usize, dom: &mut VirtualDom) -> ElementId { + let id = dom.next_element(); + self.mounted_attributes[attribute_index] = id; + id + } + + fn mount_node(&mut self, node_index: usize, dom: &mut VirtualDom) -> ElementId { + let id = dom.next_element(); + self.mounted_dynamic_nodes[node_index] = id.0; + id + } +} + +fn collect_dyn_node_range( + dynamic_nodes: &mut Peekable>, + root_idx: u8, +) -> Option<(usize, usize)> { + let start = match dynamic_nodes.peek() { + Some(((_, idx), [first, ..])) if *first == root_idx => *idx, + _ => return None, + }; + + let mut end = start; + + while let Some(((_, idx), p)) = + dynamic_nodes.next_if(|(_, p)| matches!(p, [idx, ..] if *idx == root_idx)) + { + if p.len() == 1 { + continue; + } + + end = idx; + } + + Some((start, end)) +} + +fn matching_components<'a>( + left: &'a VNode, + right: &'a VNode, +) -> Option> { + let left_node = left.template.get(); + let right_node = right.template.get(); + if left_node.roots.len() != right_node.roots.len() { + return None; + } + + // run through the components, ensuring they're the same + left_node + .roots + .iter() + .zip(right_node.roots.iter()) + .map(|(l, r)| { + let (l, r) = match (l, r) { + (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r), + _ => return None, + }; + + let (l, r) = match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) { + (Component(l), Component(r)) => (l, r), + _ => return None, + }; + + Some((l, r)) + }) + .collect() +} + +#[cfg(debug_assertions)] +fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> { + let mut with_indecies = paths.iter().copied().enumerate().collect::>(); + with_indecies.sort_unstable_by(|(_, a), (_, b)| { + let mut a = a.iter(); + let mut b = b.iter(); + loop { + match (a.next(), b.next()) { + (Some(a), Some(b)) => { + if a != b { + return a.cmp(b); + } + } + // The shorter path goes first + (None, Some(_)) => return std::cmp::Ordering::Less, + (Some(_), None) => return std::cmp::Ordering::Greater, + (None, None) => return std::cmp::Ordering::Equal, + } + } + }); + with_indecies +} + +#[test] +#[cfg(debug_assertions)] +fn sorting() { + let r: [(usize, &[u8]); 5] = [ + (0, &[0, 1]), + (1, &[0, 2]), + (2, &[1, 0]), + (3, &[1, 0, 1]), + (4, &[1, 2]), + ]; + assert_eq!( + sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 0, 1,], &[1, 2,],]), + r + ); + let r: [(usize, &[u8]); 6] = [ + (0, &[0]), + (1, &[0, 1]), + (2, &[0, 1, 2]), + (3, &[1]), + (4, &[1, 2]), + (5, &[2]), + ]; + assert_eq!( + sort_bfs(&[&[0], &[0, 1], &[0, 1, 2], &[1], &[1, 2], &[2],]), + r + ); +} diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index 4fe475d33..6c3349540 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -287,7 +287,6 @@ fn default_handler(error: CapturedError) -> Element { Some(VNode::new( None, TEMPLATE, - Box::new([Default::default(); 1]), Box::new([error.to_string().into_dyn_node()]), Default::default(), )) @@ -460,7 +459,6 @@ pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element { VNode::new( None, TEMPLATE, - Box::new([Default::default(); 1]), Box::new([(props.children).into_dyn_node()]), Default::default(), ) diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 41246a196..95a12ba54 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -5,7 +5,6 @@ mod any_props; mod arena; -mod create; mod diff; mod dirty_scope; mod error_boundary; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 138303eb0..58c1feb1e 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -2,12 +2,13 @@ use crate::any_props::{BoxedAnyProps, VProps}; use crate::innerlude::{ElementRef, EventHandler}; use crate::Properties; use crate::{arena::ElementId, Element, Event, ScopeId}; +use std::cell::RefCell; use std::ops::Deref; use std::rc::Rc; use std::vec; use std::{ any::{Any, TypeId}, - cell::{Cell, RefCell}, + cell::Cell, fmt::{Arguments, Debug}, }; @@ -27,17 +28,44 @@ pub enum RenderReturn { /// The component aborted rendering early. It might've thrown an error. /// - /// In its place we've produced a placeholder to locate its spot in the dom when - /// it recovers. - Aborted(VPlaceholder), + /// In its place we've produced a placeholder to locate its spot in the dom when it recovers. + Aborted(VNode), } impl Default for RenderReturn { fn default() -> Self { - RenderReturn::Aborted(VPlaceholder::default()) + RenderReturn::Aborted(VNode::placeholder()) } } +impl Deref for RenderReturn { + type Target = VNode; + + fn deref(&self) -> &Self::Target { + match self { + RenderReturn::Ready(node) | RenderReturn::Aborted(node) => node, + } + } +} + +/// The information about the +#[derive(Debug)] +pub(crate) struct VNodeMount { + /// The parent of this node + pub parent: Option, + + /// The IDs for the roots of this template - to be used when moving the template around and removing it from + /// the actual Dom + pub root_ids: Box<[ElementId]>, + + /// The element in the DOM that each attribute is mounted to + pub(crate) mounted_attributes: Box<[ElementId]>, + + /// For components: This is the ScopeId the component is mounted to + /// For other dynamic nodes: This is element in the DOM that each dynamic node is mounted to + pub(crate) mounted_dynamic_nodes: Box<[usize]>, +} + /// A reference to a template along with any context needed to hydrate it /// /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping @@ -49,13 +77,6 @@ pub struct VNodeInner { /// In fragments, this is the key of the first child. In other cases, it is the key of the root. pub key: Option, - /// When rendered, this template will be linked to its parent manually - pub(crate) parent: RefCell>, - - /// The IDs for the roots of this template - to be used when moving the template around and removing it from - /// the actual Dom - pub root_ids: RefCell>, - /// The static nodes and static descriptor of the template pub template: Cell