in ReactCommon/react/renderer/mounting/Differentiator.cpp [619:1082]
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 (size_t i = 0; i < flattenedNodes.size(); i++) {
auto &newChild = *flattenedNodes[i];
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 unvisitedOldChildPairIt =
unvisitedRecursiveChildPairs.begin();
unvisitedOldChildPairIt != unvisitedRecursiveChildPairs.end();
unvisitedOldChildPairIt++) {
if (unvisitedOldChildPairIt->first == 0) {
continue;
}
auto &oldFlattenedNode = *unvisitedOldChildPairIt->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 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,
// 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));
}
}
}
}