react-native-reanimated 4.3.1 → 4.3.3

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 (84) hide show
  1. package/Common/cpp/reanimated/CSS/interpolation/transforms/TransformOperationInterpolator.cpp +12 -5
  2. package/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.cpp +59 -37
  3. package/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.h +11 -8
  4. package/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.cpp +4 -0
  5. package/Common/cpp/reanimated/CSS/registries/StaticPropsRegistry.cpp +17 -4
  6. package/Common/cpp/reanimated/CSS/registries/StaticPropsRegistry.h +5 -0
  7. package/Common/cpp/reanimated/CSS/svg/values/SVGStrokeDashArray.cpp +5 -0
  8. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.cpp +25 -12
  9. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.h +3 -0
  10. package/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp +48 -23
  11. package/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h +13 -6
  12. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h +38 -0
  13. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +106 -33
  14. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp +205 -92
  15. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h +4 -0
  16. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +5 -11
  17. package/android/CMakeLists.txt +4 -1
  18. package/android/build.gradle +2 -0
  19. package/android/generate-stub-pch.gradle.kts +84 -0
  20. package/android/src/main/cpp/ReanimatedPCH.h +46 -0
  21. package/android/src/main/java/com/swmansion/reanimated/NativeProxy.java +14 -1
  22. package/android/src/main/java/com/swmansion/reanimated/keyboard/KeyboardAnimationCallback.java +9 -1
  23. package/apple/reanimated/apple/ReanimatedModule.mm +12 -1
  24. package/lib/module/PropsRegistryGarbageCollector.js +3 -6
  25. package/lib/module/PropsRegistryGarbageCollector.js.map +1 -1
  26. package/lib/module/animation/styleAnimation.js +4 -3
  27. package/lib/module/animation/styleAnimation.js.map +1 -1
  28. package/lib/module/common/constants/platform.js +5 -5
  29. package/lib/module/common/constants/platform.js.map +1 -1
  30. package/lib/module/createAnimatedComponent/getViewInfo.js +3 -1
  31. package/lib/module/createAnimatedComponent/getViewInfo.js.map +1 -1
  32. package/lib/module/css/component/AnimatedComponent.js +0 -2
  33. package/lib/module/css/component/AnimatedComponent.js.map +1 -1
  34. package/lib/module/css/native/managers/CSSManager.js +5 -5
  35. package/lib/module/css/native/managers/CSSManager.js.map +1 -1
  36. package/lib/module/css/native/managers/CSSTransitionsManager.js +8 -1
  37. package/lib/module/css/native/managers/CSSTransitionsManager.js.map +1 -1
  38. package/lib/module/css/svg/native/processors/stroke.js +9 -5
  39. package/lib/module/css/svg/native/processors/stroke.js.map +1 -1
  40. package/lib/module/layoutReanimation/animationsManager.js +9 -6
  41. package/lib/module/layoutReanimation/animationsManager.js.map +1 -1
  42. package/lib/module/platform-specific/findHostInstance.js +8 -5
  43. package/lib/module/platform-specific/findHostInstance.js.map +1 -1
  44. package/lib/module/platform-specific/jsVersion.js +1 -1
  45. package/lib/module/updateProps/updateProps.js +3 -2
  46. package/lib/module/updateProps/updateProps.js.map +1 -1
  47. package/lib/module/valueSetter.js +1 -1
  48. package/lib/module/valueSetter.js.map +1 -1
  49. package/lib/typescript/PropsRegistryGarbageCollector.d.ts +0 -1
  50. package/lib/typescript/PropsRegistryGarbageCollector.d.ts.map +1 -1
  51. package/lib/typescript/animation/styleAnimation.d.ts +1 -1
  52. package/lib/typescript/animation/styleAnimation.d.ts.map +1 -1
  53. package/lib/typescript/common/constants/platform.d.ts.map +1 -1
  54. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts +0 -1
  55. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts.map +1 -1
  56. package/lib/typescript/createAnimatedComponent/getViewInfo.d.ts.map +1 -1
  57. package/lib/typescript/css/component/AnimatedComponent.d.ts +0 -1
  58. package/lib/typescript/css/component/AnimatedComponent.d.ts.map +1 -1
  59. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts +5 -1
  60. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts.map +1 -1
  61. package/lib/typescript/css/svg/native/processors/stroke.d.ts.map +1 -1
  62. package/lib/typescript/layoutReanimation/animationsManager.d.ts.map +1 -1
  63. package/lib/typescript/platform-specific/findHostInstance.d.ts.map +1 -1
  64. package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
  65. package/lib/typescript/platform-specific/types.d.ts +1 -0
  66. package/lib/typescript/platform-specific/types.d.ts.map +1 -1
  67. package/lib/typescript/updateProps/updateProps.d.ts.map +1 -1
  68. package/lib/typescript/valueSetter.d.ts.map +1 -1
  69. package/package.json +2 -2
  70. package/src/PropsRegistryGarbageCollector.ts +3 -6
  71. package/src/animation/styleAnimation.ts +5 -3
  72. package/src/common/constants/platform.ts +6 -5
  73. package/src/createAnimatedComponent/commonTypes.ts +0 -1
  74. package/src/createAnimatedComponent/getViewInfo.ts +3 -1
  75. package/src/css/component/AnimatedComponent.tsx +0 -2
  76. package/src/css/native/managers/CSSManager.ts +8 -8
  77. package/src/css/native/managers/CSSTransitionsManager.ts +9 -2
  78. package/src/css/svg/native/processors/stroke.ts +9 -6
  79. package/src/layoutReanimation/animationsManager.ts +9 -7
  80. package/src/platform-specific/findHostInstance.ts +7 -7
  81. package/src/platform-specific/jsVersion.ts +1 -1
  82. package/src/platform-specific/types.ts +2 -0
  83. package/src/updateProps/updateProps.ts +3 -2
  84. package/src/valueSetter.ts +2 -1
@@ -5,6 +5,8 @@
5
5
  #include <reanimated/CSS/interpolation/transforms/operations/skew.h>
6
6
  #include <reanimated/CSS/interpolation/transforms/operations/translate.h>
7
7
 
8
+ #include <cmath>
9
+ #include <limits>
8
10
  #include <utility>
9
11
 
10
12
  namespace reanimated::css {
@@ -31,14 +33,19 @@ std::unique_ptr<StyleOperation> TransformOperationInterpolator<PerspectiveOperat
31
33
  const std::shared_ptr<StyleOperation> &from,
32
34
  const std::shared_ptr<StyleOperation> &to,
33
35
  const StyleOperationsInterpolationContext & /* context */) const {
34
- // TODO - check if this implementation is correct
35
36
  const auto &fromValue = std::static_pointer_cast<PerspectiveOperation>(from)->value;
36
37
  const auto &toValue = std::static_pointer_cast<PerspectiveOperation>(to)->value;
37
38
 
38
- if (fromValue.value == 0)
39
- return std::make_unique<PerspectiveOperation>(toValue);
40
- if (toValue.value == 0)
41
- return std::make_unique<PerspectiveOperation>(fromValue);
39
+ // The default "no perspective" is infinity, so interpolating the distance
40
+ // directly gives inf + t*(d - inf) = NaN. Interpolate in reciprocal space
41
+ // instead (1/inf = 0, matching how perspective enters the matrix as -1/d).
42
+ if (std::isinf(fromValue.value) || std::isinf(toValue.value)) {
43
+ const double fromReciprocal = 1.0 / fromValue.value;
44
+ const double toReciprocal = 1.0 / toValue.value;
45
+ const double reciprocal = fromReciprocal + progress * (toReciprocal - fromReciprocal);
46
+ return std::make_unique<PerspectiveOperation>(
47
+ reciprocal == 0.0 ? std::numeric_limits<double>::infinity() : 1.0 / reciprocal);
48
+ }
42
49
 
43
50
  return std::make_unique<PerspectiveOperation>(fromValue.interpolate(progress, toValue));
44
51
  }
@@ -13,13 +13,11 @@ ViewStylesRepository::ViewStylesRepository(
13
13
  jsi::Value ViewStylesRepository::getNodeProp(
14
14
  const std::shared_ptr<const ShadowNode> &shadowNode,
15
15
  const std::string &propName) {
16
- int tag = shadowNode->getTag();
17
-
18
- auto &cachedNode = shadowNodeCache_[tag];
19
- updateCacheIfNeeded(cachedNode, shadowNode);
16
+ const auto resolvedNode = getNewestNode(shadowNode);
17
+ const auto *layoutableShadowNode = dynamic_cast<const LayoutableShadowNode *>(resolvedNode.get());
20
18
 
21
19
  if (propName == "width" || propName == "height" || propName == "top" || propName == "left") {
22
- const auto &layoutMetrics = cachedNode.layoutMetrics;
20
+ const auto layoutMetrics = layoutableShadowNode ? layoutableShadowNode->layoutMetrics_ : LayoutMetrics{};
23
21
 
24
22
  if (propName == "width") {
25
23
  return {layoutMetrics.frame.size.width};
@@ -31,7 +29,13 @@ jsi::Value ViewStylesRepository::getNodeProp(
31
29
  return {layoutMetrics.frame.origin.x};
32
30
  }
33
31
  } else {
34
- const auto &viewProps = cachedNode.viewProps;
32
+ if (!layoutableShadowNode) {
33
+ return jsi::Value::undefined();
34
+ }
35
+ const auto viewProps = std::static_pointer_cast<const ViewProps>(resolvedNode->getProps());
36
+ if (!viewProps) {
37
+ return jsi::Value::undefined();
38
+ }
35
39
 
36
40
  if (propName == "opacity") {
37
41
  return {viewProps->opacity};
@@ -48,15 +52,7 @@ jsi::Value ViewStylesRepository::getNodeProp(
48
52
  jsi::Value ViewStylesRepository::getParentNodeProp(
49
53
  const std::shared_ptr<const ShadowNode> &shadowNode,
50
54
  const std::string &propName) {
51
- const auto surfaceId = shadowNode->getSurfaceId();
52
- const auto &shadowTreeRegistry = uiManager_->getShadowTreeRegistry();
53
-
54
- std::shared_ptr<const ShadowNode> parentNode = nullptr;
55
-
56
- shadowTreeRegistry.visit(surfaceId, [&](ShadowTree const &shadowTree) {
57
- auto currentRevision = shadowTree.getCurrentRevision();
58
- parentNode = dom::getParentNode(currentRevision.rootShadowNode, *shadowNode);
59
- });
55
+ const auto parentNode = getParentNode(shadowNode);
60
56
 
61
57
  if (!parentNode) {
62
58
  return jsi::Value::undefined();
@@ -65,37 +61,63 @@ jsi::Value ViewStylesRepository::getParentNodeProp(
65
61
  return getNodeProp(parentNode, propName);
66
62
  }
67
63
 
68
- folly::dynamic ViewStylesRepository::getStyleProp(const Tag tag, const PropertyPath &propertyPath) {
69
- auto animatedValue = getPropertyValue(animatedPropsRegistry_->get(tag), propertyPath);
70
- if (!animatedValue.isNull()) {
71
- return animatedValue;
64
+ std::shared_ptr<const ShadowNode> ViewStylesRepository::getNewestNode(
65
+ const std::shared_ptr<const ShadowNode> &shadowNode) const {
66
+ // Resolve shadowNode against the last mounted root instead of reading the live
67
+ // ShadowTree: uiManager_->getNewestCloneOfShadowNode takes the ShadowTree commit
68
+ // lock, and this method runs under the updates-registry lock while a concurrent
69
+ // Fabric commit takes the same two locks in the opposite order (the ANR
70
+ // deadlock). Falls back to the passed node. Mirrors RN's getShadowNodeInSubtree:
71
+ // https://github.com/facebook/react-native/blob/v0.86.0-rc.3/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp#L339
72
+ RootShadowNode::Shared root;
73
+ {
74
+ const std::lock_guard<std::mutex> lock{lastMountedRootMutex_};
75
+ const auto it = lastMountedRootBySurface_.find(shadowNode->getSurfaceId());
76
+ if (it == lastMountedRootBySurface_.end()) {
77
+ return shadowNode;
78
+ }
79
+ root = it->second;
72
80
  }
73
81
 
74
- return getPropertyValue(staticPropsRegistry_->get(tag), propertyPath);
75
- }
82
+ if (ShadowNode::sameFamily(*root, *shadowNode)) {
83
+ return root;
84
+ }
76
85
 
77
- void ViewStylesRepository::clearNodesCache() {
78
- shadowNodeCache_.clear();
79
- }
86
+ const auto ancestors = shadowNode->getFamily().getAncestors(*root);
87
+ if (ancestors.empty()) {
88
+ return shadowNode;
89
+ }
80
90
 
81
- void ViewStylesRepository::updateCacheIfNeeded(
82
- CachedShadowNode &cachedNode,
83
- const std::shared_ptr<const ShadowNode> &shadowNode) {
84
- auto newestCloneOfShadowNode = uiManager_->getNewestCloneOfShadowNode(*shadowNode);
91
+ const auto &deepest = ancestors.back();
92
+ return deepest.first.get().getChildren().at(deepest.second);
93
+ }
85
94
 
86
- // Check if newestCloneOfShadowNode is valid (is already mounted / not
87
- // yet unmounted)
88
- if (!newestCloneOfShadowNode) {
89
- return;
95
+ std::shared_ptr<const ShadowNode> ViewStylesRepository::getParentNode(
96
+ const std::shared_ptr<const ShadowNode> &shadowNode) const {
97
+ RootShadowNode::Shared root;
98
+ {
99
+ const std::lock_guard<std::mutex> lock{lastMountedRootMutex_};
100
+ const auto it = lastMountedRootBySurface_.find(shadowNode->getSurfaceId());
101
+ if (it == lastMountedRootBySurface_.end()) {
102
+ return nullptr;
103
+ }
104
+ root = it->second;
90
105
  }
106
+ return dom::getParentNode(root, *shadowNode);
107
+ }
108
+
109
+ void ViewStylesRepository::setLastMountedRoot(const RootShadowNode::Shared &rootShadowNode) {
110
+ const std::lock_guard<std::mutex> lock{lastMountedRootMutex_};
111
+ lastMountedRootBySurface_[rootShadowNode->getSurfaceId()] = rootShadowNode;
112
+ }
91
113
 
92
- auto layoutableShadowNode = dynamic_cast<const LayoutableShadowNode *>(newestCloneOfShadowNode.get());
93
- if (!layoutableShadowNode) {
94
- return;
114
+ folly::dynamic ViewStylesRepository::getStyleProp(const Tag tag, const PropertyPath &propertyPath) {
115
+ auto animatedValue = getPropertyValue(animatedPropsRegistry_->get(tag), propertyPath);
116
+ if (!animatedValue.isNull()) {
117
+ return animatedValue;
95
118
  }
96
119
 
97
- cachedNode.layoutMetrics = layoutableShadowNode->layoutMetrics_;
98
- cachedNode.viewProps = std::static_pointer_cast<const ViewProps>(newestCloneOfShadowNode->getProps());
120
+ return getPropertyValue(staticPropsRegistry_->get(tag), propertyPath);
99
121
  }
100
122
 
101
123
  folly::dynamic ViewStylesRepository::getPropertyValue(const folly::dynamic &value, const PropertyPath &propertyPath) {
@@ -4,11 +4,14 @@
4
4
  #include <reanimated/CSS/registries/StaticPropsRegistry.h>
5
5
  #include <reanimated/Fabric/updates/AnimatedPropsRegistry.h>
6
6
 
7
+ #include <react/renderer/components/root/RootShadowNode.h>
7
8
  #include <react/renderer/components/view/ViewProps.h>
8
9
  #include <react/renderer/core/LayoutableShadowNode.h>
9
10
  #include <react/renderer/dom/DOM.h>
11
+ #include <react/renderer/uimanager/UIManager.h>
10
12
 
11
13
  #include <memory>
14
+ #include <mutex>
12
15
  #include <string>
13
16
  #include <unordered_map>
14
17
 
@@ -17,11 +20,6 @@ namespace reanimated::css {
17
20
  using namespace facebook;
18
21
  using namespace react;
19
22
 
20
- struct CachedShadowNode {
21
- LayoutMetrics layoutMetrics;
22
- std::shared_ptr<const ViewProps> viewProps;
23
- };
24
-
25
23
  class ViewStylesRepository {
26
24
  public:
27
25
  ViewStylesRepository(
@@ -36,16 +34,21 @@ class ViewStylesRepository {
36
34
  jsi::Value getParentNodeProp(const std::shared_ptr<const ShadowNode> &shadowNode, const std::string &propName);
37
35
  folly::dynamic getStyleProp(Tag tag, const PropertyPath &propertyPath);
38
36
 
39
- void clearNodesCache();
37
+ void setLastMountedRoot(const RootShadowNode::Shared &rootShadowNode);
40
38
 
41
39
  private:
42
40
  std::shared_ptr<UIManager> uiManager_;
43
41
  std::shared_ptr<StaticPropsRegistry> staticPropsRegistry_;
44
42
  std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry_;
45
43
 
46
- std::unordered_map<int, CachedShadowNode> shadowNodeCache_;
44
+ // Transition setup resolves relative lengths on the JS thread without the
45
+ // updates-registry lock, while the mount hook records roots under it, so the
46
+ // map needs its own guard.
47
+ mutable std::mutex lastMountedRootMutex_;
48
+ std::unordered_map<SurfaceId, RootShadowNode::Shared> lastMountedRootBySurface_;
47
49
 
48
- void updateCacheIfNeeded(CachedShadowNode &cachedNode, const std::shared_ptr<const ShadowNode> &shadowNode);
50
+ std::shared_ptr<const ShadowNode> getNewestNode(const std::shared_ptr<const ShadowNode> &shadowNode) const;
51
+ std::shared_ptr<const ShadowNode> getParentNode(const std::shared_ptr<const ShadowNode> &shadowNode) const;
49
52
 
50
53
  static folly::dynamic getPropertyValue(const folly::dynamic &value, const PropertyPath &propertyPath);
51
54
  };
@@ -293,6 +293,10 @@ void CSSAnimationsRegistry::applyViewAnimationsStyle(const Tag viewTag, const do
293
293
  }
294
294
 
295
295
  setInUpdatesRegistry(shadowNode, updatedStyle);
296
+
297
+ if (shadowNode && !updatedStyle.empty()) {
298
+ addUpdatesToBatch(shadowNode, updatedStyle);
299
+ }
296
300
  }
297
301
 
298
302
  void CSSAnimationsRegistry::activateDelayedAnimations(const double timestamp) {
@@ -7,16 +7,26 @@ namespace reanimated::css {
7
7
  void StaticPropsRegistry::set(jsi::Runtime &rt, const Tag viewTag, const jsi::Value &props) {
8
8
  if (props.isNull() || props.isUndefined()) {
9
9
  remove(viewTag);
10
- } else {
11
- const auto newProps = dynamicFromValue(rt, props);
12
- if (has(viewTag)) {
13
- notifyObservers(viewTag, get(viewTag), newProps);
10
+ return;
11
+ }
12
+
13
+ const auto newProps = dynamicFromValue(rt, props);
14
+ folly::dynamic oldProps;
15
+ {
16
+ std::lock_guard<std::mutex> lock{mutex_};
17
+ const auto it = registry_.find(viewTag);
18
+ if (it != registry_.end()) {
19
+ oldProps = it->second;
14
20
  }
15
21
  registry_[viewTag] = newProps;
16
22
  }
23
+ if (!oldProps.isNull()) {
24
+ notifyObservers(viewTag, oldProps, newProps);
25
+ }
17
26
  }
18
27
 
19
28
  folly::dynamic StaticPropsRegistry::get(const Tag viewTag) const {
29
+ std::lock_guard<std::mutex> lock{mutex_};
20
30
  auto it = registry_.find(viewTag);
21
31
  if (it == registry_.end()) {
22
32
  return nullptr;
@@ -25,14 +35,17 @@ folly::dynamic StaticPropsRegistry::get(const Tag viewTag) const {
25
35
  }
26
36
 
27
37
  bool StaticPropsRegistry::has(const Tag viewTag) const {
38
+ std::lock_guard<std::mutex> lock{mutex_};
28
39
  return registry_.find(viewTag) != registry_.end();
29
40
  }
30
41
 
31
42
  void StaticPropsRegistry::remove(const Tag viewTag) {
43
+ std::lock_guard<std::mutex> lock{mutex_};
32
44
  registry_.erase(viewTag);
33
45
  }
34
46
 
35
47
  bool StaticPropsRegistry::isEmpty() const {
48
+ std::lock_guard<std::mutex> lock{mutex_};
36
49
  return registry_.empty() && observers_.empty();
37
50
  }
38
51
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include <react/renderer/core/ShadowNode.h>
4
4
 
5
+ #include <mutex>
5
6
  #include <unordered_map>
6
7
 
7
8
  namespace reanimated::css {
@@ -24,6 +25,10 @@ class StaticPropsRegistry {
24
25
  void removeObserver(Tag viewTag);
25
26
 
26
27
  private:
28
+ /// registry_ is written on the JS thread (setViewStyle) and read on the UI
29
+ /// thread during interpolation and on the commit/mount thread during node
30
+ /// removals, so it needs its own guard. observers_ is JS-thread-only.
31
+ mutable std::mutex mutex_;
27
32
  std::unordered_map<Tag, folly::dynamic> registry_;
28
33
  std::unordered_map<Tag, PropsObserver> observers_;
29
34
 
@@ -43,6 +43,11 @@ bool SVGStrokeDashArray::canConstruct(const folly::dynamic &value) {
43
43
  }
44
44
 
45
45
  folly::dynamic SVGStrokeDashArray::toDynamic() const {
46
+ // Empty means "no dashing" - emit null instead of [], which react-native-svg
47
+ // would feed to Android's DashPathEffect that requires at least 2 intervals.
48
+ if (values.empty()) {
49
+ return nullptr;
50
+ }
46
51
  folly::dynamic array = folly::dynamic::array;
47
52
  array.reserve(values.size());
48
53
  for (const auto &value : values) {
@@ -9,8 +9,12 @@ namespace reanimated {
9
9
  ReanimatedMountHook::ReanimatedMountHook(
10
10
  const std::shared_ptr<UIManager> &uiManager,
11
11
  const std::shared_ptr<UpdatesRegistryManager> &updatesRegistryManager,
12
+ const std::shared_ptr<css::ViewStylesRepository> &viewStylesRepository,
12
13
  const std::function<void()> &requestFlush)
13
- : uiManager_(uiManager), updatesRegistryManager_(updatesRegistryManager), requestFlush_(requestFlush) {
14
+ : uiManager_(uiManager),
15
+ updatesRegistryManager_(updatesRegistryManager),
16
+ viewStylesRepository_(viewStylesRepository),
17
+ requestFlush_(requestFlush) {
14
18
  uiManager_->registerMountHook(*this);
15
19
  }
16
20
 
@@ -26,24 +30,33 @@ void ReanimatedMountHook::shadowTreeDidMount(
26
30
  auto reaShadowNode = std::reinterpret_pointer_cast<ReanimatedCommitShadowNode>(
27
31
  std::const_pointer_cast<RootShadowNode>(rootShadowNode));
28
32
 
29
- if (reaShadowNode->hasReanimatedMountTrait()) {
30
- // We mark reanimated commits with ReanimatedMountTrait. We don't want other
31
- // shadow nodes to use this trait, but since this rootShadowNode is Shared,
32
- // we don't have that guarantee. That's why we also unset this trait in the
33
- // commit hook. We remove it here mainly for the sake of cleanliness.
33
+ // We mark reanimated commits with ReanimatedMountTrait. We don't want other
34
+ // shadow nodes to use this trait, but since this rootShadowNode is Shared,
35
+ // we don't have that guarantee. That's why we also unset this trait in the
36
+ // commit hook. We remove it here mainly for the sake of cleanliness.
37
+ const bool isReanimatedMount = reaShadowNode->hasReanimatedMountTrait();
38
+ if (isReanimatedMount) {
34
39
  reaShadowNode->unsetReanimatedMountTrait();
35
- return;
36
40
  }
37
41
 
38
42
  {
39
43
  auto lock = updatesRegistryManager_->lock();
44
+ // Record the mounted tree for relative-length resolution.
45
+ viewStylesRepository_->setLastMountedRoot(rootShadowNode);
46
+
47
+ // Always drain removable nodes, even on Reanimated's own commits. While CSS
48
+ // animations run every mount carries the mount trait, so returning early here
49
+ // would skip removals for the whole animation and leak unmounted nodes if the
50
+ // tree is torn down mid-animation.
40
51
  updatesRegistryManager_->handleNodeRemovals(*rootShadowNode);
41
52
 
42
- // When commit from React Native has finished, we reset the skip commit flag
43
- // in order to allow Reanimated to commit its tree
44
- updatesRegistryManager_->unpauseReanimatedCommits();
45
- if (updatesRegistryManager_->shouldCommitAfterPause()) {
46
- requestFlush_();
53
+ if (!isReanimatedMount) {
54
+ // When a commit from React Native has finished, we reset the skip commit
55
+ // flag in order to allow Reanimated to commit its tree.
56
+ updatesRegistryManager_->unpauseReanimatedCommits();
57
+ if (updatesRegistryManager_->shouldCommitAfterPause()) {
58
+ requestFlush_();
59
+ }
47
60
  }
48
61
  }
49
62
  }
@@ -1,5 +1,6 @@
1
1
  #pragma once
2
2
 
3
+ #include <reanimated/CSS/misc/ViewStylesRepository.h>
3
4
  #include <reanimated/Fabric/ShadowTreeCloner.h>
4
5
  #include <reanimated/Fabric/updates/UpdatesRegistryManager.h>
5
6
 
@@ -16,6 +17,7 @@ class ReanimatedMountHook : public UIManagerMountHook {
16
17
  ReanimatedMountHook(
17
18
  const std::shared_ptr<UIManager> &uiManager,
18
19
  const std::shared_ptr<UpdatesRegistryManager> &updatesRegistryManager,
20
+ const std::shared_ptr<css::ViewStylesRepository> &viewStylesRepository,
19
21
  const std::function<void()> &requestFlush);
20
22
  ~ReanimatedMountHook() noexcept override;
21
23
 
@@ -24,6 +26,7 @@ class ReanimatedMountHook : public UIManagerMountHook {
24
26
  private:
25
27
  const std::shared_ptr<UIManager> uiManager_;
26
28
  const std::shared_ptr<UpdatesRegistryManager> updatesRegistryManager_;
29
+ const std::shared_ptr<css::ViewStylesRepository> viewStylesRepository_;
27
30
  const std::function<void()> requestFlush_;
28
31
  };
29
32
 
@@ -1,8 +1,10 @@
1
1
  #include <reanimated/Fabric/updates/AnimatedPropsRegistry.h>
2
2
  #include <reanimated/Tools/FeatureFlags.h>
3
3
 
4
+ #include <functional>
4
5
  #include <memory>
5
6
  #include <utility>
7
+ #include <vector>
6
8
 
7
9
  namespace reanimated {
8
10
 
@@ -25,25 +27,59 @@ void AnimatedPropsRegistry::update(jsi::Runtime &rt, const jsi::Value &operation
25
27
  addUpdatesToBatch(shadowNode, jsi::dynamicFromValue(rt, updates));
26
28
 
27
29
  if constexpr (StaticFeatureFlags::getFlag("FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS")) {
28
- timestampMap_[shadowNode->getTag()] = timestamp;
30
+ const auto tag = shadowNode->getTag();
31
+ timestampMap_[tag] = timestamp;
32
+ // If JS already has a `settledProps` snapshot for this tag, it is now
33
+ // stale — schedule a refresh on the next `collectSettledUpdates`.
34
+ if (syncedTags_.erase(tag) > 0) {
35
+ invalidatedTags_.insert(tag);
36
+ }
29
37
  }
30
38
  }
31
39
  }
32
40
 
33
- jsi::Value AnimatedPropsRegistry::getUpdatesOlderThanTimestamp(
34
- jsi::Runtime &rt,
35
- const double timestamp,
36
- const double cleanupTimestamp) {
41
+ jsi::Value AnimatedPropsRegistry::collectSettledUpdates(jsi::Runtime &rt, const double settledTimestamp) {
37
42
  std::lock_guard<std::mutex> lock{mutex_};
38
- removeUpdatesOlderThanTimestamp(cleanupTimestamp);
39
43
 
40
44
  std::vector<std::pair<Tag, std::reference_wrapper<const folly::dynamic>>> updates;
41
45
 
42
- for (const auto &[viewTag, pair] : updatesRegistry_) {
43
- auto it = timestampMap_.find(viewTag);
44
- if (it != timestampMap_.end() && it->second < timestamp) {
45
- updates.emplace_back(viewTag, std::cref(pair.second));
46
+ for (auto it = updatesRegistry_.begin(); it != updatesRegistry_.end();) {
47
+ const auto viewTag = it->first;
48
+
49
+ if (syncedTags_.contains(viewTag)) {
50
+ // React already has the latest value for this tag (synced on a previous
51
+ // call, so the `settledProps` state is committed by now) — the registry
52
+ // entry is redundant. `syncedTags_` is intentionally retained to detect
53
+ // re-animation staleness. Note that `syncedTags_` and `invalidatedTags_`
54
+ // are disjoint — `update()` moves tags from the former to the latter.
55
+ timestampMap_.erase(viewTag);
56
+ it = updatesRegistry_.erase(it);
57
+ continue;
58
+ }
59
+
60
+ const auto timestampIt = timestampMap_.find(viewTag);
61
+ if (timestampIt == timestampMap_.end()) {
62
+ ++it;
63
+ continue;
64
+ }
65
+ const bool isSettled = timestampIt->second < settledTimestamp;
66
+ const auto invalidatedIt = invalidatedTags_.find(viewTag);
67
+ const bool isInvalidated = invalidatedIt != invalidatedTags_.end();
68
+ if (isSettled || isInvalidated) {
69
+ updates.emplace_back(viewTag, std::cref(it->second.second));
70
+ if (isSettled) {
71
+ // Only settled-path tags are tracked as "synced" so that an ongoing
72
+ // animation doesn't re-trigger an invalidation/sync on every GC tick.
73
+ syncedTags_.insert(viewTag);
74
+ }
75
+ if (isInvalidated) {
76
+ // Only erase serviced invalidations; if a tag was invalidated but the
77
+ // matching update batch hasn't been flushed into updatesRegistry_ yet,
78
+ // we leave the entry so the next sync picks it up.
79
+ invalidatedTags_.erase(invalidatedIt);
80
+ }
46
81
  }
82
+ ++it;
47
83
  }
48
84
 
49
85
  const jsi::Array array(rt, updates.size());
@@ -58,22 +94,11 @@ jsi::Value AnimatedPropsRegistry::getUpdatesOlderThanTimestamp(
58
94
  return jsi::Value(rt, array);
59
95
  }
60
96
 
61
- void AnimatedPropsRegistry::removeUpdatesOlderThanTimestamp(const double timestamp) {
62
- for (auto it = timestampMap_.begin(); it != timestampMap_.end();) {
63
- const auto viewTag = it->first;
64
- const auto viewTimestamp = it->second;
65
- if (viewTimestamp < timestamp) {
66
- it = timestampMap_.erase(it);
67
- updatesRegistry_.erase(viewTag);
68
- } else {
69
- it++;
70
- }
71
- }
72
- }
73
-
74
97
  void AnimatedPropsRegistry::removeTag(const Tag tag) {
75
98
  updatesRegistry_.erase(tag);
76
99
  timestampMap_.erase(tag);
100
+ syncedTags_.erase(tag);
101
+ invalidatedTags_.erase(tag);
77
102
  }
78
103
 
79
104
  } // namespace reanimated
@@ -4,10 +4,8 @@
4
4
 
5
5
  #include <react/renderer/uimanager/UIManager.h>
6
6
 
7
- #include <memory>
8
- #include <string>
9
7
  #include <unordered_map>
10
- #include <vector>
8
+ #include <unordered_set>
11
9
 
12
10
  namespace reanimated {
13
11
 
@@ -15,13 +13,22 @@ class AnimatedPropsRegistry : public UpdatesRegistry {
15
13
  public:
16
14
  void update(jsi::Runtime &rt, const jsi::Value &operations, double timestamp);
17
15
 
18
- /// Also removes updates older than `cleanupTimestamp` from the registry.
19
- jsi::Value getUpdatesOlderThanTimestamp(jsi::Runtime &rt, double timestamp, double cleanupTimestamp);
16
+ /// Returns updates that settled (received no update since `settledTimestamp`)
17
+ /// or whose synced `settledProps` snapshot was invalidated by a fresh update.
18
+ /// Also evicts entries that have already been synced to React — by the time
19
+ /// of the next call, the corresponding `settledProps` state is guaranteed to
20
+ /// be committed, so the registry entries are redundant.
21
+ jsi::Value collectSettledUpdates(jsi::Runtime &rt, double settledTimestamp);
20
22
 
21
23
  private:
22
24
  std::unordered_map<Tag, double> timestampMap_; // viewTag -> timestamp, protected by `mutex_`
25
+ // Tags whose latest values have already been pushed to React `settledProps`.
26
+ // Intentionally retained after eviction to detect re-animation staleness.
27
+ std::unordered_set<Tag> syncedTags_;
28
+ // Tags that were synced to React but received a fresh worklet update since;
29
+ // their `settledProps` are stale and need to be refreshed on the next sync.
30
+ std::unordered_set<Tag> invalidatedTags_;
23
31
 
24
- void removeUpdatesOlderThanTimestamp(double timestamp);
25
32
  void removeTag(Tag tag) override;
26
33
  };
27
34
 
@@ -1,6 +1,7 @@
1
1
  #pragma once
2
2
 
3
3
  #include <jsi/jsi.h>
4
+ #include <react/debug/react_native_assert.h>
4
5
  #include <react/renderer/componentregistry/ComponentDescriptorFactory.h>
5
6
  #include <react/renderer/mounting/MountingOverrideDelegate.h>
6
7
  #include <react/renderer/uimanager/UIManager.h>
@@ -24,6 +25,31 @@ struct LayoutAnimation {
24
25
  LayoutAnimation &operator=(const LayoutAnimation &other) = default;
25
26
  };
26
27
 
28
+ #ifdef ANDROID
29
+ // Bookkeeping for animation starts that were scheduled onto the UI thread but
30
+ // haven't run yet — see `pendingStarts_` below.
31
+ struct PendingStart {
32
+ int count = 0;
33
+ uint64_t handle = 0;
34
+ };
35
+
36
+ // Removes one pending start for the given tag and returns whether it was
37
+ // cancelled since it was scheduled (i.e. its handle is no longer current).
38
+ // Call under the proxy mutex.
39
+ inline bool
40
+ consumeIsCancelled(std::unordered_map<Tag, PendingStart> &pendingStarts, const Tag tag, const uint64_t handle) {
41
+ const auto it = pendingStarts.find(tag);
42
+ // every scheduled start keeps its entry alive until it is consumed —
43
+ // cancellations only bump the handle, they never erase
44
+ react_native_assert(it != pendingStarts.end() && "PendingStart not found");
45
+ const bool isCancelled = it->second.handle != handle;
46
+ if (--it->second.count == 0) {
47
+ pendingStarts.erase(it);
48
+ }
49
+ return isCancelled;
50
+ }
51
+ #endif
52
+
27
53
  class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDelegate {
28
54
  public:
29
55
  LayoutAnimationsProxyCommon(
@@ -75,6 +101,18 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
75
101
 
76
102
  void restoreOpacityInCaseOfFlakyEnteringAnimation(SurfaceId surfaceId) const;
77
103
 
104
+ // On Android pullTransaction can run on the JS thread, so animation starts
105
+ // are scheduled onto the UI thread. If `maybeCancelAnimation` is called between the
106
+ // start was scheduled and the lambda runs, it
107
+ // finds no `layoutAnimations_` entry to erase (the start lambda hasn't created it
108
+ // yet) and the cancellation is lost — the stale start would later
109
+ // "resurrect" the animation for a view whose Remove+Delete are already on
110
+ // their way to the mounting layer
111
+ // (https://github.com/software-mansion/react-native-reanimated/issues/7493).
112
+
113
+ // To work around this, we keep a separate `pendingStarts_` map that tracks scheduled starts by tag,
114
+ // with a generation counter to detect cancellations.
115
+ mutable std::unordered_map<Tag, PendingStart> pendingStarts_;
78
116
  #endif
79
117
  };
80
118