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:
|
//! More info on how to improve this diffing algorithm:
|
||||||
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
||||||
|
|
||||||
|
use std::borrow::BorrowMut;
|
||||||
|
|
||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
use fxhash::{FxHashMap, FxHashSet};
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
@@ -427,10 +429,11 @@ impl<'bump> DiffState<'bump> {
|
|||||||
let parent_idx = self.stack.current_scope().unwrap();
|
let parent_idx = self.stack.current_scope().unwrap();
|
||||||
|
|
||||||
// Insert a new scope into our component list
|
// 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(
|
let new_idx = self.scopes.new_with_key(
|
||||||
vcomponent.user_fc,
|
vcomponent.user_fc,
|
||||||
caller,
|
props,
|
||||||
Some(parent_idx),
|
Some(parent_idx),
|
||||||
self.stack.element_stack.last().copied().unwrap(),
|
self.stack.element_stack.last().copied().unwrap(),
|
||||||
0,
|
0,
|
||||||
@@ -441,9 +444,7 @@ impl<'bump> DiffState<'bump> {
|
|||||||
|
|
||||||
match vcomponent.can_memoize {
|
match vcomponent.can_memoize {
|
||||||
true => {
|
true => {
|
||||||
// promote the props onto the heap
|
// todo: implement promotion logic. save us from boxing props that we don't need
|
||||||
// when we diff these nodes, we'll want to move it over
|
|
||||||
vcomponent.props.promote();
|
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
// track this component internally so we know the right drop order
|
// track this component internally so we know the right drop order
|
||||||
@@ -709,43 +710,30 @@ impl<'bump> DiffState<'bump> {
|
|||||||
.get_scope(scope_addr)
|
.get_scope(scope_addr)
|
||||||
.unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
|
.unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
|
||||||
|
|
||||||
/*
|
// take the new props out regardless
|
||||||
old.props is the previous vcomponent
|
// when memoizing, push to the existing scope if memoization happens
|
||||||
existing_props is the current ScopeState's
|
let new_props = new.props.borrow_mut().take().unwrap();
|
||||||
*/
|
|
||||||
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 {
|
let should_run = {
|
||||||
// release the old props since they will be overwritten next frame
|
if old.can_memoize {
|
||||||
// existing_props.release();
|
let props_are_the_same = unsafe {
|
||||||
|
scope
|
||||||
// move the old props onto the heap
|
.props
|
||||||
// todo: maybe we can reuse the old props box?
|
.borrow()
|
||||||
new.props.promote();
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
scope
|
.memoize(new_props.as_ref())
|
||||||
.render
|
};
|
||||||
.set(unsafe { std::mem::transmute(new.props as *const dyn AnyProps) });
|
!props_are_the_same || self.force_diff
|
||||||
|
|
||||||
// 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),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// release the old props since they will be overwritten next frame
|
true
|
||||||
if existing_props.as_ptr() != old.props.as_ptr() {
|
|
||||||
old.props.release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
};
|
||||||
scope
|
|
||||||
.render
|
if should_run {
|
||||||
.set(unsafe { std::mem::transmute(new.props as *const dyn AnyProps) });
|
let _old_props = scope
|
||||||
|
.props
|
||||||
|
.replace(unsafe { std::mem::transmute(Some(new_props)) });
|
||||||
|
|
||||||
// this should auto drop the previous props
|
// this should auto drop the previous props
|
||||||
self.scopes.run_scope(scope_addr);
|
self.scopes.run_scope(scope_addr);
|
||||||
@@ -753,7 +741,10 @@ impl<'bump> DiffState<'bump> {
|
|||||||
self.scopes.wip_head(scope_addr),
|
self.scopes.wip_head(scope_addr),
|
||||||
self.scopes.fin_head(scope_addr),
|
self.scopes.fin_head(scope_addr),
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
|
// memoization has taken place
|
||||||
|
drop(new_props);
|
||||||
|
};
|
||||||
|
|
||||||
self.stack.scope_stack.pop();
|
self.stack.scope_stack.pop();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ pub struct VComponent<'src> {
|
|||||||
pub scope: Cell<Option<ScopeId>>,
|
pub scope: Cell<Option<ScopeId>>,
|
||||||
pub can_memoize: bool,
|
pub can_memoize: bool,
|
||||||
pub user_fc: *const (),
|
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> {
|
pub(crate) struct VComponentProps<P> {
|
||||||
@@ -587,7 +587,7 @@ impl<'a> NodeFactory<'a> {
|
|||||||
scope: Default::default(),
|
scope: Default::default(),
|
||||||
can_memoize: P::IS_STATIC,
|
can_memoize: P::IS_STATIC,
|
||||||
user_fc: component as *const (),
|
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)),
|
// local_props: RefCell::new(Some(props)),
|
||||||
// heap_props: RefCell::new(None),
|
// heap_props: RefCell::new(None),
|
||||||
props,
|
props,
|
||||||
|
|||||||
+85
-63
@@ -91,15 +91,18 @@ impl ScopeArena {
|
|||||||
container: ElementId,
|
container: ElementId,
|
||||||
subtree: u32,
|
subtree: u32,
|
||||||
) -> ScopeId {
|
) -> ScopeId {
|
||||||
|
// Increment the ScopeId system. ScopeIDs are never reused
|
||||||
let new_scope_id = ScopeId(self.scope_counter.get());
|
let new_scope_id = ScopeId(self.scope_counter.get());
|
||||||
self.scope_counter.set(self.scope_counter.get() + 1);
|
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
|
let height = parent_scope
|
||||||
.as_ref()
|
.map(|id| self.get_scope(id).map(|scope| scope.height))
|
||||||
.map(|f| unsafe { &**f })
|
.flatten()
|
||||||
.map(|f| f.height + 1)
|
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let parent_scope = parent_scope.map(|f| self.get_scope_raw(f)).flatten();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This scopearena aggressively reuse old scopes when possible.
|
This scopearena aggressively reuse old scopes when possible.
|
||||||
We try to minimize the new allocations for props/arenas.
|
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.
|
However, this will probably lead to some sort of fragmentation.
|
||||||
I'm not exactly sure how to improve this today.
|
I'm not exactly sure how to improve this today.
|
||||||
*/
|
*/
|
||||||
match self.free_scopes.borrow_mut().pop() {
|
if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
|
||||||
// No free scope, make a new scope
|
// reuse the old scope
|
||||||
None => {
|
let scope = unsafe { &mut *old_scope };
|
||||||
let (node_capacity, hook_capacity) = self
|
scope.props.get_mut().replace(vcomp);
|
||||||
.heuristics
|
scope.parent_scope = parent_scope;
|
||||||
.borrow()
|
scope.height = height;
|
||||||
.get(&fc_ptr)
|
scope.subtree.set(subtree);
|
||||||
.map(|h| (h.node_arena_size, h.hook_arena_size))
|
scope.our_arena_idx = new_scope_id;
|
||||||
.unwrap_or_default();
|
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)];
|
self.scopes.borrow_mut().insert(
|
||||||
|
new_scope_id,
|
||||||
let scope = self.bump.alloc(ScopeState {
|
self.bump.alloc(ScopeState::new(
|
||||||
sender: self.sender.clone(),
|
|
||||||
container,
|
|
||||||
our_arena_idx: new_scope_id,
|
|
||||||
parent_scope,
|
|
||||||
height,
|
height,
|
||||||
frames,
|
container,
|
||||||
subtree: Cell::new(subtree),
|
new_scope_id,
|
||||||
is_subtree_root: Cell::new(false),
|
self.sender.clone(),
|
||||||
|
parent_scope,
|
||||||
props: vcomp,
|
vcomp,
|
||||||
generation: 0.into(),
|
node_capacity,
|
||||||
|
hook_capacity,
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new_scope_id
|
new_scope_id
|
||||||
@@ -176,9 +158,6 @@ impl ScopeArena {
|
|||||||
let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
|
let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
|
||||||
scope.reset();
|
scope.reset();
|
||||||
|
|
||||||
todo!("drop");
|
|
||||||
// unsafe { &*scope.render.get() }.release();
|
|
||||||
|
|
||||||
self.free_scopes.borrow_mut().push(scope);
|
self.free_scopes.borrow_mut().push(scope);
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
@@ -230,8 +209,7 @@ impl ScopeArena {
|
|||||||
|
|
||||||
self.ensure_drop_safety(scope_id);
|
self.ensure_drop_safety(scope_id);
|
||||||
|
|
||||||
let g = comp.props.take();
|
drop(comp.props.take());
|
||||||
// comp.props.release();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
// 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.
|
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.
|
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() {
|
if !scope.items.borrow().tasks.is_empty() {
|
||||||
self.pending_futures.borrow_mut().insert(id);
|
self.pending_futures.borrow_mut().insert(id);
|
||||||
}
|
}
|
||||||
@@ -425,7 +406,7 @@ pub struct ScopeState {
|
|||||||
pub(crate) is_subtree_root: Cell<bool>,
|
pub(crate) is_subtree_root: Cell<bool>,
|
||||||
pub(crate) subtree: Cell<u32>,
|
pub(crate) subtree: Cell<u32>,
|
||||||
|
|
||||||
pub(crate) props: Option<RefCell<Box<dyn AnyProps>>>,
|
pub(crate) props: RefCell<Option<Box<dyn AnyProps>>>,
|
||||||
|
|
||||||
// nodes, items
|
// nodes, items
|
||||||
pub(crate) frames: [BumpFrame; 2],
|
pub(crate) frames: [BumpFrame; 2],
|
||||||
@@ -449,6 +430,47 @@ pub struct SelfReferentialItems<'a> {
|
|||||||
|
|
||||||
// Public methods exposed to libraries and components
|
// Public methods exposed to libraries and components
|
||||||
impl ScopeState {
|
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.
|
/// 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
|
/// 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 fxhash::FxHashSet;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{any::Any, collections::VecDeque, iter::FromIterator, pin::Pin, sync::Arc, task::Poll};
|
||||||
any::Any, cell::RefCell, collections::VecDeque, iter::FromIterator, pin::Pin, sync::Arc,
|
|
||||||
task::Poll,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A virtual node s ystem that progresses user events and diffs UI trees.
|
/// A virtual node s ystem that progresses user events and diffs UI trees.
|
||||||
///
|
///
|
||||||
@@ -207,8 +204,8 @@ impl VirtualDom {
|
|||||||
root as *const _,
|
root as *const _,
|
||||||
Box::new(VComponentProps {
|
Box::new(VComponentProps {
|
||||||
props: root_props,
|
props: root_props,
|
||||||
memo: |_a, _b| false,
|
memo: |_a, _b| unreachable!("memo on root will neve be run"),
|
||||||
render_fn: unsafe { std::mem::transmute(root) },
|
render_fn: root,
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
ElementId(0),
|
ElementId(0),
|
||||||
|
|||||||
Reference in new issue
Block a user