Skip to content

Commit aa0c8c4

Browse files
tomekzawclaude
andcommitted
Record transforms of the batched AnimatedProps path in the synchronous transform overlay
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 6b7b2f5 commit aa0c8c4

10 files changed

Lines changed: 92 additions & 7 deletions

File tree

‎packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -860,9 +860,17 @@ void FabricUIManagerBinding::schedulerShouldSynchronouslyUpdateViewOnUIThread(
860860
void FabricUIManagerBinding::schedulerShouldSynchronouslyUpdateAnimatedProps(
861861
const std::unordered_map<Tag, AnimatedProps>& updates) {
862862
if (ReactNativeFeatureFlags::cxxNativeAnimatedEnabled() && mountingManager_) {
863-
// TODO: record the transforms of this batched path (behind
864-
// `optimizedAnimatedPropUpdates`) in the synchronous transform overlay;
865-
// it bypasses `FabricUIManager.synchronouslyUpdateViewOnUIThread`.
863+
// This batched path bypasses
864+
// `FabricUIManager.synchronouslyUpdateViewOnUIThread`, which records the
865+
// synchronous transforms of the per-view path.
866+
if (ReactNativeFeatureFlags::enableSynchronousTransformOverlay() &&
867+
scheduler_ != nullptr) {
868+
if (auto uiManager = scheduler_->getUIManager()) {
869+
for (const auto& [tag, props] : updates) {
870+
uiManager->recordSynchronousTransform(tag, props);
871+
}
872+
}
873+
}
866874
mountingManager_->synchronouslyUpdateAnimatedProps(updates);
867875
}
868876
}

‎packages/react-native/ReactCommon/react/renderer/uimanager/SynchronousTransformOverlay.cpp‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,32 @@ void SynchronousTransformOverlay::update(
4141
updateEntry(tag, std::move(entry));
4242
}
4343

44+
void SynchronousTransformOverlay::update(
45+
Tag tag,
46+
const AnimatedProps& props,
47+
const PropsParserContext& context) {
48+
if (props.rawProps != nullptr) {
49+
// `rawProps` carries the props the animation system could not type,
50+
// which may include `transform` (e.g. matrices or percentages).
51+
update(tag, static_cast<folly::dynamic>(*props.rawProps), context);
52+
}
53+
54+
auto entry = Entry{};
55+
for (const auto& prop : props.props) {
56+
switch (prop->propName) {
57+
case TRANSFORM:
58+
entry.transform = get<Transform>(*prop);
59+
break;
60+
case TRANSFORM_ORIGIN:
61+
entry.transformOrigin = get<TransformOrigin>(*prop);
62+
break;
63+
default:
64+
break;
65+
}
66+
}
67+
updateEntry(tag, std::move(entry));
68+
}
69+
4470
void SynchronousTransformOverlay::updateEntry(Tag tag, Entry&& entry) {
4571
if (!entry.transform.has_value() && !entry.transformOrigin.has_value()) {
4672
return;

‎packages/react-native/ReactCommon/react/renderer/uimanager/SynchronousTransformOverlay.h‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <react/cxxstableapi/PrivateGuard.h>
1111

1212
#include <folly/dynamic.h>
13+
#include <react/renderer/animationbackend/AnimatedProps.h>
1314
#include <react/renderer/core/LayoutableShadowNode.h>
1415
#include <react/renderer/core/PropsParserContext.h>
1516
#include <react/renderer/core/ReactPrimitives.h>
@@ -32,8 +33,8 @@ namespace facebook::react {
3233
* final values.
3334
*
3435
* Layout queries (`measure`, `measureInWindow`, `getBoundingClientRect`,
35-
* `findNodeAtPoint`, `UIManager::getRelativeLayoutMetrics`) consult the
36-
* overlay through the `TransformOverrideProvider` interface, so they describe
36+
* `UIManager::getRelativeLayoutMetrics`) consult the overlay through the
37+
* `TransformOverrideProvider` interface, so they describe
3738
* the mounted views rather than the stale committed props. Only `transform`
3839
* is tracked because it is the only synchronously updated prop that affects
3940
* geometry; layout props always go through a commit.
@@ -51,6 +52,12 @@ class SynchronousTransformOverlay final : public LayoutableShadowNode::Transform
5152
*/
5253
void update(Tag tag, const folly::dynamic &props, const PropsParserContext &context);
5354

55+
/*
56+
* Same as above for the typed `AnimatedProps` representation used by the
57+
* batched synchronous update path (`optimizedAnimatedPropUpdates`).
58+
*/
59+
void update(Tag tag, const AnimatedProps &props, const PropsParserContext &context);
60+
5461
/*
5562
* Drops the entry for `tag`, if any. Called by the systems that write
5663
* synchronous props when they stop managing a view, and by `prune`.

‎packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,15 @@ void UIManager::recordSynchronousTransform(Tag tag, const folly::dynamic& props)
801801
synchronousTransformOverlay_.update(tag, props, propsParserContext);
802802
}
803803

804+
void UIManager::recordSynchronousTransform(Tag tag, const AnimatedProps& props)
805+
const {
806+
if (!ReactNativeFeatureFlags::enableSynchronousTransformOverlay()) {
807+
return;
808+
}
809+
PropsParserContext propsParserContext{-1, *contextContainer_};
810+
synchronousTransformOverlay_.update(tag, props, propsParserContext);
811+
}
812+
804813
void UIManager::removeSynchronousTransform(Tag tag) const {
805814
synchronousTransformOverlay_.remove(tag);
806815
}

‎packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class UIManager final : public ShadowTreeDelegate {
101101
* `enableSynchronousTransformOverlay` feature flag is enabled.
102102
*/
103103
void recordSynchronousTransform(Tag tag, const folly::dynamic &props) const;
104+
void recordSynchronousTransform(Tag tag, const AnimatedProps &props) const;
104105

105106
/*
106107
* Drops the recorded synchronous transform for `tag`. To be called by the

‎packages/react-native/ReactCommon/react/renderer/uimanager/__docs__/SynchronousTransformOverlay.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ on animated views.
2020
Animation systems that write props through the platform entry points
2121
(`FabricUIManager.synchronouslyUpdateViewOnUIThread` on Android,
2222
`RCTSurfacePresenter`'s `schedulerDidSynchronouslyUpdateViewOnUIThread:props:`
23-
on iOS, `UIManager::synchronouslyUpdateViewOnUIThread` in C++) are covered
24-
automatically. Systems that bypass them call
23+
on iOS, `UIManager::synchronouslyUpdateViewOnUIThread` in C++, and the batched
24+
`AnimatedProps` path of `UIManager::synchronouslyUpdateAnimatedProps`) are
25+
covered automatically. Systems that bypass them call
2526
`UIManager::recordSynchronousTransform(tag, props)` with the props they applied,
2627
and `UIManager::removeSynchronousTransform(tag)` when they stop managing a view.
2728

‎packages/react-native/ReactCommon/react/renderer/uimanager/tests/SynchronousTransformOverlayTest.cpp‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <folly/dynamic.h>
1111
#include <gtest/gtest.h>
12+
#include <react/renderer/animationbackend/AnimatedProps.h>
1213
#include <react/renderer/components/view/ViewShadowNode.h>
1314
#include <react/renderer/core/PropsParserContext.h>
1415
#include <react/renderer/element/Element.h>
@@ -101,6 +102,32 @@ TEST_F(SynchronousTransformOverlayTest, parsesTransformLikeProps) {
101102
EXPECT_EQ(*transform, Transform::Translate(0, 100, 0));
102103
}
103104

105+
TEST_F(SynchronousTransformOverlayTest, parsesAnimatedProps) {
106+
auto animatedProps = AnimatedProps{};
107+
animatedProps.props.push_back(
108+
std::make_unique<AnimatedProp<Transform>>(
109+
TRANSFORM, Transform::Translate(0, 300, 0)));
110+
111+
overlay_.update(2, animatedProps, propsParserContext_);
112+
113+
auto transform = overlay_.getTransformOverride(*childShadowNode_);
114+
ASSERT_TRUE(transform.has_value());
115+
EXPECT_EQ(*transform, Transform::Translate(0, 300, 0));
116+
}
117+
118+
TEST_F(SynchronousTransformOverlayTest, parsesUntypedAnimatedProps) {
119+
auto animatedProps = AnimatedProps{};
120+
animatedProps.rawProps = std::make_unique<RawProps>(folly::dynamic::object(
121+
"transform",
122+
folly::dynamic::array(folly::dynamic::object("translateY", 300))));
123+
124+
overlay_.update(2, animatedProps, propsParserContext_);
125+
126+
auto transform = overlay_.getTransformOverride(*childShadowNode_);
127+
ASSERT_TRUE(transform.has_value());
128+
EXPECT_EQ(*transform, Transform::Translate(0, 300, 0));
129+
}
130+
104131
TEST_F(SynchronousTransformOverlayTest, mergesTransformOriginUpdates) {
105132
// The committed props have no transform origin; a synchronous update of
106133
// the origin alone applies to the committed transform (5, 5).

‎scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3927,6 +3927,7 @@ class facebook::react::SynchronousTransformOverlay : public facebook::react::Lay
39273927
public void clear();
39283928
public void prune(const std::function<bool(facebook::react::Tag)>& isAlive);
39293929
public void remove(facebook::react::Tag tag);
3930+
public void update(facebook::react::Tag tag, const facebook::react::AnimatedProps& props, const facebook::react::PropsParserContext& context);
39303931
public void update(facebook::react::Tag tag, const folly::dynamic& props, const facebook::react::PropsParserContext& context);
39313932
}
39323933

@@ -4217,6 +4218,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate {
42174218
public void appendChild(const std::shared_ptr<const facebook::react::ShadowNode>& parentShadowNode, const std::shared_ptr<const facebook::react::ShadowNode>& childShadowNode) const;
42184219
public void completeSurface(facebook::react::SurfaceId surfaceId, const std::shared_ptr<std::vector<std::shared_ptr<const facebook::react::ShadowNode>>>& rootChildren, facebook::react::ShadowTree::CommitOptions commitOptions);
42194220
public void dispatchCommand(const std::shared_ptr<const facebook::react::ShadowNode>& shadowNode, const std::string& commandName, const folly::dynamic& args) const;
4221+
public void recordSynchronousTransform(facebook::react::Tag tag, const facebook::react::AnimatedProps& props) const;
42204222
public void recordSynchronousTransform(facebook::react::Tag tag, const folly::dynamic& props) const;
42214223
public void registerCommitHook(facebook::react::UIManagerCommitHook& commitHook);
42224224
public void registerMountHook(facebook::react::UIManagerMountHook& mountHook);

‎scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6102,6 +6102,7 @@ class facebook::react::SynchronousTransformOverlay : public facebook::react::Lay
61026102
public void clear();
61036103
public void prune(const std::function<bool(facebook::react::Tag)>& isAlive);
61046104
public void remove(facebook::react::Tag tag);
6105+
public void update(facebook::react::Tag tag, const facebook::react::AnimatedProps& props, const facebook::react::PropsParserContext& context);
61056106
public void update(facebook::react::Tag tag, const folly::dynamic& props, const facebook::react::PropsParserContext& context);
61066107
}
61076108

@@ -6390,6 +6391,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate {
63906391
public void appendChild(const std::shared_ptr<const facebook::react::ShadowNode>& parentShadowNode, const std::shared_ptr<const facebook::react::ShadowNode>& childShadowNode) const;
63916392
public void completeSurface(facebook::react::SurfaceId surfaceId, const std::shared_ptr<std::vector<std::shared_ptr<const facebook::react::ShadowNode>>>& rootChildren, facebook::react::ShadowTree::CommitOptions commitOptions);
63926393
public void dispatchCommand(const std::shared_ptr<const facebook::react::ShadowNode>& shadowNode, const std::string& commandName, const folly::dynamic& args) const;
6394+
public void recordSynchronousTransform(facebook::react::Tag tag, const facebook::react::AnimatedProps& props) const;
63936395
public void recordSynchronousTransform(facebook::react::Tag tag, const folly::dynamic& props) const;
63946396
public void registerCommitHook(facebook::react::UIManagerCommitHook& commitHook);
63956397
public void registerMountHook(facebook::react::UIManagerMountHook& mountHook);

‎scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2464,6 +2464,7 @@ class facebook::react::SynchronousTransformOverlay : public facebook::react::Lay
24642464
public void clear();
24652465
public void prune(const std::function<bool(facebook::react::Tag)>& isAlive);
24662466
public void remove(facebook::react::Tag tag);
2467+
public void update(facebook::react::Tag tag, const facebook::react::AnimatedProps& props, const facebook::react::PropsParserContext& context);
24672468
public void update(facebook::react::Tag tag, const folly::dynamic& props, const facebook::react::PropsParserContext& context);
24682469
}
24692470

@@ -2700,6 +2701,7 @@ class facebook::react::UIManager : public facebook::react::ShadowTreeDelegate {
27002701
public void appendChild(const std::shared_ptr<const facebook::react::ShadowNode>& parentShadowNode, const std::shared_ptr<const facebook::react::ShadowNode>& childShadowNode) const;
27012702
public void completeSurface(facebook::react::SurfaceId surfaceId, const std::shared_ptr<std::vector<std::shared_ptr<const facebook::react::ShadowNode>>>& rootChildren, facebook::react::ShadowTree::CommitOptions commitOptions);
27022703
public void dispatchCommand(const std::shared_ptr<const facebook::react::ShadowNode>& shadowNode, const std::string& commandName, const folly::dynamic& args) const;
2704+
public void recordSynchronousTransform(facebook::react::Tag tag, const facebook::react::AnimatedProps& props) const;
27032705
public void recordSynchronousTransform(facebook::react::Tag tag, const folly::dynamic& props) const;
27042706
public void registerCommitHook(facebook::react::UIManagerCommitHook& commitHook);
27052707
public void registerMountHook(facebook::react::UIManagerMountHook& mountHook);

0 commit comments

Comments
 (0)