wip: it works?

This commit is contained in:
Jonathan Kelley 2021-08-23 15:35:26 -04:00
parent 082765f687
commit 778baffb10
3 changed files with 103 additions and 238 deletions

View file

@ -1,3 +1,3 @@
{
"rust-analyzer.inlayHints.enable": false
"rust-analyzer.inlayHints.enable": true
}

View file

@ -188,6 +188,10 @@ impl<'bump> DiffMachine<'bump> {
DiffInstruction::Mount { and } => {
self.mount(and);
}
DiffInstruction::PrepareMoveNode { node } => {
//
}
};
}
@ -224,6 +228,12 @@ impl<'bump> DiffMachine<'bump> {
let root = self.find_first_element(other_node).direct_id();
self.mutations.insert_before(root, nodes_created as u32);
}
MountType::InsertAfterFlush { other_node } => {
self.stack.push_nodes_created(0);
let root = self.find_last_element(other_node).direct_id();
self.mutations.insert_after(root, nodes_created as u32);
}
}
}
@ -693,94 +703,72 @@ impl<'bump> DiffMachine<'bump> {
//
// `shared_prefix_count` is the count of how many nodes at the start of
// `new` and `old` share the same keys.
//
// TODO: just inline this
let shared_prefix_count = match self.diff_keyed_prefix(old, new) {
KeyedPrefixResult::Finished => return,
KeyedPrefixResult::MoreWorkToDo(count) => count,
let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
Some(count) => count,
None => return,
};
// Next, we find out how many of the nodes at the end of the children have
// the same key. We do _not_ diff them yet, since we want to emit the change
// list instructions such that they can be applied in a single pass over the
// DOM. Instead, we just save this information for later.
//
// `shared_suffix_count` is the count of how many nodes at the end of `new`
// and `old` share the same keys.
let shared_suffix_count = old[shared_prefix_count..]
.iter()
.rev()
.zip(new[shared_prefix_count..].iter().rev())
.take_while(|&(old, new)| old.key() == new.key())
.count();
let old_shared_suffix_start = old.len() - shared_suffix_count;
let new_shared_suffix_start = new.len() - shared_suffix_count;
// Ok, we now hopefully have a smaller range of children in the middle
// within which to re-order nodes with the same keys, remove old nodes with
// now-unused keys, and create new nodes with fresh keys.
self.diff_keyed_middle(
&old[shared_prefix_count..old_shared_suffix_start],
&new[shared_prefix_count..new_shared_suffix_start],
shared_prefix_count,
shared_suffix_count,
old_shared_suffix_start,
&old[left_offset..(old.len() - right_offset)],
&new[left_offset..(new.len() - right_offset)],
);
// Finally, diff the nodes at the end of `old` and `new` that share keys.
let old_suffix = &old[old_shared_suffix_start..];
let new_suffix = &new[new_shared_suffix_start..];
debug_assert_eq!(old_suffix.len(), new_suffix.len());
if !old_suffix.is_empty() {
self.diff_keyed_suffix(old_suffix, new_suffix, new_shared_suffix_start)
}
}
// Diff the prefix of children in `new` and `old` that share the same keys in
// the same order.
//
// The stack is empty upon entry.
fn diff_keyed_prefix(
/// Diff both ends of the children that share keys.
///
/// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing.
///
/// If there is no offset, then this function returns None and the diffing is complete.
fn diff_keyed_ends(
&mut self,
old: &'bump [VNode<'bump>],
new: &'bump [VNode<'bump>],
) -> KeyedPrefixResult {
let mut shared_prefix_count = 0;
) -> Option<(usize, usize)> {
let mut left_offset = 0;
for (old, new) in old.iter().zip(new.iter()) {
// abort early if we finally run into nodes with different keys
if old.key() != new.key() {
break;
}
self.diff_node(old, new);
shared_prefix_count += 1;
self.stack.push(DiffInstruction::DiffNode { old, new });
left_offset += 1;
}
// If that was all of the old children, then create and append the remaining
// new children and we're finished.
if shared_prefix_count == old.len() {
// Load the last element
let last_node = self.find_last_element(new.last().unwrap()).direct_id();
self.mutations.push_root(last_node);
// Create the new children and insert them after
//
todo!();
// let meta = self.create_children(&new[shared_prefix_count..]);
// self.mutations.insert_after(meta.added_to_stack);
return KeyedPrefixResult::Finished;
if left_offset == old.len() {
self.stack.create_children(
&new[left_offset..],
MountType::InsertAfter {
other_node: old.last().unwrap(),
},
);
return None;
}
// And if that was all of the new children, then remove all of the remaining
// old children and we're finished.
if shared_prefix_count == new.len() {
self.remove_nodes(&old[shared_prefix_count..]);
return KeyedPrefixResult::Finished;
if left_offset == new.len() {
self.remove_nodes(&old[left_offset..]);
return None;
}
KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
// if the shared prefix is less than either length, then we need to walk backwards
let mut right_offset = 0;
for (old, new) in old.iter().rev().zip(new.iter().rev()) {
// abort early if we finally run into nodes with different keys
if old.key() != new.key() {
break;
}
self.diff_node(old, new);
right_offset += 1;
}
Some((left_offset, right_offset))
}
// The most-general, expensive code path for keyed children diffing.
@ -796,14 +784,7 @@ impl<'bump> DiffMachine<'bump> {
// This function will load the appropriate nodes onto the stack and do diffing in place.
//
// Upon exit from this function, it will be restored to that same state.
fn diff_keyed_middle(
&mut self,
old: &'bump [VNode<'bump>],
mut new: &'bump [VNode<'bump>],
shared_prefix_count: usize,
shared_suffix_count: usize,
old_shared_suffix_start: usize,
) {
fn diff_keyed_middle(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
/*
1. Map the old keys into a numerical ordering based on indicies.
2. Create a map of old key to its index
@ -841,39 +822,30 @@ impl<'bump> DiffMachine<'bump> {
.collect::<FxHashMap<_, _>>();
let mut shared_keys = FxHashSet::default();
let mut to_add = FxHashSet::default();
// 3. Map each new key to the old key, carrying over the old index.
let new_index_to_old_index = new
.iter()
.map(|n| {
let key = n.key().unwrap();
.map(|node| {
let key = node.key().unwrap();
if let Some(&index) = old_key_to_old_index.get(&key) {
shared_keys.insert(key);
index
} else {
to_add.insert(key);
u32::MAX as usize
}
})
.collect::<Vec<_>>();
// If none of the old keys are reused by the new children, then we
// remove all the remaining old children and create the new children
// afresh.
if shared_suffix_count == 0 && shared_keys.is_empty() {
// If none of the old keys are reused by the new children, then we remove all the remaining old children and
// create the new children afresh.
if shared_keys.is_empty() {
self.replace_and_create_many_with_many(old, new);
return;
}
// 4. Compute the LIS of this list
// The longest increasing subsequence within `new_index_to_old_index`. This
// is the longest sequence on DOM nodes in `old` that are relatively ordered
// correctly within `new`. We will leave these nodes in place in the DOM,
// and only move nodes that are not part of the LIS. This results in the
// maximum number of DOM nodes left in place, AKA the minimum number of DOM
// nodes moved.
// TODO: investigate if we can go without the hashset here, I can't quite tell from the LIS crate
let mut new_index_is_in_lis = FxHashSet::default();
new_index_is_in_lis.reserve(new_index_to_old_index.len());
@ -888,141 +860,50 @@ impl<'bump> DiffMachine<'bump> {
&mut starts,
);
// use the old nodes to navigate the new nodes
let mut lis_in_order = new_index_is_in_lis.into_iter().collect::<Vec<_>>();
lis_in_order.sort_unstable();
// REMEMBER: we're generating instructions. we cannot rely on anything stack related to be consistent.
// we walk front to back, creating the head node
// diff the shared, in-place nodes first
// this makes sure we can rely on their first/last nodes being correct later on
for id in &lis_in_order {
let new_node = &new[*id];
let key = new_node.key().unwrap();
let old_index = old_key_to_old_index.get(&key).unwrap();
let old_node = &old[*old_index];
self.diff_node(old_node, new_node);
}
// walk the new list, creating nodes or diffing them. if we hit an LIS, then we call "insert before"
let mut last_lis = 0;
// return the old node from the key
let load_old_node_from_lsi = |key| -> &VNode {
let old_index = old_key_to_old_index.get(key).unwrap();
let old_node = &old[*old_index];
old_node
};
self.stack.push_nodes_created(0);
for (idx, new_node) in new.into_iter().enumerate().rev() {
let old_index = new_index_to_old_index[idx];
let mut root = None;
let mut new_iter = new.iter().enumerate();
for lis_id in &lis_in_order {
eprintln!("tracking {:?}", lis_id);
// this is the next milestone node we are working up to
let new_anchor = &new[*lis_id];
root = Some(new_anchor);
// let anchor_el = self.find_first_element(new_anchor);
// self.mutations.push_root(anchor_el.direct_id());
// // let mut pushed = false;
'inner: loop {
let (next_id, next_new) = new_iter.next().unwrap();
if next_id == *lis_id {
// we've reached the milestone, break this loop so we can step to the next milestone
// remember: we already diffed this node
eprintln!("breaking {:?}", next_id);
break 'inner;
} else {
let key = next_new.key().unwrap();
eprintln!("found key {:?}", key);
if shared_keys.contains(&key) {
eprintln!("key is contained {:?}", key);
shared_keys.remove(key);
// diff the two nodes
let old_node = load_old_node_from_lsi(key);
self.diff_node(old_node, next_new);
// now move all the nodes into the right spot
for child in RealChildIterator::new(next_new, self.vdom) {
let el = child.direct_id();
self.mutations.push_root(el);
todo!();
// self.mutations.insert_before(1);
}
} else {
self.stack.create_node(
next_new,
MountType::InsertBefore {
other_node: new_anchor,
},
);
}
}
}
self.mutations.pop();
}
let final_lis_node = root.unwrap();
let final_el_node = self.find_last_element(final_lis_node);
let final_el = final_el_node.direct_id();
self.mutations.push_root(final_el);
let mut last_iter = new.iter().rev().enumerate();
let last_key = final_lis_node.key().unwrap();
loop {
let (last_id, last_node) = last_iter.next().unwrap();
let key = last_node.key().unwrap();
eprintln!("checking final nodes {:?}", key);
if last_key == key {
eprintln!("breaking final nodes");
break;
}
if shared_keys.contains(&key) {
eprintln!("key is contained {:?}", key);
shared_keys.remove(key);
// diff the two nodes
let old_node = load_old_node_from_lsi(key);
self.diff_node(old_node, last_node);
// now move all the nodes into the right spot
for child in RealChildIterator::new(last_node, self.vdom) {
let el = child.direct_id();
self.mutations.push_root(el);
// self.mutations.insert_after(1);
todo!();
}
if old_index == u32::MAX as usize {
self.stack.create_node(new_node, MountType::Absorb);
} else {
eprintln!("key is not contained {:?}", key);
// new node needs to be created
// insert it before the current milestone
todo!();
// let meta = self.create_vnode(last_node);
// self.mutations.insert_after(meta.added_to_stack);
// diff / move
if let Some(new_index) = new_index_is_in_lis.get(&idx) {
// take all the nodes on the stack and insert them before this one
self.stack.push(DiffInstruction::Mount {
and: MountType::InsertAfterFlush {
other_node: new_node,
},
});
self.stack.push(DiffInstruction::DiffNode {
new: new_node,
old: &old[old_index],
});
last_lis = *new_index;
} else {
// this is not an LIS node, we need to diff it and move it
self.stack
.push(DiffInstruction::PrepareMoveNode { node: new_node });
self.stack.push(DiffInstruction::DiffNode {
new: new_node,
old: &old[old_index],
});
}
}
}
self.mutations.pop();
}
// Diff the suffix of keyed children that share the same keys in the same order.
//
// The parent must be on the change list stack when we enter this function:
//
// [... parent]
//
// When this function exits, the change list stack remains the same.
fn diff_keyed_suffix(
&mut self,
old: &'bump [VNode<'bump>],
new: &'bump [VNode<'bump>],
new_shared_suffix_start: usize,
) {
debug_assert_eq!(old.len(), new.len());
debug_assert!(!old.is_empty());
for (old_child, new_child) in old.iter().zip(new.iter()) {
self.diff_node(old_child, new_child);
}
// There will be nodes left on top of the stack. They need to be inserted before the final
let last_node = &old[last_lis];
self.stack.push(DiffInstruction::Mount {
and: MountType::InsertBefore {
other_node: last_node,
},
});
}
// =====================
@ -1075,14 +956,6 @@ impl<'bump> DiffMachine<'bump> {
}
}
fn replace_many_with_many(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
//
}
fn replace_one_with_many(&mut self, old: &'bump VNode<'bump>, new: &'bump [VNode<'bump>]) {
self.stack.create_children(new, MountType::Replace { old });
}
fn replace_and_create_many_with_one(
&mut self,
old: &'bump [VNode<'bump>],
@ -1230,15 +1103,6 @@ impl<'bump> DiffMachine<'bump> {
}
}
fn replace_node_with_node(
&mut self,
old_node: &'bump VNode<'bump>,
new_node: &'bump VNode<'bump>,
) {
self.stack
.create_node(new_node, MountType::Replace { old: old_node });
}
fn fix_listener<'a>(&mut self, listener: &'a Listener<'a>) {
let scope_id = self.stack.current_scope();
if let Some(scope_id) = scope_id {
@ -1249,12 +1113,3 @@ impl<'bump> DiffMachine<'bump> {
}
}
}
enum KeyedPrefixResult {
// Fast path: we finished diffing all the children just by looking at the
// prefix of shared keys!
Finished,
// There is more diffing work to do. Here is a count of how many children at
// the beginning of `new` and `old` we already processed.
MoreWorkToDo(usize),
}

View file

@ -18,6 +18,11 @@ pub enum DiffInstruction<'a> {
node: &'a VNode<'a>,
},
/// pushes the node elements onto the stack for use in mount
PrepareMoveNode {
node: &'a VNode<'a>,
},
Mount {
and: MountType<'a>,
},
@ -34,6 +39,7 @@ pub enum MountType<'a> {
Replace { old: &'a VNode<'a> },
ReplaceByElementId { el: ElementId },
InsertAfter { other_node: &'a VNode<'a> },
InsertAfterFlush { other_node: &'a VNode<'a> },
InsertBefore { other_node: &'a VNode<'a> },
}
@ -74,6 +80,10 @@ impl<'bump> DiffStack<'bump> {
}
}
pub fn push_nodes_created(&mut self, count: usize) {
self.nodes_created_stack.push(count);
}
pub fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
self.nodes_created_stack.push(0);
self.instructions.push(DiffInstruction::Mount { and });