Преглед изворни кода

feat: it works with a new bump each time!!

Jonathan Kelley пре 3 година
родитељ
комит
78d9056
4 измењених фајлова са 122 додато и 112 уклоњено
  1. 32 41
      packages/core/src/diff.rs
  2. 2 2
      packages/core/src/nodes.rs
  3. 85 63
      packages/core/src/scopes.rs
  4. 3 6
      packages/core/src/virtual_dom.rs

+ 32 - 41
packages/core/src/diff.rs

@@ -88,6 +88,8 @@
 //! More info on how to improve this diffing algorithm:
 //!  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
 
+use std::borrow::BorrowMut;
+
 use crate::innerlude::*;
 use fxhash::{FxHashMap, FxHashSet};
 use smallvec::{smallvec, SmallVec};
@@ -427,10 +429,11 @@ impl<'bump> DiffState<'bump> {
         let parent_idx = self.stack.current_scope().unwrap();
 
         // Insert a new scope into our component list
-        let caller = unsafe { std::mem::transmute(vcomponent.props as *const _) };
+        let props: Box<dyn AnyProps + 'bump> = vcomponent.props.borrow_mut().take().unwrap();
+        let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) };
         let new_idx = self.scopes.new_with_key(
             vcomponent.user_fc,
-            caller,
+            props,
             Some(parent_idx),
             self.stack.element_stack.last().copied().unwrap(),
             0,
@@ -441,9 +444,7 @@ impl<'bump> DiffState<'bump> {
 
         match vcomponent.can_memoize {
             true => {
-                // promote the props onto the heap
-                // when we diff these nodes, we'll want to move it over
-                vcomponent.props.promote();
+                // todo: implement promotion logic. save us from boxing props that we don't need
             }
             false => {
                 // track this component internally so we know the right drop order
@@ -709,43 +710,30 @@ impl<'bump> DiffState<'bump> {
                 .get_scope(scope_addr)
                 .unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
 
-            /*
-            old.props is the previous vcomponent
-            existing_props is the current ScopeState's
-            */
-            let existing_props = unsafe { &*scope.render.get() };
-            if old.can_memoize {
-                // let props_are_the_same = unsafe { existing_props.memoize(new.props) };
-
-                if !props_are_the_same || self.force_diff {
-                    // release the old props since they will be overwritten next frame
-                    // existing_props.release();
-
-                    // move the old props onto the heap
-                    // todo: maybe we can reuse the old props box?
-                    new.props.promote();
-
-                    scope
-                        .render
-                        .set(unsafe { std::mem::transmute(new.props as *const dyn AnyProps) });
-
-                    // this should auto drop the previous props
-                    self.scopes.run_scope(scope_addr);
-
-                    self.diff_node(
-                        self.scopes.wip_head(scope_addr),
-                        self.scopes.fin_head(scope_addr),
-                    );
+            // take the new props out regardless
+            // when memoizing, push to the existing scope if memoization happens
+            let new_props = new.props.borrow_mut().take().unwrap();
+
+            let should_run = {
+                if old.can_memoize {
+                    let props_are_the_same = unsafe {
+                        scope
+                            .props
+                            .borrow()
+                            .as_ref()
+                            .unwrap()
+                            .memoize(new_props.as_ref())
+                    };
+                    !props_are_the_same || self.force_diff
                 } else {
-                    // release the old props since they will be overwritten next frame
-                    if existing_props.as_ptr() != old.props.as_ptr() {
-                        old.props.release();
-                    }
+                    true
                 }
-            } else {
-                scope
-                    .render
-                    .set(unsafe { std::mem::transmute(new.props as *const dyn AnyProps) });
+            };
+
+            if should_run {
+                let _old_props = scope
+                    .props
+                    .replace(unsafe { std::mem::transmute(Some(new_props)) });
 
                 // this should auto drop the previous props
                 self.scopes.run_scope(scope_addr);
@@ -753,7 +741,10 @@ impl<'bump> DiffState<'bump> {
                     self.scopes.wip_head(scope_addr),
                     self.scopes.fin_head(scope_addr),
                 );
-            }
+            } else {
+                // memoization has taken place
+                drop(new_props);
+            };
 
             self.stack.scope_stack.pop();
         } else {

+ 2 - 2
packages/core/src/nodes.rs

@@ -359,7 +359,7 @@ pub struct VComponent<'src> {
     pub scope: Cell<Option<ScopeId>>,
     pub can_memoize: bool,
     pub user_fc: *const (),
-    pub props: Option<RefCell<Box<dyn AnyProps + 'src>>>,
+    pub props: RefCell<Option<Box<dyn AnyProps + 'src>>>,
 }
 
 pub(crate) struct VComponentProps<P> {
@@ -587,7 +587,7 @@ impl<'a> NodeFactory<'a> {
             scope: Default::default(),
             can_memoize: P::IS_STATIC,
             user_fc: component as *const (),
-            props: Some(RefCell::new(Box::new(VComponentProps {
+            props: RefCell::new(Some(Box::new(VComponentProps {
                 // local_props: RefCell::new(Some(props)),
                 // heap_props: RefCell::new(None),
                 props,

+ 85 - 63
packages/core/src/scopes.rs

@@ -91,15 +91,18 @@ impl ScopeArena {
         container: ElementId,
         subtree: u32,
     ) -> ScopeId {
+        // Increment the ScopeId system. ScopeIDs are never reused
         let new_scope_id = ScopeId(self.scope_counter.get());
         self.scope_counter.set(self.scope_counter.get() + 1);
-        let parent_scope = parent_scope.map(|f| self.get_scope_raw(f)).flatten();
+
+        // Get the height of the scope
         let height = parent_scope
-            .as_ref()
-            .map(|f| unsafe { &**f })
-            .map(|f| f.height + 1)
+            .map(|id| self.get_scope(id).map(|scope| scope.height))
+            .flatten()
             .unwrap_or_default();
 
+        let parent_scope = parent_scope.map(|f| self.get_scope_raw(f)).flatten();
+
         /*
         This scopearena aggressively reuse old scopes when possible.
         We try to minimize the new allocations for props/arenas.
@@ -107,60 +110,39 @@ impl ScopeArena {
         However, this will probably lead to some sort of fragmentation.
         I'm not exactly sure how to improve this today.
         */
-        match self.free_scopes.borrow_mut().pop() {
-            // No free scope, make a new scope
-            None => {
-                let (node_capacity, hook_capacity) = self
-                    .heuristics
-                    .borrow()
-                    .get(&fc_ptr)
-                    .map(|h| (h.node_arena_size, h.hook_arena_size))
-                    .unwrap_or_default();
-
-                let frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)];
-
-                let scope = self.bump.alloc(ScopeState {
-                    sender: self.sender.clone(),
+        if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
+            // reuse the old scope
+            let scope = unsafe { &mut *old_scope };
+            scope.props.get_mut().replace(vcomp);
+            scope.parent_scope = parent_scope;
+            scope.height = height;
+            scope.subtree.set(subtree);
+            scope.our_arena_idx = new_scope_id;
+            scope.container = container;
+            let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
+            debug_assert!(any_item.is_none());
+        } else {
+            // else create a new scope
+            let (node_capacity, hook_capacity) = self
+                .heuristics
+                .borrow()
+                .get(&fc_ptr)
+                .map(|h| (h.node_arena_size, h.hook_arena_size))
+                .unwrap_or_default();
+
+            self.scopes.borrow_mut().insert(
+                new_scope_id,
+                self.bump.alloc(ScopeState::new(
+                    height,
                     container,
-                    our_arena_idx: new_scope_id,
+                    new_scope_id,
+                    self.sender.clone(),
                     parent_scope,
-                    height,
-                    frames,
-                    subtree: Cell::new(subtree),
-                    is_subtree_root: Cell::new(false),
-
-                    props: vcomp,
-                    generation: 0.into(),
-
-                    shared_contexts: Default::default(),
-
-                    items: RefCell::new(SelfReferentialItems {
-                        listeners: Default::default(),
-                        borrowed_props: Default::default(),
-                        tasks: Default::default(),
-                    }),
-
-                    hook_arena: Bump::new(),
-                    hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
-                    hook_idx: Default::default(),
-                });
-
-                let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
-                debug_assert!(any_item.is_none());
-            }
-
-            // Reuse a free scope
-            Some(old_scope) => {
-                let scope = unsafe { &mut *old_scope };
-                scope.props.set(vcomp);
-                scope.parent_scope = parent_scope;
-                scope.height = height;
-                scope.subtree = Cell::new(subtree);
-                scope.our_arena_idx = new_scope_id;
-                scope.container = container;
-                let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
-                debug_assert!(any_item.is_none());
-            }
+                    vcomp,
+                    node_capacity,
+                    hook_capacity,
+                )),
+            );
         }
 
         new_scope_id
@@ -176,9 +158,6 @@ impl ScopeArena {
         let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
         scope.reset();
 
-        todo!("drop");
-        // unsafe { &*scope.render.get() }.release();
-
         self.free_scopes.borrow_mut().push(scope);
 
         Some(())
@@ -230,8 +209,7 @@ impl ScopeArena {
 
                 self.ensure_drop_safety(scope_id);
 
-                let g = comp.props.take();
-                // comp.props.release();
+                drop(comp.props.take());
             });
 
             // Now that all the references are gone, we can safely drop our own references in our listeners.
@@ -285,7 +263,10 @@ impl ScopeArena {
         Also, the way we implement hooks allows us to cut rendering short before the next hook is recalled.
         I'm not sure if React lets you abort the component early, but we let you do that.
         */
-        if let Some(node) = scope.props.render(scope) {
+
+        let props = scope.props.borrow();
+        let render = props.as_ref().unwrap();
+        if let Some(node) = render.render(scope) {
             if !scope.items.borrow().tasks.is_empty() {
                 self.pending_futures.borrow_mut().insert(id);
             }
@@ -425,7 +406,7 @@ pub struct ScopeState {
     pub(crate) is_subtree_root: Cell<bool>,
     pub(crate) subtree: Cell<u32>,
 
-    pub(crate) props: Option<RefCell<Box<dyn AnyProps>>>,
+    pub(crate) props: RefCell<Option<Box<dyn AnyProps>>>,
 
     // nodes, items
     pub(crate) frames: [BumpFrame; 2],
@@ -449,6 +430,47 @@ pub struct SelfReferentialItems<'a> {
 
 // Public methods exposed to libraries and components
 impl ScopeState {
+    fn new(
+        height: u32,
+        container: ElementId,
+        our_arena_idx: ScopeId,
+        sender: UnboundedSender<SchedulerMsg>,
+        parent_scope: Option<*mut ScopeState>,
+        vcomp: Box<dyn AnyProps>,
+        node_capacity: usize,
+        hook_capacity: usize,
+    ) -> Self {
+        let frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)];
+
+        ScopeState {
+            sender,
+            container,
+            our_arena_idx,
+            parent_scope,
+            props: RefCell::new(Some(vcomp)),
+            height,
+            frames,
+
+            // todo: subtrees
+            subtree: Cell::new(0),
+            is_subtree_root: Cell::new(false),
+
+            generation: 0.into(),
+
+            shared_contexts: Default::default(),
+
+            items: RefCell::new(SelfReferentialItems {
+                listeners: Default::default(),
+                borrowed_props: Default::default(),
+                tasks: Default::default(),
+            }),
+
+            hook_arena: Bump::new(),
+            hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
+            hook_idx: Default::default(),
+        }
+    }
+
     /// Get the subtree ID that this scope belongs to.
     ///
     /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route

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

@@ -8,10 +8,7 @@ use futures_util::{Future, StreamExt};
 use fxhash::FxHashSet;
 use indexmap::IndexSet;
 use smallvec::SmallVec;
-use std::{
-    any::Any, cell::RefCell, collections::VecDeque, iter::FromIterator, pin::Pin, sync::Arc,
-    task::Poll,
-};
+use std::{any::Any, collections::VecDeque, iter::FromIterator, pin::Pin, sync::Arc, task::Poll};
 
 /// A virtual node s ystem that progresses user events and diffs UI trees.
 ///
@@ -207,8 +204,8 @@ impl VirtualDom {
             root as *const _,
             Box::new(VComponentProps {
                 props: root_props,
-                memo: |_a, _b| false,
-                render_fn: unsafe { std::mem::transmute(root) },
+                memo: |_a, _b| unreachable!("memo on root will neve be run"),
+                render_fn: root,
             }),
             None,
             ElementId(0),