on master help sign in
Simplify `diff_keyed_middle`

Remove the whole "segments" thing and just walk backwards through the `new` children. This also lets us remove the did-we-push-a-child-or-not-yet boolean and simplifies a bunch of stuff.

commit2c647bf82038a0c44d7a8e958acdf74dd164693a authorNick Fitzgerald <fitzgen@gmail.com> dateFri 19 Apr 2019, 12:46 (8 years ago) parente461b17
1 file changed+103−150
src/diff.rs+103−150
src/diff.rs+103−150
@@ -6,8 +6,8 @@ use crate::{
66};
77use fxhash::{FxHashMap, FxHashSet};
88use std::cmp::Ordering;
9−use std::ops::Range;
109use std::u32;
10+use wasm_bindgen::UnwrapThrowExt;
1111
1212// Diff the `old` node with the `new` node. Emits instructions to modify a
1313// physical DOM node that reflects `old` into something that reflects `new`.
@@ -465,7 +465,7 @@ fn diff_keyed_middle(
465465 change_list: &mut ChangeListBuilder,
466466 registry: &mut EventsRegistry,
467467 old: &[Node],
468− new: &[Node],
468+ mut new: &[Node],
469469 cached_roots: &mut FxHashSet<CacheId>,
470470 shared_prefix_count: usize,
471471 shared_suffix_count: usize,
@@ -516,22 +516,6 @@ fn diff_keyed_middle(
516516 return;
517517 }
518518
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,
529− &mut reverse_lis,
530− |a, b| a < b,
531− &mut predecessors,
532− &mut starts,
533− );
534−
535519 // Save each of the old children whose keys are reused in the new
536520 // children.
537521 let mut old_index_to_temp = vec![u32::MAX; old.len()];
@@ -568,165 +552,134 @@ fn diff_keyed_middle(
568552 let mut removed_count = 0;
569553 for (i, old_child) in old.iter().enumerate().rev() {
570554 if !shared_keys.contains(&old_child.key()) {
555+ registry.remove_subtree(old_child);
571556 change_list.remove_child(i + shared_prefix_count);
572557 removed_count += 1;
573558 }
574559 }
575560
576− // Whether we have pushed a child onto the change list stack or not.
577− let mut pushed = false;
578−
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
583− // children.
584− if shared_suffix_count > 0 {
585− change_list.push_child(old_shared_suffix_start - removed_count);
586− pushed = true;
587− }
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 {
594− continue;
595− }
596−
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);
603−
604− // Go through each of the non-LIS child and move-and-diff or create
605− // it.
606− diff_and_move_or_create_segment(
607− cached_set,
608− change_list,
609− registry,
610− old,
611− new,
612− cached_roots,
613− lis_index + 1..segment_end,
614− &new_index_to_old_index,
615− &old_index_to_temp,
616− &mut pushed,
617− );
618−
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);
624− if pushed {
625− change_list.pop();
626− }
627− change_list.push_temporary(temp);
628− pushed = true;
629− diff(
630− cached_set,
631− change_list,
632− registry,
633− &old[old_index],
634− new_child,
635− cached_roots,
636− );
637−
638− segment_end = lis_index;
561+ // If there aren't any more new children, then we are done!
562+ if new.is_empty() {
563+ return;
639564 }
640565
641− // And now diff-and-move or create each of the non-LIS children that
642− // appear before the first LIS-member.
643− diff_and_move_or_create_segment(
644− cached_set,
645− change_list,
646− registry,
647− old,
648− new,
649− cached_roots,
650− 0..segment_end,
566+ // The longest increasing subsequence within `new_index_to_old_index`. This
567+ // is the longest sequence on DOM nodes in `old` that are relatively ordered
568+ // correctly within `new`. We will leave these nodes in place in the DOM,
569+ // and only move nodes that are not part of the LIS. This results in the
570+ // maximum number of DOM nodes left in place, AKA the minimum number of DOM
571+ // nodes moved.
572+ let mut new_index_is_in_lis = FxHashSet::default();
573+ new_index_is_in_lis.reserve(new_index_to_old_index.len());
574+ let mut predecessors = vec![0; new_index_to_old_index.len()];
575+ let mut starts = vec![0; new_index_to_old_index.len()];
576+ longest_increasing_subsequence::lis_with(
651577 &new_index_to_old_index,
652− &old_index_to_temp,
653− &mut pushed,
578+ &mut new_index_is_in_lis,
579+ |a, b| a < b,
580+ &mut predecessors,
581+ &mut starts,
654582 );
655583
656− if pushed {
657− change_list.pop();
658− }
659−}
660−
661−// Diff-and-then-move or create each new node in the given segment. The
662−// segment's nodes must _not_ be members of the LIS (those nodes we will diff in
663−// place elsewhere, and we should not move them nor create them afresh).
664−//
665−// On entering this function, the change list stack has the parent and
666−// optionally the next sibling after this segment of children:
667−//
668−// [... parent next_sibling] if *pushed == true
669−// [... parent] otherwise
670−//
671−// After exiting, if any children were moved or created, then `pushed` is set to
672−// true and the change list stack has the first child in this segment on
673−// top. Otherwise the stack is left as it was on entering.
674−//
675−// [... parent child] if *pushed == true
676−// [... parent] otherwise
677−fn diff_and_move_or_create_segment(
678− cached_set: &CachedSet,
679− change_list: &mut ChangeListBuilder,
680− registry: &mut EventsRegistry,
681− old: &[Node],
682− new: &[Node],
683− cached_roots: &mut FxHashSet<CacheId>,
684− segment: Range<usize>,
685− new_index_to_old_index: &[usize],
686− old_index_to_temp: &[u32],
687− pushed: &mut bool,
688−) {
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);
695− } else {
696− // The key is reused. Diff it with the old node and then
697− // move the old node into its final destination.
584+ // Now we will iterate from the end of the new children back to the
585+ // beginning, diffing old children we are reusing and if they aren't in the
586+ // LIS moving them to their new destination, or creating new children. Note
587+ // that iterating in reverse order lets us use `Node.prototype.insertBefore`
588+ // to move/insert children.
589+ //
590+ // But first, we ensure that we have a child on the change list stack that
591+ // we can `insertBefore`. We handle this once before looping over `new`
592+ // children, so that we don't have to keep checking on every loop iteration.
593+ if shared_suffix_count > 0 {
594+ // There is a shared suffix after these middle children. We will be
595+ // inserting before that shared suffix, so add the first child of that
596+ // shared suffix to the change list stack.
597+ //
598+ // [... parent]
599+ change_list.push_child(old_shared_suffix_start - removed_count);
600+ // [... parent first_child_of_shared_suffix]
601+ } else {
602+ // There is no shared suffix coming after these middle children.
603+ // Therefore we have to process the last child in `new` and move it to
604+ // the end of the parent's children if it isn't already there.
605+ let last_index = new.len() - 1;
606+ let last = new.last().unwrap_throw();
607+ new = &new[..new.len() - 1];
608+ if shared_keys.contains(&last.key()) {
609+ let old_index = new_index_to_old_index[last_index];
698610 let temp = old_index_to_temp[old_index];
699− debug_assert_ne!(temp, u32::MAX);
611+ // [... parent]
700612 change_list.push_temporary(temp);
613+ // [... parent last]
701614 diff(
702615 cached_set,
703616 change_list,
704617 registry,
705− new_child,
706618 &old[old_index],
619+ last,
707620 cached_roots,
708621 );
622+ if new_index_is_in_lis.contains(&last_index) {
623+ // Don't move it, since it is already where it needs to be.
624+ } else {
625+ change_list.append_child();
626+ // [... parent]
627+ change_list.push_temporary(temp);
628+ // [... parent last]
629+ }
630+ } else {
631+ // [... parent]
632+ create(cached_set, change_list, registry, last, cached_roots);
633+ // [... parent last]
634+ change_list.append_child();
635+ // [... parent]
636+ change_list.push_last_child();
637+ // [... parent last]
709638 }
639+ }
710640
711− // At this point the change list stack can have one of two shapes.
712− if *pushed {
713− // [... parent next_child new_child]
641+ for (new_index, new_child) in new.iter().enumerate().rev() {
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);
647+ // [... parent successor new_child]
714648 change_list.insert_before();
715649 // [... parent new_child]
716650 } else {
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);
654+
655+ if new_index_is_in_lis.contains(&new_index) {
656+ // [... parent successor]
657+ change_list.pop();
658+ // [... parent]
659+ change_list.push_temporary(temp);
717660 // [... parent new_child]
718− change_list.append_child();
719− // [... parent]
720− change_list.push_last_child();
721− // [... parent new_child]
722− *pushed = true;
723− }
661+ } else {
662+ // [... parent successor]
663+ change_list.push_temporary(temp);
664+ // [... parent successor new_child]
665+ change_list.insert_before();
666+ // [... parent new_child]
667+ }
724668
725− // At the end of each loop iteration, the change list stack looks like:
726− //
727− // [... parent new_child]
728− debug_assert!(*pushed);
669+ diff(
670+ cached_set,
671+ change_list,
672+ registry,
673+ &old[old_index],
674+ new_child,
675+ cached_roots,
676+ );
677+ }
729678 }
679+
680+ // [... parent child]
681+ change_list.pop();
682+ // [... parent]
730683}
731684
732685// Diff the suffix of keyed children that share the same keys in the same order.