@@ -6,8 +6,8 @@ use crate::{
7use fxhash::{FxHashMap, FxHashSet};
7use fxhash::{FxHashMap, FxHashSet};
8use std::cmp::Ordering;
8use std::cmp::Ordering;
10use std::u32;
9use std::u32;
10+use wasm_bindgen::UnwrapThrowExt;
12// Diff the `old` node with the `new` node. Emits instructions to modify a
12// Diff the `old` node with the `new` node. Emits instructions to modify a
13// physical DOM node that reflects `old` into something that reflects `new`.
13// physical DOM node that reflects `old` into something that reflects `new`.
@@ -465,7 +465,7 @@ fn diff_keyed_middle(
465 change_list: &mut ChangeListBuilder,
465 change_list: &mut ChangeListBuilder,
466 registry: &mut EventsRegistry,
466 registry: &mut EventsRegistry,
467 old: &[Node],
467 old: &[Node],
468− new: &[Node],
468+ mut new: &[Node],
469 cached_roots: &mut FxHashSet<CacheId>,
469 cached_roots: &mut FxHashSet<CacheId>,
470 shared_prefix_count: usize,
470 shared_prefix_count: usize,
471 shared_suffix_count: usize,
471 shared_suffix_count: usize,
@@ -516,22 +516,6 @@ fn diff_keyed_middle(
519− // The longest increasing subsequence within `new_index_to_old_index` in
520− // reverse order (since `lis_with` adds the indices in reverse
521− // order). We will leave these nodes in place in the DOM, and only move
522− // nodes that are not part of the LIS. This results in the minimum
523− // number of DOM nodes moved.
524− let mut reverse_lis = Vec::with_capacity(new_index_to_old_index.len());
525− let mut predecessors = vec![0; new_index_to_old_index.len()];
526− let mut starts = vec![0; new_index_to_old_index.len()];
527− longest_increasing_subsequence::lis_with(
528− &new_index_to_old_index,
535 // Save each of the old children whose keys are reused in the new
519 // Save each of the old children whose keys are reused in the new
536 // children.
520 // children.
537 let mut old_index_to_temp = vec![u32::MAX; old.len()];
521 let mut old_index_to_temp = vec![u32::MAX; old.len()];
@@ -568,165 +552,134 @@ fn diff_keyed_middle(
568 let mut removed_count = 0;
552 let mut removed_count = 0;
569 for (i, old_child) in old.iter().enumerate().rev() {
553 for (i, old_child) in old.iter().enumerate().rev() {
570 if !shared_keys.contains(&old_child.key()) {
554 if !shared_keys.contains(&old_child.key()) {
555+ registry.remove_subtree(old_child);
571 change_list.remove_child(i + shared_prefix_count);
556 change_list.remove_child(i + shared_prefix_count);
572 removed_count += 1;
557 removed_count += 1;
576− // Whether we have pushed a child onto the change list stack or not.
561+ // If there aren't any more new children, then we are done!
577− let mut pushed = false;
562+ if new.is_empty() {
579− // Now iterate from the end of the new children back to the beginning,
580− // moving old children to their new destination, diffing them, and
581− // creating new children as necessary. Note that iterating in reverse
582− // order lets us use `Node.prototype.insertBefore` to move/insert
584− if shared_suffix_count > 0 {
585− change_list.push_child(old_shared_suffix_start - removed_count);
588− let mut segment_end = new.len();
589− for lis_index in reverse_lis {
590− // Because we use `u32::MAX` as a sentinel value representing "a child
591− // with this key is not a member of `new`", we filter that out here.
592− let old_index = new_index_to_old_index[lis_index];
593− if old_index == u32::MAX as usize {
597− // A segment begins with a member of the LIS, which will remain in
598− // place in the DOM, followed by zero or more new children that are
599− // not members of the LIS. If one of these new children shares a key
600− // with one of the old children, then we will move that old child in
601− // the DOM. Otherwise, we create the new child afresh.
602− debug_assert!(segment_end - lis_index > 0);
604− // Go through each of the non-LIS child and move-and-diff or create
606− diff_and_move_or_create_segment(
613− lis_index + 1..segment_end,
614− &new_index_to_old_index,
619− // Now we push the member of the LIS and diff it with its
620− // correspondingly-keyed new child.
621− let new_child = &new[lis_index];
622− let temp = old_index_to_temp[old_index];
623− debug_assert_ne!(temp, u32::MAX);
627− change_list.push_temporary(temp);
638− segment_end = lis_index;
641− // And now diff-and-move or create each of the non-LIS children that
566+ // The longest increasing subsequence within `new_index_to_old_index`. This
642− // appear before the first LIS-member.
567+ // is the longest sequence on DOM nodes in `old` that are relatively ordered
643− diff_and_move_or_create_segment(
568+ // correctly within `new`. We will leave these nodes in place in the DOM,
644− cached_set,
569+ // and only move nodes that are not part of the LIS. This results in the
645− change_list,
570+ // maximum number of DOM nodes left in place, AKA the minimum number of DOM
646− registry,
571+ // nodes moved.
647− old,
572+ let mut new_index_is_in_lis = FxHashSet::default();
648− new,
573+ new_index_is_in_lis.reserve(new_index_to_old_index.len());
649− cached_roots,
574+ let mut predecessors = vec![0; new_index_to_old_index.len()];
650− 0..segment_end,
575+ let mut starts = vec![0; new_index_to_old_index.len()];
576+ longest_increasing_subsequence::lis_with(
651 &new_index_to_old_index,
577 &new_index_to_old_index,
652− &old_index_to_temp,
578+ &mut new_index_is_in_lis,
653− &mut pushed,
579+ |a, b| a < b,
656− if pushed {
584+ // Now we will iterate from the end of the new children back to the
657− change_list.pop();
585+ // beginning, diffing old children we are reusing and if they aren't in the
658− }
586+ // LIS moving them to their new destination, or creating new children. Note
659−}
587+ // that iterating in reverse order lets us use `Node.prototype.insertBefore`
660−
588+ // to move/insert children.
661−// Diff-and-then-move or create each new node in the given segment. The
589+ //
662−// segment's nodes must _not_ be members of the LIS (those nodes we will diff in
590+ // But first, we ensure that we have a child on the change list stack that
663−// place elsewhere, and we should not move them nor create them afresh).
591+ // we can `insertBefore`. We handle this once before looping over `new`
664−//
592+ // children, so that we don't have to keep checking on every loop iteration.
665−// On entering this function, the change list stack has the parent and
593+ if shared_suffix_count > 0 {
666−// optionally the next sibling after this segment of children:
594+ // There is a shared suffix after these middle children. We will be
667−//
595+ // inserting before that shared suffix, so add the first child of that
668−// [... parent next_sibling] if *pushed == true
596+ // shared suffix to the change list stack.
669−// [... parent] otherwise
597+ //
670−//
598+ // [... parent]
671−// After exiting, if any children were moved or created, then `pushed` is set to
599+ change_list.push_child(old_shared_suffix_start - removed_count);
672−// true and the change list stack has the first child in this segment on
600+ // [... parent first_child_of_shared_suffix]
673−// top. Otherwise the stack is left as it was on entering.
601+ } else {
674−//
602+ // There is no shared suffix coming after these middle children.
675−// [... parent child] if *pushed == true
603+ // Therefore we have to process the last child in `new` and move it to
676−// [... parent] otherwise
604+ // the end of the parent's children if it isn't already there.
677−fn diff_and_move_or_create_segment(
605+ let last_index = new.len() - 1;
678− cached_set: &CachedSet,
606+ let last = new.last().unwrap_throw();
679− change_list: &mut ChangeListBuilder,
607+ new = &new[..new.len() - 1];
680− registry: &mut EventsRegistry,
608+ if shared_keys.contains(&last.key()) {
681− old: &[Node],
609+ let old_index = new_index_to_old_index[last_index];
683− cached_roots: &mut FxHashSet<CacheId>,
684− segment: Range<usize>,
685− new_index_to_old_index: &[usize],
686− old_index_to_temp: &[u32],
689− for new_index in segment.rev() {
690− let new_child = &new[new_index];
691− let old_index = new_index_to_old_index[new_index];
692− if old_index == u32::MAX as usize {
693− // The key is not reused. Create this new child afresh.
694− create(cached_set, change_list, registry, new_child, cached_roots);
696− // The key is reused. Diff it with the old node and then
697− // move the old node into its final destination.
698 let temp = old_index_to_temp[old_index];
610 let temp = old_index_to_temp[old_index];
699− debug_assert_ne!(temp, u32::MAX);
611+ // [... parent]
700 change_list.push_temporary(temp);
612 change_list.push_temporary(temp);
613+ // [... parent last]
702 cached_set,
615 cached_set,
703 change_list,
616 change_list,
704 registry,
617 registry,
706 &old[old_index],
618 &old[old_index],
707 cached_roots,
620 cached_roots,
622+ if new_index_is_in_lis.contains(&last_index) {
623+ // Don't move it, since it is already where it needs to be.
625+ change_list.append_child();
627+ change_list.push_temporary(temp);
628+ // [... parent last]
632+ create(cached_set, change_list, registry, last, cached_roots);
633+ // [... parent last]
634+ change_list.append_child();
636+ change_list.push_last_child();
637+ // [... parent last]
711− // At this point the change list stack can have one of two shapes.
641+ for (new_index, new_child) in new.iter().enumerate().rev() {
712− if *pushed {
642+ let old_index = new_index_to_old_index[new_index];
643+ if old_index == u32::MAX as usize {
644+ debug_assert!(!shared_keys.contains(&new_child.key()));
645+ // [... parent successor]
646+ create(cached_set, change_list, registry, new_child, cached_roots);
713− // [... parent next_child new_child]
647+ // [... parent successor new_child]
714 change_list.insert_before();
648 change_list.insert_before();
715 // [... parent new_child]
649 // [... parent new_child]
651+ debug_assert!(shared_keys.contains(&new_child.key()));
652+ let temp = old_index_to_temp[old_index];
653+ debug_assert_ne!(temp, u32::MAX);
655+ if new_index_is_in_lis.contains(&new_index) {
656+ // [... parent successor]
659+ change_list.push_temporary(temp);
717 // [... parent new_child]
660 // [... parent new_child]
662+ // [... parent successor]
718− change_list.append_child();
663+ change_list.push_temporary(temp);
719− // [... parent]
664+ // [... parent successor new_child]
720− change_list.push_last_child();
665+ change_list.insert_before();
721− // [... parent new_child]
666+ // [... parent new_child]
725− // At the end of each loop iteration, the change list stack looks like:
669+ diff(
727− // [... parent new_child]
674+ new_child,
728− debug_assert!(*pushed);
675+ cached_roots,
680+ // [... parent child]
732// Diff the suffix of keyed children that share the same keys in the same order.
685// Diff the suffix of keyed children that share the same keys in the same order.