wip: ....sigh..... so the diffing algorithm is robust

but it's still not finished.

We need to re-enable the fancier keyed diffing versions some point (soon!).
This commit is contained in:
Jonathan Kelley committed 2021-07-12 03:58:46 -04:00
1 parent ff0a3d1c83
commit 68ed1c04e7
10 files changed
+168 -102

No files matched your search

+2
View File
@@ -17,6 +17,7 @@ pub struct Element {
attributes: Vec<ElementAttr>,
listeners: Vec<ElementAttr>,
children: Vec<Node>,
is_static: bool,
}
impl ToTokens for Element {
@@ -100,6 +101,7 @@ impl Parse for Element {
attributes,
children,
listeners,
is_static: false,
})
}
}
+41 -54
View File
@@ -66,7 +66,7 @@ use std::any::Any;
/// target-specific Node type as well as easily serializing the edits to be sent over a network or IPC connection.
pub trait RealDom<'a> {
// Navigation
fn push_root(&mut self, root: RealDomNode);
fn push(&mut self, root: RealDomNode);
fn pop(&mut self);
// Add Nodes to the dom
@@ -168,7 +168,7 @@ where
}
if old.text != new.text {
self.dom.push_root(old.dom_id.get());
self.dom.push(old.dom_id.get());
log::debug!("Text has changed {}, {}", old.text, new.text);
self.dom.set_text(new.text);
self.dom.pop();
@@ -183,7 +183,7 @@ where
//
// In Dioxus, this is less likely to occur unless through a fragment
if new.tag_name != old.tag_name || new.namespace != old.namespace {
self.dom.push_root(old.dom_id.get());
self.dom.push(old.dom_id.get());
let meta = self.create(new_node);
self.dom.replace_with(meta.added_to_stack);
self.dom.pop();
@@ -194,7 +194,7 @@ where
new.dom_id.set(oldid);
// push it just in case
self.dom.push_root(oldid);
self.dom.push(oldid);
self.diff_listeners(old.listeners, new.listeners);
self.diff_attr(old.attributes, new.attributes, new.namespace);
self.diff_children(old.children, new.children);
@@ -210,65 +210,48 @@ where
let scope_id = old.ass_scope.get();
new.ass_scope.set(scope_id);
// new.mounted_root.set(old.mounted_root.get());
// make sure the component's caller function is up to date
let scope = self.components.try_get_mut(scope_id.unwrap()).unwrap();
// .with_scope(scope_id.unwrap(), |scope| {
scope.caller = new.caller.clone();
// ack - this doesn't work on its own!
scope.update_children(new.children);
// })
// .unwrap();
// React doesn't automatically memoize, but we do.
// The cost is low enough to make it worth checking
// Rust produces fairly performant comparison methods, sometimes SIMD accelerated
// let should_render = match old.comparator {
// Some(comparator) => comparator(new),
// None => true,
// };
let should_render = match old.comparator {
Some(comparator) => comparator(new),
None => true,
};
if should_render {
scope.run_scope().unwrap();
self.diff_node(scope.old_frame(), scope.next_frame());
} else {
//
}
// if should_render {
// // self.dom.commit_traversal();
// let f = self.components.try_get_mut(scope_id.unwrap()).unwrap();
// self.components
// .with_scope(scope_id.unwrap(), |f| {
// log::debug!("running scope during diff {:#?}", scope_id);
scope.run_scope().unwrap();
self.diff_node(scope.old_frame(), scope.next_frame());
// log::debug!("scope completed {:#?}", scope_id);
self.seen_nodes.insert(scope_id.unwrap());
// })
// .unwrap();
// diff_machine.change_list.load_known_root(root_id);
// run the scope
//
// } else {
// log::error!("Memoized componented");
// // Component has memoized itself and doesn't need to be re-rendered.
// // We still need to make sure the child's props are up-to-date.
// // Don't commit traversal
// }
} else {
// It's an entirely different component
// this seems to be a fairy common code path that we could
let mut old_iter = RealChildIterator::new(old_node, &self.components);
let first = old_iter
.next()
.expect("Components should generate a placeholder root");
// A new component has shown up! We need to destroy the old node
// remove any leftovers
for to_remove in old_iter {
self.dom.push(to_remove);
self.dom.remove();
}
// seems like we could combine this into a single instruction....
self.dom.push(first);
let meta = self.create(new_node);
self.dom.replace_with(meta.added_to_stack);
self.dom.pop();
// Wipe the old one and plant the new one
// self.dom.commit_traversal();
// self.dom.replace_node_with(old.dom_id, new.dom_id);
// self.create(new_node);
log::warn!("creating and replacing...");
self.create(new_node);
// self.dom.replace_with();
// self.create_and_repalce(new_node, old.mounted_root.get());
// Now we need to remove the old scope and all of its descendents
let old_scope = old.ass_scope.get().unwrap();
self.destroy_scopes(old_scope);
}
@@ -278,7 +261,7 @@ where
// This is the case where options or direct vnodes might be used.
// In this case, it's faster to just skip ahead to their diff
if old.children.len() == 1 && new.children.len() == 1 {
self.diff_node(old.children.get(0).unwrap(), new.children.get(0).unwrap());
self.diff_node(&old.children[0], &new.children[0]);
return;
}
@@ -300,6 +283,7 @@ where
) => {
// Choose the node to use as the placeholder for replacewith
let back_node = match old_node {
// We special case these two types to avoid allocating the small-vecs
VNode::Element(_) | VNode::Text(_) => old_node
.get_mounted_id(&self.components)
.expect("Element and text always have a real node"),
@@ -313,7 +297,7 @@ where
// remove any leftovers
for to_remove in old_iter {
self.dom.push_root(to_remove);
self.dom.push(to_remove);
self.dom.remove();
}
@@ -322,9 +306,11 @@ where
};
// replace the placeholder or first node with the nodes generated from the "new"
self.dom.push_root(back_node);
self.dom.push(back_node);
let meta = self.create(new_node);
self.dom.replace_with(meta.added_to_stack);
// todo use the is_static metadata to update this subtree
}
// TODO
@@ -1317,9 +1303,10 @@ enum KeyedPrefixResult {
struct RealChildIterator<'a> {
scopes: &'a SharedArena,
// Heuristcally we should never bleed into 5 completely nested fragments/components
// Heuristcally we should never bleed into 4 completely nested fragments/components
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
// TODO: use const generics instead of the 4 estimation
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 4]>,
}
impl<'a> RealChildIterator<'a> {
+1
View File
@@ -18,6 +18,7 @@ pub enum DomEdit<'bump> {
PushRoot {
root: u64,
},
PopRoot,
AppendChildren {
many: u32,
},
+2 -2
View File
@@ -62,7 +62,7 @@ impl RealDomNode {
pub fn new(id: u64) -> Self {
Self(id)
}
pub fn empty() -> Self {
pub const fn empty() -> Self {
Self(u64::MIN)
}
}
@@ -86,7 +86,7 @@ impl DebugDom {
}
}
impl<'a> RealDom<'a> for DebugDom {
fn push_root(&mut self, root: RealDomNode) {}
fn push(&mut self, root: RealDomNode) {}
fn pop(&mut self) {}
fn append_children(&mut self, many: u32) {}
+53 -43
View File
@@ -71,32 +71,46 @@ impl WebsysDom {
}
impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
fn push_root(&mut self, root: RealDomNode) {
fn push(&mut self, root: RealDomNode) {
log::debug!("Called [push_root] {:?}", root);
let key: DefaultKey = KeyData::from_ffi(root.0).into();
let domnode = self.nodes.get(key).expect("Failed to pop know root");
self.stack.push(domnode.clone());
}
// drop the node off the stack
fn pop(&mut self) {
self.stack.pop();
}
fn append_children(&mut self, many: u32) {
log::debug!("Called [`append_child`]");
let child = self.stack.pop();
if child.dyn_ref::<web_sys::Text>().is_some() {
if self.last_node_was_text {
let comment_node = self
.document
.create_comment("dioxus")
.dyn_into::<Node>()
.unwrap();
self.stack.top().append_child(&comment_node).unwrap();
let mut root: Node = self
.stack
.list
.get(self.stack.list.len() - many as usize)
.unwrap()
.clone();
for _ in 0..many {
let child = self.stack.pop();
if child.dyn_ref::<web_sys::Text>().is_some() {
if self.last_node_was_text {
let comment_node = self
.document
.create_comment("dioxus")
.dyn_into::<Node>()
.unwrap();
self.stack.top().append_child(&comment_node).unwrap();
}
self.last_node_was_text = true;
} else {
self.last_node_was_text = false;
}
self.last_node_was_text = true;
} else {
self.last_node_was_text = false;
}
self.stack.top().append_child(&child).unwrap();
root.append_child(&child).unwrap();
}
}
fn replace_with(&mut self, many: u32) {
@@ -104,35 +118,31 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
let new_node = self.stack.pop();
let old_node = self.stack.pop();
if old_node.has_type::<Element>() {
old_node
.dyn_ref::<Element>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else if old_node.has_type::<web_sys::CharacterData>() {
old_node
.dyn_ref::<web_sys::CharacterData>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else if old_node.has_type::<web_sys::DocumentType>() {
old_node
.dyn_ref::<web_sys::DocumentType>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else {
panic!("Cannot replace node: {:?}", old_node);
// TODO: use different-sized replace withs
if many == 1 {
if old_node.has_type::<Element>() {
old_node
.dyn_ref::<Element>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else if old_node.has_type::<web_sys::CharacterData>() {
old_node
.dyn_ref::<web_sys::CharacterData>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else if old_node.has_type::<web_sys::DocumentType>() {
old_node
.dyn_ref::<web_sys::DocumentType>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else {
panic!("Cannot replace node: {:?}", old_node);
}
}
// // poc to see if this is a valid solution
// if let Some(id) = self.current_known {
// // update mapping
// self.known_roots.insert(id, new_node.clone());
// self.current_known = None;
// }
self.stack.push(new_node);
}
@@ -303,7 +313,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
#[derive(Debug, Default)]
pub struct Stack {
list: Vec<Node>,
pub list: Vec<Node>,
}
impl Stack {
+4 -1
View File
@@ -31,9 +31,12 @@ impl WebviewDom<'_> {
}
}
impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
fn push_root(&mut self, root: RealDomNode) {
fn push(&mut self, root: RealDomNode) {
self.edits.push(PushRoot { root: root.0 });
}
fn pop(&mut self) {
self.edits.push(PopRoot {});
}
fn append_children(&mut self, many: u32) {
self.edits.push(AppendChildren { many });