port over more diffing + creation logic

This commit is contained in:
Evan Almloff committed 2024-01-05 12:07:29 -06:00
1 parent 717f38a5ec
commit 113b2c1529
7 files changed
+69 -68

No files matched your search

+3 -7
View File
@@ -408,11 +408,7 @@ impl VirtualDom {
} }
/// Insert a new template into the VirtualDom's template registry /// Insert a new template into the VirtualDom's template registry
pub(crate) fn register_template_first_byte_index( pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template) {
&mut self,
mut template: Template<'static>,
to: &mut impl WriteMutations,
) {
// First, make sure we mark the template as seen, regardless if we process it // First, make sure we mark the template as seen, regardless if we process it
let (path, _) = template.name.rsplit_once(':').unwrap(); let (path, _) = template.name.rsplit_once(':').unwrap();
if let Some((_, old_template)) = self if let Some((_, old_template)) = self
@@ -435,7 +431,7 @@ impl VirtualDom {
// If it's all dynamic nodes, then we don't need to register it // If it's all dynamic nodes, then we don't need to register it
if !template.is_completely_dynamic() { if !template.is_completely_dynamic() {
to.register_template(template); self.queued_templates.push(template);
} }
} }
@@ -444,7 +440,7 @@ impl VirtualDom {
#[allow(unused_mut)] #[allow(unused_mut)]
pub(crate) fn register_template( pub(crate) fn register_template(
&mut self, &mut self,
mut template: Template<'static>, mut template: Template,
to: &mut impl WriteMutations, to: &mut impl WriteMutations,
) { ) {
let (path, byte_index) = template.name.rsplit_once(':').unwrap(); let (path, byte_index) = template.name.rsplit_once(':').unwrap();
+29 -38
View File
@@ -6,7 +6,6 @@ use crate::{
innerlude::{ innerlude::{
DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, VText, WriteMutations, DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, VText, WriteMutations,
}, },
mutations::Mutation,
nodes::RenderReturn, nodes::RenderReturn,
nodes::{DynamicNode, VNode}, nodes::{DynamicNode, VNode},
scopes::ScopeId, scopes::ScopeId,
@@ -63,15 +62,10 @@ impl VirtualDom {
*p.parent.borrow_mut() = l.parent.borrow().clone(); *p.parent.borrow_mut() = l.parent.borrow().clone();
to.create_placeholder(id); to.create_placeholder(id);
self.remove_node(l, true, to); to.insert_nodes_before(id, 1);
// We want to optimize the replace case to use one less mutation if possible // TODO: Instead of *just* removing it, we can use the replace mutation
// Since mutations are done in reverse, the last node removed will be the first in the stack self.remove_node(l, true, to);
// Instead of *just* removing it, we can use the replace mutation
match self.mutations.edits.pop().unwrap() {
Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m: 1 }),
_ => panic!("Expected remove mutation from remove_node"),
};
} }
fn diff_node( fn diff_node(
@@ -250,17 +244,12 @@ impl VirtualDom {
parent: Option<ElementRef>, parent: Option<ElementRef>,
to: &mut impl WriteMutations, to: &mut impl WriteMutations,
) { ) {
let m = self.create_component_node(parent, right); let m = self.create_component_node(parent, right, to);
// TODO: Instead of *just* removing it, we can use the replace mutation
self.remove_component_node(left, true, to); self.remove_component_node(left, true, to);
// We want to optimize the replace case to use one less mutation if possible todo!()
// Since mutations are done in reverse, the last node removed will be the first in the stack
// Instead of *just* removing it, we can use the replace mutation
match self.mutations.edits.pop().unwrap() {
Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m }),
at => panic!("Expected remove mutation from remove_node {:#?}", at),
};
} }
/// Lightly diff the two templates, checking only their roots. /// Lightly diff the two templates, checking only their roots.
@@ -656,7 +645,7 @@ impl VirtualDom {
let new_idx = idx + last + 1; let new_idx = idx + last + 1;
let old_index = new_index_to_old_index[new_idx]; let old_index = new_index_to_old_index[new_idx];
if old_index == u32::MAX as usize { if old_index == u32::MAX as usize {
nodes_created += self.create(new_node); nodes_created += self.create(new_node, to);
} else { } else {
self.diff_node(&old[old_index], new_node, to); self.diff_node(&old[old_index], new_node, to);
nodes_created += self.push_all_real_nodes(new_node, to); nodes_created += self.push_all_real_nodes(new_node, to);
@@ -679,7 +668,7 @@ impl VirtualDom {
let new_idx = idx + next + 1; let new_idx = idx + next + 1;
let old_index = new_index_to_old_index[new_idx]; let old_index = new_index_to_old_index[new_idx];
if old_index == u32::MAX as usize { if old_index == u32::MAX as usize {
nodes_created += self.create(new_node); nodes_created += self.create(new_node, to);
} else { } else {
self.diff_node(&old[old_index], new_node, to); self.diff_node(&old[old_index], new_node, to);
nodes_created += self.push_all_real_nodes(new_node, to); nodes_created += self.push_all_real_nodes(new_node, to);
@@ -702,7 +691,7 @@ impl VirtualDom {
for (idx, new_node) in new[..first_lis].iter().enumerate() { for (idx, new_node) in new[..first_lis].iter().enumerate() {
let old_index = new_index_to_old_index[idx]; let old_index = new_index_to_old_index[idx];
if old_index == u32::MAX as usize { if old_index == u32::MAX as usize {
nodes_created += self.create(new_node); nodes_created += self.create(new_node, to);
} else { } else {
self.diff_node(&old[old_index], new_node, to); self.diff_node(&old[old_index], new_node, to);
nodes_created += self.push_all_real_nodes(new_node, to); nodes_created += self.push_all_real_nodes(new_node, to);
@@ -768,7 +757,7 @@ impl VirtualDom {
.into_iter() .into_iter()
.map(|child| { .map(|child| {
self.assign_boundary_ref(parent, child); self.assign_boundary_ref(parent, child);
self.create(child) self.create(child, to)
}) })
.sum() .sum()
} }
@@ -807,7 +796,7 @@ impl VirtualDom {
) { ) {
let m = self.create_children(r, Some(parent), to); let m = self.create_children(r, Some(parent), to);
let id = l.id.get().unwrap(); let id = l.id.get().unwrap();
to.replace_with(id, m); to.replace_node_with(id, m);
self.reclaim(id); self.reclaim(id);
} }
@@ -820,15 +809,10 @@ impl VirtualDom {
) { ) {
let m = self.create_children(right, parent, to); let m = self.create_children(right, parent, to);
self.remove_node(left, true, to); // TODO: Instead of *just* removing it, we can use the replace mutation
to.insert_nodes_before(self.find_first_element(left), m);
// We want to optimize the replace case to use one less mutation if possible self.remove_node(left, true, to);
// Since mutations are done in reverse, the last node removed will be the first in the stack
// Instead of *just* removing it, we can use the replace mutation
match self.mutations.edits.pop().unwrap() {
Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m }),
_ => panic!("Expected remove mutation from remove_node"),
};
} }
fn node_to_placeholder( fn node_to_placeholder(
@@ -846,15 +830,22 @@ impl VirtualDom {
to.create_placeholder(placeholder); to.create_placeholder(placeholder);
self.remove_nodes(l, to); self.replace_nodes(l, 1, to);
}
/// Replace many nodes with a number of nodes on the stack
fn replace_nodes(&mut self, nodes: &[VNode], m: usize, to: &mut impl WriteMutations) {
// We want to optimize the replace case to use one less mutation if possible // 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 // Since mutations are done in reverse, the last node removed will be the first in the stack
// Instead of *just* removing it, we can use the replace mutation // TODO: Instead of *just* removing it, we can use the replace mutation
match self.mutations.edits.pop().unwrap() { to.insert_nodes_before(self.find_first_element(&nodes[0]), m);
Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m: 1 }),
_ => panic!("Expected remove mutation from remove_node"), debug_assert!(
}; !nodes.is_empty(),
"replace_nodes must have at least one node"
);
self.remove_nodes(&nodes, to);
} }
/// Remove these nodes from the dom /// Remove these nodes from the dom
@@ -869,7 +860,7 @@ impl VirtualDom {
fn remove_node(&mut self, node: &VNode, gen_muts: bool, to: &mut impl WriteMutations) { fn remove_node(&mut self, node: &VNode, gen_muts: bool, to: &mut impl WriteMutations) {
// Clean up any attributes that have claimed a static node as dynamic for mount/unmounta // Clean up any attributes that have claimed a static node as dynamic for mount/unmounta
// Will not generate mutations! // Will not generate mutations!
self.reclaim_attributes(node, to); self.reclaim_attributes(node);
// Remove the nested dynamic nodes // Remove the nested dynamic nodes
// We don't generate mutations for these, as they will be removed by the parent (in the next line) // We don't generate mutations for these, as they will be removed by the parent (in the next line)
@@ -895,7 +886,7 @@ impl VirtualDom {
} }
} }
fn reclaim_attributes(&mut self, node: &VNode, to: &mut impl WriteMutations) { fn reclaim_attributes(&mut self, node: &VNode) {
let mut id = None; let mut id = None;
for (idx, attr) in node.dynamic_attrs.iter().enumerate() { for (idx, attr) in node.dynamic_attrs.iter().enumerate() {
// We'll clean up the root nodes either way, so don't worry // We'll clean up the root nodes either way, so don't worry
+4 -4
View File
@@ -15,7 +15,7 @@ use crate::{arena::ElementId, AttributeValue, ScopeId, Template};
/// Mutations are the only link between the RealDOM and the VirtualDOM. /// Mutations are the only link between the RealDOM and the VirtualDOM.
pub trait WriteMutations { pub trait WriteMutations {
/// Register a template with the renderer /// Register a template with the renderer
fn register_template(&mut self, template: Template<'static>); fn register_template(&mut self, template: Template);
/// Add these m children to the target element /// Add these m children to the target element
/// ///
@@ -333,7 +333,7 @@ pub struct MutationsVec {
/// Any templates encountered while diffing the DOM. /// Any templates encountered while diffing the DOM.
/// ///
/// These must be loaded into a cache before applying the edits /// These must be loaded into a cache before applying the edits
pub templates: Vec<Template<'static>>, pub templates: Vec<Template>,
/// Any mutations required to patch the renderer to match the layout of the VirtualDom /// Any mutations required to patch the renderer to match the layout of the VirtualDom
pub edits: Vec<Mutation>, pub edits: Vec<Mutation>,
@@ -355,7 +355,7 @@ impl MutationsVec {
} }
impl WriteMutations for MutationsVec { impl WriteMutations for MutationsVec {
fn register_template(&mut self, template: Template<'static>) { fn register_template(&mut self, template: Template) {
self.templates.push(template) self.templates.push(template)
} }
@@ -468,7 +468,7 @@ impl WriteMutations for MutationsVec {
pub struct NoOpMutations; pub struct NoOpMutations;
impl WriteMutations for NoOpMutations { impl WriteMutations for NoOpMutations {
fn register_template(&mut self, template: Template<'static>) {} fn register_template(&mut self, template: Template) {}
fn append_children(&mut self, id: ElementId, m: usize) {} fn append_children(&mut self, id: ElementId, m: usize) {}
+8 -8
View File
@@ -55,7 +55,7 @@ pub struct VNodeInner {
pub root_ids: RefCell<Vec<ElementId>>, pub root_ids: RefCell<Vec<ElementId>>,
/// The static nodes and static descriptor of the template /// The static nodes and static descriptor of the template
pub template: Cell<Template<'static>>, pub template: Cell<Template>,
/// The dynamic parts of the template /// The dynamic parts of the template
pub dynamic_nodes: Vec<DynamicNode>, pub dynamic_nodes: Vec<DynamicNode>,
@@ -106,7 +106,7 @@ impl VNode {
/// Create a new VNode /// Create a new VNode
pub fn new( pub fn new(
key: Option<String>, key: Option<String>,
template: Template<'static>, template: Template,
root_ids: Vec<ElementId>, root_ids: Vec<ElementId>,
dynamic_nodes: Vec<DynamicNode>, dynamic_nodes: Vec<DynamicNode>,
dynamic_attrs: Vec<Attribute>, dynamic_attrs: Vec<Attribute>,
@@ -144,7 +144,7 @@ impl VNode {
/// ways, with the suggested approach being the unique code location (file, line, col, etc). /// ways, with the suggested approach being the unique code location (file, line, col, etc).
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
pub struct Template<'a> { pub struct Template {
/// The name of the template. This must be unique across your entire program for template diffing to work properly /// 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. /// If two templates have the same name, it's likely that Dioxus will panic when diffing.
@@ -152,13 +152,13 @@ pub struct Template<'a> {
feature = "serialize", feature = "serialize",
serde(deserialize_with = "deserialize_string_leaky") serde(deserialize_with = "deserialize_string_leaky")
)] )]
pub name: &'a str, pub name: &'static str,
/// The list of template nodes that make up the template /// The list of template nodes that make up the template
/// ///
/// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm. /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))] #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
pub roots: &'a [TemplateNode], pub roots: &'static [TemplateNode],
/// The paths of each node relative to the root of the template. /// The paths of each node relative to the root of the template.
/// ///
@@ -168,7 +168,7 @@ pub struct Template<'a> {
feature = "serialize", feature = "serialize",
serde(deserialize_with = "deserialize_bytes_leaky") serde(deserialize_with = "deserialize_bytes_leaky")
)] )]
pub node_paths: &'a [&'a [u8]], pub node_paths: &'static [&'static [u8]],
/// The paths of each dynamic attribute relative to the root of the template /// The paths of each dynamic attribute relative to the root of the template
/// ///
@@ -178,7 +178,7 @@ pub struct Template<'a> {
feature = "serialize", feature = "serialize",
serde(deserialize_with = "deserialize_bytes_leaky") serde(deserialize_with = "deserialize_bytes_leaky")
)] )]
pub attr_paths: &'a [&'a [u8]], pub attr_paths: &'static [&'static [u8]],
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
@@ -232,7 +232,7 @@ where
Ok(deserialized.map(|deserialized| &*Box::leak(deserialized.into_boxed_str()))) Ok(deserialized.map(|deserialized| &*Box::leak(deserialized.into_boxed_str())))
} }
impl<'a> Template<'a> { impl Template {
/// Is this template worth caching at all, since it's completely runtime? /// Is this template worth caching at all, since it's completely runtime?
/// ///
/// There's no point in saving templates that are completely dynamic, since they'll be recreated every time anyway. /// There's no point in saving templates that are completely dynamic, since they'll be recreated every time anyway.
+3 -3
View File
@@ -310,7 +310,7 @@ impl<'src> ScopeState {
} }
/// Create a new [`EventHandler`] from an [`FnMut`] /// Create a new [`EventHandler`] from an [`FnMut`]
pub fn event_handler<T>(&self, f: impl FnMut(T)) -> EventHandler<T> { pub fn event_handler<T>(&self, f: impl FnMut(T) + 'static) -> EventHandler<T> {
let callback = RefCell::new(Some(Box::new(move |event: T| { let callback = RefCell::new(Some(Box::new(move |event: T| {
f(event); f(event);
}) as Box<dyn FnMut(T)>)); }) as Box<dyn FnMut(T)>));
@@ -324,8 +324,8 @@ impl<'src> ScopeState {
/// ///
/// The callback must be confined to the lifetime of the ScopeState /// The callback must be confined to the lifetime of the ScopeState
pub fn listener<T: 'static>( pub fn listener<T: 'static>(
&'src self, &self,
mut callback: impl FnMut(Event<T>) + 'src, mut callback: impl FnMut(Event<T>) + 'static,
) -> AttributeValue { ) -> AttributeValue {
AttributeValue::Listener(RefCell::new(Box::new(move |event: Event<dyn Any>| { AttributeValue::Listener(RefCell::new(Box::new(move |event: Event<dyn Any>| {
if let Ok(data) = event.data.downcast::<T>() { if let Ok(data) = event.data.downcast::<T>() {
+21 -7
View File
@@ -6,10 +6,9 @@ use crate::{
any_props::{BoxedAnyProps, VProps}, any_props::{BoxedAnyProps, VProps},
arena::ElementId, arena::ElementId,
innerlude::{ innerlude::{
DirtyScope, ElementRef, ErrorBoundary, Mutations, NoOpMutations, Scheduler, SchedulerMsg, DirtyScope, ElementRef, ErrorBoundary, NoOpMutations, Scheduler, SchedulerMsg,
WriteMutations, WriteMutations,
}, },
mutations::Mutation,
nodes::RenderReturn, nodes::RenderReturn,
nodes::{Template, TemplateId}, nodes::{Template, TemplateId},
runtime::{Runtime, RuntimeGuard}, runtime::{Runtime, RuntimeGuard},
@@ -186,7 +185,10 @@ pub struct VirtualDom {
pub(crate) dirty_scopes: BTreeSet<DirtyScope>, 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 byteindexes to templates
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>, pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template>>,
// Templates changes that are queued for the next render
pub(crate) queued_templates: Vec<Template>,
// The element ids that are used in the renderer // The element ids that are used in the renderer
pub(crate) elements: Slab<Option<ElementRef>>, pub(crate) elements: Slab<Option<ElementRef>>,
@@ -263,6 +265,7 @@ impl VirtualDom {
scopes: Default::default(), scopes: Default::default(),
dirty_scopes: Default::default(), dirty_scopes: Default::default(),
templates: Default::default(), templates: Default::default(),
queued_templates: Default::default(),
elements: Default::default(), elements: Default::default(),
suspended_scopes: Default::default(), suspended_scopes: Default::default(),
}; };
@@ -361,7 +364,6 @@ impl VirtualDom {
_ => return, _ => return,
}; };
let mut parent_node = Some(parent_path); let mut parent_node = Some(parent_path);
let mut listeners = vec![];
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required // We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
let uievent = Event { let uievent = Event {
@@ -373,6 +375,8 @@ impl VirtualDom {
if bubbles { if bubbles {
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent. // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
while let Some(path) = parent_node { while let Some(path) = parent_node {
let mut listeners = vec![];
let el_ref = &path.element; let el_ref = &path.element;
let node_template = el_ref.template.get(); let node_template = el_ref.template.get();
let target_path = path.path; let target_path = path.path;
@@ -397,7 +401,7 @@ impl VirtualDom {
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
// We check the bubble state between each call to see if the event has been stopped from bubbling // We check the bubble state between each call to see if the event has been stopped from bubbling
for listener in listeners.drain(..).rev() { for listener in listeners.into_iter().rev() {
if let AttributeValue::Listener(listener) = listener { if let AttributeValue::Listener(listener) = listener {
let origin = path.scope; let origin = path.scope;
self.runtime.scope_stack.borrow_mut().push(origin); self.runtime.scope_stack.borrow_mut().push(origin);
@@ -505,7 +509,7 @@ impl VirtualDom {
/// The caller must ensure that the template refrences the same dynamic attributes and nodes as the original template. /// The caller must ensure that the template refrences the same dynamic attributes and nodes as the original template.
/// ///
/// This will only replace the the parent template, not any nested templates. /// This will only replace the the parent template, not any nested templates.
pub fn replace_template(&mut self, template: Template<'static>) { pub fn replace_template(&mut self, template: Template) {
self.register_template_first_byte_index(template); self.register_template_first_byte_index(template);
// iterating a slab is very inefficient, but this is a rare operation that will only happen during development so it's fine // iterating a slab is very inefficient, but this is a rare operation that will only happen during development so it's fine
for (_, scope) in self.scopes.iter() { for (_, scope) in self.scopes.iter() {
@@ -545,8 +549,9 @@ impl VirtualDom {
/// apply_edits(edits); /// apply_edits(edits);
/// ``` /// ```
pub fn rebuild(&mut self, to: &mut impl WriteMutations) { pub fn rebuild(&mut self, to: &mut impl WriteMutations) {
self.flush_templates(to);
let _runtime = RuntimeGuard::new(self.runtime.clone()); let _runtime = RuntimeGuard::new(self.runtime.clone());
match unsafe { self.run_scope(ScopeId::ROOT) } { match self.run_scope(ScopeId::ROOT) {
// Rebuilding implies we append the created elements to the root // Rebuilding implies we append the created elements to the root
RenderReturn::Ready(node) => { RenderReturn::Ready(node) => {
let m = self.create_scope(ScopeId::ROOT, &node, to); let m = self.create_scope(ScopeId::ROOT, &node, to);
@@ -565,6 +570,7 @@ impl VirtualDom {
/// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
/// suspended subtrees. /// suspended subtrees.
pub fn render_immediate(&mut self, to: &mut impl WriteMutations) { pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
self.flush_templates(to);
// Build a waker that won't wake up since our deadline is already expired when it's polled // Build a waker that won't wake up since our deadline is already expired when it's polled
let waker = futures_util::task::noop_waker(); let waker = futures_util::task::noop_waker();
let mut cx = std::task::Context::from_waker(&waker); let mut cx = std::task::Context::from_waker(&waker);
@@ -605,6 +611,7 @@ impl VirtualDom {
deadline: impl Future<Output = ()>, deadline: impl Future<Output = ()>,
to: &mut impl WriteMutations, to: &mut impl WriteMutations,
) { ) {
self.flush_templates(to);
pin_mut!(deadline); pin_mut!(deadline);
self.process_events(); self.process_events();
@@ -653,6 +660,13 @@ impl VirtualDom {
pub fn runtime(&self) -> Rc<Runtime> { pub fn runtime(&self) -> Rc<Runtime> {
self.runtime.clone() self.runtime.clone()
} }
/// Flush any queued template changes
pub fn flush_templates(&mut self, to: &mut impl WriteMutations) {
for template in self.queued_templates.drain(..) {
to.register_template(template);
}
}
} }
impl Drop for VirtualDom { impl Drop for VirtualDom {
+1 -1
View File
@@ -130,7 +130,7 @@ enum DynamicNodeType {
Other, Other,
} }
fn create_random_template(name: &'static str) -> (Template<'static>, Vec<DynamicNodeType>) { fn create_random_template(name: &'static str) -> (Template, Vec<DynamicNodeType>) {
let mut dynamic_node_type = Vec::new(); let mut dynamic_node_type = Vec::new();
let mut template_idx = 0; let mut template_idx = 0;
let mut attr_idx = 0; let mut attr_idx = 0;