feat: it works with a new bump each time!!
This commit is contained in:
1 parent
e2e4d431e1
commit
78d9056e35
4 files changed
+121
-111
No files matched your search
+31
-40
@@ -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) };
|
||||
// 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();
|
||||
|
||||
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),
|
||||
);
|
||||
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 {
|
||||
|
||||
@@ -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
@@ -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();
|
||||
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();
|
||||
|
||||
let frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)];
|
||||
|
||||
let scope = self.bump.alloc(ScopeState {
|
||||
sender: self.sender.clone(),
|
||||
container,
|
||||
our_arena_idx: new_scope_id,
|
||||
parent_scope,
|
||||
self.scopes.borrow_mut().insert(
|
||||
new_scope_id,
|
||||
self.bump.alloc(ScopeState::new(
|
||||
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());
|
||||
}
|
||||
container,
|
||||
new_scope_id,
|
||||
self.sender.clone(),
|
||||
parent_scope,
|
||||
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
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in new issue
Block a user