Skip to content
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

feat: refactor snapshots when going back on Fabric #2134

Merged
merged 26 commits into from
Jul 31, 2024

Conversation

WoLewicki
Copy link
Member

@WoLewicki WoLewicki commented May 14, 2024

Description

PR adding snapshots when going back on Fabric on Android and changing the behavior a bit on iOS.

@WoLewicki WoLewicki marked this pull request as ready for review May 16, 2024 13:21
@WoLewicki WoLewicki changed the title feat: first look at working version on fabric feat: refactor snapshots when going back on Fabric May 16, 2024
Copy link
Member

@tboba tboba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Overall, this code looks good to me. Just left a few remarks there 🎉

@@ -4,7 +4,7 @@ package = JSON.parse(File.read(File.join(__dir__, "package.json")))

new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
platform = new_arch_enabled ? "11.0" : "9.0"
source_files = new_arch_enabled ? 'ios/**/*.{h,m,mm,cpp}' : ["ios/**/*.{h,m,mm}", "cpp/**/*.{cpp,h}"]
source_files = new_arch_enabled ? 'ios/**/*.{h,m,mm,cpp}' : ["ios/**/*.{h,m,mm}", "cpp/RNScreensTurboModule.cpp", "cpp/RNScreensTurboModule.h"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we're not scoping all .cpp and .h files right now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the new files added to the folder should not be compiled on old arch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's worth to consider grouping the files. E.g. place screen-transition related staff in a cpp/transition / cpp/turbomodule directory & new screen listeners into cpp/mounting or sth similar.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do a cleanup of those in the follow-up PR?

android/CMakeLists.txt Outdated Show resolved Hide resolved
android/src/main/cpp/NativeProxy.h Outdated Show resolved Hide resolved
@tboba
Copy link
Member

tboba commented May 20, 2024

Hi @WoLewicki! While I was testing all examples from FabricTestExample, I've stumbled onto one app crash while being on the branch from this PR. When I'm trying to run Test640 and I'm coming back to the previous screen (from the screen with TouchableOpacities), I'm getting crash with exception AssertionException and IndexOutOfBoundsException. Do you know what might be the cause of this? 🤔

Exception from Logcat
2024-05-20 15:56:17.375 23934-23978 unknown:SoftAssertions  com.fabrictestexample                E  Unhandled SoftException
                                                                                                    com.facebook.react.bridge.AssertionException: Expected to run on UI thread!
                                                                                                    	at com.facebook.react.bridge.SoftAssertions.assertCondition(SoftAssertions.java:37)
                                                                                                    	at com.facebook.react.bridge.UiThreadUtil.assertOnUiThread(UiThreadUtil.java:44)
                                                                                                    	at com.facebook.react.fabric.FabricUIManager.resolveView(FabricUIManager.java:921)
                                                                                                    	at com.swmansion.rnscreens.NativeProxy.notifyScreenRemoved(NativeProxy.kt:26)
                                                                                                    	at com.facebook.jni.NativeRunnable.run(Native Method)
                                                                                                    	at android.os.Handler.handleCallback(Handler.java:958)
                                                                                                    	at android.os.Handler.dispatchMessage(Handler.java:99)
                                                                                                    	at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27)
                                                                                                    	at android.os.Looper.loopOnce(Looper.java:205)
                                                                                                    	at android.os.Looper.loop(Looper.java:294)
                                                                                                    	at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:233)
                                                                                                    	at java.lang.Thread.run(Thread.java:1012)
2024-05-20 15:56:17.420 23934-23934 AndroidRuntime          com.fabrictestexample                D  Shutting down VM
2024-05-20 15:56:17.422 23934-23934 AndroidRuntime          com.fabrictestexample                E  FATAL EXCEPTION: main
                                                                                                    Process: com.fabrictestexample, PID: 23934
                                                                                                    java.lang.IndexOutOfBoundsException: getChildDrawingOrder() returned invalid index 1 (child count is 1)
                                                                                                    	at android.view.ViewGroup.getAndVerifyPreorderedIndex(ViewGroup.java:2100)
                                                                                                    	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4314)
                                                                                                    	at android.view.View.draw(View.java:23892)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22756)
                                                                                                    	at android.view.View.draw(View.java:23620)
                                                                                                    	at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
                                                                                                    	at com.facebook.react.views.view.ReactViewGroup.drawChild(ReactViewGroup.java:873)
                                                                                                    	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4341)
                                                                                                    	at com.facebook.react.views.view.ReactViewGroup.dispatchDraw(ReactViewGroup.java:846)
                                                                                                    	at android.view.View.draw(View.java:23892)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22756)
                                                                                                    	at android.view.View.draw(View.java:23620)
                                                                                                    	at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
                                                                                                    	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4341)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22747)
                                                                                                    	at android.view.View.draw(View.java:23620)
                                                                                                    	at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
                                                                                                    	at androidx.coordinatorlayout.widget.CoordinatorLayout.drawChild(CoordinatorLayout.java:1312)
                                                                                                    	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22747)
                                                                                                    	at android.view.View.draw(View.java:23620)
                                                                                                    	at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
                                                                                                    	at com.swmansion.rnscreens.ScreenStack.performDraw(ScreenStack.kt:315)
                                                                                                    	at com.swmansion.rnscreens.ScreenStack.access$performDraw(ScreenStack.kt:15)
                                                                                                    	at com.swmansion.rnscreens.ScreenStack$DrawingOp.draw(ScreenStack.kt:327)
                                                                                                    	at com.swmansion.rnscreens.ScreenStack.drawAndRelease(ScreenStack.kt:282)
                                                                                                    	at com.swmansion.rnscreens.ScreenStack.dispatchDraw(ScreenStack.kt:298)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22747)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4540)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4513)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22712)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4540)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4513)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22712)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4540)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4513)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22712)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4540)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4513)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22712)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4540)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4513)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22712)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4540)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4513)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22712)
                                                                                                    	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4540)
                                                                                                    	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4513)
                                                                                                    	at android.view.View.updateDisplayListIfDirty(View.java:22712)
                                                                                                    	at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:694)
                                                                                                    	at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:700)
                                                                                                    	at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:798)
                                                                                                    	at android.view.ViewRootImpl.draw(ViewRootImpl.java:4939)
                                                                                                    	at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4643)
                                                                                                    	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3822)
                                                                                                    	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2465)
                                                                                                    	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9305)
2024-05-20 15:56:17.422 23934-23934 AndroidRuntime          com.fabrictestexample                E  	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1339)
                                                                                                    	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1348)
                                                                                                    	at android.view.Choreographer.doCallbacks(Choreographer.java:952)
                                                                                                    	at android.view.Choreographer.doFrame(Choreographer.java:882)
                                                                                                    	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1322)
                                                                                                    	at android.os.Handler.handleCallback(Handler.java:958)
                                                                                                    	at android.os.Handler.dispatchMessage(Handler.java:99)
                                                                                                    	at android.os.Looper.loopOnce(Looper.java:205)
                                                                                                    	at android.os.Looper.loop(Looper.java:294)
                                                                                                    	at android.app.ActivityThread.main(ActivityThread.java:8177)
                                                                                                    	at java.lang.reflect.Method.invoke(Native Method)
                                                                                                    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
                                                                                                    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

@tboba tboba self-requested a review June 11, 2024 08:37
Copy link
Member

@tboba tboba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides two comments, everything LGTM!


PODFILE CHECKSUM: 67b3d295da87c29349179e51bb3526b67059b646

COCOAPODS: 1.15.2
COCOAPODS: 1.14.3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we shouldn't introduce some policy of using chosen version of cocoapods to omit this sort of changes from Podfiles 🤔 what do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, would be nice to just hardcode it so it does not land in many PRs by mistake

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This depends on cocoapods version you have installed locally I believe. I don't think we should include this in your PR, especially that it's a lower version, and there should be no need to downgrade.

Comment on lines 6 to 8
if(${IS_NEW_ARCHITECTURE_ENABLED})
string(APPEND CMAKE_CXX_FLAGS " -DRCT_NEW_ARCH_ENABLED")
endif()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we setting this? Is that because of some unmerged changes earlier for matching RN 0.74?

facebook-github-bot pushed a commit to facebook/react-native that referenced this pull request Jun 14, 2024
Summary:
PR changing the single mountingOverrideDelegate to a vector of those, so other listeners can operate on the transaction. Used by `react-native-screens` in software-mansion/react-native-screens#2134 and `react-native-reanimated` in software-mansion/react-native-reanimated#6055.

Till now, only one listener could be added there, meaning that e.g. `Layout Animations` from `react-native`,  `Layout Animations` from `react-native-reanimated` and listening for `Screen` removal in `react-native-screens` could not operate at the same time.

## Changelog:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[GENERAL] [FIXED] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[GENERAL] [FIXED] - Add option for multiple `mountingOverrideDelegates`

Pull Request resolved: #44927

Test Plan: The code of `LayoutAnimations` inside `react-native` should work the same since it will add just one listener then. For other cases, different libraries can read/mutate transactions.

Reviewed By: javache

Differential Revision: D58530278

Pulled By: sammy-SC

fbshipit-source-id: d6305963621000be11d51a50cffff64526cca934
Copy link
Member

@kkafar kkafar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I think I have solid grasp of the changes. These looks solid 💪🏻 I still have some questions and change requests I've posted in the review.

Note I think we should vastly improve the PR description, with more information on the solution architecture, especially initialisation phase of the C++ layer and some simple flow of the solution.

@@ -68,7 +68,7 @@ GEM
i18n (1.14.5)
concurrent-ruby (~> 1.0)
json (2.7.2)
minitest (5.22.3)
minitest (5.22.2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's going on here, why do we change that?


PODFILE CHECKSUM: 67b3d295da87c29349179e51bb3526b67059b646

COCOAPODS: 1.15.2
COCOAPODS: 1.14.3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This depends on cocoapods version you have installed locally I believe. I don't think we should include this in your PR, especially that it's a lower version, and there should be no need to downgrade.

@@ -4,7 +4,7 @@ package = JSON.parse(File.read(File.join(__dir__, "package.json")))

new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
platform = new_arch_enabled ? "11.0" : "9.0"
source_files = new_arch_enabled ? 'ios/**/*.{h,m,mm,cpp}' : ["ios/**/*.{h,m,mm}", "cpp/**/*.{cpp,h}"]
source_files = new_arch_enabled ? 'ios/**/*.{h,m,mm,cpp}' : ["ios/**/*.{h,m,mm}", "cpp/RNScreensTurboModule.cpp", "cpp/RNScreensTurboModule.h"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's worth to consider grouping the files. E.g. place screen-transition related staff in a cpp/transition / cpp/turbomodule directory & new screen listeners into cpp/mounting or sth similar.

Comment on lines 15 to 19
set_target_properties(rnscreens PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
POSITION_INDEPENDENT_CODE ON
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we replace setting properties on particular target with setting the non-target-scoped variable CMAKE_CXX_STANDARD? Also why do we drop standard requirement, turning off extensions and enabling -fpic explicitly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why I removed it, reverted.

target_compile_definitions(
rnscreens
PRIVATE
-DFOLLY_NO_CONFIG=1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
-DFOLLY_NO_CONFIG=1
FOLLY_NO_CONFIG=1

I believe you can omit -D prefix in target_compile_definition (link)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't work if I remove it so I think it is not true.

Copy link
Member

@kkafar kkafar Jul 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not gonna lie, I'm surprised as CMake docs I've linked explicitly state that you can omit -D, but that's a nitpick anyway, we can proceed w/o this change.

struct RNSScreenRemovalListener : public MountingOverrideDelegate {
std::function<void(int)> listenerFunction_;
RNSScreenRemovalListener(std::function<void(int)> &&listenerFunction_)
: listenerFunction_(listenerFunction_) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haven't opened the code, writing from memory: don't we need here to have std::move / std::forward called to invoke move constructor of std::function?

Suggested change
: listenerFunction_(listenerFunction_) {}
: listenerFunction_(std::move(listenerFunction_)) {}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works so I guess we don't?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not matter of whether it works, it is matter of how it works.

See this snippet for the explanation.

Heres the example code in case the snippet times out (you can paste it to an online C++ compiler, e.g. this one

Code snippet

#include <iostream>

class SomeClass {
    int someField;

public:
    // DefaultConstructor
    SomeClass() : someField{0} {
        std::cout << "DefaultConstructor invoked\n";
    }

    // CopyConstructor
    SomeClass(const SomeClass& other) {
        std::cout << "CopyConstructor invoked\n";
        this->someField = other.someField;
    }

    // MoveConstructor
    SomeClass(SomeClass&& other) {
        std::cout << "MoveConstructor invoked\n";
        this->someField = std::move(other.someField);
    }
};

class CompositeClass {
    SomeClass someClassInstance;
public:
    CompositeClass(SomeClass&& other) : someClassInstance(other) {}

    // LOOK HERE!!!!
    // Comment the line above and uncomment the line below to see the difference.
    // CompositeClass(SomeClass&& other) : someClassInstance(std::move(other)) {}
};

// Type your code here, or load an example.
int main() {
    SomeClass someClassInstance{};
    CompositeClass ccInstance(std::move(someClassInstance));
    return 0;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And now, after testing I'm sure we shall have std::move there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what is the difference exactly and how it affects the code I added?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I provided a code snippet with mimicking the setup you have here in this PR & appended comments explaining.
The difference comes down to which constructor is being called & that's why I've put "couts" in ctors of SomeClass class.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically right now you take rvalue reference as RNSScreenRemovalListener constructor parameter just to later pass it by value to initialiser of listenerFunction_ field effectively copying it (invoking copy constructor). By moving there, as I've suggested, you invoke move constructor of std::function forwarding the parameter value correctly w/o unnecessary copy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

ShadowViewMutationList mutations) const {
for (ShadowViewMutation mutation : mutations) {
if (mutation.type == ShadowViewMutation::Type::Remove &&
mutation.oldChildShadowView.componentName != nullptr &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we check oldChildShadowView.componentName for nullish value while later using parentShadowView.componentName for comparison purposes?

Also, are we looking for RNSScreenStack here, cause we want this fix to work only for native-stack? Isn't this (whiteflash) also an issue with ScreenContainer? (I don't have knowledge here, just wondering reading through the code).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we check oldChildShadowView.componentName for nullish value while later using parentShadowView.componentName for comparison purposes?

Because we use oldChildShadowView.componentName in listenerFunction_.

Also, are we looking for RNSScreenStack here, cause we want this fix to work only for native-stack? Isn't this (whiteflash) also an issue with ScreenContainer? (I don't have knowledge here, just wondering reading through the code).

Indeed it is only needed in RNSScreenStack since other navigators do not remove the view until the animation is completed.

ios/RNSModule.mm Outdated Show resolved Hide resolved
ios/RNSModule.mm Outdated Show resolved Hide resolved
ios/RNSModule.mm Outdated Show resolved Hide resolved
@chrispader chrispader mentioned this pull request Sep 2, 2024
8 tasks
ja1ns pushed a commit to WiseOwlTech/react-native-screens that referenced this pull request Oct 9, 2024
…oftware-mansion#2261)

## Description

> [!note] 
This PR applies to iOS only. 

Ok, so this PR is related to software-mansion#2247 & to get broader context I highly
recommend to read [this
comment](software-mansion#2247 (review))
at the very minimum.

### Issue context

On Fabric during JS initialised Screen dismissal (view removing in
general) children are unmounted before their parents, thus when
dismissing screen from screen stack & starting a dismiss transition all
Screen content is already removed & we're animating only a blank screen
resulting in visual glitch.

### Current approaches

Right now we're utilising `RCTMountingTransactionObserving` protocol,
filter all mounting operations *before* they are applied and if screen
dismissal is to be done, we take a snapshot of to-be-removed-screen.

### Alternative approaches

software-mansion#2134 sets mounting coordinator delegate and effectively does the same
as the current approach, however it can also be applied to Android.

### Proposed approach

On iOS we can utilise the platform & how it works. Namely the fact of
unmounting child view does not impact the hardware buffer, nor bitmap
layer immediately, thus we can take the snapshot simply in `- [RNSScreen
unmountChildComponentView: index:]` and the children will still be
visible.

This approach is safe and reliable, because:

##### 10k feet explanation

Drawing is not performed immediately after an update to UIKit model
(such as removing a view), the system handles all operations pending on
main queue and just after that it schedules drawing. We're removing the
views & making snapshot in the middle of block execution on the main
thread, thus the drawing can't happen and just-unmounted-views will be
visible on the snapshot.

##### More detailed explanation

1. the main thread run loop of Cocoa application drains the main queue
till it's empty
[[1]](https://opensource.apple.com/source/CF/CF-1153.18/CFRunLoop.c.auto.html)
[[2]](https://fabernovel.github.io/2021-01-04/uikit-rendering-part-3)
[[5]](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1)
2. CoreAnimation framework integrates with the main run loop by
registering an observer and listening for `kCFRunLoopBeforeWaiting`
event (so after the main queue is drained & run loop is to become idle
due to no more pending tasks).
[[2]](https://fabernovel.github.io/2021-01-04/uikit-rendering-part-3)
3. CoreAnimation is responsible for applying all transactions from the
last loop pass & sending them to render server (this happens on main
thread), which in turn finally leads up to the changes being applied,
drawn & displayed (this happens on different threads).
[[2]](https://fabernovel.github.io/2021-01-04/uikit-rendering-part-3)
[[3]](https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/WindowsandViews/WindowsandViews.html#//apple_ref/doc/uid/TP40009503-CH2-SW1)
4. [We know that the RN's mounting stage will be executed on main
thread](https://github.com/facebook/react-native/blob/91ecd7eb5330f5d725f5587744713064d614a6b3/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm#L258),
because UIKit is thread-safe only in selected parts and requires calling
from the main thread.
5. Single RN transaction is a complete diff between ["rendered tree" &
"next
tree"](https://reactnative.dev/architecture/render-pipeline#phase-2-commit-1)
and is performed [atomically &
synchronously](https://github.com/facebook/react-native/blob/91ecd7eb5330f5d725f5587744713064d614a6b3/packages/react-native/ReactCommon/react/renderer/mounting/TelemetryController.cpp#L18-L51)
on [main
thread](https://github.com/facebook/react-native/blob/91ecd7eb5330f5d725f5587744713064d614a6b3/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm#L258),
thus whole batch of updates will be finished before drawing instructions
will be send to render server.

#### Reference:


[[1]](https://opensource.apple.com/source/CF/CF-1153.18/CFRunLoop.c.auto.html)
(Look for `__CFRunLoopDoBlocks(...)` & `__CFRunLoopRun(...)` functions)

Important thing to notice if `__CFRunLoopDoBlocks` is that it locks the
`rl` (run loop) lock, takes & copies reference to the list of the blocks
to execute, clears the original list of blocks and releases the `rl`
lock. Thus only the "already scheduled" blocks are executed in the
single pass of this function. It is called multiple times in the single
pass of the run loop, but I haven't dug deeper, it should be enough for
our use case that we have guarantee that all the blocks are drained.

[[2]](https://fabernovel.github.io/2021-01-04/uikit-rendering-part-3)
(Blog post on rendering in UIKit)


[[3]](https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/WindowsandViews/WindowsandViews.html#//apple_ref/doc/uid/TP40009503-CH2-SW1)
(Apple docs - The View Drawing Cycle section)

[[4]](https://bou.io/RunRunLoopRun.html) (Blog post on the run loop)


[[5]](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1)
(Apple docs on run loops)

## Changes

* Snapshot is not done in `unmountChildComponentView: index:` & only
when needed.
* Removed old mechanism
* Removed now unused implementation of `RCTMountingObserving` protocol

## Test code and steps to reproduce

Run any example on Fabric, push a screen, initiate go-back via JS (e.g.
by clicking a button with `navigation.goBack()` action), see that the
screen transitions correctly (the content is visible throughout
transition)

## Checklist

- [x] Ensured that CI passes
ja1ns pushed a commit to WiseOwlTech/react-native-screens that referenced this pull request Oct 9, 2024
…2134)

PR adding snapshots when going back on Fabric on Android and changing the behavior a bit on iOS.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants