Browse Source

remove a lot of unsafe

Evan Almloff 1 year ago
parent
commit
0c76770da0

+ 23 - 11
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<dyn AnyProps>;
 }
 
@@ -41,25 +42,35 @@ pub(crate) struct VProps<P> {
     pub render_fn: fn(P) -> Element,
     pub memo: fn(&P, &P) -> bool,
     pub props: P,
+    pub name: &'static str,
 }
 
 impl<P> VProps<P> {
-    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<P: Clone> AnyProps for VProps<P> {
-    // 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::<Self>() {
+            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<P: Clone> AnyProps for VProps<P> {
             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<P: Clone> AnyProps for VProps<P> {
             render_fn: self.render_fn,
             memo: self.memo,
             props: self.props.clone(),
+            name: self.name,
         })
     }
 }

+ 5 - 20
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::<NonNull<VNode>, _>(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::<NonNull<VNode>, _>(vnode.into()) });
-    }
-
     // Drop a scope and all its children
     //
     // Note: This will not remove any ids from the arena

+ 22 - 33
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<dyn AnyProps> = 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<ElementRef>) -> 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
     }

+ 50 - 79
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 new = scope_state
-                .current_frame()
-                .try_load_node()
-                .expect("Call rebuild before diffing");
-
-            use RenderReturn::{Aborted, Ready};
-
-            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),
-
-                // Just move over the placeholder
-                (Aborted(l), Aborted(r)) => {
-                    r.id.set(l.id.get());
-                    r.parent.set(l.parent.get())
-                }
+        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();
 
-                // 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"),
-                ),
-            };
-        }
+        use RenderReturn::{Aborted, Ready};
+
+        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),
+
+            // Just move over the placeholder
+            (Aborted(l), Aborted(r)) => {
+                r.id.set(l.id.get());
+                *r.parent.borrow_mut() = l.parent.borrow().clone();
+            }
+
+            // 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<ElementRef>, child: &VNode) {
         if let Some(parent) = parent {
             // assign the parent of the child
-            child.parent.set(Some(parent));
+            child.parent.borrow_mut().replace(parent);
         }
     }
 }

+ 25 - 29
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<T> Throw for Option<T> {
     }
 }
 
-pub struct ErrorHandler(Box<dyn Fn(CapturedError) -> Element>);
+#[derive(Clone)]
+pub struct ErrorHandler(Rc<dyn Fn(CapturedError) -> Element>);
 impl<F: Fn(CapturedError) -> Element> From<F> 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(),
+            )
         }),
     }
 }

+ 1 - 10
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)]

+ 4 - 4
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

+ 2 - 8
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,

+ 45 - 115
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<String>,
 
     /// When rendered, this template will be linked to its parent manually
-    pub(crate) parent: Cell<Option<ElementRef>>,
-
-    /// The bubble id assigned to the child that we need to update and drop when diffing happens
-    pub(crate) stable_id: Cell<Option<VNodeId>>,
-
-    /// The static nodes and static descriptor of the template
-    pub template: Cell<Template<'static>>,
+    pub(crate) parent: RefCell<Option<ElementRef>>,
 
     /// The IDs for the roots of this template - to be used when moving the template around and removing it from
     /// the actual Dom
     pub root_ids: RefCell<Vec<ElementId>>,
 
+    /// The static nodes and static descriptor of the template
+    pub template: Cell<Template<'static>>,
+
     /// The dynamic parts of the template
     pub dynamic_nodes: Vec<DynamicNode>,
 
@@ -63,13 +64,33 @@ pub struct VNode {
     pub dynamic_attrs: Vec<Attribute>,
 }
 
+/// 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<VNodeInner>);
+
+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<DynamicNode>,
         dynamic_attrs: Vec<Attribute>,
     ) -> 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<VNodeId> {
-        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<Option<ElementId>>,
     /// The parent of this node
-    pub(crate) parent: Cell<Option<ElementRef>>,
+    pub(crate) parent: RefCell<Option<ElementRef>>,
 }
 
 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<ListenerCb>),
 
     /// An arbitrary value that implements PartialEq and is static
     Any(Box<dyn AnyValue>),
@@ -509,85 +522,6 @@ pub enum AttributeValue {
 
 pub type ListenerCb = Box<dyn FnMut(Event<dyn Any>)>;
 
-/// 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<S>(_: &std::cell::Ref<'_, dyn AnyValue>, _: S) -> Result<S::Ok, S::Error>
 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<dyn AnyValue> {
     fn into_value(self) -> AttributeValue {
-        AttributeValue::Any(RefCell::new(Some(self)))
+        AttributeValue::Any(self)
     }
 }
 

+ 2 - 6
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<dyn AnyProps>,
-        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))

+ 6 - 11
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<RenderReturn>,
 
-    pub(crate) props: Box<dyn AnyProps>,
+    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<dyn AnyProps> = Box::new(vcomp);
-        let extended: Box<dyn AnyProps> = 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<T>) + 'src,
     ) -> AttributeValue {
-        AttributeValue::Listener(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>() {
                 callback(Event {
                     propagates: event.propagates,
                     data,
                 });
             }
-        }))
+        })))
     }
 
     /// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]

+ 20 - 41
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<TemplateId, FxHashMap<usize, Template<'static>>>,
 
-    // 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<Option<NonNull<VNode>>>,
-
     // The element ids that are used in the renderer
     pub(crate) elements: Slab<Option<ElementRef>>,
 
@@ -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);
                 }
             }