Skip to content

Commit

Permalink
Merge pull request #182 from DioxusLabs/jk/debugging-diff
Browse files Browse the repository at this point in the history
fix: nodes being unmounted when used in highly nested contexts
  • Loading branch information
jkelleyrtp authored Feb 1, 2022
2 parents e02dfc3 + 94c1da8 commit 80d7929
Show file tree
Hide file tree
Showing 17 changed files with 934 additions and 890 deletions.
1,118 changes: 457 additions & 661 deletions packages/core/src/diff.rs

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion packages/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ pub(crate) mod util;
pub(crate) mod virtual_dom;

pub(crate) mod innerlude {
pub(crate) use crate::diff::*;
pub use crate::events::*;
pub use crate::lazynodes::*;
pub use crate::mutations::*;
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ impl<'a> Mutations<'a> {
self.edits.push(InsertBefore { n, root });
}

pub(crate) fn append_children(&mut self, n: u32) {
self.edits.push(AppendChildren { many: n });
}

// Remove Nodes from the dom
pub(crate) fn remove(&mut self, id: u64) {
self.edits.push(Remove { root: id });
Expand Down Expand Up @@ -217,6 +221,10 @@ impl<'a> Mutations<'a> {
let name = attribute.name;
self.edits.push(RemoveAttribute { name, root });
}

pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
self.dirty_scopes.insert(scope);
}
}

// refs are only assigned once
Expand Down
8 changes: 1 addition & 7 deletions packages/core/src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,6 @@ impl<'src> VNode<'src> {
}
}

pub(crate) fn children(&self) -> &[VNode<'src>] {
match &self {
VNode::Fragment(f) => f.children,
_ => &[],
}
}

// Create an "owned" version of the vnode.
pub fn decouple(&self) -> VNode<'src> {
match *self {
Expand All @@ -175,6 +168,7 @@ impl Debug for VNode<'_> {
.field("key", &el.key)
.field("attrs", &el.attributes)
.field("children", &el.children)
.field("id", &el.id)
.finish(),
VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
VNode::Placeholder(t) => write!(s, "VNode::VPlaceholder {{ id: {:?} }}", t.id),
Expand Down
117 changes: 54 additions & 63 deletions packages/core/src/scopes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use std::{
rc::Rc,
};

/// for traceability, we use the raw fn pointer to identify the function
/// we also get the component name, but that's not necessarily unique in the app
pub(crate) type ComponentPtr = *mut std::os::raw::c_void;

pub(crate) struct Heuristic {
Expand Down Expand Up @@ -94,7 +96,7 @@ impl ScopeArena {

// Get the height of the scope
let height = parent_scope
.map(|id| self.get_scope(id).map(|scope| scope.height))
.map(|id| self.get_scope(id).map(|scope| scope.height + 1))
.flatten()
.unwrap_or_default();

Expand All @@ -110,31 +112,62 @@ impl ScopeArena {
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.container = container;
scope.our_arena_idx = new_scope_id;
scope.parent_scope = parent_scope;
scope.height = height;
scope.fnptr = fc_ptr;
scope.props.get_mut().replace(vcomp);
scope.subtree.set(subtree);
scope.our_arena_idx = new_scope_id;
scope.container = container;
scope.frames[0].reset();
scope.frames[1].reset();
scope.shared_contexts.get_mut().clear();
scope.items.get_mut().listeners.clear();
scope.items.get_mut().borrowed_props.clear();
scope.hook_idx.set(0);
scope.hook_vals.get_mut().clear();

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,
self.bump.alloc(ScopeState {
container,
new_scope_id,
our_arena_idx: new_scope_id,
parent_scope,
vcomp,
self.tasks.clone(),
self.heuristics
.borrow()
.get(&fc_ptr)
.map(|h| (h.node_arena_size, h.hook_arena_size))
.unwrap_or_default(),
)),
height,
fnptr: fc_ptr,
props: RefCell::new(Some(vcomp)),
frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],

// todo: subtrees
subtree: Cell::new(0),
is_subtree_root: Cell::new(false),

generation: 0.into(),

tasks: self.tasks.clone(),
shared_contexts: Default::default(),

items: RefCell::new(SelfReferentialItems {
listeners: Default::default(),
borrowed_props: Default::default(),
}),

hook_arena: Bump::new(),
hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
hook_idx: Default::default(),
}),
);
}

Expand Down Expand Up @@ -189,8 +222,6 @@ impl ScopeArena {
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
/// be dropped.
pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
log::trace!("Ensuring drop safety for scope {:?}", scope_id);

if let Some(scope) = self.get_scope(scope_id) {
let mut items = scope.items.borrow_mut();

Expand All @@ -201,7 +232,6 @@ impl ScopeArena {
if let Some(scope_id) = comp.scope.get() {
self.ensure_drop_safety(scope_id);
}

drop(comp.props.take());
});

Expand All @@ -217,18 +247,18 @@ impl ScopeArena {
// 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
log::trace!("Running scope {:?}", id);
self.ensure_drop_safety(id);

// todo: we *know* that this is aliased by the contents of the scope itself
let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") };

log::trace!("running scope {:?} symbol: {:?}", id, scope.fnptr);

// Safety:
// - We dropped the listeners, so no more &mut T can be used while these are held
// - All children nodes that rely on &mut T are replaced with a new reference
scope.hook_idx.set(0);

// book keeping to ensure safety around the borrowed data
{
// Safety:
// - We've dropped all references to the wip bump frame with "ensure_drop_safety"
Expand All @@ -241,8 +271,6 @@ impl ScopeArena {
debug_assert!(items.borrowed_props.is_empty());
}

// safety: this is definitely not dropped

/*
If the component returns None, then we fill in a placeholder node. This will wipe what was there.
An alternate approach is to leave the Real Dom the same, but that can lead to safety issues and a lot more checks.
Expand Down Expand Up @@ -284,14 +312,13 @@ impl ScopeArena {

while let Some(id) = cur_el.take() {
if let Some(el) = nodes.get(id.0) {
log::trace!("Found valid receiver element");

let real_el = unsafe { &**el };
log::debug!("looking for listener on {:?}", real_el);

if let VNode::Element(real_el) = real_el {
for listener in real_el.listeners.borrow().iter() {
if listener.event == event.name {
log::trace!("Found valid receiver event");

log::debug!("calling listener {:?}", listener.event);
if state.canceled.get() {
// stop bubbling if canceled
break;
Expand Down Expand Up @@ -421,6 +448,7 @@ pub struct ScopeState {
pub(crate) container: ElementId,
pub(crate) our_arena_idx: ScopeId,
pub(crate) height: u32,
pub(crate) fnptr: ComponentPtr,

// todo: subtrees
pub(crate) is_subtree_root: Cell<bool>,
Expand Down Expand Up @@ -449,43 +477,6 @@ pub struct SelfReferentialItems<'a> {

// Public methods exposed to libraries and components
impl ScopeState {
fn new(
height: u32,
container: ElementId,
our_arena_idx: ScopeId,
parent_scope: Option<*mut ScopeState>,
vcomp: Box<dyn AnyProps>,
tasks: Rc<TaskQueue>,
(node_capacity, hook_capacity): (usize, usize),
) -> Self {
ScopeState {
container,
our_arena_idx,
parent_scope,
height,
props: RefCell::new(Some(vcomp)),
frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],

// todo: subtrees
subtree: Cell::new(0),
is_subtree_root: Cell::new(false),

generation: 0.into(),

tasks,
shared_contexts: Default::default(),

items: RefCell::new(SelfReferentialItems {
listeners: Default::default(),
borrowed_props: 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
Expand Down
Loading

0 comments on commit 80d7929

Please sign in to comment.