diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index ff9331942..52a235972 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -1,5 +1,5 @@ use crate::{nodes::RenderReturn, Element}; -use std::{ops::Deref, panic::AssertUnwindSafe}; +use std::{any::Any, ops::Deref, panic::AssertUnwindSafe}; /// A boxed version of AnyProps that can be cloned pub(crate) struct BoxedAnyProps { @@ -7,7 +7,7 @@ pub(crate) struct BoxedAnyProps { } impl BoxedAnyProps { - fn new(inner: impl AnyProps + 'static) -> Self { + pub fn new(inner: impl AnyProps + 'static) -> Self { Self { inner: Box::new(inner), } @@ -33,7 +33,8 @@ impl Clone for BoxedAnyProps { /// A trait that essentially allows VComponentProps to be used generically pub(crate) trait AnyProps { fn render<'a>(&'a self) -> RenderReturn; - fn memoize(&self, other: &dyn AnyProps) -> bool; + fn memoize(&self, other: &dyn Any) -> bool; + fn props(&self) -> &dyn Any; fn duplicate(&self) -> Box; } @@ -41,25 +42,35 @@ pub(crate) struct VProps

{ pub render_fn: fn(P) -> Element, pub memo: fn(&P, &P) -> bool, pub props: P, + pub name: &'static str, } impl

VProps

{ - pub(crate) fn new(render_fn: fn(P) -> Element, memo: fn(&P, &P) -> bool, props: P) -> Self { + pub(crate) fn new( + render_fn: fn(P) -> Element, + memo: fn(&P, &P) -> bool, + props: P, + name: &'static str, + ) -> Self { Self { render_fn, memo, props, + name, } } } impl AnyProps for VProps

{ - // Safety: - // this will downcast the other ptr as our swallowed type! - // you *must* make this check *before* calling this method - // if your functions are not the same, then you will downcast a pointer into a different type (UB) - fn memoize(&self, other: &dyn AnyProps) -> bool { - (self.memo)(self, other) + fn memoize(&self, other: &dyn Any) -> bool { + match other.downcast_ref::() { + Some(other) => (self.memo)(&self.props, &other.props), + None => false, + } + } + + fn props(&self) -> &dyn Any { + &self.props } fn render(&self) -> RenderReturn { @@ -72,7 +83,7 @@ impl AnyProps for VProps

{ Ok(Some(e)) => RenderReturn::Ready(e), Ok(None) => RenderReturn::default(), Err(err) => { - let component_name = cx.name(); + let component_name = self.name; tracing::error!("Error while rendering component `{component_name}`: {err:?}"); RenderReturn::default() } @@ -84,6 +95,7 @@ impl AnyProps for VProps

{ render_fn: self.render_fn, memo: self.memo, props: self.props.clone(), + name: self.name, }) } } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 7ad3986ec..3aa82712f 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,5 +1,3 @@ -use std::ptr::NonNull; - use crate::{ innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode, ScopeId, @@ -21,16 +19,16 @@ pub struct ElementId(pub usize); #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct VNodeId(pub usize); -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct ElementRef { // the pathway of the real element inside the template pub(crate) path: ElementPath, - // The actual template - pub(crate) template: VNodeId, - - // The scope the element belongs to + // the scope that this element belongs to pub(crate) scope: ScopeId, + + // The actual element + pub(crate) element: VNode, } #[derive(Clone, Copy, Debug)] @@ -43,14 +41,6 @@ impl VirtualDom { ElementId(self.elements.insert(None)) } - pub(crate) fn next_vnode_ref(&mut self, vnode: &VNode) -> VNodeId { - let new_id = VNodeId(self.element_refs.insert(Some(unsafe { - std::mem::transmute::, _>(vnode.into()) - }))); - - new_id - } - pub(crate) fn reclaim(&mut self, el: ElementId) { self.try_reclaim(el) .unwrap_or_else(|| panic!("cannot reclaim {:?}", el)); @@ -67,11 +57,6 @@ impl VirtualDom { self.elements.try_remove(el.0).map(|_| ()) } - pub(crate) fn set_template(&mut self, id: VNodeId, vnode: &VNode) { - self.element_refs[id.0] = - Some(unsafe { std::mem::transmute::, _>(vnode.into()) }); - } - // Drop a scope and all its children // // Note: This will not remove any ids from the arena diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index c4390c212..64196a969 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,7 +1,4 @@ -use crate::any_props::AnyProps; -use crate::innerlude::{ - BorrowedAttributeValue, ElementPath, ElementRef, VComponent, VPlaceholder, VText, -}; +use crate::innerlude::{ElementPath, ElementRef, VComponent, VPlaceholder, VText}; use crate::mutations::Mutation; use crate::mutations::Mutation::*; use crate::nodes::VNode; @@ -96,9 +93,6 @@ impl VirtualDom { nodes_mut.resize(len, ElementId::default()); }; - // Set this node id - node.stable_id.set(Some(self.next_vnode_ref(node))); - // 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()); @@ -190,7 +184,7 @@ impl VirtualDom { path: ElementPath { path: template.template.get().node_paths[idx], }, - template: template.stable_id().unwrap(), + element: template.clone(), scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), }; self.create_dynamic_node(template_ref, node) @@ -200,10 +194,10 @@ impl VirtualDom { path: ElementPath { path: template.template.get().node_paths[idx], }, - template: template.stable_id().unwrap(), + element: template.clone(), scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), }; - parent.set(Some(template_ref)); + *parent.borrow_mut() = Some(template_ref); let id = self.set_slot(id); self.mutations.push(CreatePlaceholder { id }); 1 @@ -217,12 +211,7 @@ impl VirtualDom { } fn create_static_text(&mut self, value: &str, id: ElementId) { - // Safety: we promise not to re-alias this text later on after committing it to the mutation - let unbounded_text: &str = unsafe { std::mem::transmute(value) }; - self.mutations.push(CreateTextNode { - value: unbounded_text, - id, - }); + self.mutations.push(CreateTextNode { value, id }); } /// We write all the descendent data for this element @@ -289,7 +278,7 @@ impl VirtualDom { path: ElementPath { path: template.template.get().node_paths[idx], }, - template: template.stable_id().unwrap(), + element: template.clone(), scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), }; let m = self.create_dynamic_node(boundary_ref, &template.dynamic_nodes[idx]); @@ -336,14 +325,14 @@ impl VirtualDom { 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 = unsafe { std::mem::transmute(attribute.name) }; + 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 }, - template: template.stable_id().unwrap(), + element: template.clone(), scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), }; self.elements[id.0] = Some(element_ref); @@ -354,9 +343,7 @@ impl VirtualDom { }) } _ => { - // Safety: we promise not to re-alias this text later on after committing it to the mutation - let value: BorrowedAttributeValue = (&attribute.value).into(); - let unbounded_value = unsafe { std::mem::transmute(value) }; + let unbounded_value = &attribute.value; self.mutations.push(SetAttribute { name: unbounded_name, @@ -516,7 +503,7 @@ impl VirtualDom { placeholder.id.set(Some(id)); // Assign the placeholder's parent - placeholder.parent.set(Some(parent)); + *placeholder.parent.borrow_mut() = Some(parent); // Assign the ID to the existing node in the template self.mutations.push(AssignId { @@ -540,7 +527,11 @@ impl VirtualDom { component.scope.set(Some(scope)); - match self.run_scope(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); @@ -550,22 +541,20 @@ impl VirtualDom { } } - /// Load a scope from a vcomponent. If the props don't exist, that means the component is currently "live" + /// 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) -> ScopeId { - component - .props - .map(|props| { - let unbounded_props: Box = unsafe { std::mem::transmute(props) }; - self.new_scope(unbounded_props, component.name).context().id - }) - .unwrap_or_else(|| component.scope.get().unwrap()) + 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) -> usize { let id = self.next_element(); self.mutations.push(Mutation::CreatePlaceholder { id }); placeholder.id.set(Some(id)); - placeholder.parent.set(parent); + *placeholder.parent.borrow_mut() = parent; 1 } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 924536ef0..90f2d3d79 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,10 +1,9 @@ +use std::ops::Deref; + use crate::{ any_props::AnyProps, arena::ElementId, - innerlude::{ - BorrowedAttributeValue, DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, - VText, - }, + innerlude::{DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, VText}, mutations::Mutation, nodes::RenderReturn, nodes::{DynamicNode, VNode}, @@ -17,52 +16,43 @@ use rustc_hash::{FxHashMap, FxHashSet}; use DynamicNode::*; impl VirtualDom { - pub(super) fn diff_scope(&mut self, scope: ScopeId) { + pub(super) fn diff_scope(&mut self, scope: ScopeId, new_nodes: RenderReturn) { self.runtime.scope_stack.borrow_mut().push(scope); - let scope_state = &mut self.get_scope(scope).unwrap(); - unsafe { - // Load the old and new bump arenas - let old = scope_state - .previous_frame() - .try_load_node() - .expect("Call rebuild before diffing"); + let scope_state = &mut self.scopes[scope.0]; + // Load the old and new bump arenas + let old = std::mem::replace(&mut scope_state.last_rendered_node, Some(new_nodes)).unwrap(); + let new = scope_state.last_rendered_node.as_ref().unwrap(); - let new = scope_state - .current_frame() - .try_load_node() - .expect("Call rebuild before diffing"); + use RenderReturn::{Aborted, Ready}; - use RenderReturn::{Aborted, Ready}; + match (&old, new) { + // Normal pathway + (Ready(l), Ready(r)) => self.diff_node(l, r), - match (old, new) { - // Normal pathway - (Ready(l), Ready(r)) => self.diff_node(l, r), + // Unwind the mutations if need be + (Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p), - // Unwind the mutations if need be - (Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p), + // Just move over the placeholder + (Aborted(l), Aborted(r)) => { + r.id.set(l.id.get()); + *r.parent.borrow_mut() = l.parent.borrow().clone(); + } - // Just move over the placeholder - (Aborted(l), Aborted(r)) => { - r.id.set(l.id.get()); - r.parent.set(l.parent.get()) - } - - // Placeholder becomes something - // We should also clear the error now - (Aborted(l), Ready(r)) => self.replace_placeholder( - l, - [r], - l.parent.get().expect("root node should not be none"), - ), - }; - } + // Placeholder becomes something + // We should also clear the error now + (Aborted(l), Ready(r)) => self.replace_placeholder( + l, + [r], + l.parent.borrow().expect("root node should not be none"), + ), + }; self.runtime.scope_stack.borrow_mut().pop(); } fn diff_ok_to_err(&mut self, l: &VNode, p: &VPlaceholder) { let id = self.next_element(); p.id.set(Some(id)); - p.parent.set(l.parent.get()); + *p.parent.borrow_mut() = l.parent.borrow().clone(); self.mutations.push(Mutation::CreatePlaceholder { id }); let pre_edits = self.mutations.edits.len(); @@ -101,13 +91,7 @@ impl VirtualDom { // Copy over the parent { - right_template.parent.set(left_template.parent.get()); - } - - // Update the bubble id pointer - right_template.stable_id.set(left_template.stable_id.get()); - if let Some(bubble_id) = right_template.stable_id.get() { - self.set_template(bubble_id, right_template); + *right_template.parent.borrow_mut() = left_template.parent.borrow().clone(); } // If the templates are the same, we don't need to do anything, nor do we want to @@ -145,7 +129,7 @@ impl VirtualDom { .enumerate() .for_each(|(dyn_node_idx, (left_node, right_node))| { let current_ref = ElementRef { - template: right_template.stable_id().unwrap(), + element: right_template.clone(), path: ElementPath { path: left_template.template.get().node_paths[dyn_node_idx], }, @@ -175,19 +159,18 @@ impl VirtualDom { (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right, parent), (Placeholder(left), Placeholder(right)) => { right.id.set(left.id.get()); - right.parent.set(left.parent.get()); + *right.parent.borrow_mut() = left.parent.borrow().clone(); }, (Component(left), Component(right)) => self.diff_vcomponent(left, right, Some(parent)), - (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right, parent), + (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, right, parent), (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right, parent), _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."), }; } fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) { - let name = unsafe { std::mem::transmute(left_attr.name) }; - let value: BorrowedAttributeValue = (&right_attr.value).into(); - let value = unsafe { std::mem::transmute(value) }; + let name = &left_attr.name; + let value = &right_attr.value; self.mutations.push(Mutation::SetAttribute { id: left_attr.mounted_element.get(), ns: right_attr.namespace, @@ -218,13 +201,13 @@ impl VirtualDom { // copy out the box for both let old_scope = &self.scopes[scope_id.0]; - let old = old_scope.props.as_ref(); - let new: &dyn AnyProps = right.props.as_ref(); + let old = old_scope.props.deref(); + let new: &dyn AnyProps = right.props.deref(); // If the props are static, then we try to memoize by setting the new with the old // The target scopestate still has the reference to the old props, so there's no need to update anything // This also implicitly drops the new props since they're not used - if old.memoize(new) { + if old.memoize(new.props()) { tracing::trace!( "Memoized props for component {:#?} ({})", scope_id, @@ -234,11 +217,11 @@ impl VirtualDom { } // First, move over the props from the old to the new, dropping old props in the process - self.scopes[scope_id.0].props = new; + self.scopes[scope_id.0].props = right.props.clone(); // Now run the component and diff it - self.run_scope(scope_id); - self.diff_scope(scope_id); + let new = self.run_scope(scope_id); + self.diff_scope(scope_id, new); self.dirty_scopes.remove(&DirtyScope { height: self.runtime.get_context(scope_id).unwrap().height, @@ -325,8 +308,10 @@ impl VirtualDom { right.id.set(Some(id)); if left.value != right.value { - let value = unsafe { std::mem::transmute(right.value) }; - self.mutations.push(Mutation::SetText { id, value }); + self.mutations.push(Mutation::SetText { + id, + value: &right.value, + }); } } @@ -824,7 +809,7 @@ impl VirtualDom { let placeholder = self.next_element(); r.id.set(Some(placeholder)); - r.parent.set(Some(parent)); + r.parent.borrow_mut().replace(parent); self.mutations .push(Mutation::CreatePlaceholder { id: placeholder }); @@ -862,16 +847,6 @@ impl VirtualDom { // Clean up the roots, assuming we need to generate mutations for these // This is done last in order to preserve Node ID reclaim order (reclaim in reverse order of claim) self.reclaim_roots(node, gen_muts); - - // Clean up the vnode id - self.reclaim_vnode_id(node); - } - - fn reclaim_vnode_id(&mut self, node: &VNode) { - // Clean up the vnode id - if let Some(id) = node.stable_id() { - self.element_refs.remove(id.0); - } } fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) { @@ -976,10 +951,6 @@ impl VirtualDom { RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts), }; - // Restore the props back to the vcomponent in case it gets rendered again - let props = self.scopes[scope.0].props.take(); - *comp.props.borrow_mut() = unsafe { std::mem::transmute(props) }; - // Now drop all the resouces self.drop_scope(scope, false); } @@ -1019,7 +990,7 @@ impl VirtualDom { pub(crate) fn assign_boundary_ref(&mut self, parent: Option, child: &VNode) { if let Some(parent) = parent { // assign the parent of the child - child.parent.set(Some(parent)); + child.parent.borrow_mut().replace(parent); } } } diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index 2901acbca..44c42606c 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -1,7 +1,6 @@ use crate::{ scope_context::{consume_context, current_scope_id, schedule_update_any}, - Element, IntoDynNode, Properties, ScopeId, ScopeState, Template, TemplateAttribute, - TemplateNode, VNode, + Element, IntoDynNode, Properties, ScopeId, Template, TemplateAttribute, TemplateNode, VNode, }; use std::{ any::{Any, TypeId}, @@ -14,8 +13,9 @@ use std::{ }; /// Provide an error boundary to catch errors from child components -pub fn use_error_boundary(cx: &ScopeState) -> &ErrorBoundary { - cx.use_hook(|| cx.provide_context(ErrorBoundary::new())) +pub fn use_error_boundary() -> ErrorBoundary { + // use_hook(|| cx.provide_context(ErrorBoundary::new())) + todo!() } /// A boundary that will capture any errors from child components @@ -262,10 +262,11 @@ impl Throw for Option { } } -pub struct ErrorHandler(Box Element>); +#[derive(Clone)] +pub struct ErrorHandler(Rc Element>); impl Element> From for ErrorHandler { fn from(value: F) -> Self { - Self(Box::new(value)) + Self(Rc::new(value)) } } fn default_handler(error: CapturedError) -> Element { @@ -284,15 +285,13 @@ fn default_handler(error: CapturedError) -> Element { node_paths: &[&[0u8, 0u8]], attr_paths: &[], }; - Some(VNode { - parent: Default::default(), - stable_id: Default::default(), - key: None, - template: std::cell::Cell::new(TEMPLATE), - root_ids: Vec::with_capacity(1usize).into(), - dynamic_nodes: vec![error.to_string().into_dyn_node()], - dynamic_attrs: Default::default(), - }) + Some(VNode::new( + None, + TEMPLATE, + Vec::with_capacity(1usize), + vec![error.to_string().into_dyn_node()], + Default::default(), + )) } #[derive(Clone)] @@ -417,7 +416,7 @@ impl< ::core::default::Default::default() }); let handle_error = ErrorBoundaryPropsBuilder_Optional::into_value(handle_error, || { - ErrorHandler(Box::new(default_handler)) + ErrorHandler(Rc::new(default_handler)) }); ErrorBoundaryProps { children, @@ -448,27 +447,24 @@ impl< /// They are similar to `try/catch` in JavaScript, but they only catch errors in the tree below them. /// Error boundaries are quick to implement, but it can be useful to individually handle errors in your components to provide a better user experience when you know that an error is likely to occur. #[allow(non_upper_case_globals, non_snake_case)] -pub fn ErrorBoundary(cx: ErrorBoundaryProps) -> Element { - let error_boundary = use_error_boundary(cx); +pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element { + let error_boundary = use_error_boundary(); match error_boundary.take_error() { - Some(error) => cx.render((cx.props.handle_error.0)(error)), + Some(error) => (props.handle_error.0)(error), None => Some({ - let __cx = cx; static TEMPLATE: Template = Template { name: "examples/error_handle.rs:81:17:2342", roots: &[TemplateNode::Dynamic { id: 0usize }], node_paths: &[&[0u8]], attr_paths: &[], }; - VNode { - parent: Default::default(), - stable_id: Default::default(), - key: None, - template: std::cell::Cell::new(TEMPLATE), - root_ids: Vec::with_capacity(1usize).into(), - dynamic_nodes: vec![(&cx.props.children).into_dyn_node()], - dynamic_attrs: __cx.bump().alloc([]), - } + VNode::new( + None, + TEMPLATE, + Vec::with_capacity(1usize), + vec![(props.children).into_dyn_node()], + Default::default(), + ) }), } } diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index 9f5ad7e16..577c04a65 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -27,16 +27,7 @@ use crate::innerlude::*; /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. #[allow(non_upper_case_globals, non_snake_case)] pub fn Fragment(cx: FragmentProps) -> Element { - let children = cx.0.as_ref()?; - Some(VNode { - key: children.key, - parent: children.parent.clone(), - stable_id: children.stable_id.clone(), - template: children.template.clone(), - root_ids: children.root_ids.clone(), - dynamic_nodes: children.dynamic_nodes, - dynamic_attrs: children.dynamic_attrs, - }) + cx.0.clone() } #[derive(Clone, PartialEq)] diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index feecc0590..e5576310f 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -73,10 +73,10 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, - CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, - Mutation, Mutations, Properties, RenderReturn, ScopeId, ScopeState, TaskId, Template, - TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, + fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, CapturedError, + Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, Mutation, Mutations, + Properties, RenderReturn, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, + TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, }; /// The purpose of this module is to alleviate imports of many common types diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 25e64cecc..021eb1406 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -1,6 +1,6 @@ use rustc_hash::FxHashSet; -use crate::{arena::ElementId, innerlude::BorrowedAttributeValue, ScopeId, Template}; +use crate::{arena::ElementId, AttributeValue, ScopeId, Template}; /// A container for all the relevant steps to modify the Real DOM /// @@ -13,7 +13,6 @@ use crate::{arena::ElementId, innerlude::BorrowedAttributeValue, ScopeId, Templa /// Templates, however, apply to all subtrees, not just target subtree. /// /// Mutations are the only link between the RealDOM and the VirtualDOM. -#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[derive(Debug, Default)] #[must_use = "not handling edits can lead to visual inconsistencies in UI"] pub struct Mutations<'a> { @@ -56,11 +55,6 @@ impl<'a> Mutations<'a> { /// of the Dioxus VirtualDom. /// /// These edits can be serialized and sent over the network or through any interface -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - serde(tag = "type") -)] #[derive(Debug, PartialEq)] pub enum Mutation<'a> { /// Add these m children to the target element @@ -195,7 +189,7 @@ pub enum Mutation<'a> { name: &'a str, /// The value of the attribute. - value: BorrowedAttributeValue<'a>, + value: &'a AttributeValue, /// The ID of the node to set the attribute of. id: ElementId, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 767b81da3..e25a69209 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,5 +1,8 @@ -use crate::innerlude::{ElementRef, VNodeId}; -use crate::{any_props::AnyProps, arena::ElementId, Element, Event, ScopeId, ScopeState}; +use crate::any_props::BoxedAnyProps; +use crate::innerlude::ElementRef; +use crate::{arena::ElementId, Element, Event, ScopeId}; +use std::ops::Deref; +use std::rc::Rc; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, @@ -15,6 +18,7 @@ pub type TemplateId = &'static str; /// /// Dioxus will do its best to immediately resolve any async components into a regular Element, but as an implementor /// you might need to handle the case where there's no node immediately ready. +#[derive(Clone)] pub enum RenderReturn { /// A currently-available element Ready(VNode), @@ -36,26 +40,23 @@ impl Default for RenderReturn { /// /// 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(Debug, Clone)] -pub struct VNode { +#[derive(Debug)] +pub struct VNodeInner { /// The key given to the root of this template. /// /// In fragments, this is the key of the first child. In other cases, it is the key of the root. pub key: Option, /// When rendered, this template will be linked to its parent manually - pub(crate) parent: Cell>, - - /// The bubble id assigned to the child that we need to update and drop when diffing happens - pub(crate) stable_id: Cell>, - - /// The static nodes and static descriptor of the template - pub template: Cell>, + pub(crate) parent: RefCell>, /// The IDs for the roots of this template - to be used when moving the template around and removing it from /// the actual Dom pub root_ids: RefCell>, + /// The static nodes and static descriptor of the template + pub template: Cell>, + /// The dynamic parts of the template pub dynamic_nodes: Vec, @@ -63,13 +64,33 @@ pub struct VNode { pub dynamic_attrs: Vec, } +/// 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 +/// static parts of the template. +#[derive(Clone, Debug)] +pub struct VNode(Rc); + +impl PartialEq for VNode { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} + +impl Deref for VNode { + type Target = VNodeInner; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl VNode { /// Create a template with no nodes that will be skipped over during diffing pub fn empty() -> Element { - Some(VNode { + Some(Self(Rc::new(VNodeInner { key: None, parent: Default::default(), - stable_id: Default::default(), root_ids: Default::default(), dynamic_nodes: Vec::new(), dynamic_attrs: Vec::new(), @@ -79,7 +100,7 @@ impl VNode { node_paths: &[], attr_paths: &[], }), - }) + }))) } /// Create a new VNode @@ -90,20 +111,14 @@ impl VNode { dynamic_nodes: Vec, dynamic_attrs: Vec, ) -> Self { - Self { + Self(Rc::new(VNodeInner { key, - parent: Cell::new(None), - stable_id: Cell::new(None), + parent: Default::default(), template: Cell::new(template), root_ids: RefCell::new(root_ids), dynamic_nodes, dynamic_attrs, - } - } - - /// Get the stable id of this node used for bubbling events - pub(crate) fn stable_id(&self) -> Option { - self.stable_id.get() + })) } /// Load a dynamic root at the given index @@ -293,7 +308,7 @@ pub enum TemplateNode { /// A node created at runtime /// /// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum DynamicNode { /// A component node /// @@ -355,7 +370,6 @@ impl<'a> 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("static_props", &self.static_props) .field("scope", &self.scope) .finish() } @@ -387,12 +401,12 @@ impl<'a> VText { } /// A placeholder node, used by suspense and fragments -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct VPlaceholder { /// The ID of this node in the real DOM pub(crate) id: Cell>, /// The parent of this node - pub(crate) parent: Cell>, + pub(crate) parent: RefCell>, } impl VPlaceholder { @@ -436,7 +450,7 @@ pub enum TemplateAttribute { } /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"` -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Attribute { /// The name of the attribute. pub name: &'static str, @@ -483,7 +497,6 @@ impl Attribute { /// /// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`] /// variant. -#[derive(Clone)] pub enum AttributeValue { /// Text attribute Text(String), @@ -498,7 +511,7 @@ pub enum AttributeValue { Bool(bool), /// A listener, like "onclick" - Listener(ListenerCb), + Listener(RefCell), /// An arbitrary value that implements PartialEq and is static Any(Box), @@ -509,85 +522,6 @@ pub enum AttributeValue { pub type ListenerCb = Box)>; -/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements that are borrowed -/// -/// These varients are used to communicate what the value of an attribute is that needs to be updated -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serialize", serde(untagged))] -pub enum BorrowedAttributeValue<'a> { - /// Text attribute - Text(&'a str), - - /// A float - Float(f64), - - /// Signed integer - Int(i64), - - /// Boolean - Bool(bool), - - /// An arbitrary value that implements PartialEq and is static - #[cfg_attr( - feature = "serialize", - serde( - deserialize_with = "deserialize_any_value", - serialize_with = "serialize_any_value" - ) - )] - Any(std::cell::Ref<'a, dyn AnyValue>), - - /// A "none" value, resulting in the removal of an attribute from the dom - None, -} - -impl<'a> From<&'a AttributeValue> for BorrowedAttributeValue<'a> { - fn from(value: &'a AttributeValue) -> Self { - match value { - AttributeValue::Text(value) => BorrowedAttributeValue::Text(value), - AttributeValue::Float(value) => BorrowedAttributeValue::Float(*value), - AttributeValue::Int(value) => BorrowedAttributeValue::Int(*value), - AttributeValue::Bool(value) => BorrowedAttributeValue::Bool(*value), - AttributeValue::Listener(_) => { - panic!("A listener cannot be turned into a borrowed value") - } - AttributeValue::Any(value) => { - let value = value.borrow(); - BorrowedAttributeValue::Any(std::cell::Ref::map(value, |value| { - &**value.as_ref().unwrap() - })) - } - AttributeValue::None => BorrowedAttributeValue::None, - } - } -} - -impl Debug for BorrowedAttributeValue<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(), - Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(), - Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(), - Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(), - Self::Any(_) => f.debug_tuple("Any").field(&"...").finish(), - Self::None => write!(f, "None"), - } - } -} - -impl PartialEq for BorrowedAttributeValue<'_> { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Text(l0), Self::Text(r0)) => l0 == r0, - (Self::Float(l0), Self::Float(r0)) => l0 == r0, - (Self::Int(l0), Self::Int(r0)) => l0 == r0, - (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, - (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(&**r0), - _ => core::mem::discriminant(self) == core::mem::discriminant(other), - } - } -} - #[cfg(feature = "serialize")] fn serialize_any_value(_: &std::cell::Ref<'_, dyn AnyValue>, _: S) -> Result where @@ -626,11 +560,7 @@ impl PartialEq for AttributeValue { (Self::Int(l0), Self::Int(r0)) => l0 == r0, (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, (Self::Listener(_), Self::Listener(_)) => true, - (Self::Any(l0), Self::Any(r0)) => { - let l0 = l0.borrow(); - let r0 = r0.borrow(); - l0.as_ref().unwrap().any_cmp(&**r0.as_ref().unwrap()) - } + (Self::Any(l0), Self::Any(r0)) => l0.as_ref().any_cmp(r0.as_ref()), _ => false, } } @@ -820,7 +750,7 @@ impl<'a> IntoAttributeValue for Arguments<'_> { impl IntoAttributeValue for Box { fn into_value(self) -> AttributeValue { - AttributeValue::Any(RefCell::new(Some(self))) + AttributeValue::Any(self) } } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 694f9811d..a1d5d8b89 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -1,5 +1,5 @@ use crate::{ - any_props::AnyProps, + any_props::{AnyProps, BoxedAnyProps}, innerlude::DirtyScope, nodes::RenderReturn, scope_context::ScopeContext, @@ -8,11 +8,7 @@ use crate::{ }; impl VirtualDom { - pub(super) fn new_scope( - &mut self, - props: Box, - name: &'static str, - ) -> &ScopeState { + pub(super) fn new_scope(&mut self, props: BoxedAnyProps, name: &'static str) -> &ScopeState { let parent_id = self.runtime.current_scope_id(); let height = parent_id .and_then(|parent_id| self.get_scope(parent_id).map(|f| f.context().height + 1)) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index e26f38ca9..5537553a5 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -1,6 +1,5 @@ use crate::{ - any_props::AnyProps, - any_props::VProps, + any_props::{BoxedAnyProps, VProps}, innerlude::{DynamicNode, EventHandler, VComponent, VText}, nodes::{IntoAttributeValue, IntoDynNode, RenderReturn}, runtime::Runtime, @@ -49,7 +48,7 @@ pub struct ScopeState { pub(crate) last_rendered_node: Option, - pub(crate) props: Box, + pub(crate) props: BoxedAnyProps, } impl Drop for ScopeState { @@ -300,16 +299,12 @@ impl<'src> ScopeState { // The properties must be valid until the next bump frame P: Properties, { - let vcomp = VProps::new(component, P::memoize, props); - - // cast off the lifetime of the render return - let as_dyn: Box = Box::new(vcomp); - let extended: Box = unsafe { std::mem::transmute(as_dyn) }; + let vcomp = VProps::new(component, P::memoize, props, fn_name); DynamicNode::Component(VComponent { name: fn_name, render_fn: component as *const (), - props: extended, + props: BoxedAnyProps::new(vcomp), scope: Default::default(), }) } @@ -332,14 +327,14 @@ impl<'src> ScopeState { &'src self, mut callback: impl FnMut(Event) + 'src, ) -> AttributeValue { - AttributeValue::Listener(Box::new(move |event: Event| { + AttributeValue::Listener(RefCell::new(Box::new(move |event: Event| { if let Ok(data) = event.data.downcast::() { callback(Event { propagates: event.propagates, data, }); } - })) + }))) } /// Create a new [`AttributeValue`] with a value that implements [`AnyValue`] diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 5c8246355..98f510384 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -3,7 +3,7 @@ //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. use crate::{ - any_props::VProps, + any_props::{BoxedAnyProps, VProps}, arena::ElementId, innerlude::{DirtyScope, ElementRef, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, mutations::Mutation, @@ -11,14 +11,12 @@ use crate::{ nodes::{Template, TemplateId}, runtime::{Runtime, RuntimeGuard}, scopes::{ScopeId, ScopeState}, - AttributeValue, Element, Event, VNode, + AttributeValue, Element, Event, }; use futures_util::{pin_mut, StreamExt}; use rustc_hash::{FxHashMap, FxHashSet}; use slab::Slab; -use std::{ - any::Any, cell::Cell, collections::BTreeSet, future::Future, ptr::NonNull, rc::Rc, sync::Arc, -}; +use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc, sync::Arc}; /// A virtual node system that progresses user events and diffs UI trees. /// @@ -187,9 +185,6 @@ pub struct VirtualDom { // Maps a template path to a map of byteindexes to templates pub(crate) templates: FxHashMap>>, - // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template - pub(crate) element_refs: Slab>>, - // The element ids that are used in the renderer pub(crate) elements: Slab>, @@ -268,13 +263,12 @@ impl VirtualDom { dirty_scopes: Default::default(), templates: Default::default(), elements: Default::default(), - element_refs: Default::default(), mutations: Mutations::default(), suspended_scopes: Default::default(), }; let root = dom.new_scope( - Box::new(VProps::new(root, |_, _| unreachable!(), root_props)), + BoxedAnyProps::new(VProps::new(root, |_, _| unreachable!(), root_props, "root")), "app", ); @@ -363,14 +357,10 @@ impl VirtualDom { | <-- no, broke early */ let parent_path = match self.elements.get(element.0) { - Some(Some(el)) => el, + Some(Some(el)) => el.clone(), _ => return, }; - let mut parent_node = self - .element_refs - .get(parent_path.template.0) - .cloned() - .map(|el| (*parent_path, el)); + 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 @@ -382,13 +372,12 @@ impl VirtualDom { // If the event bubbles, we traverse through the tree until we find the target element. if bubbles { // 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, el_ref)) = parent_node { - // safety: we maintain references of all vnodes in the element slab - let template = unsafe { el_ref.unwrap().as_ref() }; - let node_template = template.template.get(); + while let Some(path) = parent_node { + let el_ref = &path.element; + let node_template = el_ref.template.get(); let target_path = path.path; - for (idx, attr) in template.dynamic_attrs.iter().enumerate() { + for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() { let this_path = node_template.attr_paths[idx]; // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one @@ -413,9 +402,7 @@ impl VirtualDom { let origin = path.scope; self.runtime.scope_stack.borrow_mut().push(origin); self.runtime.rendering.set(false); - if let Some(cb) = listener.borrow_mut().as_deref_mut() { - cb(uievent.clone()); - } + (listener.borrow_mut())(uievent.clone()); self.runtime.scope_stack.borrow_mut().pop(); self.runtime.rendering.set(true); @@ -425,22 +412,16 @@ impl VirtualDom { } } - parent_node = template.parent.get().and_then(|element_ref| { - self.element_refs - .get(element_ref.template.0) - .cloned() - .map(|el| (element_ref, el)) - }); + parent_node = el_ref.parent.borrow().clone(); } } else { // Otherwise, we just call the listener on the target element - if let Some((path, el_ref)) = parent_node { - // safety: we maintain references of all vnodes in the element slab - let template = unsafe { el_ref.unwrap().as_ref() }; - let node_template = template.template.get(); + if let Some(path) = parent_node { + let el_ref = &path.element; + let node_template = el_ref.template.get(); let target_path = path.path; - for (idx, attr) in template.dynamic_attrs.iter().enumerate() { + for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() { let this_path = node_template.attr_paths[idx]; // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one @@ -450,9 +431,7 @@ impl VirtualDom { let origin = path.scope; self.runtime.scope_stack.borrow_mut().push(origin); self.runtime.rendering.set(false); - if let Some(cb) = listener.as_deref_mut() { - cb(uievent.clone()); - } + (listener.borrow_mut())(uievent.clone()); self.runtime.scope_stack.borrow_mut().pop(); self.runtime.rendering.set(true); @@ -570,7 +549,7 @@ impl VirtualDom { match unsafe { self.run_scope(ScopeId::ROOT) } { // Rebuilding implies we append the created elements to the root RenderReturn::Ready(node) => { - let m = self.create_scope(ScopeId::ROOT, node); + let m = self.create_scope(ScopeId::ROOT, &node); self.mutations.edits.push(Mutation::AppendChildren { id: ElementId(0), m, @@ -645,8 +624,8 @@ impl VirtualDom { { let _runtime = RuntimeGuard::new(self.runtime.clone()); // Run the scope and get the mutations - self.run_scope(dirty.id); - self.diff_scope(dirty.id); + let new_nodes = self.run_scope(dirty.id); + self.diff_scope(dirty.id, new_nodes); } }