-
Notifications
You must be signed in to change notification settings - Fork 288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New architecture support #550
New architecture support #550
Conversation
So Fabric is actually making the performance worse? I was always hoping that Fabric is here to change that :D |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @j-piasecki!
Do we have an idea why Fabric could be slower?
RNFlashList.podspec
Outdated
s.dependency "RCTTypeSafety" | ||
s.dependency "ReactCommon/turbomodule/core" | ||
else | ||
s.platforms = { :ios => "9.0", :tvos => "9.0" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line has a different syntax than this. Also, given React Native only supports 12.4 as of now, can't we just target 11.0 versions on both Paper and Fabric?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated it, so that it matches the main
branch.
} | ||
override fun getExportedCustomDirectEventTypeConstants() = mutableMapOf( | ||
"onBlankAreaEvent" to mutableMapOf("registrationName" to "onBlankAreaEvent"), | ||
"topOnBlankAreaEvent" to mutableMapOf("registrationName" to "onBlankAreaEvent"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this? topOnBlankAreaEvent
does not exist, afaict?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without it I was getting Error: Unsupported top level event type "topOnBlankAreaEvent" dispatched
error. It's solved similarly in Screens and Gesture Handler.
@@ -0,0 +1,46 @@ | |||
/** | |||
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the generator always creating code for paper as well? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's not. I copied those files without modifying them. Do you think those comments should be removed?
fabricfixture/.buckconfig
Outdated
@@ -0,0 +1,6 @@ | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason for having buck-related files and code inside the fixture app?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, the fabricfixture
was the result of react-native init
. I found some problems with the app in the meantime so I set it up from scratch based on the fixture
app.
fabricfixture/.bundle/config
Outdated
@@ -0,0 +1,2 @@ | |||
BUNDLE_PATH: "vendor/bundle" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're creating a standalone app for the Fabric fixture – has the maintenance burden suffered on your side?
Have you considered looking at the react-native-test-app from Microsoft, which is now e.g. used in react-native-blur?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't looked at react-native-test-app
yet.
As for the standalone app for Fabric - I think it's currently the better approach. Most of the libraries don't support Fabric yet and having this layer of separation helps developing for both architectures. We also found that limiting the outside dependencies may be beneficial. As for now, every release of React Native (since 0.68) introduced some (at least for us) breaking changes - this caused problems when adapting our libraries to the newest versions, as we needed to fix problems in the app's dependencies first. Recently we've had that exact problem between Reanimated, Screens and Gesture Handler, because the example apps are dependant on our other libraries and we're considering simplifying them.
ios/Sources/AutoLayoutView.swift
Outdated
@@ -46,7 +51,15 @@ import UIKit | |||
/// Tracks where first pixel is drawn in the visible window | |||
private var lastMinBound: CGFloat = 0 | |||
|
|||
override func layoutSubviews() { | |||
private func getSubviews() -> [UIView] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit (if this can be overridden – or we can name this property differently):
private func getSubviews() -> [UIView] { | |
private override var subviews: [UIView] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to override it but it's being used internally by UIKit and was causing exceptions. I renamed it to viewsToLayout
, is that an ok name?
ios/Sources/AutoLayoutView.swift
Outdated
@@ -266,6 +289,7 @@ import UIKit | |||
} | |||
|
|||
private func footer() -> UIView? { | |||
return superview?.subviews.first(where:{($0 as? CellContainer)?.index == -1}) | |||
// TODO: Investigate hierarchy here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exactly do you mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was a reminder for me 😅. I've updated it now.
_autoLayoutView.onBlankAreaEventHandler = ^(CGFloat start, CGFloat end) { | ||
AutoLayoutViewComponentView *strongSelf = weakSelf; | ||
if (strongSelf->_eventEmitter != nullptr) { | ||
std::dynamic_pointer_cast<const facebook::react::AutoLayoutViewEventEmitter>(strongSelf->_eventEmitter) | ||
->onBlankAreaEvent(facebook::react::AutoLayoutViewEventEmitter::OnBlankAreaEvent{ | ||
.offsetStart = (int) floor(start), | ||
.offsetEnd = (int) floor(end), | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you link me some additional resources? This seems quite complicated for sending an event, so would love to learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was relying on how it's done in Screens. The weakSelf
is to make sure I don't cause a retain cycle doing this.
renderAheadOffset?: Double, | ||
enableInstrumentation?: boolean, | ||
disableAutoLayout?: boolean, | ||
onBlankAreaEvent?: DirectEventHandler<BlankAreaEvent>, // This type is not parsable by the TS codegen, once it is, let's rewrite this file to TS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know if this is tracked somewhere? Would really like to move to TS, at least eventually ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -0,0 +1,19 @@ | |||
// @ts-ignore TODO: remove once there is a .d.ts file with definitions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an issue for this somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly the same as above. Other that that - I don't know.
@j-piasecki – can we point to the iOS branch for now? This PR has bundled changes with iOS which I have, mistakenly, reviewed as well 😞 We could also bundle these two PRs together into this one (I think that's fine as long as you squash some commits, so it's easier to follow along commit-by-commit) |
Pointing this to the iOS branch would mean moving the PR to my fork, so I think we can bundle them together. I'll get to it soon. |
8462508
to
22fbea7
Compare
As for your question about the performance - at the moment the main suspect is the JS Fabric renderer as the performance improves significantly in release mode. |
@j-piasecki the posted performance number are from release mode or debug mode? |
@fortmarek They are from release mode. |
@j-piasecki Have you been able to look into why UI thread FPS is getting impacted with Fabric enabled? I think that's important before we call FlashList fabric ready. Let me know if I can help. |
I have looked into it briefly, but I don't have anything concrete yet. |
I have an update - the terrible performance on Android in debug mode is related to Hermes. It seems that the Hermes gets built in debug mode which slows it down quite a bit. After replacing the If I understand correctly, there is a plan to add support for Prefab to Hermes in React Native 0.71, which could mean that this particular problem will solve itself with the next React Native release. |
Sorry for jumping on this. I've been watching this as I have a side project app where I'm eager to try FlashList with Fabric.
Thanks for investigating this. Technically you should not be blocked by 0.71.x in any form.
Correct that we're actively looking into this. I'm wondering why this is affecting FlashList. Are you rebundling Hermes or doing anything related? If so, I'm curious to hear more as, in theory, libraries should behave transparently to the underlying JS engine. |
I may have misunderstood what using prefabs will mean. At the moment Hermes is being built from source, would that still be the case with prefabs? I think that this could be a potential cause of performance problems. I've just improved the fps of the benchmark in debug mode (the same that the numbers in the PR description are from) by about 10 times by using |
The way how hermes is built should be transparent to the end user and also to this library. The fact that we ship You can still pick between Debug and Release when building your app, and Hermes will follow accordingly.
This is really odd and, unless NDK 24 changed the default compilation flags, should not happen. |
Hey @cortinico, sorry for jumping in here. Unfortunately, the issue with Try creating a fresh RN 0.70 app, enable Fabric and add The last message in logcat is We are highly affected by this bug in Reanimated (when worklet throws JS error the app crashes on Android with Fabric enabled if using NDK 24). The crash is gone when building with NDK 21 (or just replacing |
Thanks for flagging this @tomekzaw. The issue is quite concerning to me and I'd like to investigate further. Can you open an issue on Reanimated or on React Native and let's keep the convo there. I don't want to sidetrack this PR for FlashList 👍 |
I understand, and while it should make no difference, it seems like the tools used to build hermes may have a substantial effect on its performance. I feel like it's important to keep it in mind, especially now that hermes is built from source and we cannot guarantee the setup. It may lead to hard-to-explain performance problems, like the one I encountered with FlashList. Also, it may be clear from your answer but I cannot infer whether the hermes will still be built locally in future 😅.
In that case, may I ask what's the reasoning for building it locally even though there is one already built available? |
Yes, we're looking into this at the moment. Most likely will land on the next major release of React Native
Sorry if I was unclear. What I meant that, regardless if you're building Hermes locally or getting a prebuilt, Hermes is 'variant aware'. So there is a To give you more context, the situation is a bit more complicate in terms of prebuilts as:
|
@cortinico @j-piasecki My main worry with fabric right now is that we're not able to maintain 60fps on the UI thread. It seems like drops in JS thread directly impact UI thread in the new architecture. |
I don't have any concrete ideas as to what may be the cause of it yet. What I can say is that after I overloaded the JS thread to the point where recycling the views in the list takes a few seconds, the scrolling itself remained smooth. Will investigate this more. |
## Description This PR improves developer experience of error handling in worklets by showing a RedBox with full error message rather than crashing the whole app on Android. **Note:** Android with Fabric enabled works only if building with NDK 21 via `yarn react-native run-android [--active-arch-only]`, crashes when building with NDK 24 from Android Studio, see Shopify/flash-list#550 (comment) for details (this is not an issue with Reanimated) <table> <thead> <tr> <th rowspan="2">Scenario</th> <th colspan="2">Before</th> <th colspan="2">After</th> </tr> <tr> <th>Paper</th> <th>Fabric</th> <th>Paper</th> <th>Fabric</th> </thead> <tbody> <tr> <td>worklet</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>nested worklet</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useAnimatedStyle</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useDerivedValue</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useFrameCallback</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>GestureDetector</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useAnimatedGestureHandler</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useAnimatedScrollHandler</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useScrollViewOffset</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> </tbody> </table> ## Test code and steps to reproduce ### Building with NDK 21 on M1-based Mac https://github.com/software-mansion/react-native-reanimated/blob/48af341d51ca289dc007fdfd81d124ae0c267523/FabricExample/android/build.gradle#L11-L17 ```diff - ndkVersion = "24.0.8215888" + ndkVersion = "21.4.7075529" ``` `ERROR: Unknown host CPU architecture: arm64` ```console code ~/Library/Android/sdk/ndk/21.4.7075529/ndk-build ``` ```diff #!/bin/sh DIR="$(cd "$(dirname "$0")" && pwd)" -$DIR/build/ndk-build "$@" +arch -x86_64 $DIR/build/ndk-build "$@" ``` ## Checklist - [ ] Included code example that can be used to test this change - [ ] Updated TS types - [ ] Added TS types tests - [ ] Added unit / integration tests - [ ] Updated documentation - [ ] Ensured that CI passes
This PR improves developer experience of error handling in worklets by showing a RedBox with full error message rather than crashing the whole app on Android. **Note:** Android with Fabric enabled works only if building with NDK 21 via `yarn react-native run-android [--active-arch-only]`, crashes when building with NDK 24 from Android Studio, see Shopify/flash-list#550 (comment) for details (this is not an issue with Reanimated) <table> <thead> <tr> <th rowspan="2">Scenario</th> <th colspan="2">Before</th> <th colspan="2">After</th> </tr> <tr> <th>Paper</th> <th>Fabric</th> <th>Paper</th> <th>Fabric</th> </thead> <tbody> <tr> <td>worklet</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>nested worklet</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useAnimatedStyle</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useDerivedValue</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useFrameCallback</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>GestureDetector</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useAnimatedGestureHandler</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useAnimatedScrollHandler</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> <tr> <td>useScrollViewOffset</td> <td align="center">💥</td> <td align="center">💥</td> <td align="center">✅</td> <td align="center">✅</td> </tr> </tbody> </table> https://github.com/software-mansion/react-native-reanimated/blob/48af341d51ca289dc007fdfd81d124ae0c267523/FabricExample/android/build.gradle#L11-L17 ```diff - ndkVersion = "24.0.8215888" + ndkVersion = "21.4.7075529" ``` `ERROR: Unknown host CPU architecture: arm64` ```console code ~/Library/Android/sdk/ndk/21.4.7075529/ndk-build ``` ```diff DIR="$(cd "$(dirname "$0")" && pwd)" -$DIR/build/ndk-build "$@" +arch -x86_64 $DIR/build/ndk-build "$@" ``` - [ ] Included code example that can be used to test this change - [ ] Updated TS types - [ ] Added TS types tests - [ ] Added unit / integration tests - [ ] Updated documentation - [ ] Ensured that CI passes
I think the stuttering may be caused by the text. Here's a part of a profiler session on Samsung Galaxy A53 (Release build): The duration of the |
After editing the benchmark to render rectangles in place of text I've got the following results: Fabric:
Paper:
Those numbers seem much closer than those from the original post. |
f16ec5d
to
eb5cfad
Compare
I was able to get rid of a few hacks around view hierarchy on iOS by modifying methods responsible for mounting children, so the native hierarchy is the same as on the old architecture. |
).build(); | ||
} | ||
override fun getExportedCustomDirectEventTypeConstants() = mutableMapOf( | ||
"onBlankAreaEvent" to mutableMapOf("registrationName" to "onBlankAreaEvent"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need here both onBlankAreaEvent
and topOnBlankAreaEvent
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, I don't remember 😅. It's been a while since I've written this, but it doesn't seem to be causing any issues when I removed topOnBlankAreaEvent
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@j-piasecki This is good to go. Just a few comments. It looks like E2E tests are breaking. We can proceed with the merge if you can address the comments and the E2E tests. Huge thanks for your incredible work on this PR!
@@ -274,6 +282,13 @@ import UIKit | |||
} | |||
|
|||
private func footer() -> UIView? { | |||
return superview?.subviews.first(where:{($0 as? CellContainer)?.index == -1}) | |||
// On the new arch, AutoLayoutView is wrapped with AutoLayoutViewComponentView, so we need to go up one more level |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain why do we need the extra view?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the new architecture a lot of the code needed by views is implemented inside RCTViewComponentView
which is written in ObjC++. Ideally to create a new view component on the new architecture, one would inherit after it but it has two problems:
- it's not implemented on the old architecture, and would require some tricks to make it work
- I don't think it's possible for a Swift class to inherit after ObjC++ class
Instead of that, it's possible to create a wrapper view that inherits after RCTViewComponentView
(in this case AutoLayoutViewComponentView
) that will handle props and events of the underlying view that's already been implemented.
@@ -0,0 +1,288 @@ | |||
diff --git a/node_modules/react-native-safe-area-context/android/src/main/jni/CMakeLists.txt b/node_modules/react-native-safe-area-context/android/src/main/jni/CMakeLists.txt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need these patches now? Can we just upgrade the dependencies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are necessary to address the breaking changes between different versions of React Native.
Can we just upgrade the dependencies?
In this case, it would need to be a downgrade since both screens
and safe-area-context
are on the newest versions, which are adapted for React Native 0.74.
Upgrading the version of RN used in the fixture app would also help (upgrading other dependencies like reanimated
and gesture-handler
would also be necessary) but I'm not sure whether that's desirable or even in scope of this PR.
I've changed the fixture app to have the new arch disabled by default - I couldn't get Detox to work on the new arch, it always hangs on waiting until the app is ready. It seems like it's not tested on the new arch - https://wix.github.io/Detox/docs/next/introduction/environment-setup/ |
I cannot reproduce failing e2e tests locally. It's possible that's because of e5d3bde, but in case it doesn't help, is it possible to get the screenshots from the tests where it failed to match the snapshot from the github action? |
@j-piasecki I'm gonna look into the e2e tests shortly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks a lot @j-piasecki for this PR!
LFG 🚀 |
Is this published? |
isn't published yet? |
For tests follow steps package.json: "@shopify/flash-list": "git+https://github.com/Shopify/flash-list.git" |
Any timelines for when this will be published? |
# Why The current docs has a grammar issue on the Flashlist section. When fixing the issue, I found out that Flashlist now does support the new architecture (see Shopify/flash-list#550 released in [v1.7.0](https://github.com/Shopify/flash-list/releases/tag/v1.7.0) on July 4th) and propose this line item be removed from the docs. <!-- Please describe the motivation for this PR, and link to relevant GitHub issues, forums posts, or feature requests. --> # How I removed the note about FlashList. <!-- How did you build this feature or fix this bug and why? --> # Test Plan N/A <!-- Please describe how you tested this change and how a reviewer could reproduce your test, especially if this PR does not include automated tests! If possible, please also provide terminal output and/or screenshots demonstrating your test/reproduction. --> # Checklist <!-- Please check the appropriate items below if they apply to your diff. This is required for changes to Expo modules. --> - [x] Documentation is up to date to reflect these changes (eg: https://docs.expo.dev and README.md). - [x] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) - [x] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin).
Description
This PR adds Fabric integration to the FlashList. Here are some more important highlights:
FabricUIManager
so I made an Extension file for Fabric and for Paper (the correct one is added to the source set inbuild.gradle
)CellContainer
withCellContainerComponentView
as keeping aCellContainer
in everyCellContainerComponentView
didn't look like a very good idea performance-wiseAutoLayoutView
, I stayed with wrapping it with the Fabric Component (AutoLayoutViewComponentView
). I didn't want to accidentally break the functionality converting in toobj-c
. It may be possible that they can be merged via inheritance but I'm not that knowledgeable in interop betweenswift
andobj-c
and couldn't get it to workAutoLayoutView
containing a closure, which in turn is set by theAutoLayoutViewComponentView
. This way the code in Swift has access to the generatedobj-c++
method.safe-area-context
andscreens
to make sure they work with RN 0.72 on the new architectureReviewers’ hat-rack 🎩
Screenshots or videos (if needed)
Checklist