From c10c1f418bf21e0e54b53e2babeb37c672c06901 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 11 Nov 2021 11:49:07 -0500 Subject: [PATCH] feat: wire up linked nodes --- packages/core/Cargo.toml | 1 + packages/core/src/diff.rs | 130 +++++++++++----------- packages/core/src/nodes.rs | 1 + packages/core/src/scopearena.rs | 133 +++++++++++------------ packages/core/src/virtual_dom.rs | 12 ++- packages/core/tests/lifecycle.rs | 179 +++++++++++++++++++++++++++++++ 6 files changed, 325 insertions(+), 131 deletions(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 2bffae33d..94a23c6fd 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -47,6 +47,7 @@ fern = { version = "0.6.0", features = ["colored"] } rand = { version = "0.8.4", features = ["small_rng"] } simple_logger = "1.13.0" dioxus-core-macro = { path = "../core-macro", version = "0.1.2" } +dioxus-hooks = { path = "../hooks" } # async-std = { version = "1.9.0", features = ["attributes"] } # criterion = "0.3.5" diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index e87157c89..3d72eab27 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -240,6 +240,8 @@ impl<'bump> DiffState<'bump> { pub fn diff_scope(&mut self, id: &ScopeId) { let (old, new) = (self.scopes.wip_head(id), self.scopes.fin_head(id)); self.stack.push(DiffInstruction::Diff { old, new }); + self.stack.scope_stack.push(*id); + self.stack.push_nodes_created(0); self.work(|| false); } @@ -281,13 +283,9 @@ impl<'bump> DiffState<'bump> { } VNode::Linked(linked) => { - todo!("load linked"); - 0 - // let num_on_stack = linked.children.iter().map(|child| { - // self.push_all_nodes( child) - // }).sum(); - // self.mutations.push_root(node.mounted_id()); - // num_on_stack + 1 + let node = unsafe { &*linked.node }; + let node: &VNode = unsafe { std::mem::transmute(node) }; + self.push_all_nodes(node) } VNode::Fragment(_) | VNode::Component(_) => { @@ -321,16 +319,7 @@ impl<'bump> DiffState<'bump> { } MountType::Replace { old } => { - // todo: a bug here where we remove a node that is alread being replaced - if let Some(old_id) = old.try_mounted_id() { - self.mutations.replace_with(old_id, nodes_created as u32); - self.remove_nodes(Some(old), false); - } else { - if let Some(id) = self.find_first_element_id(old) { - self.mutations.replace_with(id, nodes_created as u32); - } - self.remove_nodes(Some(old), false); - } + self.replace_node(old, nodes_created); } MountType::Append => { @@ -386,8 +375,10 @@ impl<'bump> DiffState<'bump> { fn create_anchor_node(&mut self, anchor: &'bump VAnchor, node: &'bump VNode<'bump>) { let real_id = self.scopes.reserve_node(node); + self.mutations.create_placeholder(real_id); anchor.dom_id.set(Some(real_id)); + self.stack.add_child_count(1); } @@ -488,11 +479,13 @@ impl<'bump> DiffState<'bump> { } fn create_linked_node(&mut self, link: &'bump NodeLink) { - if let Some(cur_scope) = self.stack.current_scope() { - link.scope_id.set(Some(cur_scope)); - let node: &'bump VNode<'static> = unsafe { &*link.node }; - self.create_node(unsafe { std::mem::transmute(node) }); + if link.scope_id.get().is_none() { + if let Some(cur_scope) = self.stack.current_scope() { + link.scope_id.set(Some(cur_scope)); + } } + let node: &'bump VNode<'static> = unsafe { &*link.node }; + self.create_node(unsafe { std::mem::transmute(node) }); } // ================================= @@ -692,9 +685,16 @@ impl<'bump> DiffState<'bump> { } fn diff_linked_nodes(&mut self, old: &'bump NodeLink, new: &'bump NodeLink) { - todo!(); - // new.dom_id.set(old.dom_id.get()); - // self.attach_linked_node_to_scope( new); + if !std::ptr::eq(old.node, new.node) { + // if the ptrs are the same then theyr're the same + let old: &VNode = unsafe { std::mem::transmute(&*old.node) }; + let new: &VNode = unsafe { std::mem::transmute(&*new.node) }; + self.diff_node(old, new); + } + + if new.scope_id.get().is_none() { + todo!("attach the link to the scope - when children are not created"); + } } // ============================================= @@ -735,7 +735,14 @@ impl<'bump> DiffState<'bump> { .create_children(new, MountType::Replace { old: &old[0] }); } (_, [VNode::Anchor(_)]) => { - self.replace_and_create_many_with_one(old, &new[0]); + let new: &'bump VNode<'bump> = &new[0]; + if let Some(first_old) = old.get(0) { + self.remove_nodes(&old[1..], true); + self.stack + .create_node(new, MountType::Replace { old: first_old }); + } else { + self.stack.create_node(new, MountType::Append {}); + } } _ => { let new_is_keyed = new[0].key().is_some(); @@ -1032,7 +1039,14 @@ impl<'bump> DiffState<'bump> { log::debug!("old_key_to_old_index, {:#?}", old_key_to_old_index); log::debug!("new_index_to_old_index, {:#?}", new_index_to_old_index); log::debug!("shared_keys, {:#?}", shared_keys); - self.replace_and_create_many_with_many(old, new); + + if let Some(first_old) = old.get(0) { + self.remove_nodes(&old[1..], true); + self.stack + .create_children(new, MountType::Replace { old: first_old }) + } else { + self.stack.create_children(new, MountType::Append {}); + } return; } @@ -1167,11 +1181,11 @@ impl<'bump> DiffState<'bump> { } VNode::Component(el) => { let scope_id = el.associated_scope.get().unwrap(); - // let scope = self.scopes.get_scope(&scope_id).unwrap(); search_node = Some(self.scopes.root_node(&scope_id)); } VNode::Linked(link) => { - todo!("linked") + let node = unsafe { std::mem::transmute(&*link.node) }; + search_node = Some(node); } VNode::Text(t) => break t.dom_id.get(), VNode::Element(t) => break t.dom_id.get(), @@ -1181,17 +1195,27 @@ impl<'bump> DiffState<'bump> { } } - fn replace_and_create_many_with_one( - &mut self, - old: &'bump [VNode<'bump>], - new: &'bump VNode<'bump>, - ) { - if let Some(first_old) = old.get(0) { - self.remove_nodes(&old[1..], true); - self.stack - .create_node(new, MountType::Replace { old: first_old }); - } else { - self.stack.create_node(new, MountType::Append {}); + fn replace_node(&mut self, old: &'bump VNode<'bump>, nodes_created: usize) { + match old { + VNode::Text(_) | VNode::Element(_) | VNode::Anchor(_) | VNode::Suspended(_) => { + let id = old.try_mounted_id().expect(&format!("broke on {:?}", old)); + self.mutations.replace_with(id, nodes_created as u32); + } + + VNode::Fragment(f) => { + self.replace_node(&f.children[0], nodes_created); + self.remove_nodes(f.children.iter().skip(1), true); + } + + VNode::Component(c) => { + let node = self.scopes.fin_head(&c.associated_scope.get().unwrap()); + self.replace_node(node, nodes_created); + } + + VNode::Linked(l) => { + let node: &'bump VNode<'bump> = unsafe { std::mem::transmute(&*l.node) }; + self.replace_node(node, nodes_created); + } } } @@ -1206,11 +1230,13 @@ impl<'bump> DiffState<'bump> { for node in nodes { match node { VNode::Text(t) => { - let id = t.dom_id.get().unwrap(); - self.scopes.collect_garbage(id); + // this check exists because our null node will be removed but does not have an ID + if let Some(id) = t.dom_id.get() { + self.scopes.collect_garbage(id); - if gen_muts { - self.mutations.remove(id.as_u64()); + if gen_muts { + self.mutations.remove(id.as_u64()); + } } } VNode::Suspended(s) => { @@ -1259,24 +1285,6 @@ impl<'bump> DiffState<'bump> { } } - /// Remove all the old nodes and replace them with newly created new nodes. - /// - /// The new nodes *will* be created - don't create them yourself! - fn replace_and_create_many_with_many( - &mut self, - - old: &'bump [VNode<'bump>], - new: &'bump [VNode<'bump>], - ) { - if let Some(first_old) = old.get(0) { - self.remove_nodes(&old[1..], true); - self.stack - .create_children(new, MountType::Replace { old: first_old }) - } else { - self.stack.create_children(new, MountType::Append {}); - } - } - /// Adds a listener closure to a scope during diff. fn attach_listener_to_scope(&mut self, listener: &'bump Listener<'bump>, scope: &Scope) { let long_listener = unsafe { std::mem::transmute(listener) }; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index c5b9be26a..62c3ab837 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -172,6 +172,7 @@ impl<'src> VNode<'src> { VNode::Element(el) => el.dom_id.get(), VNode::Anchor(el) => el.dom_id.get(), VNode::Suspended(el) => el.dom_id.get(), + VNode::Linked(_) => None, VNode::Fragment(_) => None, VNode::Component(_) => None, diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index 1379d468a..a08e0ea41 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -1,10 +1,9 @@ +use bumpalo::Bump; +use futures_channel::mpsc::UnboundedSender; use fxhash::FxHashMap; use slab::Slab; use std::cell::{Cell, RefCell}; -use bumpalo::{boxed::Box as BumpBox, Bump}; -use futures_channel::mpsc::UnboundedSender; - use crate::innerlude::*; pub type FcSlot = *const (); @@ -61,71 +60,71 @@ impl ScopeArena { height: u32, subtree: u32, ) -> ScopeId { - if let Some(id) = self.free_scopes.borrow_mut().pop() { - // have already called drop on it - the slot is still chillin tho - // let scope = unsafe { &mut *self.scopes.borrow()[id.0 as usize] }; + let scope_id = ScopeId(self.scopes.borrow().len()); - todo!("override the scope contents"); + let (node_capacity, hook_capacity) = { + let heuristics = self.heuristics.borrow(); + if let Some(heuristic) = heuristics.get(&fc_ptr) { + (heuristic.node_arena_size, heuristic.hook_arena_size) + } else { + (0, 0) + } + }; + + let mut frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)]; + + frames[0].nodes.get_mut().push({ + let vnode = frames[0] + .bump + .alloc(VNode::Text(frames[0].bump.alloc(VText { + dom_id: Default::default(), + is_static: false, + text: "", + }))); + unsafe { std::mem::transmute(vnode as *mut VNode) } + }); + + frames[1].nodes.get_mut().push({ + let vnode = frames[1] + .bump + .alloc(VNode::Text(frames[1].bump.alloc(VText { + dom_id: Default::default(), + is_static: false, + text: "", + }))); + unsafe { std::mem::transmute(vnode as *mut VNode) } + }); + + let mut new_scope = Scope { + sender: self.sender.clone(), + our_arena_idx: scope_id, + parent_scope, + height, + frames, + subtree: Cell::new(subtree), + is_subtree_root: Cell::new(false), + + caller, + generation: 0.into(), + + hooks: HookList::new(hook_capacity), + shared_contexts: Default::default(), + + items: RefCell::new(SelfReferentialItems { + listeners: Default::default(), + borrowed_props: Default::default(), + suspended_nodes: Default::default(), + tasks: Default::default(), + pending_effects: Default::default(), + }), + }; + + if let Some(id) = self.free_scopes.borrow_mut().pop() { + let scope = unsafe { self.get_scope_mut(&id) }.unwrap(); + std::mem::swap(&mut new_scope, scope); id } else { - let scope_id = ScopeId(self.scopes.borrow().len()); - - let (node_capacity, hook_capacity) = { - let heuristics = self.heuristics.borrow(); - if let Some(heuristic) = heuristics.get(&fc_ptr) { - (heuristic.node_arena_size, heuristic.hook_arena_size) - } else { - (0, 0) - } - }; - - let mut frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)]; - - frames[0].nodes.get_mut().push({ - let vnode = frames[0] - .bump - .alloc(VNode::Text(frames[0].bump.alloc(VText { - dom_id: Default::default(), - is_static: false, - text: "", - }))); - unsafe { std::mem::transmute(vnode as *mut VNode) } - }); - - frames[1].nodes.get_mut().push({ - let vnode = frames[1] - .bump - .alloc(VNode::Text(frames[1].bump.alloc(VText { - dom_id: Default::default(), - is_static: false, - text: "", - }))); - unsafe { std::mem::transmute(vnode as *mut VNode) } - }); - - let scope = self.bump.alloc(Scope { - sender: self.sender.clone(), - parent_scope, - our_arena_idx: scope_id, - height, - subtree: Cell::new(subtree), - is_subtree_root: Cell::new(false), - frames, - - hooks: HookList::new(hook_capacity), - shared_contexts: Default::default(), - caller, - generation: 0.into(), - - items: RefCell::new(SelfReferentialItems { - listeners: Default::default(), - borrowed_props: Default::default(), - suspended_nodes: Default::default(), - tasks: Default::default(), - pending_effects: Default::default(), - }), - }); - + let scope = self.bump.alloc(new_scope); self.scopes.borrow_mut().push(scope); scope_id } @@ -274,6 +273,8 @@ impl ScopeArena { // self. } + // make the "wip frame" contents the "finished frame" + // any future dipping into completed nodes after "render" will go through "fin head" scope.cycle_frame(); true } else { @@ -300,6 +301,6 @@ impl ScopeArena { } pub fn root_node(&self, id: &ScopeId) -> &VNode { - self.wip_head(id) + self.fin_head(id) } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 0513b690f..19fa6ca3b 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -198,15 +198,19 @@ impl VirtualDom { let caller_ref: *mut dyn Fn(&Scope) -> Element = Box::into_raw(caller); let base_scope = scopes.new_with_key(root as _, caller_ref, None, 0, 0); + let pending_messages = VecDeque::new(); + let mut dirty_scopes = IndexSet::new(); + dirty_scopes.insert(base_scope); + Self { scopes: Box::new(scopes), base_scope, receiver, // todo: clean this up manually? _root_caller: caller_ref, - pending_messages: VecDeque::new(), + pending_messages, pending_futures: Default::default(), - dirty_scopes: Default::default(), + dirty_scopes, sender, } } @@ -462,8 +466,8 @@ impl VirtualDom { self.dirty_scopes.remove(&scope); } - // I think the stack should be empty at the end of diffing? - debug_assert_eq!(stack.scope_stack.len(), 1); + // // I think the stack should be empty at the end of diffing? + // debug_assert_eq!(stack.scope_stack.len(), 1); committed_mutations.push(mutations); } else { diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 1e65f8f58..3c52214f5 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -3,7 +3,11 @@ //! Tests for the lifecycle of components. use dioxus::prelude::*; use dioxus_core as dioxus; +use dioxus_core::DomEdit::*; +use dioxus_core::ScopeId; + use dioxus_core_macro::*; +use dioxus_hooks::*; use dioxus_html as dioxus_elements; use std::sync::{Arc, Mutex}; @@ -41,3 +45,178 @@ fn manual_diffing() { log::debug!("edits: {:?}", edits); } + +#[test] +fn events_generate() { + static App: FC<()> = |cx, _| { + let mut count = use_state(cx, || 0); + + let inner = match *count { + 0 => { + rsx! { + div { + onclick: move |_| count += 1, + div { + "nested" + } + "Click me!" + } + } + } + _ => todo!(), + }; + + cx.render(inner) + }; + + let mut dom = VirtualDom::new(App); + let mut channel = dom.get_scheduler_channel(); + assert!(dom.has_any_work()); + + let edits = dom.work_with_deadline(|| false); + assert_eq!( + edits[0].edits, + [ + CreateElement { + tag: "div", + root: 0, + }, + NewEventListener { + event_name: "click", + scope: ScopeId(0), + root: 0, + }, + CreateElement { + tag: "div", + root: 1, + }, + CreateTextNode { + text: "nested", + root: 2, + }, + AppendChildren { many: 1 }, + CreateTextNode { + text: "Click me!", + root: 3, + }, + AppendChildren { many: 2 }, + ] + ) +} + +#[test] +fn components_generate() { + static App: FC<()> = |cx, _| { + let mut render_phase = use_state(cx, || 0); + render_phase += 1; + + cx.render(match *render_phase { + 0 => rsx!("Text0"), + 1 => rsx!(div {}), + 2 => rsx!("Text2"), + 3 => rsx!(Child {}), + 4 => rsx!({ None as Option<()> }), + 5 => rsx!("text 3"), + 6 => rsx!({ (0..2).map(|f| rsx!("text {f}")) }), + 7 => rsx!(Child {}), + _ => todo!(), + }) + }; + + static Child: FC<()> = |cx, _| { + println!("running child"); + cx.render(rsx! { + h1 {} + }) + }; + + let mut dom = VirtualDom::new(App); + let edits = dom.rebuild(); + assert_eq!( + edits.edits, + [ + CreateTextNode { + text: "Text0", + root: 0, + }, + AppendChildren { many: 1 }, + ] + ); + + let edits = dom.hard_diff(&ScopeId(0)).unwrap(); + assert_eq!( + edits.edits, + [ + CreateElement { + tag: "div", + root: 1, + }, + ReplaceWith { root: 0, m: 1 }, + ] + ); + + let edits = dom.hard_diff(&ScopeId(0)).unwrap(); + assert_eq!( + edits.edits, + [ + CreateTextNode { + text: "Text2", + root: 2, + }, + ReplaceWith { root: 1, m: 1 }, + ] + ); + + let edits = dom.hard_diff(&ScopeId(0)).unwrap(); + assert_eq!( + edits.edits, + [ + CreateElement { tag: "h1", root: 3 }, + ReplaceWith { root: 2, m: 1 }, + ] + ); + + let edits = dom.hard_diff(&ScopeId(0)).unwrap(); + assert_eq!( + edits.edits, + [CreatePlaceholder { root: 4 }, ReplaceWith { root: 3, m: 1 },] + ); + + let edits = dom.hard_diff(&ScopeId(0)).unwrap(); + assert_eq!( + edits.edits, + [ + CreateTextNode { + text: "text 3", + root: 5, + }, + ReplaceWith { root: 4, m: 1 }, + ] + ); + + let edits = dom.hard_diff(&ScopeId(0)).unwrap(); + assert_eq!( + edits.edits, + [ + CreateTextNode { + text: "text 0", + root: 6, + }, + CreateTextNode { + text: "text 1", + root: 7, + }, + ReplaceWith { root: 5, m: 2 }, + ] + ); + + let edits = dom.hard_diff(&ScopeId(0)).unwrap(); + assert_eq!( + edits.edits, + [ + CreateElement { tag: "h1", root: 8 }, + ReplaceWith { root: 6, m: 1 }, + Remove { root: 7 }, + ] + ); +}