Skip to content

Commit 7eca162

Browse files
Bartlomiej Bloniarzmeta-codesync[bot]
authored andcommitted
Add a batched scheduler delegate call for animated prop updates (#58773)
Summary: Pull Request resolved: #58773 The animation backend hands its synchronous updates to the platform one view at a time. This adds `UIManager::synchronouslyUpdateAnimatedProps` and a matching `SchedulerDelegate` call that receive a whole frame's updates at once, continuing #56771. The backend uses it when `optimizedAnimatedPropUpdates` is on, and that flag's description now only promises what is implemented. The default `SchedulerDelegate` implementation forwards to the existing per-view update, so platforms that don't override it are unaffected. `UIManagerDelegate` gains a required method, implemented by `Scheduler`. ## Changelog: [Internal] Differential Revision: D122570617
1 parent 36bb5bb commit 7eca162

24 files changed

Lines changed: 143 additions & 8 deletions

‎packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @fantom_flags useSharedAnimatedBackend:true updateRuntimeShadowNodeReferencesOnCommitThread:*
7+
* @fantom_flags useSharedAnimatedBackend:true updateRuntimeShadowNodeReferencesOnCommitThread:* optimizedAnimatedPropUpdates:*
88
* @flow strict-local
99
* @format
1010
*/

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<86390dbd6b2c57cb4f8cdd7cb73cc1f7>>
7+
* @generated SignedSource<<81b1343c027b377be3bdea9167e16a5d>>
88
*/
99

1010
/**
@@ -415,7 +415,7 @@ public object ReactNativeFeatureFlags {
415415
public fun fuseboxWebSocketEventsEnabled(): Boolean = accessor.fuseboxWebSocketEventsEnabled()
416416

417417
/**
418-
* When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. On iOS, this passes AnimatedProps directly through the delegate chain and applies them via cloneProps, avoiding the folly::dynamic round-trip.
418+
* When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. Other platforms apply the props of each view through the existing synchronous update.
419419
*/
420420
@JvmStatic
421421
public fun optimizedAnimatedPropUpdates(): Boolean = accessor.optimizedAnimatedPropUpdates()

‎packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<aef7d6ac25fdeb2423ebf4a2a9ac6f2e>>
7+
* @generated SignedSource<<dae6ad344267433b319e0928830e0e11>>
88
*/
99

1010
/**
@@ -362,7 +362,7 @@ class ReactNativeFeatureFlags {
362362
RN_EXPORT static bool fuseboxWebSocketEventsEnabled();
363363

364364
/**
365-
* When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. On iOS, this passes AnimatedProps directly through the delegate chain and applies them via cloneProps, avoiding the folly::dynamic round-trip.
365+
* When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. Other platforms apply the props of each view through the existing synchronous update.
366366
*/
367367
RN_EXPORT static bool optimizedAnimatedPropUpdates();
368368

‎packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@ void AnimationBackend::commitUpdates(
261261

262262
void AnimationBackend::synchronouslyUpdateProps(
263263
const std::unordered_map<Tag, AnimatedProps>& updates) {
264+
if (ReactNativeFeatureFlags::optimizedAnimatedPropUpdates()) {
265+
if (auto uiManager = uiManager_.lock()) {
266+
uiManager->synchronouslyUpdateAnimatedProps(updates);
267+
}
268+
return;
269+
}
264270
for (auto& [tag, animatedProps] : updates) {
265271
// TODO: We shouldn't repack it into dynamic, but for that a rewrite
266272
// of synchronouslyUpdateViewOnUIThread is needed

‎packages/react-native/ReactCommon/react/renderer/animationbackend/tests/AnimationBackendTest.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class RecordingUIManagerDelegate : public UIManagerDelegate {
3636
synchronousUpdates.emplace(tag, props);
3737
}
3838

39+
void uiManagerShouldSynchronouslyUpdateAnimatedProps(
40+
const std::unordered_map<Tag, AnimatedProps>& /*updates*/) override {}
3941
void uiManagerDidFinishTransaction(
4042
std::shared_ptr<const MountingCoordinator> /*mountingCoordinator*/,
4143
bool /*mountSynchronously*/) override {}

‎packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,13 @@ void Scheduler::uiManagerShouldSynchronouslyUpdateViewOnUIThread(
378378
}
379379
}
380380

381+
void Scheduler::uiManagerShouldSynchronouslyUpdateAnimatedProps(
382+
const std::unordered_map<Tag, AnimatedProps>& updates) {
383+
if (delegate_ != nullptr) {
384+
delegate_->schedulerShouldSynchronouslyUpdateAnimatedProps(updates);
385+
}
386+
}
387+
381388
void Scheduler::uiManagerDidUpdateShadowTree(
382389
const std::unordered_map<Tag, folly::dynamic>& tagToProps) {
383390
if (delegate_ != nullptr) {

‎packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class Scheduler final : public UIManagerDelegate {
9797
bool isJSResponder,
9898
bool blockNativeResponder) override;
9999
void uiManagerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) override;
100+
void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map<Tag, AnimatedProps> &updates) override;
100101
void uiManagerDidUpdateShadowTree(const std::unordered_map<Tag, folly::dynamic> &tagToProps) override;
101102
void uiManagerDidCaptureViewSnapshot(Tag tag, SurfaceId surfaceId) override;
102103
void uiManagerDidSetViewSnapshot(Tag sourceTag, Tag targetTag, SurfaceId surfaceId) override;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 "SchedulerDelegate.h"
9+
10+
#include <react/renderer/animationbackend/AnimatedProps.h>
11+
#include <react/renderer/animationbackend/AnimatedPropsSerializer.h>
12+
13+
namespace facebook::react {
14+
15+
void SchedulerDelegate::schedulerShouldSynchronouslyUpdateAnimatedProps(
16+
const std::unordered_map<Tag, AnimatedProps>& updates) {
17+
for (const auto& [tag, props] : updates) {
18+
schedulerShouldSynchronouslyUpdateViewOnUIThread(
19+
tag, animationbackend::packAnimatedProps(props));
20+
}
21+
}
22+
23+
} // namespace facebook::react

‎packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010
#include <react/cxxstableapi/FrameworksGuard.h>
1111

1212
#include <memory>
13+
#include <unordered_map>
1314

1415
#include <React/RendererCore.h>
1516
#include <react/renderer/mounting/MountingCoordinator.h>
1617
#include <react/renderer/mounting/ShadowView.h>
1718

1819
namespace facebook::react {
1920

21+
struct AnimatedProps;
22+
2023
/*
2124
* Abstract class for Scheduler's delegate.
2225
*/
@@ -66,6 +69,12 @@ class SchedulerDelegate {
6669

6770
virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) = 0;
6871

72+
/*
73+
* Synchronous update of animated props for many views at once. By default,
74+
* forwards each view to `schedulerShouldSynchronouslyUpdateViewOnUIThread`.
75+
*/
76+
virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map<Tag, AnimatedProps> &updates);
77+
6978
virtual void schedulerDidUpdateShadowTree(const std::unordered_map<Tag, folly::dynamic> &tagToProps) = 0;
7079

7180
// View transition bitmap snapshot capture and application.

‎packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include <ReactCommon/RuntimeExecutor.h>
3535
#include <react/featureflags/ReactNativeFeatureFlags.h>
3636
#include <react/featureflags/ReactNativeFeatureFlagsDefaults.h>
37+
#include <react/renderer/animationbackend/AnimatedPropsBuilder.h>
3738
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
3839
#include <react/renderer/components/root/RootComponentDescriptor.h>
3940
#include <react/renderer/components/root/RootProps.h>
@@ -588,4 +589,37 @@ TEST_F(
588589
EXPECT_EQ(delegate_->shouldRenderTransactionsCount(), 1);
589590
}
590591

592+
TEST(SchedulerDelegateTest, animatedPropsUseExistingViewUpdatesByDefault) {
593+
class PropsDelegate : public RecordingDelegate {
594+
public:
595+
std::unordered_map<Tag, folly::dynamic> received;
596+
597+
void schedulerShouldSynchronouslyUpdateViewOnUIThread(
598+
Tag tag,
599+
const folly::dynamic& props) override {
600+
received.emplace(tag, props);
601+
}
602+
} delegate;
603+
604+
std::unordered_map<Tag, AnimatedProps> updates;
605+
updates.emplace(
606+
10,
607+
AnimatedProps{
608+
.props = {},
609+
.rawProps = std::make_unique<RawProps>(
610+
folly::dynamic::object("opacity", 0.25))});
611+
AnimatedPropsBuilder builder;
612+
builder.setOpacity(0.75);
613+
updates.emplace(20, builder.get());
614+
615+
delegate.schedulerShouldSynchronouslyUpdateAnimatedProps(updates);
616+
617+
ASSERT_EQ(delegate.received.size(), 2);
618+
folly::dynamic expectedRaw = folly::dynamic::object("opacity", 0.25);
619+
folly::dynamic expectedTyped = folly::dynamic::object("opacity", 0.75);
620+
EXPECT_EQ(delegate.received.at(10), expectedRaw);
621+
EXPECT_EQ(delegate.received.at(20), expectedTyped);
622+
EXPECT_EQ(updates.at(10).rawProps->toDynamic(), delegate.received.at(10));
623+
}
624+
591625
} // namespace facebook::react

0 commit comments

Comments
 (0)