static void calculateShadowViewMutationsFlattener()

in ReactCommon/react/renderer/mounting/Differentiator.cpp [619:1078]


static void calculateShadowViewMutationsFlattener(
    BREADCRUMB_TYPE breadcrumb,
    ViewNodePairScope &scope,
    ReparentMode reparentMode,
    OrderedMutationInstructionContainer &mutationContainer,
    ShadowView const &parentShadowView,
    TinyMap<Tag, ShadowViewNodePair *> &unvisitedOtherNodes,
    ShadowViewNodePair const &node,
    TinyMap<Tag, ShadowViewNodePair *> *parentSubVisitedOtherNewNodes,
    TinyMap<Tag, ShadowViewNodePair *> *parentSubVisitedOtherOldNodes) {
  DEBUG_LOGS({
    LOG(ERROR) << "Differ Flattener 1: "
               << (reparentMode == ReparentMode::Unflatten ? "Unflattening"
                                                           : "Flattening")
               << " [" << node.shadowView.tag << "]";
  });

  // Step 1: iterate through entire tree
  ShadowViewNodePair::NonOwningList treeChildren =
      sliceChildShadowNodeViewPairsFromViewNodePair(node, scope);

  DEBUG_LOGS({
    LOG(ERROR) << "Differ Flattener 1.4: "
               << (reparentMode == ReparentMode::Unflatten ? "Unflattening"
                                                           : "Flattening")
               << " [" << node.shadowView.tag << "]";
    LOG(ERROR) << "Differ Flattener Entry: Child Pairs: ";
    std::string strTreeChildPairs;
    for (size_t k = 0; k < treeChildren.size(); k++) {
      strTreeChildPairs.append(std::to_string(treeChildren[k]->shadowView.tag));
      strTreeChildPairs.append(treeChildren[k]->isConcreteView ? "" : "'");
      strTreeChildPairs.append(treeChildren[k]->flattened ? "*" : "");
      strTreeChildPairs.append(", ");
    }
    std::string strListChildPairs;
    for (auto &unvisitedNode : unvisitedOtherNodes) {
      strListChildPairs.append(
          std::to_string(unvisitedNode.second->shadowView.tag));
      strListChildPairs.append(unvisitedNode.second->isConcreteView ? "" : "'");
      strListChildPairs.append(unvisitedNode.second->flattened ? "*" : "");
      strListChildPairs.append(", ");
    }
    LOG(ERROR) << "Differ Flattener Entry: Tree Child Pairs: "
               << strTreeChildPairs;
    LOG(ERROR) << "Differ Flattener Entry: List Child Pairs: "
               << strListChildPairs;
  });

  // Views in other tree that are visited by sub-flattening or
  // sub-unflattening
  TinyMap<Tag, ShadowViewNodePair *> subVisitedOtherNewNodes{};
  TinyMap<Tag, ShadowViewNodePair *> subVisitedOtherOldNodes{};
  auto subVisitedNewMap =
      (parentSubVisitedOtherNewNodes != nullptr ? parentSubVisitedOtherNewNodes
                                                : &subVisitedOtherNewNodes);
  auto subVisitedOldMap =
      (parentSubVisitedOtherOldNodes != nullptr ? parentSubVisitedOtherOldNodes
                                                : &subVisitedOtherOldNodes);

  // Candidates for full tree creation or deletion at the end of this function
  auto deletionCreationCandidatePairs =
      TinyMap<Tag, ShadowViewNodePair const *>{};

  for (size_t index = 0;
       index < treeChildren.size() && index < treeChildren.size();
       index++) {
    auto &treeChildPair = *treeChildren[index];

    // Try to find node in other tree
    auto unvisitedIt = unvisitedOtherNodes.find(treeChildPair.shadowView.tag);
    auto subVisitedOtherNewIt =
        (unvisitedIt == unvisitedOtherNodes.end()
             ? subVisitedNewMap->find(treeChildPair.shadowView.tag)
             : subVisitedNewMap->end());
    auto subVisitedOtherOldIt =
        (unvisitedIt == unvisitedOtherNodes.end() && subVisitedNewMap->end()
             ? subVisitedOldMap->find(treeChildPair.shadowView.tag)
             : subVisitedOldMap->end());

    bool existsInOtherTree = unvisitedIt != unvisitedOtherNodes.end() ||
        subVisitedOtherNewIt != subVisitedNewMap->end() ||
        subVisitedOtherOldIt != subVisitedOldMap->end();

    auto otherTreeNodePairPtr =
        (existsInOtherTree
             ? (unvisitedIt != unvisitedOtherNodes.end()
                    ? unvisitedIt->second
                    : (subVisitedOtherNewIt != subVisitedNewMap->end()
                           ? subVisitedOtherNewIt->second
                           : subVisitedOtherOldIt->second))
             : nullptr);

    react_native_assert(
        !existsInOtherTree ||
        (unvisitedIt != unvisitedOtherNodes.end() ||
         subVisitedOtherNewIt != subVisitedNewMap->end() ||
         subVisitedOtherOldIt != subVisitedOldMap->end()));
    react_native_assert(
        unvisitedIt == unvisitedOtherNodes.end() ||
        unvisitedIt->second->shadowView.tag == treeChildPair.shadowView.tag);
    react_native_assert(
        subVisitedOtherNewIt == subVisitedNewMap->end() ||
        subVisitedOtherNewIt->second->shadowView.tag ==
            treeChildPair.shadowView.tag);
    react_native_assert(
        subVisitedOtherOldIt == subVisitedOldMap->end() ||
        subVisitedOtherOldIt->second->shadowView.tag ==
            treeChildPair.shadowView.tag);

    bool alreadyUpdated = false;

    // Find in other tree and updated `otherTreePair` pointers
    if (existsInOtherTree) {
      react_native_assert(otherTreeNodePairPtr != nullptr);
      auto newTreeNodePair =
          (reparentMode == ReparentMode::Flatten ? otherTreeNodePairPtr
                                                 : &treeChildPair);
      auto oldTreeNodePair =
          (reparentMode == ReparentMode::Flatten ? &treeChildPair
                                                 : otherTreeNodePairPtr);

      react_native_assert(newTreeNodePair->shadowView.tag != 0);
      react_native_assert(oldTreeNodePair->shadowView.tag != 0);
      react_native_assert(
          oldTreeNodePair->shadowView.tag == newTreeNodePair->shadowView.tag);

      alreadyUpdated =
          newTreeNodePair->inOtherTree() || oldTreeNodePair->inOtherTree();

      // We want to update these values unconditionally. Always do this
      // before hitting any "continue" statements.
      newTreeNodePair->otherTreePair = oldTreeNodePair;
      oldTreeNodePair->otherTreePair = newTreeNodePair;
      react_native_assert(treeChildPair.otherTreePair != nullptr);
    }

    // Remove all children (non-recursively) of tree being flattened, or
    // insert children into parent tree if they're being unflattened.
    //  Caller will take care of the corresponding action in the other tree
    //  (caller will handle DELETE case if we REMOVE here; caller will handle
    //  CREATE case if we INSERT here).
    if (treeChildPair.isConcreteView) {
      if (reparentMode == ReparentMode::Flatten) {
        // treeChildPair.shadowView represents the "old" view in this case.
        // If there's a "new" view, an UPDATE new -> old will be generated
        // and will be executed before the REMOVE. Thus, we must actually
        // perform a REMOVE (new view) FROM (old index) in this case so that
        // we don't hit asserts in StubViewTree's REMOVE path.
        // We also only do this if the "other" (newer) view is concrete. If
        // it's not concrete, there will be no UPDATE mutation.
        react_native_assert(existsInOtherTree == treeChildPair.inOtherTree());
        if (treeChildPair.inOtherTree() &&
            treeChildPair.otherTreePair->isConcreteView) {
          mutationContainer.removeMutations.push_back(
              ShadowViewMutation::RemoveMutation(
                  node.shadowView,
                  treeChildPair.otherTreePair->shadowView,
                  static_cast<int>(treeChildPair.mountIndex)));
        } else {
          mutationContainer.removeMutations.push_back(
              ShadowViewMutation::RemoveMutation(
                  node.shadowView,
                  treeChildPair.shadowView,
                  static_cast<int>(treeChildPair.mountIndex)));
        }
      } else {
        // treeChildParent represents the "new" version of the node, so
        // we can safely insert it without checking in the other tree
        mutationContainer.insertMutations.push_back(
            ShadowViewMutation::InsertMutation(
                node.shadowView,
                treeChildPair.shadowView,
                static_cast<int>(treeChildPair.mountIndex)));
      }
    }

    // Find in other tree
    if (existsInOtherTree) {
      react_native_assert(otherTreeNodePairPtr != nullptr);
      auto &otherTreeNodePair = *otherTreeNodePairPtr;

      auto &newTreeNodePair =
          (reparentMode == ReparentMode::Flatten ? otherTreeNodePair
                                                 : treeChildPair);
      auto &oldTreeNodePair =
          (reparentMode == ReparentMode::Flatten ? treeChildPair
                                                 : otherTreeNodePair);

      react_native_assert(newTreeNodePair.shadowView.tag != 0);
      react_native_assert(oldTreeNodePair.shadowView.tag != 0);
      react_native_assert(
          oldTreeNodePair.shadowView.tag == newTreeNodePair.shadowView.tag);

      // If we've already done updates, don't repeat it.
      if (alreadyUpdated) {
        continue;
      }

      // If we've already done updates on this node, don't repeat.
      if (reparentMode == ReparentMode::Flatten &&
          unvisitedIt == unvisitedOtherNodes.end() &&
          subVisitedOtherOldIt != subVisitedOldMap->end()) {
        continue;
      } else if (
          reparentMode == ReparentMode::Unflatten &&
          unvisitedIt == unvisitedOtherNodes.end() &&
          subVisitedOtherNewIt != subVisitedNewMap->end()) {
        continue;
      }

      // TODO: compare ShadowNode pointer instead of ShadowView here?
      // Or ShadowNode ptr comparison before comparing ShadowView, to allow for
      // short-circuiting? ShadowView comparison is relatively expensive vs
      // ShadowNode.
      if (newTreeNodePair.shadowView != oldTreeNodePair.shadowView &&
          newTreeNodePair.isConcreteView && oldTreeNodePair.isConcreteView) {
        mutationContainer.updateMutations.push_back(
            ShadowViewMutation::UpdateMutation(
                oldTreeNodePair.shadowView, newTreeNodePair.shadowView));
      }

      // Update children if appropriate.
      if (!oldTreeNodePair.flattened && !newTreeNodePair.flattened) {
        if (oldTreeNodePair.shadowNode != newTreeNodePair.shadowNode) {
          ViewNodePairScope innerScope{};
          calculateShadowViewMutationsV2(
              DIFF_BREADCRUMB(
                  "(Un)Flattener trivial update of " +
                  std::to_string(newTreeNodePair.shadowView.tag)),
              innerScope,
              mutationContainer.downwardMutations,
              newTreeNodePair.shadowView,
              sliceChildShadowNodeViewPairsFromViewNodePair(
                  oldTreeNodePair, innerScope),
              sliceChildShadowNodeViewPairsFromViewNodePair(
                  newTreeNodePair, innerScope));
        }
      } else if (oldTreeNodePair.flattened != newTreeNodePair.flattened) {
        // We need to handle one of the children being flattened or
        // unflattened, in the context of a parent flattening or unflattening.
        ReparentMode childReparentMode =
            (oldTreeNodePair.flattened ? ReparentMode::Unflatten
                                       : ReparentMode::Flatten);

        // Case 1: child mode is the same as parent.
        // This is a flatten-flatten, or unflatten-unflatten.
        if (childReparentMode == reparentMode) {
          calculateShadowViewMutationsFlattener(
              DIFF_BREADCRUMB(
                  std::string(
                      reparentMode == ReparentMode::Flatten
                          ? "Flatten-Flatten"
                          : "Unflatten-Unflatten") +
                  " new:" +
                  std::to_string(
                      reparentMode == ReparentMode::Flatten
                          ? parentShadowView.tag
                          : newTreeNodePair.shadowView.tag) +
                  " old:" + std::to_string(treeChildPair.shadowView.tag)),
              scope,
              childReparentMode,
              mutationContainer,
              (reparentMode == ReparentMode::Flatten
                   ? parentShadowView
                   : newTreeNodePair.shadowView),
              unvisitedOtherNodes,
              treeChildPair,
              subVisitedNewMap,
              subVisitedOldMap);
        } else {
          // Get flattened nodes from either new or old tree
          auto flattenedNodes = sliceChildShadowNodeViewPairsFromViewNodePair(
              (childReparentMode == ReparentMode::Flatten ? newTreeNodePair
                                                          : oldTreeNodePair),
              scope,
              true);
          // Construct unvisited nodes map
          auto unvisitedRecursiveChildPairs =
              TinyMap<Tag, ShadowViewNodePair *>{};
          for (auto &flattenedNode : flattenedNodes) {
            auto &newChild = *flattenedNode;

            auto unvisitedOtherNodesIt =
                unvisitedOtherNodes.find(newChild.shadowView.tag);
            if (unvisitedOtherNodesIt != unvisitedOtherNodes.end()) {
              auto unvisitedItPair = *unvisitedOtherNodesIt->second;
              unvisitedRecursiveChildPairs.insert(
                  {unvisitedItPair.shadowView.tag, &unvisitedItPair});
            } else {
              unvisitedRecursiveChildPairs.insert(
                  {newChild.shadowView.tag, &newChild});
            }
          }

          // Unflatten parent, flatten child
          if (childReparentMode == ReparentMode::Flatten) {
            // Flatten old tree into new list
            // At the end of this loop we still want to know which of these
            // children are visited, so we reuse the `newRemainingPairs` map.
            calculateShadowViewMutationsFlattener(
                DIFF_BREADCRUMB(
                    std::string("Flatten old tree into new list; new:") +
                    std::to_string(
                        reparentMode == ReparentMode::Flatten
                            ? parentShadowView.tag
                            : newTreeNodePair.shadowView.tag) +
                    " old:" + std::to_string(oldTreeNodePair.shadowView.tag)),
                scope,
                ReparentMode::Flatten,
                mutationContainer,
                (reparentMode == ReparentMode::Flatten
                     ? parentShadowView
                     : newTreeNodePair.shadowView),
                unvisitedRecursiveChildPairs,
                oldTreeNodePair,
                subVisitedNewMap,
                subVisitedOldMap);
          }
          // Flatten parent, unflatten child
          else {
            // Unflatten old list into new tree
            calculateShadowViewMutationsFlattener(
                DIFF_BREADCRUMB(
                    "Unflatten old list into new tree; old:" +
                    std::to_string(
                        reparentMode == ReparentMode::Flatten
                            ? parentShadowView.tag
                            : newTreeNodePair.shadowView.tag) +
                    " new:" + std::to_string(newTreeNodePair.shadowView.tag)),
                scope,
                ReparentMode::Unflatten,
                mutationContainer,
                (reparentMode == ReparentMode::Flatten
                     ? parentShadowView
                     : newTreeNodePair.shadowView),
                unvisitedRecursiveChildPairs,
                newTreeNodePair,
                subVisitedNewMap,
                subVisitedOldMap);

            // If old nodes were not visited, we know that we can delete them
            // now. They will be removed from the hierarchy by the outermost
            // loop of this function.
            for (auto &unvisitedRecursiveChildPair :
                 unvisitedRecursiveChildPairs) {
              if (unvisitedRecursiveChildPair.first == 0) {
                continue;
              }
              auto &oldFlattenedNode = *unvisitedRecursiveChildPair.second;

              // Node unvisited - mark the entire subtree for deletion
              if (oldFlattenedNode.isConcreteView &&
                  !oldFlattenedNode.inOtherTree()) {
                Tag tag = oldFlattenedNode.shadowView.tag;
                auto deleteCreateIt = deletionCreationCandidatePairs.find(
                    oldFlattenedNode.shadowView.tag);
                if (deleteCreateIt == deletionCreationCandidatePairs.end()) {
                  deletionCreationCandidatePairs.insert(
                      {tag, &oldFlattenedNode});
                }
              } else {
                // Node was visited - make sure to remove it from
                // "newRemainingPairs" map
                auto newRemainingIt =
                    unvisitedOtherNodes.find(oldFlattenedNode.shadowView.tag);
                if (newRemainingIt != unvisitedOtherNodes.end()) {
                  unvisitedOtherNodes.erase(newRemainingIt);
                }
              }
            }
          }
        }
      }

      // Mark that node exists in another tree, but only if the tree node is a
      // concrete view. Removing the node from the unvisited list prevents the
      // caller from taking further action on this node, so make sure to
      // delete/create if the Concreteness of the node has changed.
      if (newTreeNodePair.isConcreteView != oldTreeNodePair.isConcreteView) {
        if (newTreeNodePair.isConcreteView) {
          mutationContainer.createMutations.push_back(
              ShadowViewMutation::CreateMutation(newTreeNodePair.shadowView));
        } else {
          mutationContainer.deleteMutations.push_back(
              ShadowViewMutation::DeleteMutation(oldTreeNodePair.shadowView));
        }
      }

      subVisitedNewMap->insert(
          {newTreeNodePair.shadowView.tag, &newTreeNodePair});
      subVisitedOldMap->insert(
          {oldTreeNodePair.shadowView.tag, &oldTreeNodePair});
    } else {
      // Node does not in exist in other tree.
      if (treeChildPair.isConcreteView && !treeChildPair.inOtherTree()) {
        auto deletionCreationIt =
            deletionCreationCandidatePairs.find(treeChildPair.shadowView.tag);
        if (deletionCreationIt == deletionCreationCandidatePairs.end()) {
          deletionCreationCandidatePairs.insert(
              {treeChildPair.shadowView.tag, &treeChildPair});
        }
      }
    }
  }

  // Final step: go through creation/deletion candidates and delete/create
  // subtrees if they were never visited during the execution of the above
  // loop and recursions.
  for (auto &deletionCreationCandidatePair : deletionCreationCandidatePairs) {
    if (deletionCreationCandidatePair.first == 0) {
      continue;
    }
    auto &treeChildPair = *deletionCreationCandidatePair.second;

    // If node was visited during a flattening/unflattening recursion,
    // and the node in the other tree is concrete, that means it was
    // already created/deleted and we don't need to do that here.
    // It is always the responsibility of the matcher to update subtrees when
    // nodes are matched.
    if (treeChildPair.inOtherTree()) {
      continue;
    }

    if (reparentMode == ReparentMode::Flatten) {
      mutationContainer.deleteMutations.push_back(
          ShadowViewMutation::DeleteMutation(treeChildPair.shadowView));

      if (!treeChildPair.flattened) {
        ViewNodePairScope innerScope{};
        calculateShadowViewMutationsV2(
            DIFF_BREADCRUMB(
                "Recursively delete tree child pair (flatten case): " +
                std::to_string(treeChildPair.shadowView.tag)),
            innerScope,
            mutationContainer.destructiveDownwardMutations,
            treeChildPair.shadowView,
            sliceChildShadowNodeViewPairsFromViewNodePair(
                treeChildPair, innerScope),
            {});
      }
    } else {
      mutationContainer.createMutations.push_back(
          ShadowViewMutation::CreateMutation(treeChildPair.shadowView));

      if (!treeChildPair.flattened) {
        ViewNodePairScope innerScope{};
        calculateShadowViewMutationsV2(
            DIFF_BREADCRUMB(
                "Recursively delete tree child pair (unflatten case): " +
                std::to_string(treeChildPair.shadowView.tag)),
            innerScope,
            mutationContainer.downwardMutations,
            treeChildPair.shadowView,
            {},
            sliceChildShadowNodeViewPairsFromViewNodePair(
                treeChildPair, innerScope));
      }
    }
  }
}