react-native 0.84.0-nightly-20251204-5bb3a6d68 → 0.84.0-nightly-20251206-63b0aef13

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 (75) hide show
  1. package/Libraries/Animated/nodes/AnimatedProps.js +29 -3
  2. package/Libraries/Core/ReactNativeVersion.js +1 -1
  3. package/Libraries/NativeAnimation/RCTNativeAnimatedTurboModule.mm +7 -0
  4. package/Libraries/NativeAnimation/React-RCTAnimation.podspec +1 -0
  5. package/React/Base/RCTVersion.m +1 -1
  6. package/React/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm +7 -0
  7. package/React/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h +2 -0
  8. package/React/FBReactNativeSpec/FBReactNativeSpecJSI.h +18 -0
  9. package/React/Fabric/RCTSurfaceTouchHandler.mm +1 -1
  10. package/ReactAndroid/api/ReactAndroid.api +0 -2
  11. package/ReactAndroid/gradle.properties +1 -1
  12. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingsObserver.kt +85 -11
  13. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +7 -1
  14. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +11 -1
  15. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +3 -1
  16. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +3 -1
  17. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +12 -1
  18. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +3 -1
  19. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
  20. package/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt +7 -3
  21. package/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java +9 -91
  22. package/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +0 -2
  23. package/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java +4 -23
  24. package/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +1 -3
  25. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +1 -10
  26. package/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewAccessibilityDelegate.kt +1 -0
  27. package/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt +42 -28
  28. package/ReactAndroid/src/main/jni/CMakeLists.txt +7 -0
  29. package/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt +7 -0
  30. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +15 -1
  31. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +4 -1
  32. package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
  33. package/ReactCommon/jsi/jsi/jsi.cpp +2 -1
  34. package/ReactCommon/jsinspector-modern/HostAgent.cpp +2 -2
  35. package/ReactCommon/jsinspector-modern/InspectorInterfaces.cpp +10 -5
  36. package/ReactCommon/jsinspector-modern/InspectorInterfaces.h +2 -2
  37. package/ReactCommon/jsinspector-modern/TracingAgent.cpp +1 -1
  38. package/ReactCommon/jsinspector-modern/tracing/TraceEventGenerator.cpp +4 -4
  39. package/ReactCommon/jsinspector-modern/tracing/TracingCategory.h +11 -6
  40. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +5 -1
  41. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +6 -1
  42. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +51 -33
  43. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +4 -2
  44. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +5 -1
  45. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +10 -1
  46. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +2 -1
  47. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +6 -1
  48. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +3 -1
  49. package/ReactCommon/react/renderer/animated/AnimatedModule.cpp +19 -1
  50. package/ReactCommon/react/renderer/animated/AnimatedModule.h +8 -0
  51. package/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp +83 -9
  52. package/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h +6 -0
  53. package/ReactCommon/react/renderer/animated/drivers/FrameAnimationDriver.cpp +1 -0
  54. package/ReactCommon/react/renderer/animated/nodes/ColorAnimatedNode.cpp +0 -1
  55. package/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.h +1 -0
  56. package/ReactCommon/react/renderer/animated/nodes/RoundAnimatedNode.cpp +1 -1
  57. package/ReactCommon/react/renderer/animated/nodes/ValueAnimatedNode.cpp +1 -1
  58. package/ReactCommon/react/renderer/animated/tests/AnimatedNodeTests.cpp +67 -0
  59. package/ReactCommon/react/renderer/animated/tests/AnimationDriverTests.cpp +59 -0
  60. package/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp +81 -0
  61. package/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h +83 -0
  62. package/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +26 -15
  63. package/ReactCommon/react/renderer/animationbackend/AnimationBackend.h +7 -4
  64. package/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp +69 -0
  65. package/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h +32 -0
  66. package/ReactCommon/react/renderer/uimanager/UIManager.cpp +6 -0
  67. package/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h +1 -0
  68. package/package.json +9 -9
  69. package/scripts/cocoapods/utils.rb +2 -0
  70. package/sdks/hermes-engine/version.properties +1 -1
  71. package/src/private/animated/NativeAnimatedHelper.js +29 -1
  72. package/src/private/featureflags/ReactNativeFeatureFlags.js +6 -1
  73. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +2 -1
  74. package/src/private/specs_DEPRECATED/modules/NativeAnimatedModule.js +4 -0
  75. package/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeKind.kt +0 -32
@@ -238,6 +238,20 @@ void NativeAnimatedNodesManager::connectAnimatedNodeToView(
238
238
  }
239
239
  }
240
240
 
241
+ void NativeAnimatedNodesManager::connectAnimatedNodeToShadowNodeFamily(
242
+ Tag propsNodeTag,
243
+ std::shared_ptr<const ShadowNodeFamily> family) noexcept {
244
+ react_native_assert(propsNodeTag);
245
+ auto node = getAnimatedNode<PropsAnimatedNode>(propsNodeTag);
246
+ if (node != nullptr && family != nullptr) {
247
+ std::lock_guard<std::mutex> lock(tagToShadowNodeFamilyMutex_);
248
+ tagToShadowNodeFamily_[family->getTag()] = family;
249
+ } else {
250
+ LOG(WARNING)
251
+ << "Cannot ConnectAnimatedNodeToShadowNodeFamily, animated node has to be props type";
252
+ }
253
+ }
254
+
241
255
  void NativeAnimatedNodesManager::disconnectAnimatedNodeFromView(
242
256
  Tag propsNodeTag,
243
257
  Tag viewTag) noexcept {
@@ -251,6 +265,10 @@ void NativeAnimatedNodesManager::disconnectAnimatedNodeFromView(
251
265
  std::lock_guard<std::mutex> lock(connectedAnimatedNodesMutex_);
252
266
  connectedAnimatedNodes_.erase(viewTag);
253
267
  }
268
+ {
269
+ std::lock_guard<std::mutex> lock(tagToShadowNodeFamilyMutex_);
270
+ tagToShadowNodeFamily_.erase(viewTag);
271
+ }
254
272
  updatedNodeTags_.insert(node->tag());
255
273
 
256
274
  onManagedPropsRemoved(viewTag);
@@ -705,7 +723,8 @@ void NativeAnimatedNodesManager::updateNodes(
705
723
  for (auto childTag : nextNode.node->getChildren()) {
706
724
  auto child = getAnimatedNode<AnimatedNode>(childTag);
707
725
  child->activeIncomingNodes--;
708
- if (child->activeIncomingNodes == 0 && child->activeIncomingNodes == 0) {
726
+ if (child->activeIncomingNodes == 0 &&
727
+ child->bfsColor != animatedGraphBFSColor_) {
709
728
  child->bfsColor = animatedGraphBFSColor_;
710
729
  #ifdef REACT_NATIVE_DEBUG
711
730
  updatedNodesCount++;
@@ -985,15 +1004,47 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
985
1004
  }
986
1005
 
987
1006
  for (auto& [tag, props] : updateViewPropsDirect_) {
988
- // TODO: also handle layout props (updateViewProps_). It is skipped for
989
- // now, because the backend requires shadowNodeFamilies to be able to
990
- // commit to the ShadowTree
991
1007
  propsBuilder.storeDynamic(props);
992
1008
  mutations.push_back(
993
1009
  AnimationMutation{tag, nullptr, propsBuilder.get()});
994
1010
  containsChange = true;
995
1011
  }
996
- updateViewPropsDirect_.clear();
1012
+ {
1013
+ std::lock_guard<std::mutex> lock(tagToShadowNodeFamilyMutex_);
1014
+ for (auto& [tag, props] : updateViewProps_) {
1015
+ auto familyIt = tagToShadowNodeFamily_.find(tag);
1016
+ if (familyIt == tagToShadowNodeFamily_.end()) {
1017
+ continue;
1018
+ }
1019
+ if (auto family = familyIt->second.lock()) {
1020
+ // C++ Animated produces props in the form of a folly::dynamic, so
1021
+ // it wouldn't make sense to unpack it here. However, for the
1022
+ // purposes of testing, we want to be able to use the statically
1023
+ // typed AnimationMutation. At a later stage we will instead just
1024
+ // pass the dynamic directly to propsBuilder and the new API could
1025
+ // be used by 3rd party libraries or in the fututre by Animated.
1026
+ if (props.find("width") != props.items().end()) {
1027
+ propsBuilder.setWidth(
1028
+ yoga::Style::SizeLength::points(props["width"].asDouble()));
1029
+ }
1030
+ if (props.find("height") != props.items().end()) {
1031
+ propsBuilder.setHeight(
1032
+ yoga::Style::SizeLength::points(props["height"].asDouble()));
1033
+ }
1034
+ mutations.push_back(
1035
+ AnimationMutation{
1036
+ .tag = tag,
1037
+ .family = family,
1038
+ .props = propsBuilder.get(),
1039
+ });
1040
+ }
1041
+ containsChange = true;
1042
+ }
1043
+ }
1044
+ if (containsChange) {
1045
+ updateViewPropsDirect_.clear();
1046
+ updateViewProps_.clear();
1047
+ }
997
1048
  }
998
1049
 
999
1050
  if (!containsChange) {
@@ -1013,16 +1064,38 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
1013
1064
  }
1014
1065
  }
1015
1066
 
1016
- // Step 2: update all nodes that are connected to the finished animations.
1067
+ // Step 2: update all nodes that are connected to the finished
1068
+ // animations.
1017
1069
  updateNodes(finishedAnimationValueNodes);
1018
1070
 
1019
1071
  isEventAnimationInProgress_ = false;
1020
1072
 
1021
1073
  for (auto& [tag, props] : updateViewPropsDirect_) {
1022
- // TODO: handle layout props
1023
1074
  propsBuilder.storeDynamic(props);
1024
1075
  mutations.push_back(
1025
- AnimationMutation{tag, nullptr, propsBuilder.get()});
1076
+ AnimationMutation{
1077
+ .tag = tag,
1078
+ .family = nullptr,
1079
+ .props = propsBuilder.get(),
1080
+ });
1081
+ }
1082
+ {
1083
+ std::lock_guard<std::mutex> lock(tagToShadowNodeFamilyMutex_);
1084
+ for (auto& [tag, props] : updateViewProps_) {
1085
+ auto familyIt = tagToShadowNodeFamily_.find(tag);
1086
+ if (familyIt == tagToShadowNodeFamily_.end()) {
1087
+ continue;
1088
+ }
1089
+ if (auto family = familyIt->second.lock()) {
1090
+ propsBuilder.storeDynamic(props);
1091
+ mutations.push_back(
1092
+ AnimationMutation{
1093
+ .tag = tag,
1094
+ .family = family,
1095
+ .props = propsBuilder.get(),
1096
+ });
1097
+ }
1098
+ }
1026
1099
  }
1027
1100
  }
1028
1101
  } else {
@@ -1103,7 +1176,8 @@ void NativeAnimatedNodesManager::onRender() {
1103
1176
  }
1104
1177
  }
1105
1178
 
1106
- // Step 2: update all nodes that are connected to the finished animations.
1179
+ // Step 2: update all nodes that are connected to the finished
1180
+ // animations.
1107
1181
  updateNodes(finishedAnimationValueNodes);
1108
1182
 
1109
1183
  isEventAnimationInProgress_ = false;
@@ -21,6 +21,7 @@
21
21
  #include <react/renderer/animationbackend/AnimationBackend.h>
22
22
  #endif
23
23
  #include <react/renderer/core/ReactPrimitives.h>
24
+ #include <react/renderer/core/ShadowNode.h>
24
25
  #include <react/renderer/uimanager/UIManagerAnimationBackend.h>
25
26
  #include <chrono>
26
27
  #include <memory>
@@ -101,6 +102,8 @@ class NativeAnimatedNodesManager {
101
102
 
102
103
  void connectAnimatedNodeToView(Tag propsNodeTag, Tag viewTag) noexcept;
103
104
 
105
+ void connectAnimatedNodeToShadowNodeFamily(Tag propsNodeTag, std::shared_ptr<const ShadowNodeFamily> family) noexcept;
106
+
104
107
  void disconnectAnimatedNodes(Tag parentTag, Tag childTag) noexcept;
105
108
 
106
109
  void disconnectAnimatedNodeFromView(Tag propsNodeTag, Tag viewTag) noexcept;
@@ -258,6 +261,9 @@ class NativeAnimatedNodesManager {
258
261
  std::unordered_map<Tag, folly::dynamic> updateViewProps_{};
259
262
  std::unordered_map<Tag, folly::dynamic> updateViewPropsDirect_{};
260
263
 
264
+ mutable std::mutex tagToShadowNodeFamilyMutex_;
265
+ std::unordered_map<Tag, std::weak_ptr<const ShadowNodeFamily>> tagToShadowNodeFamily_{};
266
+
261
267
  /*
262
268
  * Sometimes a view is not longer connected to a PropsAnimatedNode, but
263
269
  * NativeAnimated has previously changed the view's props via direct
@@ -43,6 +43,7 @@ void FrameAnimationDriver::updateConfig(folly::dynamic config) {
43
43
  void FrameAnimationDriver::onConfigChanged() {
44
44
  auto frames = config_["frames"];
45
45
  react_native_assert(frames.type() == folly::dynamic::ARRAY);
46
+ frames_.clear();
46
47
  for (const auto& frame : frames) {
47
48
  auto frameValue = frame.asDouble();
48
49
  frames_.push_back(frameValue);
@@ -62,7 +62,6 @@ Color ColorAnimatedNode::getColor() {
62
62
  manager_->updatedNodeTags_.erase(tag_);
63
63
  }
64
64
  return color_;
65
- return 0;
66
65
  }
67
66
 
68
67
  } // namespace facebook::react
@@ -14,6 +14,7 @@
14
14
  #include "AnimatedNode.h"
15
15
 
16
16
  #include <react/renderer/animated/internal/primitives.h>
17
+ #include <react/renderer/core/ShadowNode.h>
17
18
  #include <mutex>
18
19
 
19
20
  namespace facebook::react {
@@ -22,7 +22,7 @@ RoundAnimatedNode::RoundAnimatedNode(
22
22
  NativeAnimatedNodesManager& manager)
23
23
  : ValueAnimatedNode(tag, config, manager),
24
24
  inputNodeTag_(static_cast<Tag>(getConfig()["input"].asInt())),
25
- nearest_(getConfig()["input"].asDouble()) {
25
+ nearest_(getConfig()["nearest"].asDouble()) {
26
26
  react_native_assert(
27
27
  nearest_ != 0 &&
28
28
  "'nearest' cannot be 0 (can't round to the nearest multiple of 0)");
@@ -55,7 +55,7 @@ bool ValueAnimatedNode::setOffset(double offset) noexcept {
55
55
  offset_ = offset;
56
56
  return true;
57
57
  }
58
- return true;
58
+ return false;
59
59
  }
60
60
 
61
61
  double ValueAnimatedNode::getValue() const noexcept {
@@ -215,6 +215,73 @@ TEST_F(AnimatedNodeTests, DiffClampAnimatedNode) {
215
215
  EXPECT_EQ(nodesManager_->getValue(diffClampTag), 1);
216
216
  }
217
217
 
218
+ TEST_F(AnimatedNodeTests, RoundAnimatedNodeUsesNearestConfigKey) {
219
+ // This test verifies that RoundAnimatedNode reads the "nearest" config key
220
+ // for the rounding factor, not the "input" key.
221
+ initNodesManager();
222
+
223
+ auto rootTag = getNextRootViewTag();
224
+
225
+ auto valueTag = ++rootTag;
226
+ auto roundTag = ++rootTag;
227
+
228
+ nodesManager_->createAnimatedNode(
229
+ valueTag,
230
+ folly::dynamic::object("type", "value")("value", 7.3)("offset", 0));
231
+
232
+ // The round node should read "nearest" for the rounding factor (5.0),
233
+ // not "input" (which is the valueTag integer).
234
+ nodesManager_->createAnimatedNode(
235
+ roundTag,
236
+ folly::dynamic::object("type", "round")("input", valueTag)(
237
+ "nearest", 5.0));
238
+ nodesManager_->connectAnimatedNodes(valueTag, roundTag);
239
+
240
+ runAnimationFrame(0);
241
+
242
+ // 7.3 rounded to nearest 5.0 should be 5.0 (since round(7.3/5) * 5 = 1 * 5)
243
+ // If the bug existed (reading "input" instead of "nearest"), it would use
244
+ // the valueTag as the rounding factor, giving incorrect results.
245
+ EXPECT_DOUBLE_EQ(nodesManager_->getValue(roundTag).value(), 5.0);
246
+
247
+ // Test another value to ensure rounding works correctly
248
+ nodesManager_->setAnimatedNodeValue(valueTag, 12.6);
249
+ runAnimationFrame(0);
250
+
251
+ // 12.6 rounded to nearest 5.0 should be 15.0 (since round(12.6/5) * 5 = 3 *
252
+ // 5)
253
+ EXPECT_DOUBLE_EQ(nodesManager_->getValue(roundTag).value(), 15.0);
254
+ }
255
+
256
+ TEST_F(AnimatedNodeTests, SetOffsetReturnsFalseWhenUnchanged) {
257
+ // This test verifies that setAnimatedNodeOffset doesn't trigger unnecessary
258
+ // updates when the offset value hasn't changed.
259
+ initNodesManager();
260
+
261
+ auto rootTag = getNextRootViewTag();
262
+ auto valueTag = ++rootTag;
263
+
264
+ nodesManager_->createAnimatedNode(
265
+ valueTag,
266
+ folly::dynamic::object("type", "value")("value", 10)("offset", 0));
267
+
268
+ runAnimationFrame(0);
269
+ EXPECT_EQ(nodeNeedsUpdate(valueTag), false);
270
+
271
+ // First setOffset should mark the node as needing update
272
+ nodesManager_->setAnimatedNodeOffset(valueTag, 5);
273
+ EXPECT_EQ(nodeNeedsUpdate(valueTag), true);
274
+ EXPECT_EQ(nodesManager_->getValue(valueTag), 15); // 10 + 5
275
+
276
+ runAnimationFrame(0);
277
+ EXPECT_EQ(nodeNeedsUpdate(valueTag), false);
278
+
279
+ // Setting the same offset again should NOT mark the node as needing update
280
+ nodesManager_->setAnimatedNodeOffset(valueTag, 5);
281
+ EXPECT_EQ(nodeNeedsUpdate(valueTag), false); // No change, no update needed
282
+ EXPECT_EQ(nodesManager_->getValue(valueTag), 15); // Still 10 + 5
283
+ }
284
+
218
285
  TEST_F(AnimatedNodeTests, ObjectAnimatedNode) {
219
286
  initNodesManager();
220
287
 
@@ -58,4 +58,63 @@ TEST_F(AnimationDriverTests, framesAnimation) {
58
58
  EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), toValue);
59
59
  }
60
60
 
61
+ TEST_F(AnimationDriverTests, framesAnimationReconfigurationClearsFrames) {
62
+ // This test verifies that when an animation is reconfigured via updateConfig,
63
+ // the frames array is cleared before adding new frames. Without clearing,
64
+ // frames would accumulate and cause incorrect animation behavior.
65
+ initNodesManager();
66
+
67
+ auto rootTag = getNextRootViewTag();
68
+
69
+ auto valueNodeTag = ++rootTag;
70
+ nodesManager_->createAnimatedNode(
71
+ valueNodeTag,
72
+ folly::dynamic::object("type", "value")("value", 0)("offset", 0));
73
+
74
+ const auto animationId = 1;
75
+ // First animation: 5 frames from 0 to 100
76
+ const auto frames1 = folly::dynamic::array(0.0f, 0.25f, 0.5f, 0.75f, 1.0f);
77
+ const auto toValue1 = 100;
78
+ nodesManager_->startAnimatingNode(
79
+ animationId,
80
+ valueNodeTag,
81
+ folly::dynamic::object("type", "frames")("frames", frames1)(
82
+ "toValue", toValue1),
83
+ std::nullopt);
84
+
85
+ const double startTimeInTick = 12345;
86
+
87
+ // Run first frame
88
+ runAnimationFrame(startTimeInTick);
89
+ EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), 0);
90
+
91
+ // Reconfigure the same animation (same animationId) with new frames
92
+ // This triggers updateConfig on the existing FrameAnimationDriver
93
+ const auto frames2 = folly::dynamic::array(0.0f, 0.5f, 1.0f);
94
+ const auto toValue2 = 200;
95
+ nodesManager_->startAnimatingNode(
96
+ animationId,
97
+ valueNodeTag,
98
+ folly::dynamic::object("type", "frames")("frames", frames2)(
99
+ "toValue", toValue2),
100
+ std::nullopt);
101
+
102
+ // Reset animation timing
103
+ const double newStartTimeInTick = 20000;
104
+
105
+ // Run animation at halfway point (1 frame into 3-frame animation)
106
+ runAnimationFrame(newStartTimeInTick);
107
+ runAnimationFrame(newStartTimeInTick + SingleFrameIntervalMs * 1);
108
+
109
+ // At frame 1 of 3 frames (50% progress), value should be approximately:
110
+ // startValue (0) + 0.5 * (toValue2 - startValue) = 0 + 0.5 * 200 = 100
111
+ // If frames accumulated (5 + 3 = 8 frames), we'd be at wrong position
112
+ // Use ceil rounding so 100.00x becomes 100.01
113
+ EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), 100.01);
114
+
115
+ // Complete the animation
116
+ runAnimationFrame(newStartTimeInTick + SingleFrameIntervalMs * 2);
117
+ EXPECT_EQ(round(nodesManager_->getValue(valueNodeTag).value()), toValue2);
118
+ }
119
+
61
120
  } // namespace facebook::react
@@ -0,0 +1,81 @@
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ #include "AnimatedPropsRegistry.h"
9
+ #include <react/renderer/core/PropsParserContext.h>
10
+ #include "AnimatedProps.h"
11
+
12
+ namespace facebook::react {
13
+
14
+ void AnimatedPropsRegistry::update(
15
+ const std::unordered_map<SurfaceId, SurfaceUpdates>& surfaceUpdates) {
16
+ auto lock = std::lock_guard(mutex_);
17
+ for (const auto& [surfaceId, updates] : surfaceUpdates) {
18
+ auto& surfaceContext = surfaceContexts_[surfaceId];
19
+ auto& pendingMap = surfaceContext.pendingMap;
20
+ auto& pendingFamilies = surfaceContext.pendingFamilies;
21
+
22
+ auto& updatesMap = updates.propsMap;
23
+ auto& updatesFamilies = updates.families;
24
+
25
+ for (auto& family : updatesFamilies) {
26
+ pendingFamilies.insert(family);
27
+ }
28
+
29
+ for (auto& [tag, animatedProps] : updatesMap) {
30
+ auto it = pendingMap.find(tag);
31
+ if (it == pendingMap.end()) {
32
+ it = pendingMap.insert_or_assign(tag, std::make_unique<PropsSnapshot>())
33
+ .first;
34
+ }
35
+ auto& snapshot = it->second;
36
+ auto& viewProps = snapshot->props;
37
+
38
+ for (const auto& animatedProp : animatedProps.props) {
39
+ snapshot->propNames.insert(animatedProp->propName);
40
+ cloneProp(viewProps, *animatedProp);
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ std::pair<std::unordered_set<const ShadowNodeFamily*>&, SnapshotMap&>
47
+ AnimatedPropsRegistry::getMap(SurfaceId surfaceId) {
48
+ auto lock = std::lock_guard(mutex_);
49
+ auto& [pendingMap, map, pendingFamilies, families] =
50
+ surfaceContexts_[surfaceId];
51
+
52
+ for (auto& family : pendingFamilies) {
53
+ families.insert(family);
54
+ }
55
+ for (auto& [tag, propsSnapshot] : pendingMap) {
56
+ auto currentIt = map.find(tag);
57
+ if (currentIt == map.end()) {
58
+ map.insert_or_assign(tag, std::move(propsSnapshot));
59
+ } else {
60
+ auto& currentSnapshot = currentIt->second;
61
+ for (auto& propName : propsSnapshot->propNames) {
62
+ currentSnapshot->propNames.insert(propName);
63
+ updateProp(propName, currentSnapshot->props, *propsSnapshot);
64
+ }
65
+ }
66
+ }
67
+ pendingMap.clear();
68
+ pendingFamilies.clear();
69
+
70
+ return {families, map};
71
+ }
72
+
73
+ void AnimatedPropsRegistry::clear(SurfaceId surfaceId) {
74
+ auto lock = std::lock_guard(mutex_);
75
+
76
+ auto& surfaceContext = surfaceContexts_[surfaceId];
77
+ surfaceContext.families.clear();
78
+ surfaceContext.map.clear();
79
+ }
80
+
81
+ } // namespace facebook::react
@@ -0,0 +1,83 @@
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ #pragma once
9
+
10
+ #include <folly/dynamic.h>
11
+ #include <react/renderer/components/view/BaseViewProps.h>
12
+ #include <react/renderer/core/ReactPrimitives.h>
13
+ #include <react/renderer/uimanager/UIManager.h>
14
+ #include <react/renderer/uimanager/UIManagerCommitHook.h>
15
+ #include "AnimatedProps.h"
16
+
17
+ namespace facebook::react {
18
+
19
+ struct PropsSnapshot {
20
+ BaseViewProps props;
21
+ std::unordered_set<PropName> propNames;
22
+ };
23
+
24
+ struct SurfaceContext {
25
+ std::unordered_map<Tag, std::unique_ptr<PropsSnapshot>> pendingMap, map;
26
+ std::unordered_set<const ShadowNodeFamily *> pendingFamilies, families;
27
+ };
28
+
29
+ struct SurfaceUpdates {
30
+ std::unordered_set<const ShadowNodeFamily *> families;
31
+ std::unordered_map<Tag, AnimatedProps> propsMap;
32
+ };
33
+
34
+ using SnapshotMap = std::unordered_map<Tag, std::unique_ptr<PropsSnapshot>>;
35
+
36
+ class AnimatedPropsRegistry {
37
+ public:
38
+ void update(const std::unordered_map<SurfaceId, SurfaceUpdates> &surfaceUpdates);
39
+ void clear(SurfaceId surfaceId);
40
+ std::pair<std::unordered_set<const ShadowNodeFamily *> &, SnapshotMap &> getMap(SurfaceId surfaceId);
41
+
42
+ private:
43
+ std::unordered_map<SurfaceId, SurfaceContext> surfaceContexts_;
44
+ std::mutex mutex_;
45
+ };
46
+
47
+ inline void updateProp(const PropName propName, BaseViewProps &viewProps, const PropsSnapshot &snapshot)
48
+ {
49
+ switch (propName) {
50
+ case OPACITY:
51
+ viewProps.opacity = snapshot.props.opacity;
52
+ break;
53
+
54
+ case WIDTH:
55
+ viewProps.yogaStyle.setDimension(
56
+ yoga::Dimension::Width, snapshot.props.yogaStyle.dimension(yoga::Dimension::Width));
57
+ break;
58
+
59
+ case HEIGHT: {
60
+ auto d = snapshot.props.yogaStyle.dimension(yoga::Dimension::Height);
61
+ viewProps.yogaStyle.setDimension(yoga::Dimension::Height, d);
62
+ break;
63
+ }
64
+
65
+ case TRANSFORM:
66
+ viewProps.transform = snapshot.props.transform;
67
+ break;
68
+
69
+ case BORDER_RADII:
70
+ viewProps.borderRadii = snapshot.props.borderRadii;
71
+ break;
72
+
73
+ case FLEX:
74
+ viewProps.yogaStyle.setFlex(snapshot.props.yogaStyle.flex());
75
+ break;
76
+
77
+ case BACKGROUND_COLOR:
78
+ viewProps.backgroundColor = snapshot.props.backgroundColor;
79
+ break;
80
+ }
81
+ }
82
+
83
+ } // namespace facebook::react
@@ -9,6 +9,7 @@
9
9
  #include <react/renderer/animationbackend/AnimatedPropsSerializer.h>
10
10
  #include <react/renderer/graphics/Color.h>
11
11
  #include <chrono>
12
+ #include "AnimatedPropsRegistry.h"
12
13
 
13
14
  namespace facebook::react {
14
15
 
@@ -70,12 +71,14 @@ AnimationBackend::AnimationBackend(
70
71
  stopOnRenderCallback_(std::move(stopOnRenderCallback)),
71
72
  directManipulationCallback_(std::move(directManipulationCallback)),
72
73
  fabricCommitCallback_(std::move(fabricCommitCallback)),
73
- uiManager_(uiManager) {}
74
+ animatedPropsRegistry_(std::make_shared<AnimatedPropsRegistry>()),
75
+ uiManager_(uiManager),
76
+ commitHook_(uiManager, animatedPropsRegistry_) {}
74
77
 
75
78
  void AnimationBackend::onAnimationFrame(double timestamp) {
76
- std::unordered_map<Tag, AnimatedProps> updates;
77
- std::unordered_map<SurfaceId, std::unordered_set<const ShadowNodeFamily*>>
78
- surfaceToFamilies;
79
+ std::unordered_map<Tag, AnimatedProps> synchronousUpdates;
80
+ std::unordered_map<SurfaceId, SurfaceUpdates> surfaceUpdates;
81
+
79
82
  bool hasAnyLayoutUpdates = false;
80
83
  for (auto& callback : callbacks) {
81
84
  auto muatations = callback(static_cast<float>(timestamp));
@@ -83,22 +86,28 @@ void AnimationBackend::onAnimationFrame(double timestamp) {
83
86
  hasAnyLayoutUpdates |= mutationHasLayoutUpdates(mutation);
84
87
  const auto family = mutation.family;
85
88
  if (family != nullptr) {
86
- surfaceToFamilies[family->getSurfaceId()].insert(family);
89
+ auto& [families, updates] = surfaceUpdates[family->getSurfaceId()];
90
+ families.insert(family.get());
91
+ updates[mutation.tag] = std::move(mutation.props);
92
+ } else {
93
+ synchronousUpdates[mutation.tag] = std::move(mutation.props);
87
94
  }
88
- updates[mutation.tag] = std::move(mutation.props);
89
95
  }
90
96
  }
91
97
 
98
+ animatedPropsRegistry_->update(surfaceUpdates);
99
+
92
100
  if (hasAnyLayoutUpdates) {
93
- commitUpdates(surfaceToFamilies, updates);
101
+ commitUpdates(surfaceUpdates);
94
102
  } else {
95
- synchronouslyUpdateProps(updates);
103
+ synchronouslyUpdateProps(synchronousUpdates);
96
104
  }
97
105
  }
98
106
 
99
107
  void AnimationBackend::start(const Callback& callback, bool isAsync) {
100
108
  callbacks.push_back(callback);
101
- // TODO: startOnRenderCallback_ should provide the timestamp from the platform
109
+ // TODO: startOnRenderCallback_ should provide the timestamp from the
110
+ // platform
102
111
  if (startOnRenderCallback_) {
103
112
  startOnRenderCallback_(
104
113
  [this]() {
@@ -117,13 +126,11 @@ void AnimationBackend::stop(bool isAsync) {
117
126
  }
118
127
 
119
128
  void AnimationBackend::commitUpdates(
120
- const std::unordered_map<
121
- SurfaceId,
122
- std::unordered_set<const ShadowNodeFamily*>>& surfaceToFamilies,
123
- std::unordered_map<Tag, AnimatedProps>& updates) {
124
- for (const auto& surfaceEntry : surfaceToFamilies) {
129
+ std::unordered_map<SurfaceId, SurfaceUpdates>& surfaceUpdates) {
130
+ for (auto& surfaceEntry : surfaceUpdates) {
125
131
  const auto& surfaceId = surfaceEntry.first;
126
- const auto& surfaceFamilies = surfaceEntry.second;
132
+ const auto& surfaceFamilies = surfaceEntry.second.families;
133
+ auto& updates = surfaceEntry.second.propsMap;
127
134
  uiManager_->getShadowTreeRegistry().visit(
128
135
  surfaceId, [&surfaceFamilies, &updates](const ShadowTree& shadowTree) {
129
136
  shadowTree.commit(
@@ -165,4 +172,8 @@ void AnimationBackend::synchronouslyUpdateProps(
165
172
  }
166
173
  }
167
174
 
175
+ void AnimationBackend::clearRegistry(SurfaceId surfaceId) {
176
+ animatedPropsRegistry_->clear(surfaceId);
177
+ }
178
+
168
179
  } // namespace facebook::react
@@ -15,6 +15,8 @@
15
15
  #include <vector>
16
16
  #include "AnimatedProps.h"
17
17
  #include "AnimatedPropsBuilder.h"
18
+ #include "AnimatedPropsRegistry.h"
19
+ #include "AnimationBackendCommitHook.h"
18
20
 
19
21
  namespace facebook::react {
20
22
 
@@ -32,7 +34,7 @@ class UIManagerNativeAnimatedDelegateBackendImpl : public UIManagerNativeAnimate
32
34
 
33
35
  struct AnimationMutation {
34
36
  Tag tag;
35
- const ShadowNodeFamily *family;
37
+ std::shared_ptr<const ShadowNodeFamily> family;
36
38
  AnimatedProps props;
37
39
  };
38
40
 
@@ -51,7 +53,9 @@ class AnimationBackend : public UIManagerAnimationBackend {
51
53
  const StopOnRenderCallback stopOnRenderCallback_;
52
54
  const DirectManipulationCallback directManipulationCallback_;
53
55
  const FabricCommitCallback fabricCommitCallback_;
56
+ std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry_;
54
57
  UIManager *uiManager_;
58
+ AnimationBackendCommitHook commitHook_;
55
59
 
56
60
  AnimationBackend(
57
61
  StartOnRenderCallback &&startOnRenderCallback,
@@ -59,10 +63,9 @@ class AnimationBackend : public UIManagerAnimationBackend {
59
63
  DirectManipulationCallback &&directManipulationCallback,
60
64
  FabricCommitCallback &&fabricCommitCallback,
61
65
  UIManager *uiManager);
62
- void commitUpdates(
63
- const std::unordered_map<SurfaceId, std::unordered_set<const ShadowNodeFamily *>> &surfaceToFamilies,
64
- std::unordered_map<Tag, AnimatedProps> &updates);
66
+ void commitUpdates(std::unordered_map<SurfaceId, SurfaceUpdates> &surfaceUpdates);
65
67
  void synchronouslyUpdateProps(const std::unordered_map<Tag, AnimatedProps> &updates);
68
+ void clearRegistry(SurfaceId surfaceId) override;
66
69
 
67
70
  void onAnimationFrame(double timestamp) override;
68
71
  void start(const Callback &callback, bool isAsync);