wip: more work on diffing

This commit is contained in:
Jonathan Kelley committed 2021-06-28 22:22:11 -04:00
1 parent f4fb5bb454
commit 21685fbb16
4 files changed
+40 -50

No files matched your search

+21 -24
View File
@@ -30,22 +30,20 @@ use fxhash::{FxHashMap, FxHashSet};
use std::{ use std::{
any::Any, any::Any,
cell::Cell,
cmp::Ordering,
marker::PhantomData,
rc::{Rc, Weak}, rc::{Rc, Weak},
}; };
/// The accompanying "real dom" exposes an imperative API for controlling the UI layout /// The accompanying "real dom" exposes an imperative API for controlling the UI layout
/// ///
/// Instead of having handles directly over nodes, Dioxus uses simple u32s as node IDs. /// Instead of having handles directly over nodes, Dioxus uses simple u64s as node IDs.
/// This allows layouts with up to 4,294,967,295 nodes. If we use nohasher, then retrieving is very fast. /// The expectation is that the underlying renderer will mainain their Nodes in something like slotmap or an ECS memory
/// where indexing is very fast. For reference, the slotmap in the WebSys renderer takes about 3ns to randomly access any
/// The "RealDom" abstracts over the... real dom. Elements are mapped by ID. The RealDom is inteded to maintain a stack /// node.
/// of real nodes as the diffing algorithm descenes through the tree. This means that whatever is on top of the stack ///
/// will receive modifications. However, instead of using child-based methods for descending through the tree, we instead /// The "RealDom" abstracts over the... real dom. The RealDom trait assumes that the renderer maintains a stack of real
/// ask the RealDom to either push or pop real nodes onto the stack. This saves us the indexing cost while working on a /// nodes as the diffing algorithm descenes through the tree. This means that whatever is on top of the stack will receive
/// single node /// any modifications that follow. This technique enables the diffing algorithm to avoid directly handling or storing any
/// target-specific Node type as well as easily serializing the edits to be sent over a network or IPC connection.
pub trait RealDom<'a> { pub trait RealDom<'a> {
// Navigation // Navigation
fn push_root(&mut self, root: RealDomNode); fn push_root(&mut self, root: RealDomNode);
@@ -60,8 +58,8 @@ pub trait RealDom<'a> {
// Create // Create
fn create_text_node(&mut self, text: &'a str) -> RealDomNode; fn create_text_node(&mut self, text: &'a str) -> RealDomNode;
fn create_element(&mut self, tag: &'static str) -> RealDomNode; fn create_element(&mut self, tag: &'static str, ns: Option<&'static str>) -> RealDomNode;
fn create_element_ns(&mut self, tag: &'static str, namespace: &'static str) -> RealDomNode;
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
fn create_placeholder(&mut self) -> RealDomNode; fn create_placeholder(&mut self) -> RealDomNode;
@@ -73,12 +71,11 @@ pub trait RealDom<'a> {
element_id: usize, element_id: usize,
realnode: RealDomNode, realnode: RealDomNode,
); );
// fn new_event_listener(&mut self, event: &str);
fn remove_event_listener(&mut self, event: &'static str); fn remove_event_listener(&mut self, event: &'static str);
// modify // modify
fn set_text(&mut self, text: &'a str); fn set_text(&mut self, text: &'a str);
fn set_attribute(&mut self, name: &'static str, value: &'a str, is_namespaced: bool); fn set_attribute(&mut self, name: &'static str, value: &'a str, ns: Option<&'a str>);
fn remove_attribute(&mut self, name: &'static str); fn remove_attribute(&mut self, name: &'static str);
// node ref // node ref
@@ -152,7 +149,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
new.dom_id.set(old.dom_id.get()); new.dom_id.set(old.dom_id.get());
self.diff_listeners(old.listeners, new.listeners); self.diff_listeners(old.listeners, new.listeners);
self.diff_attr(old.attributes, new.attributes, new.namespace.is_some()); self.diff_attr(old.attributes, new.attributes, new.namespace);
self.diff_children(old.children, new.children); self.diff_children(old.children, new.children);
} }
// New node is a text element, need to replace the element with a simple text node // New node is a text element, need to replace the element with a simple text node
@@ -348,9 +345,9 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
} = el; } = el;
// log::info!("Creating {:#?}", node); // log::info!("Creating {:#?}", node);
let real_id = if let Some(namespace) = namespace { let real_id = if let Some(namespace) = namespace {
self.dom.create_element_ns(tag_name, namespace) self.dom.create_element(tag_name, Some(namespace))
} else { } else {
self.dom.create_element(tag_name) self.dom.create_element(tag_name, None)
}; };
el.dom_id.set(real_id); el.dom_id.set(real_id);
@@ -361,8 +358,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
}); });
for attr in *attributes { for attr in *attributes {
self.dom self.dom.set_attribute(&attr.name, &attr.value, *namespace);
.set_attribute(&attr.name, &attr.value, namespace.is_some());
} }
// Fast path: if there is a single text child, it is faster to // Fast path: if there is a single text child, it is faster to
@@ -564,7 +560,8 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
&mut self, &mut self,
old: &'bump [Attribute<'bump>], old: &'bump [Attribute<'bump>],
new: &'bump [Attribute<'bump>], new: &'bump [Attribute<'bump>],
is_namespaced: bool, namespace: Option<&'bump str>,
// is_namespaced: bool,
) { ) {
// Do O(n^2) passes to add/update and remove attributes, since // Do O(n^2) passes to add/update and remove attributes, since
// there are almost always very few attributes. // there are almost always very few attributes.
@@ -575,14 +572,14 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
if new_attr.is_volatile() { if new_attr.is_volatile() {
// self.dom.commit_traversal(); // self.dom.commit_traversal();
self.dom self.dom
.set_attribute(new_attr.name, new_attr.value, is_namespaced); .set_attribute(new_attr.name, new_attr.value, namespace);
} else { } else {
for old_attr in old { for old_attr in old {
if old_attr.name == new_attr.name { if old_attr.name == new_attr.name {
if old_attr.value != new_attr.value { if old_attr.value != new_attr.value {
// self.dom.commit_traversal(); // self.dom.commit_traversal();
self.dom self.dom
.set_attribute(new_attr.name, new_attr.value, is_namespaced); .set_attribute(new_attr.name, new_attr.value, namespace);
} }
continue 'outer; continue 'outer;
} else { } else {
@@ -592,7 +589,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
// self.dom.commit_traversal(); // self.dom.commit_traversal();
self.dom self.dom
.set_attribute(new_attr.name, new_attr.value, is_namespaced); .set_attribute(new_attr.name, new_attr.value, namespace);
} }
} }
+2 -6
View File
@@ -24,15 +24,11 @@ impl<'a> RealDom<'a> for DebugDom {
RealDomNode::new(self.counter) RealDomNode::new(self.counter)
} }
fn create_element(&mut self, tag: &str) -> RealDomNode { fn create_element(&mut self, tag: &str, ns: Option<&'a str>) -> RealDomNode {
self.counter += 1; self.counter += 1;
RealDomNode::new(self.counter) RealDomNode::new(self.counter)
} }
fn create_element_ns(&mut self, tag: &str, namespace: &str) -> RealDomNode {
self.counter += 1;
RealDomNode::new(self.counter)
}
fn create_placeholder(&mut self) -> RealDomNode { fn create_placeholder(&mut self) -> RealDomNode {
self.counter += 1; self.counter += 1;
RealDomNode::new(self.counter) RealDomNode::new(self.counter)
@@ -51,7 +47,7 @@ impl<'a> RealDom<'a> for DebugDom {
fn set_text(&mut self, text: &str) {} fn set_text(&mut self, text: &str) {}
fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) {} fn set_attribute(&mut self, name: &str, value: &str, namespace: Option<&str>) {}
fn remove_attribute(&mut self, name: &str) {} fn remove_attribute(&mut self, name: &str) {}
+3
View File
@@ -0,0 +1,3 @@
{
"rust-analyzer.inlayHints.enable": false
}
+14 -20
View File
@@ -58,11 +58,11 @@ pub enum SerializedDom<'bump> {
id: u64, id: u64,
}, },
NewEventListener { NewEventListener {
event: &'bump str,
scope: ScopeIdx, scope: ScopeIdx,
node: u64, node: u64,
idx: usize, idx: usize,
}, },
RemoveEventListener { RemoveEventListener {
event: &'bump str, event: &'bump str,
}, },
@@ -72,6 +72,7 @@ pub enum SerializedDom<'bump> {
SetAttribute { SetAttribute {
field: &'bump str, field: &'bump str,
value: &'bump str, value: &'bump str,
ns: Option<&'bump str>,
}, },
RemoveAttribute { RemoveAttribute {
name: &'bump str, name: &'bump str,
@@ -85,7 +86,7 @@ impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
} }
fn append_child(&mut self) { fn append_child(&mut self) {
self.edits.push(AppendChild {}); self.edits.push(AppendChild);
} }
fn replace_with(&mut self) { fn replace_with(&mut self) {
@@ -107,25 +108,17 @@ impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
id id
} }
fn create_element(&mut self, tag: &'bump str) -> dioxus_core::virtual_dom::RealDomNode { fn create_element(
self.node_counter += 1;
let id = RealDomNode::new(self.node_counter);
self.edits.push(CreateElement { id: id.0, tag });
id
}
fn create_element_ns(
&mut self, &mut self,
tag: &'static str, tag: &'bump str,
namespace: &'static str, ns: Option<&'bump str>,
) -> dioxus_core::virtual_dom::RealDomNode { ) -> dioxus_core::virtual_dom::RealDomNode {
self.node_counter += 1; self.node_counter += 1;
let id = RealDomNode::new(self.node_counter); let id = RealDomNode::new(self.node_counter);
self.edits.push(CreateElementNs { match ns {
id: id.0, Some(ns) => self.edits.push(CreateElementNs { id: id.0, ns, tag }),
ns: namespace, None => self.edits.push(CreateElement { id: id.0, tag }),
tag, }
});
id id
} }
@@ -138,13 +131,14 @@ impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
fn new_event_listener( fn new_event_listener(
&mut self, &mut self,
event: &str, event: &'static str,
scope: dioxus_core::prelude::ScopeIdx, scope: dioxus_core::prelude::ScopeIdx,
element_id: usize, element_id: usize,
realnode: dioxus_core::virtual_dom::RealDomNode, realnode: dioxus_core::virtual_dom::RealDomNode,
) { ) {
self.edits.push(NewEventListener { self.edits.push(NewEventListener {
scope, scope,
event,
idx: element_id, idx: element_id,
node: realnode.0, node: realnode.0,
}); });
@@ -158,8 +152,8 @@ impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
self.edits.push(SetText { text }); self.edits.push(SetText { text });
} }
fn set_attribute(&mut self, field: &'static str, value: &'bump str, is_namespaced: bool) { fn set_attribute(&mut self, field: &'static str, value: &'bump str, ns: Option<&'bump str>) {
self.edits.push(SetAttribute { field, value }); self.edits.push(SetAttribute { field, value, ns });
} }
fn remove_attribute(&mut self, name: &'static str) { fn remove_attribute(&mut self, name: &'static str) {