react-native-reanimated 4.5.3 → 4.5.5
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 +121 -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,15 @@
|
|
|
1
1
|
#include <reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h>
|
|
2
2
|
|
|
3
|
+
#include <ReactCommon/CallInvoker.h>
|
|
3
4
|
#include <react/debug/react_native_assert.h>
|
|
5
|
+
#include <react/renderer/mounting/ShadowTree.h>
|
|
4
6
|
#include <react/renderer/mounting/ShadowViewMutation.h>
|
|
5
7
|
|
|
6
8
|
#include <memory>
|
|
7
9
|
#include <ranges>
|
|
8
10
|
#include <set>
|
|
9
11
|
#include <string>
|
|
12
|
+
#include <thread>
|
|
10
13
|
#include <unordered_map>
|
|
11
14
|
#include <unordered_set>
|
|
12
15
|
#include <utility>
|
|
@@ -33,7 +36,8 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Legacy::pullTransaction
|
|
|
33
36
|
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
34
37
|
PropsParserContext propsParserContext{surfaceId, *contextContainer_};
|
|
35
38
|
ShadowViewMutationList filteredMutations;
|
|
36
|
-
auto &
|
|
39
|
+
auto &surfaceCtx = getSurfaceContext(surfaceId);
|
|
40
|
+
auto &deadNodes = surfaceCtx.deadNodes;
|
|
37
41
|
|
|
38
42
|
std::vector<std::shared_ptr<MutationNode>> roots;
|
|
39
43
|
std::unordered_map<Tag, Tag> movedViews;
|
|
@@ -60,17 +64,47 @@ std::optional<MountingTransaction> LayoutAnimationsProxy_Legacy::pullTransaction
|
|
|
60
64
|
|
|
61
65
|
parseRemoveMutations(movedViews, mutations, roots);
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
// We recognize dropped surfaces by the presence of a Remove mutation for a root child. This can produce false
|
|
68
|
+
// positives. Ideal solution will be to introduce an appropriate API in RN
|
|
69
|
+
auto surfaceDropped = false;
|
|
70
|
+
const auto removesRootChildren = std::ranges::any_of(mutations, [surfaceId](const auto &mutation) {
|
|
71
|
+
return mutation.type == ShadowViewMutation::Remove && mutation.parentTag == surfaceId;
|
|
72
|
+
});
|
|
73
|
+
if (removesRootChildren) {
|
|
74
|
+
surfaceDropped = surfacesToRemove_.erase(surfaceId) != 0;
|
|
75
|
+
}
|
|
76
|
+
const bool flushDeadNodes = shouldFlushDeadNodes(surfaceDropped);
|
|
77
|
+
handleRemovals(filteredMutations, roots, deadNodes, surfaceDropped, flushDeadNodes);
|
|
78
|
+
#ifdef ANDROID
|
|
79
|
+
maybeScheduleCleanupPull(surfaceCtx, surfaceId, flushDeadNodes);
|
|
80
|
+
#endif // ANDROID
|
|
66
81
|
|
|
67
82
|
handleUpdatesAndEnterings(filteredMutations, movedViews, mutations, propsParserContext, surfaceId);
|
|
68
83
|
|
|
69
84
|
addOngoingAnimations(surfaceId, filteredMutations);
|
|
70
85
|
|
|
86
|
+
dropUpdatesForDeletedViews(filteredMutations);
|
|
87
|
+
|
|
71
88
|
return MountingTransaction{surfaceId, transactionNumber, std::move(filteredMutations), telemetry};
|
|
72
89
|
}
|
|
73
90
|
|
|
91
|
+
// The LayoutAnimationDriver can pair a final keyframe update with the withheld
|
|
92
|
+
// Remove/Delete it replays in the same transaction; we emit removals first, so
|
|
93
|
+
// the update would reach the mounting layer after its view was deleted.
|
|
94
|
+
void LayoutAnimationsProxy_Legacy::dropUpdatesForDeletedViews(ShadowViewMutationList &filteredMutations) const {
|
|
95
|
+
std::unordered_set<Tag> deletedTags;
|
|
96
|
+
for (const auto &mutation : filteredMutations) {
|
|
97
|
+
if (mutation.type == ShadowViewMutation::Delete) {
|
|
98
|
+
deletedTags.insert(mutation.oldChildShadowView.tag);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!deletedTags.empty()) {
|
|
102
|
+
std::erase_if(filteredMutations, [&deletedTags](const auto &mutation) {
|
|
103
|
+
return mutation.type == ShadowViewMutation::Update && deletedTags.contains(mutation.newChildShadowView.tag);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
74
108
|
// If React re-creates or re-inserts a tag whose exiting removal we are still
|
|
75
109
|
// withholding, it has contradicted that withheld Remove/Delete. Flush it now
|
|
76
110
|
// instead of letting it fire later against a stale hierarchy (which would
|
|
@@ -83,7 +117,7 @@ void LayoutAnimationsProxy_Legacy::reconcileContradictedRemovals(
|
|
|
83
117
|
ShadowViewMutationList &mutations,
|
|
84
118
|
ShadowViewMutationList &filteredMutations,
|
|
85
119
|
SurfaceId surfaceId) const {
|
|
86
|
-
auto &
|
|
120
|
+
auto &deadNodes = getSurfaceContext(surfaceId).deadNodes;
|
|
87
121
|
for (auto &mutation : mutations) {
|
|
88
122
|
if (mutation.type != ShadowViewMutation::Type::Create && mutation.type != ShadowViewMutation::Type::Insert) {
|
|
89
123
|
continue;
|
|
@@ -105,6 +139,45 @@ void LayoutAnimationsProxy_Legacy::reconcileContradictedRemovals(
|
|
|
105
139
|
}
|
|
106
140
|
}
|
|
107
141
|
|
|
142
|
+
// On android mutations that alter the view hierarchy are only produced on the JS thread (the push model), so to not
|
|
143
|
+
// race with those, we apply the dead nodes cleanup only on the JS thread, unless there is a surface drop, in which case
|
|
144
|
+
// we can safely cleanup on the UI thread since the surface is gone and no more mutations will be produced for it.
|
|
145
|
+
bool LayoutAnimationsProxy_Legacy::shouldFlushDeadNodes([[maybe_unused]] const bool surfaceDropped) const {
|
|
146
|
+
#ifdef ANDROID
|
|
147
|
+
return surfaceDropped || std::this_thread::get_id() != uiThreadId_;
|
|
148
|
+
#else
|
|
149
|
+
return true;
|
|
150
|
+
#endif
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#ifdef ANDROID
|
|
154
|
+
// We schedule a pullTransaction call to happen on the JS thread so it can safely remove dead nodes after exiting
|
|
155
|
+
// finished
|
|
156
|
+
void LayoutAnimationsProxy_Legacy::maybeScheduleCleanupPull(
|
|
157
|
+
SurfaceContext &surfaceCtx,
|
|
158
|
+
const SurfaceId surfaceId,
|
|
159
|
+
const bool flushedDeadNodes) const {
|
|
160
|
+
if (flushedDeadNodes) {
|
|
161
|
+
surfaceCtx.cleanupPullScheduled = false;
|
|
162
|
+
} else if (!surfaceCtx.deadNodes.empty() && !surfaceCtx.cleanupPullScheduled) {
|
|
163
|
+
surfaceCtx.cleanupPullScheduled = true;
|
|
164
|
+
scheduleDeferredCleanupPull(surfaceId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
void LayoutAnimationsProxy_Legacy::scheduleDeferredCleanupPull(SurfaceId surfaceId) const {
|
|
169
|
+
const std::weak_ptr<UIManager> weakUiManager = uiManager_;
|
|
170
|
+
jsInvoker_->invokeAsync([weakUiManager, surfaceId](jsi::Runtime &) {
|
|
171
|
+
auto uiManager = weakUiManager.lock();
|
|
172
|
+
if (!uiManager) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
uiManager->getShadowTreeRegistry().visit(
|
|
176
|
+
surfaceId, [](ShadowTree const &shadowTree) { shadowTree.notifyDelegatesOfUpdates(); });
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
#endif
|
|
180
|
+
|
|
108
181
|
std::optional<SurfaceId> LayoutAnimationsProxy_Legacy::progressLayoutAnimation(int tag, const jsi::Object &newStyle) {
|
|
109
182
|
#ifdef LAYOUT_ANIMATIONS_LOGS
|
|
110
183
|
LOG(INFO) << "progress layout animation for tag " << tag << std::endl;
|
|
@@ -169,7 +242,7 @@ std::optional<SurfaceId> LayoutAnimationsProxy_Legacy::endLayoutAnimation(int ta
|
|
|
169
242
|
}
|
|
170
243
|
auto mutationNode = std::static_pointer_cast<MutationNode>(node);
|
|
171
244
|
mutationNode->state = ExitingState_Legacy::DEAD;
|
|
172
|
-
auto &
|
|
245
|
+
auto &deadNodes = getSurfaceContext(surfaceId).deadNodes;
|
|
173
246
|
deadNodes.insert(mutationNode);
|
|
174
247
|
|
|
175
248
|
return surfaceId;
|
|
@@ -281,12 +354,13 @@ void LayoutAnimationsProxy_Legacy::handleRemovals(
|
|
|
281
354
|
ShadowViewMutationList &filteredMutations,
|
|
282
355
|
std::vector<std::shared_ptr<MutationNode>> &roots,
|
|
283
356
|
std::unordered_set<std::shared_ptr<MutationNode>> &deadNodes,
|
|
284
|
-
bool
|
|
357
|
+
bool surfaceDropped,
|
|
358
|
+
bool flushDeadNodes) const {
|
|
285
359
|
// iterate from the end, so that children
|
|
286
360
|
// with higher indices appear first in the mutations list
|
|
287
361
|
for (auto it = roots.rbegin(); it != roots.rend(); it++) {
|
|
288
362
|
auto &node = *it;
|
|
289
|
-
if (!startAnimationsRecursively(node, true,
|
|
363
|
+
if (!startAnimationsRecursively(node, true, !surfaceDropped, false, filteredMutations)) {
|
|
290
364
|
filteredMutations.push_back(node->mutation);
|
|
291
365
|
node->unflattenedParent->removeChildFromUnflattenedTree(node); //???
|
|
292
366
|
if (node->state != ExitingState_Legacy::MOVED) {
|
|
@@ -301,6 +375,10 @@ void LayoutAnimationsProxy_Legacy::handleRemovals(
|
|
|
301
375
|
}
|
|
302
376
|
}
|
|
303
377
|
|
|
378
|
+
if (!flushDeadNodes) {
|
|
379
|
+
// Deferred - the host still has these views mounted, bookkeeping stays.
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
304
382
|
for (const auto &node : deadNodes) {
|
|
305
383
|
if (node->state != ExitingState_Legacy::DELETED) {
|
|
306
384
|
endAnimationsRecursively(node, filteredMutations);
|
|
@@ -713,6 +791,7 @@ void LayoutAnimationsProxy_Legacy::startEnteringAnimation(const int tag, ShadowV
|
|
|
713
791
|
auto &mutex = strongThis->mutex;
|
|
714
792
|
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
715
793
|
#ifdef ANDROID
|
|
794
|
+
strongThis->uiThreadId_ = std::this_thread::get_id();
|
|
716
795
|
if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
|
|
717
796
|
// the view was removed before this start could run
|
|
718
797
|
return;
|
|
@@ -773,12 +852,12 @@ void LayoutAnimationsProxy_Legacy::startExitingAnimation(const int tag, ShadowVi
|
|
|
773
852
|
auto &mutex = strongThis->mutex;
|
|
774
853
|
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
775
854
|
#ifdef ANDROID
|
|
855
|
+
strongThis->uiThreadId_ = std::this_thread::get_id();
|
|
776
856
|
if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
|
|
777
|
-
//
|
|
778
|
-
//
|
|
779
|
-
//
|
|
780
|
-
//
|
|
781
|
-
strongThis->layoutAnimationsManager_->clearLayoutAnimationConfig(tag);
|
|
857
|
+
// The view was removed before this start could run. Deliberately no
|
|
858
|
+
// clearLayoutAnimationConfig: with tag reuse the config maps already
|
|
859
|
+
// describe the re-created view, and wiping them would leave it mounted
|
|
860
|
+
// at opacity 0. A dead tag's stale configs can never fire again.
|
|
782
861
|
return;
|
|
783
862
|
}
|
|
784
863
|
#endif
|
|
@@ -836,6 +915,7 @@ void LayoutAnimationsProxy_Legacy::startLayoutAnimation(const int tag, const Sha
|
|
|
836
915
|
auto &mutex = strongThis->mutex;
|
|
837
916
|
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
838
917
|
#ifdef ANDROID
|
|
918
|
+
strongThis->uiThreadId_ = std::this_thread::get_id();
|
|
839
919
|
if (consumeIsCancelled(strongThis->pendingStarts_, tag, handle)) {
|
|
840
920
|
// the view was removed before this start could run
|
|
841
921
|
return;
|
|
@@ -998,23 +1078,37 @@ inline bool MutationNode::isMutationNode() {
|
|
|
998
1078
|
return true;
|
|
999
1079
|
}
|
|
1000
1080
|
|
|
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) {}
|
|
1081
|
+
void LayoutAnimationsProxy_Legacy::startSurface(const SurfaceId surfaceId) {
|
|
1082
|
+
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
1083
|
+
surfaceContext_.try_emplace(surfaceId);
|
|
1084
|
+
}
|
|
1011
1085
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1086
|
+
SurfaceContext &LayoutAnimationsProxy_Legacy::getSurfaceContext(const SurfaceId surfaceId) const {
|
|
1087
|
+
// startSurface() creates the entry for a surface before any other method
|
|
1088
|
+
// uses it. The proxy does not remove entries during its lifetime. Thus a
|
|
1089
|
+
// missing entry is an initialization bug, not a stopped surface.
|
|
1090
|
+
const auto it = surfaceContext_.find(surfaceId);
|
|
1091
|
+
react_native_assert(it != surfaceContext_.end() && "surface must be registered by startSurface");
|
|
1092
|
+
return it->second;
|
|
1014
1093
|
}
|
|
1015
1094
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1095
|
+
// UIManagerCommitHook
|
|
1096
|
+
|
|
1097
|
+
// Surface teardown commits an empty root (SurfaceHandler::stop) before the
|
|
1098
|
+
// teardown transaction is pulled — mark it so pullTransaction skips exit
|
|
1099
|
+
// animations. Reading the ShadowTreeRegistry here instead would deadlock.
|
|
1100
|
+
RootShadowNode::Unshared LayoutAnimationsProxy_Legacy::shadowTreeWillCommit(
|
|
1101
|
+
const ShadowTree &shadowTree,
|
|
1102
|
+
const RootShadowNode::Shared & /*oldRootShadowNode*/,
|
|
1103
|
+
const RootShadowNode::Unshared &newRootShadowNode) noexcept {
|
|
1104
|
+
const auto surfaceId = shadowTree.getSurfaceId();
|
|
1105
|
+
auto lock = std::unique_lock<std::recursive_mutex>(mutex);
|
|
1106
|
+
if (newRootShadowNode->getChildren().empty()) {
|
|
1107
|
+
surfacesToRemove_.insert(surfaceId);
|
|
1108
|
+
} else {
|
|
1109
|
+
surfacesToRemove_.erase(surfaceId);
|
|
1110
|
+
}
|
|
1111
|
+
return newRootShadowNode;
|
|
1018
1112
|
}
|
|
1019
1113
|
|
|
1020
1114
|
} // 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.5";
|
|
7
7
|
//# sourceMappingURL=jsVersion.d.ts.map
|
package/package.json
CHANGED