From 30f65e5695b06658a8ac59bdfbb058d7324b0b8a Mon Sep 17 00:00:00 2001 From: Bartlomiej Bloniarz Date: Fri, 2 Oct 2026 05:58:52 -0700 Subject: [PATCH 1/2] Keep non-layout animations on the synchronous path while another view animates layout (#58772) Summary: Pull Request resolved: https://github.com/react/react-native/pull/58772 The shared animation backend decided per surface whether a frame's animated props go through a shadow tree commit or straight to the mounted views. As soon as one view animated a layout prop, every animated view on that surface went through the commit path for the whole animation, including views that only animate `transform` or `opacity`. Besides the extra commit work, on Android this moved those views from `updatePropsSynchronously` to regular mounts. There the synchronous mount props cache (`overrideBySynchronousMountPropsAtMountingAndroid`) replaced the incoming transform with its last synchronously written value, so the view froze while the other view's layout animation ran and jumped when it ended. The decision is now made per view. `applySurfaceUpdates` splits a surface's mutations, keyed by view tag, into views with layout updates, which go through `commitUpdates`, and the rest, which are applied synchronously. Views with layout updates are also written synchronously, as the non-shared Animated path already does, so a view that starts animating a layout prop while animating `opacity` doesn't freeze its opacity behind the cache either. `AnimationMutation` and `AnimationMutations` move to `AnimationMutation.h`, still included by `AnimationBackend.h`. `AnimatedPropsRegistry::update` reads the frame's batches instead of the merged per-surface map. When the same view receives several mutations in one frame, they are merged, so the mounted view and the registry stay in sync. Raw animated props are now merged per key, like the animation drivers merge them, instead of with merge-patch, which dropped `null` values meant to reset a prop. `RawProps::getDynamic` exposes a dynamic source without copying it. ## Changelog: [General] [Fixed] - Non-layout props animated on the shared animation backend no longer freeze while the same view or another view on the surface animates a layout prop Differential Revision: D122570616 --- .../__tests__/AnimatedBackend-itest.js | 192 +++++++++++++++++- .../AnimatedPropsRegistry.cpp | 51 +++-- .../animationbackend/AnimatedPropsRegistry.h | 15 +- .../animationbackend/AnimationBackend.cpp | 110 +++++++--- .../animationbackend/AnimationBackend.h | 19 +- .../animationbackend/AnimationMutation.h | 32 +++ .../__docs__/AnimationBackend.md | 17 +- .../react/renderer/core/RawProps.h | 6 + .../renderer/core/tests/RawPropsTest.cpp | 23 +++ .../api-snapshots/ReactAndroidDebugCxx.api | 11 +- .../api-snapshots/ReactAndroidNewarchCxx.api | 11 +- .../api-snapshots/ReactAndroidReleaseCxx.api | 11 +- .../api-snapshots/ReactAppleDebugCxx.api | 11 +- .../api-snapshots/ReactAppleNewarchCxx.api | 11 +- .../api-snapshots/ReactAppleReleaseCxx.api | 11 +- .../api-snapshots/ReactCommonDebugCxx.api | 11 +- .../api-snapshots/ReactCommonNewarchCxx.api | 11 +- .../api-snapshots/ReactCommonReleaseCxx.api | 11 +- 18 files changed, 410 insertions(+), 154 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationMutation.h diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js index 15df90071365..33c228543e2e 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js @@ -17,7 +17,7 @@ import * as Fantom from '@react-native/fantom'; import nullthrows from 'nullthrows'; import * as React from 'react'; import {Component, createRef, memo, useEffect, useMemo, useState} from 'react'; -import {Animated, View, useAnimatedValue} from 'react-native'; +import {Animated, Easing, View, useAnimatedValue} from 'react-native'; // marginLeft (and the other margin props) are only on the native animated // allowlist when the shared backend is enabled. This verifies the prop is @@ -77,6 +77,196 @@ test('animate marginLeft layout prop', () => { ); }); +test('non-layout props stay on the direct path while another view animates layout', () => { + const movingRef = createRef(); + + let _translateX; + let _translateXAnimation; + let _siblingHeight; + let _siblingHeightAnimation; + + function MyApp() { + const translateX = useAnimatedValue(0); + const siblingHeight = useAnimatedValue(10); + _translateX = translateX; + _siblingHeight = siblingHeight; + return ( + + + + + ); + } + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + Fantom.runTask(() => { + _translateXAnimation = Animated.timing(_translateX, { + toValue: 100, + duration: 200, + useNativeDriver: true, + }).start(); + _siblingHeightAnimation = Animated.timing(_siblingHeight, { + toValue: 110, + duration: 200, + useNativeDriver: true, + }).start(); + }); + + Fantom.unstable_produceFramesForDuration(100); + + // The sibling's height went through a commit; the transform did not. + expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual( + + + + , + ); + expect( + Fantom.unstable_getDirectManipulationProps(nullthrows(movingRef.current)) + .transform, + ).toEqual([{translateX: 50}]); + + Fantom.runTask(() => { + _translateXAnimation?.stop(); + _siblingHeightAnimation?.stop(); + }); +}); + +test('direct-path props survive a re-render while another view animates layout', () => { + let _opacity; + let _opacityAnimation; + let _siblingHeight; + let _siblingHeightAnimation; + let _setWidth; + + function MyApp() { + const opacity = useAnimatedValue(0); + const siblingHeight = useAnimatedValue(10); + const [width, setWidth] = useState(100); + _opacity = opacity; + _siblingHeight = siblingHeight; + _setWidth = setWidth; + return ( + + + + + ); + } + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + Fantom.runTask(() => { + _opacityAnimation = Animated.timing(_opacity, { + toValue: 0.5, + duration: 1000, + useNativeDriver: true, + }).start(); + _siblingHeightAnimation = Animated.timing(_siblingHeight, { + toValue: 110, + duration: 1000, + useNativeDriver: true, + }).start(); + }); + + Fantom.unstable_produceFramesForDuration(500); + + Fantom.runTask(() => { + _setWidth(150); + }); + + expect( + root.getRenderedOutput({props: ['opacity', 'width', 'height']}).toJSX(), + ).toEqual( + + + + , + ); + + Fantom.runTask(() => { + _opacityAnimation?.stop(); + _siblingHeightAnimation?.stop(); + }); +}); + +test('a view keeps writing its props directly after it starts animating layout', () => { + const viewRef = createRef(); + + let _opacity; + let _opacityAnimation; + let _height; + let _heightAnimation; + + function MyApp() { + const opacity = useAnimatedValue(0); + const height = useAnimatedValue(10); + _opacity = opacity; + _height = height; + return ( + + ); + } + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + const viewElement = nullthrows(viewRef.current); + + Fantom.runTask(() => { + _opacityAnimation = Animated.timing(_opacity, { + toValue: 0.5, + duration: 1000, + easing: Easing.linear, + useNativeDriver: true, + }).start(); + }); + + Fantom.unstable_produceFramesForDuration(500); + + expect( + Fantom.unstable_getDirectManipulationProps(viewElement).opacity, + ).toBeCloseTo(0.25, 0.001); + + Fantom.runTask(() => { + _heightAnimation = Animated.timing(_height, { + toValue: 110, + duration: 1000, + easing: Easing.linear, + useNativeDriver: true, + }).start(); + }); + + Fantom.unstable_produceFramesForDuration(250); + + expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual( + , + ); + expect( + Fantom.unstable_getDirectManipulationProps(viewElement).opacity, + ).toBeCloseTo(0.375, 0.001); + + Fantom.runTask(() => { + _opacityAnimation?.stop(); + _heightAnimation?.stop(); + }); +}); + test('animated opacity', () => { let _opacity; let _opacityAnimation; diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp index 02fef88041cd..e8df4f4ca0e1 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp @@ -6,31 +6,40 @@ */ #include "AnimatedPropsRegistry.h" +#include #include #include "AnimatedProps.h" namespace facebook::react { +void mergeAnimatedRawProps( + folly::dynamic& target, + const folly::dynamic& source) { + if (!target.isObject() || !source.isObject()) { + target = source; + return; + } + for (const auto& [key, value] : source.items()) { + target[key] = value; + } +} + void AnimatedPropsRegistry::update( - const std::unordered_map& surfaceUpdates) { + const std::vector& batches) { auto lock = std::lock_guard(mutex_); - for (const auto& [surfaceId, updates] : surfaceUpdates) { - auto contextIt = surfaceContexts_.find(surfaceId); - if (contextIt == surfaceContexts_.end()) { - continue; - } - auto& surfaceContext = contextIt->second; - auto& pendingMap = surfaceContext.pendingMap; - auto& pendingFamilies = surfaceContext.pendingFamilies; - - auto& updatesMap = updates.propsMap; - auto& updatesFamilies = updates.families; - - for (auto& family : updatesFamilies) { - pendingFamilies.insert(family); - } - - for (auto& [tag, animatedProps] : updatesMap) { + for (const auto& mutations : batches) { + for (const auto& mutation : mutations.batch) { + const auto& family = mutation.family; + react_native_assert(family != nullptr); + auto contextIt = surfaceContexts_.find(family->getSurfaceId()); + if (contextIt == surfaceContexts_.end()) { + continue; + } + auto& surfaceContext = contextIt->second; + auto& pendingMap = surfaceContext.pendingMap; + surfaceContext.pendingFamilies.insert(family); + const auto tag = mutation.tag; + const auto& animatedProps = mutation.props; auto it = pendingMap.find(tag); if (it == pendingMap.end()) { it = pendingMap.insert_or_assign(tag, std::make_unique()) @@ -44,8 +53,7 @@ void AnimatedPropsRegistry::update( auto& currentRawProps = snapshot->rawProps; if (currentRawProps) { - auto newRawPropsDynamic = newRawProps.toDynamic(); - currentRawProps->merge_patch(newRawPropsDynamic); + mergeAnimatedRawProps(*currentRawProps, newRawProps.toDynamic()); } else { currentRawProps = std::make_unique(newRawProps.toDynamic()); @@ -83,7 +91,8 @@ AnimatedPropsRegistry::getMap(SurfaceId surfaceId) { auto& currentSnapshot = currentIt->second; if (propsSnapshot->rawProps) { if (currentSnapshot->rawProps) { - currentSnapshot->rawProps->merge_patch(*propsSnapshot->rawProps); + mergeAnimatedRawProps( + *currentSnapshot->rawProps, *propsSnapshot->rawProps); } else { currentSnapshot->rawProps = std::move(propsSnapshot->rawProps); } diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h index 3c004ec9502f..da0e6e884248 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h @@ -15,6 +15,7 @@ #include #include #include "AnimatedProps.h" +#include "AnimationMutation.h" namespace facebook::react { @@ -29,17 +30,17 @@ struct SurfaceContext { std::unordered_set> pendingFamilies, families; }; -struct SurfaceUpdates { - std::unordered_set> families; - std::unordered_map propsMap; - bool hasLayoutUpdates{false}; -}; - using SnapshotMap = std::unordered_map>; +/* + * Merges raw animated props per key, like the props merged by the animation + * drivers. A null value is kept, so it still resets the prop on commit. + */ +void mergeAnimatedRawProps(folly::dynamic &target, const folly::dynamic &source); + class AnimatedPropsRegistry { public: - void update(const std::unordered_map &surfaceUpdates); + void update(const std::vector &batches); void initializeSurface(SurfaceId surfaceId); void clear(SurfaceId surfaceId); void clearOnSurfaceStop(SurfaceId surfaceId); diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp index e38386a9e44a..d8fb510dd41d 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -48,6 +49,26 @@ static inline Props::Shared cloneProps( return newProps; } +// Combines two mutations of the same view from one frame. +static void mergeMutation( + AnimationMutation& existing, + AnimationMutation&& incoming) { + auto& props = existing.props; + for (auto& animatedProp : incoming.props.props) { + props.props.push_back(std::move(animatedProp)); + } + if (incoming.props.rawProps) { + if (props.rawProps) { + auto merged = props.rawProps->toDynamic(); + mergeAnimatedRawProps(merged, incoming.props.rawProps->toDynamic()); + props.rawProps = std::make_unique(std::move(merged)); + } else { + props.rawProps = std::move(incoming.props.rawProps); + } + } + existing.hasLayoutUpdates |= incoming.hasLayoutUpdates; +} + AnimationBackend::AnimationBackend( std::shared_ptr animationChoreographer, std::shared_ptr uiManager) @@ -80,14 +101,13 @@ void AnimationBackend::unpackMutations( std::unordered_map& surfaceUpdates, std::set& asyncFlushSurfaces) { for (auto& mutation : mutations.batch) { - const auto family = mutation.family; - react_native_assert(family != nullptr); - - auto& [families, updates, hasLayoutUpdates] = - surfaceUpdates[family->getSurfaceId()]; - hasLayoutUpdates |= mutation.hasLayoutUpdates; - families.insert(family); - updates[mutation.tag] = std::move(mutation.props); + auto& updates = surfaceUpdates[mutation.family->getSurfaceId()]; + const auto tag = mutation.tag; + if (auto it = updates.find(tag); it != updates.end()) { + mergeMutation(it->second, std::move(mutation)); + } else { + updates.emplace(tag, std::move(mutation)); + } } asyncFlushSurfaces.merge(mutations.asyncFlushSurfaces); @@ -96,23 +116,42 @@ void AnimationBackend::unpackMutations( void AnimationBackend::applySurfaceUpdates( std::unordered_map& surfaceUpdates, const std::set& asyncFlushSurfaces) { - animatedPropsRegistry_->update(surfaceUpdates); - for (auto& [surfaceId, updates] : surfaceUpdates) { - if (updates.hasLayoutUpdates) { - commitUpdates(surfaceId, updates); - } else { - synchronouslyUpdateProps(updates.propsMap); + SurfaceUpdates layoutUpdates; + std::unordered_map directProps; + for (auto& [tag, mutation] : updates) { + if (mutation.hasLayoutUpdates) { + layoutUpdates.emplace(tag, std::move(mutation)); + } else { + directProps.emplace(tag, std::move(mutation.props)); + } + } + if (!layoutUpdates.empty()) { + // A platform may re-apply a view's earlier direct writes when mounting + // it, so committed views are written directly too. + if (auto uiManager = uiManager_.lock()) { + for (const auto& [tag, mutation] : layoutUpdates) { + uiManager->synchronouslyUpdateViewOnUIThread( + tag, animationbackend::packAnimatedProps(mutation.props)); + } + } + commitUpdates(surfaceId, layoutUpdates); + } + if (!directProps.empty()) { + synchronouslyUpdateProps(directProps); } } requestAsyncFlushForSurfaces(asyncFlushSurfaces); } -void AnimationBackend::applyMutations(AnimationMutations mutations) { +void AnimationBackend::applyMutations(std::vector batches) { + animatedPropsRegistry_->update(batches); std::unordered_map surfaceUpdates; std::set asyncFlushSurfaces; - unpackMutations(mutations, surfaceUpdates, asyncFlushSurfaces); + for (auto& mutations : batches) { + unpackMutations(mutations, surfaceUpdates, asyncFlushSurfaces); + } applySurfaceUpdates(surfaceUpdates, asyncFlushSurfaces); } @@ -124,13 +163,17 @@ void AnimationBackend::onAnimationFrame(AnimationTimestamp timestamp) { callbacksCopy = callbacks; } - std::unordered_map surfaceUpdates; - std::set asyncFlushSurfaces; - for (auto& callbackWithId : callbacksCopy) { - auto mutations = callbackWithId.callback(timestamp); - unpackMutations(mutations, surfaceUpdates, asyncFlushSurfaces); - } - applySurfaceUpdates(surfaceUpdates, asyncFlushSurfaces); + // Sized up front rather than grown: MSVC's std::set move isn't noexcept, so + // growing a vector of AnimationMutations would try to copy move-only props. + std::vector batches(callbacksCopy.size()); + std::transform( + callbacksCopy.begin(), + callbacksCopy.end(), + batches.begin(), + [timestamp](const CallbackWithId& callbackWithId) { + return callbackWithId.callback(timestamp); + }); + applyMutations(std::move(batches)); } CallbackId AnimationBackend::start(const Callback& callback) { @@ -169,8 +212,9 @@ void AnimationBackend::trigger() { void AnimationBackend::pushAnimationMutations(const Callback& callback) { auto timestamp = animationChoreographer_->now(); - auto mutations = callback(timestamp); - applyMutations(std::move(mutations)); + std::vector batches(1); + batches[0] = callback(timestamp); + applyMutations(std::move(batches)); } void AnimationBackend::commitUpdates( @@ -181,24 +225,28 @@ void AnimationBackend::commitUpdates( return; } - auto& surfaceFamilies = surfaceUpdates.families; - auto& updates = surfaceUpdates.propsMap; + std::unordered_set> surfaceFamilies; + for (const auto& [tag, mutation] : surfaceUpdates) { + surfaceFamilies.insert(mutation.family); + } uiManager->getShadowTreeRegistry().visit( - surfaceId, [&surfaceFamilies, &updates](const ShadowTree& shadowTree) { + surfaceId, + [&surfaceFamilies, &surfaceUpdates](const ShadowTree& shadowTree) { shadowTree.commit( [&surfaceFamilies, - &updates](const RootShadowNode& oldRootShadowNode) { + &surfaceUpdates](const RootShadowNode& oldRootShadowNode) { return std::static_pointer_cast( oldRootShadowNode.cloneMultiple( surfaceFamilies, - [&surfaceFamilies, &updates]( + [&surfaceFamilies, &surfaceUpdates]( const ShadowNode& shadowNode, const ShadowNodeFragment& fragment) { auto newProps = ShadowNodeFragment::propsPlaceholder(); if (surfaceFamilies.contains( shadowNode.getFamilyShared())) { - auto& animatedProps = updates.at(shadowNode.getTag()); + auto& animatedProps = + surfaceUpdates.at(shadowNode.getTag()).props; newProps = cloneProps(animatedProps, shadowNode); } return shadowNode.clone( diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h index af4a37a9c260..33de5c50a0ef 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h @@ -21,23 +21,12 @@ #include "AnimatedPropsRegistry.h" #include "AnimationBackendCommitHook.h" #include "AnimationChoreographer.h" +#include "AnimationMutation.h" namespace facebook::react { class AnimationBackend; -struct AnimationMutation { - Tag tag; - std::shared_ptr family; - AnimatedProps props; - bool hasLayoutUpdates{false}; -}; - -struct AnimationMutations { - std::vector batch; - std::set asyncFlushSurfaces; -}; - using Callback = std::function; struct CallbackWithId { @@ -53,7 +42,6 @@ class AnimationBackend : public UIManagerAnimationBackend { AnimationBackend( std::shared_ptr animationChoreographer, std::shared_ptr uiManager); - void commitUpdates(SurfaceId surfaceId, SurfaceUpdates &surfaceUpdates); void synchronouslyUpdateProps(const std::unordered_map &updates); void requestAsyncFlushForSurfaces(const std::set &surfaces); void clearRegistry(SurfaceId surfaceId) override; @@ -67,6 +55,9 @@ class AnimationBackend : public UIManagerAnimationBackend { void stop(CallbackId callbackId) override; private: + using SurfaceUpdates = std::unordered_map; + + void commitUpdates(SurfaceId surfaceId, SurfaceUpdates &surfaceUpdates); void unpackMutations( AnimationMutations &mutations, std::unordered_map &surfaceUpdates, @@ -74,7 +65,7 @@ class AnimationBackend : public UIManagerAnimationBackend { void applySurfaceUpdates( std::unordered_map &surfaceUpdates, const std::set &asyncFlushSurfaces); - void applyMutations(AnimationMutations mutations); + void applyMutations(std::vector batches); std::vector callbacks; std::shared_ptr animatedPropsRegistry_; std::shared_ptr animationChoreographer_; diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationMutation.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationMutation.h new file mode 100644 index 000000000000..fb491d290959 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationMutation.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include "AnimatedProps.h" + +namespace facebook::react { + +struct AnimationMutation { + Tag tag; + std::shared_ptr family; + AnimatedProps props; + bool hasLayoutUpdates{false}; +}; + +struct AnimationMutations { + std::vector batch; + std::set asyncFlushSurfaces; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/__docs__/AnimationBackend.md b/packages/react-native/ReactCommon/react/renderer/animationbackend/__docs__/AnimationBackend.md index 65414a372252..ddb1bc77cd63 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/__docs__/AnimationBackend.md +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/__docs__/AnimationBackend.md @@ -6,10 +6,11 @@ Shared Animation Backend is a part of the React Native renderer that enables animation frameworks to update props of React components without going through React's JavaScript rendering pipeline. -Animation Backend allows for updates of both layout and non-layout props. If -there are no layout updates, the animations will go through the -`synchronouslyUpdateProps` path, otherwise a Fabric commit will be performed. To -synchronize the changes with React, we use the `AnimationBackendCommitHook`. +Animation Backend allows for updates of both layout and non-layout props. The +decision is made per view: views whose updates include layout props go through a +Fabric commit, and the remaining views go through the `synchronouslyUpdateProps` +path. To synchronize the changes with React, we use the +`AnimationBackendCommitHook`. ## 🚀 Usage @@ -42,10 +43,10 @@ components that make up the Shared Animation Backend. ### AnimationBackend This is the heart of the backend. This component is responsible for managing -user-provided callbacks, segregating props, and applying them either through the -`synchronouslyUpdateProps` path (when there are no layout updates), or through a -Fabric commit performed on the main thread. This component is exposed through -`UIManager` with the `UIManagerAnimationBackend` interface. +user-provided callbacks, segregating props, and applying them per view: through +a Fabric commit performed on the main thread for views with layout updates, and +through the `synchronouslyUpdateProps` path for the rest. This component is +exposed through `UIManager` with the `UIManagerAnimationBackend` interface. ### AnimatedProps diff --git a/packages/react-native/ReactCommon/react/renderer/core/RawProps.h b/packages/react-native/ReactCommon/react/renderer/core/RawProps.h index 8afc6f7ec890..8685ec6f575d 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/RawProps.h +++ b/packages/react-native/ReactCommon/react/renderer/core/RawProps.h @@ -83,6 +83,12 @@ class RawProps final { */ folly::dynamic toDynamic(const std::function &filterObjectKeys = nullptr) const; + // The pointer is valid while this RawProps owns its dynamic source. + const folly::dynamic *getDynamic() const noexcept + { + return mode_ == Mode::Dynamic ? &dynamic_ : nullptr; + } + /* * Returns `true` if the object is empty. * Empty `RawProps` does not have any stored data. diff --git a/packages/react-native/ReactCommon/react/renderer/core/tests/RawPropsTest.cpp b/packages/react-native/ReactCommon/react/renderer/core/tests/RawPropsTest.cpp index caff5c160c32..b23760236f21 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/tests/RawPropsTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/tests/RawPropsTest.cpp @@ -20,6 +20,29 @@ using namespace facebook; using namespace facebook::react; +TEST(RawPropsTest, dynamicSourceIsBorrowed) { + const auto rawProps = RawProps(folly::dynamic::object("opacity", 0.5)); + const auto* dynamic = rawProps.getDynamic(); + ASSERT_NE(dynamic, nullptr); + EXPECT_EQ(dynamic, rawProps.getDynamic()); + EXPECT_EQ(*dynamic, rawProps.toDynamic()); +} + +TEST(RawPropsTest, emptyPropsHaveNoDynamicSource) { + EXPECT_EQ(RawProps().getDynamic(), nullptr); + EXPECT_EQ(RawProps(nullptr).getDynamic(), nullptr); +} + +TEST(RawPropsTest, jsiPropsHaveNoDynamicSource) { + auto runtime = facebook::hermes::makeHermesRuntime(); + auto object = jsi::Object(*runtime); + object.setProperty(*runtime, "opacity", 0.5); + const auto rawProps = RawProps(*runtime, jsi::Value(*runtime, object)); + EXPECT_EQ(rawProps.getDynamic(), nullptr); + folly::dynamic expected = folly::dynamic::object("opacity", 0.5); + EXPECT_EQ(rawProps.toDynamic(), expected); +} + class PropsSingleFloat : public Props { public: PropsSingleFloat() = default; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 458bb76080fb..afcba1a55387 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -1151,6 +1151,7 @@ void facebook::react::fromRawValueShared(const facebook::react::ContextContainer void facebook::react::fromString(const std::string& string, facebook::react::AccessibilityTraits& result); void facebook::react::g_setNativeAnimatedNowTimestampFunction(facebook::react::TimePointFunction nowFunction); void facebook::react::handleJSError(facebook::jsi::Runtime& runtime, const facebook::jsi::JSError& error, bool isFatal); +void facebook::react::mergeAnimatedRawProps(folly::dynamic& target, const folly::dynamic& source); void facebook::react::parseProcessedBackgroundImage(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedBoxShadow(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedFilter(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); @@ -1579,7 +1580,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -1594,7 +1595,6 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; public virtual void stop(facebook::react::CallbackId callbackId) override; public virtual void trigger() override; - public void commitUpdates(facebook::react::SurfaceId surfaceId, facebook::react::SurfaceUpdates& surfaceUpdates); public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); } @@ -4258,6 +4258,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -8111,12 +8112,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 642535bf6bd7..2525a1b5e3a1 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -1147,6 +1147,7 @@ void facebook::react::fromRawValueShared(const facebook::react::ContextContainer void facebook::react::fromString(const std::string& string, facebook::react::AccessibilityTraits& result); void facebook::react::g_setNativeAnimatedNowTimestampFunction(facebook::react::TimePointFunction nowFunction); void facebook::react::handleJSError(facebook::jsi::Runtime& runtime, const facebook::jsi::JSError& error, bool isFatal); +void facebook::react::mergeAnimatedRawProps(folly::dynamic& target, const folly::dynamic& source); void facebook::react::parseProcessedBackgroundImage(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedBoxShadow(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedFilter(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); @@ -1574,7 +1575,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -1589,7 +1590,6 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; public virtual void stop(facebook::react::CallbackId callbackId) override; public virtual void trigger() override; - public void commitUpdates(facebook::react::SurfaceId surfaceId, facebook::react::SurfaceUpdates& surfaceUpdates); public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); } @@ -4102,6 +4102,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -7871,12 +7872,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index a965c1b79d69..8291f9d7b6b9 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -1151,6 +1151,7 @@ void facebook::react::fromRawValueShared(const facebook::react::ContextContainer void facebook::react::fromString(const std::string& string, facebook::react::AccessibilityTraits& result); void facebook::react::g_setNativeAnimatedNowTimestampFunction(facebook::react::TimePointFunction nowFunction); void facebook::react::handleJSError(facebook::jsi::Runtime& runtime, const facebook::jsi::JSError& error, bool isFatal); +void facebook::react::mergeAnimatedRawProps(folly::dynamic& target, const folly::dynamic& source); void facebook::react::parseProcessedBackgroundImage(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedBoxShadow(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedFilter(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); @@ -1578,7 +1579,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -1593,7 +1594,6 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; public virtual void stop(facebook::react::CallbackId callbackId) override; public virtual void trigger() override; - public void commitUpdates(facebook::react::SurfaceId surfaceId, facebook::react::SurfaceUpdates& surfaceUpdates); public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); } @@ -4255,6 +4255,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -8102,12 +8103,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index d0d014ec91cb..14b54def60c6 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -3951,6 +3951,7 @@ void facebook::react::fromString(const std::string& string, facebook::react::Acc void facebook::react::g_setNativeAnimatedNowTimestampFunction(facebook::react::TimePointFunction nowFunction); void facebook::react::handleJSError(facebook::jsi::Runtime& runtime, const facebook::jsi::JSError& error, bool isFatal); void facebook::react::installLegacyUIManagerConstantsProviderBinding(facebook::jsi::Runtime& runtime); +void facebook::react::mergeAnimatedRawProps(folly::dynamic& target, const folly::dynamic& source); void facebook::react::parseProcessedBackgroundImage(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedBoxShadow(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedFilter(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); @@ -4128,7 +4129,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -4143,7 +4144,6 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; public virtual void stop(facebook::react::CallbackId callbackId) override; public virtual void trigger() override; - public void commitUpdates(facebook::react::SurfaceId surfaceId, facebook::react::SurfaceUpdates& surfaceUpdates); public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); } @@ -6429,6 +6429,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -10078,12 +10079,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 4290dcb0e425..a6c63942f7ee 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -3940,6 +3940,7 @@ void facebook::react::fromString(const std::string& string, facebook::react::Acc void facebook::react::g_setNativeAnimatedNowTimestampFunction(facebook::react::TimePointFunction nowFunction); void facebook::react::handleJSError(facebook::jsi::Runtime& runtime, const facebook::jsi::JSError& error, bool isFatal); void facebook::react::installLegacyUIManagerConstantsProviderBinding(facebook::jsi::Runtime& runtime); +void facebook::react::mergeAnimatedRawProps(folly::dynamic& target, const folly::dynamic& source); void facebook::react::parseProcessedBackgroundImage(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedBoxShadow(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedFilter(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); @@ -4116,7 +4117,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -4131,7 +4132,6 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; public virtual void stop(facebook::react::CallbackId callbackId) override; public virtual void trigger() override; - public void commitUpdates(facebook::react::SurfaceId surfaceId, facebook::react::SurfaceUpdates& surfaceUpdates); public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); } @@ -6305,6 +6305,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -9894,12 +9895,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 72a9805b05fd..ab9eb8631ba3 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -3951,6 +3951,7 @@ void facebook::react::fromString(const std::string& string, facebook::react::Acc void facebook::react::g_setNativeAnimatedNowTimestampFunction(facebook::react::TimePointFunction nowFunction); void facebook::react::handleJSError(facebook::jsi::Runtime& runtime, const facebook::jsi::JSError& error, bool isFatal); void facebook::react::installLegacyUIManagerConstantsProviderBinding(facebook::jsi::Runtime& runtime); +void facebook::react::mergeAnimatedRawProps(folly::dynamic& target, const folly::dynamic& source); void facebook::react::parseProcessedBackgroundImage(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedBoxShadow(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedFilter(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); @@ -4127,7 +4128,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -4142,7 +4143,6 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; public virtual void stop(facebook::react::CallbackId callbackId) override; public virtual void trigger() override; - public void commitUpdates(facebook::react::SurfaceId surfaceId, facebook::react::SurfaceUpdates& surfaceUpdates); public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); } @@ -6426,6 +6426,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -10069,12 +10070,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 8b46097a89f9..217e6254dca4 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -711,6 +711,7 @@ void facebook::react::fromRawValueShared(const facebook::react::ContextContainer void facebook::react::fromString(const std::string& string, facebook::react::AccessibilityTraits& result); void facebook::react::g_setNativeAnimatedNowTimestampFunction(facebook::react::TimePointFunction nowFunction); void facebook::react::handleJSError(facebook::jsi::Runtime& runtime, const facebook::jsi::JSError& error, bool isFatal); +void facebook::react::mergeAnimatedRawProps(folly::dynamic& target, const folly::dynamic& source); void facebook::react::parseProcessedBackgroundImage(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedBoxShadow(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedFilter(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); @@ -904,7 +905,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -919,7 +920,6 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; public virtual void stop(facebook::react::CallbackId callbackId) override; public virtual void trigger() override; - public void commitUpdates(facebook::react::SurfaceId surfaceId, facebook::react::SurfaceUpdates& surfaceUpdates); public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); } @@ -2830,6 +2830,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -6228,12 +6229,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 692b5064e583..47a606c3d203 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -708,6 +708,7 @@ void facebook::react::fromRawValueShared(const facebook::react::ContextContainer void facebook::react::fromString(const std::string& string, facebook::react::AccessibilityTraits& result); void facebook::react::g_setNativeAnimatedNowTimestampFunction(facebook::react::TimePointFunction nowFunction); void facebook::react::handleJSError(facebook::jsi::Runtime& runtime, const facebook::jsi::JSError& error, bool isFatal); +void facebook::react::mergeAnimatedRawProps(folly::dynamic& target, const folly::dynamic& source); void facebook::react::parseProcessedBackgroundImage(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedBoxShadow(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedFilter(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); @@ -900,7 +901,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -915,7 +916,6 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; public virtual void stop(facebook::react::CallbackId callbackId) override; public virtual void trigger() override; - public void commitUpdates(facebook::react::SurfaceId surfaceId, facebook::react::SurfaceUpdates& surfaceUpdates); public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); } @@ -2714,6 +2714,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -6052,12 +6053,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index ff0ef95bbfdb..48b4ddb1b251 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -711,6 +711,7 @@ void facebook::react::fromRawValueShared(const facebook::react::ContextContainer void facebook::react::fromString(const std::string& string, facebook::react::AccessibilityTraits& result); void facebook::react::g_setNativeAnimatedNowTimestampFunction(facebook::react::TimePointFunction nowFunction); void facebook::react::handleJSError(facebook::jsi::Runtime& runtime, const facebook::jsi::JSError& error, bool isFatal); +void facebook::react::mergeAnimatedRawProps(folly::dynamic& target, const folly::dynamic& source); void facebook::react::parseProcessedBackgroundImage(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedBoxShadow(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); void facebook::react::parseProcessedFilter(const facebook::react::PropsParserContext& context, const facebook::react::RawValue& value, std::vector& result); @@ -903,7 +904,7 @@ class facebook::react::AnimatedPropsRegistry { public void clear(facebook::react::SurfaceId surfaceId); public void clearOnSurfaceStop(facebook::react::SurfaceId surfaceId); public void initializeSurface(facebook::react::SurfaceId surfaceId); - public void update(const std::unordered_map& surfaceUpdates); + public void update(const std::vector& batches); } class facebook::react::AnimationBackend : public facebook::react::UIManagerAnimationBackend { @@ -918,7 +919,6 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima public virtual void registerJSInvoker(std::shared_ptr jsInvoker) override; public virtual void stop(facebook::react::CallbackId callbackId) override; public virtual void trigger() override; - public void commitUpdates(facebook::react::SurfaceId surfaceId, facebook::react::SurfaceUpdates& surfaceUpdates); public void requestAsyncFlushForSurfaces(const std::set& surfaces); public void synchronouslyUpdateProps(const std::unordered_map& updates); } @@ -2827,6 +2827,7 @@ class facebook::react::RawProps { public bool isEmpty() const noexcept; public const facebook::react::RawValue* at(const char* name) const noexcept; public const facebook::react::RawValue* at(const char* name, const char* prefix, const char* suffix) const noexcept; + public const folly::dynamic* getDynamic() const noexcept; public facebook::react::RawProps& operator=(const facebook::react::RawProps& other) noexcept = delete; public facebook::react::RawProps& operator=(facebook::react::RawProps&& other) noexcept = delete; public folly::dynamic toDynamic(const std::function& filterObjectKeys = nullptr) const; @@ -6219,12 +6220,6 @@ struct facebook::react::SurfaceContext { public std::unordered_set> pendingFamilies; } -struct facebook::react::SurfaceUpdates { - public bool hasLayoutUpdates; - public std::unordered_map propsMap; - public std::unordered_set> families; -} - struct facebook::react::SystraceSection : public facebook::react::DummyTraceSection { template public SystraceSection(const char* name, ConvertsToStringPiece &&... args); From e572af433108ca337caa7f65cef3aad1c0f87afd Mon Sep 17 00:00:00 2001 From: Bartlomiej Bloniarz Date: Fri, 2 Oct 2026 07:02:49 -0700 Subject: [PATCH 2/2] Add a batched scheduler delegate call for animated prop updates (#58773) Summary: Pull Request resolved: https://github.com/react/react-native/pull/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 --- .../__tests__/AnimatedBackend-itest.js | 2 +- .../featureflags/ReactNativeFeatureFlags.kt | 4 +-- .../featureflags/ReactNativeFeatureFlags.h | 4 +-- .../animationbackend/AnimationBackend.cpp | 6 ++++ .../react/renderer/scheduler/Scheduler.cpp | 7 ++++ .../react/renderer/scheduler/Scheduler.h | 1 + .../renderer/scheduler/SchedulerDelegate.cpp | 23 +++++++++++++ .../renderer/scheduler/SchedulerDelegate.h | 9 +++++ .../SchedulerDelegateInvalidationTest.cpp | 33 +++++++++++++++++++ .../react/renderer/uimanager/UIManager.cpp | 7 ++++ .../react/renderer/uimanager/UIManager.h | 2 ++ .../renderer/uimanager/UIManagerDelegate.h | 8 +++++ .../ReactNativeFeatureFlags.config.js | 2 +- .../featureflags/ReactNativeFeatureFlags.js | 4 +-- .../api-snapshots/ReactAndroidDebugCxx.api | 4 +++ .../api-snapshots/ReactAndroidNewarchCxx.api | 4 +++ .../api-snapshots/ReactAndroidReleaseCxx.api | 4 +++ .../api-snapshots/ReactAppleDebugCxx.api | 4 +++ .../api-snapshots/ReactAppleNewarchCxx.api | 4 +++ .../api-snapshots/ReactAppleReleaseCxx.api | 4 +++ .../api-snapshots/ReactCommonDebugCxx.api | 4 +++ .../api-snapshots/ReactCommonNewarchCxx.api | 4 +++ .../api-snapshots/ReactCommonReleaseCxx.api | 4 +++ 23 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.cpp diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js index 33c228543e2e..df8472d8fe90 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @fantom_flags useSharedAnimatedBackend:true updateRuntimeShadowNodeReferencesOnCommitThread:* + * @fantom_flags useSharedAnimatedBackend:true updateRuntimeShadowNodeReferencesOnCommitThread:* optimizedAnimatedPropUpdates:* * @flow strict-local * @format */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index b4cf39c69be8..3194db3a7f53 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<5a3796afdf02ba39aa578d83dc3f8332>> */ /** @@ -409,7 +409,7 @@ public object ReactNativeFeatureFlags { public fun fuseboxWebSocketEventsEnabled(): Boolean = accessor.fuseboxWebSocketEventsEnabled() /** - * 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. + * 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. */ @JvmStatic public fun optimizedAnimatedPropUpdates(): Boolean = accessor.optimizedAnimatedPropUpdates() diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index f16f57306b30..31d42cb06fb6 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<4c93a39dd2a8ee5ba24f3f8bc99ac1ae>> + * @generated SignedSource<<467f462832f58b37ed97105595fbc843>> */ /** @@ -357,7 +357,7 @@ class ReactNativeFeatureFlags { RN_EXPORT static bool fuseboxWebSocketEventsEnabled(); /** - * 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. + * 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. */ RN_EXPORT static bool optimizedAnimatedPropUpdates(); diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp index d8fb510dd41d..5b10873950e9 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp @@ -261,6 +261,12 @@ void AnimationBackend::commitUpdates( void AnimationBackend::synchronouslyUpdateProps( const std::unordered_map& updates) { + if (ReactNativeFeatureFlags::optimizedAnimatedPropUpdates()) { + if (auto uiManager = uiManager_.lock()) { + uiManager->synchronouslyUpdateAnimatedProps(updates); + } + return; + } for (auto& [tag, animatedProps] : updates) { // TODO: We shouldn't repack it into dynamic, but for that a rewrite // of synchronouslyUpdateViewOnUIThread is needed diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 81d9de17660c..a278fb2b3400 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -378,6 +378,13 @@ void Scheduler::uiManagerShouldSynchronouslyUpdateViewOnUIThread( } } +void Scheduler::uiManagerShouldSynchronouslyUpdateAnimatedProps( + const std::unordered_map& updates) { + if (delegate_ != nullptr) { + delegate_->schedulerShouldSynchronouslyUpdateAnimatedProps(updates); + } +} + void Scheduler::uiManagerDidUpdateShadowTree( const std::unordered_map& tagToProps) { if (delegate_ != nullptr) { diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h index 28c2d86462bf..929421f9ed8a 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -97,6 +97,7 @@ class Scheduler final : public UIManagerDelegate { bool isJSResponder, bool blockNativeResponder) override; void uiManagerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) override; + void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map &updates) override; void uiManagerDidUpdateShadowTree(const std::unordered_map &tagToProps) override; void uiManagerDidCaptureViewSnapshot(Tag tag, SurfaceId surfaceId) override; void uiManagerDidSetViewSnapshot(Tag sourceTag, Tag targetTag, SurfaceId surfaceId) override; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.cpp new file mode 100644 index 000000000000..52a9f38d50c1 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "SchedulerDelegate.h" + +#include +#include + +namespace facebook::react { + +void SchedulerDelegate::schedulerShouldSynchronouslyUpdateAnimatedProps( + const std::unordered_map& updates) { + for (const auto& [tag, props] : updates) { + schedulerShouldSynchronouslyUpdateViewOnUIThread( + tag, animationbackend::packAnimatedProps(props)); + } +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h index e6ca676fdbae..27c28495fe23 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -17,6 +18,8 @@ namespace facebook::react { +struct AnimatedProps; + /* * Abstract class for Scheduler's delegate. */ @@ -66,6 +69,12 @@ class SchedulerDelegate { virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) = 0; + /* + * Synchronous update of animated props for many views at once. By default, + * forwards each view to `schedulerShouldSynchronouslyUpdateViewOnUIThread`. + */ + virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map &updates); + virtual void schedulerDidUpdateShadowTree(const std::unordered_map &tagToProps) = 0; // View transition bitmap snapshot capture and application. diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp index 76c1646058f9..2f7957473128 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/tests/SchedulerDelegateInvalidationTest.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -588,4 +589,36 @@ TEST_F( EXPECT_EQ(delegate_->shouldRenderTransactionsCount(), 1); } +TEST(SchedulerDelegateTest, animatedPropsUseExistingViewUpdatesByDefault) { + class PropsDelegate : public RecordingDelegate { + public: + std::unordered_map received; + + void schedulerShouldSynchronouslyUpdateViewOnUIThread( + Tag tag, + const folly::dynamic& props) override { + received.emplace(tag, props); + } + } delegate; + + std::unordered_map updates; + updates.emplace( + 10, + AnimatedProps{ + {}, + std::make_unique(folly::dynamic::object("opacity", 0.25))}); + AnimatedPropsBuilder builder; + builder.setOpacity(0.75); + updates.emplace(20, builder.get()); + + delegate.schedulerShouldSynchronouslyUpdateAnimatedProps(updates); + + ASSERT_EQ(delegate.received.size(), 2); + folly::dynamic expectedRaw = folly::dynamic::object("opacity", 0.25); + folly::dynamic expectedTyped = folly::dynamic::object("opacity", 0.75); + EXPECT_EQ(delegate.received.at(10), expectedRaw); + EXPECT_EQ(delegate.received.at(20), expectedTyped); + EXPECT_EQ(updates.at(10).rawProps->toDynamic(), delegate.received.at(10)); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 0cb62936c8b5..7eefe4606177 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -769,6 +769,13 @@ void UIManager::synchronouslyUpdateViewOnUIThread( } } +void UIManager::synchronouslyUpdateAnimatedProps( + const std::unordered_map& updates) { + if (delegate_ != nullptr) { + delegate_->uiManagerShouldSynchronouslyUpdateAnimatedProps(updates); + } +} + #pragma mark ContextContainer std::shared_ptr UIManager::getContextContainer() const { diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index aaf758bcdf6b..541d57a0b258 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h @@ -89,6 +89,8 @@ class UIManager final : public ShadowTreeDelegate { void synchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props); + void synchronouslyUpdateAnimatedProps(const std::unordered_map &updates); + /* * Provides access to a UIManagerBinding. * The `callback` methods will not be called if the internal pointer to diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h index 579989d8e01f..e431d6ee22b1 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h @@ -21,6 +21,8 @@ namespace facebook::react { +struct AnimatedProps; + /* * Abstract class for UIManager's delegate. */ @@ -71,6 +73,12 @@ class UIManagerDelegate { */ virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props) = 0; + /* + * Synchronous update of animated props for many views at once. + */ + virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps( + const std::unordered_map &updates) = 0; + /* * Called after updateShadowTree is invoked. */ diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 9d776f20b10e..b0ede5eca2ff 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -730,7 +730,7 @@ const definitions: FeatureFlagDefinitions = { metadata: { dateAdded: '2026-04-07', description: - '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.', + '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.', expectedReleaseValue: true, purpose: 'experimentation', }, diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index b06551330c29..7e995e47b1bf 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<6e73a99aafb36218e1d679637bb80c82>> + * @generated SignedSource<<6c4bab0fafa6367cebfd4f00797e88a9>> * @flow strict * @noformat */ @@ -467,7 +467,7 @@ export const fuseboxScreenshotCaptureEnabled: Getter = createNativeFlag */ export const fuseboxWebSocketEventsEnabled: Getter = createNativeFlagGetter('fuseboxWebSocketEventsEnabled', false); /** - * 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. + * 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. */ export const optimizedAnimatedPropUpdates: Getter = createNativeFlagGetter('optimizedAnimatedPropUpdates', false); /** diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index afcba1a55387..0bea49a26427 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -4617,6 +4617,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -4640,6 +4641,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -5371,6 +5373,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -5438,6 +5441,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 2525a1b5e3a1..2be07a561c10 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -4433,6 +4433,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -4456,6 +4457,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -5181,6 +5183,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -5248,6 +5251,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 8291f9d7b6b9..919f8f0b18fe 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -4614,6 +4614,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -4637,6 +4638,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -5362,6 +5364,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -5429,6 +5432,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 14b54def60c6..fdeda28b5c01 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -6769,6 +6769,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -6792,6 +6793,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -7504,6 +7506,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -7571,6 +7574,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index a6c63942f7ee..6a561259e110 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -6617,6 +6617,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -6640,6 +6641,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -7346,6 +7348,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -7413,6 +7416,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index ab9eb8631ba3..ff5f5f926240 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -6766,6 +6766,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -6789,6 +6790,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -7495,6 +7497,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -7562,6 +7565,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 217e6254dca4..1a1172791c85 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -3162,6 +3162,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -3185,6 +3186,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -3813,6 +3815,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -3880,6 +3883,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 47a606c3d203..c980e32d8d4b 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -3018,6 +3018,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -3041,6 +3042,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -3663,6 +3665,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -3730,6 +3733,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; } diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 48b4ddb1b251..626894e8b909 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -3159,6 +3159,7 @@ class facebook::react::Scheduler : public facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) final; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) override; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) final; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) override; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) override; public void addEventListener(std::shared_ptr listener); public void animationTick() const; @@ -3182,6 +3183,7 @@ class facebook::react::SchedulerDelegate { public virtual void schedulerDidUpdateShadowTree(const std::unordered_map& tagToProps) = 0; public virtual void schedulerShouldMergeReactRevision(facebook::react::SurfaceId surfaceId) = 0; public virtual void schedulerShouldRenderTransactions(const std::shared_ptr& mountingCoordinator) = 0; + public virtual void schedulerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public virtual void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~SchedulerDelegate() noexcept = default; } @@ -3804,6 +3806,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate { public void startEmptySurface(facebook::react::ShadowTree::Unique&& shadowTree) const noexcept; public void startSurface(facebook::react::ShadowTree::Unique&& shadowTree, std::string moduleName, folly::dynamic props, facebook::react::DisplayMode displayMode) const noexcept; public void stopSurfaceForAnimationDelegate(facebook::react::SurfaceId surfaceId) const; + public void synchronouslyUpdateAnimatedProps(const std::unordered_map& updates); public void synchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props); public void unregisterCommitHook(facebook::react::UIManagerCommitHook& commitHook); public void unregisterMountHook(facebook::react::UIManagerMountHook& mountHook); @@ -3871,6 +3874,7 @@ class facebook::react::UIManagerDelegate { public virtual void uiManagerShouldAddEventListener(std::shared_ptr listener) = 0; public virtual void uiManagerShouldAddOnSurfaceStartCallback(facebook::react::UIManagerDelegate::OnSurfaceStartCallback&& callback) = 0; public virtual void uiManagerShouldRemoveEventListener(const std::shared_ptr& listener) = 0; + public virtual void uiManagerShouldSynchronouslyUpdateAnimatedProps(const std::unordered_map& updates) = 0; public virtual void uiManagerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic& props) = 0; public virtual ~UIManagerDelegate() noexcept = default; }