Browse Source

move diffing into the global runtime

Evan Almloff 1 year ago
parent
commit
c70e2bfcb6

+ 42 - 8
packages/core/src/any_props.rs

@@ -1,14 +1,40 @@
-use crate::{nodes::RenderReturn, scopes::ScopeState, Element};
-use std::panic::AssertUnwindSafe;
+use crate::{nodes::RenderReturn, Element};
+use std::{ops::Deref, panic::AssertUnwindSafe};
+
+/// A boxed version of AnyProps that can be cloned
+pub(crate) struct BoxedAnyProps {
+    inner: Box<dyn AnyProps>,
+}
+
+impl BoxedAnyProps {
+    fn new(inner: impl AnyProps + 'static) -> Self {
+        Self {
+            inner: Box::new(inner),
+        }
+    }
+}
+
+impl Deref for BoxedAnyProps {
+    type Target = dyn AnyProps;
+
+    fn deref(&self) -> &Self::Target {
+        &*self.inner
+    }
+}
+
+impl Clone for BoxedAnyProps {
+    fn clone(&self) -> Self {
+        Self {
+            inner: self.inner.duplicate(),
+        }
+    }
+}
 
 /// A trait that essentially allows VComponentProps to be used generically
-///
-/// # Safety
-///
-/// This should not be implemented outside this module
 pub(crate) trait AnyProps {
-    fn render<'a>(&'a self, bump: &'a ScopeState) -> RenderReturn;
+    fn render<'a>(&'a self) -> RenderReturn;
     fn memoize(&self, other: &dyn AnyProps) -> bool;
+    fn duplicate(&self) -> Box<dyn AnyProps>;
 }
 
 pub(crate) struct VProps<P> {
@@ -36,7 +62,7 @@ impl<P: Clone> AnyProps for VProps<P> {
         (self.memo)(self, other)
     }
 
-    fn render(&self, cx: &ScopeState) -> RenderReturn {
+    fn render(&self) -> RenderReturn {
         let res = std::panic::catch_unwind(AssertUnwindSafe(move || {
             // Call the render function directly
             (self.render_fn)(self.props.clone())
@@ -52,4 +78,12 @@ impl<P: Clone> AnyProps for VProps<P> {
             }
         }
     }
+
+    fn duplicate(&self) -> Box<dyn AnyProps> {
+        Box::new(Self {
+            render_fn: self.render_fn,
+            memo: self.memo,
+            props: self.props.clone(),
+        })
+    }
 }

+ 2 - 72
packages/core/src/arena.rs

@@ -1,8 +1,8 @@
 use std::ptr::NonNull;
 
 use crate::{
-    innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom,
-    AttributeValue, DynamicNode, ScopeId,
+    innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode,
+    ScopeId,
 };
 
 /// An Element's unique identifier.
@@ -48,14 +48,6 @@ impl VirtualDom {
             std::mem::transmute::<NonNull<VNode>, _>(vnode.into())
         })));
 
-        // Set this id to be dropped when the scope is rerun
-        if let Some(scope) = self.runtime.current_scope_id() {
-            self.scopes[scope.0]
-                .element_refs_to_drop
-                .borrow_mut()
-                .push(new_id);
-        }
-
         new_id
     }
 
@@ -89,17 +81,6 @@ impl VirtualDom {
             id,
         });
 
-        // Remove all VNode ids from the scope
-        for id in self.scopes[id.0]
-            .element_refs_to_drop
-            .borrow_mut()
-            .drain(..)
-        {
-            self.element_refs.try_remove(id.0);
-        }
-
-        self.ensure_drop_safety(id);
-
         if recursive {
             if let Some(root) = self.scopes[id.0].try_root_node() {
                 if let RenderReturn::Ready(node) = root {
@@ -110,18 +91,6 @@ impl VirtualDom {
 
         let scope = &mut self.scopes[id.0];
 
-        // Drop all the hooks once the children are dropped
-        // this means we'll drop hooks bottom-up
-        scope.hooks.get_mut().clear();
-        {
-            let context = scope.context();
-
-            // Drop all the futures once the hooks are dropped
-            for task_id in context.spawned_tasks.borrow_mut().drain() {
-                context.tasks.remove(task_id);
-            }
-        }
-
         self.scopes.remove(id.0);
     }
 
@@ -139,45 +108,6 @@ impl VirtualDom {
             DynamicNode::Text(_) => {}
         });
     }
-
-    /// Descend through the tree, removing any borrowed props and listeners
-    pub(crate) fn ensure_drop_safety(&mut self, scope_id: ScopeId) {
-        let scope = &self.scopes[scope_id.0];
-
-        {
-            // Drop all element refs that could be invalidated when the component was rerun
-            let mut element_refs = self.scopes[scope_id.0].element_refs_to_drop.borrow_mut();
-            let element_refs_slab = &mut self.element_refs;
-            for element_ref in element_refs.drain(..) {
-                if let Some(element_ref) = element_refs_slab.get_mut(element_ref.0) {
-                    *element_ref = None;
-                }
-            }
-        }
-
-        // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
-        // run the hooks (which hold an &mut Reference)
-        // recursively call ensure_drop_safety on all children
-        let props = { scope.borrowed_props.borrow_mut().clone() };
-        for comp in props {
-            let comp = unsafe { &*comp };
-            match comp.scope.get() {
-                Some(child) if child != scope_id => self.ensure_drop_safety(child),
-                _ => (),
-            }
-        }
-        let scope = &self.scopes[scope_id.0];
-        scope.borrowed_props.borrow_mut().clear();
-
-        // Now that all the references are gone, we can safely drop our own references in our listeners.
-        let mut listeners = scope.attributes_to_drop_before_render.borrow_mut();
-        listeners.drain(..).for_each(|listener| {
-            let listener = unsafe { &*listener };
-            if let AttributeValue::Listener(l) = &listener.value {
-                _ = l.take();
-            }
-        });
-    }
 }
 
 impl ElementPath {

+ 3 - 4
packages/core/src/diff.rs

@@ -219,13 +219,12 @@ 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: Box<dyn AnyProps> = right.props.take().unwrap();
-        let new: Box<dyn AnyProps> = unsafe { std::mem::transmute(new) };
+        let new: &dyn AnyProps = right.props.as_ref();
 
         // 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 left.static_props && unsafe { old.as_ref().unwrap().memoize(new.as_ref()) } {
+        if old.memoize(new) {
             tracing::trace!(
                 "Memoized props for component {:#?} ({})",
                 scope_id,
@@ -235,7 +234,7 @@ 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 = Some(new);
+        self.scopes[scope_id.0].props = new;
 
         // Now run the component and diff it
         self.run_scope(scope_id);

+ 6 - 5
packages/core/src/nodes.rs

@@ -341,7 +341,7 @@ pub struct VComponent {
     /// It is possible that components get folded at compile time, so these shouldn't be really used as a key
     pub(crate) render_fn: *const (),
 
-    pub(crate) props: Box<dyn AnyProps>,
+    pub(crate) props: BoxedAnyProps,
 }
 
 impl<'a> VComponent {
@@ -362,7 +362,7 @@ impl<'a> std::fmt::Debug for VComponent {
 }
 
 /// An instance of some text, mounted to the DOM
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct VText {
     /// The actual text itself
     pub value: String,
@@ -436,7 +436,7 @@ pub enum TemplateAttribute {
 }
 
 /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct Attribute {
     /// The name of the attribute.
     pub name: &'static str,
@@ -483,6 +483,7 @@ 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),
@@ -497,10 +498,10 @@ pub enum AttributeValue {
     Bool(bool),
 
     /// A listener, like "onclick"
-    Listener(RefCell<Option<ListenerCb>>),
+    Listener(ListenerCb),
 
     /// An arbitrary value that implements PartialEq and is static
-    Any(RefCell<Option<Box<dyn AnyValue>>>),
+    Any(Box<dyn AnyValue>),
 
     /// A "none" value, resulting in the removal of an attribute from the dom
     None,

+ 13 - 35
packages/core/src/scope_arena.rs

@@ -24,15 +24,8 @@ impl VirtualDom {
             runtime: self.runtime.clone(),
             context_id: id,
 
-            props: Some(props),
-
-            render_cnt: Default::default(),
-            hooks: Default::default(),
-            hook_idx: Default::default(),
-
-            borrowed_props: Default::default(),
-            attributes_to_drop_before_render: Default::default(),
-            element_refs_to_drop: Default::default(),
+            props,
+            last_rendered_node: Default::default(),
         }));
 
         let context =
@@ -42,42 +35,30 @@ impl VirtualDom {
         scope
     }
 
-    pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
+    pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> RenderReturn {
         self.runtime.scope_stack.borrow_mut().push(scope_id);
-        // Cycle to the next frame and then reset it
-        // This breaks any latent references, invalidating every pointer referencing into it.
-        // Remove all the outdated listeners
-        self.ensure_drop_safety(scope_id);
 
         let new_nodes = unsafe {
             let scope = &self.scopes[scope_id.0];
-            scope.previous_frame().reset();
 
-            scope.context().suspended.set(false);
-
-            scope.hook_idx.set(0);
+            let context = scope.context();
+            context.suspended.set(false);
+            context.hook_index.set(0);
 
             // safety: due to how we traverse the tree, we know that the scope is not currently aliased
-            let props: &dyn AnyProps = scope.props.as_ref().unwrap().as_ref();
-            let props: &dyn AnyProps = std::mem::transmute(props);
+            let props: &dyn AnyProps = &*scope.props;
 
             let _span = tracing::trace_span!("render", scope = %scope.context().name);
-            props.render(scope)
+            props.render()
         };
 
-        let scope = &self.scopes[scope_id.0];
-
-        // We write on top of the previous frame and then make it the current by pushing the generation forward
-        let frame = scope.previous_frame();
+        let scope = &mut self.scopes[scope_id.0];
 
-        // set the new head of the bump frame
-        let allocated = &*frame.bump().alloc(new_nodes);
-        frame.node.set(allocated);
+        let context = scope.context();
 
         // And move the render generation forward by one
-        scope.render_cnt.set(scope.render_cnt.get() + 1);
+        context.render_count.set(context.render_count.get() + 1);
 
-        let context = scope.context();
         // remove this scope from dirty scopes
         self.dirty_scopes.remove(&DirtyScope {
             height: context.height,
@@ -85,18 +66,15 @@ impl VirtualDom {
         });
 
         if context.suspended.get() {
-            if matches!(allocated, RenderReturn::Aborted(_)) {
+            if matches!(new_nodes, RenderReturn::Aborted(_)) {
                 self.suspended_scopes.insert(context.id);
             }
         } else if !self.suspended_scopes.is_empty() {
             _ = self.suspended_scopes.remove(&context.id);
         }
 
-        // rebind the lifetime now that its stored internally
-        let result = unsafe { allocated };
-
         self.runtime.scope_stack.borrow_mut().pop();
 
-        result
+        new_nodes
     }
 }

+ 67 - 0
packages/core/src/scope_context.rs

@@ -22,10 +22,15 @@ pub(crate) struct ScopeContext {
     pub(crate) parent_id: Option<ScopeId>,
 
     pub(crate) height: u32,
+    pub(crate) render_count: Cell<usize>,
+
     pub(crate) suspended: Cell<bool>,
 
     pub(crate) shared_contexts: RefCell<Vec<Box<dyn Any>>>,
 
+    pub(crate) hooks: RefCell<Vec<Box<dyn Any>>>,
+    pub(crate) hook_index: Cell<usize>,
+
     pub(crate) tasks: Rc<Scheduler>,
     pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
 }
@@ -43,10 +48,13 @@ impl ScopeContext {
             id,
             parent_id,
             height,
+            render_count: Cell::new(0),
             suspended: Cell::new(false),
             shared_contexts: RefCell::new(vec![]),
             tasks,
             spawned_tasks: RefCell::new(FxHashSet::default()),
+            hooks: RefCell::new(vec![]),
+            hook_index: Cell::new(0),
         }
     }
 
@@ -244,6 +252,65 @@ impl ScopeContext {
         self.suspended.set(true);
         None
     }
+
+    /// Store a value between renders. The foundational hook for all other hooks.
+    ///
+    /// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.
+    ///
+    /// When the component is unmounted (removed from the UI), the value is dropped. This means you can return a custom type and provide cleanup code by implementing the [`Drop`] trait
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use dioxus_core::ScopeState;
+    ///
+    /// // prints a greeting on the initial render
+    /// pub fn use_hello_world(cx: &ScopeState) {
+    ///     cx.use_hook(|| println!("Hello, world!"));
+    /// }
+    /// ```
+    #[allow(clippy::mut_from_ref)]
+    pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
+        let cur_hook = self.hook_index.get();
+        let mut hooks = self.hooks.try_borrow_mut().expect("The hook list is already borrowed: This error is likely caused by trying to use a hook inside a hook which violates the rules of hooks.");
+
+        if cur_hook >= hooks.len() {
+            hooks.push(Box::new(initializer()));
+        }
+
+        hooks
+            .get(cur_hook)
+            .and_then(|inn| {
+                self.hook_index.set(cur_hook + 1);
+                let raw_ref: &mut dyn Any = inn.as_mut();
+                raw_ref.downcast_mut::<State>()
+            })
+            .expect(
+                r#"
+                Unable to retrieve the hook that was initialized at this index.
+                Consult the `rules of hooks` to understand how to use hooks properly.
+
+                You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
+                Functions prefixed with "use" should never be called conditionally.
+                "#,
+            )
+    }
+
+    /// Get the current render since the inception of this component
+    ///
+    /// This can be used as a helpful diagnostic when debugging hooks/renders, etc
+    pub fn generation(&self) -> usize {
+        self.render_count.get()
+    }
+}
+
+impl Drop for ScopeContext {
+    fn drop(&mut self) {
+        // Drop all spawned tasks
+        for id in self.spawned_tasks.borrow().iter() {
+            self.tasks.remove(*id);
+        }
+    }
 }
 
 /// Schedule an update for any component given its [`ScopeId`].

+ 14 - 81
packages/core/src/scopes.rs

@@ -1,7 +1,7 @@
 use crate::{
     any_props::AnyProps,
     any_props::VProps,
-    innerlude::{DynamicNode, EventHandler, VComponent, VNodeId, VText},
+    innerlude::{DynamicNode, EventHandler, VComponent, VText},
     nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
     runtime::Runtime,
     scope_context::ScopeContext,
@@ -9,7 +9,7 @@ use crate::{
 };
 use std::{
     any::Any,
-    cell::{Cell, Ref, RefCell, UnsafeCell},
+    cell::{Ref, RefCell},
     fmt::{Arguments, Debug},
     future::Future,
     rc::Rc,
@@ -47,16 +47,9 @@ pub struct ScopeState {
     pub(crate) runtime: Rc<Runtime>,
     pub(crate) context_id: ScopeId,
 
-    pub(crate) render_cnt: Cell<usize>,
+    pub(crate) last_rendered_node: Option<RenderReturn>,
 
-    pub(crate) hooks: RefCell<Vec<Box<UnsafeCell<dyn Any>>>>,
-    pub(crate) hook_idx: Cell<usize>,
-
-    pub(crate) borrowed_props: RefCell<Vec<*const VComponent>>,
-    pub(crate) element_refs_to_drop: RefCell<Vec<VNodeId>>,
-    pub(crate) attributes_to_drop_before_render: RefCell<Vec<*const Attribute>>,
-
-    pub(crate) props: Option<Box<dyn AnyProps>>,
+    pub(crate) props: Box<dyn AnyProps>,
 }
 
 impl Drop for ScopeState {
@@ -75,13 +68,6 @@ impl<'src> ScopeState {
         self.context().name
     }
 
-    /// Get the current render since the inception of this component
-    ///
-    /// This can be used as a helpful diagnostic when debugging hooks/renders, etc
-    pub fn generation(&self) -> usize {
-        self.render_cnt.get()
-    }
-
     /// Get a handle to the currently active head node arena for this Scope
     ///
     /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
@@ -98,15 +84,7 @@ impl<'src> ScopeState {
     ///
     /// Returns [`None`] if the tree has not been built yet.
     pub fn try_root_node(&self) -> Option<&RenderReturn> {
-        let ptr = self.current_frame().node.get();
-
-        if ptr.is_null() {
-            return None;
-        }
-
-        let r: &RenderReturn = unsafe { &*ptr };
-
-        unsafe { std::mem::transmute(r) }
+        self.last_rendered_node.as_ref()
     }
 
     /// Get the height of this Scope - IE the number of scopes above it.
@@ -354,21 +332,19 @@ impl<'src> ScopeState {
         &'src self,
         mut callback: impl FnMut(Event<T>) + 'src,
     ) -> AttributeValue {
-        AttributeValue::Listener(RefCell::new(Some(Box::new(
-            move |event: Event<dyn Any>| {
-                if let Ok(data) = event.data.downcast::<T>() {
-                    callback(Event {
-                        propagates: event.propagates,
-                        data,
-                    });
-                }
-            },
-        ))))
+        AttributeValue::Listener(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`]
     pub fn any_value<T: AnyValue>(&'src self, value: T) -> AttributeValue {
-        AttributeValue::Any(RefCell::new(Some(Box::new(value))))
+        AttributeValue::Any(Box::new(value))
     }
 
     /// Mark this component as suspended and then return None
@@ -377,47 +353,4 @@ impl<'src> ScopeState {
         cx.suspend();
         None
     }
-
-    /// Store a value between renders. The foundational hook for all other hooks.
-    ///
-    /// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.
-    ///
-    /// When the component is unmounted (removed from the UI), the value is dropped. This means you can return a custom type and provide cleanup code by implementing the [`Drop`] trait
-    ///
-    /// # Example
-    ///
-    /// ```
-    /// use dioxus_core::ScopeState;
-    ///
-    /// // prints a greeting on the initial render
-    /// pub fn use_hello_world(cx: &ScopeState) {
-    ///     cx.use_hook(|| println!("Hello, world!"));
-    /// }
-    /// ```
-    #[allow(clippy::mut_from_ref)]
-    pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
-        let cur_hook = self.hook_idx.get();
-        let mut hooks = self.hooks.try_borrow_mut().expect("The hook list is already borrowed: This error is likely caused by trying to use a hook inside a hook which violates the rules of hooks.");
-
-        if cur_hook >= hooks.len() {
-            hooks.push(Box::new(UnsafeCell::new(initializer())));
-        }
-
-        hooks
-            .get(cur_hook)
-            .and_then(|inn| {
-                self.hook_idx.set(cur_hook + 1);
-                let raw_ref = unsafe { &mut *inn.get() };
-                raw_ref.downcast_mut::<State>()
-            })
-            .expect(
-                r#"
-                Unable to retrieve the hook that was initialized at this index.
-                Consult the `rules of hooks` to understand how to use hooks properly.
-
-                You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
-                Functions prefixed with "use" should never be called conditionally.
-                "#,
-            )
-    }
 }

+ 3 - 3
packages/core/src/virtual_dom.rs

@@ -224,7 +224,7 @@ impl VirtualDom {
     /// ```
     ///
     /// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
-    pub fn new(app: fn() -> Element) -> Self {
+    pub fn new(app: fn(()) -> Element) -> Self {
         Self::new_with_props(app, ())
     }
 
@@ -258,7 +258,7 @@ impl VirtualDom {
     /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
     /// let mutations = dom.rebuild();
     /// ```
-    pub fn new_with_props<P: 'static>(root: fn(P) -> Element, root_props: P) -> Self {
+    pub fn new_with_props<P: Clone + 'static>(root: fn(P) -> Element, root_props: P) -> Self {
         let (tx, rx) = futures_channel::mpsc::unbounded();
         let scheduler = Scheduler::new(tx);
         let mut dom = Self {
@@ -450,7 +450,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() {
+                            if let Some(cb) = listener.as_deref_mut() {
                                 cb(uievent.clone());
                             }
                             self.runtime.scope_stack.borrow_mut().pop();