diff --git a/packages/core-macro/src/rsx/element.rs b/packages/core-macro/src/rsx/element.rs index e5973d263..c4fa949e8 100644 --- a/packages/core-macro/src/rsx/element.rs +++ b/packages/core-macro/src/rsx/element.rs @@ -109,20 +109,29 @@ impl Parse for Element { let name_str = name.to_string(); stream.parse::()?; if name_str.starts_with("on") { - todo!() + let inner; + syn::braced!(inner in stream); + let toks = inner.parse::()?; + let ty = AttrType::EventTokens(toks); + listeners.push(ElementAttr { + element_name: el_name.clone(), + name, + value: ty, + namespace: None, + }) } else { match name_str.as_str() { - "style" => todo!(), - "key" => todo!(), - "classes" => todo!(), - "namespace" => todo!(), - "ref" => todo!(), - _ => { + "style" => {} + "key" => {} + "classes" | "namespace" | "ref" | _ => { let ty = if stream.peek(LitStr) { let rawtext = stream.parse::().unwrap(); AttrType::BumpText(rawtext) } else { - let toks = stream.parse::()?; + // like JSX, we expect raw expressions + let inner; + syn::braced!(inner in stream); + let toks = inner.parse::()?; AttrType::FieldTokens(toks) }; attributes.push(ElementAttr { @@ -134,13 +143,6 @@ impl Parse for Element { } } }; - // if stream.peek(LitStr) { - - // } else { - // } - // if name_str.starts_with("on") {} - - // attributes.push(stream.parse()?); } stream.parse::]>()?; diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 2b58ae251..1c68f97cc 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": false + "rust-analyzer.inlayHints.enable": true } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 6d24afee0..0e8be0eaf 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,55 +1,99 @@ +use std::cell::{RefCell, RefMut}; use std::{cell::UnsafeCell, rc::Rc}; +use crate::heuristics::*; use crate::innerlude::*; +use futures_util::stream::FuturesUnordered; use slotmap::SlotMap; +slotmap::new_key_type! { + // A dedicated key type for the all the scopes + pub struct ScopeId; +} + +slotmap::new_key_type! { + // A dedicated key type for every real element that the virtualdom creates. + // + // This is a slotmap key because we expect the "mirror" realdom to also maintain a slotmap mapping + // of virtual element to real element. + pub struct ElementId; +} + +impl ElementId { + pub fn as_u64(self) -> u64 { + self.0.as_ffi() + } +} + +type Shared = Rc>; + +/// These are resources shared among all the components and the virtualdom itself #[derive(Clone)] -pub struct SharedArena { - pub components: Rc>, -} -pub type ScopeMap = SlotMap; +pub struct SharedResources { + pub components: Rc>>, -enum MutStatus { - Immut, - Mut, + pub event_queue: Shared>, + + pub events: Shared>, + + pub(crate) heuristics: Shared, + + pub(crate) tasks: Shared>, + + /// We use a SlotSet to keep track of the keys that are currently being used. + /// However, we don't store any specific data since the "mirror" + pub raw_elements: Shared>, + + pub task_setter: Rc, } -impl SharedArena { - pub fn new(arena: ScopeMap) -> Self { - let components = Rc::new(UnsafeCell::new(arena)); - SharedArena { components } +impl SharedResources { + pub fn new() -> Self { + // preallocate 1000 elements and 20 scopes to avoid dynamic allocation + let components = Rc::new(UnsafeCell::new( + SlotMap::::with_capacity_and_key(20), + )); + let raw_elements = SlotMap::::with_capacity_and_key(1000); + + let event_queue = Rc::new(RefCell::new(Vec::new())); + let tasks = Vec::new(); + let heuristics = HeuristicsEngine::new(); + + let queue = event_queue.clone(); + let _components = components.clone(); + let task_setter = Rc::new(move |idx| { + let comps = unsafe { &*_components.get() }; + if let Some(scope) = comps.get(idx) { + queue.borrow_mut().push(HeightMarker { + height: scope.height, + idx, + }) + } + }); + + Self { + event_queue, + components, + tasks: Rc::new(RefCell::new(FuturesUnordered::new())), + events: Rc::new(RefCell::new(tasks)), + heuristics: Rc::new(RefCell::new(heuristics)), + raw_elements: Rc::new(RefCell::new(raw_elements)), + task_setter, + } } - /// THIS METHOD IS CURRENTLY UNSAFE - /// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS - pub fn get(&self, idx: ScopeId) -> Option<&Scope> { - let inner = unsafe { &*self.components.get() }; + /// this is unsafe because the caller needs to track which other scopes it's already using + pub unsafe fn get_scope(&self, idx: ScopeId) -> Option<&Scope> { + let inner = &*self.components.get(); inner.get(idx) } - /// THIS METHOD IS CURRENTLY UNSAFE - /// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS - pub fn get_mut(&self, idx: ScopeId) -> Option<&mut Scope> { - let inner = unsafe { &mut *self.components.get() }; + /// this is unsafe because the caller needs to track which other scopes it's already using + pub unsafe fn get_sope_mut(&self, idx: ScopeId) -> Option<&mut Scope> { + let inner = &mut *self.components.get(); inner.get_mut(idx) } - fn inner(&self) -> &ScopeMap { - todo!() - } - - fn inner_mut(&mut self) -> &mut ScopeMap { - todo!() - } - - /// THIS METHOD IS CURRENTLY UNSAFE - /// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS - pub fn with(&self, f: impl FnOnce(&mut ScopeMap) -> T) -> Result { - let inner = unsafe { &mut *self.components.get() }; - Ok(f(inner)) - // todo!() - } - pub fn with_scope<'b, O: 'static>( &'b self, _id: ScopeId, @@ -75,7 +119,37 @@ impl SharedArena { .ok_or_else(|| Error::FatalInternal("Scope not found")) } - unsafe fn inner_unchecked<'s>() -> &'s mut ScopeMap { - todo!() + pub fn reserve_node(&self) -> ElementId { + self.raw_elements.borrow_mut().insert(()) + } + + /// return the id, freeing the space of the original node + pub fn collect_garbage(&self, id: ElementId) {} + + pub fn borrow_queue(&self) -> RefMut> { + self.event_queue.borrow_mut() + } + + pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId { + let g = unsafe { &mut *self.components.get() }; + g.insert_with_key(f) + } + + pub fn schedule_update(&self) -> Rc { + self.task_setter.clone() + } + + pub fn submit_task(&self, task: FiberTask) -> TaskHandle { + self.tasks.borrow_mut().push(task); + TaskHandle {} } } + +pub struct TaskHandle {} + +impl TaskHandle { + pub fn toggle(&self) {} + pub fn start(&self) {} + pub fn stop(&self) {} + pub fn restart(&self) {} +} diff --git a/packages/core/src/bumpframe.rs b/packages/core/src/bumpframe.rs index 62d5035dd..19c545679 100644 --- a/packages/core/src/bumpframe.rs +++ b/packages/core/src/bumpframe.rs @@ -1,10 +1,10 @@ use crate::innerlude::*; use bumpalo::Bump; +use std::cell::Cell; -use std::cell::RefCell; pub struct ActiveFrame { // We use a "generation" for users of contents in the bump frames to ensure their data isn't broken - pub generation: RefCell, + pub generation: Cell, // The double-buffering situation that we will use pub frames: [BumpFrame; 2], @@ -13,78 +13,100 @@ pub struct ActiveFrame { pub struct BumpFrame { pub bump: Bump, pub head_node: VNode<'static>, + + #[cfg(test)] + name: &'static str, } impl ActiveFrame { pub fn new() -> Self { - Self::from_frames( - BumpFrame { - bump: Bump::new(), - head_node: NodeFactory::static_text(""), - }, - BumpFrame { - bump: Bump::new(), - head_node: NodeFactory::static_text(""), - }, - ) - } + let frame_a = BumpFrame { + bump: Bump::new(), + head_node: NodeFactory::unstable_place_holder(), - pub fn from_frames(a: BumpFrame, b: BumpFrame) -> Self { + #[cfg(test)] + name: "old", + }; + let frame_b = BumpFrame { + bump: Bump::new(), + head_node: NodeFactory::unstable_place_holder(), + + #[cfg(test)] + name: "new", + }; Self { generation: 0.into(), - frames: [a, b], + frames: [frame_a, frame_b], } } - pub fn cur_frame(&self) -> &BumpFrame { - match *self.generation.borrow() & 1 == 0 { + pub fn prev_frame(&self) -> &BumpFrame { + match self.generation.get() & 1 == 0 { true => &self.frames[0], false => &self.frames[1], } } - pub fn cur_frame_mut(&mut self) -> &mut BumpFrame { - match *self.generation.borrow() & 1 == 0 { + + pub fn prev_frame_mut(&mut self) -> &mut BumpFrame { + match self.generation.get() & 1 == 0 { true => &mut self.frames[0], false => &mut self.frames[1], } } + pub fn cur_frame(&self) -> &BumpFrame { + match self.generation.get() & 1 == 1 { + true => &self.frames[0], + false => &self.frames[1], + } + } + + pub fn cur_frame_mut(&mut self) -> &mut BumpFrame { + match self.generation.get() & 1 == 1 { + true => &mut self.frames[0], + false => &mut self.frames[1], + } + } + /// Give out our self-referential item with our own borrowed lifetime pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> { - let raw_node = match *self.generation.borrow() & 1 == 0 { - true => &self.frames[0], - false => &self.frames[1], - }; - - // Give out our self-referential item with our own borrowed lifetime - unsafe { - let unsafe_head = &raw_node.head_node; - let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head); - safe_node - } + let cur_head = &self.cur_frame().head_node; + unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) } } + /// Give out our self-referential item with our own borrowed lifetime pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> { - let raw_node = match *self.generation.borrow() & 1 != 0 { - true => &self.frames[0], - false => &self.frames[1], - }; - - // Give out our self-referential item with our own borrowed lifetime - unsafe { - let unsafe_head = &raw_node.head_node; - let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head); - safe_node - } - } - - pub fn old_frame_mut(&mut self) -> &mut BumpFrame { - match *self.generation.borrow() & 1 == 0 { - true => &mut self.frames[1], - false => &mut self.frames[0], - } + let cur_head = &self.prev_frame().head_node; + unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) } } pub fn cycle_frame(&mut self) { - *self.generation.borrow_mut() += 1; + self.generation.set(self.generation.get() + 1); + } +} + +#[cfg(test)] +mod tests { + //! These tests are bad. I don't have a good way of properly testing the ActiveFrame stuff + use super::*; + + #[test] + fn test_bump_frame() { + let mut frames = ActiveFrame::new(); + + // just cycle a few times and make sure we get the right frames out + for _ in 0..5 { + let old = frames.prev_frame(); + let new = frames.cur_frame(); + assert_eq!(old.name, "old"); + assert_eq!(new.name, "new"); + frames.cycle_frame(); + + let old = frames.prev_frame(); + let new = frames.cur_frame(); + assert_eq!(old.name, "new"); + assert_eq!(new.name, "old"); + frames.cycle_frame(); + } + assert_eq!(frames.generation.get(), 10); } } diff --git a/packages/core/src/childiter.rs b/packages/core/src/childiter.rs deleted file mode 100644 index 875bf6ec6..000000000 --- a/packages/core/src/childiter.rs +++ /dev/null @@ -1,91 +0,0 @@ -/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text). -/// -/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like -/// "InsertBefore". -struct RealChildIterator<'a> { - scopes: &'a SharedArena, - - // Heuristcally we should never bleed into 5 completely nested fragments/components - // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane - stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>, -} - -impl<'a> RealChildIterator<'a> { - fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self { - Self { - scopes, - stack: smallvec::smallvec![(0, starter)], - } - } -} - -impl<'a> Iterator for RealChildIterator<'a> { - type Item = &'a VNode<'a>; - - fn next(&mut self) -> Option<&'a VNode<'a>> { - let mut should_pop = false; - let mut returned_node = None; - let mut should_push = None; - - while returned_node.is_none() { - if let Some((count, node)) = self.stack.last_mut() { - match node { - // We can only exit our looping when we get "real" nodes - // This includes fragments and components when they're empty (have a single root) - VNode::Element(_) | VNode::Text(_) => { - // We've recursed INTO an element/text - // We need to recurse *out* of it and move forward to the next - should_pop = true; - returned_node = Some(&**node); - } - - // If we get a fragment we push the next child - VNode::Fragment(frag) => { - let subcount = *count as usize; - - if frag.children.len() == 0 { - should_pop = true; - returned_node = Some(&**node); - } - - if subcount >= frag.children.len() { - should_pop = true; - } else { - should_push = Some(&frag.children[subcount]); - } - } - - // Immediately abort suspended nodes - can't do anything with them yet - // VNode::Suspended => should_pop = true, - VNode::Suspended { real } => todo!(), - - // For components, we load their root and push them onto the stack - VNode::Component(sc) => { - let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap(); - - // Simply swap the current node on the stack with the root of the component - *node = scope.root(); - } - } - } else { - // If there's no more items on the stack, we're done! - return None; - } - - if should_pop { - self.stack.pop(); - if let Some((id, _)) = self.stack.last_mut() { - *id += 1; - } - should_pop = false; - } - - if let Some(push) = should_push { - self.stack.push((0, push)); - should_push = None; - } - } - - returned_node - } -} diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index c6acdb721..754fa5498 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -5,8 +5,40 @@ //! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder //! that ensures compile-time required and optional fields on cx. -use crate::innerlude::{Context, DomTree, LazyNodes, VNode, FC}; +use crate::innerlude::{Context, DomTree, LazyNodes, FC}; +/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus +/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the +/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe. +/// +/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However, +/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored. +/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure); +/// +/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However, +/// this is only safe if the props are 'static - otherwise you might borrow references after-free. +/// +/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally, +/// we advise the use of smart pointers in cases where memoization is important. +/// +/// ## Example +/// +/// For props that are 'static: +/// ```rust ignore +/// #[derive(Props, PartialEq)] +/// struct MyProps { +/// data: String +/// } +/// ``` +/// +/// For props that borrow: +/// +/// ```rust ignore +/// #[derive(Props)] +/// struct MyProps<'a >{ +/// data: &'a str +/// } +/// ``` pub trait Properties: Sized { type Builder; const IS_STATIC: bool = false; @@ -50,6 +82,16 @@ pub fn fc_to_builder(_: FC) -> T::Builder { /// /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase. /// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash. +/// +/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro. +/// +/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. +/// +/// ```rust +/// rsx!{ +/// Fragment { key: "abc" } +/// } +/// ``` #[allow(non_upper_case_globals, non_snake_case)] pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> { cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children()))) diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 0c8879eae..efc7a5613 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -99,12 +99,21 @@ impl<'src, P> Context<'src, P> { /// } /// ``` pub fn children(&self) -> &'src [VNode<'src>] { - self.scope.child_nodes + self.scope.child_nodes() } /// Create a subscription that schedules a future render for the reference component + /// + /// ## Notice: you should prefer using prepare_update and get_scope_id + /// pub fn schedule_update(&self) -> Rc { - self.scope.event_channel.clone() + let cb = self.scope.vdom.schedule_update(); + let id = self.get_scope_id(); + Rc::new(move || cb(id)) + } + + fn prepare_update(&self) -> Rc { + self.scope.vdom.schedule_update() } pub fn schedule_effect(&self) -> Rc { @@ -142,7 +151,7 @@ impl<'src, P> Context<'src, P> { let scope_ref = self.scope; let listener_id = &scope_ref.listener_idx; Some(lazy_nodes.into_vnode(NodeFactory { - scope_ref, + scope: scope_ref, listener_id, })) } @@ -297,7 +306,7 @@ Any function prefixed with "use" should not be called conditionally. } else { match inner.parent_idx { Some(parent_id) => { - scope = inner.arena_link.get(parent_id); + scope = unsafe { inner.vdom.get_scope(parent_id) }; } None => break, } @@ -329,8 +338,7 @@ Any function prefixed with "use" should not be called conditionally. /// /// pub fn submit_task(&self, task: FiberTask) -> TaskHandle { - (self.scope.task_submitter)(task); - TaskHandle { _p: PhantomData {} } + self.scope.vdom.submit_task(task) } /// Awaits the given task, forcing the component to re-render when the value is ready. @@ -341,13 +349,14 @@ Any function prefixed with "use" should not be called conditionally. pub fn use_task( self, task_initializer: Init, - ) -> (TaskHandle<'src>, &'src Option) + ) -> (&'src TaskHandle, &'src Option) where Out: 'static, Fut: Future + 'static, Init: FnOnce() -> Fut + 'src, { struct TaskHook { + handle: TaskHandle, task_dump: Rc>>, value: Option, } @@ -360,12 +369,15 @@ Any function prefixed with "use" should not be called conditionally. let task_dump = Rc::new(RefCell::new(None)); let slot = task_dump.clone(); - let update = self.schedule_update(); + + let updater = self.prepare_update(); + let update_id = self.get_scope_id(); + let originator = self.scope.our_arena_idx.clone(); - self.submit_task(Box::pin(task_fut.then(move |output| async move { + let handle = self.submit_task(Box::pin(task_fut.then(move |output| async move { *slot.as_ref().borrow_mut() = Some(output); - update(); + updater(update_id); EventTrigger { event: VirtualEvent::AsyncEvent { hook_idx }, originator, @@ -377,13 +389,14 @@ Any function prefixed with "use" should not be called conditionally. TaskHook { task_dump, value: None, + handle, } }, |hook| { if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() { hook.value = Some(val); } - (TaskHandle { _p: PhantomData }, &hook.value) + (&hook.handle, &hook.value) }, |_| {}, ) @@ -393,7 +406,7 @@ Any function prefixed with "use" should not be called conditionally. pub(crate) struct SuspenseHook { pub value: Rc>>>, pub callback: SuspendedCallback, - pub dom_node_id: Rc>, + pub dom_node_id: Rc>>, } type SuspendedCallback = Box Fn(SuspendedContext<'a>) -> DomTree<'a>>; @@ -422,7 +435,7 @@ impl<'src, P> Context<'src, P> { move |hook_idx| { let value = Rc::new(RefCell::new(None)); - let dom_node_id = Rc::new(RealDomNode::empty_cell()); + let dom_node_id = Rc::new(empty_cell()); let domnode = dom_node_id.clone(); let slot = value.clone(); @@ -438,7 +451,7 @@ impl<'src, P> Context<'src, P> { None => { // Some(VNode { - dom_id: RealDomNode::empty_cell(), + dom_id: empty_cell(), key: None, kind: VNodeKind::Suspended { node: domnode.clone(), @@ -495,20 +508,8 @@ impl<'src> SuspendedContext<'src> { let scope_ref = self.inner.scope; let listener_id = &scope_ref.listener_idx; Some(lazy_nodes.into_vnode(NodeFactory { - scope_ref, + scope: scope_ref, listener_id, })) } } - -#[derive(Clone, Copy)] -pub struct TaskHandle<'src> { - _p: PhantomData<&'src ()>, -} - -impl<'src> TaskHandle<'src> { - pub fn toggle(&self) {} - pub fn start(&self) {} - pub fn stop(&self) {} - pub fn restart(&self) {} -} diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index a91a81cdc..f29c1fc84 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -15,7 +15,7 @@ //! whose keys can be converted to u64 on FFI boundaries. //! //! When new nodes are created through `render`, they won't know which real node they correspond to. During diffing, we -//! always make sure to copy over the ID. If we don't do this properly, the realdomnode will be populated incorrectly and +//! always make sure to copy over the ID. If we don't do this properly, the ElementId will be populated incorrectly and //! brick the user's page. //! //! ## Subtree Memoization @@ -48,11 +48,11 @@ //! 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 crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue}; -use fxhash::FxHashSet; +use crate::{arena::SharedResources, innerlude::*}; +use fxhash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; -use std::any::Any; +use std::{any::Any, borrow::Borrow}; /// The accompanying "real dom" exposes an imperative API for controlling the UI layout /// @@ -66,21 +66,35 @@ use std::any::Any; /// any modifications that follow. This technique enables the diffing algorithm to avoid directly handling or storing any /// target-specific Node type as well as easily serializing the edits to be sent over a network or IPC connection. pub trait RealDom<'a> { - fn request_available_node(&mut self) -> RealDomNode; fn raw_node_as_any(&self) -> &mut dyn Any; } pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> { - pub dom: &'real mut Dom, + pub real_dom: &'real mut Dom, + pub vdom: &'bump SharedResources, pub edits: DomEditor<'real, 'bump>, - pub components: &'bump SharedArena, - pub task_queue: &'bump TaskQueue, + pub cur_idxs: SmallVec<[ScopeId; 5]>, pub diffed: FxHashSet, - pub event_queue: EventQueue, pub seen_nodes: FxHashSet, } +impl<'r, 'b, D: RealDom<'b>> DiffMachine<'r, 'b, D> { + pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'b mut Scope> { + // ensure we haven't seen this scope before + // if we have, then we're trying to alias it, which is not allowed + debug_assert!(!self.seen_nodes.contains(id)); + let compon = unsafe { &mut *self.vdom.components.get() }; + compon.get_mut(*id) + } + pub fn get_scope(&mut self, id: &ScopeId) -> Option<&'b Scope> { + // ensure we haven't seen this scope before + // if we have, then we're trying to alias it, which is not allowed + let compon = unsafe { &*self.vdom.components.get() }; + compon.get(*id) + } +} + impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom> where Dom: RealDom<'bump>, @@ -88,18 +102,14 @@ where pub fn new( edits: &'real mut Vec>, dom: &'real mut Dom, - components: &'bump SharedArena, cur_idx: ScopeId, - event_queue: EventQueue, - task_queue: &'bump TaskQueue, + shared: &'bump SharedResources, ) -> Self { Self { + real_dom: dom, edits: DomEditor::new(edits), - components, - dom, cur_idxs: smallvec![cur_idx], - event_queue, - task_queue, + vdom: shared, diffed: FxHashSet::default(), seen_nodes: FxHashSet::default(), } @@ -112,20 +122,24 @@ where // // each function call assumes the stack is fresh (empty). pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) { + let root = old_node + .dom_id + .get() + .expect("Should not be diffing old nodes that were never assigned"); + match (&old_node.kind, &new_node.kind) { // Handle the "sane" cases first. // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment". // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable. (VNodeKind::Text(old), VNodeKind::Text(new)) => { - let root = old_node.dom_id.get(); if old.text != new.text { - self.edits.push(root); + self.edits.push_root(root); log::debug!("Text has changed {}, {}", old.text, new.text); self.edits.set_text(new.text); self.edits.pop(); } - new_node.dom_id.set(root); + new_node.dom_id.set(Some(root)); } (VNodeKind::Element(old), VNodeKind::Element(new)) => { @@ -133,20 +147,19 @@ where // This is an optimization React makes due to how users structure their code // // In Dioxus, this is less likely to occur unless through a fragment - let root = old_node.dom_id.get(); if new.tag_name != old.tag_name || new.namespace != old.namespace { - self.edits.push(root); + self.edits.push_root(root); let meta = self.create(new_node); self.edits.replace_with(meta.added_to_stack); self.edits.pop(); return; } - new_node.dom_id.set(root); + new_node.dom_id.set(Some(root)); // push it just in case // TODO: remove this - it clogs up things and is inefficient - self.edits.push(root); + self.edits.push_root(root); self.diff_listeners(old.listeners, new.listeners); self.diff_attr(old.attributes, new.attributes, new.namespace); self.diff_children(old.children, new.children); @@ -160,11 +173,11 @@ where self.cur_idxs.push(old.ass_scope.get().unwrap()); // Make sure the new component vnode is referencing the right scope id - let scope_id = old.ass_scope.get(); - new.ass_scope.set(scope_id); + let scope_addr = old.ass_scope.get().unwrap(); + new.ass_scope.set(Some(scope_addr)); // make sure the component's caller function is up to date - let scope = self.components.get_mut(scope_id.unwrap()).unwrap(); + let scope = self.get_scope_mut(&scope_addr).unwrap(); scope.caller = new.caller.clone(); @@ -185,22 +198,22 @@ where } self.cur_idxs.pop(); - self.seen_nodes.insert(scope_id.unwrap()); + self.seen_nodes.insert(scope_addr); } else { // this seems to be a fairy common code path that we could - let mut old_iter = RealChildIterator::new(old_node, &self.components); + let mut old_iter = RealChildIterator::new(old_node, &self.vdom); let first = old_iter .next() .expect("Components should generate a placeholder root"); // remove any leftovers for to_remove in old_iter { - self.edits.push(to_remove); + self.edits.push_root(to_remove); self.edits.remove(); } // seems like we could combine this into a single instruction.... - self.edits.push(first); + self.edits.push_root(first); let meta = self.create(new_node); self.edits.replace_with(meta.added_to_stack); self.edits.pop(); @@ -229,8 +242,12 @@ where } // The strategy here is to pick the first possible node from the previous set and use that as our replace with root + // // We also walk the "real node" list to make sure all latent roots are claened up // This covers the case any time a fragment or component shows up with pretty much anything else + // + // This likely isn't the fastest way to go about replacing one node with a virtual node, but the "insane" cases + // are pretty rare. IE replacing a list (component or fragment) with a single node. ( VNodeKind::Component(_) | VNodeKind::Fragment(_) @@ -242,12 +259,12 @@ where | VNodeKind::Element(_), ) => { // Choose the node to use as the placeholder for replacewith - let back_node = match old_node.kind { + let back_node_id = match old_node.kind { // We special case these two types to avoid allocating the small-vecs - VNodeKind::Element(_) | VNodeKind::Text(_) => old_node.dom_id.get(), + VNodeKind::Element(_) | VNodeKind::Text(_) => root, _ => { - let mut old_iter = RealChildIterator::new(old_node, &self.components); + let mut old_iter = RealChildIterator::new(old_node, &self.vdom); let back_node = old_iter .next() @@ -255,7 +272,7 @@ where // remove any leftovers for to_remove in old_iter { - self.edits.push(to_remove); + self.edits.push_root(to_remove); self.edits.remove(); } @@ -264,7 +281,7 @@ where }; // replace the placeholder or first node with the nodes generated from the "new" - self.edits.push(back_node); + self.edits.push_root(back_node_id); let meta = self.create(new_node); self.edits.replace_with(meta.added_to_stack); @@ -314,9 +331,10 @@ where log::warn!("Creating node! ... {:#?}", node); match &node.kind { VNodeKind::Text(text) => { - let real_id = self.dom.request_available_node(); + let real_id = self.vdom.reserve_node(); self.edits.create_text_node(text.text, real_id); - node.dom_id.set(real_id); + node.dom_id.set(Some(real_id)); + CreateMeta::new(text.is_static, 1) } VNodeKind::Element(el) => { @@ -338,18 +356,18 @@ where static_listeners: _, } = el; - let real_id = self.dom.request_available_node(); + let real_id = self.vdom.reserve_node(); if let Some(namespace) = namespace { self.edits .create_element(tag_name, Some(namespace), real_id) } else { self.edits.create_element(tag_name, None, real_id) }; - node.dom_id.set(real_id); + node.dom_id.set(Some(real_id)); listeners.iter().enumerate().for_each(|(idx, listener)| { log::info!("setting listener id to {:#?}", real_id); - listener.mounted_node.set(real_id); + listener.mounted_node.set(Some(real_id)); self.edits .new_event_listener(listener.event, listener.scope, idx, real_id); @@ -405,25 +423,18 @@ where let parent_idx = self.cur_idxs.last().unwrap().clone(); // Insert a new scope into our component list - let new_idx = self - .components - .with(|components| { - components.insert_with_key(|new_idx| { - let parent_scope = self.components.get(parent_idx).unwrap(); - let height = parent_scope.height + 1; - Scope::new( - caller, - new_idx, - Some(parent_idx), - height, - self.event_queue.new_channel(height, new_idx), - self.components.clone(), - vcomponent.children, - self.task_queue.new_submitter(), - ) - }) - }) - .unwrap(); + let new_idx = self.vdom.insert_scope_with_key(|new_idx| { + let parent_scope = self.get_scope(&parent_idx).unwrap(); + let height = parent_scope.height + 1; + Scope::new( + caller, + new_idx, + Some(parent_idx), + height, + vcomponent.children, + self.vdom.clone(), + ) + }); // This code is supposed to insert the new idx into the parent's descendent list, but it doesn't really work. // This is mostly used for cleanup - to remove old scopes when components are destroyed. @@ -437,7 +448,7 @@ where // .insert(idx); // TODO: abstract this unsafe into the arena abstraction - let inner: &'bump mut _ = unsafe { &mut *self.components.components.get() }; + let inner: &'bump mut _ = unsafe { &mut *self.vdom.components.get() }; let new_component = inner.get_mut(new_idx).unwrap(); // Actually initialize the caller's slot with the right address @@ -477,10 +488,10 @@ where } VNodeKind::Suspended { node: real_node } => { - let id = self.dom.request_available_node(); + let id = self.vdom.reserve_node(); self.edits.create_placeholder(id); - node.dom_id.set(id); - real_node.set(id); + node.dom_id.set(Some(id)); + real_node.set(Some(id)); CreateMeta::new(false, 1) } } @@ -500,7 +511,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { while let Some(scope_id) = scopes_to_explore.pop() { // If we're planning on deleting this node, then we don't need to both rendering it self.seen_nodes.insert(scope_id); - let scope = self.components.get(scope_id).unwrap(); + let scope = self.get_scope(&scope_id).unwrap(); for child in scope.descendents.borrow().iter() { // Add this node to be explored scopes_to_explore.push(child.clone()); @@ -513,7 +524,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { // Delete all scopes that we found as part of this subtree for node in nodes_to_delete { log::debug!("Removing scope {:#?}", node); - let _scope = self.components.try_remove(node).unwrap(); + let _scope = self.vdom.try_remove(node).unwrap(); // do anything we need to do to delete the scope // I think we need to run the destructors on the hooks // TODO @@ -527,7 +538,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { // [... node] // // The change list stack is left unchanged. - fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) { + fn diff_listeners(&mut self, old: &[&mut Listener<'_>], new: &[&mut Listener<'_>]) { if !old.is_empty() || !new.is_empty() { // self.edits.commit_traversal(); } @@ -547,7 +558,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> { new_l.mounted_node.set(old_l.mounted_node.get()); // if new_l.id != old_l.id { // self.edits.remove_event_listener(event_type); - // // TODO! we need to mess with events and assign them by RealDomNode + // // TODO! we need to mess with events and assign them by ElementId // // self.edits // // .update_event_listener(event_type, new_l.scope, new_l.id) // } @@ -1270,7 +1281,7 @@ enum KeyedPrefixResult { /// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like /// "InsertBefore". struct RealChildIterator<'a> { - scopes: &'a SharedArena, + scopes: &'a SharedResources, // Heuristcally we should never bleed into 4 completely nested fragments/components // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane @@ -1279,7 +1290,7 @@ struct RealChildIterator<'a> { } impl<'a> RealChildIterator<'a> { - fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self { + fn new(starter: &'a VNode<'a>, scopes: &'a SharedResources) -> Self { Self { scopes, stack: smallvec::smallvec![(0, starter)], @@ -1288,9 +1299,9 @@ impl<'a> RealChildIterator<'a> { } impl<'a> Iterator for RealChildIterator<'a> { - type Item = RealDomNode; + type Item = ElementId; - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { let mut should_pop = false; let mut returned_node = None; let mut should_push = None; @@ -1304,7 +1315,7 @@ impl<'a> Iterator for RealChildIterator<'a> { // We've recursed INTO an element/text // We need to recurse *out* of it and move forward to the next should_pop = true; - returned_node = Some(node.dom_id.get()); + returned_node = node.dom_id.get(); } // If we get a fragment we push the next child @@ -1313,7 +1324,7 @@ impl<'a> Iterator for RealChildIterator<'a> { if frag.children.len() == 0 { should_pop = true; - returned_node = Some(node.dom_id.get()); + returned_node = node.dom_id.get(); } if subcount >= frag.children.len() { @@ -1325,11 +1336,16 @@ impl<'a> Iterator for RealChildIterator<'a> { // Immediately abort suspended nodes - can't do anything with them yet // VNodeKind::Suspended => should_pop = true, - VNodeKind::Suspended { .. } => todo!(), + VNodeKind::Suspended { node, .. } => { + todo!() + // *node = node.as_ref().borrow().get().expect("msg"); + } // For components, we load their root and push them onto the stack VNodeKind::Component(sc) => { - let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap(); + let scope = + unsafe { self.scopes.get_scope(sc.ass_scope.get().unwrap()) }.unwrap(); + // let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap(); // Simply swap the current node on the stack with the root of the component *node = scope.root(); diff --git a/packages/core/src/editor.rs b/packages/core/src/editor.rs index c443f884c..5e8a685c8 100644 --- a/packages/core/src/editor.rs +++ b/packages/core/src/editor.rs @@ -5,7 +5,7 @@ //! //! -use crate::{innerlude::ScopeId, RealDomNode}; +use crate::{innerlude::ScopeId, ElementId}; /// The `DomEditor` provides an imperative interface for the Diffing algorithm to plan out its changes. /// @@ -24,7 +24,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> { } // Navigation - pub(crate) fn push(&mut self, root: RealDomNode) { + pub(crate) fn push_root(&mut self, root: ElementId) { let id = root.as_u64(); self.edits.push(PushRoot { id }); } @@ -61,7 +61,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> { // Create #[inline] - pub(crate) fn create_text_node(&mut self, text: &'bump str, id: RealDomNode) { + pub(crate) fn create_text_node(&mut self, text: &'bump str, id: ElementId) { let id = id.as_u64(); self.edits.push(CreateTextNode { text, id }); } @@ -70,7 +70,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> { &mut self, tag: &'static str, ns: Option<&'static str>, - id: RealDomNode, + id: ElementId, ) { let id = id.as_u64(); match ns { @@ -80,7 +80,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> { } // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom - pub(crate) fn create_placeholder(&mut self, id: RealDomNode) { + pub(crate) fn create_placeholder(&mut self, id: ElementId) { let id = id.as_u64(); self.edits.push(CreatePlaceholder { id }); } @@ -91,7 +91,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> { event: &'static str, scope: ScopeId, element_id: usize, - realnode: RealDomNode, + realnode: ElementId, ) { self.edits.push(NewEventListener { scope, diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 66cc0bf5e..9cd6d2c0b 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -6,7 +6,7 @@ use std::{cell::Cell, rc::Rc}; -use crate::innerlude::{RealDomNode, ScopeId}; +use crate::innerlude::{ElementId, HeightMarker, ScopeId}; #[derive(Debug)] pub struct EventTrigger { @@ -14,7 +14,7 @@ pub struct EventTrigger { pub originator: ScopeId, /// The optional real node associated with the trigger - pub real_node_id: Option, + pub real_node_id: Option, /// The type of event pub event: VirtualEvent, @@ -72,7 +72,7 @@ impl EventTrigger { pub fn new( event: VirtualEvent, scope: ScopeId, - mounted_dom_id: Option, + mounted_dom_id: Option, priority: EventPriority, ) -> Self { Self { @@ -105,6 +105,9 @@ pub enum VirtualEvent { // image event has conflicting method types // ImageEvent(event_data::ImageEvent), + SetStateEvent { + height: HeightMarker, + }, // Whenever a task is ready (complete) Dioxus produces this "AsyncEvent" // @@ -117,10 +120,8 @@ pub enum VirtualEvent { // These are more intrusive than the rest SuspenseEvent { hook_idx: usize, - domnode: Rc>, + domnode: Rc>>, }, - - OtherEvent, } pub mod on { @@ -138,7 +139,7 @@ pub mod on { use crate::{ innerlude::NodeFactory, - innerlude::{Attribute, Listener, RealDomNode, VNode}, + innerlude::{Attribute, ElementId, Listener, VNode}, }; use std::cell::Cell; @@ -180,8 +181,8 @@ pub mod on { let bump = &c.bump(); Listener { event: stringify!($name), - mounted_node: bump.alloc(Cell::new(RealDomNode::empty())), - scope: c.scope_ref.our_arena_idx, + mounted_node: Cell::new(None), + scope: c.scope.our_arena_idx, callback: bump.alloc(move |evt: VirtualEvent| match evt { VirtualEvent::$wrapper(event) => callback(event), _ => unreachable!("Downcasted VirtualEvent to wrong event type - this is an internal bug!") diff --git a/packages/core/src/heuristics.rs b/packages/core/src/heuristics.rs index 80c788359..366c29d85 100644 --- a/packages/core/src/heuristics.rs +++ b/packages/core/src/heuristics.rs @@ -1,14 +1,23 @@ use std::collections::HashMap; +use fxhash::FxHashMap; + use crate::FC; -pub(crate) struct HeuristicsEngine { - heuristics: HashMap, +/// Provides heuristics to the "SharedResources" object for improving allocation performance. +/// +/// This heueristic engine records the memory footprint of bump arenas and hook lists for each component. These records are +/// then used later on to optimize the initial allocation for future components. This helps save large allocations later on +/// that would slow down the diffing and initializion process. +/// +/// +pub struct HeuristicsEngine { + heuristics: FxHashMap, } -pub(crate) type FcSlot = *const (); +pub type FcSlot = *const (); -pub(crate) struct Heuristic { +pub struct Heuristic { hooks: u32, bump_size: u64, } @@ -16,7 +25,7 @@ pub(crate) struct Heuristic { impl HeuristicsEngine { pub(crate) fn new() -> Self { Self { - heuristics: HashMap::new(), + heuristics: FxHashMap::default(), } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 6a2fab33f..5d36787e2 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -10,8 +10,8 @@ //! pub use crate::innerlude::{ - format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, EventTrigger, LazyNodes, - NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom, + format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventTrigger, + LazyNodes, NodeFactory, Properties, RealDom, ScopeId, VNode, VNodeKind, VirtualDom, VirtualEvent, FC, }; @@ -38,7 +38,6 @@ pub(crate) mod innerlude { pub use crate::hooklist::*; pub use crate::nodes::*; pub use crate::scope::*; - pub use crate::tasks::*; pub use crate::util::*; pub use crate::virtual_dom::*; @@ -66,6 +65,5 @@ pub mod hooklist; pub mod nodes; pub mod scope; pub mod signals; -pub mod tasks; pub mod util; pub mod virtual_dom; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index d6db0a637..a9e2ea9cc 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -5,7 +5,7 @@ //! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick. use crate::{ events::VirtualEvent, - innerlude::{Context, DomTree, Properties, RealDomNode, Scope, ScopeId, FC}, + innerlude::{empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, FC}, }; use std::{ cell::Cell, @@ -16,7 +16,12 @@ use std::{ pub struct VNode<'src> { pub kind: VNodeKind<'src>, - pub(crate) dom_id: Cell, + + /// + /// ElementId supports NonZero32 and Cell is zero cost, so the size of this field is unaffected + /// + /// + pub(crate) dom_id: Cell>, pub(crate) key: Option<&'src str>, } impl VNode<'_> { @@ -39,7 +44,7 @@ pub enum VNodeKind<'src> { Component(&'src VComponent<'src>), - Suspended { node: Rc> }, + Suspended { node: Rc>> }, } pub struct VText<'src> { @@ -50,7 +55,6 @@ pub struct VText<'src> { pub struct VFragment<'src> { pub children: &'src [VNode<'src>], pub is_static: bool, - pub is_error: bool, } pub trait DioxusElement { @@ -72,7 +76,7 @@ pub struct VElement<'a> { pub namespace: Option<&'static str>, pub static_listeners: bool, - pub listeners: &'a [Listener<'a>], + pub listeners: &'a [&'a mut Listener<'a>], pub static_attrs: bool, pub attributes: &'a [Attribute<'a>], @@ -105,9 +109,9 @@ pub struct Listener<'bump> { pub scope: ScopeId, - pub mounted_node: &'bump mut Cell, + pub mounted_node: Cell>, - pub(crate) callback: &'bump dyn FnMut(VirtualEvent), + pub(crate) callback: &'bump mut dyn FnMut(VirtualEvent), } /// Virtual Components for custom user-defined components @@ -136,20 +140,31 @@ pub struct VComponent<'src> { /// This struct adds metadata to the final VNode about listeners, attributes, and children #[derive(Copy, Clone)] pub struct NodeFactory<'a> { - pub scope_ref: &'a Scope, + pub scope: &'a Scope, pub listener_id: &'a Cell, } impl<'a> NodeFactory<'a> { #[inline] pub fn bump(&self) -> &'a bumpalo::Bump { - &self.scope_ref.cur_frame().bump + &self.scope.cur_frame().bump + } + + pub fn unstable_place_holder() -> VNode<'static> { + VNode { + dom_id: empty_cell(), + key: None, + kind: VNodeKind::Text(VText { + text: "", + is_static: true, + }), + } } /// Used in a place or two to make it easier to build vnodes from dummy text - pub fn static_text(text: &'static str) -> VNode { + pub fn static_text(&self, text: &'static str) -> VNode<'a> { VNode { - dom_id: RealDomNode::empty_cell(), + dom_id: empty_cell(), key: None, kind: VNodeKind::Text(VText { text, @@ -178,7 +193,7 @@ impl<'a> NodeFactory<'a> { pub fn text(&self, args: Arguments) -> VNode<'a> { let (text, is_static) = self.raw_text(args); VNode { - dom_id: RealDomNode::empty_cell(), + dom_id: empty_cell(), key: None, kind: VNodeKind::Text(VText { text, is_static }), } @@ -187,7 +202,7 @@ impl<'a> NodeFactory<'a> { pub fn element( &self, el: impl DioxusElement, - listeners: &'a mut [Listener<'a>], + listeners: &'a mut [&'a mut Listener<'a>], attributes: &'a [Attribute<'a>], children: &'a [VNode<'a>], key: Option<&'a str>, @@ -206,7 +221,7 @@ impl<'a> NodeFactory<'a> { &self, tag: &'static str, namespace: Option<&'static str>, - listeners: &'a mut [Listener], + listeners: &'a mut [&'a mut Listener<'a>], attributes: &'a [Attribute], children: &'a [VNode<'a>], key: Option<&'a str>, @@ -215,15 +230,17 @@ impl<'a> NodeFactory<'a> { // TODO: this code shouldn't necessarily be here of all places // It would make more sense to do this in diffing - let mut queue = self.scope_ref.listeners.borrow_mut(); + let mut queue = self.scope.listeners.borrow_mut(); for listener in listeners.iter_mut() { - let mounted = listener.mounted_node as *mut _; - let callback = listener.callback as *const _ as *mut _; - queue.push((mounted, callback)) + let raw_listener = &mut **listener; + + let long_listener: &mut Listener<'static> = + unsafe { std::mem::transmute(raw_listener) }; + queue.push(long_listener as *mut _) } VNode { - dom_id: RealDomNode::empty_cell(), + dom_id: empty_cell(), key, kind: VNodeKind::Element(self.bump().alloc(VElement { tag_name: tag, @@ -242,10 +259,10 @@ impl<'a> NodeFactory<'a> { pub fn suspended() -> VNode<'static> { VNode { - dom_id: RealDomNode::empty_cell(), + dom_id: empty_cell(), key: None, kind: VNodeKind::Suspended { - node: Rc::new(RealDomNode::empty_cell()), + node: Rc::new(empty_cell()), }, } } @@ -322,7 +339,7 @@ impl<'a> NodeFactory<'a> { VNode { key, - dom_id: Cell::new(RealDomNode::empty()), + dom_id: empty_cell(), kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent { user_fc, comparator, @@ -383,12 +400,11 @@ To help you identify where this error is coming from, we've generated a backtrac } } VNode { - dom_id: RealDomNode::empty_cell(), + dom_id: empty_cell(), key: None, kind: VNodeKind::Fragment(VFragment { children: nodes.into_bump_slice(), is_static: false, - is_error: false, }), } } @@ -444,7 +460,6 @@ impl<'a> IntoVNode<'a> for &VNode<'a> { VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment { children: fragment.children, is_static: fragment.is_static, - is_error: false, }), VNodeKind::Component(component) => VNodeKind::Component(component), @@ -535,7 +550,7 @@ impl<'a> IntoVNode<'a> for Option> { impl IntoVNode<'_> for &'static str { fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { - NodeFactory::static_text(self) + cx.static_text(self) } } impl IntoVNode<'_> for Arguments<'_> { diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 1c6dd4244..01a52fc7b 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -18,40 +18,30 @@ use std::{ /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever /// usecase they might have. pub struct Scope { - // Book-keeping about the arena + // Book-keeping about our spot in the arena pub(crate) parent_idx: Option, - pub(crate) descendents: RefCell>, pub(crate) our_arena_idx: ScopeId, pub(crate) height: u32, + pub(crate) descendents: RefCell>, // Nodes // an internal, highly efficient storage of vnodes pub(crate) frames: ActiveFrame, - pub(crate) child_nodes: &'static [VNode<'static>], pub(crate) caller: Rc, + pub(crate) child_nodes: &'static [VNode<'static>], // Listeners - pub(crate) listeners: RefCell, *mut dyn FnMut(VirtualEvent))>>, + pub(crate) listeners: RefCell>>, pub(crate) listener_idx: Cell, // State pub(crate) hooks: HookList, pub(crate) shared_contexts: RefCell>>, - // Events - pub(crate) event_channel: Rc, - - // Tasks - pub(crate) task_submitter: TaskSubmitter, - - // A reference to the list of components. - // This lets us traverse the component list whenever we need to access our parent or children. - pub(crate) arena_link: SharedArena, + // A reference to the resources shared by all the comonents + pub(crate) vdom: SharedResources, } -// The type of the channel function -type EventChannel = Rc; - // The type of closure that wraps calling components pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> DomTree<'b>; @@ -68,13 +58,16 @@ impl Scope { // Therefore, their lifetimes are connected exclusively to the virtual dom pub fn new<'creator_node>( caller: Rc, + arena_idx: ScopeId, + parent: Option, + height: u32, - event_channel: EventChannel, - arena_link: SharedArena, + child_nodes: &'creator_node [VNode<'creator_node>], - task_submitter: TaskSubmitter, + + vdom: SharedResources, ) -> Self { let child_nodes = unsafe { std::mem::transmute(child_nodes) }; Self { @@ -83,9 +76,7 @@ impl Scope { parent_idx: parent, our_arena_idx: arena_idx, height, - event_channel, - arena_link, - task_submitter, + vdom, listener_idx: Default::default(), frames: ActiveFrame::new(), hooks: Default::default(), @@ -113,7 +104,7 @@ impl Scope { // Remove all the outdated listeners // This is a very dangerous operation - let next_frame = self.frames.old_frame_mut(); + let next_frame = self.frames.prev_frame_mut(); next_frame.bump.reset(); self.listeners.borrow_mut().clear(); @@ -132,7 +123,7 @@ impl Scope { } Some(new_head) => { // the user's component succeeded. We can safely cycle to the next frame - self.frames.old_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; + self.frames.prev_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; self.frames.cycle_frame(); Ok(()) } @@ -162,33 +153,27 @@ impl Scope { let listners = self.listeners.borrow_mut(); - let raw_listener = listners.iter().find(|(domptr, _)| { - let search = unsafe { &**domptr }; - let search_id = search.get(); + let raw_listener = listners.iter().find(|lis| { + let search = unsafe { &mut ***lis }; + let search_id = search.mounted_node.get(); log::info!("searching listener {:#?}", search_id); - match real_node_id { - Some(e) => search_id == e, - None => false, + + match (real_node_id, search_id) { + (Some(e), Some(search_id)) => search_id == e, + _ => false, } }); - match raw_listener { - Some((_node, listener)) => unsafe { - // TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster! - let listener_fn = &mut **listener; - listener_fn(event); - }, - None => todo!(), + if let Some(raw_listener) = raw_listener { + let listener = unsafe { &mut **raw_listener }; + (listener.callback)(event); + } else { + log::warn!("An event was triggered but there was no listener to handle it"); } Ok(()) } - pub(crate) fn submit_task(&self, task: FiberTask) { - log::debug!("Task submitted into scope"); - (self.task_submitter)(task); - } - #[inline] pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { self.frames.current_head_node() @@ -207,18 +192,10 @@ impl Scope { /// Get the root VNode of this component #[inline] pub fn root<'a>(&'a self) -> &'a VNode<'a> { - &self.frames.cur_frame().head_node + &self.frames.current_head_node() } -} - -pub fn errored_fragment() -> VNode<'static> { - VNode { - dom_id: RealDomNode::empty_cell(), - key: None, - kind: VNodeKind::Fragment(VFragment { - children: &[], - is_static: false, - is_error: true, - }), + #[inline] + pub fn child_nodes<'a>(&'a self) -> &'a [VNode<'a>] { + unsafe { std::mem::transmute(self.child_nodes) } } } diff --git a/packages/core/src/tasks.rs b/packages/core/src/tasks.rs deleted file mode 100644 index 875dc99ee..000000000 --- a/packages/core/src/tasks.rs +++ /dev/null @@ -1,137 +0,0 @@ -//! The TaskQueue serves as a centralized async store for all tasks in Dioxus. -//! When a component renders, it may submit an async task to the queue. -//! -//! Then the task complete, it is emitted from the virtual dom in the event loop, which is then fed back into the virtualdom -//! as an event trigger. -//! -//! When a component is scheduled to re-render, the awaing task must be dumped from the queue. -//! -//! This is all pretty unsafe stuff. -//! The major invariant here is that tasks that enter the queue may be invalidated during transitions. - -use std::{ - cell::Cell, - sync::{Arc, RwLock}, -}; - -use futures_util::{stream::FuturesUnordered, Future, Stream, StreamExt}; -use slotmap::{DefaultKey, SlotMap}; - -use crate::innerlude::{EventTrigger, FiberTask, ScopeId}; - -pub type TaskSubmitter = Arc; - -pub struct TaskQueue { - slots: Arc>>, - // slots: Arc>>, - submitter: TaskSubmitter, -} - -impl TaskQueue { - pub fn new() -> Self { - let slots = Arc::new(RwLock::new(FuturesUnordered::new())); - let slots2 = slots.clone(); - - let submitter = Arc::new(move |task| { - let mut slots = slots2.write().unwrap(); - log::debug!("Task submitted into global task queue"); - slots.push(task); - }); - Self { slots, submitter } - } - - pub fn new_submitter(&self) -> TaskSubmitter { - self.submitter.clone() - } - - pub fn submit_task(&mut self, task: FiberTask) { - self.slots.write().unwrap().push(task); - // TaskHandle { key } - } - - pub fn is_empty(&self) -> bool { - self.slots.read().unwrap().is_empty() - } - pub fn len(&self) -> usize { - self.slots.read().unwrap().len() - } - - pub async fn next(&mut self) -> Option { - let mut slots = self.slots.write().unwrap(); - slots.next().await - } -} - -// impl Stream for TaskQueue { -// type Item = EventTrigger; - -// /// We can never be finished polling -// fn poll_next( -// self: Pin<&mut Self>, -// cx: &mut std::task::Context<'_>, -// ) -> std::task::Poll> { -// // let yield_every = self.len(); -// // let mut polled = 0; - -// let mut slots = self.slots.write().unwrap(); -// for (_key, slot) in slots.iter_mut() { -// if slot.dead.get() { -// continue; -// } -// let r = slot.fut; -// // let fut = unsafe { &mut *r }; -// // use futures::{future::Future, poll, FutureExt}; - -// let f2 = fut.as_mut(); -// let w = cx.waker(); -// let mut cx = Context::from_waker(&w); - -// // Pin::new_unchecked(pointer) -// // use std::future::Future; -// match f2.poll(&mut cx) { -// Poll::Ready(_) => { -// let trigger = EventTrigger::new_from_task(slot.originator); -// slot.dead.set(true); -// return Poll::Ready(Some(trigger)); -// } -// Poll::Pending => continue, -// } -// } - -// // we tried polling every active task. -// // give up and relinquish controlto the parent - -// // We have polled a large number of futures in a row without yielding. -// // To ensure we do not starve other tasks waiting on the executor, -// // we yield here, but immediately wake ourselves up to continue. -// // cx.waker().wake_by_ref(); -// return Poll::Pending; -// } -// } - -pub struct TaskHandle { - key: DefaultKey, -} - -pub struct DTask { - fut: FiberTask, - originator: ScopeId, - dead: Cell, -} -impl DTask { - pub fn new(fut: FiberTask, originator: ScopeId) -> Self { - Self { - fut, - originator, - dead: Cell::new(false), - } - } - pub fn debug_new(fut: FiberTask) -> Self { - let originator = ScopeId::default(); - Self { - fut, - originator, - dead: Cell::new(false), - } - } -} diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs index c357651c4..b4409a813 100644 --- a/packages/core/src/util.rs +++ b/packages/core/src/util.rs @@ -1,35 +1,11 @@ -use std::{ - cell::{Cell, RefCell, RefMut}, - rc::Rc, -}; - -use futures_util::StreamExt; -use slotmap::{DefaultKey, Key, KeyData}; +use std::cell::Cell; use crate::innerlude::*; -#[derive(PartialEq, Debug, Clone, Default)] -pub struct EventQueue { - pub queue: Rc>>, -} - -impl EventQueue { - pub fn new_channel(&self, height: u32, idx: ScopeId) -> Rc { - let inner = self.clone(); - let marker = HeightMarker { height, idx }; - Rc::new(move || { - log::debug!("channel updated {:#?}", marker); - inner.queue.as_ref().borrow_mut().push(marker) - }) - } - - pub fn sort_unstable(&self) { - self.queue.borrow_mut().sort_unstable() - } - - pub fn borrow_mut(&self) -> RefMut> { - self.queue.borrow_mut() - } +// create a cell with a "none" value +#[inline] +pub fn empty_cell() -> Cell> { + Cell::new(None as Option) } /// A helper type that lets scopes be ordered by their height @@ -51,41 +27,10 @@ impl PartialOrd for HeightMarker { } } -/// The `RealDomNode` is an ID handle that corresponds to a foreign DOM node. -/// -/// "u64" was chosen for two reasons -/// - 0 cost hashing -/// - use with slotmap and other versioned slot arenas - -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct RealDomNode(pub u64); -impl RealDomNode { - #[inline] - pub fn empty() -> Self { - Self(u64::MIN) - } - #[inline] - pub fn empty_cell() -> Cell { - Cell::new(Self::empty()) - } - #[inline] - pub fn from_u64(id: u64) -> Self { - Self(id) - } - - #[inline] - pub fn as_u64(&self) -> u64 { - self.0 - } -} - -pub struct DebugDom { - counter: u64, -} +pub struct DebugDom {} impl DebugDom { pub fn new() -> Self { - Self { counter: 0 } + Self {} } } @@ -93,9 +38,4 @@ impl<'a> RealDom<'a> for DebugDom { fn raw_node_as_any(&self) -> &mut dyn std::any::Any { todo!() } - - fn request_available_node(&mut self) -> RealDomNode { - self.counter += 1; - RealDomNode::from_u64(self.counter) - } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index db7a26a92..a59705547 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -19,8 +19,7 @@ //! This module includes just the barebones for a complete VirtualDOM API. //! Additional functionality is defined in the respective files. -use crate::tasks::TaskQueue; -use crate::{arena::SharedArena, innerlude::*}; +use crate::{arena::SharedResources, innerlude::*}; use slotmap::DefaultKey; use slotmap::SlotMap; @@ -30,10 +29,6 @@ use std::any::TypeId; use std::cell::RefCell; use std::pin::Pin; -slotmap::new_key_type! { - pub struct ScopeId; -} - /// An integrated virtual node system that progresses events and diffs UI trees. /// Differences are converted into patches which a renderer can use to draw the UI. /// @@ -49,7 +44,7 @@ pub struct VirtualDom { /// /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas /// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually. - pub components: SharedArena, + pub shared: SharedResources, /// The index of the root component /// Should always be the first (gen=0, id=0) @@ -57,21 +52,12 @@ pub struct VirtualDom { pub triggers: RefCell>, - /// All components dump their updates into a queue to be processed - pub event_queue: EventQueue, - - pub tasks: TaskQueue, - - heuristics: HeuristicsEngine, - - root_props: std::pin::Pin>, - - /// Type of the original props. This is stored as TypeId so VirtualDom does not need to be generic. - /// - /// Whenver props need to be updated, an Error will be thrown if the new props do not - /// match the props used to create the VirtualDom. + // for managing the props that were used to create the dom #[doc(hidden)] _root_prop_type: std::any::TypeId, + + #[doc(hidden)] + _root_props: std::pin::Pin>, } // ====================================== @@ -142,37 +128,22 @@ impl VirtualDom { /// let dom = VirtualDom::new(Example); /// ``` pub fn new_with_props(root: FC

, root_props: P) -> Self { - let components = SharedArena::new(SlotMap::::with_key()); + let components = SharedResources::new(); let root_props: Pin> = Box::pin(root_props); let props_ptr = root_props.as_ref().downcast_ref::

().unwrap() as *const P; - // Build a funnel for hooks to send their updates into. The `use_hook` method will call into the update funnel. - let event_queue = EventQueue::default(); - let _event_queue = event_queue.clone(); - let link = components.clone(); - let tasks = TaskQueue::new(); - let submitter = tasks.new_submitter(); - - let base_scope = components - .with(|arena| { - arena.insert_with_key(move |myidx| { - let event_channel = _event_queue.new_channel(0, myidx); - let caller = NodeFactory::create_component_caller(root, props_ptr as *const _); - Scope::new(caller, myidx, None, 0, event_channel, link, &[], submitter) - }) - }) - .unwrap(); + let base_scope = components.insert_scope_with_key(move |myidx| { + let caller = NodeFactory::create_component_caller(root, props_ptr as *const _); + Scope::new(caller, myidx, None, 0, &[], link) + }); Self { base_scope, - event_queue, - components, - root_props, - tasks, - heuristics: HeuristicsEngine::new(), + _root_props: root_props, + shared: components, triggers: Default::default(), _root_prop_type: TypeId::of::

(), } @@ -209,29 +180,28 @@ impl VirtualDom { /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch /// - /// Currently this doesn't do what we want it to do - /// /// The diff machine expects the RealDom's stack to be the root of the application pub fn rebuild<'s, Dom: RealDom<'s>>( &'s mut self, realdom: &mut Dom, edits: &mut Vec>, ) -> Result<()> { - let mut diff_machine = DiffMachine::new( - edits, - realdom, - &self.components, - self.base_scope, - self.event_queue.clone(), - &self.tasks, - ); + let mut diff_machine = DiffMachine::new(edits, realdom, self.base_scope, &self.shared); - let cur_component = self.components.get_mut(self.base_scope).unwrap(); + let cur_component = diff_machine + .get_scope_mut(&self.base_scope) + .expect("The base scope should never be moved"); // We run the component. If it succeeds, then we can diff it and add the changes to the dom. if cur_component.run_scope().is_ok() { let meta = diff_machine.create(cur_component.next_frame()); diff_machine.edits.append_children(meta.added_to_stack); + } else { + // todo: should this be a hard error? + log::warn!( + "Component failed to run succesfully during rebuild. + This does not result in a failed rebuild, but indicates a logic failure within your app." + ); } Ok(()) @@ -242,10 +212,9 @@ impl VirtualDom { /// /// /// - pub fn queue_event(&self, trigger: EventTrigger) -> Result<()> { + pub fn queue_event(&self, trigger: EventTrigger) { let mut triggers = self.triggers.borrow_mut(); triggers.push(trigger); - Ok(()) } /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered. @@ -298,26 +267,18 @@ impl VirtualDom { ) -> Result<()> { let trigger = self.triggers.borrow_mut().pop().expect("failed"); - let mut diff_machine = DiffMachine::new( - edits, - realdom, - &self.components, - trigger.originator, - self.event_queue.clone(), - &self.tasks, - ); + let mut diff_machine = DiffMachine::new(edits, realdom, trigger.originator, &self.shared); match &trigger.event { - VirtualEvent::OtherEvent => todo!(), - // Nothing yet VirtualEvent::AsyncEvent { .. } => {} // Suspense Events! A component's suspended node is updated VirtualEvent::SuspenseEvent { hook_idx, domnode } => { - let scope = self.components.get_mut(trigger.originator).unwrap(); + // Safety: this handler is the only thing that can mutate shared items at this moment in tim + let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap(); - // safety: we are sure that there are no other references to the inner content of this hook + // safety: we are sure that there are no other references to the inner content of suspense hooks let hook = unsafe { scope.hooks.get_mut::(*hook_idx) }.unwrap(); let cx = Context { scope, props: &() }; @@ -325,17 +286,24 @@ impl VirtualDom { // generate the new node! let nodes: Option> = (&hook.callback)(scx); - let nodes = nodes.unwrap_or_else(|| errored_fragment()); - let nodes = scope.cur_frame().bump.alloc(nodes); + match nodes { + None => { + log::warn!("Suspense event came through, but there was no mounted node to update >:("); + } + Some(nodes) => { + let nodes = scope.cur_frame().bump.alloc(nodes); - // push the old node's root onto the stack - diff_machine.edits.push(domnode.get()); + // push the old node's root onto the stack + let real_id = domnode.get().ok_or(Error::NotMounted)?; + diff_machine.edits.push_root(real_id); - // push these new nodes onto the diff machines stack - let meta = diff_machine.create(&*nodes); + // push these new nodes onto the diff machines stack + let meta = diff_machine.create(&*nodes); - // replace the placeholder with the new nodes we just pushed on the stack - diff_machine.edits.replace_with(meta.added_to_stack); + // replace the placeholder with the new nodes we just pushed on the stack + diff_machine.edits.replace_with(meta.added_to_stack); + } + } } // This is the "meat" of our cooperative scheduler @@ -343,12 +311,12 @@ impl VirtualDom { // // We use the reconciler to request new IDs and then commit/uncommit the IDs when the scheduler is finished _ => { - self.components - .get_mut(trigger.originator) + diff_machine + .get_scope_mut(&trigger.originator) .map(|f| f.call_listener(trigger)); // Now, there are events in the queue - let mut updates = self.event_queue.queue.as_ref().borrow_mut(); + let mut updates = self.shared.borrow_queue(); // Order the nodes by their height, we want the nodes with the smallest depth on top // This prevents us from running the same component multiple times @@ -370,8 +338,10 @@ impl VirtualDom { diff_machine.seen_nodes.insert(update.idx.clone()); // Start a new mutable borrow to components - // We are guaranteeed that this scope is unique because we are tracking which nodes have modified - let cur_component = self.components.get_mut(update.idx).unwrap(); + // We are guaranteeed that this scope is unique because we are tracking which nodes have modified in the diff machine + let cur_component = diff_machine + .get_scope_mut(&update.idx) + .expect("Failed to find scope or borrow would be aliasing"); if cur_component.run_scope().is_ok() { let (old, new) = (cur_component.old_frame(), cur_component.next_frame()); @@ -385,7 +355,7 @@ impl VirtualDom { } pub fn base_scope(&self) -> &Scope { - self.components.get(self.base_scope).unwrap() + unsafe { self.shared.get_scope(self.base_scope).unwrap() } } } diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index dbae758e6..6a9636364 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -21,7 +21,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option { "{:}", TextRenderer { cfg: SsrConfig::default(), - root: vdom.components.get(scope).unwrap().root(), + root: vdom.shared.get_scope(scope).unwrap().root(), vdom: Some(vdom) } )) @@ -163,7 +163,7 @@ impl<'a> TextRenderer<'a> { VNodeKind::Component(vcomp) => { let idx = vcomp.ass_scope.get().unwrap(); if let Some(vdom) = self.vdom { - let new_node = vdom.components.get(idx).unwrap().root(); + let new_node = vdom.shared.get_scope(idx).unwrap().root(); self.html_render(new_node, f, il + 1)?; } } diff --git a/packages/web/src/new.rs b/packages/web/src/new.rs index e1138206a..129ffb702 100644 --- a/packages/web/src/new.rs +++ b/packages/web/src/new.rs @@ -13,7 +13,7 @@ use web_sys::{ pub struct WebsysDom { pub stack: Stack, - nodes: slotmap::SlotMap>, + nodes: slotmap::SlotMap, document: Document, root: Element, @@ -51,7 +51,7 @@ impl WebsysDom { let mut nodes = slotmap::SlotMap::with_capacity(1000); - let root_id = nodes.insert(Some(root.clone().dyn_into::().unwrap())); + let root_id = nodes.insert(root.clone().dyn_into::().unwrap()); Self { stack: Stack::with_capacity(10), @@ -101,11 +101,16 @@ impl WebsysDom { let key = DefaultKey::from(KeyData::from_ffi(root)); let domnode = self.nodes.get_mut(key); - let domnode = domnode.unwrap().as_mut().unwrap(); + let real_node: Node = match domnode { + Some(n) => n.clone(), + None => todo!(), + }; + + // let domnode = domnode.unwrap().as_mut().unwrap(); // .expect(&format!("Failed to pop know root: {:#?}", key)) // .unwrap(); - self.stack.push(domnode.clone()); + self.stack.push(real_node); } // drop the node off the stack fn pop(&mut self) { @@ -202,7 +207,7 @@ impl WebsysDom { *self .nodes .get_mut(DefaultKey::from(KeyData::from_ffi(id))) - .unwrap() = Some(textnode); + .unwrap() = textnode; } fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) { @@ -224,7 +229,7 @@ impl WebsysDom { let id = DefaultKey::from(KeyData::from_ffi(id)); self.stack.push(el.clone()); - *self.nodes.get_mut(id).unwrap() = Some(el); + *self.nodes.get_mut(id).unwrap() = el; // let nid = self.node_counter.?next(); // let nid = self.nodes.insert(el).data().as_ffi(); // log::debug!("Called [`create_element`]: {}, {:?}", tag, nid); @@ -344,11 +349,11 @@ impl WebsysDom { } impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom { - fn request_available_node(&mut self) -> RealDomNode { - let key = self.nodes.insert(None); - log::debug!("making new key: {:#?}", key); - RealDomNode(key.data().as_ffi()) - } + // fn request_available_node(&mut self) -> RealDomNode { + // let key = self.nodes.insert(None); + // log::debug!("making new key: {:#?}", key); + // RealDomNode(key.data().as_ffi()) + // } fn raw_node_as_any(&self) -> &mut dyn std::any::Any { todo!()