diff --git a/packages/core/src/diff/component.rs b/packages/core/src/diff/component.rs index 4359f91ef6..5a5e137609 100644 --- a/packages/core/src/diff/component.rs +++ b/packages/core/src/diff/component.rs @@ -1,41 +1,29 @@ -use std::{ - any::TypeId, - ops::{Deref, DerefMut}, -}; +use std::ops::{Deref, DerefMut}; use crate::{ - Element, SuspenseContext, any_props::AnyProps, - innerlude::{ - ElementRef, MountId, ScopeOrder, SuspenseBoundaryProps, SuspenseBoundaryPropsWithOwner, - VComponent, WriteMutations, - }, + innerlude::{ElementRef, MountId, ScopeOrder, VComponent, WriteMutations}, nodes::VNode, scopes::{LastRenderedNode, ScopeId}, virtual_dom::VirtualDom, }; impl VirtualDom { - pub(crate) fn run_and_diff_scope( + pub(crate) fn run_and_diff_scope( &mut self, - to: Option<&mut M>, + to: Option<&mut (dyn WriteMutations + '_)>, scope_id: ScopeId, ) { - let scope = &mut self.scopes[scope_id.0]; - if SuspenseBoundaryProps::downcast_from_props(&mut *scope.props).is_some() { - SuspenseBoundaryProps::diff(scope_id, self, to) - } else { - let new_nodes = self.run_scope(scope_id); - self.diff_scope(to, scope_id, new_nodes); - } + let driver = self.runtime.get_state(scope_id).render_driver(); + driver.diff(self, scope_id, to); } #[tracing::instrument(skip(self, to), level = "trace", name = "VirtualDom::diff_scope")] - fn diff_scope( + pub(crate) fn diff_scope( &mut self, - to: Option<&mut M>, + to: Option<&mut (dyn WriteMutations + '_)>, scope: ScopeId, - new_nodes: Element, + new_nodes: crate::Element, ) { self.runtime.clone().with_scope_on_stack(scope, || { // We don't diff the nodes if the scope is suspended or has an error @@ -64,9 +52,9 @@ impl VirtualDom { /// /// Returns the number of nodes created on the stack #[tracing::instrument(skip(self, to), level = "trace", name = "VirtualDom::create_scope")] - pub(crate) fn create_scope( + pub(crate) fn create_scope( &mut self, - to: Option<&mut M>, + to: Option<&mut (dyn WriteMutations + '_)>, scope: ScopeId, new_nodes: LastRenderedNode, parent: Option, @@ -91,25 +79,15 @@ impl VirtualDom { }) } - pub(crate) fn remove_component_node( + pub(crate) fn remove_component_node( &mut self, - to: Option<&mut M>, + to: Option<&mut (dyn WriteMutations + '_)>, destroy_component_state: bool, scope_id: ScopeId, replace_with: Option, ) { - // If this is a suspense boundary, remove the suspended nodes as well - SuspenseContext::remove_suspended_nodes::(self, scope_id, destroy_component_state); - - // Remove the component from the dom - if let Some(node) = self.scopes[scope_id.0].last_rendered_node.clone() { - node.remove_node_inner(self, to, destroy_component_state, replace_with) - }; - - if destroy_component_state { - // Now drop all the resources - self.drop_scope(scope_id); - } + let driver = self.runtime.get_state(scope_id).render_driver(); + driver.remove(self, scope_id, to, destroy_component_state, replace_with); } } @@ -123,7 +101,7 @@ impl VNode { scope_id: ScopeId, parent: Option, dom: &mut VirtualDom, - to: Option<&mut impl WriteMutations>, + to: Option<&mut (dyn WriteMutations + '_)>, ) { // Replace components that have different render fns if old.render_fn != new.render_fn { @@ -157,7 +135,7 @@ impl VNode { new: &VComponent, parent: Option, dom: &mut VirtualDom, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, ) { let scope = ScopeId(dom.get_mounted_dyn_node(mount, idx)); @@ -179,39 +157,27 @@ impl VNode { component: &VComponent, parent: Option, dom: &mut VirtualDom, - to: Option<&mut impl WriteMutations>, + to: Option<&mut (dyn WriteMutations + '_)>, ) -> usize { - // If this is a suspense boundary, run our suspense creation logic instead of running the component - if component.props.props().type_id() == TypeId::of::() { - return SuspenseBoundaryProps::create(mount, idx, component, parent, dom, to); - } - let mut scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx)); + let new = scope_id.is_placeholder(); // If the scopeid is a placeholder, we need to load up a new scope for this vcomponent. If it's already mounted, then we can just use that - if scope_id.is_placeholder() { + if new { scope_id = dom - .new_scope(component.props.duplicate(), component.name) + .new_scope( + component.name, + component.driver.clone(), + component.props.duplicate(), + ) .state() .id; // Store the scope id for the next render dom.set_mounted_dyn_node(mount, idx, scope_id.0); - - // If this is a new scope, we also need to run it once to get the initial state - let new = dom.run_scope(scope_id); - - // Then set the new node as the last rendered node - dom.scopes[scope_id.0].last_rendered_node = Some(LastRenderedNode::new(new)); } - let scope = ScopeId(dom.get_mounted_dyn_node(mount, idx)); - - let new_node = dom.scopes[scope.0] - .last_rendered_node - .clone() - .expect("Component to be mounted"); - - dom.create_scope(to, scope, new_node, parent) + let driver = dom.runtime.get_state(scope_id).render_driver(); + driver.create(dom, scope_id, new, parent, to) } } diff --git a/packages/core/src/diff/iterator.rs b/packages/core/src/diff/iterator.rs index 194ecec55e..634b6f128c 100644 --- a/packages/core/src/diff/iterator.rs +++ b/packages/core/src/diff/iterator.rs @@ -9,7 +9,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; impl VirtualDom { pub(crate) fn diff_non_empty_fragment( &mut self, - to: Option<&mut impl WriteMutations>, + to: Option<&mut (dyn WriteMutations + '_)>, old: &[VNode], new: &[VNode], parent: Option, @@ -42,7 +42,7 @@ impl VirtualDom { // the change list stack is in the same state when this function returns. fn diff_non_keyed_children( &mut self, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, old: &[VNode], new: &[VNode], parent: Option, @@ -87,7 +87,7 @@ impl VirtualDom { // The stack is empty upon entry. fn diff_keyed_children( &mut self, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, old: &[VNode], new: &[VNode], parent: Option, @@ -156,7 +156,7 @@ impl VirtualDom { /// If there is no offset, then this function returns None and the diffing is complete. fn diff_keyed_ends( &mut self, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, old: &[VNode], new: &[VNode], parent: Option, @@ -238,7 +238,7 @@ impl VirtualDom { #[allow(clippy::too_many_lines)] fn diff_keyed_middle( &mut self, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, old: &[VNode], new: &[VNode], parent: Option, @@ -346,7 +346,7 @@ impl VirtualDom { vdom: &mut VirtualDom, new: &[VNode], old: &[VNode], - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, parent: Option, new_index_to_old_index: &[usize], range: std::ops::Range, @@ -431,7 +431,7 @@ impl VirtualDom { fn create_and_insert_before( &mut self, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, new: &[VNode], before: &VNode, parent: Option, @@ -440,7 +440,12 @@ impl VirtualDom { self.insert_before(to, m, before); } - fn insert_before(&mut self, to: Option<&mut impl WriteMutations>, new: usize, before: &VNode) { + fn insert_before( + &mut self, + to: Option<&mut (dyn WriteMutations + '_)>, + new: usize, + before: &VNode, + ) { if let Some(to) = to { if new > 0 { let id = before.find_first_element(self); @@ -451,7 +456,7 @@ impl VirtualDom { fn create_and_insert_after( &mut self, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, new: &[VNode], after: &VNode, parent: Option, @@ -460,7 +465,12 @@ impl VirtualDom { self.insert_after(to, m, after); } - fn insert_after(&mut self, to: Option<&mut impl WriteMutations>, new: usize, after: &VNode) { + fn insert_after( + &mut self, + to: Option<&mut (dyn WriteMutations + '_)>, + new: usize, + after: &VNode, + ) { if let Some(to) = to { if new > 0 { let id = after.find_last_element(self); @@ -475,7 +485,7 @@ impl VNode { pub(crate) fn push_all_root_nodes( &self, dom: &VirtualDom, - to: &mut impl WriteMutations, + to: &mut (dyn WriteMutations + '_), ) -> usize { let template = self.template; diff --git a/packages/core/src/diff/mod.rs b/packages/core/src/diff/mod.rs index 7a7a89ee7b..3ea016b831 100644 --- a/packages/core/src/diff/mod.rs +++ b/packages/core/src/diff/mod.rs @@ -5,7 +5,7 @@ //! - Diffing nodes that are not mounted //! - Mounted nodes that have already been created //! -//! To support those cases, we lazily create components and only optionally write to the real dom while diffing with Option<&mut impl WriteMutations> +//! To support those cases, we lazily create components and only optionally write to the real dom while diffing with Option<&mut (dyn WriteMutations + '_)> #![allow(clippy::too_many_arguments)] @@ -24,7 +24,7 @@ mod node; impl VirtualDom { pub(crate) fn create_children( &mut self, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, nodes: &[VNode], parent: Option, ) -> usize { @@ -78,7 +78,7 @@ impl VirtualDom { /// Wont generate mutations for the inner nodes fn remove_nodes( &mut self, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, nodes: &[VNode], replace_with: Option, ) { diff --git a/packages/core/src/diff/node.rs b/packages/core/src/diff/node.rs index c39a2bebf8..0998f33f0a 100644 --- a/packages/core/src/diff/node.rs +++ b/packages/core/src/diff/node.rs @@ -16,7 +16,7 @@ impl VNode { &self, new: &VNode, dom: &mut VirtualDom, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, ) { // The node we are diffing from should always be mounted debug_assert!( @@ -82,7 +82,7 @@ impl VNode { old_node: &DynamicNode, new_node: &DynamicNode, dom: &mut VirtualDom, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, ) { tracing::trace!("diffing dynamic node from {old_node:?} to {new_node:?}"); match (old_node, new_node) { @@ -212,7 +212,13 @@ impl VNode { /// 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, id: ElementId, left: &VText, right: &VText) { + fn diff_vtext( + &self, + to: &mut (dyn WriteMutations + '_), + id: ElementId, + left: &VText, + right: &VText, + ) { if left.value != right.value { to.set_node_text(&right.value, id); } @@ -223,7 +229,7 @@ impl VNode { right: &[VNode], parent: Option, dom: &mut VirtualDom, - to: Option<&mut impl WriteMutations>, + to: Option<&mut (dyn WriteMutations + '_)>, ) { self.replace_inner(right, parent, dom, to, true) } @@ -236,7 +242,7 @@ impl VNode { right: &[VNode], parent: Option, dom: &mut VirtualDom, - to: Option<&mut impl WriteMutations>, + to: Option<&mut (dyn WriteMutations + '_)>, ) { self.replace_inner(right, parent, dom, to, false) } @@ -246,7 +252,7 @@ impl VNode { right: &[VNode], parent: Option, dom: &mut VirtualDom, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, destroy_component_state: bool, ) { let m = dom.create_children(to.as_deref_mut(), right, parent); @@ -256,20 +262,20 @@ impl VNode { } /// Remove a node from the dom and potentially replace it with the top m nodes from the stack - pub(crate) fn remove_node( + pub(crate) fn remove_node( &self, dom: &mut VirtualDom, - to: Option<&mut M>, + to: Option<&mut (dyn WriteMutations + '_)>, replace_with: Option, ) { self.remove_node_inner(dom, to, true, replace_with) } /// Remove a node, but only maybe destroy the component state of that node. During suspense, we need to remove a node from the real dom without wiping the component state - pub(crate) fn remove_node_inner( + pub(crate) fn remove_node_inner( &self, dom: &mut VirtualDom, - to: Option<&mut M>, + to: Option<&mut (dyn WriteMutations + '_)>, destroy_component_state: bool, replace_with: Option, ) { @@ -285,7 +291,7 @@ impl VNode { // 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, destroy_component_state); + self.remove_nested_dyn_nodes(mount, dom, destroy_component_state); // 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) @@ -302,7 +308,7 @@ impl VNode { &self, mount: MountId, dom: &mut VirtualDom, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, destroy_component_state: bool, replace_with: Option, ) { @@ -335,7 +341,7 @@ impl VNode { } } - fn remove_nested_dyn_nodes( + fn remove_nested_dyn_nodes( &self, mount: MountId, dom: &mut VirtualDom, @@ -349,7 +355,7 @@ impl VNode { self.remove_dynamic_node( mount, dom, - Option::<&mut M>::None, + None, destroy_component_state, idx, dyn_node, @@ -363,7 +369,7 @@ impl VNode { &self, mount: MountId, dom: &mut VirtualDom, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, destroy_component_state: bool, idx: usize, node: &DynamicNode, @@ -417,7 +423,7 @@ impl VNode { &self, new: &VNode, dom: &mut VirtualDom, - to: &mut impl WriteMutations, + to: &mut (dyn WriteMutations + '_), ) { let mount_id = new.mount.get(); for (idx, (old_attrs, new_attrs)) in self @@ -497,7 +503,12 @@ impl VNode { } } - fn remove_attribute(&self, attribute: &Attribute, id: ElementId, to: &mut impl WriteMutations) { + fn remove_attribute( + &self, + attribute: &Attribute, + id: ElementId, + to: &mut (dyn WriteMutations + '_), + ) { match &attribute.value { AttributeValue::Listener(_) => { to.remove_event_listener(&attribute.name[2..], id); @@ -520,7 +531,7 @@ impl VNode { id: ElementId, mount: MountId, dom: &mut VirtualDom, - to: &mut impl WriteMutations, + to: &mut (dyn WriteMutations + '_), ) { match &attribute.value { AttributeValue::Listener(_) => { @@ -543,7 +554,7 @@ impl VNode { &self, dom: &mut VirtualDom, parent: Option, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, ) -> usize { // Get the most up to date template let template = self.template; @@ -657,7 +668,7 @@ impl VNode { mount: MountId, dynamic_node_id: usize, dom: &mut VirtualDom, - to: Option<&mut impl WriteMutations>, + to: Option<&mut (dyn WriteMutations + '_)>, ) -> usize { use DynamicNode::*; match node { @@ -714,7 +725,7 @@ impl VNode { dynamic_nodes_iter: &mut Peekable>, root_idx: u8, dom: &mut VirtualDom, - mut to: Option<&mut impl WriteMutations>, + mut to: Option<&mut (dyn WriteMutations + '_)>, ) { fn collect_dyn_node_range( dynamic_nodes: &mut Peekable>, @@ -797,7 +808,7 @@ impl VNode { dynamic_attributes_iter: &mut Peekable>, root_idx: u8, dom: &mut VirtualDom, - to: &mut impl WriteMutations, + to: &mut (dyn WriteMutations + '_), ) { let mut last_path = None; // Only take nodes that are under this root node @@ -834,7 +845,7 @@ impl VNode { mount: MountId, root_idx: usize, dom: &mut VirtualDom, - to: &mut impl WriteMutations, + to: &mut (dyn WriteMutations + '_), ) -> ElementId { // Get an ID for this root since it's a real root let this_id = dom.next_element(); @@ -857,7 +868,7 @@ impl VNode { mount: MountId, path: &'static [u8], dom: &mut VirtualDom, - to: &mut impl WriteMutations, + to: &mut (dyn WriteMutations + '_), ) -> ElementId { // This is just the root node. We already know it's id if let [root_idx] = path { @@ -878,7 +889,7 @@ impl VNode { idx: usize, text: &VText, dom: &mut VirtualDom, - to: &mut impl WriteMutations, + to: &mut (dyn WriteMutations + '_), ) -> usize { let new_id = mount.mount_node(idx, dom); @@ -893,7 +904,7 @@ impl VNode { mount: MountId, idx: usize, dom: &mut VirtualDom, - to: &mut impl WriteMutations, + to: &mut (dyn WriteMutations + '_), ) -> usize { let new_id = mount.mount_node(idx, dom); diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 2abc70dfa0..557b09e982 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -17,6 +17,7 @@ mod mutations; mod nodes; mod properties; mod reactive_context; +pub(crate) mod render_driver; mod render_error; mod root_wrapper; mod runtime; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 10e673545b..2bf0963a5a 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -5,6 +5,7 @@ use crate::{ events::ListenerCallback, innerlude::{ElementRef, MountId, ScopeState, VProps}, properties::ComponentFunction, + render_driver::{BodyDriver, RenderDriver}, }; use dioxus_core_types::DioxusFormattable; use std::ops::Deref; @@ -634,6 +635,9 @@ pub struct VComponent { /// The raw pointer to the render function pub(crate) render_fn: usize, + /// The driver owning this component's rendering lifecycle. + pub(crate) driver: Rc, + /// The props for this component pub(crate) props: BoxedAnyProps, } @@ -644,6 +648,7 @@ impl Clone for VComponent { name: self.name, props: self.props.duplicate(), render_fn: self.render_fn, + driver: self.driver.clone(), } } } @@ -665,10 +670,22 @@ impl VComponent { props, fn_name, )); + let driver = BodyDriver::new(); + + Self::new_with_driver(fn_name, render_fn, driver, props) + } + /// Create a [`VComponent`] with a custom [`RenderDriver`]. + pub(crate) fn new_with_driver( + name: &'static str, + render_fn: usize, + driver: impl RenderDriver, + props: BoxedAnyProps, + ) -> Self { VComponent { + name, render_fn, - name: fn_name, + driver: Rc::new(driver), props, } } diff --git a/packages/core/src/render_driver.rs b/packages/core/src/render_driver.rs new file mode 100644 index 0000000000..15cb928e91 --- /dev/null +++ b/packages/core/src/render_driver.rs @@ -0,0 +1,107 @@ +use crate::{ + WriteMutations, + innerlude::ElementRef, + scope_context::SuspenseLocation, + scopes::{LastRenderedNode, ScopeId}, + virtual_dom::VirtualDom, +}; + +/// The rendering lifecycle driver for a scope. +/// +/// Every scope owns exactly one driver: +/// - Plain components use [`BodyDriver`]. +/// - Custom components may use specialized drivers, such as +/// [`SuspenseDriver`](crate::suspense::SuspenseDriver). +pub(crate) trait RenderDriver: 'static { + /// The suspense location to store on a newly-created scope owned by this + /// driver. + fn initial_suspense_location(&self, parent: SuspenseLocation) -> SuspenseLocation { + parent + } + + /// Mount this scope's output. `to` receives DOM mutations; pass `None` for + /// background rendering (e.g. suspended children). + fn create( + &self, + dom: &mut VirtualDom, + scope_id: ScopeId, + new: bool, + parent: Option, + to: Option<&mut (dyn WriteMutations + '_)>, + ) -> usize; + + /// Diff this scope's output against its current props. + fn diff( + &self, + dom: &mut VirtualDom, + scope_id: ScopeId, + to: Option<&mut (dyn WriteMutations + '_)>, + ); + + /// Remove this scope's output. + fn remove( + &self, + dom: &mut VirtualDom, + scope_id: ScopeId, + to: Option<&mut (dyn WriteMutations + '_)>, + destroy_component_state: bool, + replace_with: Option, + ); +} + +/// The concrete driver for plain (non-suspense) components. +pub(crate) struct BodyDriver; + +impl BodyDriver { + pub fn new() -> BodyDriver { + BodyDriver + } +} + +impl RenderDriver for BodyDriver { + fn create( + &self, + dom: &mut VirtualDom, + scope_id: ScopeId, + new: bool, + parent: Option, + to: Option<&mut (dyn WriteMutations + '_)>, + ) -> usize { + if new { + let body = dom.run_scope(scope_id); + dom.scopes[scope_id.0].last_rendered_node = Some(LastRenderedNode::new(body)); + } + let new_node = dom.scopes[scope_id.0] + .last_rendered_node + .clone() + .expect("Component to be mounted"); + dom.create_scope(to, scope_id, new_node, parent) + } + + fn diff( + &self, + dom: &mut VirtualDom, + scope_id: ScopeId, + to: Option<&mut (dyn WriteMutations + '_)>, + ) { + let body = dom.run_scope(scope_id); + dom.diff_scope(to, scope_id, body); + } + + fn remove( + &self, + dom: &mut VirtualDom, + scope_id: ScopeId, + to: Option<&mut (dyn WriteMutations + '_)>, + destroy_component_state: bool, + replace_with: Option, + ) { + if let Some(node) = dom.scopes[scope_id.0].last_rendered_node.clone() { + node.remove_node_inner(dom, to, destroy_component_state, replace_with) + }; + + if destroy_component_state { + dom.drop_scope(scope_id); + } + } +} diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index a23113ed68..c90450a69d 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -2,6 +2,7 @@ use crate::{ Element, ReactiveContext, any_props::{AnyProps, BoxedAnyProps}, innerlude::{RenderError, ScopeOrder, ScopeState}, + render_driver::RenderDriver, scope_context::{Scope, SuspenseLocation}, scopes::ScopeId, virtual_dom::VirtualDom, @@ -10,22 +11,24 @@ use crate::{ impl VirtualDom { pub(super) fn new_scope( &mut self, - props: BoxedAnyProps, name: &'static str, + driver: std::rc::Rc, + props: BoxedAnyProps, ) -> &mut ScopeState { let parent_id = self.runtime.try_current_scope_id(); let height = match parent_id.and_then(|id| self.runtime.try_get_state(id)) { Some(parent) => parent.height() + 1, None => 0, }; - let suspense_boundary = self + let parent_suspense_location = self .runtime .current_suspense_location() .unwrap_or(SuspenseLocation::NotSuspended); + let suspense_location = driver.initial_suspense_location(parent_suspense_location); let entry = self.scopes.vacant_entry(); let id = ScopeId(entry.key()); - let scope_runtime = Scope::new(name, id, parent_id, height, suspense_boundary); + let scope_runtime = Scope::new(name, id, parent_id, height, suspense_location, driver); let reactive_context = ReactiveContext::new_for_scope(&scope_runtime, &self.runtime); let scope = entry.insert(ScopeState { diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index 603643b38c..1f22b36852 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -1,6 +1,7 @@ use crate::{ Runtime, ScopeId, Task, innerlude::{SchedulerMsg, SuspenseContext}, + render_driver::RenderDriver, }; use generational_box::{AnyStorage, Owner}; use rustc_hash::FxHashSet; @@ -8,6 +9,7 @@ use std::{ any::Any, cell::{Cell, RefCell}, future::Future, + rc::Rc, sync::Arc, }; @@ -60,6 +62,9 @@ pub(crate) struct Scope { /// The suspense boundary that this scope is currently in (if any) suspense_boundary: SuspenseLocation, + /// The driver owning this scope's rendered output. + render_driver: Rc, + pub(crate) status: RefCell, } @@ -70,6 +75,7 @@ impl Scope { parent_id: Option, height: u32, suspense_boundary: SuspenseLocation, + render_driver: Rc, ) -> Self { Self { name, @@ -87,6 +93,7 @@ impl Scope { effects_queued: Vec::new(), }), suspense_boundary, + render_driver, } } @@ -94,6 +101,11 @@ impl Scope { self.parent_id } + /// The driver owning this scope's rendered output. + pub(crate) fn render_driver(&self) -> Rc { + self.render_driver.clone() + } + fn sender(&self) -> futures_channel::mpsc::UnboundedSender { Runtime::current().sender.clone() } diff --git a/packages/core/src/suspense/component.rs b/packages/core/src/suspense/component.rs index d4662da6e8..fa5734a2c6 100644 --- a/packages/core/src/suspense/component.rs +++ b/packages/core/src/suspense/component.rs @@ -1,4 +1,4 @@ -use crate::{innerlude::*, scope_context::SuspenseLocation}; +use crate::{innerlude::*, render_driver::RenderDriver, scope_context::SuspenseLocation}; /// Properties for the [`SuspenseBoundary()`] component. #[allow(non_camel_case_types)] @@ -180,10 +180,18 @@ impl SuspenseBoundaryPropsWithOwner { render_fn: impl ComponentFunction, ) -> VComponent { let component_name = std::any::type_name_of_val(&render_fn); - VComponent::new( + let render_fn_ptr = render_fn.fn_ptr(); + let props = VProps::new( move |wrapper: Self| render_fn.rebuild(wrapper.inner), + ::memoize, self, component_name, + ); + VComponent::new_with_driver( + component_name, + render_fn_ptr, + SuspenseDriver::new(), + Box::new(props), ) } } @@ -256,6 +264,72 @@ mod SuspenseBoundary_completions { pub use SuspenseBoundary_completions::Component::SuspenseBoundary; use generational_box::Owner; +/// The rendering lifecycle driver for a suspense boundary scope. +/// +/// The driver owns the [`SuspenseContext`] for this boundary and delegates the actual +/// create/diff/remove logic to the [`SuspenseBoundaryProps`] methods below. +pub(crate) struct SuspenseDriver { + /// The suspense context owned by this boundary. + suspense_context: SuspenseContext, +} + +impl SuspenseDriver { + fn new() -> Self { + Self { + suspense_context: SuspenseContext::new(), + } + } +} + +impl RenderDriver for SuspenseDriver { + fn initial_suspense_location(&self, _parent: SuspenseLocation) -> SuspenseLocation { + SuspenseLocation::SuspenseBoundary(self.suspense_context.clone()) + } + + fn create( + &self, + dom: &mut VirtualDom, + scope_id: ScopeId, + new: bool, + parent: Option, + to: Option<&mut (dyn WriteMutations + '_)>, + ) -> usize { + if new { + self.suspense_context.mount(scope_id); + } + SuspenseBoundaryProps::create(scope_id, parent, dom, to) + } + + fn diff( + &self, + dom: &mut VirtualDom, + scope_id: ScopeId, + to: Option<&mut (dyn WriteMutations + '_)>, + ) { + SuspenseBoundaryProps::diff(scope_id, dom, to) + } + + fn remove( + &self, + dom: &mut VirtualDom, + scope_id: ScopeId, + to: Option<&mut (dyn WriteMutations + '_)>, + destroy_component_state: bool, + replace_with: Option, + ) { + // If this is a suspense boundary, remove the suspended nodes as well + SuspenseContext::remove_suspended_nodes(dom, scope_id, destroy_component_state); + + if let Some(node) = dom.scopes[scope_id.0].last_rendered_node.clone() { + node.remove_node_inner(dom, to, destroy_component_state, replace_with) + }; + + if destroy_component_state { + dom.drop_scope(scope_id); + } + } +} + /// Suspense has a custom diffing algorithm that diffs the suspended nodes in the background without rendering them impl SuspenseBoundaryProps { /// Try to downcast [`AnyProps`] to [`SuspenseBoundaryProps`] @@ -264,38 +338,12 @@ impl SuspenseBoundaryProps { inner.map(|inner| &mut inner.inner) } - pub(crate) fn create( - mount: MountId, - idx: usize, - component: &VComponent, + pub(crate) fn create( + scope_id: ScopeId, parent: Option, dom: &mut VirtualDom, - to: Option<&mut M>, + to: Option<&mut (dyn WriteMutations + '_)>, ) -> usize { - let mut scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx)); - // If the ScopeId is a placeholder, we need to load up a new scope for this vcomponent. If it's already mounted, then we can just use that - if scope_id.is_placeholder() { - { - let suspense_context = SuspenseContext::new(); - - let suspense_boundary_location = - crate::scope_context::SuspenseLocation::SuspenseBoundary( - suspense_context.clone(), - ); - dom.runtime - .clone() - .with_suspense_location(suspense_boundary_location, || { - let scope_state = dom - .new_scope(component.props.duplicate(), component.name) - .state(); - suspense_context.mount(scope_state.id); - scope_id = scope_state.id; - }); - } - - // Store the scope id for the next render - dom.set_mounted_dyn_node(mount, idx, scope_id.0); - } dom.runtime.clone().with_scope_on_stack(scope_id, || { let scope_state = &mut dom.scopes[scope_id.0]; let props = Self::downcast_from_props(&mut *scope_state.props).unwrap(); @@ -307,7 +355,7 @@ impl SuspenseBoundaryProps { // First always render the children in the background. Rendering the children may cause this boundary to suspend suspense_context.under_suspense_boundary(&dom.runtime(), || { - children.create(dom, parent, None::<&mut M>); + children.create(dom, parent, None); }); // Store the (now mounted) children back into the scope state @@ -413,7 +461,7 @@ impl SuspenseBoundaryProps { // Take the suspended nodes out of the suspense boundary so the children know that the boundary is not suspended while diffing let suspended = suspense_context.take_suspended_nodes(); if let Some(node) = suspended { - node.remove_node(&mut *dom, None::<&mut M>, None); + node.remove_node(&mut *dom, None, None); } // Replace the rendered nodes with resolved nodes @@ -440,10 +488,10 @@ impl SuspenseBoundaryProps { }) } - pub(crate) fn diff( + pub(crate) fn diff( scope_id: ScopeId, dom: &mut VirtualDom, - to: Option<&mut M>, + to: Option<&mut (dyn WriteMutations + '_)>, ) { dom.runtime.clone().with_scope_on_stack(scope_id, || { let scope = &mut dom.scopes[scope_id.0]; @@ -482,7 +530,7 @@ impl SuspenseBoundaryProps { // Diff the suspended nodes in the background suspense_context.under_suspense_boundary(&dom.runtime(), || { - suspended_nodes.diff_node(&new_suspended_nodes, dom, None::<&mut M>); + suspended_nodes.diff_node(&new_suspended_nodes, dom, None); }); let suspense_context = SuspenseContext::downcast_suspense_boundary_from_scope( @@ -527,7 +575,7 @@ impl SuspenseBoundaryProps { // Then diff the new children in the background suspense_context.under_suspense_boundary(&dom.runtime(), || { - old_children.diff_node(&new_children, dom, None::<&mut M>); + old_children.diff_node(&new_children, dom, None); }); // Set the last rendered node to the new suspense placeholder @@ -551,7 +599,7 @@ impl SuspenseBoundaryProps { // First diff the two children nodes in the background suspense_context.under_suspense_boundary(&dom.runtime(), || { - old_suspended_nodes.diff_node(&new_children, dom, None::<&mut M>); + old_suspended_nodes.diff_node(&new_children, dom, None); // Then replace the placeholder with the new children let mount = old_placeholder.mount.get(); @@ -611,7 +659,7 @@ impl SuspenseContext { .and_then(|scope| scope.suspense_boundary()) } - pub(crate) fn remove_suspended_nodes( + pub(crate) fn remove_suspended_nodes( dom: &mut VirtualDom, scope_id: ScopeId, destroy_component_state: bool, @@ -622,7 +670,7 @@ impl SuspenseContext { }; // Remove the suspended nodes if let Some(node) = scope.take_suspended_nodes() { - node.remove_node_inner(dom, None::<&mut M>, destroy_component_state, None) + node.remove_node_inner(dom, None, destroy_component_state, None) } } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 5d31e69f6f..0d0c4b15bf 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -2,7 +2,9 @@ //! //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. +use crate::innerlude::Work; use crate::properties::RootProps; +use crate::render_driver::BodyDriver; use crate::root_wrapper::RootScopeWrapper; use crate::{ ComponentFunction, Element, Mutations, @@ -12,7 +14,6 @@ use crate::{ scopes::ScopeId, }; use crate::{Task, VComponent}; -use crate::{innerlude::Work, scopes::LastRenderedNode}; use futures_util::StreamExt; use slab::Slab; use std::collections::BTreeSet; @@ -289,11 +290,13 @@ impl VirtualDom { ) -> Self { let render_fn = root.fn_ptr(); let props = VProps::new(root, |_, _| true, root_props, "Root"); - Self::new_with_component(VComponent { - name: "root", + let driver = BodyDriver::new(); + Self::new_with_component(VComponent::new_with_driver( + "root", render_fn, - props: Box::new(props), - }) + driver, + Box::new(props), + )) } /// Create a new virtualdom and build it immediately @@ -316,13 +319,8 @@ impl VirtualDom { resolved_scopes: Default::default(), }; - let root = VProps::new( - RootScopeWrapper, - |_, _| true, - RootProps(root), - "RootWrapper", - ); - dom.new_scope(Box::new(root), "app"); + let root = VComponent::new(RootScopeWrapper, RootProps(root), "RootWrapper"); + dom.new_scope("app", root.driver.clone(), root.props.duplicate()); #[cfg(debug_assertions)] dom.register_subsecond_handler(); @@ -578,17 +576,12 @@ impl VirtualDom { #[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")] pub fn rebuild(&mut self, to: &mut impl WriteMutations) { let _runtime = RuntimeGuard::new(self.runtime.clone()); - let new_nodes = self + + let driver = self.runtime.get_state(ScopeId::ROOT).render_driver(); + let m = self .runtime .clone() - .while_rendering(|| self.run_scope(ScopeId::ROOT)); - - let new_nodes = LastRenderedNode::new(new_nodes); - - self.scopes[ScopeId::ROOT.0].last_rendered_node = Some(new_nodes.clone()); - - // Rebuilding implies we append the created elements to the root - let m = self.create_scope(Some(to), ScopeId::ROOT, new_nodes, None); + .while_rendering(|| driver.create(self, ScopeId::ROOT, true, None, Some(to))); to.append_children(ElementId(0), m); } @@ -726,7 +719,7 @@ impl VirtualDom { if run_scope { // If the scope is dirty, run the scope and get the mutations self.runtime.clone().while_rendering(|| { - self.run_and_diff_scope(None::<&mut NoOpMutations>, scope_id); + self.run_and_diff_scope(None, scope_id); }); tracing::trace!("Ran scope {:?} during suspense", scope_id);