react-native-reanimated 4.5.3 → 4.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Common/cpp/reanimated/Fabric/ShadowTreeCloner.cpp +1 -1
- package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h +5 -5
- package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +84 -3
- package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h +7 -4
- package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp +120 -27
- package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h +36 -18
- package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +18 -9
- package/apple/reanimated/apple/REANodesManager.mm +5 -1
- package/lib/module/platform-specific/jsVersion.js +1 -1
- package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
- package/package.json +1 -1
- package/src/platform-specific/jsVersion.ts +1 -1
|
@@ -25,7 +25,7 @@ mergeProps(const ShadowNode &shadowNode, const PropsMap &propsMap, const ShadowN
|
|
|
25
25
|
if (propsVector.size() > 1) {
|
|
26
26
|
folly::dynamic newPropsDynamic = folly::dynamic::object;
|
|
27
27
|
for (const auto &props : propsVector) {
|
|
28
|
-
newPropsDynamic = folly::dynamic::merge(props.operator folly::dynamic()
|
|
28
|
+
newPropsDynamic = folly::dynamic::merge(newPropsDynamic, props.operator folly::dynamic());
|
|
29
29
|
}
|
|
30
30
|
return shadowNode.getComponentDescriptor().cloneProps(propsParserContext, newProps, RawProps(newPropsDynamic));
|
|
31
31
|
}
|
|
@@ -62,11 +62,11 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
|
|
62
62
|
const SharedComponentDescriptorRegistry &componentDescriptorRegistry,
|
|
63
63
|
const std::shared_ptr<const ContextContainer> &contextContainer,
|
|
64
64
|
jsi::Runtime &uiRuntime,
|
|
65
|
-
const std::shared_ptr<UIScheduler> &uiScheduler
|
|
65
|
+
const std::shared_ptr<UIScheduler> &uiScheduler,
|
|
66
|
+
const std::shared_ptr<facebook::react::UIManager> &uiManager
|
|
66
67
|
#ifdef ANDROID
|
|
67
68
|
,
|
|
68
69
|
const PreserveMountedTagsFunction &filterUnmountedTagsFunction,
|
|
69
|
-
const std::shared_ptr<facebook::react::UIManager> &uiManager,
|
|
70
70
|
const std::shared_ptr<facebook::react::CallInvoker> &jsInvoker
|
|
71
71
|
#endif
|
|
72
72
|
)
|
|
@@ -74,11 +74,11 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
|
|
74
74
|
contextContainer_(contextContainer),
|
|
75
75
|
componentDescriptorRegistry_(componentDescriptorRegistry),
|
|
76
76
|
uiRuntime_(uiRuntime),
|
|
77
|
-
uiScheduler_(uiScheduler)
|
|
77
|
+
uiScheduler_(uiScheduler),
|
|
78
|
+
uiManager_(uiManager)
|
|
78
79
|
#ifdef ANDROID
|
|
79
80
|
,
|
|
80
81
|
preserveMountedTags_(filterUnmountedTagsFunction),
|
|
81
|
-
uiManager_(uiManager),
|
|
82
82
|
jsInvoker_(jsInvoker)
|
|
83
83
|
#endif
|
|
84
84
|
{
|
|
@@ -98,10 +98,10 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
|
|
|
98
98
|
SharedComponentDescriptorRegistry componentDescriptorRegistry_;
|
|
99
99
|
jsi::Runtime &uiRuntime_;
|
|
100
100
|
const std::shared_ptr<UIScheduler> uiScheduler_;
|
|
101
|
+
std::shared_ptr<facebook::react::UIManager> uiManager_;
|
|
101
102
|
PreserveMountedTagsFunction preserveMountedTags_;
|
|
102
103
|
|
|
103
104
|
#ifdef ANDROID
|
|
104
|
-
std::shared_ptr<facebook::react::UIManager> uiManager_;
|
|
105
105
|
std::shared_ptr<facebook::react::CallInvoker> jsInvoker_;
|
|
106
106
|
|
|
107
107
|
void restoreOpacityInCaseOfFlakyEnteringAnimation(SurfaceId surfaceId) const;
|
|
@@ -30,6 +30,8 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Experimental::pullTrans
|
|
|
30
30
|
const std::vector<std::shared_ptr<MutationNode>> roots;
|
|
31
31
|
const bool isInTransition = static_cast<bool>(transitionState_);
|
|
32
32
|
|
|
33
|
+
reconcileContradictedRemovals(mutations, filteredMutations);
|
|
34
|
+
|
|
33
35
|
if (isInTransition) {
|
|
34
36
|
updateLightTree(propsParserContext, mutations, filteredMutations);
|
|
35
37
|
handleProgressTransition(filteredMutations, mutations, propsParserContext, surfaceId);
|
|
@@ -107,6 +109,67 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Experimental::pullTrans
|
|
|
107
109
|
return MountingTransaction{surfaceId, transactionNumber, std::move(filteredMutations), telemetry};
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
// If React re-creates or re-inserts a tag whose exiting removal we are still
|
|
113
|
+
// withholding, it has contradicted that withheld removal. Flush it now instead
|
|
114
|
+
// of letting the stale node linger: updateLightTree would overwrite its
|
|
115
|
+
// lightNodes_ entry (the "LightNode already exists" assert is compiled out in
|
|
116
|
+
// release), orphaning the still-mounted exiting view, and the eventual
|
|
117
|
+
// endLayoutAnimation would then remove the wrong, live view and crash the
|
|
118
|
+
// mounting layer.
|
|
119
|
+
//
|
|
120
|
+
// This must run before updateLightTree (so the tag is re-registered cleanly)
|
|
121
|
+
// and before addOngoingAnimations (which would otherwise emit an Update for a
|
|
122
|
+
// tag we are about to Delete this frame).
|
|
123
|
+
void LayoutAnimationsProxy_Experimental::reconcileContradictedRemovals(
|
|
124
|
+
const ShadowViewMutationList &mutations,
|
|
125
|
+
ShadowViewMutationList &filteredMutations) const {
|
|
126
|
+
for (const auto &mutation : mutations) {
|
|
127
|
+
if (mutation.type != ShadowViewMutation::Type::Create && mutation.type != ShadowViewMutation::Type::Insert) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const auto tag = mutation.newChildShadowView.tag;
|
|
131
|
+
std::shared_ptr<LightNode> node;
|
|
132
|
+
if (const auto it = lightNodes_.find(tag); it != lightNodes_.end() && it->second->state != UNDEFINED) {
|
|
133
|
+
node = it->second;
|
|
134
|
+
lightNodes_.erase(it);
|
|
135
|
+
if (node->state == DELETED) {
|
|
136
|
+
// already unmounted — only the stale map entry had to go
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
// A settled exiting view (state DEAD) has already left lightNodes_ but is
|
|
141
|
+
// still mounted, awaiting the deadNodes cleanup in handleRemovals. That
|
|
142
|
+
// cleanup runs at the end of the transaction — after this Create would
|
|
143
|
+
// have re-registered the tag in the mounting layer's view registry — so
|
|
144
|
+
// it must be flushed now instead.
|
|
145
|
+
const auto deadIt = std::find_if(
|
|
146
|
+
deadNodes.begin(), deadNodes.end(), [tag](const auto &deadNode) { return deadNode->current.tag == tag; });
|
|
147
|
+
if (deadIt == deadNodes.end()) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
node = *deadIt;
|
|
151
|
+
deadNodes.erase(deadIt);
|
|
152
|
+
if (node->state == DELETED) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Flush the withheld removal for this tag (and its withheld subtree) right
|
|
157
|
+
// now, mirroring the deadNodes cleanup in handleRemovals.
|
|
158
|
+
const auto parent = node->parent.lock();
|
|
159
|
+
react_native_assert(parent && "Parent node is nullptr");
|
|
160
|
+
if (!parent) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const auto index = parent->removeChild(node);
|
|
164
|
+
react_native_assert(index != -1 && "Exiting node not found");
|
|
165
|
+
if (index == -1) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
endAnimationsRecursively(node, index, filteredMutations);
|
|
169
|
+
maybeDropAncestors(parent, filteredMutations);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
110
173
|
bool LayoutAnimationsProxy_Experimental::shouldOverridePullTransaction() const {
|
|
111
174
|
// we need to listen to every possible mutation to keep the light tree updated
|
|
112
175
|
return true;
|
|
@@ -305,11 +368,18 @@ std::optional<SurfaceId> LayoutAnimationsProxy_Experimental::endLayoutAnimation(
|
|
|
305
368
|
return surfaceId;
|
|
306
369
|
}
|
|
307
370
|
|
|
308
|
-
auto
|
|
309
|
-
|
|
371
|
+
const auto nodeIt = lightNodes_.find(tag);
|
|
372
|
+
// the withheld removal may have already been flushed (e.g. reconciled after
|
|
373
|
+
// React re-created the tag) — the assert alone is compiled out in release
|
|
374
|
+
// and operator[] would insert a null node here
|
|
375
|
+
if (nodeIt == lightNodes_.end() || !nodeIt->second) {
|
|
376
|
+
react_native_assert(false && "LightNode not found");
|
|
377
|
+
return surfaceId;
|
|
378
|
+
}
|
|
379
|
+
auto node = nodeIt->second;
|
|
310
380
|
|
|
311
381
|
node->state = DEAD;
|
|
312
|
-
lightNodes_.erase(
|
|
382
|
+
lightNodes_.erase(nodeIt);
|
|
313
383
|
deadNodes.insert(node);
|
|
314
384
|
|
|
315
385
|
return surfaceId;
|
|
@@ -349,6 +419,7 @@ void LayoutAnimationsProxy_Experimental::handleRemovals(
|
|
|
349
419
|
parent->children.push_back(node);
|
|
350
420
|
if (node->state == UNDEFINED) {
|
|
351
421
|
node->state = WAITING;
|
|
422
|
+
lightNodes_[node->current.tag] = node;
|
|
352
423
|
}
|
|
353
424
|
} else {
|
|
354
425
|
maybeCancelAnimation(node->current.tag);
|
|
@@ -432,6 +503,10 @@ void LayoutAnimationsProxy_Experimental::endAnimationsRecursively(
|
|
|
432
503
|
ShadowViewMutationList &mutations) const {
|
|
433
504
|
maybeCancelAnimation(node->current.tag);
|
|
434
505
|
node->state = DELETED;
|
|
506
|
+
// drop the tag mapping unless it was already re-registered for a new node
|
|
507
|
+
if (const auto it = lightNodes_.find(node->current.tag); it != lightNodes_.end() && it->second == node) {
|
|
508
|
+
lightNodes_.erase(it);
|
|
509
|
+
}
|
|
435
510
|
// iterate from the end, so that children
|
|
436
511
|
// with higher indices appear first in the mutations list
|
|
437
512
|
|
|
@@ -463,6 +538,9 @@ void LayoutAnimationsProxy_Experimental::maybeDropAncestors(
|
|
|
463
538
|
react_native_assert(index != -1 && "Child node not found");
|
|
464
539
|
|
|
465
540
|
node->state = DELETED;
|
|
541
|
+
if (const auto it = lightNodes_.find(node->current.tag); it != lightNodes_.end() && it->second == node) {
|
|
542
|
+
lightNodes_.erase(it);
|
|
543
|
+
}
|
|
466
544
|
maybeCancelAnimation(node->current.tag);
|
|
467
545
|
cleanupMutations.push_back(ShadowViewMutation::RemoveMutation(parent->current.tag, node->current, index));
|
|
468
546
|
cleanupMutations.push_back(ShadowViewMutation::DeleteMutation(node->current));
|
|
@@ -515,6 +593,9 @@ bool LayoutAnimationsProxy_Experimental::startAnimationsRecursively(
|
|
|
515
593
|
mutations.push_back(ShadowViewMutation::DeleteMutation(subNode->current));
|
|
516
594
|
} else {
|
|
517
595
|
subNode->state = WAITING;
|
|
596
|
+
// register withheld subtree members, so that reconcileContradictedRemovals
|
|
597
|
+
// can find them when React re-creates their tags
|
|
598
|
+
lightNodes_[subNode->current.tag] = subNode;
|
|
518
599
|
}
|
|
519
600
|
}
|
|
520
601
|
|
|
@@ -67,11 +67,11 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon,
|
|
|
67
67
|
const SharedComponentDescriptorRegistry &componentDescriptorRegistry,
|
|
68
68
|
const std::shared_ptr<const ContextContainer> &contextContainer,
|
|
69
69
|
jsi::Runtime &uiRuntime,
|
|
70
|
-
const std::shared_ptr<UIScheduler> &uiScheduler
|
|
70
|
+
const std::shared_ptr<UIScheduler> &uiScheduler,
|
|
71
|
+
const std::shared_ptr<UIManager> &uiManager
|
|
71
72
|
#ifdef ANDROID
|
|
72
73
|
,
|
|
73
74
|
const PreserveMountedTagsFunction &filterUnmountedTagsFunction,
|
|
74
|
-
const std::shared_ptr<UIManager> &uiManager,
|
|
75
75
|
const std::shared_ptr<CallInvoker> &jsInvoker
|
|
76
76
|
#endif
|
|
77
77
|
)
|
|
@@ -80,11 +80,11 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon,
|
|
|
80
80
|
componentDescriptorRegistry,
|
|
81
81
|
contextContainer,
|
|
82
82
|
uiRuntime,
|
|
83
|
-
uiScheduler
|
|
83
|
+
uiScheduler,
|
|
84
|
+
uiManager
|
|
84
85
|
#ifdef ANDROID
|
|
85
86
|
,
|
|
86
87
|
filterUnmountedTagsFunction,
|
|
87
|
-
uiManager,
|
|
88
88
|
jsInvoker
|
|
89
89
|
#endif
|
|
90
90
|
),
|
|
@@ -109,6 +109,9 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon,
|
|
|
109
109
|
const ShadowViewMutationList &mutations,
|
|
110
110
|
ShadowViewMutationList &filteredMutations) const;
|
|
111
111
|
|
|
112
|
+
void reconcileContradictedRemovals(const ShadowViewMutationList &mutations, ShadowViewMutationList &filteredMutations)
|
|
113
|
+
const;
|
|
114
|
+
|
|
112
115
|
void handleSharedTransitionsStart(
|
|
113
116
|
const std::shared_ptr<LightNode> &afterTopScreen,
|
|
114
117
|
const std::shared_ptr<LightNode> &beforeTopScreen,
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#include <reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h>
|
|
2
2
|
|
|
3
3
|
#include <react/debug/react_native_assert.h>
|
|
4
|
+
#include <react/renderer/mounting/ShadowTree.h>
|
|
4
5
|
#include <react/renderer/mounting/ShadowViewMutation.h>
|
|
5
6
|
|
|
6
7
|
#include <memory>
|
|
7
8
|
#include <ranges>
|
|
8
9
|
#include <set>
|
|
9
10
|
#include <string>
|
|
11
|
+
#include <thread>
|
|
10
12
|
#include <unordered_map>
|
|
11
13
|
#include <unordered_set>
|
|
12
14
|
#include <utility>
|
|
@@ -33,7 +35,8 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Legacy::pullTransaction
|
|
|
33
35
|
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
34
36
|
PropsParserContext propsParserContext{surfaceId, *contextContainer_};
|
|
35
37
|
ShadowViewMutationList filteredMutations;
|
|
36
|
-
auto &
|
|
38
|
+
auto &surfaceCtx = getSurfaceContext(surfaceId);
|
|
39
|
+
auto &deadNodes = surfaceCtx.deadNodes;
|
|
37
40
|
|
|
38
41
|
std::vector<std::shared_ptr<MutationNode>> roots;
|
|
39
42
|
std::unordered_map<Tag, Tag> movedViews;
|
|
@@ -60,17 +63,47 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Legacy::pullTransaction
|
|
|
60
63
|
|
|
61
64
|
parseRemoveMutations(movedViews, mutations, roots);
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
// We recognize dropped surfaces by the presence of a Remove mutation for a root child. This can produce false
|
|
67
|
+
// positives. Ideal solution will be to introduce an appropriate API in RN
|
|
68
|
+
auto surfaceDropped = false;
|
|
69
|
+
const auto removesRootChildren = std::ranges::any_of(mutations, [surfaceId](const auto &mutation) {
|
|
70
|
+
return mutation.type == ShadowViewMutation::Remove && mutation.parentTag == surfaceId;
|
|
71
|
+
});
|
|
72
|
+
if (removesRootChildren) {
|
|
73
|
+
surfaceDropped = surfacesToRemove_.erase(surfaceId) != 0;
|
|
74
|
+
}
|
|
75
|
+
const bool flushDeadNodes = shouldFlushDeadNodes(surfaceDropped);
|
|
76
|
+
handleRemovals(filteredMutations, roots, deadNodes, surfaceDropped, flushDeadNodes);
|
|
77
|
+
#ifdef ANDROID
|
|
78
|
+
maybeScheduleCleanupPull(surfaceCtx, surfaceId, flushDeadNodes);
|
|
79
|
+
#endif // ANDROID
|
|
66
80
|
|
|
67
81
|
handleUpdatesAndEnterings(filteredMutations, movedViews, mutations, propsParserContext, surfaceId);
|
|
68
82
|
|
|
69
83
|
addOngoingAnimations(surfaceId, filteredMutations);
|
|
70
84
|
|
|
85
|
+
dropUpdatesForDeletedViews(filteredMutations);
|
|
86
|
+
|
|
71
87
|
return MountingTransaction{surfaceId, transactionNumber, std::move(filteredMutations), telemetry};
|
|
72
88
|
}
|
|
73
89
|
|
|
90
|
+
// The LayoutAnimationDriver can pair a final keyframe update with the withheld
|
|
91
|
+
// Remove/Delete it replays in the same transaction; we emit removals first, so
|
|
92
|
+
// the update would reach the mounting layer after its view was deleted.
|
|
93
|
+
void LayoutAnimationsProxy_Legacy::dropUpdatesForDeletedViews(ShadowViewMutationList &filteredMutations) const {
|
|
94
|
+
std::unordered_set<Tag> deletedTags;
|
|
95
|
+
for (const auto &mutation : filteredMutations) {
|
|
96
|
+
if (mutation.type == ShadowViewMutation::Delete) {
|
|
97
|
+
deletedTags.insert(mutation.oldChildShadowView.tag);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!deletedTags.empty()) {
|
|
101
|
+
std::erase_if(filteredMutations, [&deletedTags](const auto &mutation) {
|
|
102
|
+
return mutation.type == ShadowViewMutation::Update && deletedTags.contains(mutation.newChildShadowView.tag);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
74
107
|
// If React re-creates or re-inserts a tag whose exiting removal we are still
|
|
75
108
|
// withholding, it has contradicted that withheld Remove/Delete. Flush it now
|
|
76
109
|
// instead of letting it fire later against a stale hierarchy (which would
|
|
@@ -83,7 +116,7 @@ void LayoutAnimationsProxy_Legacy::reconcileContradictedRemovals(
|
|
|
83
116
|
ShadowViewMutationList &mutations,
|
|
84
117
|
ShadowViewMutationList &filteredMutations,
|
|
85
118
|
SurfaceId surfaceId) const {
|
|
86
|
-
auto &
|
|
119
|
+
auto &deadNodes = getSurfaceContext(surfaceId).deadNodes;
|
|
87
120
|
for (auto &mutation : mutations) {
|
|
88
121
|
if (mutation.type != ShadowViewMutation::Type::Create && mutation.type != ShadowViewMutation::Type::Insert) {
|
|
89
122
|
continue;
|
|
@@ -105,6 +138,45 @@ void LayoutAnimationsProxy_Legacy::reconcileContradictedRemovals(
|
|
|
105
138
|
}
|
|
106
139
|
}
|
|
107
140
|
|
|
141
|
+
// On android mutations that alter the view hierarchy are only produced on the JS thread (the push model), so to not
|
|
142
|
+
// race with those, we apply the dead nodes cleanup only on the JS thread, unless there is a surface drop, in which case
|
|
143
|
+
// we can safely cleanup on the UI thread since the surface is gone and no more mutations will be produced for it.
|
|
144
|
+
bool LayoutAnimationsProxy_Legacy::shouldFlushDeadNodes([[maybe_unused]] const bool surfaceDropped) const {
|
|
145
|
+
#ifdef ANDROID
|
|
146
|
+
return surfaceDropped || std::this_thread::get_id() != uiThreadId_;
|
|
147
|
+
#else
|
|
148
|
+
return true;
|
|
149
|
+
#endif
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#ifdef ANDROID
|
|
153
|
+
// We schedule a pullTransaction call to happen on the JS thread so it can safely remove dead nodes after exiting
|
|
154
|
+
// finished
|
|
155
|
+
void LayoutAnimationsProxy_Legacy::maybeScheduleCleanupPull(
|
|
156
|
+
SurfaceContext &surfaceCtx,
|
|
157
|
+
const SurfaceId surfaceId,
|
|
158
|
+
const bool flushedDeadNodes) const {
|
|
159
|
+
if (flushedDeadNodes) {
|
|
160
|
+
surfaceCtx.cleanupPullScheduled = false;
|
|
161
|
+
} else if (!surfaceCtx.deadNodes.empty() && !surfaceCtx.cleanupPullScheduled) {
|
|
162
|
+
surfaceCtx.cleanupPullScheduled = true;
|
|
163
|
+
scheduleDeferredCleanupPull(surfaceId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
void LayoutAnimationsProxy_Legacy::scheduleDeferredCleanupPull(SurfaceId surfaceId) const {
|
|
168
|
+
const std::weak_ptr<UIManager> weakUiManager = uiManager_;
|
|
169
|
+
jsInvoker_->invokeAsync([weakUiManager, surfaceId](jsi::Runtime &) {
|
|
170
|
+
auto uiManager = weakUiManager.lock();
|
|
171
|
+
if (!uiManager) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
uiManager->getShadowTreeRegistry().visit(
|
|
175
|
+
surfaceId, [](ShadowTree const &shadowTree) { shadowTree.notifyDelegatesOfUpdates(); });
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
#endif
|
|
179
|
+
|
|
108
180
|
std::optional<SurfaceId> LayoutAnimationsProxy_Legacy::progressLayoutAnimation(int tag, const jsi::Object &newStyle) {
|
|
109
181
|
#ifdef LAYOUT_ANIMATIONS_LOGS
|
|
110
182
|
LOG(INFO) << "progress layout animation for tag " << tag << std::endl;
|
|
@@ -169,7 +241,7 @@ std::optional<SurfaceId> LayoutAnimationsProxy_Legacy::endLayoutAnimation(int ta
|
|
|
169
241
|
}
|
|
170
242
|
auto mutationNode = std::static_pointer_cast<MutationNode>(node);
|
|
171
243
|
mutationNode->state = ExitingState_Legacy::DEAD;
|
|
172
|
-
auto &
|
|
244
|
+
auto &deadNodes = getSurfaceContext(surfaceId).deadNodes;
|
|
173
245
|
deadNodes.insert(mutationNode);
|
|
174
246
|
|
|
175
247
|
return surfaceId;
|
|
@@ -281,12 +353,13 @@ void LayoutAnimationsProxy_Legacy::handleRemovals(
|
|
|
281
353
|
ShadowViewMutationList &filteredMutations,
|
|
282
354
|
std::vector<std::shared_ptr<MutationNode>> &roots,
|
|
283
355
|
std::unordered_set<std::shared_ptr<MutationNode>> &deadNodes,
|
|
284
|
-
bool
|
|
356
|
+
bool surfaceDropped,
|
|
357
|
+
bool flushDeadNodes) const {
|
|
285
358
|
// iterate from the end, so that children
|
|
286
359
|
// with higher indices appear first in the mutations list
|
|
287
360
|
for (auto it = roots.rbegin(); it != roots.rend(); it++) {
|
|
288
361
|
auto &node = *it;
|
|
289
|
-
if (!startAnimationsRecursively(node, true,
|
|
362
|
+
if (!startAnimationsRecursively(node, true, !surfaceDropped, false, filteredMutations)) {
|
|
290
363
|
filteredMutations.push_back(node->mutation);
|
|
291
364
|
node->unflattenedParent->removeChildFromUnflattenedTree(node); //???
|
|
292
365
|
if (node->state != ExitingState_Legacy::MOVED) {
|
|
@@ -301,6 +374,10 @@ void LayoutAnimationsProxy_Legacy::handleRemovals(
|
|
|
301
374
|
}
|
|
302
375
|
}
|
|
303
376
|
|
|
377
|
+
if (!flushDeadNodes) {
|
|
378
|
+
// Deferred - the host still has these views mounted, bookkeeping stays.
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
304
381
|
for (const auto &node : deadNodes) {
|
|
305
382
|
if (node->state != ExitingState_Legacy::DELETED) {
|
|
306
383
|
endAnimationsRecursively(node, filteredMutations);
|
|
@@ -713,6 +790,7 @@ void LayoutAnimationsProxy_Legacy::startEnteringAnimation(const int tag, ShadowV
|
|
|
713
790
|
auto &mutex = strongThis->mutex;
|
|
714
791
|
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
715
792
|
#ifdef ANDROID
|
|
793
|
+
strongThis->uiThreadId_ = std::this_thread::get_id();
|
|
716
794
|
if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
|
|
717
795
|
// the view was removed before this start could run
|
|
718
796
|
return;
|
|
@@ -773,12 +851,12 @@ void LayoutAnimationsProxy_Legacy::startExitingAnimation(const int tag, ShadowVi
|
|
|
773
851
|
auto &mutex = strongThis->mutex;
|
|
774
852
|
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
775
853
|
#ifdef ANDROID
|
|
854
|
+
strongThis->uiThreadId_ = std::this_thread::get_id();
|
|
776
855
|
if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
|
|
777
|
-
//
|
|
778
|
-
//
|
|
779
|
-
//
|
|
780
|
-
//
|
|
781
|
-
strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
|
|
856
|
+
// The view was removed before this start could run. Deliberately no
|
|
857
|
+
// clearLayoutAnimationConfig: with tag reuse the config maps already
|
|
858
|
+
// describe the re-created view, and wiping them would leave it mounted
|
|
859
|
+
// at opacity 0. A dead tag's stale configs can never fire again.
|
|
782
860
|
return;
|
|
783
861
|
}
|
|
784
862
|
#endif
|
|
@@ -836,6 +914,7 @@ void LayoutAnimationsProxy_Legacy::startLayoutAnimation(const int tag, const Sha
|
|
|
836
914
|
auto &mutex = strongThis->mutex;
|
|
837
915
|
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
838
916
|
#ifdef ANDROID
|
|
917
|
+
strongThis->uiThreadId_ = std::this_thread::get_id();
|
|
839
918
|
if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
|
|
840
919
|
// the view was removed before this start could run
|
|
841
920
|
return;
|
|
@@ -998,23 +1077,37 @@ inline bool MutationNode::isMutationNode() {
|
|
|
998
1077
|
return true;
|
|
999
1078
|
}
|
|
1000
1079
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
const RawValue &config,
|
|
1006
|
-
const jsi::Value &successCallbackValue,
|
|
1007
|
-
const jsi::Value &failureCallbackValue) const {}
|
|
1008
|
-
|
|
1009
|
-
void LayoutAnimationsProxy_Legacy::setComponentDescriptorRegistry(
|
|
1010
|
-
const SharedComponentDescriptorRegistry &componentDescriptorRegistry) {}
|
|
1080
|
+
void LayoutAnimationsProxy_Legacy::startSurface(const SurfaceId surfaceId) {
|
|
1081
|
+
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
1082
|
+
surfaceContext_.try_emplace(surfaceId);
|
|
1083
|
+
}
|
|
1011
1084
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1085
|
+
SurfaceContext &LayoutAnimationsProxy_Legacy::getSurfaceContext(const SurfaceId surfaceId) const {
|
|
1086
|
+
// startSurface() creates the entry for a surface before any other method
|
|
1087
|
+
// uses it. The proxy does not remove entries during its lifetime. Thus a
|
|
1088
|
+
// missing entry is an initialization bug, not a stopped surface.
|
|
1089
|
+
const auto it = surfaceContext_.find(surfaceId);
|
|
1090
|
+
react_native_assert(it != surfaceContext_.end() && "surface must be registered by startSurface");
|
|
1091
|
+
return it->second;
|
|
1014
1092
|
}
|
|
1015
1093
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1094
|
+
// UIManagerCommitHook
|
|
1095
|
+
|
|
1096
|
+
// Surface teardown commits an empty root (SurfaceHandler::stop) before the
|
|
1097
|
+
// teardown transaction is pulled — mark it so pullTransaction skips exit
|
|
1098
|
+
// animations. Reading the ShadowTreeRegistry here instead would deadlock.
|
|
1099
|
+
RootShadowNode::Unshared LayoutAnimationsProxy_Legacy::shadowTreeWillCommit(
|
|
1100
|
+
const ShadowTree &shadowTree,
|
|
1101
|
+
const RootShadowNode::Shared & /*oldRootShadowNode*/,
|
|
1102
|
+
const RootShadowNode::Unshared &newRootShadowNode) noexcept {
|
|
1103
|
+
const auto surfaceId = shadowTree.getSurfaceId();
|
|
1104
|
+
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
1105
|
+
if (newRootShadowNode->getChildren().empty()) {
|
|
1106
|
+
surfacesToRemove_.insert(surfaceId);
|
|
1107
|
+
} else {
|
|
1108
|
+
surfacesToRemove_.erase(surfaceId);
|
|
1109
|
+
}
|
|
1110
|
+
return newRootShadowNode;
|
|
1018
1111
|
}
|
|
1019
1112
|
|
|
1020
1113
|
} // namespace reanimated
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
#include <react/renderer/componentregistry/ComponentDescriptorFactory.h>
|
|
4
4
|
#include <react/renderer/mounting/MountingOverrideDelegate.h>
|
|
5
5
|
#include <react/renderer/scheduler/Scheduler.h>
|
|
6
|
-
#include <react/renderer/uimanager/UIManagerAnimationDelegate.h>
|
|
7
6
|
#include <react/renderer/uimanager/UIManagerBinding.h>
|
|
7
|
+
#include <react/renderer/uimanager/UIManagerCommitHook.h>
|
|
8
8
|
#include <reanimated/Compat/WorkletsApi.h>
|
|
9
9
|
#include <reanimated/LayoutAnimations/LayoutAnimationsManager.h>
|
|
10
10
|
#include <reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
#include <memory>
|
|
15
15
|
#include <string>
|
|
16
|
+
#include <thread>
|
|
16
17
|
#include <unordered_map>
|
|
17
18
|
#include <unordered_set>
|
|
18
19
|
#include <utility>
|
|
@@ -97,12 +98,17 @@ static inline void mergeAndSwap(
|
|
|
97
98
|
std::swap(A, merged);
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
// Created by startSurface before the proxy can pull for that surface.
|
|
102
|
+
// Entries are never erased, so stray pulls after teardown always find state.
|
|
100
103
|
struct SurfaceContext {
|
|
101
104
|
mutable std::unordered_set<std::shared_ptr<MutationNode>> deadNodes;
|
|
105
|
+
#ifdef ANDROID
|
|
106
|
+
mutable bool cleanupPullScheduled = false;
|
|
107
|
+
#endif
|
|
102
108
|
};
|
|
103
109
|
|
|
104
110
|
struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
|
|
105
|
-
public
|
|
111
|
+
public UIManagerCommitHook,
|
|
106
112
|
public std::enable_shared_from_this<LayoutAnimationsProxy_Legacy> {
|
|
107
113
|
mutable std::unordered_map<Tag, std::shared_ptr<Node>> nodeForTag_;
|
|
108
114
|
mutable std::recursive_mutex mutex;
|
|
@@ -110,17 +116,24 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
|
|
|
110
116
|
mutable std::unordered_map<SurfaceId, SurfaceContext> surfaceContext_;
|
|
111
117
|
mutable std::unordered_map<Tag, int> leastRemoved;
|
|
112
118
|
mutable std::unordered_set<SurfaceId> surfacesToRemove_;
|
|
119
|
+
bool shouldFlushDeadNodes(bool surfaceDropped) const;
|
|
120
|
+
#ifdef ANDROID
|
|
121
|
+
mutable std::thread::id uiThreadId_;
|
|
122
|
+
|
|
123
|
+
void maybeScheduleCleanupPull(SurfaceContext &surfaceCtx, SurfaceId surfaceId, bool flushedDeadNodes) const;
|
|
124
|
+
void scheduleDeferredCleanupPull(SurfaceId surfaceId) const;
|
|
125
|
+
#endif
|
|
113
126
|
|
|
114
127
|
LayoutAnimationsProxy_Legacy(
|
|
115
128
|
const std::shared_ptr<LayoutAnimationsManager> &layoutAnimationsManager,
|
|
116
129
|
const SharedComponentDescriptorRegistry &componentDescriptorRegistry,
|
|
117
130
|
const std::shared_ptr<const ContextContainer> &contextContainer,
|
|
118
131
|
jsi::Runtime &uiRuntime,
|
|
119
|
-
const std::shared_ptr<UIScheduler> &uiScheduler
|
|
132
|
+
const std::shared_ptr<UIScheduler> &uiScheduler,
|
|
133
|
+
const std::shared_ptr<UIManager> &uiManager
|
|
120
134
|
#ifdef ANDROID
|
|
121
135
|
,
|
|
122
136
|
const PreserveMountedTagsFunction &filterUnmountedTagsFunction,
|
|
123
|
-
const std::shared_ptr<UIManager> &uiManager,
|
|
124
137
|
const std::shared_ptr<CallInvoker> &jsInvoker
|
|
125
138
|
#endif
|
|
126
139
|
)
|
|
@@ -129,14 +142,19 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
|
|
|
129
142
|
componentDescriptorRegistry,
|
|
130
143
|
contextContainer,
|
|
131
144
|
uiRuntime,
|
|
132
|
-
uiScheduler
|
|
145
|
+
uiScheduler,
|
|
146
|
+
uiManager
|
|
133
147
|
#ifdef ANDROID
|
|
134
148
|
,
|
|
135
149
|
filterUnmountedTagsFunction,
|
|
136
|
-
uiManager,
|
|
137
150
|
jsInvoker
|
|
138
151
|
#endif
|
|
139
152
|
) {
|
|
153
|
+
uiManager->registerCommitHook(*this);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
~LayoutAnimationsProxy_Legacy() override {
|
|
157
|
+
uiManager_->unregisterCommitHook(*this);
|
|
140
158
|
}
|
|
141
159
|
|
|
142
160
|
void startEnteringAnimation(const int tag, ShadowViewMutation &mutation) const;
|
|
@@ -146,6 +164,8 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
|
|
|
146
164
|
void transferConfigFromNativeID(const std::string &nativeId, const int tag) const;
|
|
147
165
|
std::optional<SurfaceId> progressLayoutAnimation(int tag, const jsi::Object &newStyle) override;
|
|
148
166
|
std::optional<SurfaceId> endLayoutAnimation(int tag, bool shouldRemove) override;
|
|
167
|
+
void startSurface(const SurfaceId surfaceId) override;
|
|
168
|
+
SurfaceContext &getSurfaceContext(SurfaceId surfaceId) const;
|
|
149
169
|
void maybeCancelAnimation(const int tag) const;
|
|
150
170
|
|
|
151
171
|
void reconcileContradictedRemovals(
|
|
@@ -160,7 +180,8 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
|
|
|
160
180
|
ShadowViewMutationList &filteredMutations,
|
|
161
181
|
std::vector<std::shared_ptr<MutationNode>> &roots,
|
|
162
182
|
std::unordered_set<std::shared_ptr<MutationNode>> &deadNodes,
|
|
163
|
-
bool
|
|
183
|
+
bool surfaceDropped,
|
|
184
|
+
bool flushDeadNodes) const;
|
|
164
185
|
|
|
165
186
|
void handleUpdatesAndEnterings(
|
|
166
187
|
ShadowViewMutationList &filteredMutations,
|
|
@@ -169,6 +190,7 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
|
|
|
169
190
|
const PropsParserContext &propsParserContext,
|
|
170
191
|
SurfaceId surfaceId) const;
|
|
171
192
|
void addOngoingAnimations(SurfaceId surfaceId, ShadowViewMutationList &mutations) const;
|
|
193
|
+
void dropUpdatesForDeletedViews(ShadowViewMutationList &filteredMutations) const;
|
|
172
194
|
void updateOngoingAnimationTarget(const int tag, const ShadowViewMutation &mutation) const;
|
|
173
195
|
std::shared_ptr<ShadowView> cloneViewWithoutOpacity(
|
|
174
196
|
facebook::react::ShadowViewMutation &mutation,
|
|
@@ -206,19 +228,15 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon,
|
|
|
206
228
|
const TransactionTelemetry &telemetry,
|
|
207
229
|
ShadowViewMutationList mutations) const override;
|
|
208
230
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
void uiManagerDidConfigureNextLayoutAnimation(
|
|
212
|
-
jsi::Runtime &runtime,
|
|
213
|
-
const RawValue &config,
|
|
214
|
-
const jsi::Value &successCallbackValue,
|
|
215
|
-
const jsi::Value &failureCallbackValue) const override;
|
|
216
|
-
|
|
217
|
-
void setComponentDescriptorRegistry(const SharedComponentDescriptorRegistry &componentDescriptorRegistry) override;
|
|
231
|
+
// UIManagerCommitHook
|
|
218
232
|
|
|
219
|
-
|
|
233
|
+
void commitHookWasRegistered(const UIManager &) noexcept override {}
|
|
234
|
+
void commitHookWasUnregistered(const UIManager &) noexcept override {}
|
|
220
235
|
|
|
221
|
-
|
|
236
|
+
RootShadowNode::Unshared shadowTreeWillCommit(
|
|
237
|
+
const ShadowTree &shadowTree,
|
|
238
|
+
const RootShadowNode::Shared &oldRootShadowNode,
|
|
239
|
+
const RootShadowNode::Unshared &newRootShadowNode) noexcept override;
|
|
222
240
|
};
|
|
223
241
|
|
|
224
242
|
} // namespace reanimated
|
|
@@ -688,7 +688,19 @@ bool ReanimatedModuleProxy::handleRawEvent(const RawEvent &rawEvent, double curr
|
|
|
688
688
|
return false;
|
|
689
689
|
}
|
|
690
690
|
|
|
691
|
-
|
|
691
|
+
#if REACT_NATIVE_VERSION_MINOR >= 87
|
|
692
|
+
const auto tag = eventTarget->getTag();
|
|
693
|
+
#else
|
|
694
|
+
// A stale event dispatched during unmount may carry an EventTarget with a null
|
|
695
|
+
// InstanceHandle which getTag() would dereference (see #9925).
|
|
696
|
+
// Fixed in React Native 0.87 by https://github.com/facebook/react-native/pull/56763.
|
|
697
|
+
const auto shadowNodeFamily = rawEvent.shadowNodeFamily.lock();
|
|
698
|
+
if (shadowNodeFamily == nullptr) {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
const auto tag = shadowNodeFamily->getTag();
|
|
702
|
+
#endif
|
|
703
|
+
|
|
692
704
|
auto eventType = rawEvent.type;
|
|
693
705
|
if (eventType.rfind("top", 0) == 0) {
|
|
694
706
|
eventType = "on" + eventType.substr(3);
|
|
@@ -1222,11 +1234,11 @@ void ReanimatedModuleProxy::initializeLayoutAnimationsProxy() {
|
|
|
1222
1234
|
componentDescriptorRegistry,
|
|
1223
1235
|
scheduler->getContextContainer(),
|
|
1224
1236
|
getJSIRuntimeFromWorkletRuntime(uiRuntime_),
|
|
1225
|
-
uiScheduler_
|
|
1237
|
+
uiScheduler_,
|
|
1238
|
+
uiManager_
|
|
1226
1239
|
#ifdef ANDROID
|
|
1227
1240
|
,
|
|
1228
1241
|
filterUnmountedTagsFunction_,
|
|
1229
|
-
uiManager_,
|
|
1230
1242
|
jsInvoker_
|
|
1231
1243
|
#endif
|
|
1232
1244
|
);
|
|
@@ -1235,22 +1247,19 @@ void ReanimatedModuleProxy::initializeLayoutAnimationsProxy() {
|
|
|
1235
1247
|
#endif
|
|
1236
1248
|
layoutAnimationsProxy_ = std::move(layoutAnimationsProxyExperimental);
|
|
1237
1249
|
} else {
|
|
1238
|
-
|
|
1250
|
+
layoutAnimationsProxy_ = std::make_shared<LayoutAnimationsProxy_Legacy>(
|
|
1239
1251
|
layoutAnimationsManager_,
|
|
1240
1252
|
componentDescriptorRegistry,
|
|
1241
1253
|
scheduler->getContextContainer(),
|
|
1242
1254
|
getJSIRuntimeFromWorkletRuntime(uiRuntime_),
|
|
1243
|
-
uiScheduler_
|
|
1255
|
+
uiScheduler_,
|
|
1256
|
+
uiManager_
|
|
1244
1257
|
#ifdef ANDROID
|
|
1245
1258
|
,
|
|
1246
1259
|
filterUnmountedTagsFunction_,
|
|
1247
|
-
uiManager_,
|
|
1248
1260
|
jsInvoker_
|
|
1249
1261
|
#endif
|
|
1250
1262
|
);
|
|
1251
|
-
// TODO (future): support in experimental
|
|
1252
|
-
uiManager_->setAnimationDelegate(layoutAnimationsProxyLegacy.get());
|
|
1253
|
-
layoutAnimationsProxy_ = std::move(layoutAnimationsProxyLegacy);
|
|
1254
1263
|
}
|
|
1255
1264
|
}
|
|
1256
1265
|
}
|
|
@@ -125,7 +125,11 @@ using namespace facebook::react;
|
|
|
125
125
|
- (void)performOperations
|
|
126
126
|
{
|
|
127
127
|
RCTAssertMainQueue();
|
|
128
|
-
|
|
128
|
+
REAPerformOperations performOperations = _performOperations;
|
|
129
|
+
if (performOperations == nil) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
performOperations(); // calls ReanimatedModuleProxy::performOperations
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
- (void)dispatchEvent:(id<RCTEvent>)event
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* version used to build the native part of the library in runtime. Remember to
|
|
4
4
|
* keep this in sync with the version declared in `package.json`
|
|
5
5
|
*/
|
|
6
|
-
export declare const jsVersion = "4.5.
|
|
6
|
+
export declare const jsVersion = "4.5.4";
|
|
7
7
|
//# sourceMappingURL=jsVersion.d.ts.map
|
package/package.json
CHANGED