Fix nested rsx expansion by not using template titles (#2799)

* Fix nested rsx expansion by not using template titles

* fix writers with nameless templates

* fix clippy

* dont commit vscode fix

* fix release mode, pull out __template_name

* fix axum_desktop

* Fix core tests

* Make most fields of HotReloadedTemplate public for testing

* wip: formatting, compare all diff cases

* slightly smarter diffing for dynamic nodes

* add a comment about generic node diffing

* clean up mutations a bit

* fix load template

* simplify maybe_rebuild

* Restore write mutations flag in web

* write_mutations -> skip_mutations

---------

Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
This commit is contained in:
Jonathan KelleyandEvan Almloff authored and GitHub committed 2024-08-13 01:59:04 +00:00
1 parent a2180b92e9
commit 0de3bf7aeb
40 files changed
+597 -1038

No files matched your search

+3 -3
View File
@@ -27,10 +27,10 @@ impl MountId {
pub(crate) const PLACEHOLDER: Self = Self(usize::MAX);
pub(crate) fn as_usize(self) -> Option<usize> {
if self == Self::PLACEHOLDER {
None
} else {
if self.mounted() {
Some(self.0)
} else {
None
}
}
+2 -79
View File
@@ -10,11 +10,10 @@
#![allow(clippy::too_many_arguments)]
use crate::{
arena::ElementId,
innerlude::{ElementRef, MountId, WriteMutations},
innerlude::{ElementRef, WriteMutations},
nodes::VNode,
virtual_dom::VirtualDom,
Template, TemplateNode,
TemplateNode,
};
mod component;
@@ -34,62 +33,6 @@ impl VirtualDom {
.sum()
}
/// Simply replace a placeholder with a list of nodes
fn replace_placeholder(
&mut self,
mut to: Option<&mut impl WriteMutations>,
placeholder_id: ElementId,
r: &[VNode],
parent: Option<ElementRef>,
) {
let m = self.create_children(to.as_deref_mut(), r, parent);
if let Some(to) = to {
self.replace_placeholder_with_nodes_on_stack(to, placeholder_id, m)
}
}
fn replace_placeholder_with_nodes_on_stack(
&mut self,
to: &mut impl WriteMutations,
placeholder_id: ElementId,
m: usize,
) {
to.replace_node_with(placeholder_id, m);
self.reclaim(placeholder_id);
}
fn nodes_to_placeholder(
&mut self,
mut to: Option<&mut impl WriteMutations>,
mount: MountId,
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
self.mounts[mount.0].mounted_dynamic_nodes[dyn_node_idx] = placeholder.0;
if let Some(to) = to.as_deref_mut() {
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: Option<&mut impl WriteMutations>, nodes: &[VNode], m: usize) {
debug_assert!(
!nodes.is_empty(),
"replace_nodes must have at least one node"
);
// We want to optimize the replace case to use one less mutation if possible
// Instead of *just* removing it, we can use the replace mutation
self.remove_nodes(to, nodes, Some(m));
}
/// Remove these nodes from the dom
/// Wont generate mutations for the inner nodes
fn remove_nodes(
@@ -103,26 +46,6 @@ impl VirtualDom {
node.remove_node(self, to.as_deref_mut(), replace_with.filter(|_| last_node));
}
}
/// 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,
) {
if self.templates.contains(&template.name) {
return;
}
_ = self.templates.insert(template.name);
// If it's all dynamic nodes, then we don't need to register it
if !template.is_completely_dynamic() {
to.register_template(template)
}
}
}
/// We can apply various optimizations to dynamic nodes that are the single child of their parent.
+75 -224
View File
@@ -5,7 +5,7 @@ use core::iter::Peekable;
use crate::{
arena::ElementId,
innerlude::{ElementPath, ElementRef, VComponent, VNodeMount, VText},
innerlude::{ElementPath, ElementRef, VNodeMount, VText},
nodes::DynamicNode,
scopes::ScopeId,
TemplateNode,
@@ -21,9 +21,12 @@ impl VNode {
// The node we are diffing from should always be mounted
debug_assert!(dom.mounts.get(self.mount.get().0).is_some() || to.is_none());
// 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);
// If the templates are different, we need to replace the entire template
if self.template != new.template {
let mount_id = self.mount.get();
let mount = &dom.mounts[mount_id.0];
let parent = mount.parent;
return self.replace(std::slice::from_ref(new), parent, dom, to);
}
let mount_id = self.mount.get();
@@ -73,75 +76,62 @@ impl VNode {
old_node: &DynamicNode,
new_node: &DynamicNode,
dom: &mut VirtualDom,
to: Option<&mut impl WriteMutations>,
mut to: Option<&mut impl WriteMutations>,
) {
tracing::trace!("diffing dynamic node from {old_node:?} to {new_node:?}");
match (old_node, new_node) {
(Text(old), Text(new)) => {
// Diffing text is just a side effect, if we are diffing suspended nodes and are not outputting mutations, we can skip it
if let Some(to) = to{
if let Some(to) = to {
let mount = &dom.mounts[mount.0];
self.diff_vtext(to, mount, idx, old, new)
}
},
(Text(_), Placeholder(_)) => {
self.replace_text_with_placeholder(to, mount, idx, dom)
},
(Placeholder(_), Text(new)) => {
self.replace_placeholder_with_text(to, mount, idx, new, dom)
},
(Placeholder(_), Placeholder(_)) => {},
(Fragment(old), Fragment(new)) => dom.diff_non_empty_fragment(to, old, new, Some(self.reference_to_dynamic_node(mount, idx))),
}
(Placeholder(_), Placeholder(_)) => {}
(Fragment(old), Fragment(new)) => dom.diff_non_empty_fragment(
to,
old,
new,
Some(self.reference_to_dynamic_node(mount, idx)),
),
(Component(old), Component(new)) => {
let scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
self.diff_vcomponent(mount, idx, new, old, scope_id, Some(self.reference_to_dynamic_node(mount, idx)), dom, to)
},
(Placeholder(_), Fragment(right)) => {
let placeholder_id = ElementId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
dom.replace_placeholder(to, placeholder_id, right, Some(self.reference_to_dynamic_node(mount, idx)))},
(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."),
let scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
self.diff_vcomponent(
mount,
idx,
new,
old,
scope_id,
Some(self.reference_to_dynamic_node(mount, idx)),
dom,
to,
)
}
(old, new) => {
// TODO: we should pass around the mount instead of the mount id
// that would make moving the mount around here much easier
// Mark the mount as unused. When a scope is created, it reads the mount and
// if it is the placeholder value, it will create the scope, otherwise it will
// reuse the scope
let old_mount = dom.mounts[mount.0].mounted_dynamic_nodes[idx];
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = usize::MAX;
let new_nodes_on_stack =
self.create_dynamic_node(new, mount, idx, dom, to.as_deref_mut());
// Restore the mount for the scope we are removing
let new_mount = dom.mounts[mount.0].mounted_dynamic_nodes[idx];
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = old_mount;
self.remove_dynamic_node(mount, dom, to, true, idx, old, Some(new_nodes_on_stack));
// Restore the mount for the node we created
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = new_mount;
}
};
}
/// Replace a text node with a placeholder node
pub(crate) fn replace_text_with_placeholder(
&self,
to: Option<&mut impl WriteMutations>,
mount: MountId,
idx: usize,
dom: &mut VirtualDom,
) {
if let Some(to) = to {
// Grab the text element id from the mount and replace it with a new placeholder
let text_id = ElementId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
let (id, _) = self.create_dynamic_node_with_path(mount, idx, dom);
to.create_placeholder(id);
to.replace_node_with(text_id, 1);
dom.reclaim(text_id);
}
}
/// Replace a placeholder node with a text node
pub(crate) fn replace_placeholder_with_text(
&self,
to: Option<&mut impl WriteMutations>,
mount: MountId,
idx: usize,
new: &VText,
dom: &mut VirtualDom,
) {
if let Some(to) = to {
// Grab the placeholder id from the mount and replace it with a new text node
let placeholder_id = ElementId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
let (new_id, _) = self.create_dynamic_node_with_path(mount, idx, dom);
to.create_text_node(&new.value, new_id);
dom.replace_placeholder_with_nodes_on_stack(to, placeholder_id, 1);
}
}
/// Try to get the dynamic node and its index for a root node
pub(crate) fn get_dynamic_root_node_and_id(
&self,
@@ -401,12 +391,6 @@ impl VNode {
};
}
fn templates_are_different(&self, other: &VNode) -> bool {
let self_node_name = self.template.id();
let other_node_name = other.template.id();
self_node_name != other_node_name
}
pub(super) fn reclaim_attributes(&self, mount: MountId, dom: &mut VirtualDom) {
let mut next_id = None;
for (idx, path) in self.template.attr_paths.iter().enumerate() {
@@ -530,83 +514,6 @@ impl VNode {
}
}
/// 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, no_run
/// # use dioxus::prelude::*;
/// # let enabled = true;
/// # #[component]
/// # fn Component(enabled_sign: String) -> Element { unimplemented!() }
/// 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, no_run
/// # use dioxus::prelude::*;
/// # #[component]
/// # fn Component(enabled_sign: String) -> Element { unimplemented!() }
/// # let enabled = true;
/// let props = if enabled {
/// ComponentProps { enabled_sign: "abc".to_string() }
/// } else {
/// ComponentProps { enabled_sign: "xyz".to_string() }
/// };
///
/// rsx! {
/// Component { ..props }
/// };
/// ```
pub(crate) fn light_diff_templates(
&self,
new: &VNode,
dom: &mut VirtualDom,
mut to: Option<&mut impl WriteMutations>,
) {
let mount_id = self.mount.get();
let mount = &dom.mounts[mount_id.0];
let parent = mount.parent;
match matching_components(self, new) {
None => self.replace(std::slice::from_ref(new), parent, dom, to),
Some(components) => {
self.move_mount_to(new, dom);
for (idx, (old_component, new_component)) in components.into_iter().enumerate() {
let mount = &dom.mounts[mount_id.0];
let scope_id = ScopeId(mount.mounted_dynamic_nodes[idx]);
self.diff_vcomponent(
mount_id,
idx,
new_component,
old_component,
scope_id,
parent,
dom,
to.as_deref_mut(),
)
}
}
}
}
/// Create this rsx block. This will create scopes from components that this rsx block contains, but it will not write anything to the DOM.
pub(crate) fn create(
&self,
@@ -634,13 +541,6 @@ impl VNode {
});
}
// If we are outputting mutations, mount the node as well
if let Some(to) = to.as_deref_mut() {
// 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, path) and attrs in an iterator of (attr_index, path)
let mut nodes = template.node_paths.iter().copied().enumerate().peekable();
@@ -671,7 +571,13 @@ impl VNode {
// Take a dynamic node off the depth first iterator
nodes.next().unwrap();
// Then mount the node
self.create_dynamic_node(mount, *id, dom, to.as_deref_mut())
self.create_dynamic_node(
&self.dynamic_nodes[*id],
mount,
*id,
dom,
to.as_deref_mut(),
)
}
// For static text and element nodes, just load the template root. This may be a placeholder or just a static node. We now know that each root node has a unique id
TemplateNode::Text { .. } | TemplateNode::Element { .. } => {
@@ -720,13 +626,13 @@ impl VNode {
pub(crate) fn create_dynamic_node(
&self,
node: &DynamicNode,
mount: MountId,
dynamic_node_id: usize,
dom: &mut VirtualDom,
to: Option<&mut impl WriteMutations>,
) -> usize {
use DynamicNode::*;
let node = &self.dynamic_nodes[dynamic_node_id];
match node {
Component(component) => {
let parent = Some(self.reference_to_dynamic_node(mount, dynamic_node_id));
@@ -786,7 +692,13 @@ impl VNode {
// Only take nodes that are under this root node
let from_root_node = |(_, path): &(usize, &[u8])| path.first() == Some(&root_idx);
while let Some((dynamic_node_id, path)) = dynamic_nodes_iter.next_if(from_root_node) {
let m = self.create_dynamic_node(mount, dynamic_node_id, dom, to.as_deref_mut());
let m = self.create_dynamic_node(
&self.dynamic_nodes[dynamic_node_id],
mount,
dynamic_node_id,
dom,
to.as_deref_mut(),
);
if let Some(to) = to.as_deref_mut() {
// If we actually created real new nodes, we need to replace the placeholder for this dynamic node with the new dynamic nodes
if m > 0 {
@@ -856,7 +768,7 @@ impl VNode {
let this_id = dom.next_element();
dom.mounts[mount.0].root_ids[root_idx] = this_id;
to.load_template(self.template.name, root_idx, this_id);
to.load_template(self.template, root_idx, this_id);
this_id
}
@@ -888,22 +800,6 @@ impl VNode {
id
}
/// Mount a root node and return its ID and the path to the node
fn create_dynamic_node_with_path(
&self,
mount: MountId,
idx: usize,
dom: &mut VirtualDom,
) -> (ElementId, &'static [u8]) {
// Add the mutation to the list
let path = self.template.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: MountId,
@@ -912,19 +808,12 @@ impl VNode {
dom: &mut VirtualDom,
to: &mut impl WriteMutations,
) -> usize {
let (new_id, path) = self.create_dynamic_node_with_path(mount, idx, dom);
let new_id = mount.mount_node(idx, dom);
// If this is a root node, the path is empty and we need to create a new text node
if path.is_empty() {
to.create_text_node(&text.value, new_id);
// We create one node on the stack
1
} else {
// Dynamic text nodes always exist as a placeholder text node in the template, we can just hydrate that text node instead of creating a new one
to.hydrate_text_node(path, &text.value, new_id);
// Since we're hydrating an existing node, we don't create any new nodes
0
}
to.create_text_node(&text.value, new_id);
// We create one node on the stack
1
}
pub(crate) fn create_placeholder(
@@ -934,19 +823,12 @@ impl VNode {
dom: &mut VirtualDom,
to: &mut impl WriteMutations,
) -> usize {
let (id, path) = self.create_dynamic_node_with_path(mount, idx, dom);
let new_id = mount.mount_node(idx, dom);
// If this is a root node, the path is empty and we need to create a new placeholder node
if path.is_empty() {
to.create_placeholder(id);
// We create one node on the stack
1
} else {
// 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
}
to.create_placeholder(new_id);
// We create one node on the stack
1
}
}
@@ -957,34 +839,3 @@ impl MountId {
id
}
}
fn matching_components<'a>(
left: &'a VNode,
right: &'a VNode,
) -> Option<Vec<(&'a VComponent, &'a VComponent)>> {
let left_node = left.template;
let right_node = right.template;
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()
}
-3
View File
@@ -527,7 +527,6 @@ impl<F: Fn(ErrorContext) -> Element + 'static> From<F> for ErrorHandler {
fn default_handler(errors: ErrorContext) -> Element {
static TEMPLATE: Template = Template {
name: "error_handle.rs:42:5:884",
roots: &[TemplateNode::Element {
tag: "div",
namespace: None,
@@ -549,7 +548,6 @@ fn default_handler(errors: ErrorContext) -> Element {
.iter()
.map(|e| {
static TEMPLATE: Template = Template {
name: "error_handle.rs:43:5:884",
roots: &[TemplateNode::Element {
tag: "pre",
namespace: None,
@@ -753,7 +751,6 @@ pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element {
if errors.is_empty() {
std::result::Result::Ok({
static TEMPLATE: Template = Template {
name: "examples/error_handle.rs:81:17:2342",
roots: &[TemplateNode::Dynamic { id: 0usize }],
node_paths: &[&[0u8]],
attr_paths: &[],
+17 -17
View File
@@ -235,15 +235,6 @@ impl DynamicValuePool {
pub fn render_with(&mut self, hot_reload: &HotReloadedTemplate) -> VNode {
// Get the node_paths from a depth first traversal of the template
let node_paths = hot_reload.node_paths();
let attr_paths = hot_reload.attr_paths();
let template = Template {
name: hot_reload.name,
roots: hot_reload.roots,
node_paths,
attr_paths,
};
let key = hot_reload
.key
.as_ref()
@@ -259,7 +250,7 @@ impl DynamicValuePool {
.map(|attr| self.render_attribute(attr))
.collect();
VNode::new(key, template, dynamic_nodes, dynamic_attrs)
VNode::new(key, hot_reload.template, dynamic_nodes, dynamic_attrs)
}
fn render_dynamic_node(&mut self, node: &HotReloadDynamicNode) -> DynamicNode {
@@ -318,8 +309,8 @@ pub struct HotReloadTemplateWithLocation {
#[doc(hidden)]
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
pub struct HotReloadedTemplate {
pub name: &'static str,
pub key: Option<FmtedSegments>,
pub dynamic_nodes: Vec<HotReloadDynamicNode>,
pub dynamic_attributes: Vec<HotReloadDynamicAttribute>,
@@ -329,28 +320,37 @@ pub struct HotReloadedTemplate {
serde(deserialize_with = "crate::nodes::deserialize_leaky")
)]
pub roots: &'static [TemplateNode],
/// The template that is computed from the hot reload roots
template: Template,
}
impl HotReloadedTemplate {
pub fn new(
name: &'static str,
key: Option<FmtedSegments>,
dynamic_nodes: Vec<HotReloadDynamicNode>,
dynamic_attributes: Vec<HotReloadDynamicAttribute>,
component_values: Vec<HotReloadLiteral>,
roots: &'static [TemplateNode],
) -> Self {
let node_paths = Self::node_paths(roots);
let attr_paths = Self::attr_paths(roots);
let template = Template {
roots,
node_paths,
attr_paths,
};
Self {
name,
key,
dynamic_nodes,
dynamic_attributes,
component_values,
roots,
template,
}
}
fn node_paths(&self) -> &'static [&'static [u8]] {
fn node_paths(roots: &'static [TemplateNode]) -> &'static [&'static [u8]] {
fn add_node_paths(
roots: &[TemplateNode],
node_paths: &mut Vec<&'static [u8]>,
@@ -373,12 +373,12 @@ impl HotReloadedTemplate {
}
let mut node_paths = Vec::new();
add_node_paths(self.roots, &mut node_paths, Vec::new());
add_node_paths(roots, &mut node_paths, Vec::new());
let leaked: &'static [&'static [u8]] = Box::leak(node_paths.into_boxed_slice());
leaked
}
fn attr_paths(&self) -> &'static [&'static [u8]] {
fn attr_paths(roots: &'static [TemplateNode]) -> &'static [&'static [u8]] {
fn add_attr_paths(
roots: &[TemplateNode],
attr_paths: &mut Vec<&'static [u8]>,
@@ -403,7 +403,7 @@ impl HotReloadedTemplate {
}
let mut attr_paths = Vec::new();
add_attr_paths(self.roots, &mut attr_paths, Vec::new());
add_attr_paths(roots, &mut attr_paths, Vec::new());
let leaked: &'static [&'static [u8]] = Box::leak(attr_paths.into_boxed_slice());
leaked
}
+6 -95
View File
@@ -1,6 +1,4 @@
use rustc_hash::FxHashSet;
use crate::{arena::ElementId, AttributeValue, ScopeId, Template};
use crate::{arena::ElementId, AttributeValue, Template};
/// Something that can handle the mutations that are generated by the diffing process and apply them to the Real DOM
///
@@ -14,9 +12,6 @@ use crate::{arena::ElementId, AttributeValue, ScopeId, Template};
///
/// Mutations are the only link between the RealDOM and the VirtualDOM.
pub trait WriteMutations {
/// Register a template with the renderer
fn register_template(&mut self, template: Template);
/// Add these m children to the target element
///
/// Id: The ID of the element being mounted to
@@ -45,15 +40,6 @@ pub trait WriteMutations {
/// Id: The ID we're assigning to this specific text nodes. This will be used later to modify the element or replace it with another element.
fn create_text_node(&mut self, value: &str, id: ElementId);
/// Hydrate an existing text node at the given path with the given text.
///
/// Assign this text node the given ID since we will likely need to modify this text at a later point
///
/// Path: The path of the child of the topmost node on the stack. A path of `[]` represents the topmost node. A path of `[0]` represents the first child. `[0,1,2]` represents 1st child's 2nd child's 3rd child.
/// Value: The value of the textnode that we want to set the placeholder with
/// Id: The ID we're assigning to this specific text nodes. This will be used later to modify the element or replace it with another element.
fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId);
/// Load and clone an existing node from a template saved under that specific name
///
/// Dioxus guarantees that the renderer will have already been provided the template.
@@ -62,7 +48,7 @@ pub trait WriteMutations {
/// Name: The unique "name" of the template based on the template location. When paired with `rsx!`, this is autogenerated
/// Index: The index root we loading from the template. The template is stored as a list of nodes. This index represents the position of that root
/// Id: The ID we're assigning to this element being loaded from the template (This will be used later to move the element around in lists)
fn load_template(&mut self, name: &'static str, index: usize, id: ElementId);
fn load_template(&mut self, template: Template, index: usize, id: ElementId);
/// Replace the target element (given by its ID) with the topmost m nodes on the stack
///
@@ -129,12 +115,6 @@ pub trait WriteMutations {
///
/// Id: The ID of the root node to push.
fn push_root(&mut self, id: ElementId);
/// Swap to a new subtree
fn swap_subtree(&mut self, _subtree_index: usize) {}
/// Mark a scope as dirty
fn mark_scope_dirty(&mut self, _scope_id: ScopeId) {}
}
/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
@@ -156,8 +136,6 @@ pub enum Mutation {
///
/// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per
/// element, hence the use of a single byte.
///
///
AssignId {
/// The path of the child of the topmost node on the stack
///
@@ -192,33 +170,11 @@ pub enum Mutation {
id: ElementId,
},
/// Hydrate an existing text node at the given path with the given text.
///
/// Assign this text node the given ID since we will likely need to modify this text at a later point
HydrateText {
/// The path of the child of the topmost node on the stack
///
/// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
/// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
path: &'static [u8],
/// The value of the textnode that we want to set the placeholder with
value: String,
/// The ID we're assigning to this specific text nodes
///
/// This will be used later to modify the element or replace it with another element.
id: ElementId,
},
/// Load and clone an existing node from a template saved under that specific name
/// Load and clone an existing node from a template with a given ID
///
/// Dioxus guarantees that the renderer will have already been provided the template.
/// When the template is picked up in the template list, it should be saved under its "name" - here, the name
LoadTemplate {
/// The "name" of the template. When paired with `rsx!`, this is autogenerated
name: &'static str,
/// Which root are we loading from the template?
///
/// The template is stored as a list of nodes. This index represents the position of that root
@@ -328,38 +284,11 @@ pub enum Mutation {
/// A static list of mutations that can be applied to the DOM. Note: this list does not contain any `Any` attribute values
#[derive(Debug, PartialEq, Default)]
pub struct Mutations {
/// The list of Scopes that were diffed, created, and removed during the Diff process.
pub dirty_scopes: FxHashSet<ScopeId>,
/// Any templates encountered while diffing the DOM.
///
/// These must be loaded into a cache before applying the edits
pub templates: Vec<Template>,
/// Any mutations required to patch the renderer to match the layout of the VirtualDom
pub edits: Vec<Mutation>,
}
impl Mutations {
/// Rewrites IDs to just be "template", so you can compare the mutations
///
/// Used really only for testing
pub fn sanitize(mut self) -> Self {
for edit in self.edits.iter_mut() {
if let Mutation::LoadTemplate { name, .. } = edit {
*name = "template"
}
}
self
}
}
impl WriteMutations for Mutations {
fn register_template(&mut self, template: Template) {
self.templates.push(template)
}
fn append_children(&mut self, id: ElementId, m: usize) {
self.edits.push(Mutation::AppendChildren { id, m })
}
@@ -379,16 +308,8 @@ impl WriteMutations for Mutations {
})
}
fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId) {
self.edits.push(Mutation::HydrateText {
path,
value: value.into(),
id,
})
}
fn load_template(&mut self, name: &'static str, index: usize, id: ElementId) {
self.edits.push(Mutation::LoadTemplate { name, index, id })
fn load_template(&mut self, _template: Template, index: usize, id: ElementId) {
self.edits.push(Mutation::LoadTemplate { index, id })
}
fn replace_node_with(&mut self, id: ElementId, m: usize) {
@@ -457,20 +378,12 @@ impl WriteMutations for Mutations {
fn push_root(&mut self, id: ElementId) {
self.edits.push(Mutation::PushRoot { id })
}
fn swap_subtree(&mut self, _subtree_index: usize) {}
fn mark_scope_dirty(&mut self, scope_id: ScopeId) {
self.dirty_scopes.insert(scope_id);
}
}
/// A struct that ignores all mutations
pub struct NoOpMutations;
impl WriteMutations for NoOpMutations {
fn register_template(&mut self, _: Template) {}
fn append_children(&mut self, _: ElementId, _: usize) {}
fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {}
@@ -479,9 +392,7 @@ impl WriteMutations for NoOpMutations {
fn create_text_node(&mut self, _: &str, _: ElementId) {}
fn hydrate_text_node(&mut self, _: &'static [u8], _: &str, _: ElementId) {}
fn load_template(&mut self, _: &'static str, _: usize, _: ElementId) {}
fn load_template(&mut self, _: Template, _: usize, _: ElementId) {}
fn replace_node_with(&mut self, _: ElementId, _: usize) {}
+18 -20
View File
@@ -15,8 +15,6 @@ use std::{
fmt::{Arguments, Debug},
};
pub type TemplateId = &'static str;
/// The actual state of the component's most recent computation
///
/// If the component returned early (e.g. `return None`), this will be Aborted(None)
@@ -252,7 +250,6 @@ impl VNode {
dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]),
dynamic_attrs: Box::new([]),
template: Template {
name: "packages/core/nodes.rs:198:0:0",
roots: &[TemplateNode::Dynamic { id: 0 }],
node_paths: &[&[0]],
attr_paths: &[],
@@ -345,17 +342,9 @@ impl VNode {
/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
/// ways, with the suggested approach being the unique code location (file, line, col, etc).
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord)]
pub struct Template {
/// The name of the template. This must be unique across your entire program for template diffing to work properly
///
/// If two templates have the same name, it's likely that Dioxus will panic when diffing.
#[cfg_attr(
feature = "serialize",
serde(deserialize_with = "deserialize_string_leaky")
)]
pub name: &'static str,
/// The list of template nodes that make up the template
///
/// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
@@ -383,6 +372,22 @@ pub struct Template {
pub attr_paths: &'static [&'static [u8]],
}
impl std::hash::Hash for Template {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::ptr::hash(self.roots as *const _, state);
std::ptr::hash(self.node_paths as *const _, state);
std::ptr::hash(self.attr_paths as *const _, state);
}
}
impl PartialEq for Template {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.roots as *const _, other.roots as *const _)
&& std::ptr::eq(self.node_paths as *const _, other.node_paths as *const _)
&& std::ptr::eq(self.attr_paths as *const _, other.attr_paths as *const _)
}
}
#[cfg(feature = "serialize")]
pub(crate) fn deserialize_string_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a str, D::Error>
where
@@ -442,13 +447,6 @@ impl Template {
use TemplateNode::*;
self.roots.iter().all(|root| matches!(root, Dynamic { .. }))
}
/// Get a unique id for this template. If the id between two templates are different, the contents of the template may be different.
pub fn id(&self) -> usize {
// We compare the template name by pointer so that the id is different after hot reloading even if the name is the same
let ptr: *const str = self.name;
ptr as *const () as usize
}
}
/// A statically known node in a layout.
-1
View File
@@ -5,7 +5,6 @@ use crate::{prelude::*, properties::RootProps, DynamicNode, VComponent};
#[allow(clippy::let_and_return)]
pub(crate) fn RootScopeWrapper(props: RootProps<VComponent>) -> Element {
static TEMPLATE: Template = Template {
name: "root_wrapper.rs:16:5:561",
roots: &[TemplateNode::Dynamic { id: 0usize }],
node_paths: &[&[0u8]],
attr_paths: &[],
-21
View File
@@ -11,14 +11,12 @@ use crate::{
ElementRef, NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState, VNodeMount, VProps,
WriteMutations,
},
nodes::{Template, TemplateId},
runtime::{Runtime, RuntimeGuard},
scopes::ScopeId,
AttributeValue, ComponentFunction, Element, Event, Mutations,
};
use crate::{Task, VComponent};
use futures_util::StreamExt;
use rustc_hash::FxHashSet;
use slab::Slab;
use std::collections::BTreeSet;
use std::{any::Any, rc::Rc};
@@ -208,12 +206,6 @@ pub struct VirtualDom {
pub(crate) dirty_scopes: BTreeSet<ScopeOrder>,
// A map of templates we have sent to the renderer
pub(crate) templates: FxHashSet<TemplateId>,
// Templates changes that are queued for the next render
pub(crate) queued_templates: Vec<Template>,
// The element ids that are used in the renderer
// These mark a specific place in a whole rsx block
pub(crate) elements: Slab<Option<ElementRef>>,
@@ -336,8 +328,6 @@ impl VirtualDom {
runtime: Runtime::new(tx),
scopes: Default::default(),
dirty_scopes: Default::default(),
templates: Default::default(),
queued_templates: Default::default(),
elements: Default::default(),
mounts: Default::default(),
resolved_scopes: Default::default(),
@@ -612,7 +602,6 @@ impl VirtualDom {
/// ```
#[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")]
pub fn rebuild(&mut self, to: &mut impl WriteMutations) {
self.flush_templates(to);
let _runtime = RuntimeGuard::new(self.runtime.clone());
let new_nodes = self.run_scope(ScopeId::ROOT);
@@ -628,8 +617,6 @@ impl VirtualDom {
/// suspended subtrees.
#[instrument(skip(self, to), level = "trace", name = "VirtualDom::render_immediate")]
pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
self.flush_templates(to);
// Process any events that might be pending in the queue
// Signals marked with .write() need a chance to be handled by the effect driver
// This also processes futures which might progress into immediately rerunning a scope
@@ -785,14 +772,6 @@ impl VirtualDom {
self.runtime.clone()
}
/// Flush any queued template changes
#[instrument(skip(self, to), level = "trace", name = "VirtualDom::flush_templates")]
fn flush_templates(&mut self, to: &mut impl WriteMutations) {
for template in self.queued_templates.drain(..) {
to.register_template(template);
}
}
/*
------------------------
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when