Merge pull request #44 from DioxusLabs/jk/memoization
feat: memoization safety
This commit is contained in:
15 files changed
+943
-704
No files matched your search
@@ -20,7 +20,7 @@ struct Borrowed<'a> {
|
|||||||
|
|
||||||
fn App2<'a>(cx: Scope<'a, Borrowed<'a>>) -> Element {
|
fn App2<'a>(cx: Scope<'a, Borrowed<'a>>) -> Element {
|
||||||
let g = eat2(&cx);
|
let g = eat2(&cx);
|
||||||
todo!()
|
rsx!(cx, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eat2(s: &ScopeState) {}
|
fn eat2(s: &ScopeState) {}
|
||||||
|
|||||||
@@ -4,7 +4,3 @@ use dioxus_core_macro::*;
|
|||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
fn App(cx: Scope<()>) -> Element {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
@@ -6,16 +6,15 @@ use dioxus_core_macro::*;
|
|||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _ = VirtualDom::new(Parent);
|
let _ = VirtualDom::new(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Parent(cx: Scope<()>) -> Element {
|
fn parent(cx: Scope<()>) -> Element {
|
||||||
let value = cx.use_hook(|_| String::new(), |f| f);
|
let value = cx.use_hook(|_| String::new(), |f| f);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
Child { name: value }
|
child( name: value )
|
||||||
Fragment { "asd" }
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -25,11 +24,11 @@ struct ChildProps<'a> {
|
|||||||
name: &'a str,
|
name: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
|
fn child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 { "it's nested" }
|
h1 { "it's nested" }
|
||||||
Child2 { name: cx.props.name }
|
grandchild( name: cx.props.name )
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -39,8 +38,26 @@ struct Grandchild<'a> {
|
|||||||
name: &'a str,
|
name: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Child2<'a>(cx: Scope<'a, Grandchild<'a>>) -> Element {
|
fn grandchild<'a>(cx: Scope<'a, Grandchild>) -> Element<'a> {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div { "Hello {cx.props.name}!" }
|
div { "Hello {cx.props.name}!" }
|
||||||
|
great_grandchild( name: cx.props.name )
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn great_grandchild<'a>(cx: Scope<'a, Grandchild>) -> Element<'a> {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
h1 { "it's nested" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
can we implement memoization as a wrapper or something? Like we just intercept the
|
||||||
|
render function?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
+77
-109
@@ -88,6 +88,8 @@
|
|||||||
//! More info on how to improve this diffing algorithm:
|
//! More info on how to improve this diffing algorithm:
|
||||||
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
||||||
|
|
||||||
|
use std::borrow::BorrowMut;
|
||||||
|
|
||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
use fxhash::{FxHashMap, FxHashSet};
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
@@ -239,15 +241,6 @@ impl<'bump> DiffStack<'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'bump> DiffState<'bump> {
|
impl<'bump> DiffState<'bump> {
|
||||||
pub fn diff_scope(&mut self, id: ScopeId) {
|
|
||||||
let (old, new) = (self.scopes.wip_head(id), self.scopes.fin_head(id));
|
|
||||||
self.stack.push(DiffInstruction::Diff { old, new });
|
|
||||||
self.stack.scope_stack.push(id);
|
|
||||||
let scope = self.scopes.get_scope(id).unwrap();
|
|
||||||
self.stack.element_stack.push(scope.container);
|
|
||||||
self.work(|| false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Progress the diffing for this "fiber"
|
/// Progress the diffing for this "fiber"
|
||||||
///
|
///
|
||||||
/// This method implements a depth-first iterative tree traversal.
|
/// This method implements a depth-first iterative tree traversal.
|
||||||
@@ -415,7 +408,7 @@ impl<'bump> DiffState<'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo: the settext optimization
|
// todo: the settext optimization
|
||||||
|
//
|
||||||
// if children.len() == 1 {
|
// if children.len() == 1 {
|
||||||
// if let VNode::Text(vtext) = children[0] {
|
// if let VNode::Text(vtext) = children[0] {
|
||||||
// self.mutations.set_text(vtext.text, real_id.as_u64());
|
// self.mutations.set_text(vtext.text, real_id.as_u64());
|
||||||
@@ -436,40 +429,31 @@ impl<'bump> DiffState<'bump> {
|
|||||||
let parent_idx = self.stack.current_scope().unwrap();
|
let parent_idx = self.stack.current_scope().unwrap();
|
||||||
|
|
||||||
// Insert a new scope into our component list
|
// Insert a new scope into our component list
|
||||||
let parent_scope = self.scopes.get_scope(parent_idx).unwrap();
|
let props: Box<dyn AnyProps + 'bump> = vcomponent.props.borrow_mut().take().unwrap();
|
||||||
let height = parent_scope.height + 1;
|
let props: Box<dyn AnyProps + 'static> = unsafe { std::mem::transmute(props) };
|
||||||
let subtree = parent_scope.subtree.get();
|
let new_idx = self.scopes.new_with_key(
|
||||||
|
vcomponent.user_fc,
|
||||||
let parent_scope = self.scopes.get_scope_raw(parent_idx);
|
props,
|
||||||
let caller = unsafe { std::mem::transmute(vcomponent.caller as *const _) };
|
Some(parent_idx),
|
||||||
let fc_ptr = vcomponent.user_fc;
|
self.stack.element_stack.last().copied().unwrap(),
|
||||||
|
0,
|
||||||
let container = *self.stack.element_stack.last().unwrap();
|
);
|
||||||
|
|
||||||
let new_idx =
|
|
||||||
self.scopes
|
|
||||||
.new_with_key(fc_ptr, caller, parent_scope, container, height, subtree);
|
|
||||||
|
|
||||||
// Actually initialize the caller's slot with the right address
|
// Actually initialize the caller's slot with the right address
|
||||||
vcomponent.associated_scope.set(Some(new_idx));
|
vcomponent.scope.set(Some(new_idx));
|
||||||
|
|
||||||
if !vcomponent.can_memoize {
|
match vcomponent.can_memoize {
|
||||||
let cur_scope = self.scopes.get_scope(parent_idx).unwrap();
|
true => {
|
||||||
let extended = unsafe { std::mem::transmute(vcomponent) };
|
// todo: implement promotion logic. save us from boxing props that we don't need
|
||||||
cur_scope.items.borrow_mut().borrowed_props.push(extended);
|
}
|
||||||
} else {
|
false => {
|
||||||
// the props are currently bump allocated but we need to move them to the heap
|
// track this component internally so we know the right drop order
|
||||||
|
let cur_scope = self.scopes.get_scope(parent_idx).unwrap();
|
||||||
|
let extended = unsafe { std::mem::transmute(vcomponent) };
|
||||||
|
cur_scope.items.borrow_mut().borrowed_props.push(extended);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add noderefs to current noderef list Noderefs
|
|
||||||
let _new_component = self.scopes.get_scope(new_idx).unwrap();
|
|
||||||
|
|
||||||
log::debug!(
|
|
||||||
"initializing component {:?} with height {:?}",
|
|
||||||
new_idx,
|
|
||||||
height + 1
|
|
||||||
);
|
|
||||||
|
|
||||||
// Run the scope for one iteration to initialize it
|
// Run the scope for one iteration to initialize it
|
||||||
self.scopes.run_scope(new_idx);
|
self.scopes.run_scope(new_idx);
|
||||||
|
|
||||||
@@ -477,11 +461,6 @@ impl<'bump> DiffState<'bump> {
|
|||||||
let nextnode = self.scopes.fin_head(new_idx);
|
let nextnode = self.scopes.fin_head(new_idx);
|
||||||
self.stack.create_component(new_idx, nextnode);
|
self.stack.create_component(new_idx, nextnode);
|
||||||
|
|
||||||
// todo: subtrees
|
|
||||||
// if new_component.is_subtree_root.get() {
|
|
||||||
// self.stack.push_subtree();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Finally, insert this scope as a seen node.
|
// Finally, insert this scope as a seen node.
|
||||||
self.mutations.dirty_scopes.insert(new_idx);
|
self.mutations.dirty_scopes.insert(new_idx);
|
||||||
}
|
}
|
||||||
@@ -494,15 +473,23 @@ impl<'bump> DiffState<'bump> {
|
|||||||
use VNode::*;
|
use VNode::*;
|
||||||
match (old_node, new_node) {
|
match (old_node, new_node) {
|
||||||
// Check the most common cases first
|
// Check the most common cases first
|
||||||
|
// these are *actual* elements, not wrappers around lists
|
||||||
(Text(old), Text(new)) => {
|
(Text(old), Text(new)) => {
|
||||||
self.diff_text_nodes(old, new, old_node, new_node);
|
self.diff_text_nodes(old, new, old_node, new_node);
|
||||||
}
|
}
|
||||||
|
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
|
||||||
|
(Placeholder(old), Placeholder(new)) => {
|
||||||
|
if let Some(root) = old.dom_id.get() {
|
||||||
|
self.scopes.update_node(new_node, root);
|
||||||
|
new.dom_id.set(Some(root))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These two sets are pointers to nodes but are not actually nodes themselves
|
||||||
(Component(old), Component(new)) => {
|
(Component(old), Component(new)) => {
|
||||||
self.diff_component_nodes(old_node, new_node, *old, *new)
|
self.diff_component_nodes(old_node, new_node, *old, *new)
|
||||||
}
|
}
|
||||||
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
|
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
|
||||||
(Placeholder(old), Placeholder(new)) => self.diff_placeholder_nodes(old, new),
|
|
||||||
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
|
|
||||||
|
|
||||||
// The normal pathway still works, but generates slightly weird instructions
|
// The normal pathway still works, but generates slightly weird instructions
|
||||||
// This pathway just ensures we get create and replace
|
// This pathway just ensures we get create and replace
|
||||||
@@ -708,21 +695,14 @@ impl<'bump> DiffState<'bump> {
|
|||||||
old: &'bump VComponent<'bump>,
|
old: &'bump VComponent<'bump>,
|
||||||
new: &'bump VComponent<'bump>,
|
new: &'bump VComponent<'bump>,
|
||||||
) {
|
) {
|
||||||
let scope_addr = old.associated_scope.get().unwrap();
|
let scope_addr = old.scope.get().unwrap();
|
||||||
|
|
||||||
log::debug!(
|
|
||||||
"Diffing components. old_scope: {:?}, old_addr: {:?}, new_addr: {:?}",
|
|
||||||
scope_addr,
|
|
||||||
old.user_fc,
|
|
||||||
new.user_fc
|
|
||||||
);
|
|
||||||
|
|
||||||
// Make sure we're dealing with the same component (by function pointer)
|
// Make sure we're dealing with the same component (by function pointer)
|
||||||
if old.user_fc == new.user_fc {
|
if old.user_fc == new.user_fc {
|
||||||
self.stack.scope_stack.push(scope_addr);
|
self.stack.scope_stack.push(scope_addr);
|
||||||
|
|
||||||
// Make sure the new component vnode is referencing the right scope id
|
// Make sure the new component vnode is referencing the right scope id
|
||||||
new.associated_scope.set(Some(scope_addr));
|
new.scope.set(Some(scope_addr));
|
||||||
|
|
||||||
// make sure the component's caller function is up to date
|
// make sure the component's caller function is up to date
|
||||||
let scope = self
|
let scope = self
|
||||||
@@ -730,18 +710,41 @@ impl<'bump> DiffState<'bump> {
|
|||||||
.get_scope(scope_addr)
|
.get_scope(scope_addr)
|
||||||
.unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
|
.unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
|
||||||
|
|
||||||
scope.caller.set(unsafe { std::mem::transmute(new.caller) });
|
// take the new props out regardless
|
||||||
|
// when memoizing, push to the existing scope if memoization happens
|
||||||
|
let new_props = new.props.borrow_mut().take().unwrap();
|
||||||
|
|
||||||
// React doesn't automatically memoize, but we do.
|
let should_run = {
|
||||||
let props_are_the_same = old.comparator.unwrap();
|
if old.can_memoize {
|
||||||
|
let props_are_the_same = unsafe {
|
||||||
|
scope
|
||||||
|
.props
|
||||||
|
.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.memoize(new_props.as_ref())
|
||||||
|
};
|
||||||
|
!props_are_the_same || self.force_diff
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if self.force_diff || !props_are_the_same(new) {
|
if should_run {
|
||||||
|
let _old_props = scope
|
||||||
|
.props
|
||||||
|
.replace(unsafe { std::mem::transmute(Some(new_props)) });
|
||||||
|
|
||||||
|
// this should auto drop the previous props
|
||||||
self.scopes.run_scope(scope_addr);
|
self.scopes.run_scope(scope_addr);
|
||||||
self.diff_node(
|
self.diff_node(
|
||||||
self.scopes.wip_head(scope_addr),
|
self.scopes.wip_head(scope_addr),
|
||||||
self.scopes.fin_head(scope_addr),
|
self.scopes.fin_head(scope_addr),
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
|
// memoization has taken place
|
||||||
|
drop(new_props);
|
||||||
|
};
|
||||||
|
|
||||||
self.stack.scope_stack.pop();
|
self.stack.scope_stack.pop();
|
||||||
} else {
|
} else {
|
||||||
@@ -764,11 +767,6 @@ impl<'bump> DiffState<'bump> {
|
|||||||
self.diff_children(old.children, new.children);
|
self.diff_children(old.children, new.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
// probably will get inlined
|
|
||||||
fn diff_placeholder_nodes(&mut self, old: &'bump VPlaceholder, new: &'bump VPlaceholder) {
|
|
||||||
new.dom_id.set(old.dom_id.get())
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================
|
// =============================================
|
||||||
// Utilities for creating new diff instructions
|
// Utilities for creating new diff instructions
|
||||||
// =============================================
|
// =============================================
|
||||||
@@ -792,33 +790,8 @@ impl<'bump> DiffState<'bump> {
|
|||||||
// Remember, fragments can never be empty (they always have a single child)
|
// Remember, fragments can never be empty (they always have a single child)
|
||||||
match (old, new) {
|
match (old, new) {
|
||||||
([], []) => {}
|
([], []) => {}
|
||||||
([], _) => {
|
([], _) => self.stack.create_children(new, MountType::Append),
|
||||||
// we need to push the
|
(_, []) => self.remove_nodes(old, true),
|
||||||
self.stack.create_children(new, MountType::Append);
|
|
||||||
}
|
|
||||||
(_, []) => {
|
|
||||||
self.remove_nodes(old, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: placeholders explicitly replace fragments
|
|
||||||
//
|
|
||||||
// ([VNode::Placeholder(old_anchor)], [VNode::Placeholder(new_anchor)]) => {
|
|
||||||
// old_anchor.dom_id.set(new_anchor.dom_id.get());
|
|
||||||
// }
|
|
||||||
// ([VNode::Placeholder(_)], _) => {
|
|
||||||
// self.stack
|
|
||||||
// .create_children(new, MountType::Replace { old: &old[0] });
|
|
||||||
// }
|
|
||||||
// (_, [VNode::Placeholder(_)]) => {
|
|
||||||
// let new: &'bump VNode<'bump> = &new[0];
|
|
||||||
// if let Some(first_old) = old.get(0) {
|
|
||||||
// self.remove_nodes(&old[1..], true);
|
|
||||||
// self.stack
|
|
||||||
// .create_node(new, MountType::Replace { old: first_old });
|
|
||||||
// } else {
|
|
||||||
// self.stack.create_node(new, MountType::Append {});
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
_ => {
|
_ => {
|
||||||
let new_is_keyed = new[0].key().is_some();
|
let new_is_keyed = new[0].key().is_some();
|
||||||
let old_is_keyed = old[0].key().is_some();
|
let old_is_keyed = old[0].key().is_some();
|
||||||
@@ -1216,7 +1189,7 @@ impl<'bump> DiffState<'bump> {
|
|||||||
search_node = frag.children.last();
|
search_node = frag.children.last();
|
||||||
}
|
}
|
||||||
VNode::Component(el) => {
|
VNode::Component(el) => {
|
||||||
let scope_id = el.associated_scope.get().unwrap();
|
let scope_id = el.scope.get().unwrap();
|
||||||
search_node = Some(self.scopes.root_node(scope_id));
|
search_node = Some(self.scopes.root_node(scope_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1233,7 +1206,7 @@ impl<'bump> DiffState<'bump> {
|
|||||||
search_node = Some(&frag.children[0]);
|
search_node = Some(&frag.children[0]);
|
||||||
}
|
}
|
||||||
VNode::Component(el) => {
|
VNode::Component(el) => {
|
||||||
let scope_id = el.associated_scope.get().unwrap();
|
let scope_id = el.scope.get().unwrap();
|
||||||
search_node = Some(self.scopes.root_node(scope_id));
|
search_node = Some(self.scopes.root_node(scope_id));
|
||||||
}
|
}
|
||||||
VNode::Text(t) => break t.dom_id.get(),
|
VNode::Text(t) => break t.dom_id.get(),
|
||||||
@@ -1250,8 +1223,6 @@ impl<'bump> DiffState<'bump> {
|
|||||||
.try_mounted_id()
|
.try_mounted_id()
|
||||||
.unwrap_or_else(|| panic!("broke on {:?}", old));
|
.unwrap_or_else(|| panic!("broke on {:?}", old));
|
||||||
|
|
||||||
log::debug!("element parent is {:?}", el.parent_id.get());
|
|
||||||
|
|
||||||
self.mutations.replace_with(id, nodes_created as u32);
|
self.mutations.replace_with(id, nodes_created as u32);
|
||||||
self.remove_nodes(el.children, false);
|
self.remove_nodes(el.children, false);
|
||||||
}
|
}
|
||||||
@@ -1270,11 +1241,11 @@ impl<'bump> DiffState<'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VNode::Component(c) => {
|
VNode::Component(c) => {
|
||||||
let node = self.scopes.fin_head(c.associated_scope.get().unwrap());
|
let node = self.scopes.fin_head(c.scope.get().unwrap());
|
||||||
self.replace_node(node, nodes_created);
|
self.replace_node(node, nodes_created);
|
||||||
|
|
||||||
let scope_id = c.associated_scope.get().unwrap();
|
let scope_id = c.scope.get().unwrap();
|
||||||
log::debug!("Destroying scope {:?}", scope_id);
|
|
||||||
self.scopes.try_remove(scope_id).unwrap();
|
self.scopes.try_remove(scope_id).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1282,7 +1253,7 @@ impl<'bump> DiffState<'bump> {
|
|||||||
|
|
||||||
/// schedules nodes for garbage collection and pushes "remove" to the mutation stack
|
/// schedules nodes for garbage collection and pushes "remove" to the mutation stack
|
||||||
/// remove can happen whenever
|
/// remove can happen whenever
|
||||||
fn remove_nodes(
|
pub(crate) fn remove_nodes(
|
||||||
&mut self,
|
&mut self,
|
||||||
nodes: impl IntoIterator<Item = &'bump VNode<'bump>>,
|
nodes: impl IntoIterator<Item = &'bump VNode<'bump>>,
|
||||||
gen_muts: bool,
|
gen_muts: bool,
|
||||||
@@ -1323,20 +1294,17 @@ impl<'bump> DiffState<'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VNode::Component(c) => {
|
VNode::Component(c) => {
|
||||||
self.destroy_vomponent(c, gen_muts);
|
let scope_id = c.scope.get().unwrap();
|
||||||
|
let root = self.scopes.root_node(scope_id);
|
||||||
|
self.remove_nodes(Some(root), gen_muts);
|
||||||
|
|
||||||
|
log::debug!("Destroying scope {:?}", scope_id);
|
||||||
|
self.scopes.try_remove(scope_id).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn destroy_vomponent(&mut self, vc: &VComponent, gen_muts: bool) {
|
|
||||||
let scope_id = vc.associated_scope.get().unwrap();
|
|
||||||
let root = self.scopes.root_node(scope_id);
|
|
||||||
self.remove_nodes(Some(root), gen_muts);
|
|
||||||
log::debug!("Destroying scope {:?}", scope_id);
|
|
||||||
self.scopes.try_remove(scope_id).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a listener closure to a scope during diff.
|
/// Adds a listener closure to a scope during diff.
|
||||||
fn attach_listener_to_scope(&mut self, listener: &'bump Listener<'bump>, scope: &ScopeState) {
|
fn attach_listener_to_scope(&mut self, listener: &'bump Listener<'bump>, scope: &ScopeState) {
|
||||||
let long_listener = unsafe { std::mem::transmute(listener) };
|
let long_listener = unsafe { std::mem::transmute(listener) };
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ pub(crate) mod diff;
|
|||||||
pub(crate) mod lazynodes;
|
pub(crate) mod lazynodes;
|
||||||
pub(crate) mod mutations;
|
pub(crate) mod mutations;
|
||||||
pub(crate) mod nodes;
|
pub(crate) mod nodes;
|
||||||
pub(crate) mod scope;
|
pub(crate) mod scopes;
|
||||||
pub(crate) mod scopearena;
|
|
||||||
pub(crate) mod virtual_dom;
|
pub(crate) mod virtual_dom;
|
||||||
|
|
||||||
pub(crate) mod innerlude {
|
pub(crate) mod innerlude {
|
||||||
@@ -16,8 +15,8 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::lazynodes::*;
|
pub use crate::lazynodes::*;
|
||||||
pub use crate::mutations::*;
|
pub use crate::mutations::*;
|
||||||
pub use crate::nodes::*;
|
pub use crate::nodes::*;
|
||||||
pub use crate::scope::*;
|
pub use crate::scopes::*;
|
||||||
pub(crate) use crate::scopearena::*;
|
pub(crate) use crate::scopes::*;
|
||||||
pub use crate::virtual_dom::*;
|
pub use crate::virtual_dom::*;
|
||||||
|
|
||||||
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
|
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
|
||||||
@@ -61,7 +60,7 @@ pub(crate) mod innerlude {
|
|||||||
/// // ...
|
/// // ...
|
||||||
/// };
|
/// };
|
||||||
/// ```
|
/// ```
|
||||||
pub type Component<P> = for<'a> fn(Scope<'a, P>) -> Element<'a>;
|
pub type Component<P> = fn(Scope<P>) -> Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
@@ -86,3 +85,12 @@ pub mod exports {
|
|||||||
pub use bumpalo;
|
pub use bumpalo;
|
||||||
pub use futures_channel;
|
pub use futures_channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Functions that wrap unsafe functionality to prevent us from misusing it at the callsite
|
||||||
|
pub(crate) mod unsafe_utils {
|
||||||
|
use crate::VNode;
|
||||||
|
|
||||||
|
pub unsafe fn extend_vnode<'a, 'b>(node: &'a VNode<'a>) -> &'b VNode<'b> {
|
||||||
|
std::mem::transmute(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,6 +45,11 @@ pub enum DomEdit<'bump> {
|
|||||||
many: u32,
|
many: u32,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// // save a possibly-fragment node as a template
|
||||||
|
// SaveAsTemplate {
|
||||||
|
// many: u32,
|
||||||
|
// },
|
||||||
|
|
||||||
// "Root" refers to the item directly
|
// "Root" refers to the item directly
|
||||||
// it's a waste of an instruction to push the root directly
|
// it's a waste of an instruction to push the root directly
|
||||||
ReplaceWith {
|
ReplaceWith {
|
||||||
|
|||||||
+95
-82
@@ -6,6 +6,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
innerlude::{Element, Properties, Scope, ScopeId, ScopeState},
|
innerlude::{Element, Properties, Scope, ScopeId, ScopeState},
|
||||||
lazynodes::LazyNodes,
|
lazynodes::LazyNodes,
|
||||||
|
Component,
|
||||||
};
|
};
|
||||||
use bumpalo::{boxed::Box as BumpBox, Bump};
|
use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -84,7 +85,7 @@ pub enum VNode<'src> {
|
|||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// fn Example(cx: Scope<()>) -> Element {
|
/// fn Example(cx: Scope<()>) -> Element {
|
||||||
/// todo!()
|
/// ...
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let mut vdom = VirtualDom::new();
|
/// let mut vdom = VirtualDom::new();
|
||||||
@@ -186,6 +187,7 @@ impl Debug for VNode<'_> {
|
|||||||
write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
|
write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
|
||||||
}
|
}
|
||||||
VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
|
VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
|
||||||
|
// VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,26 +356,89 @@ impl Clone for EventHandler<'_> {
|
|||||||
/// Only supports the functional syntax
|
/// Only supports the functional syntax
|
||||||
pub struct VComponent<'src> {
|
pub struct VComponent<'src> {
|
||||||
pub key: Option<&'src str>,
|
pub key: Option<&'src str>,
|
||||||
|
pub scope: Cell<Option<ScopeId>>,
|
||||||
pub associated_scope: Cell<Option<ScopeId>>,
|
pub can_memoize: bool,
|
||||||
|
|
||||||
// Function pointer to the FC that was used to generate this component
|
|
||||||
pub user_fc: *const (),
|
pub user_fc: *const (),
|
||||||
|
pub props: RefCell<Option<Box<dyn AnyProps + 'src>>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) can_memoize: bool,
|
pub(crate) struct VComponentProps<P> {
|
||||||
|
// Props start local
|
||||||
|
// If they are static, then they can get promoted onto the heap
|
||||||
|
pub render_fn: Component<P>,
|
||||||
|
pub props: P,
|
||||||
|
pub memo: unsafe fn(&P, &P) -> bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) _hard_allocation: Cell<Option<*const ()>>,
|
pub trait AnyProps {
|
||||||
|
fn as_ptr(&self) -> *const ();
|
||||||
|
fn release(&self);
|
||||||
|
fn replace_heap(&self, new: Box<dyn Any>);
|
||||||
|
|
||||||
// Raw pointer into the bump arena for the props of the component
|
// move the props from the bump arena onto the heap
|
||||||
pub(crate) bump_props: *const (),
|
fn promote(&self);
|
||||||
|
fn render<'a>(&'a self, bump: &'a ScopeState) -> Element<'a>;
|
||||||
|
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
// during the "teardown" process we'll take the caller out so it can be dropped properly
|
impl<P> AnyProps for VComponentProps<P> {
|
||||||
pub(crate) caller: &'src dyn Fn(&'src ScopeState) -> Element,
|
fn as_ptr(&self) -> *const () {
|
||||||
|
&self.props as *const _ as *const ()
|
||||||
|
// // todo: maybe move this into an enum
|
||||||
|
// let heap_props = self.heap_props.borrow();
|
||||||
|
// if let Some(b) = heap_props.as_ref() {
|
||||||
|
// b.as_ref() as *const _ as *const ()
|
||||||
|
// } else {
|
||||||
|
// let local_props = self.local_props.borrow();
|
||||||
|
// local_props.as_ref().unwrap() as *const _ as *const ()
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
|
// this will downcat the other ptr as our swallowed type!
|
||||||
|
// you *must* make this check *before* calling this method
|
||||||
|
// if your functions are not the same, then you will downcast a pointer into a different type (UB)
|
||||||
|
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
|
||||||
|
let real_other: &P = &*(other.as_ptr() as *const _ as *const P);
|
||||||
|
let real_us: &P = &*(self.as_ptr() as *const _ as *const P);
|
||||||
|
(self.memo)(real_us, real_other)
|
||||||
|
}
|
||||||
|
|
||||||
// todo: re-add this
|
fn release(&self) {
|
||||||
pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
|
panic!("don't release props anymore");
|
||||||
|
// if let Some(heap_props) = self.heap_props.borrow_mut().take() {
|
||||||
|
// dbg!("releasing heap");
|
||||||
|
// drop(heap_props)
|
||||||
|
// } else if let Some(local_props) = self.local_props.borrow_mut().take() {
|
||||||
|
// dbg!("releasing local");
|
||||||
|
// drop(local_props)
|
||||||
|
// } else {
|
||||||
|
// dbg!("trying to do a double release");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn promote(&self) {
|
||||||
|
panic!("don't promote props anymore");
|
||||||
|
// dbg!("promoting props");
|
||||||
|
// if let Some(props) = self.local_props.borrow_mut().take() {
|
||||||
|
// let mut heap_slot = self.heap_props.borrow_mut();
|
||||||
|
// *heap_slot = Some(Box::new(props));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
|
||||||
|
let props: &P = &self.props;
|
||||||
|
let inline_props = unsafe { std::mem::transmute::<&P, &P>(props) };
|
||||||
|
|
||||||
|
(self.render_fn)(Scope {
|
||||||
|
scope,
|
||||||
|
props: inline_props,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_heap(&self, new: Box<dyn Any>) {
|
||||||
|
// let new = new.downcast::<P>().unwrap();
|
||||||
|
// self.heap_props.borrow_mut().replace(new);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This struct provides an ergonomic API to quickly build VNodes.
|
/// This struct provides an ergonomic API to quickly build VNodes.
|
||||||
@@ -517,75 +582,23 @@ impl<'a> NodeFactory<'a> {
|
|||||||
where
|
where
|
||||||
P: Properties + 'a,
|
P: Properties + 'a,
|
||||||
{
|
{
|
||||||
let bump = self.bump;
|
VNode::Component(self.bump.alloc(VComponent {
|
||||||
let props = bump.alloc(props);
|
key: key.map(|f| self.raw_text(f).0),
|
||||||
let bump_props = props as *mut P as *mut ();
|
scope: Default::default(),
|
||||||
let user_fc = component as *const ();
|
can_memoize: P::IS_STATIC,
|
||||||
|
user_fc: component as *const (),
|
||||||
|
props: RefCell::new(Some(Box::new(VComponentProps {
|
||||||
|
// local_props: RefCell::new(Some(props)),
|
||||||
|
// heap_props: RefCell::new(None),
|
||||||
|
props,
|
||||||
|
memo: P::memoize, // smuggle the memoization function across borders
|
||||||
|
|
||||||
let comparator: &mut dyn Fn(&VComponent) -> bool = bump.alloc_with(|| {
|
// i'm sorry but I just need to bludgeon the lifetimes into place here
|
||||||
move |other: &VComponent| {
|
// this is safe because we're managing all lifetimes to originate from previous calls
|
||||||
if user_fc == other.user_fc {
|
// the intricacies of Rust's lifetime system make it difficult to properly express
|
||||||
// Safety
|
// the transformation from this specific lifetime to the for<'a> lifetime
|
||||||
// - We guarantee that Component<P> is the same by function pointer
|
render_fn: unsafe { std::mem::transmute(component) },
|
||||||
// - Because Component<P> is the same, then P must be the same (even with generics)
|
}))),
|
||||||
// - Non-static P are autoderived to memoize as false
|
|
||||||
// - This comparator is only called on a corresponding set of bumpframes
|
|
||||||
//
|
|
||||||
// It's only okay to memoize if there are no children and the props can be memoized
|
|
||||||
// Implementing memoize is unsafe and done automatically with the props trait
|
|
||||||
unsafe {
|
|
||||||
let real_other: &P = &*(other.bump_props as *const _ as *const P);
|
|
||||||
props.memoize(real_other)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let drop_props = {
|
|
||||||
// create a closure to drop the props
|
|
||||||
let mut has_dropped = false;
|
|
||||||
|
|
||||||
let drop_props: &mut dyn FnMut() = bump.alloc_with(|| {
|
|
||||||
move || unsafe {
|
|
||||||
if !has_dropped {
|
|
||||||
let real_other = bump_props as *mut _ as *mut P;
|
|
||||||
let b = BumpBox::from_raw(real_other);
|
|
||||||
std::mem::drop(b);
|
|
||||||
|
|
||||||
has_dropped = true;
|
|
||||||
} else {
|
|
||||||
panic!("Drop props called twice - this is an internal failure of Dioxus");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let drop_props = unsafe { BumpBox::from_raw(drop_props) };
|
|
||||||
|
|
||||||
RefCell::new(Some(drop_props))
|
|
||||||
};
|
|
||||||
|
|
||||||
let key = key.map(|f| self.raw_text(f).0);
|
|
||||||
|
|
||||||
let caller: &'a mut dyn Fn(&'a ScopeState) -> Element =
|
|
||||||
bump.alloc(move |scope: &ScopeState| -> Element {
|
|
||||||
let props: &'_ P = unsafe { &*(bump_props as *const P) };
|
|
||||||
component(Scope { scope, props })
|
|
||||||
});
|
|
||||||
|
|
||||||
let can_memoize = P::IS_STATIC;
|
|
||||||
|
|
||||||
VNode::Component(bump.alloc(VComponent {
|
|
||||||
user_fc,
|
|
||||||
comparator: Some(comparator),
|
|
||||||
bump_props,
|
|
||||||
caller,
|
|
||||||
key,
|
|
||||||
can_memoize,
|
|
||||||
drop_props,
|
|
||||||
associated_scope: Cell::new(None),
|
|
||||||
_hard_allocation: Cell::new(None),
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,355 +0,0 @@
|
|||||||
use bumpalo::Bump;
|
|
||||||
use futures_channel::mpsc::UnboundedSender;
|
|
||||||
use fxhash::{FxHashMap, FxHashSet};
|
|
||||||
use slab::Slab;
|
|
||||||
use std::{
|
|
||||||
borrow::Borrow,
|
|
||||||
cell::{Cell, RefCell},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::innerlude::*;
|
|
||||||
|
|
||||||
pub(crate) type FcSlot = *const ();
|
|
||||||
|
|
||||||
pub(crate) struct Heuristic {
|
|
||||||
hook_arena_size: usize,
|
|
||||||
node_arena_size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
// a slab-like arena with stable references even when new scopes are allocated
|
|
||||||
// uses a bump arena as a backing
|
|
||||||
//
|
|
||||||
// has an internal heuristics engine to pre-allocate arenas to the right size
|
|
||||||
pub(crate) struct ScopeArena {
|
|
||||||
pub pending_futures: RefCell<FxHashSet<ScopeId>>,
|
|
||||||
scope_counter: Cell<usize>,
|
|
||||||
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
|
||||||
bump: Bump,
|
|
||||||
|
|
||||||
pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
|
|
||||||
pub heuristics: RefCell<FxHashMap<FcSlot, Heuristic>>,
|
|
||||||
free_scopes: RefCell<Vec<*mut ScopeState>>,
|
|
||||||
nodes: RefCell<Slab<*const VNode<'static>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScopeArena {
|
|
||||||
pub(crate) fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
|
|
||||||
let bump = Bump::new();
|
|
||||||
|
|
||||||
// allocate a container for the root element
|
|
||||||
// this will *never* show up in the diffing process
|
|
||||||
let el = bump.alloc(VElement {
|
|
||||||
tag_name: "root",
|
|
||||||
namespace: None,
|
|
||||||
key: None,
|
|
||||||
dom_id: Cell::new(Some(ElementId(0))),
|
|
||||||
parent_id: Default::default(),
|
|
||||||
listeners: &[],
|
|
||||||
attributes: &[],
|
|
||||||
children: &[],
|
|
||||||
});
|
|
||||||
|
|
||||||
let node = bump.alloc(VNode::Element(el));
|
|
||||||
let mut nodes = Slab::new();
|
|
||||||
let root_id = nodes.insert(unsafe { std::mem::transmute(node as *const _) });
|
|
||||||
|
|
||||||
debug_assert_eq!(root_id, 0);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
scope_counter: Cell::new(0),
|
|
||||||
bump,
|
|
||||||
pending_futures: RefCell::new(FxHashSet::default()),
|
|
||||||
scopes: RefCell::new(FxHashMap::default()),
|
|
||||||
heuristics: RefCell::new(FxHashMap::default()),
|
|
||||||
free_scopes: RefCell::new(Vec::new()),
|
|
||||||
nodes: RefCell::new(nodes),
|
|
||||||
sender,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Safety:
|
|
||||||
/// - Obtaining a mutable refernece to any Scope is unsafe
|
|
||||||
/// - Scopes use interior mutability when sharing data into components
|
|
||||||
pub(crate) fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
|
||||||
unsafe { self.scopes.borrow().get(&id).map(|f| &**f) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_scope_raw(&self, id: ScopeId) -> Option<*mut ScopeState> {
|
|
||||||
self.scopes.borrow().get(&id).copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn new_with_key(
|
|
||||||
&self,
|
|
||||||
fc_ptr: *const (),
|
|
||||||
caller: *const dyn Fn(&ScopeState) -> Element,
|
|
||||||
parent_scope: Option<*mut ScopeState>,
|
|
||||||
container: ElementId,
|
|
||||||
height: u32,
|
|
||||||
subtree: u32,
|
|
||||||
) -> ScopeId {
|
|
||||||
let new_scope_id = ScopeId(self.scope_counter.get());
|
|
||||||
self.scope_counter.set(self.scope_counter.get() + 1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
This scopearena aggressively reuse old scopes when possible.
|
|
||||||
We try to minimize the new allocations for props/arenas.
|
|
||||||
|
|
||||||
However, this will probably lead to some sort of fragmentation.
|
|
||||||
I'm not exactly sure how to improve this today.
|
|
||||||
*/
|
|
||||||
match self.free_scopes.borrow_mut().pop() {
|
|
||||||
// No free scope, make a new scope
|
|
||||||
None => {
|
|
||||||
let (node_capacity, hook_capacity) = self
|
|
||||||
.heuristics
|
|
||||||
.borrow()
|
|
||||||
.get(&fc_ptr)
|
|
||||||
.map(|h| (h.node_arena_size, h.hook_arena_size))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)];
|
|
||||||
|
|
||||||
let scope = self.bump.alloc(ScopeState {
|
|
||||||
sender: self.sender.clone(),
|
|
||||||
container,
|
|
||||||
our_arena_idx: new_scope_id,
|
|
||||||
parent_scope,
|
|
||||||
height,
|
|
||||||
frames,
|
|
||||||
subtree: Cell::new(subtree),
|
|
||||||
is_subtree_root: Cell::new(false),
|
|
||||||
|
|
||||||
caller: Cell::new(caller),
|
|
||||||
generation: 0.into(),
|
|
||||||
|
|
||||||
shared_contexts: Default::default(),
|
|
||||||
|
|
||||||
items: RefCell::new(SelfReferentialItems {
|
|
||||||
listeners: Default::default(),
|
|
||||||
borrowed_props: Default::default(),
|
|
||||||
tasks: Default::default(),
|
|
||||||
}),
|
|
||||||
|
|
||||||
hook_arena: Bump::new(),
|
|
||||||
hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
|
|
||||||
hook_idx: Default::default(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
|
|
||||||
debug_assert!(any_item.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reuse a free scope
|
|
||||||
Some(old_scope) => {
|
|
||||||
let scope = unsafe { &mut *old_scope };
|
|
||||||
scope.caller.set(caller);
|
|
||||||
scope.parent_scope = parent_scope;
|
|
||||||
scope.height = height;
|
|
||||||
scope.subtree = Cell::new(subtree);
|
|
||||||
scope.our_arena_idx = new_scope_id;
|
|
||||||
scope.container = container;
|
|
||||||
let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
|
|
||||||
debug_assert!(any_item.is_none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new_scope_id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removes a scope and its descendents from the arena
|
|
||||||
pub fn try_remove(&self, id: ScopeId) -> Option<()> {
|
|
||||||
self.ensure_drop_safety(id);
|
|
||||||
|
|
||||||
// Safety:
|
|
||||||
// - ensure_drop_safety ensures that no references to this scope are in use
|
|
||||||
// - this raw pointer is removed from the map
|
|
||||||
let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
|
|
||||||
scope.reset();
|
|
||||||
|
|
||||||
self.free_scopes.borrow_mut().push(scope);
|
|
||||||
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reserve_node(&self, node: &VNode) -> ElementId {
|
|
||||||
let mut els = self.nodes.borrow_mut();
|
|
||||||
let entry = els.vacant_entry();
|
|
||||||
let key = entry.key();
|
|
||||||
let id = ElementId(key);
|
|
||||||
let node: *const VNode = node as *const _;
|
|
||||||
let node = unsafe { std::mem::transmute::<*const VNode, *const VNode>(node) };
|
|
||||||
entry.insert(node);
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_node(&self, node: &VNode, id: ElementId) {
|
|
||||||
let node = unsafe { std::mem::transmute::<*const VNode, *const VNode>(node) };
|
|
||||||
*self.nodes.borrow_mut().get_mut(id.0).unwrap() = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn collect_garbage(&self, id: ElementId) {
|
|
||||||
self.nodes.borrow_mut().remove(id.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
|
|
||||||
/// causing UB in our tree.
|
|
||||||
///
|
|
||||||
/// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
|
|
||||||
/// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
|
|
||||||
/// any possible references to the data in the hook list.
|
|
||||||
///
|
|
||||||
/// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
|
|
||||||
/// all listeners and borrowed props so we can clear them here.
|
|
||||||
///
|
|
||||||
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
|
|
||||||
/// be dropped.
|
|
||||||
pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
|
|
||||||
if let Some(scope) = self.get_scope(scope_id) {
|
|
||||||
let mut items = scope.items.borrow_mut();
|
|
||||||
|
|
||||||
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
|
||||||
// run the hooks (which hold an &mut Reference)
|
|
||||||
// recursively call ensure_drop_safety on all children
|
|
||||||
items.borrowed_props.drain(..).for_each(|comp| {
|
|
||||||
let scope_id = comp
|
|
||||||
.associated_scope
|
|
||||||
.get()
|
|
||||||
.expect("VComponents should be associated with a valid Scope");
|
|
||||||
|
|
||||||
self.ensure_drop_safety(scope_id);
|
|
||||||
|
|
||||||
let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
|
|
||||||
drop_props();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
|
||||||
items
|
|
||||||
.listeners
|
|
||||||
.drain(..)
|
|
||||||
.for_each(|listener| drop(listener.callback.callback.borrow_mut().take()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub(crate) fn run_scope(&self, id: ScopeId) -> bool {
|
|
||||||
|
|
||||||
pub(crate) fn run_scope(&self, id: ScopeId) {
|
|
||||||
// Cycle to the next frame and then reset it
|
|
||||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
|
||||||
// Remove all the outdated listeners
|
|
||||||
self.ensure_drop_safety(id);
|
|
||||||
|
|
||||||
// todo: we *know* that this is aliased by the contents of the scope itself
|
|
||||||
let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") };
|
|
||||||
|
|
||||||
// Safety:
|
|
||||||
// - We dropped the listeners, so no more &mut T can be used while these are held
|
|
||||||
// - All children nodes that rely on &mut T are replaced with a new reference
|
|
||||||
scope.hook_idx.set(0);
|
|
||||||
|
|
||||||
// book keeping to ensure safety around the borrowed data
|
|
||||||
{
|
|
||||||
// Safety:
|
|
||||||
// - We've dropped all references to the wip bump frame with "ensure_drop_safety"
|
|
||||||
unsafe { scope.reset_wip_frame() };
|
|
||||||
|
|
||||||
let mut items = scope.items.borrow_mut();
|
|
||||||
|
|
||||||
// just forget about our suspended nodes while we're at it
|
|
||||||
items.tasks.clear();
|
|
||||||
|
|
||||||
// guarantee that we haven't screwed up - there should be no latent references anywhere
|
|
||||||
debug_assert!(items.listeners.is_empty());
|
|
||||||
debug_assert!(items.borrowed_props.is_empty());
|
|
||||||
debug_assert!(items.tasks.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
let render: &dyn Fn(&ScopeState) -> Element = unsafe { &*scope.caller.get() };
|
|
||||||
|
|
||||||
/*
|
|
||||||
If the component returns None, then we fill in a placeholder node. This will wipe what was there.
|
|
||||||
|
|
||||||
An alternate approach is to leave the Real Dom the same, but that can lead to safety issues and a lot more checks.
|
|
||||||
*/
|
|
||||||
if let Some(node) = render(scope) {
|
|
||||||
if !scope.items.borrow().tasks.is_empty() {
|
|
||||||
self.pending_futures.borrow_mut().insert(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let frame = scope.wip_frame();
|
|
||||||
let node = frame.bump.alloc(node);
|
|
||||||
frame.node.set(unsafe { std::mem::transmute(node) });
|
|
||||||
} else {
|
|
||||||
let frame = scope.wip_frame();
|
|
||||||
let node = frame
|
|
||||||
.bump
|
|
||||||
.alloc(VNode::Placeholder(frame.bump.alloc(VPlaceholder {
|
|
||||||
dom_id: Default::default(),
|
|
||||||
})));
|
|
||||||
frame.node.set(unsafe { std::mem::transmute(node) });
|
|
||||||
}
|
|
||||||
|
|
||||||
// make the "wip frame" contents the "finished frame"
|
|
||||||
// any future dipping into completed nodes after "render" will go through "fin head"
|
|
||||||
scope.cycle_frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn call_listener_with_bubbling(&self, event: UserEvent, element: ElementId) {
|
|
||||||
let nodes = self.nodes.borrow();
|
|
||||||
let mut cur_el = Some(element);
|
|
||||||
|
|
||||||
while let Some(id) = cur_el.take() {
|
|
||||||
if let Some(el) = nodes.get(id.0) {
|
|
||||||
let real_el = unsafe { &**el };
|
|
||||||
if let VNode::Element(real_el) = real_el {
|
|
||||||
//
|
|
||||||
for listener in real_el.listeners.borrow().iter() {
|
|
||||||
if listener.event == event.name {
|
|
||||||
let mut cb = listener.callback.callback.borrow_mut();
|
|
||||||
if let Some(cb) = cb.as_mut() {
|
|
||||||
(cb)(event.data.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cur_el = real_el.parent_id.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The head of the bumpframe is the first linked NodeLink
|
|
||||||
pub fn wip_head(&self, id: ScopeId) -> &VNode {
|
|
||||||
let scope = self.get_scope(id).unwrap();
|
|
||||||
let frame = scope.wip_frame();
|
|
||||||
let node = unsafe { &*frame.node.get() };
|
|
||||||
unsafe { std::mem::transmute::<&VNode, &VNode>(node) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// The head of the bumpframe is the first linked NodeLink
|
|
||||||
pub fn fin_head(&self, id: ScopeId) -> &VNode {
|
|
||||||
let scope = self.get_scope(id).unwrap();
|
|
||||||
let frame = scope.fin_frame();
|
|
||||||
let node = unsafe { &*frame.node.get() };
|
|
||||||
unsafe { std::mem::transmute::<&VNode, &VNode>(node) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn root_node(&self, id: ScopeId) -> &VNode {
|
|
||||||
self.fin_head(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// when dropping the virtualdom, we need to make sure and drop everything important
|
|
||||||
impl Drop for ScopeArena {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
for (_, scopeptr) in self.scopes.get_mut().drain() {
|
|
||||||
let scope = unsafe { bumpalo::boxed::Box::from_raw(scopeptr) };
|
|
||||||
drop(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
// these are probably complete invalid unfortunately ?
|
|
||||||
for scopeptr in self.free_scopes.get_mut().drain(..) {
|
|
||||||
let scope = unsafe { bumpalo::boxed::Box::from_raw(scopeptr) };
|
|
||||||
drop(scope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::innerlude::*;
|
|
||||||
|
|
||||||
use futures_channel::mpsc::UnboundedSender;
|
use futures_channel::mpsc::UnboundedSender;
|
||||||
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
|
use slab::Slab;
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
|
borrow::Borrow,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
future::Future,
|
future::Future,
|
||||||
@@ -10,8 +11,332 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{innerlude::*, unsafe_utils::extend_vnode};
|
||||||
use bumpalo::{boxed::Box as BumpBox, Bump};
|
use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||||
|
|
||||||
|
pub(crate) type FcSlot = *const ();
|
||||||
|
|
||||||
|
pub(crate) struct Heuristic {
|
||||||
|
hook_arena_size: usize,
|
||||||
|
node_arena_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// a slab-like arena with stable references even when new scopes are allocated
|
||||||
|
// uses a bump arena as a backing
|
||||||
|
//
|
||||||
|
// has an internal heuristics engine to pre-allocate arenas to the right size
|
||||||
|
pub(crate) struct ScopeArena {
|
||||||
|
pub pending_futures: RefCell<FxHashSet<ScopeId>>,
|
||||||
|
pub scope_counter: Cell<usize>,
|
||||||
|
pub sender: UnboundedSender<SchedulerMsg>,
|
||||||
|
pub bump: Bump,
|
||||||
|
pub scopes: RefCell<FxHashMap<ScopeId, *mut ScopeState>>,
|
||||||
|
pub heuristics: RefCell<FxHashMap<FcSlot, Heuristic>>,
|
||||||
|
pub free_scopes: RefCell<Vec<*mut ScopeState>>,
|
||||||
|
pub nodes: RefCell<Slab<*const VNode<'static>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScopeArena {
|
||||||
|
pub(crate) fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
|
||||||
|
let bump = Bump::new();
|
||||||
|
|
||||||
|
// allocate a container for the root element
|
||||||
|
// this will *never* show up in the diffing process
|
||||||
|
// todo: figure out why this is necessary. i forgot. whoops.
|
||||||
|
let el = bump.alloc(VElement {
|
||||||
|
tag_name: "root",
|
||||||
|
namespace: None,
|
||||||
|
key: None,
|
||||||
|
dom_id: Cell::new(Some(ElementId(0))),
|
||||||
|
parent_id: Default::default(),
|
||||||
|
listeners: &[],
|
||||||
|
attributes: &[],
|
||||||
|
children: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let node = bump.alloc(VNode::Element(el));
|
||||||
|
let mut nodes = Slab::new();
|
||||||
|
let root_id = nodes.insert(unsafe { std::mem::transmute(node as *const _) });
|
||||||
|
|
||||||
|
debug_assert_eq!(root_id, 0);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
scope_counter: Cell::new(0),
|
||||||
|
bump,
|
||||||
|
pending_futures: RefCell::new(FxHashSet::default()),
|
||||||
|
scopes: RefCell::new(FxHashMap::default()),
|
||||||
|
heuristics: RefCell::new(FxHashMap::default()),
|
||||||
|
free_scopes: RefCell::new(Vec::new()),
|
||||||
|
nodes: RefCell::new(nodes),
|
||||||
|
sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safety:
|
||||||
|
/// - Obtaining a mutable refernece to any Scope is unsafe
|
||||||
|
/// - Scopes use interior mutability when sharing data into components
|
||||||
|
pub(crate) fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
||||||
|
unsafe { self.scopes.borrow().get(&id).map(|f| &**f) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_scope_raw(&self, id: ScopeId) -> Option<*mut ScopeState> {
|
||||||
|
self.scopes.borrow().get(&id).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_with_key(
|
||||||
|
&self,
|
||||||
|
fc_ptr: *const (),
|
||||||
|
vcomp: Box<dyn AnyProps>,
|
||||||
|
parent_scope: Option<ScopeId>,
|
||||||
|
container: ElementId,
|
||||||
|
subtree: u32,
|
||||||
|
) -> ScopeId {
|
||||||
|
// Increment the ScopeId system. ScopeIDs are never reused
|
||||||
|
let new_scope_id = ScopeId(self.scope_counter.get());
|
||||||
|
self.scope_counter.set(self.scope_counter.get() + 1);
|
||||||
|
|
||||||
|
// Get the height of the scope
|
||||||
|
let height = parent_scope
|
||||||
|
.map(|id| self.get_scope(id).map(|scope| scope.height))
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let parent_scope = parent_scope.map(|f| self.get_scope_raw(f)).flatten();
|
||||||
|
|
||||||
|
/*
|
||||||
|
This scopearena aggressively reuse old scopes when possible.
|
||||||
|
We try to minimize the new allocations for props/arenas.
|
||||||
|
|
||||||
|
However, this will probably lead to some sort of fragmentation.
|
||||||
|
I'm not exactly sure how to improve this today.
|
||||||
|
*/
|
||||||
|
if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
|
||||||
|
// reuse the old scope
|
||||||
|
let scope = unsafe { &mut *old_scope };
|
||||||
|
scope.props.get_mut().replace(vcomp);
|
||||||
|
scope.parent_scope = parent_scope;
|
||||||
|
scope.height = height;
|
||||||
|
scope.subtree.set(subtree);
|
||||||
|
scope.our_arena_idx = new_scope_id;
|
||||||
|
scope.container = container;
|
||||||
|
let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
|
||||||
|
debug_assert!(any_item.is_none());
|
||||||
|
} else {
|
||||||
|
// else create a new scope
|
||||||
|
let (node_capacity, hook_capacity) = self
|
||||||
|
.heuristics
|
||||||
|
.borrow()
|
||||||
|
.get(&fc_ptr)
|
||||||
|
.map(|h| (h.node_arena_size, h.hook_arena_size))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
self.scopes.borrow_mut().insert(
|
||||||
|
new_scope_id,
|
||||||
|
self.bump.alloc(ScopeState::new(
|
||||||
|
height,
|
||||||
|
container,
|
||||||
|
new_scope_id,
|
||||||
|
self.sender.clone(),
|
||||||
|
parent_scope,
|
||||||
|
vcomp,
|
||||||
|
node_capacity,
|
||||||
|
hook_capacity,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_scope_id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a scope and its descendents from the arena
|
||||||
|
pub fn try_remove(&self, id: ScopeId) -> Option<()> {
|
||||||
|
self.ensure_drop_safety(id);
|
||||||
|
|
||||||
|
// Safety:
|
||||||
|
// - ensure_drop_safety ensures that no references to this scope are in use
|
||||||
|
// - this raw pointer is removed from the map
|
||||||
|
let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
|
||||||
|
scope.reset();
|
||||||
|
|
||||||
|
self.free_scopes.borrow_mut().push(scope);
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reserve_node<'a>(&self, node: &'a VNode<'a>) -> ElementId {
|
||||||
|
let mut els = self.nodes.borrow_mut();
|
||||||
|
let entry = els.vacant_entry();
|
||||||
|
let key = entry.key();
|
||||||
|
let id = ElementId(key);
|
||||||
|
let node = unsafe { extend_vnode(node) };
|
||||||
|
entry.insert(node as *const _);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) {
|
||||||
|
let node = unsafe { extend_vnode(node) };
|
||||||
|
*self.nodes.borrow_mut().get_mut(id.0).unwrap() = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect_garbage(&self, id: ElementId) {
|
||||||
|
self.nodes.borrow_mut().remove(id.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
|
||||||
|
/// causing UB in our tree.
|
||||||
|
///
|
||||||
|
/// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
|
||||||
|
/// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
|
||||||
|
/// any possible references to the data in the hook list.
|
||||||
|
///
|
||||||
|
/// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
|
||||||
|
/// all listeners and borrowed props so we can clear them here.
|
||||||
|
///
|
||||||
|
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
|
||||||
|
/// be dropped.
|
||||||
|
pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
|
||||||
|
if let Some(scope) = self.get_scope(scope_id) {
|
||||||
|
let mut items = scope.items.borrow_mut();
|
||||||
|
|
||||||
|
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
||||||
|
// run the hooks (which hold an &mut Reference)
|
||||||
|
// recursively call ensure_drop_safety on all children
|
||||||
|
items.borrowed_props.drain(..).for_each(|comp| {
|
||||||
|
let scope_id = comp
|
||||||
|
.scope
|
||||||
|
.get()
|
||||||
|
.expect("VComponents should be associated with a valid Scope");
|
||||||
|
|
||||||
|
self.ensure_drop_safety(scope_id);
|
||||||
|
|
||||||
|
drop(comp.props.take());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
||||||
|
items
|
||||||
|
.listeners
|
||||||
|
.drain(..)
|
||||||
|
.for_each(|listener| drop(listener.callback.callback.borrow_mut().take()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run_scope(&self, id: ScopeId) {
|
||||||
|
// Cycle to the next frame and then reset it
|
||||||
|
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||||
|
// Remove all the outdated listeners
|
||||||
|
self.ensure_drop_safety(id);
|
||||||
|
|
||||||
|
// todo: we *know* that this is aliased by the contents of the scope itself
|
||||||
|
let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") };
|
||||||
|
|
||||||
|
// Safety:
|
||||||
|
// - We dropped the listeners, so no more &mut T can be used while these are held
|
||||||
|
// - All children nodes that rely on &mut T are replaced with a new reference
|
||||||
|
scope.hook_idx.set(0);
|
||||||
|
|
||||||
|
// book keeping to ensure safety around the borrowed data
|
||||||
|
{
|
||||||
|
// Safety:
|
||||||
|
// - We've dropped all references to the wip bump frame with "ensure_drop_safety"
|
||||||
|
unsafe { scope.reset_wip_frame() };
|
||||||
|
|
||||||
|
let mut items = scope.items.borrow_mut();
|
||||||
|
|
||||||
|
// just forget about our suspended nodes while we're at it
|
||||||
|
items.tasks.clear();
|
||||||
|
|
||||||
|
// guarantee that we haven't screwed up - there should be no latent references anywhere
|
||||||
|
debug_assert!(items.listeners.is_empty());
|
||||||
|
debug_assert!(items.borrowed_props.is_empty());
|
||||||
|
debug_assert!(items.tasks.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// safety: this is definitely not dropped
|
||||||
|
|
||||||
|
/*
|
||||||
|
If the component returns None, then we fill in a placeholder node. This will wipe what was there.
|
||||||
|
An alternate approach is to leave the Real Dom the same, but that can lead to safety issues and a lot more checks.
|
||||||
|
|
||||||
|
Instead, we just treat the `None` as a shortcut to placeholder.
|
||||||
|
If the developer wants to prevent a scope from updating, they should control its memoization instead.
|
||||||
|
|
||||||
|
Also, the way we implement hooks allows us to cut rendering short before the next hook is recalled.
|
||||||
|
I'm not sure if React lets you abort the component early, but we let you do that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let props = scope.props.borrow();
|
||||||
|
let render = props.as_ref().unwrap();
|
||||||
|
if let Some(node) = render.render(scope) {
|
||||||
|
if !scope.items.borrow().tasks.is_empty() {
|
||||||
|
self.pending_futures.borrow_mut().insert(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame = scope.wip_frame();
|
||||||
|
let node = frame.bump.alloc(node);
|
||||||
|
frame.node.set(unsafe { extend_vnode(node) });
|
||||||
|
} else {
|
||||||
|
let frame = scope.wip_frame();
|
||||||
|
let node = frame
|
||||||
|
.bump
|
||||||
|
.alloc(VNode::Placeholder(frame.bump.alloc(VPlaceholder {
|
||||||
|
dom_id: Default::default(),
|
||||||
|
})));
|
||||||
|
frame.node.set(unsafe { extend_vnode(node) });
|
||||||
|
}
|
||||||
|
|
||||||
|
// make the "wip frame" contents the "finished frame"
|
||||||
|
// any future dipping into completed nodes after "render" will go through "fin head"
|
||||||
|
scope.cycle_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call_listener_with_bubbling(&self, event: UserEvent, element: ElementId) {
|
||||||
|
let nodes = self.nodes.borrow();
|
||||||
|
let mut cur_el = Some(element);
|
||||||
|
|
||||||
|
while let Some(id) = cur_el.take() {
|
||||||
|
if let Some(el) = nodes.get(id.0) {
|
||||||
|
let real_el = unsafe { &**el };
|
||||||
|
if let VNode::Element(real_el) = real_el {
|
||||||
|
for listener in real_el.listeners.borrow().iter() {
|
||||||
|
if listener.event == event.name {
|
||||||
|
let mut cb = listener.callback.callback.borrow_mut();
|
||||||
|
if let Some(cb) = cb.as_mut() {
|
||||||
|
// todo: arcs are pretty heavy to clone
|
||||||
|
// we really want to convert arc to rc
|
||||||
|
// unfortunately, the SchedulerMsg must be send/sync to be sent across threads
|
||||||
|
// we could convert arc to rc internally or something
|
||||||
|
(cb)(event.data.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_el = real_el.parent_id.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The head of the bumpframe is the first linked NodeLink
|
||||||
|
pub fn wip_head(&self, id: ScopeId) -> &VNode {
|
||||||
|
let scope = self.get_scope(id).unwrap();
|
||||||
|
let frame = scope.wip_frame();
|
||||||
|
let node = unsafe { &*frame.node.get() };
|
||||||
|
unsafe { extend_vnode(node) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// The head of the bumpframe is the first linked NodeLink
|
||||||
|
pub fn fin_head(&self, id: ScopeId) -> &VNode {
|
||||||
|
let scope = self.get_scope(id).unwrap();
|
||||||
|
let frame = scope.fin_frame();
|
||||||
|
let node = unsafe { &*frame.node.get() };
|
||||||
|
unsafe { extend_vnode(node) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root_node(&self, id: ScopeId) -> &VNode {
|
||||||
|
self.fin_head(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
||||||
///
|
///
|
||||||
/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
|
/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
|
||||||
@@ -72,34 +397,29 @@ pub struct ScopeId(pub usize);
|
|||||||
/// use case they might have.
|
/// use case they might have.
|
||||||
pub struct ScopeState {
|
pub struct ScopeState {
|
||||||
pub(crate) parent_scope: Option<*mut ScopeState>,
|
pub(crate) parent_scope: Option<*mut ScopeState>,
|
||||||
|
|
||||||
pub(crate) container: ElementId,
|
pub(crate) container: ElementId,
|
||||||
|
|
||||||
pub(crate) our_arena_idx: ScopeId,
|
pub(crate) our_arena_idx: ScopeId,
|
||||||
|
|
||||||
pub(crate) height: u32,
|
pub(crate) height: u32,
|
||||||
|
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
||||||
|
|
||||||
|
// todo: subtrees
|
||||||
|
pub(crate) is_subtree_root: Cell<bool>,
|
||||||
pub(crate) subtree: Cell<u32>,
|
pub(crate) subtree: Cell<u32>,
|
||||||
|
|
||||||
pub(crate) is_subtree_root: Cell<bool>,
|
pub(crate) props: RefCell<Option<Box<dyn AnyProps>>>,
|
||||||
|
|
||||||
pub(crate) generation: Cell<u32>,
|
|
||||||
|
|
||||||
|
// nodes, items
|
||||||
pub(crate) frames: [BumpFrame; 2],
|
pub(crate) frames: [BumpFrame; 2],
|
||||||
|
pub(crate) generation: Cell<u32>,
|
||||||
pub(crate) caller: Cell<*const dyn Fn(&ScopeState) -> Element>,
|
|
||||||
|
|
||||||
pub(crate) items: RefCell<SelfReferentialItems<'static>>,
|
pub(crate) items: RefCell<SelfReferentialItems<'static>>,
|
||||||
|
|
||||||
|
// hooks
|
||||||
pub(crate) hook_arena: Bump,
|
pub(crate) hook_arena: Bump,
|
||||||
|
|
||||||
pub(crate) hook_vals: RefCell<Vec<*mut dyn Any>>,
|
pub(crate) hook_vals: RefCell<Vec<*mut dyn Any>>,
|
||||||
|
|
||||||
pub(crate) hook_idx: Cell<usize>,
|
pub(crate) hook_idx: Cell<usize>,
|
||||||
|
|
||||||
|
// shared state -> todo: move this out of scopestate
|
||||||
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
|
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
|
||||||
|
|
||||||
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SelfReferentialItems<'a> {
|
pub struct SelfReferentialItems<'a> {
|
||||||
@@ -110,6 +430,47 @@ pub struct SelfReferentialItems<'a> {
|
|||||||
|
|
||||||
// Public methods exposed to libraries and components
|
// Public methods exposed to libraries and components
|
||||||
impl ScopeState {
|
impl ScopeState {
|
||||||
|
fn new(
|
||||||
|
height: u32,
|
||||||
|
container: ElementId,
|
||||||
|
our_arena_idx: ScopeId,
|
||||||
|
sender: UnboundedSender<SchedulerMsg>,
|
||||||
|
parent_scope: Option<*mut ScopeState>,
|
||||||
|
vcomp: Box<dyn AnyProps>,
|
||||||
|
node_capacity: usize,
|
||||||
|
hook_capacity: usize,
|
||||||
|
) -> Self {
|
||||||
|
let frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)];
|
||||||
|
|
||||||
|
ScopeState {
|
||||||
|
sender,
|
||||||
|
container,
|
||||||
|
our_arena_idx,
|
||||||
|
parent_scope,
|
||||||
|
props: RefCell::new(Some(vcomp)),
|
||||||
|
height,
|
||||||
|
frames,
|
||||||
|
|
||||||
|
// todo: subtrees
|
||||||
|
subtree: Cell::new(0),
|
||||||
|
is_subtree_root: Cell::new(false),
|
||||||
|
|
||||||
|
generation: 0.into(),
|
||||||
|
|
||||||
|
shared_contexts: Default::default(),
|
||||||
|
|
||||||
|
items: RefCell::new(SelfReferentialItems {
|
||||||
|
listeners: Default::default(),
|
||||||
|
borrowed_props: Default::default(),
|
||||||
|
tasks: Default::default(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
hook_arena: Bump::new(),
|
||||||
|
hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
|
||||||
|
hook_idx: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the subtree ID that this scope belongs to.
|
/// Get the subtree ID that this scope belongs to.
|
||||||
///
|
///
|
||||||
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
|
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
|
||||||
@@ -143,7 +504,6 @@ impl ScopeState {
|
|||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// fn App(cx: Scope<()>) -> Element {
|
/// fn App(cx: Scope<()>) -> Element {
|
||||||
/// todo!();
|
|
||||||
/// rsx!(cx, div { "Subtree {id}"})
|
/// rsx!(cx, div { "Subtree {id}"})
|
||||||
/// };
|
/// };
|
||||||
/// ```
|
/// ```
|
||||||
@@ -251,7 +611,7 @@ impl ScopeState {
|
|||||||
|
|
||||||
/// Get the Root Node of this scope
|
/// Get the Root Node of this scope
|
||||||
pub fn root_node(&self) -> &VNode {
|
pub fn root_node(&self) -> &VNode {
|
||||||
let node = unsafe { &*self.wip_frame().node.get() };
|
let node = unsafe { &*self.fin_frame().node.get() };
|
||||||
unsafe { std::mem::transmute(node) }
|
unsafe { std::mem::transmute(node) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,45 +825,37 @@ impl ScopeState {
|
|||||||
&self.wip_frame().bump
|
&self.wip_frame().bump
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn drop_hooks(&mut self) {
|
// todo: disable bookkeeping on drop (unncessary)
|
||||||
self.hook_vals.get_mut().drain(..).for_each(|state| {
|
|
||||||
let as_mut = unsafe { &mut *state };
|
|
||||||
let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
|
|
||||||
drop(boxed);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn reset(&mut self) {
|
pub(crate) fn reset(&mut self) {
|
||||||
// we're just reusing scopes so we need to clear it out
|
// first: book keaping
|
||||||
self.drop_hooks();
|
|
||||||
|
|
||||||
self.hook_idx.set(0);
|
self.hook_idx.set(0);
|
||||||
|
|
||||||
self.hook_arena.reset();
|
|
||||||
self.shared_contexts.get_mut().clear();
|
|
||||||
self.parent_scope = None;
|
self.parent_scope = None;
|
||||||
self.generation.set(0);
|
self.generation.set(0);
|
||||||
self.is_subtree_root.set(false);
|
self.is_subtree_root.set(false);
|
||||||
self.subtree.set(0);
|
self.subtree.set(0);
|
||||||
|
|
||||||
self.frames[0].reset();
|
// next: shared context data
|
||||||
self.frames[1].reset();
|
self.shared_contexts.get_mut().clear();
|
||||||
|
|
||||||
|
// next: reset the node data
|
||||||
let SelfReferentialItems {
|
let SelfReferentialItems {
|
||||||
borrowed_props,
|
borrowed_props,
|
||||||
listeners,
|
listeners,
|
||||||
tasks,
|
tasks,
|
||||||
} = self.items.get_mut();
|
} = self.items.get_mut();
|
||||||
|
|
||||||
borrowed_props.clear();
|
borrowed_props.clear();
|
||||||
listeners.clear();
|
listeners.clear();
|
||||||
tasks.clear();
|
tasks.clear();
|
||||||
}
|
self.frames[0].reset();
|
||||||
}
|
self.frames[1].reset();
|
||||||
|
|
||||||
impl Drop for ScopeState {
|
// Finally, free up the hook values
|
||||||
fn drop(&mut self) {
|
self.hook_arena.reset();
|
||||||
self.drop_hooks();
|
self.hook_vals.get_mut().drain(..).for_each(|state| {
|
||||||
|
let as_mut = unsafe { &mut *state };
|
||||||
|
let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
|
||||||
|
drop(boxed);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ use futures_util::{Future, StreamExt};
|
|||||||
use fxhash::FxHashSet;
|
use fxhash::FxHashSet;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{any::Any, collections::VecDeque, pin::Pin, sync::Arc, task::Poll};
|
use std::{any::Any, collections::VecDeque, iter::FromIterator, pin::Pin, sync::Arc, task::Poll};
|
||||||
|
|
||||||
/// A virtual node s ystem that progresses user events and diffs UI trees.
|
/// A virtual node s ystem that progresses user events and diffs UI trees.
|
||||||
///
|
///
|
||||||
@@ -103,10 +103,8 @@ use std::{any::Any, collections::VecDeque, pin::Pin, sync::Arc, task::Poll};
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct VirtualDom {
|
pub struct VirtualDom {
|
||||||
scopes: Box<ScopeArena>,
|
scopes: ScopeArena,
|
||||||
|
|
||||||
// should always be ScopeId(0)
|
|
||||||
base_scope: ScopeId,
|
|
||||||
pending_messages: VecDeque<SchedulerMsg>,
|
pending_messages: VecDeque<SchedulerMsg>,
|
||||||
dirty_scopes: IndexSet<ScopeId>,
|
dirty_scopes: IndexSet<ScopeId>,
|
||||||
|
|
||||||
@@ -114,8 +112,6 @@ pub struct VirtualDom {
|
|||||||
UnboundedSender<SchedulerMsg>,
|
UnboundedSender<SchedulerMsg>,
|
||||||
UnboundedReceiver<SchedulerMsg>,
|
UnboundedReceiver<SchedulerMsg>,
|
||||||
),
|
),
|
||||||
|
|
||||||
caller: *mut dyn for<'r> Fn(&'r ScopeState) -> Element<'r>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods to create the VirtualDom
|
// Methods to create the VirtualDom
|
||||||
@@ -174,7 +170,10 @@ impl VirtualDom {
|
|||||||
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
|
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
|
||||||
/// let mutations = dom.rebuild();
|
/// let mutations = dom.rebuild();
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new_with_props<P: 'static>(root: Component<P>, root_props: P) -> Self {
|
pub fn new_with_props<P>(root: Component<P>, root_props: P) -> Self
|
||||||
|
where
|
||||||
|
P: 'static,
|
||||||
|
{
|
||||||
Self::new_with_props_and_scheduler(
|
Self::new_with_props_and_scheduler(
|
||||||
root,
|
root,
|
||||||
root_props,
|
root_props,
|
||||||
@@ -188,8 +187,8 @@ impl VirtualDom {
|
|||||||
/// VirtualDom to be created just to retrieve its channel receiver.
|
/// VirtualDom to be created just to retrieve its channel receiver.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// let (sender, receiver) = futures_channel::mpsc::unbounded();
|
/// let channel = futures_channel::mpsc::unbounded();
|
||||||
/// let dom = VirtualDom::new_with_scheduler(Example, (), sender, receiver);
|
/// let dom = VirtualDom::new_with_scheduler(Example, (), channel);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new_with_props_and_scheduler<P: 'static>(
|
pub fn new_with_props_and_scheduler<P: 'static>(
|
||||||
root: Component<P>,
|
root: Component<P>,
|
||||||
@@ -199,36 +198,25 @@ impl VirtualDom {
|
|||||||
UnboundedReceiver<SchedulerMsg>,
|
UnboundedReceiver<SchedulerMsg>,
|
||||||
),
|
),
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// move these two things onto the heap so we have stable ptrs even if the VirtualDom itself is moved
|
let scopes = ScopeArena::new(channel.0.clone());
|
||||||
let scopes = Box::new(ScopeArena::new(channel.0.clone()));
|
|
||||||
let root_props = Box::new(root_props);
|
|
||||||
|
|
||||||
// effectively leak the caller so we can drop it when the VirtualDom is dropped
|
scopes.new_with_key(
|
||||||
let caller: *mut dyn for<'r> Fn(&'r ScopeState) -> Element<'r> =
|
root as *const _,
|
||||||
Box::into_raw(Box::new(move |scope: &ScopeState| -> Element {
|
Box::new(VComponentProps {
|
||||||
// Safety: The props at this pointer can never be moved.
|
props: root_props,
|
||||||
// Also, this closure will never be ran when the VirtualDom is destroyed.
|
memo: |_a, _b| unreachable!("memo on root will neve be run"),
|
||||||
// This is where the root lifetime of the VirtualDom originates.
|
render_fn: root,
|
||||||
let props = unsafe { std::mem::transmute::<&P, &P>(root_props.as_ref()) };
|
}),
|
||||||
let scp = Scope { scope, props };
|
None,
|
||||||
root(scp)
|
ElementId(0),
|
||||||
}));
|
0,
|
||||||
|
);
|
||||||
let base_scope = scopes.new_with_key(root as _, caller, None, ElementId(0), 0, 0);
|
|
||||||
|
|
||||||
let mut dirty_scopes = IndexSet::new();
|
|
||||||
dirty_scopes.insert(base_scope);
|
|
||||||
|
|
||||||
// todo: add a pending message to the scheduler to start the scheduler?
|
|
||||||
let pending_messages = VecDeque::new();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
scopes,
|
scopes,
|
||||||
base_scope,
|
|
||||||
caller,
|
|
||||||
pending_messages,
|
|
||||||
dirty_scopes,
|
|
||||||
channel,
|
channel,
|
||||||
|
dirty_scopes: IndexSet::from_iter([ScopeId(0)]),
|
||||||
|
pending_messages: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,9 +225,18 @@ impl VirtualDom {
|
|||||||
/// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
|
/// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
|
||||||
/// directly.
|
/// directly.
|
||||||
///
|
///
|
||||||
|
/// This method is equivalent to calling `get_scope(ScopeId(0))`
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let mut dom = VirtualDom::new(example);
|
||||||
|
/// dom.rebuild();
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
pub fn base_scope(&self) -> &ScopeState {
|
pub fn base_scope(&self) -> &ScopeState {
|
||||||
self.get_scope(self.base_scope).unwrap()
|
self.get_scope(ScopeId(0)).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the [`ScopeState`] for a component given its [`ScopeId`]
|
/// Get the [`ScopeState`] for a component given its [`ScopeId`]
|
||||||
@@ -248,7 +245,7 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
pub fn get_scope<'a>(&'a self, id: ScopeId) -> Option<&'a ScopeState> {
|
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
||||||
self.scopes.get_scope(id)
|
self.scopes.get_scope(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +257,7 @@ impl VirtualDom {
|
|||||||
/// let dom = VirtualDom::new(App);
|
/// let dom = VirtualDom::new(App);
|
||||||
/// let sender = dom.get_scheduler_channel();
|
/// let sender = dom.get_scheduler_channel();
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get_scheduler_channel(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
|
pub fn get_scheduler_channel(&self) -> UnboundedSender<SchedulerMsg> {
|
||||||
self.channel.0.clone()
|
self.channel.0.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,10 +270,12 @@ impl VirtualDom {
|
|||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// let dom = VirtualDom::new(App);
|
/// let dom = VirtualDom::new(App);
|
||||||
/// dom.insert_scheduler_message(SchedulerMsg::Immediate(ScopeId(0)));
|
/// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn insert_scheduler_message(&self, msg: SchedulerMsg) {
|
pub fn handle_message(&mut self, msg: SchedulerMsg) {
|
||||||
self.channel.0.unbounded_send(msg).unwrap()
|
if self.channel.0.unbounded_send(msg).is_ok() {
|
||||||
|
self.process_all_messages();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the [`VirtualDom`] has any pending updates or work to be done.
|
/// Check if the [`VirtualDom`] has any pending updates or work to be done.
|
||||||
@@ -326,26 +325,38 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Ok(Some(msg)) = self.channel.1.try_next() {
|
// Move all the messages into the queue
|
||||||
self.pending_messages.push_front(msg);
|
self.process_all_messages();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(msg) = self.pending_messages.pop_back() {
|
/// Manually kick the VirtualDom to process any
|
||||||
match msg {
|
pub fn process_all_messages(&mut self) {
|
||||||
// just keep looping, the task is now saved but we should actually poll it
|
// clear out the scheduler queue
|
||||||
SchedulerMsg::NewTask(id) => {
|
while let Ok(Some(msg)) = self.channel.1.try_next() {
|
||||||
self.scopes.pending_futures.borrow_mut().insert(id);
|
self.pending_messages.push_front(msg);
|
||||||
}
|
}
|
||||||
SchedulerMsg::UiEvent(event) => {
|
|
||||||
if let Some(element) = event.element {
|
// process all the messages pulled from the queue
|
||||||
self.scopes.call_listener_with_bubbling(event, element);
|
while let Some(msg) = self.pending_messages.pop_back() {
|
||||||
}
|
self.process_message(msg);
|
||||||
}
|
}
|
||||||
SchedulerMsg::Immediate(s) => {
|
}
|
||||||
self.dirty_scopes.insert(s);
|
|
||||||
}
|
pub fn process_message(&mut self, msg: SchedulerMsg) {
|
||||||
|
match msg {
|
||||||
|
// just keep looping, the task is now saved but we should actually poll it
|
||||||
|
SchedulerMsg::NewTask(id) => {
|
||||||
|
self.scopes.pending_futures.borrow_mut().insert(id);
|
||||||
|
}
|
||||||
|
SchedulerMsg::UiEvent(event) => {
|
||||||
|
if let Some(element) = event.element {
|
||||||
|
self.scopes.call_listener_with_bubbling(event, element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SchedulerMsg::Immediate(s) => {
|
||||||
|
self.dirty_scopes.insert(s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,7 +477,7 @@ impl VirtualDom {
|
|||||||
/// apply_edits(edits);
|
/// apply_edits(edits);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn rebuild(&mut self) -> Mutations {
|
pub fn rebuild(&mut self) -> Mutations {
|
||||||
let scope_id = self.base_scope;
|
let scope_id = ScopeId(0);
|
||||||
let mut diff_state = DiffState::new(&self.scopes);
|
let mut diff_state = DiffState::new(&self.scopes);
|
||||||
|
|
||||||
self.scopes.run_scope(scope_id);
|
self.scopes.run_scope(scope_id);
|
||||||
@@ -510,12 +521,24 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// let edits = dom.diff();
|
/// let edits = dom.diff();
|
||||||
/// ```
|
/// ```
|
||||||
pub fn hard_diff<'a>(&'a mut self, scope_id: ScopeId) -> Option<Mutations<'a>> {
|
pub fn hard_diff(&mut self, scope_id: ScopeId) -> Mutations {
|
||||||
let mut diff_machine = DiffState::new(&self.scopes);
|
let mut diff_machine = DiffState::new(&self.scopes);
|
||||||
self.scopes.run_scope(scope_id);
|
self.scopes.run_scope(scope_id);
|
||||||
|
|
||||||
|
let (old, new) = (
|
||||||
|
diff_machine.scopes.wip_head(scope_id),
|
||||||
|
diff_machine.scopes.fin_head(scope_id),
|
||||||
|
);
|
||||||
|
|
||||||
diff_machine.force_diff = true;
|
diff_machine.force_diff = true;
|
||||||
diff_machine.diff_scope(scope_id);
|
diff_machine.stack.push(DiffInstruction::Diff { old, new });
|
||||||
Some(diff_machine.mutations)
|
diff_machine.stack.scope_stack.push(scope_id);
|
||||||
|
|
||||||
|
let scope = diff_machine.scopes.get_scope(scope_id).unwrap();
|
||||||
|
diff_machine.stack.element_stack.push(scope.container);
|
||||||
|
diff_machine.work(|| false);
|
||||||
|
|
||||||
|
diff_machine.mutations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders an `rsx` call into the Base Scope's allocator.
|
/// Renders an `rsx` call into the Base Scope's allocator.
|
||||||
@@ -531,7 +554,7 @@ impl VirtualDom {
|
|||||||
/// let nodes = dom.render_nodes(rsx!("div"));
|
/// let nodes = dom.render_nodes(rsx!("div"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'a VNode<'a> {
|
pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'a VNode<'a> {
|
||||||
let scope = self.scopes.get_scope(self.base_scope).unwrap();
|
let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
|
||||||
let frame = scope.wip_frame();
|
let frame = scope.wip_frame();
|
||||||
let factory = NodeFactory { bump: &frame.bump };
|
let factory = NodeFactory { bump: &frame.bump };
|
||||||
let node = lazy_nodes.unwrap().call(factory);
|
let node = lazy_nodes.unwrap().call(factory);
|
||||||
@@ -554,7 +577,7 @@ impl VirtualDom {
|
|||||||
let mut machine = DiffState::new(&self.scopes);
|
let mut machine = DiffState::new(&self.scopes);
|
||||||
machine.stack.push(DiffInstruction::Diff { new, old });
|
machine.stack.push(DiffInstruction::Diff { new, old });
|
||||||
machine.stack.element_stack.push(ElementId(0));
|
machine.stack.element_stack.push(ElementId(0));
|
||||||
machine.stack.scope_stack.push(self.base_scope);
|
machine.stack.scope_stack.push(ScopeId(0));
|
||||||
machine.work(|| false);
|
machine.work(|| false);
|
||||||
machine.mutations
|
machine.mutations
|
||||||
}
|
}
|
||||||
@@ -572,11 +595,12 @@ impl VirtualDom {
|
|||||||
/// let dom = VirtualDom::new(Base);
|
/// let dom = VirtualDom::new(Base);
|
||||||
/// let nodes = dom.render_nodes(rsx!("div"));
|
/// let nodes = dom.render_nodes(rsx!("div"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn create_vnodes<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
|
pub fn create_vnodes<'a>(&'a self, nodes: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
|
||||||
let nodes = self.render_vnodes(left);
|
|
||||||
let mut machine = DiffState::new(&self.scopes);
|
let mut machine = DiffState::new(&self.scopes);
|
||||||
machine.stack.element_stack.push(ElementId(0));
|
machine.stack.element_stack.push(ElementId(0));
|
||||||
machine.stack.create_node(nodes, MountType::Append);
|
machine
|
||||||
|
.stack
|
||||||
|
.create_node(self.render_vnodes(nodes), MountType::Append);
|
||||||
machine.work(|| false);
|
machine.work(|| false);
|
||||||
machine.mutations
|
machine.mutations
|
||||||
}
|
}
|
||||||
@@ -602,29 +626,78 @@ impl VirtualDom {
|
|||||||
let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
|
let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
|
||||||
|
|
||||||
let mut create = DiffState::new(&self.scopes);
|
let mut create = DiffState::new(&self.scopes);
|
||||||
create.stack.scope_stack.push(self.base_scope);
|
create.stack.scope_stack.push(ScopeId(0));
|
||||||
create.stack.element_stack.push(ElementId(0));
|
create.stack.element_stack.push(ElementId(0));
|
||||||
create.stack.create_node(old, MountType::Append);
|
create.stack.create_node(old, MountType::Append);
|
||||||
create.work(|| false);
|
create.work(|| false);
|
||||||
|
|
||||||
let mut edit = DiffState::new(&self.scopes);
|
let mut edit = DiffState::new(&self.scopes);
|
||||||
edit.stack.scope_stack.push(self.base_scope);
|
edit.stack.scope_stack.push(ScopeId(0));
|
||||||
edit.stack.element_stack.push(ElementId(0));
|
edit.stack.element_stack.push(ElementId(0));
|
||||||
edit.stack.push(DiffInstruction::Diff { old, new });
|
edit.stack.push(DiffInstruction::Diff { old, new });
|
||||||
edit.work(&mut || false);
|
edit.work(|| false);
|
||||||
|
|
||||||
(create.mutations, edit.mutations)
|
(create.mutations, edit.mutations)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Scopes and ScopeArenas are never dropped internally.
|
||||||
|
An app will always occupy as much memory as its biggest form.
|
||||||
|
|
||||||
|
This means we need to handle all specifics of drop *here*. It's easier
|
||||||
|
to reason about centralizing all the drop logic in one spot rather than scattered in each module.
|
||||||
|
|
||||||
|
Broadly speaking, we want to use the remove_nodes method to clean up *everything*
|
||||||
|
This will drop listeners, borrowed props, and hooks for all components.
|
||||||
|
We need to do this in the correct order - nodes at the very bottom must be dropped first to release
|
||||||
|
the borrow chain.
|
||||||
|
|
||||||
|
Once the contents of the tree have been cleaned up, we can finally clean up the
|
||||||
|
memory used by ScopeState itself.
|
||||||
|
|
||||||
|
questions:
|
||||||
|
should we build a vcomponent for the root?
|
||||||
|
- probably - yes?
|
||||||
|
- store the vcomponent in the root dom
|
||||||
|
|
||||||
|
- 1: Use remove_nodes to use the ensure_drop_safety pathway to safely drop the tree
|
||||||
|
- 2: Drop the ScopeState itself
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
impl Drop for VirtualDom {
|
impl Drop for VirtualDom {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// ensure the root component's caller is dropped
|
// the best way to drop the dom is to replace the root scope with a dud
|
||||||
let caller = unsafe { Box::from_raw(self.caller) };
|
// the diff infrastructure will then finish the rest
|
||||||
drop(caller);
|
let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
|
||||||
|
|
||||||
// destroy the root component (which will destroy all of the other scopes)
|
// todo: move the remove nodes method onto scopearena
|
||||||
// load the root node and descend
|
// this will clear *all* scopes *except* the root scope
|
||||||
|
let mut machine = DiffState::new(&self.scopes);
|
||||||
|
machine.remove_nodes([scope.root_node()], false);
|
||||||
|
|
||||||
|
// Now, clean up the root scope
|
||||||
|
// safety: there are no more references to the root scope
|
||||||
|
let scope = unsafe { &mut *self.scopes.get_scope_raw(ScopeId(0)).unwrap() };
|
||||||
|
scope.reset();
|
||||||
|
|
||||||
|
// make sure there are no "live" components
|
||||||
|
for (_, scopeptr) in self.scopes.scopes.get_mut().drain() {
|
||||||
|
let scope = unsafe { bumpalo::boxed::Box::from_raw(scopeptr) };
|
||||||
|
drop(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
for scopeptr in self.scopes.free_scopes.get_mut().drain(..) {
|
||||||
|
let mut scope = unsafe { bumpalo::boxed::Box::from_raw(scopeptr) };
|
||||||
|
scope.reset();
|
||||||
|
drop(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety: all refereneces to this caller have been dropped
|
||||||
|
// the pointer is still valid since we haven't moved it since the virtualdom was created
|
||||||
|
// ensure the root component's caller is dropped
|
||||||
|
// drop(unsafe { Box::from_raw(self.caller) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ mod test_logging;
|
|||||||
fn new_dom() -> VirtualDom {
|
fn new_dom() -> VirtualDom {
|
||||||
const IS_LOGGING_ENABLED: bool = false;
|
const IS_LOGGING_ENABLED: bool = false;
|
||||||
test_logging::set_up_logging(IS_LOGGING_ENABLED);
|
test_logging::set_up_logging(IS_LOGGING_ENABLED);
|
||||||
VirtualDom::new(|cx| todo!())
|
VirtualDom::new(|cx| rsx!(cx, "hi"))
|
||||||
}
|
}
|
||||||
|
|
||||||
use DomEdit::*;
|
use DomEdit::*;
|
||||||
|
|||||||
@@ -55,13 +55,13 @@ fn test_early_abort() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0)).unwrap();
|
let edits = dom.hard_diff(ScopeId(0));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[CreatePlaceholder { root: 3 }, ReplaceWith { root: 1, m: 1 },],
|
[CreatePlaceholder { root: 3 }, ReplaceWith { root: 1, m: 1 },],
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0)).unwrap();
|
let edits = dom.hard_diff(ScopeId(0));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ fn manual_diffing() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn events_generate() {
|
fn events_generate() {
|
||||||
static App: Component<()> = |cx| {
|
static App: Component<()> = |cx| {
|
||||||
let mut count = use_state(&cx, || 0);
|
let count = cx.use_hook(|_| 0, |f| f);
|
||||||
|
|
||||||
let inner = match *count {
|
let inner = match *count {
|
||||||
0 => {
|
0 => {
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
onclick: move |_| count += 1,
|
onclick: move |_| *count += 1,
|
||||||
div {
|
div {
|
||||||
"nested"
|
"nested"
|
||||||
}
|
}
|
||||||
@@ -105,8 +105,8 @@ fn events_generate() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn components_generate() {
|
fn components_generate() {
|
||||||
static App: Component<()> = |cx| {
|
static App: Component<()> = |cx| {
|
||||||
let mut render_phase = use_state(&cx, || 0);
|
let render_phase = cx.use_hook(|_| 0, |f| f);
|
||||||
render_phase += 1;
|
*render_phase += 1;
|
||||||
|
|
||||||
cx.render(match *render_phase {
|
cx.render(match *render_phase {
|
||||||
0 => rsx!("Text0"),
|
0 => rsx!("Text0"),
|
||||||
@@ -140,7 +140,7 @@ fn components_generate() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0)).unwrap();
|
let edits = dom.hard_diff(ScopeId(0));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
@@ -152,7 +152,7 @@ fn components_generate() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0)).unwrap();
|
let edits = dom.hard_diff(ScopeId(0));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
@@ -164,7 +164,7 @@ fn components_generate() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0)).unwrap();
|
let edits = dom.hard_diff(ScopeId(0));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
@@ -173,13 +173,13 @@ fn components_generate() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0)).unwrap();
|
let edits = dom.hard_diff(ScopeId(0));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[CreatePlaceholder { root: 5 }, ReplaceWith { root: 4, m: 1 },]
|
[CreatePlaceholder { root: 5 }, ReplaceWith { root: 4, m: 1 },]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0)).unwrap();
|
let edits = dom.hard_diff(ScopeId(0));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
@@ -191,7 +191,7 @@ fn components_generate() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0)).unwrap();
|
let edits = dom.hard_diff(ScopeId(0));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
@@ -207,7 +207,7 @@ fn components_generate() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let edits = dom.hard_diff(ScopeId(0)).unwrap();
|
let edits = dom.hard_diff(ScopeId(0));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
@@ -222,8 +222,8 @@ fn components_generate() {
|
|||||||
fn component_swap() {
|
fn component_swap() {
|
||||||
// simple_logger::init();
|
// simple_logger::init();
|
||||||
static App: Component<()> = |cx| {
|
static App: Component<()> = |cx| {
|
||||||
let mut render_phase = use_state(&cx, || 0);
|
let render_phase = cx.use_hook(|_| 0, |f| f);
|
||||||
render_phase += 1;
|
*render_phase += 1;
|
||||||
|
|
||||||
cx.render(match *render_phase {
|
cx.render(match *render_phase {
|
||||||
0 => rsx!(
|
0 => rsx!(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Specifically:
|
|||||||
- [ ] Async isn't busted
|
- [ ] Async isn't busted
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use dioxus::{prelude::*, DomEdit, ScopeId};
|
use dioxus::{prelude::*, DomEdit, SchedulerMsg, ScopeId};
|
||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core_macro::*;
|
use dioxus_core_macro::*;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
@@ -55,12 +55,6 @@ fn test_memory_leak() {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn child(cx: Scope<()>) -> Element {
|
|
||||||
rsx!(cx, div {
|
|
||||||
"goodbye world"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
struct BorrowedProps<'a> {
|
struct BorrowedProps<'a> {
|
||||||
na: &'a str,
|
na: &'a str,
|
||||||
@@ -74,6 +68,10 @@ fn test_memory_leak() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn child(cx: Scope<()>) -> Element {
|
||||||
|
rsx!(cx, div { "goodbye world" })
|
||||||
|
}
|
||||||
|
|
||||||
let mut dom = new_dom(app, ());
|
let mut dom = new_dom(app, ());
|
||||||
|
|
||||||
dom.rebuild();
|
dom.rebuild();
|
||||||
@@ -100,7 +98,7 @@ fn memo_works_properly() {
|
|||||||
|
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div { "Hello, world!" }
|
div { "Hello, world!" }
|
||||||
child(na: "asd".to_string())
|
child(na: "asdfg".to_string())
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,9 +108,7 @@ fn memo_works_properly() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn child(cx: Scope<ChildProps>) -> Element {
|
fn child(cx: Scope<ChildProps>) -> Element {
|
||||||
rsx!(cx, div {
|
rsx!(cx, div { "goodbye world" })
|
||||||
"goodbye world"
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dom = new_dom(app, ());
|
let mut dom = new_dom(app, ());
|
||||||
@@ -123,4 +119,170 @@ fn memo_works_properly() {
|
|||||||
dom.hard_diff(ScopeId(0));
|
dom.hard_diff(ScopeId(0));
|
||||||
dom.hard_diff(ScopeId(0));
|
dom.hard_diff(ScopeId(0));
|
||||||
dom.hard_diff(ScopeId(0));
|
dom.hard_diff(ScopeId(0));
|
||||||
|
dom.hard_diff(ScopeId(0));
|
||||||
|
dom.hard_diff(ScopeId(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn free_works_on_root_props() {
|
||||||
|
fn app(cx: Scope<Custom>) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
child(a: "alpha")
|
||||||
|
child(a: "beta")
|
||||||
|
child(a: "gamma")
|
||||||
|
child(a: "delta")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Props, PartialEq)]
|
||||||
|
struct ChildProps {
|
||||||
|
a: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn child(cx: Scope<ChildProps>) -> Element {
|
||||||
|
rsx!(cx, "child {cx.props.a}")
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Custom {
|
||||||
|
val: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Custom {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
dbg!("dropped!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = new_dom(
|
||||||
|
app,
|
||||||
|
Custom {
|
||||||
|
val: String::from("asd"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
dom.rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn free_works_on_borrowed() {
|
||||||
|
fn app(cx: Scope<()>) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
child(a: "alpha", b: "asd".to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[derive(Props)]
|
||||||
|
struct ChildProps<'a> {
|
||||||
|
a: &'a str,
|
||||||
|
b: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
|
||||||
|
dbg!("rendering child");
|
||||||
|
rsx!(cx, "child {cx.props.a}, {cx.props.b}")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ChildProps<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
dbg!("dropped child!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = new_dom(app, ());
|
||||||
|
let _ = dom.rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn free_works_on_root_hooks() {
|
||||||
|
/*
|
||||||
|
On Drop, scopearena drops all the hook contents.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct Droppable<T>(T);
|
||||||
|
impl<T> Drop for Droppable<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
dbg!("dropping droppable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope<()>) -> Element {
|
||||||
|
let name = cx.use_hook(|_| Droppable(String::from("asd")), |f| f);
|
||||||
|
rsx!(cx, div { "{name.0}" })
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = new_dom(app, ());
|
||||||
|
let _ = dom.rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn old_props_arent_stale() {
|
||||||
|
fn app(cx: Scope<()>) -> Element {
|
||||||
|
dbg!("rendering parent");
|
||||||
|
let cnt = cx.use_hook(|_| 0, |f| f);
|
||||||
|
*cnt += 1;
|
||||||
|
|
||||||
|
if *cnt == 1 {
|
||||||
|
rsx!(cx, div { child(a: "abcdef".to_string()) })
|
||||||
|
} else {
|
||||||
|
rsx!(cx, div { child(a: "abcdef".to_string()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Props, PartialEq)]
|
||||||
|
struct ChildProps {
|
||||||
|
a: String,
|
||||||
|
}
|
||||||
|
fn child(cx: Scope<ChildProps>) -> Element {
|
||||||
|
dbg!("rendering child", &cx.props.a);
|
||||||
|
rsx!(cx, div { "child {cx.props.a}" })
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = new_dom(app, ());
|
||||||
|
let _ = dom.rebuild();
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
dbg!("forcing update to child");
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(1)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic() {
|
||||||
|
fn app(cx: Scope<()>) -> Element {
|
||||||
|
rsx!(cx, div {
|
||||||
|
child(a: "abcdef".to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Props, PartialEq)]
|
||||||
|
struct ChildProps {
|
||||||
|
a: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn child(cx: Scope<ChildProps>) -> Element {
|
||||||
|
dbg!("rendering child", &cx.props.a);
|
||||||
|
rsx!(cx, div { "child {cx.props.a}" })
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = new_dom(app, ());
|
||||||
|
let _ = dom.rebuild();
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
|
|
||||||
|
dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||||
|
dom.work_with_deadline(|| false);
|
||||||
}
|
}
|
||||||
@@ -244,7 +244,7 @@ impl<'a> TextRenderer<'a, '_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
VNode::Component(vcomp) => {
|
VNode::Component(vcomp) => {
|
||||||
let idx = vcomp.associated_scope.get().unwrap();
|
let idx = vcomp.scope.get().unwrap();
|
||||||
|
|
||||||
if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
|
if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
|
||||||
let new_node = vdom.get_scope(idx).unwrap().root_node();
|
let new_node = vdom.get_scope(idx).unwrap().root_node();
|
||||||
|
|||||||
Reference in new issue
Block a user