separate mount information from VNodes

This commit is contained in:
Evan Almloff 2024-01-09 10:09:08 -06:00
parent c9bd5a4e6e
commit aed29b1dec
10 changed files with 1913 additions and 1844 deletions

View file

@ -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::<Vec<_>>();
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<impl Iterator<Item = (usize, &'static [u8])>>,
dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
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<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
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<impl Iterator<Item = (usize, &'static [u8])>>,
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::<usize>().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<Option<ElementId>>) -> ElementId {
let id = self.next_element();
slot.set(Some(id));
id
}
}
fn collect_dyn_node_range(
dynamic_nodes: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
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))
}

File diff suppressed because it is too large Load diff

View file

@ -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<ElementRef>,
) -> 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<ElementRef>,
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<ElementRef>,
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<ElementRef>,
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)
}
}

View file

@ -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<ElementRef>,
) {
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<ElementRef>,
) {
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<ElementRef>,
) {
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<ElementRef>,
) -> 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<ElementRef>,
) {
/*
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::<FxHashMap<_, _>>();
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::<Vec<_>>();
// 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<ElementRef>,
) {
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<ElementRef>,
) {
let m = self.create_children(to, new, parent);
let id = after.find_last_element(self);
to.insert_nodes_after(id, m);
}
}

View file

@ -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<Item = &'a VNode>,
parent: Option<ElementRef>,
) -> 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<Item = &'a VNode>,
parent: Option<ElementRef>,
) {
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::<usize>().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,
}
}

View file

@ -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::<usize>().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<Item = &'a VNode>,
parent: Option<ElementRef>,
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<ElementRef>,
) -> 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<impl Iterator<Item = (usize, &'static [u8])>>,
dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
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<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
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<impl Iterator<Item = (usize, &'static [u8])>>,
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<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
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<Vec<(&'a VComponent, &'a VComponent)>> {
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::<Vec<_>>();
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
);
}

View file

@ -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(),
)

View file

@ -5,7 +5,6 @@
mod any_props;
mod arena;
mod create;
mod diff;
mod dirty_scope;
mod error_boundary;

View file

@ -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<ElementRef>,
/// 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<String>,
/// When rendered, this template will be linked to its parent manually
pub(crate) parent: RefCell<Option<ElementRef>>,
/// 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<Box<[ElementId]>>,
/// The static nodes and static descriptor of the template
pub template: Cell<Template>,
@ -70,12 +91,26 @@ pub struct VNodeInner {
///
/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
/// static parts of the template.
#[derive(Clone, Debug)]
pub struct VNode(Rc<VNodeInner>);
#[derive(Debug)]
pub struct VNode {
vnode: Rc<VNodeInner>,
/// The mount information for this template
pub(crate) mount: Rc<RefCell<Option<VNodeMount>>>,
}
impl Clone for VNode {
fn clone(&self) -> Self {
Self {
vnode: self.vnode.clone(),
mount: Default::default(),
}
}
}
impl PartialEq for VNode {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
Rc::ptr_eq(&self.vnode, &other.vnode)
}
}
@ -83,44 +118,78 @@ impl Deref for VNode {
type Target = VNodeInner;
fn deref(&self) -> &Self::Target {
&self.0
&self.vnode
}
}
impl VNode {
/// Clone the element while retaining the mounted parent of the node
pub(crate) fn clone_with_parent(&self) -> Self {
Self {
vnode: self.vnode.clone(),
mount: self.mount.clone(),
}
}
/// Try to get the parent of this node
///
/// This will fail if the VNode is not mounted
pub(crate) fn parent(&self) -> Option<ElementRef> {
self.mount.borrow().as_ref()?.parent.clone()
}
/// Create a template with no nodes that will be skipped over during diffing
pub fn empty() -> Element {
Some(Self(Rc::new(VNodeInner {
key: None,
parent: Default::default(),
root_ids: Default::default(),
dynamic_nodes: Box::new([]),
dynamic_attrs: Box::new([]),
template: Cell::new(Template {
name: "dioxus-empty",
roots: &[],
node_paths: &[],
attr_paths: &[],
Some(Self {
vnode: Rc::new(VNodeInner {
key: None,
dynamic_nodes: Box::new([]),
dynamic_attrs: Box::new([]),
template: Cell::new(Template {
name: "dioxus-empty",
roots: &[],
node_paths: &[],
attr_paths: &[],
}),
}),
})))
mount: Default::default(),
})
}
/// Create a template with a single placeholder node
pub fn placeholder() -> Self {
Self {
vnode: Rc::new(VNodeInner {
key: None,
dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]),
dynamic_attrs: Box::new([]),
template: Cell::new(Template {
name: "dioxus-placeholder",
roots: &[TemplateNode::Dynamic { id: 0 }],
node_paths: &[&[]],
attr_paths: &[],
}),
}),
mount: Default::default(),
}
}
/// Create a new VNode
pub fn new(
key: Option<String>,
template: Template,
root_ids: Box<[ElementId]>,
dynamic_nodes: Box<[DynamicNode]>,
dynamic_attrs: Box<[Attribute]>,
) -> Self {
Self(Rc::new(VNodeInner {
key,
parent: Default::default(),
template: Cell::new(template),
root_ids: RefCell::new(root_ids),
dynamic_nodes,
dynamic_attrs,
}))
Self {
vnode: Rc::new(VNodeInner {
key,
template: Cell::new(template),
dynamic_nodes,
dynamic_attrs,
}),
mount: Default::default(),
}
}
/// Load a dynamic root at the given index
@ -275,7 +344,7 @@ pub enum TemplateNode {
)]
namespace: Option<&'static str>,
/// A list of possibly dynamic attribues for this element
/// A list of possibly dynamic attributes for this element
///
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
@ -307,6 +376,17 @@ pub enum TemplateNode {
},
}
impl TemplateNode {
/// Try to load the dynamic node at the given index
pub fn dynamic_id(&self) -> Option<usize> {
use TemplateNode::*;
match self {
Dynamic { id } | DynamicText { id } => Some(*id),
_ => None,
}
}
}
/// A node created at runtime
///
/// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index
@ -357,9 +437,6 @@ pub struct VComponent {
/// The name of this component
pub name: &'static str,
/// The assigned Scope for this component
pub(crate) scope: Cell<Option<ScopeId>>,
/// The function pointer of the component, known at compile time
///
/// It is possible that components get folded at compile time, so these shouldn't be really used as a key
@ -394,47 +471,29 @@ impl VComponent {
name: fn_name,
render_fn: component as *const (),
props: BoxedAnyProps::new(vcomp),
scope: Default::default(),
}
}
/// Get the scope that this component is mounted to
pub fn mounted_scope(&self) -> Option<ScopeId> {
self.scope.get()
}
}
impl std::fmt::Debug for VComponent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VComponent")
.field("name", &self.name)
.field("scope", &self.scope)
.finish()
}
}
/// An instance of some text, mounted to the DOM
/// A text node
#[derive(Clone, Debug)]
pub struct VText {
/// The actual text itself
pub value: String,
/// The ID of this node in the real DOM
pub(crate) id: Cell<Option<ElementId>>,
}
impl VText {
/// Create a new VText
pub fn new(value: String) -> Self {
Self {
value,
id: Default::default(),
}
}
/// Get the mounted ID of this node
pub fn mounted_element(&self) -> Option<ElementId> {
self.id.get()
Self { value }
}
}
@ -446,19 +505,8 @@ impl From<Arguments<'_>> for VText {
/// A placeholder node, used by suspense and fragments
#[derive(Clone, Debug, Default)]
pub struct VPlaceholder {
/// The ID of this node in the real DOM
pub(crate) id: Cell<Option<ElementId>>,
/// The parent of this node
pub(crate) parent: RefCell<Option<ElementRef>>,
}
impl VPlaceholder {
/// Get the mounted ID of this node
pub fn mounted_element(&self) -> Option<ElementId> {
self.id.get()
}
}
#[non_exhaustive]
pub struct VPlaceholder {}
/// An attribute of the TemplateNode, created at compile time
#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
@ -509,9 +557,6 @@ pub struct Attribute {
/// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
pub volatile: bool,
/// The element in the DOM that this attribute belongs to
pub(crate) mounted_element: Cell<ElementId>,
}
impl Attribute {
@ -529,15 +574,9 @@ impl Attribute {
name,
namespace,
volatile,
mounted_element: Default::default(),
value: value.into_value(),
}
}
/// Get the element that this attribute is mounted to
pub fn mounted_element(&self) -> ElementId {
self.mounted_element.get()
}
}
/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements
@ -688,17 +727,13 @@ impl IntoDynNode for &str {
fn into_dyn_node(self) -> DynamicNode {
DynamicNode::Text(VText {
value: self.to_string(),
id: Default::default(),
})
}
}
impl IntoDynNode for String {
fn into_dyn_node(self) -> DynamicNode {
DynamicNode::Text(VText {
value: self,
id: Default::default(),
})
DynamicNode::Text(VText { value: self })
}
}
@ -706,7 +741,6 @@ impl IntoDynNode for Arguments<'_> {
fn into_dyn_node(self) -> DynamicNode {
DynamicNode::Text(VText {
value: self.to_string(),
id: Default::default(),
})
}
}

View file

@ -184,7 +184,7 @@ pub struct VirtualDom {
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
// Maps a template path to a map of byteindexes to templates
// Maps a template path to a map of byte indexes to templates
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template>>,
// Templates changes that are queued for the next render
@ -324,7 +324,7 @@ impl VirtualDom {
}
}
/// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an dynamic element, not a static node or a text node.**
/// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an element with a listener, not a static node or a text node.**
///
/// This method will identify the appropriate element. The data must match up with the listener declared. Note that
/// this method does not give any indication as to the success of the listener call. If the listener is not found,
@ -417,7 +417,8 @@ impl VirtualDom {
}
}
parent_node = el_ref.parent.borrow().clone();
let mount = el_ref.mount.borrow();
parent_node = mount.as_ref().unwrap().parent.clone();
}
} else {
// Otherwise, we just call the listener on the target element
@ -551,20 +552,11 @@ impl VirtualDom {
let _runtime = RuntimeGuard::new(self.runtime.clone());
let new_nodes = self.run_scope(ScopeId::ROOT);
self.scopes[ScopeId::ROOT.0].last_rendered_node = Some(new_nodes.clone());
match new_nodes {
// Rebuilding implies we append the created elements to the root
RenderReturn::Ready(node) => {
let m = self.create_scope(ScopeId::ROOT, &node, to);
to.append_children(ElementId(0), m);
}
// If an error occurs, we should try to render the default error component and context where the error occured
RenderReturn::Aborted(placeholder) => {
tracing::debug!("Ran into suspended or aborted scope during rebuild");
let id = self.next_element();
placeholder.id.set(Some(id));
to.create_placeholder(id);
}
}
let (RenderReturn::Ready(mut node) | RenderReturn::Aborted(mut node)) = new_nodes;
// Rebuilding implies we append the created elements to the root
let m = self.create_scope(to, ScopeId::ROOT, &mut node, None);
to.append_children(ElementId(0), m);
}
/// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
@ -629,7 +621,8 @@ impl VirtualDom {
let _runtime = RuntimeGuard::new(self.runtime.clone());
// Run the scope and get the mutations
let new_nodes = self.run_scope(dirty.id);
self.diff_scope(dirty.id, new_nodes, to);
self.diff_scope(to, dirty.id, new_nodes);
}
}