wip: more work on diffing machine
This commit is contained in:
1 parent
ca3b80f069
commit
9813f23cdf
9 files changed
+172
-299
No files matched your search
+112
-94
@@ -4,15 +4,15 @@
|
||||
//! Notice:
|
||||
//! ------
|
||||
//!
|
||||
//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and modified to support Components,
|
||||
//! Fragments, Suspense, and additional batching operations.
|
||||
//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
|
||||
//! Components, Fragments, Suspense, and additional batching operations.
|
||||
//!
|
||||
//! Implementation Details:
|
||||
//! -----------------------
|
||||
//!
|
||||
//! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
|
||||
//! We don't necessarily intend for changes to happen exactly during the diffing process, so the implementor may choose
|
||||
//! to batch nodes if it is more performant for their application. The u32 should be a no-op to hash,
|
||||
//! We don't necessarily require that DOM changes happen instnatly during the diffing process, so the implementor may choose
|
||||
//! to batch nodes if it is more performant for their application. We care about an ID size of u32
|
||||
//!
|
||||
//!
|
||||
//! Further Reading and Thoughts
|
||||
@@ -58,6 +58,8 @@ pub trait RealDom {
|
||||
fn create_text_node(&mut self, text: &str) -> RealDomNode;
|
||||
fn create_element(&mut self, tag: &str) -> RealDomNode;
|
||||
fn create_element_ns(&mut self, tag: &str, namespace: &str) -> RealDomNode;
|
||||
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
|
||||
fn create_placeholder(&mut self) -> RealDomNode;
|
||||
|
||||
// events
|
||||
fn new_event_listener(
|
||||
@@ -152,7 +154,6 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
}
|
||||
// New node is a text element, need to replace the element with a simple text node
|
||||
VNode::Text(_) => {
|
||||
log::debug!("Replacing el with text");
|
||||
self.create(new_node);
|
||||
self.dom.replace_with();
|
||||
}
|
||||
@@ -167,10 +168,22 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
// New node is actually a sequence of nodes.
|
||||
// We need to replace this one node with a sequence of nodes
|
||||
// Not yet implement because it's kinda hairy
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Fragment(new) => {
|
||||
match new.children.len() {
|
||||
0 => {
|
||||
// remove
|
||||
}
|
||||
1 => {
|
||||
// replace
|
||||
}
|
||||
_ => {
|
||||
// remove and mount many
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New Node is actually suspended. Todo
|
||||
VNode::Suspended => todo!(),
|
||||
VNode::Suspended { real } => todo!(),
|
||||
},
|
||||
|
||||
// Old element was text
|
||||
@@ -200,7 +213,7 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
}
|
||||
}
|
||||
}
|
||||
VNode::Suspended => todo!(),
|
||||
VNode::Suspended { real } => todo!(),
|
||||
},
|
||||
|
||||
// Old element was a component
|
||||
@@ -214,75 +227,74 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
|
||||
// It's also a component
|
||||
VNode::Component(new) => {
|
||||
match old.user_fc == new.user_fc {
|
||||
if old.user_fc == new.user_fc {
|
||||
// Make sure we're dealing with the same component (by function pointer)
|
||||
true => {
|
||||
// Make sure the new component vnode is referencing the right scope id
|
||||
let scope_id = old.ass_scope.borrow().clone();
|
||||
*new.ass_scope.borrow_mut() = scope_id;
|
||||
|
||||
// make sure the component's caller function is up to date
|
||||
// Make sure the new component vnode is referencing the right scope id
|
||||
let scope_id = old.ass_scope.borrow().clone();
|
||||
*new.ass_scope.borrow_mut() = scope_id;
|
||||
|
||||
// make sure the component's caller function is up to date
|
||||
self.components
|
||||
.with_scope(scope_id.unwrap(), |scope| {
|
||||
scope.caller = Rc::downgrade(&new.caller)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// React doesn't automatically memoize, but we do.
|
||||
// The cost is low enough to make it worth checking
|
||||
let should_render = match old.comparator {
|
||||
Some(comparator) => comparator(new),
|
||||
None => true,
|
||||
};
|
||||
|
||||
if should_render {
|
||||
// // self.dom.commit_traversal();
|
||||
self.components
|
||||
.with_scope(scope_id.unwrap(), |scope| {
|
||||
scope.caller = Rc::downgrade(&new.caller)
|
||||
.with_scope(scope_id.unwrap(), |f| {
|
||||
f.run_scope().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// React doesn't automatically memoize, but we do.
|
||||
// The cost is low enough to make it worth checking
|
||||
let should_render = match old.comparator {
|
||||
Some(comparator) => comparator(new),
|
||||
None => true,
|
||||
};
|
||||
|
||||
if should_render {
|
||||
// // self.dom.commit_traversal();
|
||||
self.components
|
||||
.with_scope(scope_id.unwrap(), |f| {
|
||||
f.run_scope().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
// diff_machine.change_list.load_known_root(root_id);
|
||||
// run the scope
|
||||
//
|
||||
} else {
|
||||
// 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
|
||||
}
|
||||
// diff_machine.change_list.load_known_root(root_id);
|
||||
// run the scope
|
||||
//
|
||||
} else {
|
||||
// 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
|
||||
false => {
|
||||
// A new component has shown up! We need to destroy the old node
|
||||
|
||||
// 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);
|
||||
// self.dom.replace_with();
|
||||
self.create(new_node);
|
||||
// self.create_and_repalce(new_node, old.mounted_root.get());
|
||||
// A new component has shown up! We need to destroy the old node
|
||||
|
||||
// Now we need to remove the old scope and all of its descendents
|
||||
let old_scope = old.ass_scope.borrow().as_ref().unwrap().clone();
|
||||
self.destroy_scopes(old_scope);
|
||||
}
|
||||
// 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);
|
||||
// self.dom.replace_with();
|
||||
self.create(new_node);
|
||||
// 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.borrow().as_ref().unwrap().clone();
|
||||
self.destroy_scopes(old_scope);
|
||||
}
|
||||
}
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Suspended => todo!(),
|
||||
VNode::Suspended { real } => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
VNode::Fragment(old) => {
|
||||
//
|
||||
match new_node {
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Fragment(new) => todo!(),
|
||||
|
||||
// going from fragment to element means we're going from many (or potentially none) to one
|
||||
VNode::Element(new) => {}
|
||||
VNode::Text(_) => todo!(),
|
||||
VNode::Suspended => todo!(),
|
||||
VNode::Suspended { real } => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
}
|
||||
}
|
||||
@@ -290,10 +302,12 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
// a suspended node will perform a mem-copy of the previous elements until it is ready
|
||||
// this means that event listeners will need to be disabled and removed
|
||||
// it also means that props will need to disabled - IE if the node "came out of hibernation" any props should be considered outdated
|
||||
VNode::Suspended => {
|
||||
VNode::Suspended { real: old_real } => {
|
||||
//
|
||||
match new_node {
|
||||
VNode::Suspended => todo!(),
|
||||
VNode::Suspended { real: new_real } => {
|
||||
//
|
||||
}
|
||||
VNode::Element(_) => todo!(),
|
||||
VNode::Text(_) => todo!(),
|
||||
VNode::Fragment(_) => todo!(),
|
||||
@@ -354,6 +368,10 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
// to emit three instructions to (1) create a text node, (2) set its
|
||||
// text content, and finally (3) append the text node to this
|
||||
// parent.
|
||||
//
|
||||
// Notice: this is a web-specific optimization and may be changed in the future
|
||||
//
|
||||
// TODO move over
|
||||
// if children.len() == 1 {
|
||||
// if let VNode::Text(text) = &children[0] {
|
||||
// self.dom.set_text(text.text);
|
||||
@@ -373,7 +391,7 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
}
|
||||
|
||||
VNode::Component(component) => {
|
||||
self.dom.create_text_node("placeholder for vcomponent");
|
||||
let real_id = self.dom.create_placeholder();
|
||||
|
||||
// let root_id = next_id();
|
||||
// self.dom.save_known_root(root_id);
|
||||
@@ -426,7 +444,6 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
new_component.run_scope().unwrap();
|
||||
|
||||
// And then run the diff algorithm
|
||||
// todo!();
|
||||
self.diff_node(new_component.old_frame(), new_component.next_frame());
|
||||
|
||||
// Finally, insert this node as a seen node.
|
||||
@@ -437,21 +454,17 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
VNode::Fragment(frag) => {
|
||||
// create the children directly in the space
|
||||
for child in frag.children {
|
||||
todo!()
|
||||
// self.create(child);
|
||||
// self.dom.append_child();
|
||||
self.create(child);
|
||||
self.dom.append_child();
|
||||
}
|
||||
}
|
||||
|
||||
VNode::Suspended => {
|
||||
todo!("Creation of VNode::Suspended not yet supported")
|
||||
VNode::Suspended { real } => {
|
||||
let id = self.dom.create_placeholder();
|
||||
real.set(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_children(&self, node: &VNode<'a>) -> ChildIterator<'a> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
@@ -680,29 +693,29 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
// [... parent]
|
||||
//
|
||||
// Upon exiting, the change list stack is in the same state.
|
||||
fn diff_keyed_children(&self, old: &[VNode<'a>], new: &[VNode<'a>]) {
|
||||
todo!();
|
||||
// if cfg!(debug_assertions) {
|
||||
// let mut keys = fxhash::FxHashSet::default();
|
||||
// let mut assert_unique_keys = |children: &[VNode]| {
|
||||
// keys.clear();
|
||||
// for child in children {
|
||||
// let key = child.key();
|
||||
// debug_assert!(
|
||||
// key.is_some(),
|
||||
// "if any sibling is keyed, all siblings must be keyed"
|
||||
// );
|
||||
// keys.insert(key);
|
||||
// }
|
||||
// debug_assert_eq!(
|
||||
// children.len(),
|
||||
// keys.len(),
|
||||
// "keyed siblings must each have a unique key"
|
||||
// );
|
||||
// };
|
||||
// assert_unique_keys(old);
|
||||
// assert_unique_keys(new);
|
||||
// }
|
||||
fn diff_keyed_children(&self, old: &'a [VNode<'a>], new: &'a [VNode<'a>]) {
|
||||
// todo!();
|
||||
if cfg!(debug_assertions) {
|
||||
let mut keys = fxhash::FxHashSet::default();
|
||||
let mut assert_unique_keys = |children: &'a [VNode<'a>]| {
|
||||
keys.clear();
|
||||
for child in children {
|
||||
let key = child.key();
|
||||
debug_assert!(
|
||||
key.is_some(),
|
||||
"if any sibling is keyed, all siblings must be keyed"
|
||||
);
|
||||
keys.insert(key);
|
||||
}
|
||||
debug_assert_eq!(
|
||||
children.len(),
|
||||
keys.len(),
|
||||
"keyed siblings must each have a unique key"
|
||||
);
|
||||
};
|
||||
assert_unique_keys(old);
|
||||
assert_unique_keys(new);
|
||||
}
|
||||
|
||||
// First up, we diff all the nodes with the same key at the beginning of the
|
||||
// children.
|
||||
@@ -1090,7 +1103,12 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
// [... parent prev_child]
|
||||
// self.dom.go_to_sibling(i);
|
||||
// [... parent this_child]
|
||||
self.dom.push_root(old_child.get_mounted_id().unwrap());
|
||||
|
||||
let did = old_child.get_mounted_id().unwrap();
|
||||
if did.0 == 0 {
|
||||
log::debug!("Root is bad {:#?}", old_child);
|
||||
}
|
||||
self.dom.push_root(did);
|
||||
self.diff_node(old_child, new_child);
|
||||
|
||||
let old_id = old_child.get_mounted_id().unwrap();
|
||||
@@ -1270,7 +1288,7 @@ impl<'a> Iterator for ChildIterator<'a> {
|
||||
|
||||
// Immediately abort suspended nodes - can't do anything with them yet
|
||||
// VNode::Suspended => should_pop = true,
|
||||
VNode::Suspended => todo!(),
|
||||
VNode::Suspended { real } => todo!(),
|
||||
|
||||
// For components, we load their root and push them onto the stack
|
||||
VNode::Component(sc) => {
|
||||
@@ -1370,7 +1388,7 @@ mod tests {
|
||||
|
||||
// These would represent failing cases.
|
||||
VNode::Fragment(_) => panic!("Found: Fragment"),
|
||||
VNode::Suspended => panic!("Found: Suspended"),
|
||||
VNode::Suspended { real } => panic!("Found: Suspended"),
|
||||
VNode::Component(_) => panic!("Found: Component"),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user