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.
- package/Libraries/Animated/nodes/AnimatedProps.js +29 -3
- package/Libraries/Core/ReactNativeVersion.js +1 -1
- package/Libraries/NativeAnimation/RCTNativeAnimatedTurboModule.mm +7 -0
- package/Libraries/NativeAnimation/React-RCTAnimation.podspec +1 -0
- package/React/Base/RCTVersion.m +1 -1
- package/React/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec-generated.mm +7 -0
- package/React/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h +2 -0
- package/React/FBReactNativeSpec/FBReactNativeSpecJSI.h +18 -0
- package/React/Fabric/RCTSurfaceTouchHandler.mm +1 -1
- package/ReactAndroid/api/ReactAndroid.api +0 -2
- package/ReactAndroid/gradle.properties +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingsObserver.kt +85 -11
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +7 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +11 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +3 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +3 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +12 -1
- package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +3 -1
- package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +1 -1
- package/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt +7 -3
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java +9 -91
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +0 -2
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java +4 -23
- package/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +1 -3
- package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +1 -10
- package/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewAccessibilityDelegate.kt +1 -0
- package/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt +42 -28
- package/ReactAndroid/src/main/jni/CMakeLists.txt +7 -0
- package/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt +7 -0
- package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +15 -1
- package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +4 -1
- package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
- package/ReactCommon/jsi/jsi/jsi.cpp +2 -1
- package/ReactCommon/jsinspector-modern/HostAgent.cpp +2 -2
- package/ReactCommon/jsinspector-modern/InspectorInterfaces.cpp +10 -5
- package/ReactCommon/jsinspector-modern/InspectorInterfaces.h +2 -2
- package/ReactCommon/jsinspector-modern/TracingAgent.cpp +1 -1
- package/ReactCommon/jsinspector-modern/tracing/TraceEventGenerator.cpp +4 -4
- package/ReactCommon/jsinspector-modern/tracing/TracingCategory.h +11 -6
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +5 -1
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +6 -1
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +51 -33
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +4 -2
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +5 -1
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +10 -1
- package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +2 -1
- package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +6 -1
- package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +3 -1
- package/ReactCommon/react/renderer/animated/AnimatedModule.cpp +19 -1
- package/ReactCommon/react/renderer/animated/AnimatedModule.h +8 -0
- package/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp +83 -9
- package/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h +6 -0
- package/ReactCommon/react/renderer/animated/drivers/FrameAnimationDriver.cpp +1 -0
- package/ReactCommon/react/renderer/animated/nodes/ColorAnimatedNode.cpp +0 -1
- package/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.h +1 -0
- package/ReactCommon/react/renderer/animated/nodes/RoundAnimatedNode.cpp +1 -1
- package/ReactCommon/react/renderer/animated/nodes/ValueAnimatedNode.cpp +1 -1
- package/ReactCommon/react/renderer/animated/tests/AnimatedNodeTests.cpp +67 -0
- package/ReactCommon/react/renderer/animated/tests/AnimationDriverTests.cpp +59 -0
- package/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp +81 -0
- package/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h +83 -0
- package/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +26 -15
- package/ReactCommon/react/renderer/animationbackend/AnimationBackend.h +7 -4
- package/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp +69 -0
- package/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h +32 -0
- package/ReactCommon/react/renderer/uimanager/UIManager.cpp +6 -0
- package/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h +1 -0
- package/package.json +9 -9
- package/scripts/cocoapods/utils.rb +2 -0
- package/sdks/hermes-engine/version.properties +1 -1
- package/src/private/animated/NativeAnimatedHelper.js +29 -1
- package/src/private/featureflags/ReactNativeFeatureFlags.js +6 -1
- package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +2 -1
- package/src/private/specs_DEPRECATED/modules/NativeAnimatedModule.js +4 -0
- 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 &&
|
|
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
|
-
|
|
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
|
|
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{
|
|
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
|
|
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);
|
|
@@ -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()["
|
|
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)");
|
|
@@ -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
|
-
|
|
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>
|
|
77
|
-
std::unordered_map<SurfaceId,
|
|
78
|
-
|
|
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
|
-
|
|
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(
|
|
101
|
+
commitUpdates(surfaceUpdates);
|
|
94
102
|
} else {
|
|
95
|
-
synchronouslyUpdateProps(
|
|
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
|
|
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
|
-
|
|
121
|
-
|
|
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
|
|
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);
|