Skip to content

Commit 1e4d8d9

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
Core/Differ: detect and optimize reparenting
Summary: # Summary In previous diffs earlier in 2020, we made changes to detect and optimize reordering of views when the order of views changed underneath the same parent. However, until now we have ignored reparenting and there's evidence of issues because of that. Because Fabric flattens views more aggressively, reparenting is also marginally more likely to happen. This diff introduces a very general Reparenting detection. It will work with view flattening/unflattening, as well as tree grafting - subtrees moved to entirely different parts of the tree, not just a single parent disappearing or reappearing because of flattening/unflattening. There is also another consideration: previously, we were generating strictly too many Create+Delete operations that were redundant and could cause consistency issues, crashes, or bugs on platforms that do not handle that gracefully - especially since the ordering of the Create+Delete is not guaranteed (a reparented view could be created "first" and then the differ could later issue a "delete" for the same view). Intuition behind how it works: we know the cases where we can detect reparenting: it's when nodes are *not* matched up with another node from the other tree, and we're either trying to delete an entire subtree, or create an entire subtree. For perf reasons, we generate whatever set of operations comes first (say, we generate all the Delete and Remove instructions) and take note in the `ReparentingMetadata` data-structure that Delete and/or Remove have been performed for each tag (if ordering is different, we do the same for Create+Insert if those come first). Then if we later detect a corresponding subtree creation/deletion, we don't generate those mutations and we mark the previous mutations for deletion. This incurs some map lookup cost, but this is only wasteful for commits where a large tree is deleted and a large tree is created, without reparenting. We may be able to improve perf further for certain edge-cases in the future. # Why can't we solve this in JS? Two things: 1. We certainly can avoid reparenting situations in JS, but it's trickier than before because of Fabric's view flattening logic - product engineers would have to think much harder about how to prevent reparenting in the general case. 2. In the case of specific views like BottomSheet that may crash if they're reparented, the solution is to make sure that the BottomSheet and the first child of the BottomSheet is never memoized, so that lifecycle functions and render are called more often; and that in every render, the BottomSheet manually clones its child, so that when the Views are recreated, the child of the BottomSheet has a tag and is an entirely different instance. This is certainly possible to do but feels like an onerous requirement for product teams, and it could be challenging to track down every specific BottomSheet that is memoized and/or hoist them higher in the view hierarchy so they're not reparented as often. Reviewed By: shergin Differential Revision: D23123575 fbshipit-source-id: 2fa7e1f026f87b6f0c60cad469a3ba85cdc234de
1 parent 4720ad9 commit 1e4d8d9

10 files changed

+852
-97
lines changed

ReactCommon/react/renderer/mounting/Differentiator.cpp

+404-13
Large diffs are not rendered by default.

ReactCommon/react/renderer/mounting/Differentiator.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ enum class DifferentiatorMode { Classic, OptimizedMoves };
2222
*/
2323
ShadowViewMutationList calculateShadowViewMutations(
2424
ShadowNode const &oldRootShadowNode,
25-
ShadowNode const &newRootShadowNode);
25+
ShadowNode const &newRootShadowNode,
26+
bool enableReparentingDetection = false);
2627

2728
/*
2829
* Generates a list of `ShadowViewNodePair`s that represents a layer of a

ReactCommon/react/renderer/mounting/MountingCoordinator.cpp

+7-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ namespace react {
2121

2222
MountingCoordinator::MountingCoordinator(
2323
ShadowTreeRevision baseRevision,
24-
std::weak_ptr<MountingOverrideDelegate const> delegate)
24+
std::weak_ptr<MountingOverrideDelegate const> delegate,
25+
bool enableReparentingDetection)
2526
: surfaceId_(baseRevision.getRootShadowNode().getSurfaceId()),
2627
baseRevision_(baseRevision),
2728
mountingOverrideDelegate_(delegate),
28-
telemetryController_(*this) {
29+
telemetryController_(*this),
30+
enableReparentingDetection_(enableReparentingDetection) {
2931
#ifdef RN_SHADOW_TREE_INTROSPECTION
3032
stubViewTree_ = stubViewTreeFromShadowNode(baseRevision_.getRootShadowNode());
3133
#endif
@@ -139,7 +141,9 @@ better::optional<MountingTransaction> MountingCoordinator::pullTransaction()
139141
telemetry.willDiff();
140142
if (lastRevision_.hasValue()) {
141143
diffMutations = calculateShadowViewMutations(
142-
baseRevision_.getRootShadowNode(), lastRevision_->getRootShadowNode());
144+
baseRevision_.getRootShadowNode(),
145+
lastRevision_->getRootShadowNode(),
146+
enableReparentingDetection_);
143147
}
144148
telemetry.didDiff();
145149

ReactCommon/react/renderer/mounting/MountingCoordinator.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ class MountingCoordinator final {
4141
*/
4242
MountingCoordinator(
4343
ShadowTreeRevision baseRevision,
44-
std::weak_ptr<MountingOverrideDelegate const> delegate);
44+
std::weak_ptr<MountingOverrideDelegate const> delegate,
45+
bool enableReparentingDetection = false);
4546

4647
/*
4748
* Returns the id of the surface that the coordinator belongs to.
@@ -109,6 +110,8 @@ class MountingCoordinator final {
109110

110111
TelemetryController telemetryController_;
111112

113+
bool enableReparentingDetection_{false}; // temporary
114+
112115
#ifdef RN_SHADOW_TREE_INTROSPECTION
113116
void validateTransactionAgainstStubViewTree(
114117
ShadowViewMutationList const &mutations,

ReactCommon/react/renderer/mounting/ShadowTree.cpp

+8-3
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,11 @@ ShadowTree::ShadowTree(
222222
LayoutContext const &layoutContext,
223223
RootComponentDescriptor const &rootComponentDescriptor,
224224
ShadowTreeDelegate const &delegate,
225-
std::weak_ptr<MountingOverrideDelegate const> mountingOverrideDelegate)
226-
: surfaceId_(surfaceId), delegate_(delegate) {
225+
std::weak_ptr<MountingOverrideDelegate const> mountingOverrideDelegate,
226+
bool enableReparentingDetection)
227+
: surfaceId_(surfaceId),
228+
delegate_(delegate),
229+
enableReparentingDetection_(enableReparentingDetection) {
227230
const auto noopEventEmitter = std::make_shared<const ViewEventEmitter>(
228231
nullptr, -1, std::shared_ptr<const EventDispatcher>());
229232

@@ -241,7 +244,9 @@ ShadowTree::ShadowTree(
241244
family));
242245

243246
mountingCoordinator_ = std::make_shared<MountingCoordinator const>(
244-
ShadowTreeRevision{rootShadowNode_, 0, {}}, mountingOverrideDelegate);
247+
ShadowTreeRevision{rootShadowNode_, 0, {}},
248+
mountingOverrideDelegate,
249+
enableReparentingDetection);
245250
}
246251

247252
ShadowTree::~ShadowTree() {

ReactCommon/react/renderer/mounting/ShadowTree.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ class ShadowTree final {
4040
LayoutContext const &layoutContext,
4141
RootComponentDescriptor const &rootComponentDescriptor,
4242
ShadowTreeDelegate const &delegate,
43-
std::weak_ptr<MountingOverrideDelegate const> mountingOverrideDelegate);
43+
std::weak_ptr<MountingOverrideDelegate const> mountingOverrideDelegate,
44+
bool enableReparentingDetection = false);
4445

4546
~ShadowTree();
4647

@@ -87,6 +88,9 @@ class ShadowTree final {
8788
void setEnableNewStateReconciliation(bool value) {
8889
enableNewStateReconciliation_ = value;
8990
}
91+
void setEnableReparentingDetection(bool value) {
92+
enableReparentingDetection_ = value;
93+
}
9094

9195
private:
9296
RootShadowNode::Unshared cloneRootShadowNode(
@@ -106,6 +110,7 @@ class ShadowTree final {
106110
0}; // Protected by `commitMutex_`.
107111
MountingCoordinator::Shared mountingCoordinator_;
108112
bool enableNewStateReconciliation_{false};
113+
bool enableReparentingDetection_{false};
109114
};
110115

111116
} // namespace react

ReactCommon/react/renderer/mounting/ShadowViewMutation.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ struct ShadowViewMutation final {
6262

6363
#pragma mark - Type
6464

65-
enum Type { Create, Delete, Insert, Remove, Update };
65+
enum Type { Create = 1, Delete = 2, Insert = 4, Remove = 8, Update = 16 };
6666

6767
#pragma mark - Fields
6868

0 commit comments

Comments
 (0)