react-native-reanimated 4.7.0 → 4.7.1

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.
Files changed (41) hide show
  1. package/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.cpp +34 -2
  2. package/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.h +15 -1
  3. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.cpp +9 -0
  4. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.h +3 -0
  5. package/Common/cpp/reanimated/Fabric/updates/SynchronousWritesTracker.cpp +103 -0
  6. package/Common/cpp/reanimated/Fabric/updates/SynchronousWritesTracker.h +74 -0
  7. package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistry.cpp +15 -0
  8. package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistry.h +5 -0
  9. package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistryManager.cpp +10 -0
  10. package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistryManager.h +3 -0
  11. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy.cpp +97 -54
  12. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy.h +5 -1
  13. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.cpp +23 -5
  14. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h +17 -1
  15. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp +9 -9
  16. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +35 -8
  17. package/Common/cpp/reanimated/LayoutAnimations/PropsDiffer.h +2 -2
  18. package/Common/cpp/reanimated/LayoutAnimations/SharedTransitions.cpp +11 -11
  19. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +75 -2
  20. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h +11 -1
  21. package/Common/cpp/reanimated/Tools/PlatformDepMethodsHolder.h +4 -1
  22. package/android/src/main/cpp/reanimated/android/NativeProxy.cpp +13 -3
  23. package/android/src/main/cpp/reanimated/android/NativeProxy.h +2 -0
  24. package/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt +84 -9
  25. package/android/src/main/java/com/swmansion/reanimated/NodesManager.kt +2 -4
  26. package/android/src/main/java/com/swmansion/reanimated/nativeProxy/EventHandler.kt +4 -3
  27. package/android/src/main/java/com/swmansion/reanimated/sensor/ReanimatedSensor.kt +1 -1
  28. package/android/src/main/java/com/swmansion/reanimated/sensor/ReanimatedSensorListener.kt +44 -33
  29. package/apple/reanimated/apple/REASynchronousPropsRewriter.h +18 -0
  30. package/apple/reanimated/apple/REASynchronousPropsRewriter.mm +37 -0
  31. package/apple/reanimated/apple/ReanimatedModule.mm +7 -0
  32. package/apple/reanimated/apple/keyboardObserver/REAKeyboardEventObserver.mm +20 -3
  33. package/apple/reanimated/apple/native/PlatformDepMethodsHolderImpl.mm +24 -1
  34. package/lib/module/animation/timing.js +15 -2
  35. package/lib/module/animation/timing.js.map +1 -1
  36. package/lib/module/platform-specific/jsVersion.js +1 -1
  37. package/lib/typescript/animation/timing.d.ts.map +1 -1
  38. package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
  39. package/package.json +3 -1
  40. package/src/animation/timing.ts +20 -2
  41. package/src/platform-specific/jsVersion.ts +1 -1
@@ -3,6 +3,7 @@
3
3
  #include <folly/dynamic.h>
4
4
  #include <react/debug/react_native_assert.h>
5
5
  #include <react/renderer/components/rnreanimated/Props.h>
6
+ #include <react/renderer/components/view/ViewProps.h>
6
7
  #include <react/renderer/mounting/MountingOverrideDelegate.h>
7
8
  #include <react/renderer/mounting/ShadowView.h>
8
9
  #include <reanimated/LayoutAnimations/LayoutAnimationsManager.h>
@@ -59,11 +60,18 @@ struct Snapshot {
59
60
  };
60
61
 
61
62
  typedef enum class ExitingState : std::uint8_t {
62
- UNDEFINED = 1,
63
- WAITING = 2,
64
- ANIMATING = 3,
65
- DEAD = 4,
66
- DELETED = 5,
63
+ // React still renders the view
64
+ LIVE = 1,
65
+ // React deletes the view in the current transaction and the proxy has not decided how it exits yet
66
+ DECISION_PENDING = 2,
67
+ // withheld until its exiting descendants finish
68
+ WAITING = 3,
69
+ // withheld while its own exiting animation runs
70
+ ANIMATING = 4,
71
+ // its exiting animation finished, it is torn down at the next flush
72
+ COMPLETED = 5,
73
+ // the proxy has emitted its Remove and Delete
74
+ TORN_DOWN = 6,
67
75
  } ExitingState;
68
76
 
69
77
  struct MutationNode;
@@ -102,17 +110,18 @@ struct LightNode {
102
110
  folly::dynamic accumulatedRawProps = nullptr;
103
111
  bool propsNeedResolve = false;
104
112
  #endif
105
- ExitingState state = ExitingState::UNDEFINED;
113
+ ExitingState state = ExitingState::LIVE;
106
114
  std::weak_ptr<LightNode> parent;
107
115
  std::vector<std::shared_ptr<LightNode>> children;
108
116
  int exitingChildrenCount = 0;
109
117
 
110
118
  bool isExiting() const {
111
- return state != ExitingState::UNDEFINED;
119
+ return state != ExitingState::LIVE;
112
120
  }
113
121
 
114
122
  void setExitingState(ExitingState newState) {
115
- const bool startsExiting = !isExiting() && newState != ExitingState::UNDEFINED;
123
+ react_native_assert(newState != ExitingState::LIVE && "A light node never becomes live again");
124
+ const bool startsExiting = !isExiting();
116
125
  state = newState;
117
126
  if (!startsExiting) {
118
127
  return;
@@ -255,6 +264,15 @@ static inline std::shared_ptr<LightNode> findParentRNSScreen(const std::shared_p
255
264
  return current;
256
265
  }
257
266
 
267
+ static inline bool isInSubtree(std::shared_ptr<LightNode> node, const std::shared_ptr<LightNode> &root) {
268
+ for (; node; node = node->parent.lock()) {
269
+ if (node == root) {
270
+ return true;
271
+ }
272
+ }
273
+ return false;
274
+ }
275
+
258
276
  static inline bool isSETBoundary(const std::shared_ptr<LightNode> &node) {
259
277
  return !std::strcmp(node->current.componentName, "REASharedTransitionBoundary");
260
278
  }
@@ -283,4 +301,13 @@ static inline bool hasLayoutChanged(const ShadowViewMutation &mutation) {
283
301
  return mutation.oldChildShadowView.layoutMetrics.frame != mutation.newChildShadowView.layoutMetrics.frame;
284
302
  }
285
303
 
304
+ static inline bool isViewKind(const ShadowView &view) {
305
+ return view.traits.check(ShadowNodeTraits::Trait::ViewKind);
306
+ }
307
+
308
+ static inline const ViewProps &getViewProps(const ShadowView &view) {
309
+ react_native_assert(isViewKind(view) && "Only ViewKind views have ViewProps");
310
+ return static_cast<const ViewProps &>(*view.props);
311
+ }
312
+
286
313
  } // namespace reanimated
@@ -90,8 +90,8 @@ class PropsDiffer {
90
90
  explicit PropsDiffer(jsi::Runtime &rt, const ShadowView &sourceView, const ShadowView &targetView)
91
91
  : sourceView_(sourceView),
92
92
  targetView_(targetView),
93
- sourceViewProps_(static_cast<const ViewProps &>(*sourceView.props)),
94
- targetViewProps_(static_cast<const ViewProps &>(*targetView.props)),
93
+ sourceViewProps_(getViewProps(sourceView)),
94
+ targetViewProps_(getViewProps(targetView)),
95
95
  sourceValues_(rt),
96
96
  targetValues_(rt) {}
97
97
 
@@ -59,7 +59,7 @@ void LayoutAnimationsProxy::findSharedElementsOnScreen(
59
59
  return;
60
60
  }
61
61
  std::optional<SharedTag> sharedTag;
62
- {
62
+ if (node->current.traits.check(ShadowNodeTraits::Trait::ViewKind)) {
63
63
  auto lock = std::unique_lock<std::mutex>(sharedTransitionManager_->mutex_);
64
64
  const auto it = sharedTransitionManager_->tagToName_.find(node->current.tag);
65
65
  if (it != sharedTransitionManager_->tagToName_.end()) {
@@ -316,6 +316,7 @@ void LayoutAnimationsProxy::overrideTransform(
316
316
  if (!transform) {
317
317
  return;
318
318
  }
319
+ react_native_assert(isViewKind(shadowView) && "Only ViewKind views have ViewProps");
319
320
  #ifdef ANDROID
320
321
  auto array = folly::dynamic::array(folly::dynamic::object("matrix", transform->operator folly::dynamic()));
321
322
  const folly::dynamic newTransformDynamic = folly::dynamic::object("transform", array);
@@ -492,9 +493,10 @@ void LayoutAnimationsProxy::hideTransitioningViews(
492
493
  }
493
494
 
494
495
  // The hide in hideTransitioningViews is not stored in the light tree, so a
495
- // later Update for the same view carries full opacity and would show the view
496
- // again. Force opacity 0 on every outgoing Update for a hidden view until the
497
- // restore in cleanupSharedTransitions removes its tag from hiddenViewTags_.
496
+ // later Update or Insert for the same view carries full opacity and would show
497
+ // the view again. Force opacity 0 on every outgoing Update and Insert for a
498
+ // hidden view until the restore in cleanupSharedTransitions removes its tag
499
+ // from hiddenViewTags_.
498
500
  void LayoutAnimationsProxy::keepTransitioningViewsHidden(
499
501
  ShadowViewMutationList &filteredMutations,
500
502
  const PropsParserContext &propsParserContext) const {
@@ -502,11 +504,9 @@ void LayoutAnimationsProxy::keepTransitioningViewsHidden(
502
504
  return;
503
505
  }
504
506
  for (auto &mutation : filteredMutations) {
505
- if (mutation.type == ShadowViewMutation::Update && hiddenViewTags_.contains(mutation.newChildShadowView.tag)) {
506
- mutation = ShadowViewMutation::UpdateMutation(
507
- mutation.oldChildShadowView,
508
- cloneViewWithoutOpacity(mutation.newChildShadowView, propsParserContext),
509
- mutation.parentTag);
507
+ const bool writesProps = mutation.type == ShadowViewMutation::Update || mutation.type == ShadowViewMutation::Insert;
508
+ if (writesProps && hiddenViewTags_.contains(mutation.newChildShadowView.tag)) {
509
+ mutation.newChildShadowView = cloneViewWithoutOpacity(mutation.newChildShadowView, propsParserContext);
510
510
  }
511
511
  }
512
512
  }
@@ -642,7 +642,7 @@ void LayoutAnimationsProxy::cleanupSharedTransitions(
642
642
  if (!parent) {
643
643
  continue;
644
644
  }
645
- const auto opacity = static_cast<const ViewProps &>(*view.props).opacity;
645
+ const auto opacity = getViewProps(view).opacity;
646
646
  filteredMutations.push_back(ShadowViewMutation::UpdateMutation(
647
647
  cloneViewWithoutOpacity(view, propsParserContext),
648
648
  cloneViewWithOpacity(view, opacity, propsParserContext),
@@ -721,7 +721,7 @@ std::optional<Transform> LayoutAnimationsProxy::parseParentTransforms(
721
721
  while (currentNode) {
722
722
  resolveLightNodeProps(currentNode);
723
723
  const auto &view = useViewsOnScreen ? viewOnScreen(currentNode) : currentNode->current;
724
- const auto &props = static_cast<const ViewProps &>(*view.props);
724
+ const auto &props = getViewProps(view);
725
725
  auto origin = props.transformOrigin;
726
726
  const auto &viewSize = view.layoutMetrics.frame.size;
727
727
  if (origin.xy[0].unit == facebook::react::UnitType::Percent) {
@@ -78,6 +78,14 @@ constexpr bool shouldUseSynchronousUpdatesInPerformOperations() {
78
78
  }
79
79
  #endif
80
80
 
81
+ std::shared_ptr<SynchronousWritesTracker> makeSynchronousWritesTracker() {
82
+ if constexpr (
83
+ shouldUseSynchronousUpdatesInPerformOperations() && !StaticFeatureFlags::getFlag("USE_ANIMATION_BACKEND")) {
84
+ return std::make_shared<SynchronousWritesTracker>();
85
+ }
86
+ return nullptr;
87
+ }
88
+
81
89
  std::pair<UpdatesBatch, UpdatesBatch> partitionUpdates(UpdatesBatch &&updatesBatch, const bool allowPartialUpdates) {
82
90
  const auto isSynchronous = [&](const std::string &keyStr, [[maybe_unused]] const folly::dynamic &value) {
83
91
  if (!isSynchronousPropName(keyStr)) {
@@ -162,6 +170,7 @@ ReanimatedModuleProxy::ReanimatedModuleProxy(
162
170
  getAnimationTimestamp_(platformDepMethodsHolder.getAnimationTimestamp),
163
171
  #ifdef __APPLE__
164
172
  forceScreenSnapshot_(platformDepMethodsHolder.forceScreenSnapshotFunction),
173
+ readMountedViewProps_(platformDepMethodsHolder.readMountedViewPropsFunction),
165
174
  #endif
166
175
  staticPropsRegistry_(std::make_shared<StaticPropsRegistry>()),
167
176
  updatesRegistryManager_(std::make_shared<UpdatesRegistryManager>(staticPropsRegistry_)),
@@ -193,6 +202,7 @@ ReanimatedModuleProxy::ReanimatedModuleProxy(
193
202
  #ifdef ANDROID
194
203
  filterUnmountedTagsFunction_(platformDepMethodsHolder.filterUnmountedTagsFunction),
195
204
  #endif // ANDROID
205
+ synchronousWritesTracker_(makeSynchronousWritesTracker()),
196
206
  subscribeForKeyboardEventsFunction_(platformDepMethodsHolder.subscribeForKeyboardEvents),
197
207
  unsubscribeFromKeyboardEventsFunction_(platformDepMethodsHolder.unsubscribeFromKeyboardEvents) {
198
208
  // Add registries in order of their priority (from the lowest to the
@@ -868,6 +878,30 @@ void ReanimatedModuleProxy::performNonLayoutOperations() {
868
878
  applySynchronousUpdates(partitionUpdates(std::move(updatesBatch), true).first);
869
879
  }
870
880
 
881
+ bool ReanimatedModuleProxy::hasSynchronousWritesTracker() const {
882
+ return synchronousWritesTracker_ != nullptr;
883
+ }
884
+
885
+ void ReanimatedModuleProxy::rewriteSynchronousProps() {
886
+ if (!synchronousWritesTracker_) {
887
+ return;
888
+ }
889
+ const auto families = synchronousWritesTracker_->getFamiliesToRewrite();
890
+ if (!families.empty()) {
891
+ UpdatesBatch registryValues;
892
+ {
893
+ auto lock = updatesRegistryManager_->lock();
894
+ for (const auto &family : families) {
895
+ folly::dynamic props = folly::dynamic::object;
896
+ updatesRegistryManager_->mergeRegistryProps(family->getTag(), props);
897
+ registryValues.emplace_back(family, std::move(props));
898
+ }
899
+ }
900
+ writeSynchronousPropsToViews(partitionUpdates(std::move(registryValues), true).first);
901
+ }
902
+ synchronousWritesTracker_->onRewrite();
903
+ }
904
+
871
905
  AnimationMutations ReanimatedModuleProxy::collectNonLayoutAnimationUpdates() {
872
906
  ReanimatedSystraceSection s("ReanimatedModuleProxy::collectNonLayoutAnimationUpdates");
873
907
 
@@ -1040,7 +1074,13 @@ void ReanimatedModuleProxy::applySynchronousUpdates(const UpdatesBatch &synchron
1040
1074
  layoutAnimationsProxyRegistry_->applySynchronousProps(
1041
1075
  synchronousUpdatesBatch, DynamicFeatureFlags::getFlag("TRACK_SYNCHRONOUS_PROPS_IN_LAYOUT_ANIMATIONS"));
1042
1076
  }
1077
+ if (synchronousWritesTracker_) {
1078
+ synchronousWritesTracker_->onSynchronousWrite(synchronousUpdatesBatch);
1079
+ }
1080
+ writeSynchronousPropsToViews(synchronousUpdatesBatch);
1081
+ }
1043
1082
 
1083
+ void ReanimatedModuleProxy::writeSynchronousPropsToViews(const UpdatesBatch &synchronousUpdatesBatch) {
1044
1084
  #ifdef ANDROID
1045
1085
  if (!synchronousUpdatesBatch.empty()) {
1046
1086
  serializeSynchronousPropsToBuffers(
@@ -1201,10 +1241,19 @@ void ReanimatedModuleProxy::initializeFabric(const std::shared_ptr<UIManager> &u
1201
1241
  // TODO: with the animation backend we still need a way to handleNodeRemovals,
1202
1242
  // for now we leave this to leak the memory, a fix will come in a follow-up
1203
1243
  mountHook_ = std::make_shared<ReanimatedMountHook>(
1204
- uiManager_, updatesRegistryManager_, viewStylesRepository_, layoutAnimationsProxyRegistry_, request);
1244
+ uiManager_,
1245
+ updatesRegistryManager_,
1246
+ viewStylesRepository_,
1247
+ layoutAnimationsProxyRegistry_,
1248
+ synchronousWritesTracker_,
1249
+ request);
1205
1250
 
1206
1251
  commitHook_ = std::make_shared<ReanimatedCommitHook>(
1207
- uiManager_, updatesRegistryManager_, viewStylesRepository_, layoutAnimationsProxyRegistry_);
1252
+ uiManager_,
1253
+ updatesRegistryManager_,
1254
+ viewStylesRepository_,
1255
+ layoutAnimationsProxyRegistry_,
1256
+ synchronousWritesTracker_);
1208
1257
  }
1209
1258
 
1210
1259
  void ReanimatedModuleProxy::initializeLayoutAnimationsProxyRegistry() {
@@ -1234,6 +1283,9 @@ void ReanimatedModuleProxy::initializeLayoutAnimationsProxyRegistry() {
1234
1283
  });
1235
1284
  };
1236
1285
 
1286
+ constexpr bool keepsSynchronousPropsInReinserts =
1287
+ shouldUseSynchronousUpdatesInPerformOperations() && !StaticFeatureFlags::getFlag("USE_ANIMATION_BACKEND");
1288
+
1237
1289
  const LayoutAnimationsProxyDependencies dependencies{
1238
1290
  layoutAnimationsManager_,
1239
1291
  componentDescriptorRegistry,
@@ -1245,9 +1297,11 @@ void ReanimatedModuleProxy::initializeLayoutAnimationsProxyRegistry() {
1245
1297
  #ifdef ANDROID
1246
1298
  filterUnmountedTagsFunction_,
1247
1299
  jsInvoker_,
1300
+ keepsSynchronousPropsInReinserts ? makeSynchronousPropsReader() : nullptr,
1248
1301
  #endif
1249
1302
  #ifdef __APPLE__
1250
1303
  forceScreenSnapshot_,
1304
+ keepsSynchronousPropsInReinserts ? readMountedViewProps_ : nullptr,
1251
1305
  #endif
1252
1306
  };
1253
1307
 
@@ -1258,6 +1312,25 @@ void ReanimatedModuleProxy::initializeLayoutAnimationsProxyRegistry() {
1258
1312
  }
1259
1313
  }
1260
1314
 
1315
+ #ifdef ANDROID
1316
+ SynchronousPropsReader ReanimatedModuleProxy::makeSynchronousPropsReader() {
1317
+ if (!ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) {
1318
+ return nullptr;
1319
+ }
1320
+ return [weakThis = weak_from_this()](const Tag tag) {
1321
+ const auto strongThis = weakThis.lock();
1322
+ return strongThis ? strongThis->readSynchronousProps(tag) : folly::dynamic::object();
1323
+ };
1324
+ }
1325
+
1326
+ folly::dynamic ReanimatedModuleProxy::readSynchronousProps(const Tag tag) {
1327
+ folly::dynamic synchronousProps = folly::dynamic::object;
1328
+ auto lock = updatesRegistryManager_->lock();
1329
+ updatesRegistryManager_->mergeRegistryProps(tag, synchronousProps, isSynchronousPropName);
1330
+ return synchronousProps;
1331
+ }
1332
+ #endif // ANDROID
1333
+
1261
1334
  #ifdef IS_REANIMATED_EXAMPLE_APP
1262
1335
 
1263
1336
  std::string format(bool b) {
@@ -21,6 +21,7 @@
21
21
  #include <reanimated/Fabric/ShadowTreeCloner.h>
22
22
  #include <reanimated/Fabric/updates/AnimatedPropsRegistry.h>
23
23
  #include <reanimated/Fabric/updates/OperationsLoop.h>
24
+ #include <reanimated/Fabric/updates/SynchronousWritesTracker.h>
24
25
  #include <reanimated/Fabric/updates/UpdatesRegistryManager.h>
25
26
  #include <reanimated/LayoutAnimations/LayoutAnimationsManager.h>
26
27
  #include <reanimated/LayoutAnimations/LayoutAnimationsProxyRegistry.h>
@@ -108,6 +109,8 @@ class ReanimatedModuleProxy : public std::enable_shared_from_this<ReanimatedModu
108
109
 
109
110
  void performOperations();
110
111
  void performNonLayoutOperations();
112
+ bool hasSynchronousWritesTracker() const;
113
+ void rewriteSynchronousProps();
111
114
  void flushLayoutAnimationOperations();
112
115
  void executeLayoutAnimationsRequests();
113
116
 
@@ -205,6 +208,11 @@ class ReanimatedModuleProxy : public std::enable_shared_from_this<ReanimatedModu
205
208
 
206
209
  void commitUpdates(const std::unordered_map<SurfaceId, PropsMap> &propsMapBySurface);
207
210
  void applySynchronousUpdates(const UpdatesBatch &synchronousUpdatesBatch);
211
+ void writeSynchronousPropsToViews(const UpdatesBatch &synchronousUpdatesBatch);
212
+ #ifdef ANDROID
213
+ SynchronousPropsReader makeSynchronousPropsReader();
214
+ folly::dynamic readSynchronousProps(Tag tag);
215
+ #endif
208
216
 
209
217
  std::shared_ptr<UIManagerAnimationBackend> getAnimationBackend();
210
218
  AnimationMutations runGrandCallback(AnimationTimestamp timestamp, GrandCallbackSource source);
@@ -239,6 +247,7 @@ class ReanimatedModuleProxy : public std::enable_shared_from_this<ReanimatedModu
239
247
 
240
248
  #ifdef __APPLE__
241
249
  ForceScreenSnapshotFunction forceScreenSnapshot_;
250
+ ReadMountedViewPropsFunction readMountedViewProps_;
242
251
  #endif
243
252
  const std::shared_ptr<StaticPropsRegistry> staticPropsRegistry_;
244
253
  const std::shared_ptr<UpdatesRegistryManager> updatesRegistryManager_;
@@ -253,9 +262,10 @@ class ReanimatedModuleProxy : public std::enable_shared_from_this<ReanimatedModu
253
262
 
254
263
  const SynchronouslyUpdateUIPropsFunction synchronouslyUpdateUIPropsFunction_;
255
264
  const PreserveMountedTagsFunction filterUnmountedTagsFunction_;
265
+ const std::shared_ptr<SynchronousWritesTracker> synchronousWritesTracker_;
256
266
 
257
267
  #ifdef ANDROID
258
- // Reused across `applySynchronousUpdates` calls to avoid per-frame heap
268
+ // Reused across `writeSynchronousPropsToViews` calls to avoid per-frame heap
259
269
  // allocations. Access only on the UI thread.
260
270
  std::vector<int> synchronousPropsIntBuffer_;
261
271
  std::vector<double> synchronousPropsDoubleBuffer_;
@@ -6,6 +6,7 @@
6
6
 
7
7
  #include <folly/dynamic.h>
8
8
  #include <jsi/jsi.h>
9
+ #include <react/renderer/core/Props.h>
9
10
  #include <react/renderer/core/ReactPrimitives.h>
10
11
 
11
12
  #include <memory>
@@ -47,7 +48,8 @@ using KeyboardEventSubscribeFunction = std::function<int(std::function<void(int,
47
48
  using KeyboardEventUnsubscribeFunction = std::function<void(int)>;
48
49
  using MaybeFlushUIUpdatesQueueFunction = std::function<void()>;
49
50
 
50
- using ForceScreenSnapshotFunction = std::function<void(Tag tag)>;
51
+ using ForceScreenSnapshotFunction = std::function<bool(Tag tag)>;
52
+ using ReadMountedViewPropsFunction = std::function<Props::Shared(Tag tag)>;
51
53
 
52
54
  using PlatformAttachPseudoSelectorFunction = std::function<void(Tag, PseudoSelector, std::function<void(bool)>)>;
53
55
  using PlatformDetachPseudoSelectorFunction = std::function<void(Tag, PseudoSelector)>;
@@ -59,6 +61,7 @@ struct PlatformDepMethodsHolder {
59
61
  #endif // ANDROID
60
62
  #ifdef __APPLE__
61
63
  ForceScreenSnapshotFunction forceScreenSnapshotFunction;
64
+ ReadMountedViewPropsFunction readMountedViewPropsFunction;
62
65
  #endif
63
66
  SynchronouslyUpdateUIPropsFunction synchronouslyUpdateUIPropsFunction;
64
67
  GetAnimationTimestampFunction getAnimationTimestamp;
@@ -138,6 +138,14 @@ void NativeProxy::performNonLayoutOperations() {
138
138
  reanimatedModuleProxy_->performNonLayoutOperations();
139
139
  }
140
140
 
141
+ bool NativeProxy::hasSynchronousWritesTracker() {
142
+ return reanimatedModuleProxy_->hasSynchronousWritesTracker();
143
+ }
144
+
145
+ void NativeProxy::rewriteSynchronousProps() {
146
+ reanimatedModuleProxy_->rewriteSynchronousProps();
147
+ }
148
+
141
149
  bool NativeProxy::getIsReducedMotion() {
142
150
  static const auto method = getJniMethod<jboolean()>("getIsReducedMotion");
143
151
  return method(javaPart_.get());
@@ -151,9 +159,11 @@ void NativeProxy::registerNatives() {
151
159
  registerHybrid(
152
160
  {makeNativeMethod("initHybrid", NativeProxy::initHybrid),
153
161
  makeNativeMethod("installJSIBindings", NativeProxy::installJSIBindings),
154
- makeNativeMethod("isAnyHandlerWaitingForEvent", NativeProxy::isAnyHandlerWaitingForEvent),
155
- makeNativeMethod("performOperations", NativeProxy::performOperations),
156
- makeNativeMethod("performNonLayoutOperations", NativeProxy::performNonLayoutOperations),
162
+ makeNativeMethod("isAnyHandlerWaitingForEventCpp", NativeProxy::isAnyHandlerWaitingForEvent),
163
+ makeNativeMethod("performOperationsCpp", NativeProxy::performOperations),
164
+ makeNativeMethod("performNonLayoutOperationsCpp", NativeProxy::performNonLayoutOperations),
165
+ makeNativeMethod("hasSynchronousWritesTracker", NativeProxy::hasSynchronousWritesTracker),
166
+ makeNativeMethod("rewriteSynchronousProps", NativeProxy::rewriteSynchronousProps),
157
167
  makeNativeMethod("invalidateCpp", NativeProxy::invalidateCpp),
158
168
  makeNativeMethod("toggleSlowAnimationsOnUIRuntime", NativeProxy::toggleSlowAnimationsOnUIRuntime)});
159
169
  }
@@ -55,6 +55,8 @@ class NativeProxy : public jni::HybridClass<NativeProxy>, std::enable_shared_fro
55
55
  bool isAnyHandlerWaitingForEvent(const std::string &eventName, const int emitterReactTag);
56
56
  void performOperations();
57
57
  void performNonLayoutOperations();
58
+ bool hasSynchronousWritesTracker();
59
+ void rewriteSynchronousProps();
58
60
  bool getIsReducedMotion();
59
61
  void requestRender(std::function<void(double)> onRender);
60
62
  void registerEventHandler();
@@ -8,8 +8,11 @@ import com.facebook.jni.HybridData
8
8
  import com.facebook.proguard.annotations.DoNotStrip
9
9
  import com.facebook.react.bridge.NativeModule
10
10
  import com.facebook.react.bridge.ReactApplicationContext
11
+ import com.facebook.react.bridge.UIManager
12
+ import com.facebook.react.bridge.UIManagerListener
11
13
  import com.facebook.react.bridge.UiThreadUtil
12
14
  import com.facebook.react.common.annotations.FrameworkAPI
15
+ import com.facebook.react.common.annotations.UnstableReactNativeAPI
13
16
  import com.facebook.react.fabric.FabricUIManager
14
17
  import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
15
18
  import com.facebook.react.uimanager.IllegalViewOperationException
@@ -30,6 +33,8 @@ import com.swmansion.reanimated.sensor.ReanimatedSensorContainer
30
33
  import com.swmansion.reanimated.sensor.ReanimatedSensorType
31
34
  import java.lang.ref.WeakReference
32
35
  import java.util.concurrent.atomic.AtomicBoolean
36
+ import java.util.concurrent.locks.ReentrantReadWriteLock
37
+ import kotlin.concurrent.write
33
38
 
34
39
  @Suppress("KotlinJniMissingFunction")
35
40
  @OptIn(FrameworkAPI::class)
@@ -64,10 +69,27 @@ open class NativeProxy {
64
69
  */
65
70
  private val mInvalidated = AtomicBoolean(false)
66
71
 
72
+ // Guards the C++ state that invalidateCpp() destroys against calls from the UI thread.
73
+ private val mNativeStateLock = ReentrantReadWriteLock()
74
+
67
75
  @field:DoNotStrip
68
76
  @Suppress("unused")
69
77
  private val mHybridData: HybridData
70
78
 
79
+ @OptIn(UnstableReactNativeAPI::class)
80
+ private val mountListener =
81
+ object : UIManagerListener {
82
+ override fun willDispatchViewUpdates(uiManager: UIManager) = Unit
83
+
84
+ override fun willMountItems(uiManager: UIManager) = Unit
85
+
86
+ override fun didMountItems(uiManager: UIManager) = ifNotInvalidated(Unit) { rewriteSynchronousProps() }
87
+
88
+ override fun didDispatchMountItems(uiManager: UIManager) = Unit
89
+
90
+ override fun didScheduleMountItems(uiManager: UIManager) = Unit
91
+ }
92
+
71
93
  constructor(context: ReactApplicationContext, nodesManager: NodesManager) {
72
94
  context.assertOnJSQueueThread()
73
95
 
@@ -109,6 +131,10 @@ open class NativeProxy {
109
131
  if (BuildConfig.DEBUG) {
110
132
  checkCppVersion() // injectCppVersion should be called during initHybrid above
111
133
  }
134
+ if (hasSynchronousWritesTracker()) {
135
+ @OptIn(UnstableReactNativeAPI::class)
136
+ mFabricUIManager.addUIManagerEventListener(mountListener)
137
+ }
112
138
  }
113
139
 
114
140
  private external fun initHybrid(
@@ -117,31 +143,67 @@ open class NativeProxy {
117
143
  fabricUIManager: FabricUIManager,
118
144
  ): HybridData
119
145
 
120
- external fun isAnyHandlerWaitingForEvent(
146
+ private external fun isAnyHandlerWaitingForEventCpp(
121
147
  eventName: String,
122
148
  emitterReactTag: Int,
123
149
  ): Boolean
124
150
 
125
- external fun performOperations()
151
+ private external fun performOperationsCpp()
152
+
153
+ private external fun performNonLayoutOperationsCpp()
154
+
155
+ private external fun hasSynchronousWritesTracker(): Boolean
126
156
 
127
- external fun performNonLayoutOperations()
157
+ private external fun rewriteSynchronousProps()
128
158
 
129
159
  external fun installJSIBindings()
130
160
 
131
161
  private external fun invalidateCpp()
132
162
 
133
- external fun toggleSlowAnimationsOnUIRuntime()
163
+ private external fun toggleSlowAnimationsOnUIRuntime()
134
164
 
135
165
  protected fun getHybridData(): HybridData = mHybridData
136
166
 
167
+ // tryLock, because the UI thread may hold the UI runtime lock, which could deadlock with invalidate()
168
+ private inline fun <T> ifNotInvalidated(
169
+ fallback: T,
170
+ block: () -> T,
171
+ ): T {
172
+ val readLock = mNativeStateLock.readLock()
173
+ if (!readLock.tryLock()) {
174
+ return fallback
175
+ }
176
+ try {
177
+ if (mInvalidated.get()) {
178
+ return fallback
179
+ }
180
+ return block()
181
+ } finally {
182
+ readLock.unlock()
183
+ }
184
+ }
185
+
186
+ fun isAnyHandlerWaitingForEvent(
187
+ eventName: String,
188
+ emitterReactTag: Int,
189
+ ): Boolean = ifNotInvalidated(false) { isAnyHandlerWaitingForEventCpp(eventName, emitterReactTag) }
190
+
191
+ fun performOperations() = ifNotInvalidated(Unit) { performOperationsCpp() }
192
+
193
+ fun performNonLayoutOperations() = ifNotInvalidated(Unit) { performNonLayoutOperationsCpp() }
194
+
137
195
  fun invalidate() {
138
196
  if (mInvalidated.getAndSet(true)) {
139
197
  return
140
198
  }
199
+ @OptIn(UnstableReactNativeAPI::class)
200
+ mFabricUIManager.removeUIManagerEventListener(mountListener)
141
201
  pseudoSelectorManager.invalidate()
142
202
  cssPlatformTransitionsManager.invalidate()
143
- if (mHybridData.isValid) {
144
- invalidateCpp()
203
+ mNativeStateLock.write {
204
+ if (mHybridData.isValid) {
205
+ invalidateCpp()
206
+ }
145
207
  }
146
208
  }
147
209
 
@@ -152,7 +214,7 @@ open class NativeProxy {
152
214
  }
153
215
  mNodesManager!!.enableSlowAnimations(slowAnimationsEnabled, animationsDragFactor)
154
216
  cssPlatformTransitionsManager.enableSlowAnimations(slowAnimationsEnabled, animationsDragFactor)
155
- toggleSlowAnimationsOnUIRuntime()
217
+ ifNotInvalidated(Unit) { toggleSlowAnimationsOnUIRuntime() }
156
218
  }
157
219
 
158
220
  private fun addDevMenuOption() {
@@ -175,8 +237,12 @@ open class NativeProxy {
175
237
 
176
238
  @DoNotStrip
177
239
  fun requestRender(callback: AnimationFrameCallback) {
240
+ val guardedCallback =
241
+ object : NodesManager.OnAnimationFrame {
242
+ override fun onAnimationFrame(timestampMs: Double) = ifNotInvalidated(Unit) { callback.onAnimationFrame(timestampMs) }
243
+ }
178
244
  UiThreadUtil.assertOnUiThread()
179
- mNodesManager!!.postOnAnimation(callback)
245
+ mNodesManager!!.postOnAnimation(guardedCallback)
180
246
  }
181
247
 
182
248
  @DoNotStrip fun getReanimatedJavaVersion(): String = BuildConfig.REANIMATED_VERSION_JAVA
@@ -250,6 +316,10 @@ open class NativeProxy {
250
316
  }.apply { isAccessible = true }
251
317
  }
252
318
 
319
+ private val getViewExistsMethod by lazy {
320
+ runCatching { mountingManager.javaClass.getMethod("getViewExists", Int::class.javaPrimitiveType) }.getOrNull()
321
+ }
322
+
253
323
  @DoNotStrip
254
324
  fun synchronouslyUpdateUIProps(
255
325
  intBuffer: IntArray,
@@ -258,9 +328,13 @@ open class NativeProxy {
258
328
  cssPlatformTransitionsManager.onPropsWrittenSynchronously()
259
329
  SynchronousPropsBufferParser.parse(intBuffer, doubleBuffer) { viewTag, props ->
260
330
  try {
331
+ // The props stay in the registry, and the commit hook applies them to the shadow tree.
332
+ if (getViewExistsMethod?.invoke(mountingManager, viewTag) == false) {
333
+ return@parse
334
+ }
261
335
  updatePropsSynchronouslyMethod.invoke(mountingManager, viewTag, props)
262
336
  } catch (e: Exception) {
263
- Log.w("Reanimated", "synchronouslyUpdateUIProps failed for tag $viewTag", e)
337
+ Log.w("Reanimated", "synchronouslyUpdateUIProps failed for tag $viewTag: ${e.cause ?: e}")
264
338
  }
265
339
  }
266
340
  }
@@ -330,6 +404,7 @@ open class NativeProxy {
330
404
  fun registerEventHandler(handler: EventHandler) {
331
405
  handler.mCustomEventNamesResolver = mNodesManager!!.getEventNameResolver()
332
406
  handler.isInDrawPassProvider = { mNodesManager!!.isInDrawPass() }
407
+ handler.nativeCallGuard = { receive -> ifNotInvalidated(Unit, receive) }
333
408
  mNodesManager!!.registerEventHandler(handler)
334
409
  }
335
410
 
@@ -199,9 +199,7 @@ class NodesManager(
199
199
  Trace.beginSection("onEventDispatch")
200
200
  }
201
201
 
202
- if (mNativeProxy == null) {
203
- return
204
- }
202
+ val nativeProxy = mNativeProxy ?: return
205
203
  // Events can be dispatched from any thread so we have to make sure handleEvent is run from
206
204
  // the UI thread.
207
205
  if (UiThreadUtil.isOnUiThread()) {
@@ -213,7 +211,7 @@ class NodesManager(
213
211
  } else {
214
212
  val eventName = mCustomEventNamesResolver.resolveCustomEventName(event.eventName) ?: return
215
213
  val viewTag = event.viewTag
216
- val shouldSaveEvent = mNativeProxy!!.isAnyHandlerWaitingForEvent(eventName, viewTag)
214
+ val shouldSaveEvent = nativeProxy.isAnyHandlerWaitingForEvent(eventName, viewTag)
217
215
  if (shouldSaveEvent) {
218
216
  mEventQueue.offer(CopiedEvent(event))
219
217
  }
@@ -14,6 +14,7 @@ class EventHandler : RCTModernEventEmitter {
14
14
 
15
15
  var mCustomEventNamesResolver: UIManagerModule.CustomEventNamesResolver? = null
16
16
  internal var isInDrawPassProvider: (() -> Boolean)? = null
17
+ internal var nativeCallGuard: (() -> Unit) -> Unit = { it() }
17
18
 
18
19
  @DoNotStrip
19
20
  private constructor(hybridData: HybridData) {
@@ -32,7 +33,7 @@ class EventHandler : RCTModernEventEmitter {
32
33
  category: Int,
33
34
  ) {
34
35
  val resolvedEventName = mCustomEventNamesResolver!!.resolveCustomEventName(eventName) ?: eventName
35
- receiveEvent(resolvedEventName, targetTag, params, isInDrawPass())
36
+ nativeCallGuard { receiveEvent(resolvedEventName, targetTag, params, isInDrawPass()) }
36
37
  }
37
38
 
38
39
  override fun receiveEvent(
@@ -42,7 +43,7 @@ class EventHandler : RCTModernEventEmitter {
42
43
  params: WritableMap?,
43
44
  ) {
44
45
  val resolvedEventName = mCustomEventNamesResolver!!.resolveCustomEventName(eventName) ?: eventName
45
- receiveEvent(resolvedEventName, targetTag, params, isInDrawPass())
46
+ nativeCallGuard { receiveEvent(resolvedEventName, targetTag, params, isInDrawPass()) }
46
47
  }
47
48
 
48
49
  override fun receiveEvent(
@@ -51,7 +52,7 @@ class EventHandler : RCTModernEventEmitter {
51
52
  params: WritableMap?,
52
53
  ) {
53
54
  val resolvedEventName = mCustomEventNamesResolver!!.resolveCustomEventName(eventName) ?: eventName
54
- receiveEvent(resolvedEventName, targetTag, params, isInDrawPass())
55
+ nativeCallGuard { receiveEvent(resolvedEventName, targetTag, params, isInDrawPass()) }
55
56
  }
56
57
 
57
58
  override fun receiveTouches(
@@ -26,7 +26,7 @@ internal class ReanimatedSensor(
26
26
  init {
27
27
  val wm = reactContext.get()!!.getSystemService(Context.WINDOW_SERVICE) as WindowManager
28
28
  val display = wm.defaultDisplay
29
- listener = ReanimatedSensorListener(setter, interval.toDouble(), display)
29
+ listener = ReanimatedSensorListener(setter, interval.toDouble(), display, sensorType)
30
30
  sensorManager =
31
31
  reactContext.get()!!.getSystemService(Context.SENSOR_SERVICE) as SensorManager
32
32
  this.interval = if (interval == -1) DEFAULT_INTERVAL else interval