Skip to content

Keep C++ Animated's direct updates out of React commits with the animation backend (#59586) - #59586

Open
bartlomiejbloniarz wants to merge 3 commits into
mainfrom
bartlomiejbloniarz/animated-props-direct-path
Open

bartlomiejbloniarz wants to merge 3 commits into
mainfrom
bartlomiejbloniarz/animated-props-direct-path

Conversation

@bartlomiejbloniarz

@bartlomiejbloniarz bartlomiejbloniarz commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary:
Stacked on #59585, which is stacked on #59584. Review the last two commits: the change and its tests.

With the shared animation backend, the backend's commit hook writes C++ Animated's latest values into every React commit made while it animates. C++ Animated without the backend does not do this. It writes non-layout props straight to the views, keeps them over React's mounts with AnimatedMountingOverrideDelegate, and commits the final values when an animation finishes.

This PR adds a way to experiment with the second approach on the backend. It adds commitDirectProps to AnimationFrontend and the enableCxxAnimatedDirectPathInBackend flag. With the flag, C++ Animated registers with commitDirectProps = false and behaves as it does without the backend:

  • The commit hook does not write its direct values into React commits. Layout updates are unchanged.
  • AnimatedMountingOverrideDelegate keeps the values of connected props nodes over React mounts.
  • The last update of an animation is recorded apart, and the end-of-animation commit writes it.
  • One end-of-animation commit per surface is scheduled at a time.

Without the flag, C++ Animated registers with commitDirectProps = true and the end-of-animation commit is unchanged. Turning commitDirectProps off without the flag asserts.

Changelog:

[INTERNAL] [ADDED] - Add enableCxxAnimatedDirectPathInBackend to keep C++ Animated's direct updates out of React commits with the shared animation backend

Test Plan:
Unit tests. AnimatedPropsRegistryTest, AnimationBackendFrontendTest and NativeAnimatedFrontendTests cover which values the commit hook writes, when final values are written, taken and restored, that older recorded values do not overwrite a final value, and one end-of-animation commit per surface. The tests for final values and for one commit per surface fail without the code they cover.

Fantom.

  • yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js runs with the flag on and off and passes.
  • yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackendDirectUpdates-itest.js checks that a React commit of an animated view keeps its animated value mounted, with the flag on and off. It fails without the mount override delegate.
  • yarn fantom packages/react-native/Libraries/Animated/__tests__/Animated-itest.js passes. I also ran it locally with the backend and the flag forced on. Fantom allows at most 12 flag combinations per file, so that run is not encoded in the file.

Devices. RNTester on an Android emulator and an iOS simulator, with the backend and C++ Animated on, once with the flag on and once with it off. I tracked a square frame by frame in screen recordings:

Check Android, flag on Android, flag off iOS, flag on iOS, flag off
React re-renders the view every 50 ms during a looping native animation No backward jumps No backward jumps No backward jumps No backward jumps
The animation finishes, then React keeps re-rendering Keeps the final values Keeps the final values Keeps the final values Keeps the final values
setValue(0), then React keeps re-rendering Stays reset Stays reset Stays reset Stays reset

Differential Revision: D128154682

Pulled By: bartlomiejbloniarz

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 28, 2026
@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner p: Facebook Partner: Facebook labels Oct 28, 2026
@bartlomiejbloniarz
bartlomiejbloniarz force-pushed the bartlomiejbloniarz/animated-props-direct-path branch from c3fd7a4 to 551099e Compare October 28, 2026 20:57
Summary:
`UIManager::startSurface` runs the callbacks registered with `UIManager::addOnSurfaceStartCallback` after adding the shadow tree. `UIManager::startEmptySurface`, which `SurfaceHandler` uses when a surface has no module name, only added the shadow tree, so those callbacks never ran for such surfaces.

The shared animation backend, `ViewTransitionModule` and C++ Animated set up per-surface state in that callback. C++ Animated installs `AnimatedMountingOverrideDelegate` there. Fantom starts all its surfaces without a module name, so none of them saw a surface start in Fantom tests.

`startEmptySurface` now notifies the delegate the same way `startSurface` does.

## Changelog:

[GENERAL] [FIXED] - Surfaces started without a module name now run the callbacks registered with `UIManager::addOnSurfaceStartCallback`

Pull Request resolved: #59584

Test Plan:
- `SchedulerDelegateInvalidationTest.EmptySurfaceStartRunsSurfaceStartCallbacks`: starting a surface with no module name runs a registered surface-start callback with that surface's shadow tree. It fails without this change.
- `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` passes.

Differential Revision: D128154376

Pulled By: bartlomiejbloniarz
@bartlomiejbloniarz
bartlomiejbloniarz force-pushed the bartlomiejbloniarz/animated-props-direct-path branch from 551099e to c4fcf12 Compare October 28, 2026 21:03
@meta-codesync

meta-codesync Bot commented Oct 28, 2026

Copy link
Copy Markdown

@bartlomiejbloniarz has imported this pull request. If you are a Meta employee, you can view this in D128154682.

meta-codesync Bot pushed a commit that referenced this pull request Oct 28, 2026
…ation backend (#59586)

Summary:
Stacked on #59585, which is stacked on #59584. Review the last two commits: the change and its tests.

With the shared animation backend, the backend's commit hook writes C++ Animated's latest values into every React commit made while it animates. C++ Animated without the backend does not do this. It writes non-layout props straight to the views, keeps them over React's mounts with `AnimatedMountingOverrideDelegate`, and commits the final values when an animation finishes.

This PR adds a way to experiment with the second approach on the backend. It adds `commitDirectProps` to `AnimationFrontend` and the `enableCxxAnimatedDirectPathInBackend` flag. With the flag, C++ Animated registers with `commitDirectProps = false` and behaves as it does without the backend:
- The commit hook does not write its direct values into React commits. Layout updates are unchanged.
- `AnimatedMountingOverrideDelegate` keeps the values of connected props nodes over React mounts.
- The last update of an animation is recorded apart, and the end-of-animation commit writes it.
- One end-of-animation commit per surface is scheduled at a time.

Without the flag, C++ Animated registers with `commitDirectProps = true` and the end-of-animation commit is unchanged. Turning `commitDirectProps` off without the flag asserts.

## Changelog:

[INTERNAL] [ADDED] - Add `enableCxxAnimatedDirectPathInBackend` to keep C++ Animated's direct updates out of React commits with the shared animation backend

Pull Request resolved: #59586

Test Plan:
**Unit tests.** `AnimatedPropsRegistryTest`, `AnimationBackendFrontendTest` and `NativeAnimatedFrontendTests` cover which values the commit hook writes, when final values are written, taken and restored, that older recorded values do not overwrite a final value, and one end-of-animation commit per surface. The tests for final values and for one commit per surface fail without the code they cover.

**Fantom.**
- `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` runs with the flag on and off and passes.
- `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackendDirectUpdates-itest.js` checks that a React commit of an animated view keeps its animated value mounted, with the flag on and off. It fails without the mount override delegate.
- `yarn fantom packages/react-native/Libraries/Animated/__tests__/Animated-itest.js` passes. I also ran it locally with the backend and the flag forced on. Fantom allows at most 12 flag combinations per file, so that run is not encoded in the file.

**Devices.** RNTester on an Android emulator and an iOS simulator, with the backend and C++ Animated on, once with the flag on and once with it off. I tracked a square frame by frame in screen recordings:

| Check | Android, flag on | Android, flag off | iOS, flag on | iOS, flag off |
|---|---|---|---|---|
| React re-renders the view every 50 ms during a looping native animation | No backward jumps | No backward jumps | No backward jumps | No backward jumps |
| The animation finishes, then React keeps re-rendering | Keeps the final values | Keeps the final values | Keeps the final values | Keeps the final values |
| `setValue(0)`, then React keeps re-rendering | Stays reset | Stays reset | Stays reset | Stays reset |

Differential Revision: D128154682

Pulled By: bartlomiejbloniarz
@meta-codesync meta-codesync Bot changed the title Keep C++ Animated's direct updates out of React commits with the animation backend Keep C++ Animated's direct updates out of React commits with the animation backend (#59586) Oct 28, 2026
@meta-codesync
meta-codesync Bot force-pushed the bartlomiejbloniarz/animated-props-direct-path branch from c4fcf12 to f7f2743 Compare October 28, 2026 21:28
@meta-codesync

meta-codesync Bot commented Oct 28, 2026

Copy link
Copy Markdown

@bartlomiejbloniarz has exported this pull request. If you are a Meta employee, you can view the originating Diff in D128154682.

Summary:
Stacked on #59584. Review the last two commits: the change and its tests.

Producers of animation mutations, such as C++ Animated and Reanimated, use the shared animation backend through anonymous frame callbacks. Settings that differ between producers have nowhere to live.

A producer can now register once as an `AnimationFrontend` with its frame callback, then start and stop it by id with `startFrontend` and `stopFrontend`. Each frame runs the callbacks passed to `start(callback)` first, then the started frontends in registration order. `start(callback)`, `stop(callbackId)` and `pushAnimationMutations(callback)` are unchanged.

C++ Animated now registers a frontend instead of starting a callback. Its frame callback therefore runs after other producers' `start(callback)` callbacks instead of in the order they were started.

The next PR adds the first frontend setting.

## Changelog:

[INTERNAL] [ADDED] - Let animation producers register with the animation backend as frontends

Pull Request resolved: #59585

Test Plan:
- `AnimationBackendFrontendTest`: a started frontend runs once per frame, starting and stopping are idempotent, frames are requested while any frontend or callback is started, a frontend without a frame callback is not started, and frames run in registration order after the callbacks.
- `NativeAnimatedFrontendTests.registersAndStartsAFrontend`: C++ Animated registers and starts a frontend with the backend instead of starting a callback.
- The other `animationbackend` and `animated` gtests pass.
- `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` and `yarn fantom packages/react-native/Libraries/Animated/__tests__/Animated-itest.js` pass.

Differential Revision: D128154522

Pulled By: bartlomiejbloniarz
…ation backend (#59586)

Summary:
Stacked on #59585, which is stacked on #59584. Review the last two commits: the change and its tests.

With the shared animation backend, the backend's commit hook writes C++ Animated's latest values into every React commit made while it animates. C++ Animated without the backend does not do this. It writes non-layout props straight to the views, keeps them over React's mounts with `AnimatedMountingOverrideDelegate`, and commits the final values when an animation finishes.

This PR adds a way to experiment with the second approach on the backend. It adds `commitDirectProps` to `AnimationFrontend` and the `enableCxxAnimatedDirectPathInBackend` flag. With the flag, C++ Animated registers with `commitDirectProps = false` and behaves as it does without the backend:
- The commit hook does not write its direct values into React commits. Layout updates are unchanged.
- `AnimatedMountingOverrideDelegate` keeps the values of connected props nodes over React mounts.
- The last update of an animation is recorded apart, and the end-of-animation commit writes it.
- One end-of-animation commit per surface is scheduled at a time.

Without the flag, C++ Animated registers with `commitDirectProps = true` and the end-of-animation commit is unchanged. Turning `commitDirectProps` off without the flag asserts.

## Changelog:

[INTERNAL] [ADDED] - Add `enableCxxAnimatedDirectPathInBackend` to keep C++ Animated's direct updates out of React commits with the shared animation backend

Pull Request resolved: #59586

Test Plan:
**Unit tests.** `AnimatedPropsRegistryTest`, `AnimationBackendFrontendTest` and `NativeAnimatedFrontendTests` cover which values the commit hook writes, when final values are written, taken and restored, that older recorded values do not overwrite a final value, and one end-of-animation commit per surface. The tests for final values and for one commit per surface fail without the code they cover.

**Fantom.**
- `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js` runs with the flag on and off and passes.
- `yarn fantom packages/react-native/Libraries/Animated/__tests__/AnimatedBackendDirectUpdates-itest.js` checks that a React commit of an animated view keeps its animated value mounted, with the flag on and off. It fails without the mount override delegate.
- `yarn fantom packages/react-native/Libraries/Animated/__tests__/Animated-itest.js` passes. I also ran it locally with the backend and the flag forced on. Fantom allows at most 12 flag combinations per file, so that run is not encoded in the file.

**Devices.** RNTester on an Android emulator and an iOS simulator, with the backend and C++ Animated on, once with the flag on and once with it off. I tracked a square frame by frame in screen recordings:

| Check | Android, flag on | Android, flag off | iOS, flag on | iOS, flag off |
|---|---|---|---|---|
| React re-renders the view every 50 ms during a looping native animation | No backward jumps | No backward jumps | No backward jumps | No backward jumps |
| The animation finishes, then React keeps re-rendering | Keeps the final values | Keeps the final values | Keeps the final values | Keeps the final values |
| `setValue(0)`, then React keeps re-rendering | Stays reset | Stays reset | Stays reset | Stays reset |

Differential Revision: D128154682

Pulled By: bartlomiejbloniarz
@meta-codesync
meta-codesync Bot force-pushed the bartlomiejbloniarz/animated-props-direct-path branch from f7f2743 to 3ee655b Compare October 28, 2026 22:46
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Visual regression

1 visual difference(s) at 3ee655b649ba. Visual differences need maintainer approval. Review the report, then apply visual-change-approved.

ios touchable/feedback-events/pressed: Changed, 1620 px (0.285%)

diff

Expected and Captured
Expected Captured
expected captured

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported p: Facebook Partner: Facebook p: Software Mansion Partner: Software Mansion Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant