diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 79505b018..1c68f97cc 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": false + "rust-analyzer.inlayHints.enable": true } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index d048fb094..53522c0b2 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -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::>(); 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::>(); - // 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::>(); - 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), -} diff --git a/packages/core/src/diff_stack.rs b/packages/core/src/diff_stack.rs index a0528e947..6876faa96 100644 --- a/packages/core/src/diff_stack.rs +++ b/packages/core/src/diff_stack.rs @@ -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 });