Skip to content

Commit e8ec1cb

Browse files
sherginfacebook-github-bot
authored andcommitted
Fabric: The diffing algorithm does not use source nodes anymore
Summary: @public ... and it's as efficient as it was before. The previous version of the algorithm used `sourceNode` reference to know the previous state of the node to call the algorithm recursively. That wasn't so good because of several reasons: - It was fragile because we had two different sources of the truth of the "previous state of the tree": committed tree and source node pointer; - We had to store weak pointers to source nodes inside cloned nodes. That is not free in terms of performance; - The old approach introduced a constraint that all previously used and now reinserted nodes must be cloned to update source node (otherwise, the algorithm would regenerate instructions recreating already existing subtrees); - That cloning required access to `isSealed` flag which is supposed to be a debug-only thing (that actually affects performance and must be compile-out for release builds). The new approach compares nodes with same react tag and naturally cloning-artifacts resilient. Yes, the new approach uses a map of inserted nodes, but the previous one already had it (otherwise there is no way to tell which nodes should be "deleted"). And anyway, this is a very little map that exists for a very little period of time. Reviewed By: mdvacca Differential Revision: D8709953 fbshipit-source-id: 027abb326cf45f00f7bb0bbd7c4e612578268c66
1 parent e0e9c15 commit e8ec1cb

File tree

1 file changed

+41
-24
lines changed

1 file changed

+41
-24
lines changed

Diff for: ReactCommon/fabric/uimanager/Differentiator.cpp

+41-24
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ static void calculateMutationInstructions(
3030
return;
3131
}
3232

33-
std::unordered_set<Tag> insertedTags;
33+
std::unordered_map<Tag, SharedShadowNode> insertedNodes;
3434
int index = 0;
3535

3636
TreeMutationInstructionList createInstructions = {};
@@ -43,8 +43,8 @@ static void calculateMutationInstructions(
4343

4444
// Stage 1: Collectings Updates
4545
for (index = 0; index < oldChildNodes->size() && index < newChildNodes->size(); index++) {
46-
SharedShadowNode oldChildNode = oldChildNodes->at(index);
47-
SharedShadowNode newChildNode = newChildNodes->at(index);
46+
const auto &oldChildNode = oldChildNodes->at(index);
47+
const auto &newChildNode = newChildNodes->at(index);
4848

4949
if (oldChildNode->getTag() != newChildNode->getTag()) {
5050
// Totally different nodes, updating is impossible.
@@ -74,7 +74,7 @@ static void calculateMutationInstructions(
7474

7575
// Stage 2: Collectings Insertions
7676
for (; index < newChildNodes->size(); index++) {
77-
SharedShadowNode newChildNode = newChildNodes->at(index);
77+
const auto &newChildNode = newChildNodes->at(index);
7878

7979
insertInstructions.push_back(
8080
TreeMutationInstruction::Insert(
@@ -84,23 +84,12 @@ static void calculateMutationInstructions(
8484
)
8585
);
8686

87-
insertedTags.insert(newChildNode->getTag());
88-
89-
SharedShadowNode newChildSourceNode = newChildNode->getSourceNode();
90-
SharedShadowNodeSharedList newChildSourceChildNodes =
91-
newChildSourceNode ? newChildSourceNode->getChildren() : ShadowNode::emptySharedShadowNodeSharedList();
92-
93-
calculateMutationInstructions(
94-
*(newChildNode->getChildren()->size() ? &downwardInstructions : &destructionDownwardInstructions),
95-
newChildNode,
96-
newChildSourceChildNodes,
97-
newChildNode->getChildren()
98-
);
87+
insertedNodes.insert({newChildNode->getTag(), newChildNode});
9988
}
10089

10190
// Stage 3: Collectings Deletions and Removals
10291
for (index = lastIndexAfterFirstStage; index < oldChildNodes->size(); index++) {
103-
SharedShadowNode oldChildNode = oldChildNodes->at(index);
92+
const auto &oldChildNode = oldChildNodes->at(index);
10493

10594
// Even if the old node was (re)inserted, we have to generate `remove`
10695
// instruction.
@@ -112,32 +101,53 @@ static void calculateMutationInstructions(
112101
)
113102
);
114103

115-
auto numberOfRemovedTags = insertedTags.erase(oldChildNode->getTag());
116-
assert(numberOfRemovedTags == 0 || numberOfRemovedTags == 1);
104+
const auto &it = insertedNodes.find(oldChildNode->getTag());
117105

118-
if (numberOfRemovedTags == 0) {
119-
// The old node was *not* (re)inserted,
120-
// so we have to generate `delete` instruction and apply the algorithm
106+
if (it == insertedNodes.end()) {
107+
// The old node was *not* (re)inserted.
108+
// We have to generate `delete` instruction and apply the algorithm
121109
// recursively.
122110
deleteInstructions.push_back(
123111
TreeMutationInstruction::Delete(
124112
oldChildNode
125113
)
126114
);
127115

116+
// We also have to call the algorithm recursively to clean up the entire
117+
// subtree starting from the removed node.
128118
calculateMutationInstructions(
129119
destructionDownwardInstructions,
130120
oldChildNode,
131121
oldChildNode->getChildren(),
132122
ShadowNode::emptySharedShadowNodeSharedList()
133123
);
124+
} else {
125+
// The old node *was* (re)inserted.
126+
// We have to call the algorithm recursively if the inserted node
127+
// is *not* the same as removed one.
128+
const auto &newChildNode = it->second;
129+
if (newChildNode != oldChildNode) {
130+
calculateMutationInstructions(
131+
*(newChildNode->getChildren()->size() ? &downwardInstructions : &destructionDownwardInstructions),
132+
newChildNode,
133+
oldChildNode->getChildren(),
134+
newChildNode->getChildren()
135+
);
136+
}
137+
138+
// In any case we have to remove the node from `insertedNodes` as
139+
// indication that the node was actually removed (which means that
140+
// the node existed before), hence we don't have to generate
141+
// `create` instruction.
142+
insertedNodes.erase(it);
134143
}
135144
}
136145

137146
// Stage 4: Collectings Creations
138147
for (index = lastIndexAfterFirstStage; index < newChildNodes->size(); index++) {
139-
SharedShadowNode newChildNode = newChildNodes->at(index);
140-
if (insertedTags.find(newChildNode->getTag()) == insertedTags.end()) {
148+
const auto &newChildNode = newChildNodes->at(index);
149+
150+
if (insertedNodes.find(newChildNode->getTag()) == insertedNodes.end()) {
141151
// The new node was (re)inserted, so there is no need to create it.
142152
continue;
143153
}
@@ -147,6 +157,13 @@ static void calculateMutationInstructions(
147157
newChildNode
148158
)
149159
);
160+
161+
calculateMutationInstructions(
162+
downwardInstructions,
163+
newChildNode,
164+
ShadowNode::emptySharedShadowNodeSharedList(),
165+
newChildNode->getChildren()
166+
);
150167
}
151168

152169
// All instructions in an optimal order:

0 commit comments

Comments
 (0)