diff --git a/ReactCommon/react/renderer/mounting/Differentiator.cpp b/ReactCommon/react/renderer/mounting/Differentiator.cpp index 329d8fb709..3d41fe2113 100644 --- a/ReactCommon/react/renderer/mounting/Differentiator.cpp +++ b/ReactCommon/react/renderer/mounting/Differentiator.cpp @@ -6,6 +6,7 @@ */ #include "Differentiator.h" +#include "DifferentiatorFlatteningClassic.h" #include #include @@ -1583,7 +1584,13 @@ ShadowViewNodePair::List sliceChildShadowNodeViewPairsLegacy( ShadowViewMutation::List calculateShadowViewMutations( ShadowNode const &oldRootShadowNode, - ShadowNode const &newRootShadowNode) { + ShadowNode const &newRootShadowNode, + bool useNewDiffer) { + if (!useNewDiffer) { + return DifferOld::calculateShadowViewMutations( + oldRootShadowNode, newRootShadowNode); + } + SystraceSection s("calculateShadowViewMutations"); // Root shadow nodes must be belong the same family. diff --git a/ReactCommon/react/renderer/mounting/Differentiator.h b/ReactCommon/react/renderer/mounting/Differentiator.h index 7601a13e24..120d719aa8 100644 --- a/ReactCommon/react/renderer/mounting/Differentiator.h +++ b/ReactCommon/react/renderer/mounting/Differentiator.h @@ -23,7 +23,8 @@ enum class ReparentMode { Flatten, Unflatten }; */ ShadowViewMutationList calculateShadowViewMutations( ShadowNode const &oldRootShadowNode, - ShadowNode const &newRootShadowNode); + ShadowNode const &newRootShadowNode, + bool useNewDiffer); /** * Generates a list of `ShadowViewNodePair`s that represents a layer of a diff --git a/ReactCommon/react/renderer/mounting/DifferentiatorFlatteningClassic.cpp b/ReactCommon/react/renderer/mounting/DifferentiatorFlatteningClassic.cpp new file mode 100644 index 0000000000..f3679faea8 --- /dev/null +++ b/ReactCommon/react/renderer/mounting/DifferentiatorFlatteningClassic.cpp @@ -0,0 +1,1617 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "DifferentiatorFlatteningClassic.h" + +#include +#include +#include +#include +#include +#include +#include "ShadowView.h" + +#ifdef DEBUG_LOGS_DIFFER +#include +#define DEBUG_LOGS_BREADCRUMBS 1 +#define DEBUG_LOGS(code) code +#else +#define DEBUG_LOGS(code) +#endif + +#ifdef DEBUG_LOGS_BREADCRUMBS +#define BREADCRUMB_TYPE std::string +#define DIFF_BREADCRUMB(X) (breadcrumb + " - " + std::string(X)) +#define CREATE_DIFF_BREADCRUMB(X) std::to_string(X) +#else + +enum class NoBreadcrumb {}; + +#define BREADCRUMB_TYPE NoBreadcrumb const & +#define DIFF_BREADCRUMB(X) \ + {} +#define CREATE_DIFF_BREADCRUMB(X) \ + {} +#endif + +namespace facebook { +namespace react { +namespace DifferOld { + +/* + * Extremely simple and naive implementation of a map. + * The map is simple but it's optimized for particular constraints that we have + * here. + * + * A regular map implementation (e.g. `std::unordered_map`) has some basic + * performance guarantees like constant average insertion and lookup complexity. + * This is nice, but it's *average* complexity measured on a non-trivial amount + * of data. The regular map is a very complex data structure that using hashing, + * buckets, multiple comprising operations, multiple allocations and so on. + * + * In our particular case, we need a map for `int` to `void *` with a dozen + * values. In these conditions, nothing can beat a naive implementation using a + * stack-allocated vector. And this implementation is exactly this: no + * allocation, no hashing, no complex branching, no buckets, no iterators, no + * rehashing, no other guarantees. It's crazy limited, unsafe, and performant on + * a trivial amount of data. + * + * Besides that, we also need to optimize for insertion performance (the case + * where a bunch of views appears on the screen first time); in this + * implementation, this is as performant as vector `push_back`. + */ +template +class TinyMap final { + public: + using Pair = std::pair; + using Iterator = Pair *; + + /** + * This must strictly only be called from outside of this class. + */ + inline Iterator begin() { + // Force a clean so that iterating over this TinyMap doesn't iterate over + // erased elements. If all elements erased are at the front of the vector, + // then we don't need to clean. + cleanVector(erasedAtFront_ != numErased_); + + Iterator it = begin_(); + + if (it != nullptr) { + return it + erasedAtFront_; + } + + return nullptr; + } + + inline Iterator end() { + // `back()` asserts on the vector being non-empty + if (vector_.empty() || numErased_ == vector_.size()) { + return nullptr; + } + + return &vector_.back() + 1; + } + + inline Iterator find(KeyT key) { + cleanVector(); + + react_native_assert(key != 0); + + if (begin_() == nullptr) { + return end(); + } + + for (auto it = begin_() + erasedAtFront_; it != end(); it++) { + if (it->first == key) { + return it; + } + } + + return end(); + } + + inline void insert(Pair pair) { + react_native_assert(pair.first != 0); + vector_.push_back(pair); + } + + inline void erase(Iterator iterator) { + // Invalidate tag. + iterator->first = 0; + + if (iterator == begin_() + erasedAtFront_) { + erasedAtFront_++; + } + + numErased_++; + } + + private: + /** + * Same as begin() but doesn't call cleanVector at the beginning. + */ + inline Iterator begin_() { + // `front()` asserts on the vector being non-empty + if (vector_.empty() || vector_.size() == numErased_) { + return nullptr; + } + + return &vector_.front(); + } + + /** + * Remove erased elements from internal vector. + * We only modify the vector if erased elements are at least half of the + * vector. + */ + inline void cleanVector(bool forceClean = false) { + if ((numErased_ < (vector_.size() / 2) && !forceClean) || vector_.empty() || + numErased_ == 0 || numErased_ == erasedAtFront_) { + return; + } + + if (numErased_ == vector_.size()) { + vector_.clear(); + } else { + vector_.erase( + std::remove_if( + vector_.begin(), + vector_.end(), + [](auto const &item) { return item.first == 0; }), + vector_.end()); + } + numErased_ = 0; + erasedAtFront_ = 0; + } + + better::small_vector vector_; + int numErased_{0}; + int erasedAtFront_{0}; +}; + +/* + * Sorting comparator for `reorderInPlaceIfNeeded`. + */ +static bool shouldFirstPairComesBeforeSecondOne( + ShadowViewNodePair const &lhs, + ShadowViewNodePair const &rhs) noexcept { + return lhs.shadowNode->getOrderIndex() < rhs.shadowNode->getOrderIndex(); +} + +/* + * Reorders pairs in-place based on `orderIndex` using a stable sort algorithm. + */ +static void reorderInPlaceIfNeeded(ShadowViewNodePair::List &pairs) noexcept { + if (pairs.size() < 2) { + return; + } + + auto isReorderNeeded = false; + for (auto const &pair : pairs) { + if (pair.shadowNode->getOrderIndex() != 0) { + isReorderNeeded = true; + break; + } + } + + if (!isReorderNeeded) { + return; + } + + std::stable_sort( + pairs.begin(), pairs.end(), &shouldFirstPairComesBeforeSecondOne); +} + +static void sliceChildShadowNodeViewPairsRecursivelyV2( + ShadowViewNodePair::List &pairList, + Point layoutOffset, + ShadowNode const &shadowNode) { + for (auto const &sharedChildShadowNode : shadowNode.getChildren()) { + auto &childShadowNode = *sharedChildShadowNode; + +#ifndef ANDROID + // Temporary disabled on Android because the mounting infrastructure + // is not fully ready yet. + if (childShadowNode.getTraits().check(ShadowNodeTraits::Trait::Hidden)) { + continue; + } +#endif + + auto shadowView = ShadowView(childShadowNode); + auto origin = layoutOffset; + if (shadowView.layoutMetrics != EmptyLayoutMetrics) { + origin += shadowView.layoutMetrics.frame.origin; + shadowView.layoutMetrics.frame.origin += layoutOffset; + } + + // This might not be a FormsView, or a FormsStackingContext. We let the + // differ handle removal of flattened views from the Mounting layer and + // shuffling their children around. + bool isConcreteView = + childShadowNode.getTraits().check(ShadowNodeTraits::Trait::FormsView); + bool areChildrenFlattened = !childShadowNode.getTraits().check( + ShadowNodeTraits::Trait::FormsStackingContext); + pairList.push_back( + {shadowView, &childShadowNode, areChildrenFlattened, isConcreteView}); + + if (!childShadowNode.getTraits().check( + ShadowNodeTraits::Trait::FormsStackingContext)) { + sliceChildShadowNodeViewPairsRecursivelyV2( + pairList, origin, childShadowNode); + } + } +} + +ShadowViewNodePair::List sliceChildShadowNodeViewPairsV2( + ShadowNode const &shadowNode, + bool allowFlattened) { + auto pairList = ShadowViewNodePair::List{}; + + if (!shadowNode.getTraits().check( + ShadowNodeTraits::Trait::FormsStackingContext) && + shadowNode.getTraits().check(ShadowNodeTraits::Trait::FormsView) && + !allowFlattened) { + return pairList; + } + + sliceChildShadowNodeViewPairsRecursivelyV2(pairList, {0, 0}, shadowNode); + + // Sorting pairs based on `orderIndex` if needed. + reorderInPlaceIfNeeded(pairList); + + // Set list and mountIndex for each after reordering + size_t mountIndex = 0; + for (auto &child : pairList) { + child.mountIndex = (child.isConcreteView ? mountIndex++ : -1); + } + + return pairList; +} + +/* + * Before we start to diff, let's make sure all our core data structures are in + * good shape to deliver the best performance. + */ +static_assert( + std::is_move_constructible::value, + "`ShadowViewMutation` must be `move constructible`."); +static_assert( + std::is_move_constructible::value, + "`ShadowView` must be `move constructible`."); +static_assert( + std::is_move_constructible::value, + "`ShadowViewNodePair` must be `move constructible`."); +static_assert( + std::is_move_constructible::value, + "`ShadowViewNodePair::List` must be `move constructible`."); + +static_assert( + std::is_move_assignable::value, + "`ShadowViewMutation` must be `move assignable`."); +static_assert( + std::is_move_assignable::value, + "`ShadowView` must be `move assignable`."); +static_assert( + std::is_move_assignable::value, + "`ShadowViewNodePair` must be `move assignable`."); +static_assert( + std::is_move_assignable::value, + "`ShadowViewNodePair::List` must be `move assignable`."); + +// Forward declaration +static void calculateShadowViewMutationsV2( + BREADCRUMB_TYPE breadcrumb, + ShadowViewMutation::List &mutations, + ShadowView const &parentShadowView, + ShadowViewNodePair::List &&oldChildPairs, + ShadowViewNodePair::List &&newChildPairs); + +struct OrderedMutationInstructionContainer { + ShadowViewMutation::List &createMutations; + ShadowViewMutation::List &deleteMutations; + ShadowViewMutation::List &insertMutations; + ShadowViewMutation::List &removeMutations; + ShadowViewMutation::List &updateMutations; + ShadowViewMutation::List &downwardMutations; + ShadowViewMutation::List &destructiveDownwardMutations; +}; + +static void calculateShadowViewMutationsFlattener( + BREADCRUMB_TYPE breadcrumb, + ReparentMode reparentMode, + OrderedMutationInstructionContainer &mutationInstructionContainer, + ShadowView const &parentShadowView, + TinyMap &unvisitedFlattenedNodes, + ShadowViewNodePair const &node, + TinyMap *parentSubVisitedOtherNewNodes = nullptr, + TinyMap *parentSubVisitedOtherOldNodes = + nullptr); + +/** + * Here we flatten or unflatten a subtree, given an unflattened node in either + * the old or new tree, and a list of flattened nodes in the other tree. + * + * For example: if you are Flattening, the node will be in the old tree and the + * list will be from the new tree. If you are Unflattening, the opposite is + true. + + * It is currently not possible for ReactJS, and therefore React Native, to move + * a node *from* one parent to another without an entirely new subtree being + * created. When we "reparent" in React Native here it is only because + intermediate + * ShadowNodes/ShadowViews, which *always* exist, are flattened or unflattened + away. + * Thus, this algorithm handles the very specialized cases of the tree + collapsing or + * expanding vertically in that way. + + * Sketch of algorithm: + * 0. Create a map of nodes in the flattened list. This should be done *before* + * calling this function. + * 1. Traverse the Node Subtree; remove elements from the map as they are + * visited in the tree. + * Perform a Remove/Insert depending on if we're flattening or unflattening + * If Tree node is not in Map/List, perform Delete/Create. + * 2. Traverse the list. + * Perform linear remove from the old View, or insert into the new parent + * View if we're flattening. + * If a node is in the list but not the map, it means it's been visited and + * Update has already been + * performed in the subtree. If it *is* in the map, it means the node is not + * * in the Tree, and should be Deleted/Created + * **after this function is called**, by the caller. + */ +static void calculateShadowViewMutationsFlattener( + BREADCRUMB_TYPE breadcrumb, + ReparentMode reparentMode, + OrderedMutationInstructionContainer &mutationInstructionContainer, + ShadowView const &parentShadowView, + TinyMap &unvisitedOtherNodes, + ShadowViewNodePair const &node, + TinyMap *parentSubVisitedOtherNewNodes, + TinyMap *parentSubVisitedOtherOldNodes) { + DEBUG_LOGS({ + LOG(ERROR) << "Differ Flattener 1: " + << (reparentMode == ReparentMode::Unflatten ? "Unflattening" + : "Flattening") + << " [" << node.shadowView.tag << "]"; + }); + + // Step 1: iterate through entire tree + ShadowViewNodePair::List treeChildren = + sliceChildShadowNodeViewPairsV2(*node.shadowNode); + + 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 subVisitedOtherNewNodes{}; + TinyMap 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{}; + + for (size_t index = 0; + index < treeChildren.size() && index < treeChildren.size(); + index++) { + // First, remove all children of the tree being flattened, or insert + // children into parent tree if they're being unflattened. Then, look up + // each node in the "unvisited" list and update the nodes and subtrees if + // appropriate. + auto &treeChildPair = treeChildren[index]; + + // Caller will take care of the corresponding action in the other tree. + if (treeChildPair.isConcreteView) { + if (reparentMode == ReparentMode::Flatten) { + mutationInstructionContainer.removeMutations.push_back( + ShadowViewMutation::RemoveMutation( + node.shadowView, + treeChildPair.shadowView, + static_cast(treeChildPair.mountIndex))); + } else { + mutationInstructionContainer.insertMutations.push_back( + ShadowViewMutation::InsertMutation( + node.shadowView, + treeChildPair.shadowView, + static_cast(treeChildPair.mountIndex))); + } + } + + // 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() + ? subVisitedOldMap->find(treeChildPair.shadowView.tag) + : subVisitedOldMap->end()); + + // Find in other tree + if (unvisitedIt != unvisitedOtherNodes.end() || + subVisitedOtherNewIt != subVisitedNewMap->end() || + subVisitedOtherOldIt != subVisitedOldMap->end()) { + // 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; + } + + auto &otherTreeNodePair = + *(unvisitedIt != unvisitedOtherNodes.end() + ? unvisitedIt->second + : (subVisitedOtherNewIt != subVisitedNewMap->end() + ? subVisitedOtherNewIt->second + : subVisitedOtherOldIt->second)); + + // If we've already done updates, don't repeat it. + if (treeChildPair.inOtherTree || otherTreeNodePair.inOtherTree) { + continue; + } + + auto &newTreeNodePair = + (reparentMode == ReparentMode::Flatten ? otherTreeNodePair + : treeChildPair); + auto &oldTreeNodePair = + (reparentMode == ReparentMode::Flatten ? treeChildPair + : otherTreeNodePair); + + if (newTreeNodePair.shadowView != oldTreeNodePair.shadowView && + newTreeNodePair.isConcreteView && oldTreeNodePair.isConcreteView) { + mutationInstructionContainer.updateMutations.push_back( + ShadowViewMutation::UpdateMutation( + oldTreeNodePair.shadowView, newTreeNodePair.shadowView)); + } + + // Update children if appropriate. + if (!oldTreeNodePair.flattened && !newTreeNodePair.flattened) { + if (oldTreeNodePair.shadowNode != newTreeNodePair.shadowNode) { + calculateShadowViewMutationsV2( + DIFF_BREADCRUMB( + "(Un)Flattener trivial update of " + + std::to_string(newTreeNodePair.shadowView.tag)), + mutationInstructionContainer.downwardMutations, + newTreeNodePair.shadowView, + sliceChildShadowNodeViewPairsV2(*oldTreeNodePair.shadowNode), + sliceChildShadowNodeViewPairsV2(*newTreeNodePair.shadowNode)); + } + } 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)), + childReparentMode, + mutationInstructionContainer, + (reparentMode == ReparentMode::Flatten + ? parentShadowView + : newTreeNodePair.shadowView), + unvisitedOtherNodes, + treeChildPair, + subVisitedNewMap, + subVisitedOldMap); + } else { + // Unflatten parent, flatten child + if (childReparentMode == ReparentMode::Flatten) { + // Construct unvisited nodes map + auto unvisitedNewChildPairs = TinyMap{}; + // Memory note: these oldFlattenedNodes all disappear at the end of + // this "else" block, including any annotations we put on them. + auto newFlattenedNodes = sliceChildShadowNodeViewPairsV2( + *newTreeNodePair.shadowNode, true); + for (size_t i = 0; i < newFlattenedNodes.size(); i++) { + auto &newChild = newFlattenedNodes[i]; + + auto unvisitedOtherNodesIt = + unvisitedOtherNodes.find(newChild.shadowView.tag); + if (unvisitedOtherNodesIt != unvisitedOtherNodes.end()) { + auto &unvisitedItPair = *unvisitedOtherNodesIt->second; + unvisitedNewChildPairs.insert( + {unvisitedItPair.shadowView.tag, &unvisitedItPair}); + } else { + unvisitedNewChildPairs.insert( + {newChild.shadowView.tag, &newChild}); + } + } + + // 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)), + ReparentMode::Flatten, + mutationInstructionContainer, + (reparentMode == ReparentMode::Flatten + ? parentShadowView + : newTreeNodePair.shadowView), + unvisitedNewChildPairs, + oldTreeNodePair, + subVisitedNewMap, + subVisitedOldMap); + + for (auto &newFlattenedNode : newFlattenedNodes) { + auto unvisitedOldChildPairIt = + unvisitedNewChildPairs.find(newFlattenedNode.shadowView.tag); + + if (unvisitedOldChildPairIt == unvisitedNewChildPairs.end()) { + // Node was visited. + + auto deleteCreateIt = deletionCreationCandidatePairs.find( + newFlattenedNode.shadowView.tag); + if (deleteCreateIt != deletionCreationCandidatePairs.end()) { + deletionCreationCandidatePairs.erase(deleteCreateIt); + } + } + } + } + // Flatten parent, unflatten child + else { + // Construct unvisited nodes map + auto unvisitedOldChildPairs = TinyMap{}; + // Memory note: these oldFlattenedNodes all disappear at the end of + // this "else" block, including any annotations we put on them. + auto oldFlattenedNodes = sliceChildShadowNodeViewPairsV2( + *oldTreeNodePair.shadowNode, true); + for (size_t i = 0; i < oldFlattenedNodes.size(); i++) { + auto &oldChild = oldFlattenedNodes[i]; + + auto unvisitedOtherNodesIt = + unvisitedOtherNodes.find(oldChild.shadowView.tag); + if (unvisitedOtherNodesIt != unvisitedOtherNodes.end()) { + auto &unvisitedItPair = *unvisitedOtherNodesIt->second; + unvisitedOldChildPairs.insert( + {unvisitedItPair.shadowView.tag, &unvisitedItPair}); + } else { + unvisitedOldChildPairs.insert( + {oldChild.shadowView.tag, &oldChild}); + } + } + + // 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)), + ReparentMode::Unflatten, + mutationInstructionContainer, + (reparentMode == ReparentMode::Flatten + ? parentShadowView + : newTreeNodePair.shadowView), + unvisitedOldChildPairs, + 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 &oldFlattenedNode : oldFlattenedNodes) { + auto unvisitedOldChildPairIt = + unvisitedOldChildPairs.find(oldFlattenedNode.shadowView.tag); + if (unvisitedOldChildPairIt != unvisitedOldChildPairs.end()) { + // Node unvisited - mark the entire subtree for deletion + if (oldFlattenedNode.isConcreteView) { + auto tag = oldFlattenedNode.shadowView.tag; + auto oldRemainingChildInListIt = std::find_if( + treeChildren.begin(), + treeChildren.end(), + [&tag](ShadowViewNodePair &nodePair) { + return nodePair.shadowView.tag == tag; + }); + if (oldRemainingChildInListIt != treeChildren.end()) { + auto deleteCreateIt = deletionCreationCandidatePairs.find( + oldFlattenedNode.shadowView.tag); + if (deleteCreateIt == + deletionCreationCandidatePairs.end()) { + deletionCreationCandidatePairs.insert( + {tag, &*oldRemainingChildInListIt}); + } + } else { + // TODO: we might want to remove this block. It seems + // impossible to hit this logically (and empirically, after + // testing on lots of randomized and pathologically + // constructed trees) but I'm leaving this here out of an + // abundance of caution. + // In theory, this path should never be hit. If we don't see + // this in dev after a few months, let's delete this path. + react_native_assert(false); + mutationInstructionContainer.deleteMutations.push_back( + ShadowViewMutation::DeleteMutation( + oldFlattenedNode.shadowView)); + + calculateShadowViewMutationsV2( + DIFF_BREADCRUMB( + "Destroy " + + std::to_string(oldFlattenedNode.shadowView.tag)), + mutationInstructionContainer + .destructiveDownwardMutations, + oldFlattenedNode.shadowView, + sliceChildShadowNodeViewPairsV2( + *oldFlattenedNode.shadowNode), + {}); + } + } + } 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); + } + + // We also remove it from delete/creation candidates + auto deleteCreateIt = deletionCreationCandidatePairs.find( + oldFlattenedNode.shadowView.tag); + if (deleteCreateIt != deletionCreationCandidatePairs.end()) { + deletionCreationCandidatePairs.erase(deleteCreateIt); + } + } + } + } + } + } + + // 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 && + !newTreeNodePair.inOtherTree) { + if (newTreeNodePair.isConcreteView) { + mutationInstructionContainer.createMutations.push_back( + ShadowViewMutation::CreateMutation(newTreeNodePair.shadowView)); + } else { + mutationInstructionContainer.deleteMutations.push_back( + ShadowViewMutation::DeleteMutation(newTreeNodePair.shadowView)); + } + } + + treeChildPair.inOtherTree = true; + otherTreeNodePair.inOtherTree = true; + + if (parentSubVisitedOtherNewNodes != nullptr) { + parentSubVisitedOtherNewNodes->insert( + {newTreeNodePair.shadowView.tag, &newTreeNodePair}); + } + if (parentSubVisitedOtherOldNodes != nullptr) { + parentSubVisitedOtherOldNodes->insert( + {oldTreeNodePair.shadowView.tag, &oldTreeNodePair}); + } + + if (unvisitedIt != unvisitedOtherNodes.end()) { + unvisitedOtherNodes.erase(unvisitedIt); + } + } 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 it = deletionCreationCandidatePairs.begin(); + it != deletionCreationCandidatePairs.end(); + it++) { + if (it->first == 0) { + continue; + } + auto &treeChildPair = *it->second; + + // If node was visited during a flattening/unflattening recursion. + if (treeChildPair.inOtherTree) { + continue; + } + + if (reparentMode == ReparentMode::Flatten) { + mutationInstructionContainer.deleteMutations.push_back( + ShadowViewMutation::DeleteMutation(treeChildPair.shadowView)); + + if (!treeChildPair.flattened) { + calculateShadowViewMutationsV2( + DIFF_BREADCRUMB( + "Recursively delete tree child pair (flatten case): " + + std::to_string(treeChildPair.shadowView.tag)), + mutationInstructionContainer.destructiveDownwardMutations, + treeChildPair.shadowView, + sliceChildShadowNodeViewPairsV2(*treeChildPair.shadowNode), + {}); + } + } else { + mutationInstructionContainer.createMutations.push_back( + ShadowViewMutation::CreateMutation(treeChildPair.shadowView)); + + if (!treeChildPair.flattened) { + calculateShadowViewMutationsV2( + DIFF_BREADCRUMB( + "Recursively delete tree child pair (unflatten case): " + + std::to_string(treeChildPair.shadowView.tag)), + mutationInstructionContainer.downwardMutations, + treeChildPair.shadowView, + {}, + sliceChildShadowNodeViewPairsV2(*treeChildPair.shadowNode)); + } + } + } +} + +static void calculateShadowViewMutationsV2( + BREADCRUMB_TYPE breadcrumb, + ShadowViewMutation::List &mutations, + ShadowView const &parentShadowView, + ShadowViewNodePair::List &&oldChildPairs, + ShadowViewNodePair::List &&newChildPairs) { + if (oldChildPairs.empty() && newChildPairs.empty()) { + return; + } + + size_t index = 0; + + // Lists of mutations + auto createMutations = ShadowViewMutation::List{}; + auto deleteMutations = ShadowViewMutation::List{}; + auto insertMutations = ShadowViewMutation::List{}; + auto removeMutations = ShadowViewMutation::List{}; + auto updateMutations = ShadowViewMutation::List{}; + auto downwardMutations = ShadowViewMutation::List{}; + auto destructiveDownwardMutations = ShadowViewMutation::List{}; + auto mutationInstructionContainer = OrderedMutationInstructionContainer{ + createMutations, + deleteMutations, + insertMutations, + removeMutations, + updateMutations, + downwardMutations, + destructiveDownwardMutations}; + + DEBUG_LOGS({ + LOG(ERROR) << "Differ Entry: Child Pairs of node: [" << parentShadowView.tag + << "]"; + std::string strOldChildPairs; + for (size_t oldIndex = 0; oldIndex < oldChildPairs.size(); oldIndex++) { + strOldChildPairs.append( + std::to_string(oldChildPairs[oldIndex].shadowView.tag)); + strOldChildPairs.append( + oldChildPairs[oldIndex].isConcreteView ? "" : "'"); + strOldChildPairs.append(oldChildPairs[oldIndex].flattened ? "*" : ""); + strOldChildPairs.append(", "); + } + std::string strNewChildPairs; + for (size_t newIndex = 0; newIndex < newChildPairs.size(); newIndex++) { + strNewChildPairs.append( + std::to_string(newChildPairs[newIndex].shadowView.tag)); + strNewChildPairs.append( + newChildPairs[newIndex].isConcreteView ? "" : "'"); + strNewChildPairs.append(newChildPairs[newIndex].flattened ? "*" : ""); + strNewChildPairs.append(", "); + } + LOG(ERROR) << "Differ Entry: Old Child Pairs: " << strOldChildPairs; + LOG(ERROR) << "Differ Entry: New Child Pairs: " << strNewChildPairs; + }); + + // Stage 1: Collecting `Update` mutations + for (index = 0; index < oldChildPairs.size() && index < newChildPairs.size(); + index++) { + auto &oldChildPair = oldChildPairs[index]; + auto &newChildPair = newChildPairs[index]; + + if (oldChildPair.shadowView.tag != newChildPair.shadowView.tag) { + DEBUG_LOGS({ + LOG(ERROR) << "Differ Branch 1.1: Tags Different: [" + << oldChildPair.shadowView.tag << "] [" + << newChildPair.shadowView.tag << "]" + << " with parent: [" << parentShadowView.tag << "]"; + }); + + // Totally different nodes, updating is impossible. + break; + } + + // If either view was flattened, and that has changed this frame, don't try + // to update + if (oldChildPair.flattened != newChildPair.flattened || + oldChildPair.isConcreteView != newChildPair.isConcreteView) { + break; + } + + DEBUG_LOGS({ + LOG(ERROR) << "Differ Branch 1.2: Same tags, update and recurse: [" + << oldChildPair.shadowView.tag << "]" + << (oldChildPair.flattened ? " (flattened)" : "") + << (oldChildPair.isConcreteView ? " (concrete)" : "") << "[" + << newChildPair.shadowView.tag << "]" + << (newChildPair.flattened ? " (flattened)" : "") + << (newChildPair.isConcreteView ? " (concrete)" : "") + << " with parent: [" << parentShadowView.tag << "]"; + }); + + if (newChildPair.isConcreteView && + oldChildPair.shadowView != newChildPair.shadowView) { + updateMutations.push_back(ShadowViewMutation::UpdateMutation( + oldChildPair.shadowView, newChildPair.shadowView)); + } + + // Recursively update tree if ShadowNode pointers are not equal + if (!oldChildPair.flattened && + oldChildPair.shadowNode != newChildPair.shadowNode) { + auto oldGrandChildPairs = + sliceChildShadowNodeViewPairsV2(*oldChildPair.shadowNode); + auto newGrandChildPairs = + sliceChildShadowNodeViewPairsV2(*newChildPair.shadowNode); + calculateShadowViewMutationsV2( + DIFF_BREADCRUMB( + "Stage 1: Recurse on " + + std::to_string(oldChildPair.shadowView.tag)), + *(newGrandChildPairs.size() ? &downwardMutations + : &destructiveDownwardMutations), + oldChildPair.shadowView, + std::move(oldGrandChildPairs), + std::move(newGrandChildPairs)); + } + } + + size_t lastIndexAfterFirstStage = index; + + if (index == newChildPairs.size()) { + // We've reached the end of the new children. We can delete+remove the + // rest. + for (; index < oldChildPairs.size(); index++) { + auto const &oldChildPair = oldChildPairs[index]; + + DEBUG_LOGS({ + LOG(ERROR) << "Differ Branch 2: Deleting Tag/Tree: [" + << oldChildPair.shadowView.tag << "]" + << " with parent: [" << parentShadowView.tag << "]"; + }); + + if (!oldChildPair.isConcreteView) { + continue; + } + + deleteMutations.push_back( + ShadowViewMutation::DeleteMutation(oldChildPair.shadowView)); + removeMutations.push_back(ShadowViewMutation::RemoveMutation( + parentShadowView, + oldChildPair.shadowView, + static_cast(oldChildPair.mountIndex))); + + // We also have to call the algorithm recursively to clean up the entire + // subtree starting from the removed view. + calculateShadowViewMutationsV2( + DIFF_BREADCRUMB( + "Trivial delete " + std::to_string(oldChildPair.shadowView.tag)), + destructiveDownwardMutations, + oldChildPair.shadowView, + sliceChildShadowNodeViewPairsV2(*oldChildPair.shadowNode), + {}); + } + } else if (index == oldChildPairs.size()) { + // If we don't have any more existing children we can choose a fast path + // since the rest will all be create+insert. + for (; index < newChildPairs.size(); index++) { + auto const &newChildPair = newChildPairs[index]; + + DEBUG_LOGS({ + LOG(ERROR) << "Differ Branch 3: Creating Tag/Tree: [" + << newChildPair.shadowView.tag << "]" + << " with parent: [" << parentShadowView.tag << "]"; + }); + + if (!newChildPair.isConcreteView) { + continue; + } + + insertMutations.push_back(ShadowViewMutation::InsertMutation( + parentShadowView, + newChildPair.shadowView, + static_cast(newChildPair.mountIndex))); + createMutations.push_back( + ShadowViewMutation::CreateMutation(newChildPair.shadowView)); + + calculateShadowViewMutationsV2( + DIFF_BREADCRUMB( + "Trivial create " + std::to_string(newChildPair.shadowView.tag)), + downwardMutations, + newChildPair.shadowView, + {}, + sliceChildShadowNodeViewPairsV2(*newChildPair.shadowNode)); + } + } else { + // Collect map of tags in the new list + auto newRemainingPairs = TinyMap{}; + auto newInsertedPairs = TinyMap{}; + auto deletionCandidatePairs = TinyMap{}; + for (; index < newChildPairs.size(); index++) { + auto &newChildPair = newChildPairs[index]; + newRemainingPairs.insert({newChildPair.shadowView.tag, &newChildPair}); + } + + // Walk through both lists at the same time + // We will perform updates, create+insert, remove+delete, remove+insert + // (move) here. + size_t oldIndex = lastIndexAfterFirstStage, + newIndex = lastIndexAfterFirstStage, newSize = newChildPairs.size(), + oldSize = oldChildPairs.size(); + while (newIndex < newSize || oldIndex < oldSize) { + bool haveNewPair = newIndex < newSize; + bool haveOldPair = oldIndex < oldSize; + + // Advance both pointers if pointing to the same element + if (haveNewPair && haveOldPair) { + auto const &oldChildPair = oldChildPairs[oldIndex]; + auto const &newChildPair = newChildPairs[newIndex]; + + Tag newTag = newChildPair.shadowView.tag; + Tag oldTag = oldChildPair.shadowView.tag; + + if (newTag == oldTag) { + DEBUG_LOGS({ + LOG(ERROR) << "Differ Branch 5: Matched Tags at indices: " + << oldIndex << " " << newIndex << ": [" + << oldChildPair.shadowView.tag << "]" + << (oldChildPair.flattened ? "(flattened)" : "") + << (oldChildPair.isConcreteView ? "(concrete)" : "") + << " [" << newChildPair.shadowView.tag << "]" + << (newChildPair.flattened ? "(flattened)" : "") + << (newChildPair.isConcreteView ? "(concrete)" : "") + << " with parent: [" << parentShadowView.tag << "]"; + }); + + // Check concrete-ness of views + // Create/Delete and Insert/Remove if necessary + if (oldChildPair.isConcreteView != newChildPair.isConcreteView) { + if (newChildPair.isConcreteView) { + insertMutations.push_back(ShadowViewMutation::InsertMutation( + parentShadowView, + newChildPair.shadowView, + static_cast(newChildPair.mountIndex))); + createMutations.push_back( + ShadowViewMutation::CreateMutation(newChildPair.shadowView)); + } else { + removeMutations.push_back(ShadowViewMutation::RemoveMutation( + parentShadowView, + oldChildPair.shadowView, + static_cast(oldChildPair.mountIndex))); + deleteMutations.push_back( + ShadowViewMutation::DeleteMutation(oldChildPair.shadowView)); + } + } else if ( + oldChildPair.isConcreteView && newChildPair.isConcreteView) { + // Even if node's children are flattened, it might still be a + // concrete view. The case where they're different is handled above. + if (oldChildPair.shadowView != newChildPair.shadowView) { + updateMutations.push_back(ShadowViewMutation::UpdateMutation( + oldChildPair.shadowView, newChildPair.shadowView)); + } + + // Remove from newRemainingPairs + auto newRemainingPairIt = newRemainingPairs.find(oldTag); + if (newRemainingPairIt != newRemainingPairs.end()) { + newRemainingPairs.erase(newRemainingPairIt); + } + } + + // Are we flattening or unflattening either one? If node was flattened + // in both trees, there's no change, just continue. + if (oldChildPair.flattened && newChildPair.flattened) { + newIndex++; + oldIndex++; + continue; + } + // We are either flattening or unflattening this node. + if (oldChildPair.flattened != newChildPair.flattened) { + DEBUG_LOGS({ + LOG(ERROR) << "Differ: flattening or unflattening at branch 6: [" + << oldChildPair.shadowView.tag << "] [" + << newChildPair.shadowView.tag << "] " + << oldChildPair.flattened << " " + << newChildPair.flattened << " with parent: [" + << parentShadowView.tag << "]"; + }); + + // Flattening + if (!oldChildPair.flattened) { + // 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( + "Flatten tree " + std::to_string(parentShadowView.tag) + + " into list " + + std::to_string(oldChildPair.shadowView.tag)), + ReparentMode::Flatten, + mutationInstructionContainer, + parentShadowView, + newRemainingPairs, + oldChildPair); + } + // Unflattening + else { + // Construct unvisited nodes map + auto unvisitedOldChildPairs = + TinyMap{}; + // We don't know where all the children of oldChildPair are within + // oldChildPairs, but we know that they're in the same relative + // order. The reason for this is because of flattening + zIndex: + // the children could be listed before the parent, interwoven with + // children from other nodes, etc. + auto oldFlattenedNodes = sliceChildShadowNodeViewPairsV2( + *oldChildPair.shadowNode, true); + for (size_t i = 0, j = 0; + i < oldChildPairs.size() && j < oldFlattenedNodes.size(); + i++) { + auto &oldChild = oldChildPairs[i]; + if (oldChild.shadowView.tag == + oldFlattenedNodes[j].shadowView.tag) { + unvisitedOldChildPairs.insert( + {oldChild.shadowView.tag, &oldChild}); + j++; + } + } + + // Unflatten old list into new tree + calculateShadowViewMutationsFlattener( + DIFF_BREADCRUMB( + "Unflatten old list " + + std::to_string(parentShadowView.tag) + " into new tree " + + std::to_string(newChildPair.shadowView.tag)), + ReparentMode::Unflatten, + mutationInstructionContainer, + parentShadowView, + unvisitedOldChildPairs, + newChildPair); + + // 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 &oldFlattenedNode : oldFlattenedNodes) { + auto unvisitedOldChildPairIt = unvisitedOldChildPairs.find( + oldFlattenedNode.shadowView.tag); + if (unvisitedOldChildPairIt == unvisitedOldChildPairs.end()) { + // Node was visited - make sure to remove it from + // "newRemainingPairs" map + auto newRemainingIt = + newRemainingPairs.find(oldFlattenedNode.shadowView.tag); + if (newRemainingIt != newRemainingPairs.end()) { + newRemainingPairs.erase(newRemainingIt); + } + } + } + } + + newIndex++; + oldIndex++; + continue; + } + + // Update subtrees if View is not flattened, and if node addresses are + // not equal + if (oldChildPair.shadowNode != newChildPair.shadowNode) { + auto oldGrandChildPairs = + sliceChildShadowNodeViewPairsV2(*oldChildPair.shadowNode); + auto newGrandChildPairs = + sliceChildShadowNodeViewPairsV2(*newChildPair.shadowNode); + calculateShadowViewMutationsV2( + DIFF_BREADCRUMB( + "Non-trivial update " + + std::to_string(oldChildPair.shadowView.tag)), + *(newGrandChildPairs.size() ? &downwardMutations + : &destructiveDownwardMutations), + oldChildPair.shadowView, + std::move(oldGrandChildPairs), + std::move(newGrandChildPairs)); + } + + newIndex++; + oldIndex++; + continue; + } + } + + // We have an old pair, but we either don't have any remaining new pairs + // or we have one but it's not matched up with the old pair + if (haveOldPair) { + auto const &oldChildPair = oldChildPairs[oldIndex]; + + Tag oldTag = oldChildPair.shadowView.tag; + + // Was oldTag already inserted? This indicates a reordering, not just + // a move. The new node has already been inserted, we just need to + // remove the node from its old position now, and update the node's + // subtree. + auto const insertedIt = newInsertedPairs.find(oldTag); + if (insertedIt != newInsertedPairs.end()) { + auto const &newChildPair = *insertedIt->second; + + // The node has been reordered and we are also flattening or + // unflattening + if (oldChildPair.flattened != newChildPair.flattened) { + DEBUG_LOGS({ + LOG(ERROR) + << "Differ: branch 7: Flattening or unflattening already-inserted node upon remove (move/reorder operation)." + << oldChildPair.shadowView.tag << " " + << oldChildPair.flattened << " // " + << newChildPair.shadowView.tag << " " + << newChildPair.flattened; + }); + + // Unflattening. + // The node in question was already inserted and we are + // *unflattening* it, so we just need to update the subtree nodes + // and remove them from the view hierarchy. Any of the unvisited + // nodes in the old tree will be deleted. + // TODO: can we consolidate this code? It's identical to the first + // block above. + if (!oldChildPair.flattened) { + // 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( + "Flatten2 " + std::to_string(parentShadowView.tag)), + ReparentMode::Flatten, + mutationInstructionContainer, + parentShadowView, + newRemainingPairs, + oldChildPair); + } + // Unflattening + else { + // Construct unvisited nodes map + auto unvisitedOldChildPairs = + TinyMap{}; + // We don't know where all the children of oldChildPair are within + // oldChildPairs, but we know that they're in the same relative + // order. The reason for this is because of flattening + zIndex: + // the children could be listed before the parent, interwoven with + // children from other nodes, etc. + auto oldFlattenedNodes = sliceChildShadowNodeViewPairsV2( + *oldChildPair.shadowNode, true); + for (size_t i = 0, j = 0; + i < oldChildPairs.size() && j < oldFlattenedNodes.size(); + i++) { + auto &oldChild = oldChildPairs[i]; + if (oldChild.shadowView.tag == + oldFlattenedNodes[j].shadowView.tag) { + unvisitedOldChildPairs.insert( + {oldChild.shadowView.tag, &oldChild}); + j++; + } + } + + // Unflatten old list into new tree + calculateShadowViewMutationsFlattener( + DIFF_BREADCRUMB( + "Unflatten2 " + std::to_string(parentShadowView.tag)), + ReparentMode::Unflatten, + mutationInstructionContainer, + parentShadowView, + unvisitedOldChildPairs, + newChildPair); + + // 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. TODO: delete recursively? create + // recursively? + for (auto &oldFlattenedNode : oldFlattenedNodes) { + auto unvisitedOldChildPairIt = unvisitedOldChildPairs.find( + oldFlattenedNode.shadowView.tag); + if (unvisitedOldChildPairIt == unvisitedOldChildPairs.end()) { + // Node was visited - make sure to remove it from + // "newRemainingPairs" map + auto newRemainingIt = + newRemainingPairs.find(oldFlattenedNode.shadowView.tag); + if (newRemainingIt != newRemainingPairs.end()) { + newRemainingPairs.erase(newRemainingIt); + } + } + } + } + } + + // Check concrete-ness of views + // Create/Delete and Insert/Remove if necessary + // TODO: document: Insert should already be handled by outermost loop, + // but not Remove + if (oldChildPair.isConcreteView != newChildPair.isConcreteView) { + if (newChildPair.isConcreteView) { + createMutations.push_back( + ShadowViewMutation::CreateMutation(newChildPair.shadowView)); + } else { + removeMutations.push_back(ShadowViewMutation::RemoveMutation( + parentShadowView, + oldChildPair.shadowView, + static_cast(oldChildPair.mountIndex))); + deleteMutations.push_back( + ShadowViewMutation::DeleteMutation(oldChildPair.shadowView)); + } + } + + // old and new child pairs are both either flattened or unflattened at + // this point. If they're not views, we don't need to update subtrees. + if (oldChildPair.isConcreteView && newChildPair.isConcreteView) { + // TODO: do we always want to remove here? There are cases where we + // might be able to remove this to prevent unnecessary + // removes/inserts in cases of (un)flattening + reorders? + removeMutations.push_back(ShadowViewMutation::RemoveMutation( + parentShadowView, + oldChildPair.shadowView, + static_cast(oldChildPair.mountIndex))); + + if (oldChildPair.shadowView != newChildPair.shadowView) { + updateMutations.push_back(ShadowViewMutation::UpdateMutation( + oldChildPair.shadowView, newChildPair.shadowView)); + } + } + + if (!oldChildPair.flattened && !newChildPair.flattened && + oldChildPair.shadowNode != newChildPair.shadowNode) { + // Update subtrees + auto oldGrandChildPairs = + sliceChildShadowNodeViewPairsV2(*oldChildPair.shadowNode); + auto newGrandChildPairs = + sliceChildShadowNodeViewPairsV2(*newChildPair.shadowNode); + calculateShadowViewMutationsV2( + DIFF_BREADCRUMB( + "Non-trivial update3 " + + std::to_string(oldChildPair.shadowView.tag)), + *(newGrandChildPairs.size() ? &downwardMutations + : &destructiveDownwardMutations), + oldChildPair.shadowView, + std::move(oldGrandChildPairs), + std::move(newGrandChildPairs)); + } + + newInsertedPairs.erase(insertedIt); + oldIndex++; + continue; + } + + // Should we generate a delete+remove instruction for the old node? + // If there's an old node and it's not found in the "new" list, we + // generate remove+delete for this node and its subtree. + auto const newIt = newRemainingPairs.find(oldTag); + if (newIt == newRemainingPairs.end()) { + DEBUG_LOGS({ + LOG(ERROR) + << "Differ Branch 9: Removing tag that was not reinserted: " + << oldIndex << ": [" << oldChildPair.shadowView.tag << "]" + << (oldChildPair.flattened ? " (flattened)" : "") + << (oldChildPair.isConcreteView ? " (concrete)" : "") + << " with parent: [" << parentShadowView.tag << "] " + << "node is in other tree? " + << (oldChildPair.inOtherTree ? "yes" : "no"); + }); + + if (oldChildPair.isConcreteView) { + removeMutations.push_back(ShadowViewMutation::RemoveMutation( + parentShadowView, + oldChildPair.shadowView, + static_cast(oldChildPair.mountIndex))); + + deletionCandidatePairs.insert( + {oldChildPair.shadowView.tag, &oldChildPair}); + } + + oldIndex++; + continue; + } + } + + // At this point, oldTag is -1 or is in the new list, and hasn't been + // inserted or matched yet. We're not sure yet if the new node is in the + // old list - generate an insert instruction for the new node. + auto &newChildPair = newChildPairs[newIndex]; + DEBUG_LOGS({ + LOG(ERROR) + << "Differ Branch 10: Inserting tag/tree that was not (yet?) removed from hierarchy: " + << newIndex << "/" << newSize << ": [" + << newChildPair.shadowView.tag << "]" + << (newChildPair.flattened ? " (flattened)" : "") + << (newChildPair.isConcreteView ? " (concrete)" : "") + << " with parent: [" << parentShadowView.tag << "]"; + }); + if (newChildPair.isConcreteView) { + insertMutations.push_back(ShadowViewMutation::InsertMutation( + parentShadowView, + newChildPair.shadowView, + static_cast(newChildPair.mountIndex))); + } + + // `inOtherTree` is only set to true during flattening/unflattening of + // parent. If the parent isn't (un)flattened, this will always be `false`, + // even if the node is in the other (old) tree. In this case, we expect + // the node to be removed from `newInsertedPairs` when we later encounter + // it in this loop. + if (!newChildPair.inOtherTree) { + newInsertedPairs.insert({newChildPair.shadowView.tag, &newChildPair}); + } + + newIndex++; + } + + // Penultimate step: generate Delete instructions for entirely deleted + // subtrees/nodes. We do this here because we need to traverse the entire + // list to make sure that a node was not reparented into an unflattened node + // that occurs *after* it in the hierarchy, due to zIndex ordering. + for (auto it = deletionCandidatePairs.begin(); + it != deletionCandidatePairs.end(); + it++) { + if (it->first == 0) { + continue; + } + + auto const &oldChildPair = *it->second; + + DEBUG_LOGS({ + LOG(ERROR) + << "Differ Branch 11: Deleting tag/tree that was not in new hierarchy: " + << "[" << oldChildPair.shadowView.tag << "]" + << (oldChildPair.flattened ? "(flattened)" : "") + << (oldChildPair.isConcreteView ? "(concrete)" : "") + << (oldChildPair.inOtherTree ? "(in other tree)" : "") + << " with parent: [" << parentShadowView.tag << "]"; + }); + + // This can happen when the parent is unflattened + if (!oldChildPair.inOtherTree) { + deleteMutations.push_back( + ShadowViewMutation::DeleteMutation(oldChildPair.shadowView)); + + // We also have to call the algorithm recursively to clean up the + // entire subtree starting from the removed view. + calculateShadowViewMutationsV2( + DIFF_BREADCRUMB( + "Non-trivial delete " + + std::to_string(oldChildPair.shadowView.tag)), + destructiveDownwardMutations, + oldChildPair.shadowView, + sliceChildShadowNodeViewPairsV2(*oldChildPair.shadowNode), + {}); + } + } + + // Final step: generate Create instructions for entirely new subtrees/nodes + // that are not the result of flattening or unflattening. + for (auto it = newInsertedPairs.begin(); it != newInsertedPairs.end(); + it++) { + // Erased elements of a TinyMap will have a Tag/key of 0 - skip those + // These *should* be removed by the map; there are currently no KNOWN + // cases where TinyMap will do the wrong thing, but there are not yet + // any unit tests explicitly for TinyMap, so this is safer for now. + if (it->first == 0) { + continue; + } + + auto const &newChildPair = *it->second; + + DEBUG_LOGS({ + LOG(ERROR) + << "Differ Branch 12: Inserting tag/tree that was not in old hierarchy: " + << "[" << newChildPair.shadowView.tag << "]" + << (newChildPair.flattened ? "(flattened)" : "") + << (newChildPair.isConcreteView ? "(concrete)" : "") + << (newChildPair.inOtherTree ? "(in other tree)" : "") + << " with parent: [" << parentShadowView.tag << "]"; + }); + + if (!newChildPair.isConcreteView) { + continue; + } + if (newChildPair.inOtherTree) { + continue; + } + + createMutations.push_back( + ShadowViewMutation::CreateMutation(newChildPair.shadowView)); + + calculateShadowViewMutationsV2( + DIFF_BREADCRUMB( + "Non-trivial create " + + std::to_string(newChildPair.shadowView.tag)), + downwardMutations, + newChildPair.shadowView, + {}, + sliceChildShadowNodeViewPairsV2(*newChildPair.shadowNode)); + } + } + + // All mutations in an optimal order: + std::move( + destructiveDownwardMutations.begin(), + destructiveDownwardMutations.end(), + std::back_inserter(mutations)); + std::move( + updateMutations.begin(), + updateMutations.end(), + std::back_inserter(mutations)); + std::move( + removeMutations.rbegin(), + removeMutations.rend(), + std::back_inserter(mutations)); + std::move( + deleteMutations.begin(), + deleteMutations.end(), + std::back_inserter(mutations)); + std::move( + createMutations.begin(), + createMutations.end(), + std::back_inserter(mutations)); + std::move( + downwardMutations.begin(), + downwardMutations.end(), + std::back_inserter(mutations)); + std::move( + insertMutations.begin(), + insertMutations.end(), + std::back_inserter(mutations)); +} + +/** + * Only used by unit tests currently. + */ +static void sliceChildShadowNodeViewPairsRecursivelyLegacy( + ShadowViewNodePair::List &pairList, + Point layoutOffset, + ShadowNode const &shadowNode) { + for (auto const &sharedChildShadowNode : shadowNode.getChildren()) { + auto &childShadowNode = *sharedChildShadowNode; + +#ifndef ANDROID + // Temporary disabled on Android because the mounting infrastructure + // is not fully ready yet. + if (childShadowNode.getTraits().check(ShadowNodeTraits::Trait::Hidden)) { + continue; + } +#endif + + auto shadowView = ShadowView(childShadowNode); + auto origin = layoutOffset; + if (shadowView.layoutMetrics != EmptyLayoutMetrics) { + origin += shadowView.layoutMetrics.frame.origin; + shadowView.layoutMetrics.frame.origin += layoutOffset; + } + + if (childShadowNode.getTraits().check( + ShadowNodeTraits::Trait::FormsStackingContext)) { + pairList.push_back({shadowView, &childShadowNode}); + } else { + if (childShadowNode.getTraits().check( + ShadowNodeTraits::Trait::FormsView)) { + pairList.push_back({shadowView, &childShadowNode}); + } + + sliceChildShadowNodeViewPairsRecursivelyLegacy( + pairList, origin, childShadowNode); + } + } +} + +/** + * Only used by unit tests currently. + */ +ShadowViewNodePair::List sliceChildShadowNodeViewPairsLegacy( + ShadowNode const &shadowNode) { + auto pairList = ShadowViewNodePair::List{}; + + if (!shadowNode.getTraits().check( + ShadowNodeTraits::Trait::FormsStackingContext) && + shadowNode.getTraits().check(ShadowNodeTraits::Trait::FormsView)) { + return pairList; + } + + sliceChildShadowNodeViewPairsRecursivelyLegacy(pairList, {0, 0}, shadowNode); + + return pairList; +} + +ShadowViewMutation::List calculateShadowViewMutations( + ShadowNode const &oldRootShadowNode, + ShadowNode const &newRootShadowNode) { + SystraceSection s("calculateShadowViewMutations"); + + // Root shadow nodes must be belong the same family. + react_native_assert( + ShadowNode::sameFamily(oldRootShadowNode, newRootShadowNode)); + + auto mutations = ShadowViewMutation::List{}; + mutations.reserve(256); + + auto oldRootShadowView = ShadowView(oldRootShadowNode); + auto newRootShadowView = ShadowView(newRootShadowNode); + + if (oldRootShadowView != newRootShadowView) { + mutations.push_back(ShadowViewMutation::UpdateMutation( + oldRootShadowView, newRootShadowView)); + } + + calculateShadowViewMutationsV2( + CREATE_DIFF_BREADCRUMB(oldRootShadowView.tag), + mutations, + ShadowView(oldRootShadowNode), + sliceChildShadowNodeViewPairsV2(oldRootShadowNode), + sliceChildShadowNodeViewPairsV2(newRootShadowNode)); + + return mutations; +} + +} // namespace DifferOld +} // namespace react +} // namespace facebook diff --git a/ReactCommon/react/renderer/mounting/DifferentiatorFlatteningClassic.h b/ReactCommon/react/renderer/mounting/DifferentiatorFlatteningClassic.h new file mode 100644 index 0000000000..fed70547f5 --- /dev/null +++ b/ReactCommon/react/renderer/mounting/DifferentiatorFlatteningClassic.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +// This file exists as an experimental baseline against which +// we can compare changes in Differentiator. +// Once changes in Differentiator have been verified safe, +// this file will be deleted. + +#include +#include +#include + +namespace facebook { +namespace react { +namespace DifferOld { + +enum class ReparentMode { Flatten, Unflatten }; + +/* + * Calculates a list of view mutations which describes how the old + * `ShadowTree` can be transformed to the new one. + * The list of mutations might be and might not be optimal. + */ +ShadowViewMutationList calculateShadowViewMutations( + ShadowNode const &oldRootShadowNode, + ShadowNode const &newRootShadowNode); + +/** + * Generates a list of `ShadowViewNodePair`s that represents a layer of a + * flattened view hierarchy. The V2 version preserves nodes even if they do + * not form views and their children are flattened. + */ +ShadowViewNodePair::List sliceChildShadowNodeViewPairsV2( + ShadowNode const &shadowNode, + bool allowFlattened = false); + +/* + * Generates a list of `ShadowViewNodePair`s that represents a layer of a + * flattened view hierarchy. This is *only* used by unit tests currently. + */ +ShadowViewNodePair::List sliceChildShadowNodeViewPairsLegacy( + ShadowNode const &shadowNode); + +} // namespace DifferOld +} // namespace react +} // namespace facebook diff --git a/ReactCommon/react/renderer/mounting/MountingCoordinator.cpp b/ReactCommon/react/renderer/mounting/MountingCoordinator.cpp index 98001cfa1c..ad66e2296d 100644 --- a/ReactCommon/react/renderer/mounting/MountingCoordinator.cpp +++ b/ReactCommon/react/renderer/mounting/MountingCoordinator.cpp @@ -90,7 +90,9 @@ better::optional MountingCoordinator::pullTransaction() telemetry.willDiff(); auto mutations = calculateShadowViewMutations( - *baseRevision_.rootShadowNode, *lastRevision_->rootShadowNode); + *baseRevision_.rootShadowNode, + *lastRevision_->rootShadowNode, + enableNewDiffer_); telemetry.didDiff(); @@ -186,5 +188,9 @@ void MountingCoordinator::setMountingOverrideDelegate( mountingOverrideDelegate_ = delegate; } +void MountingCoordinator::setEnableNewDiffer(bool enabled) const { + enableNewDiffer_ = enabled; +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/react/renderer/mounting/MountingCoordinator.h b/ReactCommon/react/renderer/mounting/MountingCoordinator.h index 00c12ba068..8d434f43ad 100644 --- a/ReactCommon/react/renderer/mounting/MountingCoordinator.h +++ b/ReactCommon/react/renderer/mounting/MountingCoordinator.h @@ -71,6 +71,8 @@ class MountingCoordinator final { TelemetryController const &getTelemetryController() const; + void setEnableNewDiffer(bool enabled) const; + /* * Methods from this section are meant to be used by * `MountingOverrideDelegate` only. @@ -112,6 +114,8 @@ class MountingCoordinator final { TelemetryController telemetryController_; + mutable bool enableNewDiffer_{false}; + #ifdef RN_SHADOW_TREE_INTROSPECTION mutable StubViewTree stubViewTree_; // Protected by `mutex_`. #endif diff --git a/ReactCommon/react/renderer/mounting/ShadowTree.cpp b/ReactCommon/react/renderer/mounting/ShadowTree.cpp index 7350dbc9d6..9c165752d6 100644 --- a/ReactCommon/react/renderer/mounting/ShadowTree.cpp +++ b/ReactCommon/react/renderer/mounting/ShadowTree.cpp @@ -223,7 +223,8 @@ ShadowTree::ShadowTree( SurfaceId surfaceId, LayoutConstraints const &layoutConstraints, LayoutContext const &layoutContext, - ShadowTreeDelegate const &delegate) + ShadowTreeDelegate const &delegate, + bool enableNewDiffer) : surfaceId_(surfaceId), delegate_(delegate) { const auto noopEventEmitter = std::make_shared( nullptr, -1, std::shared_ptr()); @@ -252,6 +253,7 @@ ShadowTree::ShadowTree( mountingCoordinator_ = std::make_shared(currentRevision_); + mountingCoordinator_->setEnableNewDiffer(enableNewDiffer); } ShadowTree::~ShadowTree() { diff --git a/ReactCommon/react/renderer/mounting/ShadowTree.h b/ReactCommon/react/renderer/mounting/ShadowTree.h index 2a8f1d1fad..dcfc584f64 100644 --- a/ReactCommon/react/renderer/mounting/ShadowTree.h +++ b/ReactCommon/react/renderer/mounting/ShadowTree.h @@ -70,7 +70,8 @@ class ShadowTree final { SurfaceId surfaceId, LayoutConstraints const &layoutConstraints, LayoutContext const &layoutContext, - ShadowTreeDelegate const &delegate); + ShadowTreeDelegate const &delegate, + bool enableNewDiffer); ~ShadowTree(); diff --git a/ReactCommon/react/renderer/mounting/stubs.cpp b/ReactCommon/react/renderer/mounting/stubs.cpp index 9a87c4987d..3c483a46ea 100644 --- a/ReactCommon/react/renderer/mounting/stubs.cpp +++ b/ReactCommon/react/renderer/mounting/stubs.cpp @@ -88,7 +88,7 @@ StubViewTree buildStubViewTreeUsingDifferentiator( ShadowNode::emptySharedShadowNodeSharedList()}); auto mutations = - calculateShadowViewMutations(*emptyRootShadowNode, rootShadowNode); + calculateShadowViewMutations(*emptyRootShadowNode, rootShadowNode, true); auto stubViewTree = StubViewTree(ShadowView(*emptyRootShadowNode)); stubViewTree.mutate(mutations); diff --git a/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp b/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp index 6a2d0034a2..0cb2fb4740 100644 --- a/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp +++ b/ReactCommon/react/renderer/mounting/tests/MountingTest.cpp @@ -242,7 +242,8 @@ TEST(MountingTest, testReorderingInstructionGeneration) { }*/ // Calculating mutations. - auto mutations1 = calculateShadowViewMutations(*rootNodeV1, *rootNodeV2); + auto mutations1 = + calculateShadowViewMutations(*rootNodeV1, *rootNodeV2, true); // The order and exact mutation instructions here may change at any time. // This test just ensures that any changes are intentional. @@ -258,7 +259,8 @@ TEST(MountingTest, testReorderingInstructionGeneration) { EXPECT_TRUE(mutations1[1].index == 0); // Calculating mutations. - auto mutations2 = calculateShadowViewMutations(*rootNodeV2, *rootNodeV3); + auto mutations2 = + calculateShadowViewMutations(*rootNodeV2, *rootNodeV3, true); // The order and exact mutation instructions here may change at any time. // This test just ensures that any changes are intentional. @@ -274,7 +276,8 @@ TEST(MountingTest, testReorderingInstructionGeneration) { EXPECT_TRUE(mutations2[1].oldChildShadowView.tag == 100); // Calculating mutations. - auto mutations3 = calculateShadowViewMutations(*rootNodeV3, *rootNodeV4); + auto mutations3 = + calculateShadowViewMutations(*rootNodeV3, *rootNodeV4, true); LOG(ERROR) << "Num mutations IN OLD TEST mutations3: " << mutations3.size(); // The order and exact mutation instructions here may change at any time. @@ -296,7 +299,8 @@ TEST(MountingTest, testReorderingInstructionGeneration) { EXPECT_TRUE(mutations3[3].index == 2); // Calculating mutations. - auto mutations4 = calculateShadowViewMutations(*rootNodeV4, *rootNodeV5); + auto mutations4 = + calculateShadowViewMutations(*rootNodeV4, *rootNodeV5, true); // The order and exact mutation instructions here may change at any time. // This test just ensures that any changes are intentional. @@ -321,7 +325,8 @@ TEST(MountingTest, testReorderingInstructionGeneration) { EXPECT_TRUE(mutations4[5].newChildShadowView.tag == 102); EXPECT_TRUE(mutations4[5].index == 3); - auto mutations5 = calculateShadowViewMutations(*rootNodeV5, *rootNodeV6); + auto mutations5 = + calculateShadowViewMutations(*rootNodeV5, *rootNodeV6, true); // The order and exact mutation instructions here may change at any time. // This test just ensures that any changes are intentional. @@ -340,7 +345,8 @@ TEST(MountingTest, testReorderingInstructionGeneration) { EXPECT_TRUE(mutations5[3].newChildShadowView.tag == 105); EXPECT_TRUE(mutations5[3].index == 3); - auto mutations6 = calculateShadowViewMutations(*rootNodeV6, *rootNodeV7); + auto mutations6 = + calculateShadowViewMutations(*rootNodeV6, *rootNodeV7, true); // The order and exact mutation instructions here may change at any time. // This test just ensures that any changes are intentional. @@ -594,7 +600,8 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) { rootNodeV5->sealRecursive(); // Calculating mutations. - auto mutations1 = calculateShadowViewMutations(*rootNodeV1, *rootNodeV2); + auto mutations1 = + calculateShadowViewMutations(*rootNodeV1, *rootNodeV2, true); EXPECT_EQ(mutations1.size(), 5); EXPECT_EQ(mutations1[0].type, ShadowViewMutation::Update); @@ -608,7 +615,8 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) { EXPECT_EQ(mutations1[4].type, ShadowViewMutation::Insert); EXPECT_EQ(mutations1[4].newChildShadowView.tag, 1000); - auto mutations2 = calculateShadowViewMutations(*rootNodeV2, *rootNodeV3); + auto mutations2 = + calculateShadowViewMutations(*rootNodeV2, *rootNodeV3, true); EXPECT_EQ(mutations2.size(), 5); EXPECT_EQ(mutations2[0].type, ShadowViewMutation::Update); @@ -624,7 +632,8 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) { EXPECT_EQ(mutations2[4].type, ShadowViewMutation::Insert); EXPECT_EQ(mutations2[4].newChildShadowView.tag, 1000); - auto mutations3 = calculateShadowViewMutations(*rootNodeV3, *rootNodeV4); + auto mutations3 = + calculateShadowViewMutations(*rootNodeV3, *rootNodeV4, true); // between these two trees, lots of new nodes are created and inserted - this // is all correct, and this is the minimal amount of mutations @@ -661,7 +670,8 @@ TEST(MountingTest, testViewReparentingInstructionGeneration) { EXPECT_EQ(mutations3[14].type, ShadowViewMutation::Insert); EXPECT_EQ(mutations3[14].newChildShadowView.tag, 1000); - auto mutations4 = calculateShadowViewMutations(*rootNodeV4, *rootNodeV5); + auto mutations4 = + calculateShadowViewMutations(*rootNodeV4, *rootNodeV5, true); EXPECT_EQ(mutations4.size(), 9); EXPECT_EQ(mutations4[0].type, ShadowViewMutation::Update); diff --git a/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp b/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp index 1661a32342..5b408b3a15 100644 --- a/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp +++ b/ReactCommon/react/renderer/mounting/tests/ShadowTreeLifeCycleTest.cpp @@ -75,7 +75,7 @@ static void testShadowNodeTreeLifeCycle( // Building an initial view hierarchy. auto viewTree = buildStubViewTreeWithoutUsingDifferentiator(*emptyRootNode); viewTree.mutate( - calculateShadowViewMutations(*emptyRootNode, *currentRootNode)); + calculateShadowViewMutations(*emptyRootNode, *currentRootNode, true)); for (int j = 0; j < stages; j++) { auto nextRootNode = currentRootNode; @@ -102,7 +102,7 @@ static void testShadowNodeTreeLifeCycle( // Calculating mutations. auto mutations = - calculateShadowViewMutations(*currentRootNode, *nextRootNode); + calculateShadowViewMutations(*currentRootNode, *nextRootNode, true); // Make sure that in a single frame, a DELETE for a // view is not followed by a CREATE for the same view. diff --git a/ReactCommon/react/renderer/mounting/tests/StackingContextTest.cpp b/ReactCommon/react/renderer/mounting/tests/StackingContextTest.cpp index 521d3135d1..b4b5b204c8 100644 --- a/ReactCommon/react/renderer/mounting/tests/StackingContextTest.cpp +++ b/ReactCommon/react/renderer/mounting/tests/StackingContextTest.cpp @@ -171,8 +171,8 @@ class StackingContextTest : public ::testing::Test { callback(buildStubViewTreeUsingDifferentiator(*rootShadowNode_)); callback(buildStubViewTreeWithoutUsingDifferentiator(*rootShadowNode_)); - auto mutations = - calculateShadowViewMutations(*currentRootShadowNode_, *rootShadowNode_); + auto mutations = calculateShadowViewMutations( + *currentRootShadowNode_, *rootShadowNode_, true); currentRootShadowNode_ = rootShadowNode_; currentStubViewTree_.mutate(mutations); callback(currentStubViewTree_); diff --git a/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp b/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp index c8f80e5b0f..d595e522ec 100644 --- a/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp +++ b/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp @@ -98,7 +98,11 @@ TEST(StateReconciliationTest, testStateReconciliation) { auto state1 = shadowNodeAB->getState(); auto shadowTreeDelegate = DummyShadowTreeDelegate{}; ShadowTree shadowTree{ - SurfaceId{11}, LayoutConstraints{}, LayoutContext{}, shadowTreeDelegate}; + SurfaceId{11}, + LayoutConstraints{}, + LayoutContext{}, + shadowTreeDelegate, + true}; shadowTree.commit( [&](RootShadowNode const &oldRootShadowNode) { diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/ReactCommon/react/renderer/scheduler/Scheduler.cpp index fb1029ea95..d6e12dbae5 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -129,11 +129,15 @@ Scheduler::Scheduler( #ifdef ANDROID removeOutstandingSurfacesOnDestruction_ = reactNativeConfig_->getBool( "react_fabric:remove_outstanding_surfaces_on_destruction_android"); + enableNewDiffer_ = reactNativeConfig_->getBool( + "react_fabric:enable_new_differ_h1_2021_android"); Constants::setPropsForwardingEnabled(reactNativeConfig_->getBool( "react_fabric:enable_props_forwarding_android")); #else removeOutstandingSurfacesOnDestruction_ = reactNativeConfig_->getBool( "react_fabric:remove_outstanding_surfaces_on_destruction_ios"); + enableNewDiffer_ = + reactNativeConfig_->getBool("react_fabric:enable_new_differ_h1_2021_ios"); #endif uiManager->extractUIManagerBindingOnDemand_ = reactNativeConfig_->getBool( @@ -199,6 +203,7 @@ Scheduler::~Scheduler() { void Scheduler::registerSurface( SurfaceHandler const &surfaceHandler) const noexcept { surfaceHandler.setUIManager(uiManager_.get()); + surfaceHandler.setEnableNewDiffer(enableNewDiffer_); } void Scheduler::unregisterSurface( diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.h b/ReactCommon/react/renderer/scheduler/Scheduler.h index 03c8f65d2e..66a437dfce 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -121,6 +121,7 @@ class Scheduler final : public UIManagerDelegate { * Temporary flags. */ bool removeOutstandingSurfacesOnDestruction_{false}; + bool enableNewDiffer_{false}; }; } // namespace react diff --git a/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp b/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp index 6cb7fb2545..cd88ce86b6 100644 --- a/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp +++ b/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp @@ -71,7 +71,8 @@ void SurfaceHandler::start() const noexcept { parameters.surfaceId, parameters.layoutConstraints, parameters.layoutContext, - *link_.uiManager); + *link_.uiManager, + enableNewDiffer_); link_.shadowTree = shadowTree.get(); @@ -138,6 +139,11 @@ DisplayMode SurfaceHandler::getDisplayMode() const noexcept { return parameters_.displayMode; } +#pragma mark - Feature Flags +void SurfaceHandler::setEnableNewDiffer(bool enabled) const noexcept { + enableNewDiffer_ = enabled; +} + #pragma mark - Accessors SurfaceId SurfaceHandler::getSurfaceId() const noexcept { diff --git a/ReactCommon/react/renderer/scheduler/SurfaceHandler.h b/ReactCommon/react/renderer/scheduler/SurfaceHandler.h index b564f5ebac..3e8233794c 100644 --- a/ReactCommon/react/renderer/scheduler/SurfaceHandler.h +++ b/ReactCommon/react/renderer/scheduler/SurfaceHandler.h @@ -138,6 +138,9 @@ class SurfaceHandler final { LayoutConstraints getLayoutConstraints() const noexcept; LayoutContext getLayoutContext() const noexcept; +#pragma mark - Feature Flags + void setEnableNewDiffer(bool enabled) const noexcept; + private: friend class Scheduler; @@ -195,6 +198,11 @@ class SurfaceHandler final { */ mutable better::shared_mutex parametersMutex_; mutable Parameters parameters_; + + /** + * Feature flags. + */ + mutable bool enableNewDiffer_{false}; }; } // namespace react