feat: enable runtime shadow node reference updates for React commits#8776
feat: enable runtime shadow node reference updates for React commits#8776jpudysz wants to merge 5 commits into
Conversation
|
Hi @jpudysz! Thanks for testing it so thoroughly. We wanted to introduce RSNRU to reanimated for some time as it would unlock huge performance benefits for our users. There are some problems with making the switch, that we want to first try to resolve. One of them is that it would break animation fill-mode in css animations. My hope is that we soon are able to migrate and your issue will be gone. Using RSNRU in the commit hook is also a part of the Animation Backend in RN. So we will move there eventually. I am worried though about the solution for the Dynamic path being just to use the synchronous updates feature flag. Synchronous updates are used only for non-layout styles, so wouldn't this break when you animate layout in the same way it breaks now? |
I saw it in the latest release notes, but it looks like you can update only a few properties right?
Me too. I wasn’t sure why RSNRU doesn’t work there. I confirmed that my update runs in sequence between ticks, but somehow it gets lost.
For most cases I’m trying to address, which are theme changes, it will work fine. Of course, if the user rotates the app while an animation is ongoing, we can see some Shadow Tree issues. To be honest, that would be fine for now, and we could wait for the I would be happy if you could find some time to check the dynamic path. I can put together a repro with the latest Reanimated and Unistyles. |
There are two ways props will be passed to the backend. One for some explicitly supported props, and others with
Yes, a repro would be great |
|
I think I'm actually close to figuring out a solution without RSNRU that should prevent Reanimated from overriding things. Need to run a few more tests to make sure it's solid though. Give me a few days to validate everything and I'll update you here with what I find. Thanks again! |
|
Ok, long story short, I was able to find a way to make it work quite reliably. It can only fail when a ShadowTree update is used together with heavy, continuous Reanimated animations that push updates every frame. I think that is good enough, as almost no one would change the theme while running such heavy animations on the same screen. I am eagerly waiting for AnimatedBackend, as it should fix all these issues (or create new ones 😅). |
Improve ShadowTree interoperability between Reanimated and 3rd party libraries
Hello Software Mansion team,
After countless hours of debugging and figuring out how to make the ShadowTree more shareable when using both Reanimated and Unistyles or Uniwind, I came up with a minimal solution that fixes all issues reported by the community.
I tested it across four repositories and the Uniwind Pro repository, which includes mappings for every React Native component (as shown in the videos below):
#7728
jpudysz/react-native-unistyles#1045
jpudysz/react-native-unistyles#888
jpudysz/react-native-unistyles#887
I decided to open a PR with a fix that I believe will satisfy everyone.
Introduction to the problem
We already discussed this in multiple GitHub threads, on the React Native Discord channel, and in person at conferences, but I think it is still worth explaining once more.
During the Unistyles 3.0 beta, I was using your approach with Commit and Mount hooks. As soon as I noticed performance issues, especially with ScrollView, I decided to look for another solution. I ended up with a single ShadowTree update, thanks to React Native 0.78 introducing runtime shadow node reference updates by default. Committing the ShadowTree update ensured that values were immediately accessible on every shadow node and after each ShadowTree clone.
After some time, I noticed new issues that I was fortunately able to fix using the
nativeProps_DEPRECATEDflag. This worked mostly fine, but relying on an internal Animated API withdeprecatedin its name is not a good long-term solution.After the release of Reanimated 4.0, when ShadowTree updates were removed from the MountHook and
uiManagerexposed a newupdateShadowTreemethod, we entered a new phase where we need to work together to figure out how both libraries can cooperate at the C++ level.With all of this in mind, let’s look at Reanimated internals.
From my observations, Reanimated updates the ShadowTree through two paths. The first one is static (as I call it) where a linear animation starts and stops after some time. The second one is dynamic, where the animation is continuous and requires frequent ShadowTree updates.
Let’s consider two scenarios in which a user:
Static
In static mode, Reanimated uses the CommitHook exclusively, followed by the MountHook, with its own traits and props mapping.
If there is no node managed by Reanimated, everything works smoothly:
1.mov
As soon as at least one node is marked as Animated, commits are overridden:
2.mov
It works inconsistently, even on nodes that are not managed by Reanimated.
Dynamic
In this mode, Reanimated uses
commitUpdatesinReanimatedModuleProxy, then the CommitHook, and finally the MountHook.This case is rare, but still possible, for example when a user continuously animates a theme change icon, such as a spinning sun icon.
3.mov
What I tried
I tried a few solutions but wasn’t happy with the outcome.
Rely more on
nativeProps_DEPRECATEDI tried updating your cloning mechanism to also copy unaffected siblings of affected nodes. It produced successful results, but I abandoned it because of the deprecated API. Additionally, it would slow down users who don’t use Unistyles or Uniwind.
Distinguish Unistyles/Uniwind commits
I use
uiManager.updateShadowTreewhich defaults toShadowTreeCommitSource::Unknown, and when I noticed the new feature flag (USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS) intended to improve performance, I hoped I could use this mechanism. Unfortunately,ScrollViewalso relies on it, so it is not exclusive to 3rd libraries and will be used by regular users.I tried adding a custom trait, pausing for one frame by skipping a single Reanimated update, and similar approaches. The results worked only partially and were not satisfactory. I encountered visual flashes, among other issues and the solution required significant changes to your code, so I ultimately abandoned this idea.
Solution
Static
After another debugging session and comparing your algorithm with the React Native algorithm, I noticed that you don’t use Runtime Shadow Node Reference Updates RSNRU.
It was disabled a few months ago by @bartlomiejbloniarz:
Don't transfer reanimated updates to ReactJS (#7529)
I’m not sure when the animation is removed, but without this flag no other ShadowTree update can make it into the ShadowTree, because Reanimated immediately overrides it and treats it as its own commit.
With this flag enabled, everything works correctly, even though you still override the commit for managed nodes:
4.mov
5.mov
Additionally, I was able to drop
nativeProps_DEPRECATED, so I no longer need to rely on a hidden React Native escape hatch.Dynamic
This fix alone does not help with dynamic animations:
6.mov
After further investigation and exploring
ReanimatedModuleProxy, I noticed that we can skip the ShadowTree commit path for non-layout properties, which makes total sense when switching themes.Enabling both flags below gives excellent results:
7.mov
8.mov
Proposed solution
Initially, I wanted to introduce a new feature flag to address this, but if you need it for some removals, I believe we can enable it only when a Reanimated trait is present. For other sources, such as
UnknownorReact, it would simply be skipped.I hope this makes sense and that it can land in the next release, as it would resolve all ongoing integration issues.