Skip to content

Register animation frontends with the animation backend (#59585) - #59585

Open
bartlomiejbloniarz wants to merge 2 commits into
mainfrom
bartlomiejbloniarz/animated-props-register-frontends
Open

bartlomiejbloniarz wants to merge 2 commits into
mainfrom
bartlomiejbloniarz/animated-props-register-frontends

Conversation

@bartlomiejbloniarz

@bartlomiejbloniarz bartlomiejbloniarz commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

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

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

@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
@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 D128154522.

@bartlomiejbloniarz
bartlomiejbloniarz force-pushed the bartlomiejbloniarz/animated-props-register-frontends branch 2 times, most recently from 253f3c9 to 87935af Compare October 28, 2026 21:03
@meta-codesync
meta-codesync Bot force-pushed the bartlomiejbloniarz/animated-props-register-frontends branch from 87935af to 907988d Compare October 28, 2026 21:28
@meta-codesync meta-codesync Bot changed the title Register animation frontends with the animation backend Register animation frontends with the animation backend (#59585) Oct 28, 2026
meta-codesync Bot pushed a commit that referenced this pull request Oct 28, 2026
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
meta-codesync Bot pushed a commit that referenced this pull request Oct 28, 2026
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
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 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 D128154522.

@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @bartlomiejbloniarz in 907988d

When will my fix make it into a release? | How to file a pick request?

@react-native-bot react-native-bot added the Merged This PR has been merged. label Oct 28, 2026
@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @bartlomiejbloniarz in 6a44efa

When will my fix make it into a release? | How to file a pick request?

meta-codesync Bot pushed a commit that referenced this pull request Oct 28, 2026
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
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
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Visual regression

2 visual difference(s) at 907988d384c6. Visual differences need maintainer approval. Review the report, then apply visual-change-approved.

ios scroll-view/scroll-to/end: Changed, 273004 px (37.729%)

diff

Expected and Captured
Expected Captured
expected captured

ios scroll-view/scroll-to/start: Changed, 280136 px (38.714%)

diff

Expected and Captured
Expected Captured
expected captured

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
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
meta-codesync Bot pushed a commit that referenced this pull request Oct 30, 2026
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
meta-codesync Bot pushed a commit that referenced this pull request Oct 30, 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 force-pushed the bartlomiejbloniarz/animated-props-register-frontends branch from 907988d to 3eb25a1 Compare October 30, 2026 12:06
@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @bartlomiejbloniarz in 3eb25a1

When will my fix make it into a release? | How to file a pick request?

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. Merged This PR has been merged. 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.

2 participants