2018-05-31 22:31:03 +00:00
|
|
|
// Copyright (c) 2004-present, Facebook, Inc.
|
|
|
|
|
|
|
|
// This source code is licensed under the MIT license found in the
|
|
|
|
// LICENSE file in the root directory of this source tree.
|
2018-04-10 19:45:59 +00:00
|
|
|
|
|
|
|
#include "Differentiator.h"
|
|
|
|
|
|
|
|
namespace facebook {
|
|
|
|
namespace react {
|
|
|
|
|
2018-05-16 06:32:34 +00:00
|
|
|
static void calculateMutationInstructions(
|
2018-04-10 19:45:59 +00:00
|
|
|
TreeMutationInstructionList &instructions,
|
|
|
|
SharedShadowNode parentNode,
|
|
|
|
SharedShadowNodeSharedList oldChildNodes,
|
|
|
|
SharedShadowNodeSharedList newChildNodes
|
|
|
|
) {
|
|
|
|
// The current version of the algorithm is otimized for simplicity,
|
|
|
|
// not for performance of optimal result.
|
|
|
|
|
|
|
|
// TODO(shergin): Consider to use Minimal Edit Distance algorithm to produce
|
|
|
|
// optimal set of instructions and improve mounting performance.
|
|
|
|
// https://en.wikipedia.org/wiki/Edit_distance
|
|
|
|
// https://www.geeksforgeeks.org/dynamic-programming-set-5-edit-distance/
|
|
|
|
|
|
|
|
if (oldChildNodes == newChildNodes) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (oldChildNodes->size() == 0 && newChildNodes->size() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unordered_set<Tag> insertedTags = {};
|
|
|
|
int index = 0;
|
|
|
|
|
|
|
|
TreeMutationInstructionList createInstructions = {};
|
|
|
|
TreeMutationInstructionList deleteInstructions = {};
|
|
|
|
TreeMutationInstructionList insertInstructions = {};
|
|
|
|
TreeMutationInstructionList removeInstructions = {};
|
|
|
|
TreeMutationInstructionList replaceInstructions = {};
|
|
|
|
TreeMutationInstructionList downwardInstructions = {};
|
|
|
|
|
|
|
|
// Stage 1: Collectings Updates
|
|
|
|
for (index = 0; index < oldChildNodes->size() && index < newChildNodes->size(); index++) {
|
|
|
|
SharedShadowNode oldChildNode = oldChildNodes->at(index);
|
|
|
|
SharedShadowNode newChildNode = newChildNodes->at(index);
|
|
|
|
|
|
|
|
if (oldChildNode->getTag() != newChildNode->getTag()) {
|
|
|
|
// Totally different nodes, updating is impossible.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*oldChildNode != *newChildNode) {
|
|
|
|
replaceInstructions.push_back(
|
|
|
|
TreeMutationInstruction::Replace(
|
|
|
|
parentNode,
|
|
|
|
oldChildNode,
|
|
|
|
newChildNode,
|
|
|
|
index
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
calculateMutationInstructions(
|
|
|
|
downwardInstructions,
|
|
|
|
oldChildNode,
|
|
|
|
oldChildNode->getChildren(),
|
|
|
|
newChildNode->getChildren()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
int lastIndexAfterFirstStage = index;
|
|
|
|
|
|
|
|
// Stage 2: Collectings Insertions
|
|
|
|
for (; index < newChildNodes->size(); index++) {
|
|
|
|
SharedShadowNode newChildNode = newChildNodes->at(index);
|
|
|
|
|
|
|
|
insertInstructions.push_back(
|
|
|
|
TreeMutationInstruction::Insert(
|
|
|
|
parentNode,
|
2018-04-10 19:46:07 +00:00
|
|
|
newChildNode,
|
2018-04-10 19:45:59 +00:00
|
|
|
index
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
insertedTags.insert(newChildNode->getTag());
|
|
|
|
|
|
|
|
SharedShadowNode newChildSourceNode = newChildNode->getSourceNode();
|
|
|
|
SharedShadowNodeSharedList newChildSourceChildNodes =
|
|
|
|
newChildSourceNode ? newChildSourceNode->getChildren() : ShadowNode::emptySharedShadowNodeSharedList();
|
|
|
|
|
|
|
|
calculateMutationInstructions(
|
|
|
|
downwardInstructions,
|
|
|
|
newChildNode,
|
|
|
|
newChildSourceChildNodes,
|
|
|
|
newChildNode->getChildren()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stage 3: Collectings Deletions and Removals
|
|
|
|
for (index = lastIndexAfterFirstStage; index < oldChildNodes->size(); index++) {
|
|
|
|
SharedShadowNode oldChildNode = oldChildNodes->at(index);
|
|
|
|
|
2018-04-10 19:46:07 +00:00
|
|
|
// Even if the old node was (re)inserted, we have to generate `remove`
|
|
|
|
// instruction.
|
|
|
|
removeInstructions.push_back(
|
|
|
|
TreeMutationInstruction::Remove(
|
|
|
|
parentNode,
|
|
|
|
oldChildNode,
|
|
|
|
index
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
2018-04-10 19:45:59 +00:00
|
|
|
auto numberOfRemovedTags = insertedTags.erase(oldChildNode->getTag());
|
|
|
|
assert(numberOfRemovedTags == 0 || numberOfRemovedTags == 1);
|
|
|
|
|
2018-04-10 19:46:07 +00:00
|
|
|
if (numberOfRemovedTags == 0) {
|
2018-04-10 19:45:59 +00:00
|
|
|
// The old node was *not* (re)inserted,
|
|
|
|
// so we have to generate `delete` instruction and apply the algorithm
|
|
|
|
// recursively.
|
|
|
|
deleteInstructions.push_back(
|
|
|
|
TreeMutationInstruction::Delete(
|
|
|
|
oldChildNode
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
calculateMutationInstructions(
|
|
|
|
downwardInstructions,
|
|
|
|
oldChildNode,
|
|
|
|
oldChildNode->getChildren(),
|
|
|
|
ShadowNode::emptySharedShadowNodeSharedList()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stage 4: Collectings Creations
|
|
|
|
for (index = lastIndexAfterFirstStage; index < newChildNodes->size(); index++) {
|
|
|
|
SharedShadowNode newChildNode = newChildNodes->at(index);
|
|
|
|
if (insertedTags.find(newChildNode->getTag()) == insertedTags.end()) {
|
|
|
|
// The new node was (re)inserted, so there is no need to create it.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
createInstructions.push_back(
|
|
|
|
TreeMutationInstruction::Create(
|
|
|
|
newChildNode
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// All instructions in an optimal order:
|
|
|
|
instructions.insert(instructions.end(), replaceInstructions.begin(), replaceInstructions.end());
|
|
|
|
instructions.insert(instructions.end(), removeInstructions.begin(), removeInstructions.end());
|
|
|
|
instructions.insert(instructions.end(), deleteInstructions.begin(), deleteInstructions.end());
|
|
|
|
instructions.insert(instructions.end(), createInstructions.begin(), createInstructions.end());
|
|
|
|
instructions.insert(instructions.end(), insertInstructions.begin(), insertInstructions.end());
|
|
|
|
instructions.insert(instructions.end(), downwardInstructions.begin(), downwardInstructions.end());
|
|
|
|
}
|
|
|
|
|
2018-05-16 06:32:34 +00:00
|
|
|
|
|
|
|
void calculateMutationInstructions(
|
|
|
|
TreeMutationInstructionList &instructions,
|
|
|
|
SharedShadowNode oldRootShadowNode,
|
|
|
|
SharedShadowNode newRootShadowNode
|
|
|
|
) {
|
|
|
|
// Root shadow nodes must have same tag.
|
|
|
|
assert(oldRootShadowNode->getTag() == newRootShadowNode->getTag());
|
|
|
|
|
|
|
|
if (*oldRootShadowNode != *newRootShadowNode) {
|
|
|
|
instructions.push_back(
|
|
|
|
TreeMutationInstruction::Replace(
|
|
|
|
nullptr,
|
|
|
|
oldRootShadowNode,
|
|
|
|
newRootShadowNode,
|
|
|
|
-1
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
calculateMutationInstructions(
|
|
|
|
instructions,
|
|
|
|
oldRootShadowNode,
|
|
|
|
oldRootShadowNode->getChildren(),
|
|
|
|
newRootShadowNode->getChildren()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-04-10 19:45:59 +00:00
|
|
|
} // namespace react
|
|
|
|
} // namespace facebook
|