wip: move things into a "shared" object

This commit is contained in:
Jonathan Kelley committed 2021-07-23 10:27:43 -04:00
1 parent 22f894e6b9
commit f644d7c441
19 files changed
+542 -698

No files matched your search

+17 -15
View File
@@ -109,20 +109,29 @@ impl Parse for Element<AS_HTML> {
let name_str = name.to_string();
stream.parse::<Token![=]>()?;
if name_str.starts_with("on") {
todo!()
let inner;
syn::braced!(inner in stream);
let toks = inner.parse::<Expr>()?;
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::<LitStr>().unwrap();
AttrType::BumpText(rawtext)
} else {
let toks = stream.parse::<Expr>()?;
// like JSX, we expect raw expressions
let inner;
syn::braced!(inner in stream);
let toks = inner.parse::<Expr>()?;
AttrType::FieldTokens(toks)
};
attributes.push(ElementAttr {
@@ -134,13 +143,6 @@ impl Parse for Element<AS_HTML> {
}
}
};
// if stream.peek(LitStr) {
// } else {
// }
// if name_str.starts_with("on") {}
// attributes.push(stream.parse()?);
}
stream.parse::<Token![>]>()?;
+1 -1
View File
@@ -1,3 +1,3 @@
{
"rust-analyzer.inlayHints.enable": false
"rust-analyzer.inlayHints.enable": true
}
+111 -37
View File
@@ -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<T> = Rc<RefCell<T>>;
/// These are resources shared among all the components and the virtualdom itself
#[derive(Clone)]
pub struct SharedArena {
pub components: Rc<UnsafeCell<ScopeMap>>,
}
pub type ScopeMap = SlotMap<ScopeId, Scope>;
pub struct SharedResources {
pub components: Rc<UnsafeCell<SlotMap<ScopeId, Scope>>>,
enum MutStatus {
Immut,
Mut,
pub event_queue: Shared<Vec<HeightMarker>>,
pub events: Shared<Vec<EventTrigger>>,
pub(crate) heuristics: Shared<HeuristicsEngine>,
pub(crate) tasks: Shared<FuturesUnordered<FiberTask>>,
/// 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<SlotMap<ElementId, ()>>,
pub task_setter: Rc<dyn Fn(ScopeId)>,
}
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::<ScopeId, Scope>::with_capacity_and_key(20),
));
let raw_elements = SlotMap::<ElementId, ()>::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<T>(&self, f: impl FnOnce(&mut ScopeMap) -> T) -> Result<T> {
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<Vec<HeightMarker>> {
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<dyn Fn(ScopeId)> {
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) {}
}
+71 -49
View File
@@ -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<usize>,
pub generation: Cell<usize>,
// 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);
}
}
-91
View File
@@ -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
}
}
+43 -1
View File
@@ -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<T: Properties>(_: FC<T>) -> 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())))
+28 -27
View File
@@ -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<dyn Fn() + 'static> {
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<dyn Fn(ScopeId)> {
self.scope.vdom.schedule_update()
}
pub fn schedule_effect(&self) -> Rc<dyn Fn() + 'static> {
@@ -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<Out, Fut, Init>(
self,
task_initializer: Init,
) -> (TaskHandle<'src>, &'src Option<Out>)
) -> (&'src TaskHandle, &'src Option<Out>)
where
Out: 'static,
Fut: Future<Output = Out> + 'static,
Init: FnOnce() -> Fut + 'src,
{
struct TaskHook<T> {
handle: TaskHandle,
task_dump: Rc<RefCell<Option<T>>>,
value: Option<T>,
}
@@ -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<RefCell<Option<Box<dyn Any>>>>,
pub callback: SuspendedCallback,
pub dom_node_id: Rc<Cell<RealDomNode>>,
pub dom_node_id: Rc<Cell<Option<ElementId>>>,
}
type SuspendedCallback = Box<dyn for<'a> 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) {}
}
+91 -75
View File
@@ -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<ScopeId>,
pub event_queue: EventQueue,
pub seen_nodes: FxHashSet<ScopeId>,
}
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<DomEdit<'bump>>,
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<RealDomNode> {
fn next(&mut self) -> Option<ElementId> {
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();
+6 -6
View File
@@ -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,
+10 -9
View File
@@ -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<RealDomNode>,
pub real_node_id: Option<ElementId>,
/// The type of event
pub event: VirtualEvent,
@@ -72,7 +72,7 @@ impl EventTrigger {
pub fn new(
event: VirtualEvent,
scope: ScopeId,
mounted_dom_id: Option<RealDomNode>,
mounted_dom_id: Option<ElementId>,
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<Cell<RealDomNode>>,
domnode: Rc<Cell<Option<ElementId>>>,
},
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!")
+14 -5
View File
@@ -1,14 +1,23 @@
use std::collections::HashMap;
use fxhash::FxHashMap;
use crate::FC;
pub(crate) struct HeuristicsEngine {
heuristics: HashMap<FcSlot, Heuristic>,
/// 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<FcSlot, Heuristic>,
}
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(),
}
}
+2 -4
View File
@@ -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;
+41 -26
View File
@@ -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<RealDomNode>,
///
/// ElementId supports NonZero32 and Cell is zero cost, so the size of this field is unaffected
///
///
pub(crate) dom_id: Cell<Option<ElementId>>,
pub(crate) key: Option<&'src str>,
}
impl VNode<'_> {
@@ -39,7 +44,7 @@ pub enum VNodeKind<'src> {
Component(&'src VComponent<'src>),
Suspended { node: Rc<Cell<RealDomNode>> },
Suspended { node: Rc<Cell<Option<ElementId>>> },
}
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<RealDomNode>,
pub mounted_node: Cell<Option<ElementId>>,
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<usize>,
}
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<VNode<'a>> {
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<'_> {
+31 -54
View File
@@ -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<ScopeId>,
pub(crate) descendents: RefCell<HashSet<ScopeId>>,
pub(crate) our_arena_idx: ScopeId,
pub(crate) height: u32,
pub(crate) descendents: RefCell<HashSet<ScopeId>>,
// Nodes
// an internal, highly efficient storage of vnodes
pub(crate) frames: ActiveFrame,
pub(crate) child_nodes: &'static [VNode<'static>],
pub(crate) caller: Rc<WrappedCaller>,
pub(crate) child_nodes: &'static [VNode<'static>],
// Listeners
pub(crate) listeners: RefCell<Vec<(*mut Cell<RealDomNode>, *mut dyn FnMut(VirtualEvent))>>,
pub(crate) listeners: RefCell<Vec<*mut Listener<'static>>>,
pub(crate) listener_idx: Cell<usize>,
// State
pub(crate) hooks: HookList,
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
// Events
pub(crate) event_channel: Rc<dyn Fn() + 'static>,
// 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<dyn Fn()>;
// 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<WrappedCaller>,
arena_idx: ScopeId,
parent: Option<ScopeId>,
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) }
}
}
-137
View File
@@ -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<dyn Fn(FiberTask)>;
pub struct TaskQueue {
slots: Arc<RwLock<FuturesUnordered<FiberTask>>>,
// slots: Arc<RwLock<SlotMap<DefaultKey, DTask>>>,
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<EventTrigger> {
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<Option<Self::Item>> {
// // 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<bool>,
}
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),
}
}
}
+7 -67
View File
@@ -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<RefCell<Vec<HeightMarker>>>,
}
impl EventQueue {
pub fn new_channel(&self, height: u32, idx: ScopeId) -> Rc<dyn Fn()> {
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<Vec<HeightMarker>> {
self.queue.borrow_mut()
}
// create a cell with a "none" value
#[inline]
pub fn empty_cell() -> Cell<Option<ElementId>> {
Cell::new(None as Option<ElementId>)
}
/// 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<Self> {
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)
}
}
+51 -81
View File
@@ -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<Vec<EventTrigger>>,
/// 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<Box<dyn std::any::Any>>,
/// 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<Box<dyn std::any::Any>>,
}
// ======================================
@@ -142,37 +128,22 @@ impl VirtualDom {
/// let dom = VirtualDom::new(Example);
/// ```
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let components = SharedArena::new(SlotMap::<ScopeId, Scope>::with_key());
let components = SharedResources::new();
let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
let props_ptr = root_props.as_ref().downcast_ref::<P>().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::<P>(),
}
@@ -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<DomEdit<'s>>,
) -> 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::<SuspenseHook>(*hook_idx) }.unwrap();
let cx = Context { scope, props: &() };
@@ -325,17 +286,24 @@ impl VirtualDom {
// generate the new node!
let nodes: Option<VNode<'s>> = (&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() }
}
}
+2 -2
View File
@@ -21,7 +21,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
"{:}",
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)?;
}
}
+16 -11
View File
@@ -13,7 +13,7 @@ use web_sys::{
pub struct WebsysDom {
pub stack: Stack,
nodes: slotmap::SlotMap<DefaultKey, Option<Node>>,
nodes: slotmap::SlotMap<DefaultKey, Node>,
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::<Node>().unwrap()));
let root_id = nodes.insert(root.clone().dyn_into::<Node>().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!()