Implement UI consistency mechanism for JS thread (#43581)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/43581

Changelog: [internal]

This implements a mechanism to ensure that JavaScript tasks have a consistent view of the state of the UI during their execution.

## Context

Fabric allows committing new revisions of the ShadowTree from any thread, but we don't make use of this capability and instead always commit them from the JS thread (e.g.: when we schedule Fabric state updates to update the offset of a list on scroll). This was done to make sure that JS work didn't see changes in the state of the tree at random points during its execution. E.g.:

```
useEffect(() => {
  const rect = ref.current.getBoundingClientRect();
  // do something
  const newRect = ref.current.getBoundingClientRect();
  // `rect` and `newRect` should always be the same
}, []);
```

This isn't used by Reanimated at the moment, which means JS can inadvertently see the result of animations in non-specific times during execution.

You can find additional context about this in the [RFC for DOM Traversal & Layout APIs in RN](https://github.com/react-native-community/discussions-and-proposals/blob/main/proposals/0607-dom-traversal-and-layout-apis.md#consistency-and-updates).

This works correctly at the moment, but we introduce a limitation in the execution model to prevent updating the tree synchronously from the main thread. One of the main problems this introduces is that computing intersections (for `IntersectionObserver`) relies on the information in the shadow tree, but this is updated asynchronously on scroll.

There are 2 potential solutions for that problem:
1) Send the timestamp of the scroll even with the state update to backdate the timestamps of the intersections. This could work but introduces more complexity and possibly accuracy problems due to batching those state updates with other changes (e.g.: what happens if we update the state and commit another tree in the same task? should we use the backdated timestamp or wait for mount?).
2) (**Preferred**/ this diff) Allow committing new revisions from any thread, but lock the JS thread into seeing a specific revision, which would only update/progress in specific moments when it's safe. Some of those moments would be:
    1) When we start a new JS task.
    2) When we commit a new tree from React (JS).

## Changes

This implements the solution outlined in 2), creating a few abstractions to handle what's the current tree that should be visible to JS and to lock/unlock it in specific moments.

More specifically:
* Creates `ShadowTreeRevisionProvider` as an abstract class for APIs consuming the visible revision of the ShadowTree (mainly DOM APIs and layout methods like `measure`, etc.).
* Creates `ShadowTreeRevisionConsistencyManager` as an abstract class to handle what trees are visible (with a `lockRevision` and `unlockRevision` to be called from `RuntimeScheduler` at the beginning and end of each JS task).
* Creates 2 different implementations of these abstractions:
  * One that preserves the current behavior (`LatestShadowTreeRevisionProvider`, which just returns the last committed revision at the time of the call).
  * One that locks revisions lazily (the first time they're accessed) (`LazyShadowTreeRevisionConsistencyManager`).

Reviewed By: sammy-SC

Differential Revision: D55024832

fbshipit-source-id: b59985bc83714ae7ec915baba72bf92b3d6fa140
This commit is contained in:
Rubén Norte
2024-03-25 09:50:21 -07:00
committed by Facebook GitHub Bot
parent 1c8751a16a
commit 332355e80f
48 changed files with 689 additions and 54 deletions
@@ -87,6 +87,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-debug")
add_dependency(s, "React-utils")
add_dependency(s, "React-rendererdebug")
add_dependency(s, "React-rendererconsistency")
add_dependency(s, "React-runtimescheduler")
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
@@ -563,6 +563,7 @@ android {
"react_cxxreactpackage",
"react_render_animations",
"react_render_core",
"react_render_consistency",
"react_render_graphics",
"rrc_image",
"rrc_root",
@@ -580,6 +581,7 @@ android {
"react_nativemodule_core",
"react_render_imagemanager",
"react_render_uimanager",
"react_render_uimanager_consistency",
"react_render_scheduler",
"react_render_mounting",
"hermes_executor",
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<bdd9f28c6de15e64ce03c505a3f4f34b>>
* @generated SignedSource<<20c445fde7a1c2607b58b7797346431f>>
*/
/**
@@ -76,6 +76,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableSpannableBuildingUnification(): Boolean = accessor.enableSpannableBuildingUnification()
/**
* Ensures that JavaScript always has a consistent view of the state of the UI (e.g.: commits done in other threads are not immediately propagated to JS during its execution).
*/
@JvmStatic
public fun enableUIConsistency(): Boolean = accessor.enableUIConsistency()
/**
* Flag determining if the C++ implementation of InspectorPackagerConnection should be used instead of the per-platform one. This flag is global and should not be changed across React Host lifetimes.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<7ae379135157666d9646f1d8eeec9989>>
* @generated SignedSource<<6e973dcdcdfae14c77a6130207333551>>
*/
/**
@@ -28,6 +28,7 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
private var enableMicrotasksCache: Boolean? = null
private var enableMountHooksAndroidCache: Boolean? = null
private var enableSpannableBuildingUnificationCache: Boolean? = null
private var enableUIConsistencyCache: Boolean? = null
private var inspectorEnableCxxInspectorPackagerConnectionCache: Boolean? = null
private var inspectorEnableModernCDPRegistryCache: Boolean? = null
private var useModernRuntimeSchedulerCache: Boolean? = null
@@ -104,6 +105,15 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
return cached
}
override fun enableUIConsistency(): Boolean {
var cached = enableUIConsistencyCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableUIConsistency()
enableUIConsistencyCache = cached
}
return cached
}
override fun inspectorEnableCxxInspectorPackagerConnection(): Boolean {
var cached = inspectorEnableCxxInspectorPackagerConnectionCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<244a0656beee8e018585bdd4bb4e5cd1>>
* @generated SignedSource<<0d34567fc05555ae401e7f5180dc8771>>
*/
/**
@@ -44,6 +44,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun enableSpannableBuildingUnification(): Boolean
@DoNotStrip @JvmStatic public external fun enableUIConsistency(): Boolean
@DoNotStrip @JvmStatic public external fun inspectorEnableCxxInspectorPackagerConnection(): Boolean
@DoNotStrip @JvmStatic public external fun inspectorEnableModernCDPRegistry(): Boolean
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<d0985b19d61af8bdf47c322b7a59e203>>
* @generated SignedSource<<cfb3e4f5d83a939f4b034bc62762837e>>
*/
/**
@@ -39,6 +39,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableSpannableBuildingUnification(): Boolean = false
override fun enableUIConsistency(): Boolean = false
override fun inspectorEnableCxxInspectorPackagerConnection(): Boolean = false
override fun inspectorEnableModernCDPRegistry(): Boolean = false
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<919eb0f27540e5dd7a1e028663c23264>>
* @generated SignedSource<<8908de9b9d0186f1916d8b55d5854cb2>>
*/
/**
@@ -32,6 +32,7 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
private var enableMicrotasksCache: Boolean? = null
private var enableMountHooksAndroidCache: Boolean? = null
private var enableSpannableBuildingUnificationCache: Boolean? = null
private var enableUIConsistencyCache: Boolean? = null
private var inspectorEnableCxxInspectorPackagerConnectionCache: Boolean? = null
private var inspectorEnableModernCDPRegistryCache: Boolean? = null
private var useModernRuntimeSchedulerCache: Boolean? = null
@@ -116,6 +117,16 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableUIConsistency(): Boolean {
var cached = enableUIConsistencyCache
if (cached == null) {
cached = currentProvider.enableUIConsistency()
accessedFeatureFlags.add("enableUIConsistency")
enableUIConsistencyCache = cached
}
return cached
}
override fun inspectorEnableCxxInspectorPackagerConnection(): Boolean {
var cached = inspectorEnableCxxInspectorPackagerConnectionCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<ddbe652a9094bd6af4bdb741fc17ea7c>>
* @generated SignedSource<<7555a704535615fcea44c1261095419a>>
*/
/**
@@ -39,6 +39,8 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun enableSpannableBuildingUnification(): Boolean
@DoNotStrip public fun enableUIConsistency(): Boolean
@DoNotStrip public fun inspectorEnableCxxInspectorPackagerConnection(): Boolean
@DoNotStrip public fun inspectorEnableModernCDPRegistry(): Boolean
@@ -79,6 +79,8 @@ add_react_common_subdir(react/renderer/scheduler)
add_react_common_subdir(react/renderer/telemetry)
add_react_common_subdir(react/renderer/uimanager)
add_react_common_subdir(react/renderer/core)
add_react_common_subdir(react/renderer/consistency)
add_react_common_subdir(react/renderer/uimanager/consistency)
add_react_common_subdir(react/renderer/element)
add_react_common_subdir(react/renderer/graphics)
add_react_common_subdir(react/renderer/debug)
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<393c6cf93399cfe0b0533927877531d5>>
* @generated SignedSource<<e6f57d186226377e4558f633433aa1fc>>
*/
/**
@@ -87,6 +87,12 @@ class ReactNativeFeatureFlagsProviderHolder
return method(javaProvider_);
}
bool enableUIConsistency() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableUIConsistency");
return method(javaProvider_);
}
bool inspectorEnableCxxInspectorPackagerConnection() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("inspectorEnableCxxInspectorPackagerConnection");
@@ -149,6 +155,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableSpannableBuildingUnification(
return ReactNativeFeatureFlags::enableSpannableBuildingUnification();
}
bool JReactNativeFeatureFlagsCxxInterop::enableUIConsistency(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableUIConsistency();
}
bool JReactNativeFeatureFlagsCxxInterop::inspectorEnableCxxInspectorPackagerConnection(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection();
@@ -205,6 +216,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"enableSpannableBuildingUnification",
JReactNativeFeatureFlagsCxxInterop::enableSpannableBuildingUnification),
makeNativeMethod(
"enableUIConsistency",
JReactNativeFeatureFlagsCxxInterop::enableUIConsistency),
makeNativeMethod(
"inspectorEnableCxxInspectorPackagerConnection",
JReactNativeFeatureFlagsCxxInterop::inspectorEnableCxxInspectorPackagerConnection),
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<e0466f307c37b0c46a09d3a792060fcd>>
* @generated SignedSource<<a90ff7ce4734046fd9aab1c501ca22b4>>
*/
/**
@@ -54,6 +54,9 @@ class JReactNativeFeatureFlagsCxxInterop
static bool enableSpannableBuildingUnification(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableUIConsistency(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool inspectorEnableCxxInspectorPackagerConnection(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -213,7 +213,7 @@ Pod::Spec.new do |s|
sss.header_dir = "react/renderer/components/textinput"
end
ss.subspec "unimplementedview" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
@@ -277,10 +277,17 @@ Pod::Spec.new do |s|
end
s.subspec "uimanager" do |ss|
ss.subspec "consistency" do |sss|
sss.dependency folly_dep_name, folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "react/renderer/uimanager/consistency/*.{m,mm,cpp,h}"
sss.header_dir = "react/renderer/uimanager/consistency"
end
ss.dependency folly_dep_name, folly_version
ss.dependency "React-rendererconsistency"
ss.compiler_flags = folly_compiler_flags
ss.source_files = "react/renderer/uimanager/**/*.{m,mm,cpp,h}"
ss.exclude_files = "react/renderer/uimanager/tests"
ss.source_files = "react/renderer/uimanager/*.{m,mm,cpp,h}"
ss.header_dir = "react/renderer/uimanager"
end
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<cc7c7aa6ac71f94f8dd8fc8cc0c18308>>
* @generated SignedSource<<a5c58cc8c0294fd073f43fda88b908f8>>
*/
/**
@@ -53,6 +53,10 @@ bool ReactNativeFeatureFlags::enableSpannableBuildingUnification() {
return getAccessor().enableSpannableBuildingUnification();
}
bool ReactNativeFeatureFlags::enableUIConsistency() {
return getAccessor().enableUIConsistency();
}
bool ReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection() {
return getAccessor().inspectorEnableCxxInspectorPackagerConnection();
}
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<d104b9219b7658544101bbd175f8fa7d>>
* @generated SignedSource<<e94d55bad5f8bf6cf933ddc50e3b4886>>
*/
/**
@@ -77,6 +77,11 @@ class ReactNativeFeatureFlags {
*/
RN_EXPORT static bool enableSpannableBuildingUnification();
/**
* Ensures that JavaScript always has a consistent view of the state of the UI (e.g.: commits done in other threads are not immediately propagated to JS during its execution).
*/
RN_EXPORT static bool enableUIConsistency();
/**
* Flag determining if the C++ implementation of InspectorPackagerConnection should be used instead of the per-platform one. This flag is global and should not be changed across React Host lifetimes.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<f7d54fe8d458d80359a8d6e0e5816b1b>>
* @generated SignedSource<<2ff39fd4c8330ddca994fc40cdeaaf4c>>
*/
/**
@@ -173,6 +173,24 @@ bool ReactNativeFeatureFlagsAccessor::enableSpannableBuildingUnification() {
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::enableUIConsistency() {
auto flagValue = enableUIConsistency_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(8, "enableUIConsistency");
flagValue = currentProvider_->enableUIConsistency();
enableUIConsistency_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::inspectorEnableCxxInspectorPackagerConnection() {
auto flagValue = inspectorEnableCxxInspectorPackagerConnection_.load();
@@ -182,7 +200,7 @@ bool ReactNativeFeatureFlagsAccessor::inspectorEnableCxxInspectorPackagerConnect
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(8, "inspectorEnableCxxInspectorPackagerConnection");
markFlagAsAccessed(9, "inspectorEnableCxxInspectorPackagerConnection");
flagValue = currentProvider_->inspectorEnableCxxInspectorPackagerConnection();
inspectorEnableCxxInspectorPackagerConnection_ = flagValue;
@@ -200,7 +218,7 @@ bool ReactNativeFeatureFlagsAccessor::inspectorEnableModernCDPRegistry() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(9, "inspectorEnableModernCDPRegistry");
markFlagAsAccessed(10, "inspectorEnableModernCDPRegistry");
flagValue = currentProvider_->inspectorEnableModernCDPRegistry();
inspectorEnableModernCDPRegistry_ = flagValue;
@@ -218,7 +236,7 @@ bool ReactNativeFeatureFlagsAccessor::useModernRuntimeScheduler() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(10, "useModernRuntimeScheduler");
markFlagAsAccessed(11, "useModernRuntimeScheduler");
flagValue = currentProvider_->useModernRuntimeScheduler();
useModernRuntimeScheduler_ = flagValue;
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<d13ffc557c3874e9bdd78558cd309829>>
* @generated SignedSource<<a8e0d8c5bc041f814bffb1701a124646>>
*/
/**
@@ -39,6 +39,7 @@ class ReactNativeFeatureFlagsAccessor {
bool enableMicrotasks();
bool enableMountHooksAndroid();
bool enableSpannableBuildingUnification();
bool enableUIConsistency();
bool inspectorEnableCxxInspectorPackagerConnection();
bool inspectorEnableModernCDPRegistry();
bool useModernRuntimeScheduler();
@@ -52,7 +53,7 @@ class ReactNativeFeatureFlagsAccessor {
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
bool wasOverridden_;
std::array<std::atomic<const char*>, 11> accessedFeatureFlags_;
std::array<std::atomic<const char*>, 12> accessedFeatureFlags_;
std::atomic<std::optional<bool>> commonTestFlag_;
std::atomic<std::optional<bool>> batchRenderingUpdatesInEventLoop_;
@@ -62,6 +63,7 @@ class ReactNativeFeatureFlagsAccessor {
std::atomic<std::optional<bool>> enableMicrotasks_;
std::atomic<std::optional<bool>> enableMountHooksAndroid_;
std::atomic<std::optional<bool>> enableSpannableBuildingUnification_;
std::atomic<std::optional<bool>> enableUIConsistency_;
std::atomic<std::optional<bool>> inspectorEnableCxxInspectorPackagerConnection_;
std::atomic<std::optional<bool>> inspectorEnableModernCDPRegistry_;
std::atomic<std::optional<bool>> useModernRuntimeScheduler_;
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<62de1b0e27590ad769296358a4f42c7a>>
* @generated SignedSource<<1450d89abc68821fb348574016874719>>
*/
/**
@@ -59,6 +59,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
return false;
}
bool enableUIConsistency() override {
return false;
}
bool inspectorEnableCxxInspectorPackagerConnection() override {
return false;
}
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<3117fc0389416297369a47ee480eb906>>
* @generated SignedSource<<d369db4d7cd374081941bff6c6f3e08f>>
*/
/**
@@ -33,6 +33,7 @@ class ReactNativeFeatureFlagsProvider {
virtual bool enableMicrotasks() = 0;
virtual bool enableMountHooksAndroid() = 0;
virtual bool enableSpannableBuildingUnification() = 0;
virtual bool enableUIConsistency() = 0;
virtual bool inspectorEnableCxxInspectorPackagerConnection() = 0;
virtual bool inspectorEnableModernCDPRegistry() = 0;
virtual bool useModernRuntimeScheduler() = 0;
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<27923a2dbf1dcbad238a4d06ebb54fb5>>
* @generated SignedSource<<7e09a7ad1d178850bdcf73da3eb5623b>>
*/
/**
@@ -77,6 +77,11 @@ bool NativeReactNativeFeatureFlags::enableSpannableBuildingUnification(
return ReactNativeFeatureFlags::enableSpannableBuildingUnification();
}
bool NativeReactNativeFeatureFlags::enableUIConsistency(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableUIConsistency();
}
bool NativeReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection();
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<53251a11631eccf1fcc334b14f9ca4c6>>
* @generated SignedSource<<a356c9a406454c7591fbfe23edeeaee6>>
*/
/**
@@ -51,6 +51,8 @@ class NativeReactNativeFeatureFlags
bool enableSpannableBuildingUnification(jsi::Runtime& runtime);
bool enableUIConsistency(jsi::Runtime& runtime);
bool inspectorEnableCxxInspectorPackagerConnection(jsi::Runtime& runtime);
bool inspectorEnableModernCDPRegistry(jsi::Runtime& runtime);
@@ -0,0 +1,20 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB react_render_consistency_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_render_consistency SHARED ${react_render_consistency_SRC})
target_include_directories(react_render_consistency PUBLIC ${REACT_COMMON_DIR})
@@ -0,0 +1,45 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
header_search_paths = []
if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the rendererconsistency access its own files
end
Pod::Spec.new do |s|
s.name = "React-rendererconsistency"
s.version = version
s.summary = "Fabric UI consistency primitives"
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = "**/*.{cpp,h}"
s.header_dir = "react/renderer/consistency"
s.exclude_files = "tests"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
"HEADER_SEARCH_PATHS" => header_search_paths.join(' ')}
if ENV['USE_FRAMEWORKS']
s.module_name = "React_rendererconsistency"
s.header_mappings_dir = "../../.."
end
end
@@ -0,0 +1,54 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h>
namespace facebook::react {
/**
* This is a RAII class that locks the shadow tree revisions during its
* lifetime.
*
* @example
* {
* ScopedShadowTreeRevisionLock lock(consistencyManager);
* runJavaScriptTask(); // During this execution, the lock will be active.
* }
*/
class ScopedShadowTreeRevisionLock {
public:
explicit ScopedShadowTreeRevisionLock(
ShadowTreeRevisionConsistencyManager* consistencyManager) noexcept
: consistencyManager_(consistencyManager) {
if (consistencyManager_ != nullptr) {
consistencyManager_->lockRevisions();
}
}
// Non-movable
ScopedShadowTreeRevisionLock(const ScopedShadowTreeRevisionLock&) = delete;
ScopedShadowTreeRevisionLock(ScopedShadowTreeRevisionLock&&) = delete;
// Non-copyable
ScopedShadowTreeRevisionLock& operator=(const ScopedShadowTreeRevisionLock&) =
delete;
ScopedShadowTreeRevisionLock& operator=(ScopedShadowTreeRevisionLock&&) =
delete;
~ScopedShadowTreeRevisionLock() noexcept {
if (consistencyManager_ != nullptr) {
consistencyManager_->unlockRevisions();
}
}
private:
ShadowTreeRevisionConsistencyManager* consistencyManager_;
};
} // namespace facebook::react
@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ShadowTreeRevisionConsistencyManager.h"
namespace facebook::react {
// This is a placeholder for CMakeFile not to complain
} // namespace facebook::react
@@ -0,0 +1,24 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
namespace facebook::react {
/**
* This interface is used for UI consistency, indicating the timeframe where
* users should see the same revision of the shadow tree.
*/
class ShadowTreeRevisionConsistencyManager {
public:
virtual ~ShadowTreeRevisionConsistencyManager() = default;
virtual void lockRevisions() = 0;
virtual void unlockRevisions() = 0;
};
} // namespace facebook::react
@@ -23,6 +23,7 @@ target_link_libraries(react_render_runtimescheduler
callinvoker
jsi
react_debug
react_render_consistency
react_render_debug
react_utils
react_featureflags
@@ -60,6 +60,7 @@ Pod::Spec.new do |s|
s.dependency "glog"
s.dependency "RCT-Folly", folly_version
s.dependency "React-jsi"
s.dependency "React-rendererconsistency"
add_dependency(s, "React-debug")
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
@@ -84,4 +84,11 @@ void RuntimeScheduler::scheduleRenderingUpdate(
std::move(renderingUpdate));
}
void RuntimeScheduler::setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager*
shadowTreeRevisionConsistencyManager) {
return runtimeSchedulerImpl_->setShadowTreeRevisionConsistencyManager(
shadowTreeRevisionConsistencyManager);
}
} // namespace facebook::react
@@ -8,6 +8,7 @@
#pragma once
#include <ReactCommon/RuntimeExecutor.h>
#include <react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerClock.h>
#include <react/renderer/runtimescheduler/Task.h>
@@ -35,6 +36,8 @@ class RuntimeSchedulerBase {
virtual void callExpiredTasks(jsi::Runtime& runtime) = 0;
virtual void scheduleRenderingUpdate(
RuntimeSchedulerRenderingUpdate&& renderingUpdate) = 0;
virtual void setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager* provider) = 0;
};
// This is a proxy for RuntimeScheduler implementation, which will be selected
@@ -127,6 +130,10 @@ class RuntimeScheduler final : RuntimeSchedulerBase {
void scheduleRenderingUpdate(
RuntimeSchedulerRenderingUpdate&& renderingUpdate) override;
void setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager*
shadowTreeRevisionConsistencyManager) override;
private:
// Actual implementation, stored as a unique pointer to simplify memory
// management.
@@ -8,6 +8,7 @@
#include "RuntimeScheduler_Legacy.h"
#include "SchedulerPriorityUtils.h"
#include <react/renderer/consistency/ScopedShadowTreeRevisionLock.h>
#include <react/renderer/debug/SystraceSection.h>
#include <utility>
#include "ErrorUtils.h"
@@ -30,7 +31,11 @@ void RuntimeScheduler_Legacy::scheduleWork(RawCallback&& callback) noexcept {
[this, callback = std::move(callback)](jsi::Runtime& runtime) {
SystraceSection s2("RuntimeScheduler::scheduleWork callback");
runtimeAccessRequests_ -= 1;
callback(runtime);
{
ScopedShadowTreeRevisionLock revisionLock(
shadowTreeRevisionConsistencyManager_);
callback(runtime);
}
startWorkLoop(runtime);
});
}
@@ -104,7 +109,11 @@ void RuntimeScheduler_Legacy::executeNowOnTheSameThread(
"RuntimeScheduler::executeNowOnTheSameThread callback");
runtimeAccessRequests_ -= 1;
callback(runtime);
{
ScopedShadowTreeRevisionLock revisionLock(
shadowTreeRevisionConsistencyManager_);
callback(runtime);
}
});
// Resume work loop if needed. In synchronous mode
@@ -145,6 +154,12 @@ void RuntimeScheduler_Legacy::scheduleRenderingUpdate(
}
}
void RuntimeScheduler_Legacy::setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager*
shadowTreeRevisionConsistencyManager) {
shadowTreeRevisionConsistencyManager_ = shadowTreeRevisionConsistencyManager;
}
#pragma mark - Private
void RuntimeScheduler_Legacy::scheduleWorkLoopIfNecessary() {
@@ -195,13 +210,19 @@ void RuntimeScheduler_Legacy::executeTask(
didUserCallbackTimeout);
currentPriority_ = task->priority;
auto result = task->execute(runtime, didUserCallbackTimeout);
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
task->callback = result.getObject(runtime).getFunction(runtime);
} else {
if (taskQueue_.top() == task) {
taskQueue_.pop();
{
ScopedShadowTreeRevisionLock revisionLock(
shadowTreeRevisionConsistencyManager_);
auto result = task->execute(runtime, didUserCallbackTimeout);
if (result.isObject() && result.getObject(runtime).isFunction(runtime)) {
task->callback = result.getObject(runtime).getFunction(runtime);
} else {
if (taskQueue_.top() == task) {
taskQueue_.pop();
}
}
}
}
@@ -8,6 +8,7 @@
#pragma once
#include <ReactCommon/RuntimeExecutor.h>
#include <react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerClock.h>
#include <react/renderer/runtimescheduler/Task.h>
@@ -105,6 +106,10 @@ class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase {
void scheduleRenderingUpdate(
RuntimeSchedulerRenderingUpdate&& renderingUpdate) override;
void setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager*
shadowTreeRevisionConsistencyManager) override;
private:
std::priority_queue<
std::shared_ptr<Task>,
@@ -151,6 +156,9 @@ class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase {
* This flag is set while performing work, to prevent re-entrancy.
*/
std::atomic_bool isPerformingWork_{false};
ShadowTreeRevisionConsistencyManager* shadowTreeRevisionConsistencyManager_{
nullptr};
};
} // namespace facebook::react
@@ -10,6 +10,7 @@
#include <cxxreact/ErrorUtils.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/consistency/ScopedShadowTreeRevisionLock.h>
#include <react/renderer/debug/SystraceSection.h>
#include <react/utils/OnScopeExit.h>
#include <utility>
@@ -153,6 +154,12 @@ void RuntimeScheduler_Modern::scheduleRenderingUpdate(
}
}
void RuntimeScheduler_Modern::setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager*
shadowTreeRevisionConsistencyManager) {
shadowTreeRevisionConsistencyManager_ = shadowTreeRevisionConsistencyManager;
}
#pragma mark - Private
void RuntimeScheduler_Modern::scheduleTask(std::shared_ptr<Task> task) {
@@ -253,16 +260,21 @@ void RuntimeScheduler_Modern::executeTask(
currentTask_ = task;
currentPriority_ = task->priority;
executeMacrotask(runtime, task, didUserCallbackTimeout);
{
ScopedShadowTreeRevisionLock revisionLock(
shadowTreeRevisionConsistencyManager_);
if (ReactNativeFeatureFlags::enableMicrotasks()) {
// "Perform a microtask checkpoint" step.
performMicrotaskCheckpoint(runtime);
}
executeMacrotask(runtime, task, didUserCallbackTimeout);
if (ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop()) {
// "Update the rendering" step.
updateRendering();
if (ReactNativeFeatureFlags::enableMicrotasks()) {
// "Perform a microtask checkpoint" step.
performMicrotaskCheckpoint(runtime);
}
if (ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop()) {
// "Update the rendering" step.
updateRendering();
}
}
}
@@ -8,6 +8,7 @@
#pragma once
#include <ReactCommon/RuntimeExecutor.h>
#include <react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
#include <react/renderer/runtimescheduler/RuntimeSchedulerClock.h>
#include <react/renderer/runtimescheduler/Task.h>
@@ -122,6 +123,10 @@ class RuntimeScheduler_Modern final : public RuntimeSchedulerBase {
void scheduleRenderingUpdate(
RuntimeSchedulerRenderingUpdate&& renderingUpdate) override;
void setShadowTreeRevisionConsistencyManager(
ShadowTreeRevisionConsistencyManager*
shadowTreeRevisionConsistencyManager) override;
private:
std::atomic<uint_fast8_t> syncTaskRequests_{0};
@@ -184,6 +189,8 @@ class RuntimeScheduler_Modern final : public RuntimeSchedulerBase {
bool isWorkLoopScheduled_{false};
std::queue<RuntimeSchedulerRenderingUpdate> pendingRenderingUpdates_;
ShadowTreeRevisionConsistencyManager* shadowTreeRevisionConsistencyManager_{
nullptr};
};
} // namespace facebook::react
@@ -25,6 +25,7 @@ target_link_libraries(react_render_scheduler
jsi
react_config
react_debug
react_featureflags
react_render_componentregistry
react_render_core
react_render_debug
@@ -11,6 +11,7 @@
#include <jsi/jsi.h>
#include <react/debug/react_native_assert.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
#include <react/renderer/core/EventQueueProcessor.h>
#include <react/renderer/core/LayoutContext.h>
@@ -54,6 +55,11 @@ Scheduler::Scheduler(
? weakRuntimeScheduler.value().lock()
: nullptr;
if (runtimeScheduler && ReactNativeFeatureFlags::enableUIConsistency()) {
runtimeScheduler->setShadowTreeRevisionConsistencyManager(
uiManager->getShadowTreeRevisionConsistencyManager());
}
auto eventPipe = [uiManager, runtimeScheduler = runtimeScheduler.get()](
jsi::Runtime& runtime,
const EventTarget* eventTarget,
@@ -27,6 +27,8 @@ target_link_libraries(react_render_uimanager
react_debug
react_featureflags
react_render_componentregistry
react_render_consistency
react_render_uimanager_consistency
react_render_core
react_render_debug
react_render_graphics
@@ -29,6 +29,15 @@ constexpr int DOCUMENT_POSITION_PRECEDING = 2;
constexpr int DOCUMENT_POSITION_FOLLOWING = 4;
constexpr int DOCUMENT_POSITION_CONTAINS = 8;
constexpr int DOCUMENT_POSITION_CONTAINED_BY = 16;
std::unique_ptr<facebook::react::LeakChecker> constructLeakCheckerIfNeeded(
const facebook::react::RuntimeExecutor& runtimeExecutor) {
#ifdef REACT_NATIVE_DEBUG
return std::make_unique<facebook::react::LeakChecker>(runtimeExecutor);
#else
return {};
#endif
}
} // namespace
namespace facebook::react {
@@ -39,23 +48,25 @@ namespace facebook::react {
// isHostObject method)
ShadowNodeListWrapper::~ShadowNodeListWrapper() = default;
static std::unique_ptr<LeakChecker> constructLeakCheckerIfNeeded(
const RuntimeExecutor& runtimeExecutor) {
#ifdef REACT_NATIVE_DEBUG
return std::make_unique<LeakChecker>(runtimeExecutor);
#else
return {};
#endif
}
UIManager::UIManager(
const RuntimeExecutor& runtimeExecutor,
BackgroundExecutor backgroundExecutor,
ContextContainer::Shared contextContainer)
: runtimeExecutor_(runtimeExecutor),
shadowTreeRegistry_(),
backgroundExecutor_(std::move(backgroundExecutor)),
contextContainer_(std::move(contextContainer)),
leakChecker_(constructLeakCheckerIfNeeded(runtimeExecutor)) {}
leakChecker_(constructLeakCheckerIfNeeded(runtimeExecutor)),
lazyShadowTreeRevisionConsistencyManager_(
ReactNativeFeatureFlags::enableUIConsistency()
? std::make_unique<LazyShadowTreeRevisionConsistencyManager>(
shadowTreeRegistry_)
: nullptr),
latestShadowTreeRevisionProvider_(
ReactNativeFeatureFlags::enableUIConsistency()
? nullptr
: std::make_unique<LatestShadowTreeRevisionProvider>(
shadowTreeRegistry_)) {}
UIManager::~UIManager() {
LOG(WARNING) << "UIManager::~UIManager() was called (address: " << this
@@ -166,11 +177,11 @@ void UIManager::appendChild(
void UIManager::completeSurface(
SurfaceId surfaceId,
const ShadowNode::UnsharedListOfShared& rootChildren,
ShadowTree::CommitOptions commitOptions) const {
ShadowTree::CommitOptions commitOptions) {
SystraceSection s("UIManager::completeSurface", "surfaceId", surfaceId);
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) {
shadowTree.commit(
auto result = shadowTree.commit(
[&](RootShadowNode const& oldRootShadowNode) {
return std::make_shared<RootShadowNode>(
oldRootShadowNode,
@@ -180,6 +191,14 @@ void UIManager::completeSurface(
});
},
commitOptions);
if (result == ShadowTree::CommitStatus::Succeeded &&
lazyShadowTreeRevisionConsistencyManager_ != nullptr) {
// It's safe to update the visible revision of the shadow tree immediately
// after we commit a specific one.
lazyShadowTreeRevisionConsistencyManager_->updateCurrentRevision(
surfaceId, shadowTree.getCurrentRevision().rootShadowNode);
}
});
}
@@ -408,6 +427,24 @@ int UIManager::compareDocumentPosition(
return DOCUMENT_POSITION_FOLLOWING;
}
ShadowTreeRevisionConsistencyManager*
UIManager::getShadowTreeRevisionConsistencyManager() {
return lazyShadowTreeRevisionConsistencyManager_.get();
}
ShadowTreeRevisionProvider* UIManager::getShadowTreeRevisionProvider() {
if (lazyShadowTreeRevisionConsistencyManager_ != nullptr) {
return lazyShadowTreeRevisionConsistencyManager_.get();
} else if (latestShadowTreeRevisionProvider_ != nullptr) {
return latestShadowTreeRevisionProvider_.get();
}
LOG(ERROR) << "Unexpected state found in UIManager where both "
<< "lazyShadowTreeRevisionConsistencyManager_ and "
<< "latestShadowTreeRevisionProvider_ were null";
return nullptr;
}
ShadowNode::Shared UIManager::findNodeAtPoint(
const ShadowNode::Shared& node,
Point point) const {
@@ -14,6 +14,7 @@
#include <shared_mutex>
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
#include <react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/core/InstanceHandle.h>
#include <react/renderer/core/RawValue.h>
#include <react/renderer/core/ShadowNode.h>
@@ -24,6 +25,9 @@
#include <react/renderer/mounting/ShadowTreeRegistry.h>
#include <react/renderer/uimanager/UIManagerAnimationDelegate.h>
#include <react/renderer/uimanager/UIManagerDelegate.h>
#include <react/renderer/uimanager/consistency/LatestShadowTreeRevisionProvider.h>
#include <react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/uimanager/consistency/ShadowTreeRevisionProvider.h>
#include <react/renderer/uimanager/primitives.h>
#include <react/utils/ContextContainer.h>
@@ -106,6 +110,10 @@ class UIManager final : public ShadowTreeDelegate {
const ShadowNode& shadowNode,
const ShadowNode& otherShadowNode) const;
ShadowTreeRevisionConsistencyManager*
getShadowTreeRevisionConsistencyManager();
ShadowTreeRevisionProvider* getShadowTreeRevisionProvider();
#pragma mark - Surface Start & Stop
void startSurface(
@@ -152,7 +160,7 @@ class UIManager final : public ShadowTreeDelegate {
void completeSurface(
SurfaceId surfaceId,
const ShadowNode::UnsharedListOfShared& rootChildren,
ShadowTree::CommitOptions commitOptions) const;
ShadowTree::CommitOptions commitOptions);
void setIsJSResponder(
const ShadowNode::Shared& shadowNode,
@@ -238,6 +246,11 @@ class UIManager final : public ShadowTreeDelegate {
mutable std::vector<UIManagerMountHook*> mountHooks_;
std::unique_ptr<LeakChecker> leakChecker_;
std::unique_ptr<LazyShadowTreeRevisionConsistencyManager>
lazyShadowTreeRevisionConsistencyManager_;
std::unique_ptr<LatestShadowTreeRevisionProvider>
latestShadowTreeRevisionProvider_;
};
} // namespace facebook::react
@@ -0,0 +1,26 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++20
-Wall
-Wpedantic
-DLOG_TAG=\"Fabric\")
file(GLOB react_render_uimanager_consistency_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_render_uimanager_consistency SHARED ${react_render_uimanager_consistency_SRC})
target_include_directories(react_render_uimanager_consistency PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_render_uimanager_consistency
glog
rrc_root
react_render_consistency
react_render_mounting)
@@ -0,0 +1,29 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "LatestShadowTreeRevisionProvider.h"
namespace facebook::react {
LatestShadowTreeRevisionProvider::LatestShadowTreeRevisionProvider(
ShadowTreeRegistry& shadowTreeRegistry)
: shadowTreeRegistry_(shadowTreeRegistry) {}
#pragma mark - ShadowTreeRevisionProvider
RootShadowNode::Shared LatestShadowTreeRevisionProvider::getCurrentRevision(
SurfaceId surfaceId) {
RootShadowNode::Shared rootShadowNode;
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) {
rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
});
return rootShadowNode;
}
} // namespace facebook::react
@@ -0,0 +1,35 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/mounting/ShadowTreeRegistry.h>
#include <react/renderer/uimanager/consistency/ShadowTreeRevisionProvider.h>
#include <memory>
namespace facebook::react {
/**
* This is a drop-in replacement for `LazyShadowTreeRevisionConsistencyManager`
* that preserves the current behavior (always providing the latest committed
* revision instead of locking to a specific one).
*/
class LatestShadowTreeRevisionProvider : public ShadowTreeRevisionProvider {
public:
explicit LatestShadowTreeRevisionProvider(
ShadowTreeRegistry& shadowTreeRegistry);
#pragma mark - ShadowTreeRevisionProvider
RootShadowNode::Shared getCurrentRevision(SurfaceId surfaceId) override;
private:
ShadowTreeRegistry& shadowTreeRegistry_;
};
} // namespace facebook::react
@@ -0,0 +1,72 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "LazyShadowTreeRevisionConsistencyManager.h"
#include <glog/logging.h>
namespace facebook::react {
LazyShadowTreeRevisionConsistencyManager::
LazyShadowTreeRevisionConsistencyManager(
ShadowTreeRegistry& shadowTreeRegistry)
: shadowTreeRegistry_(shadowTreeRegistry) {}
void LazyShadowTreeRevisionConsistencyManager::updateCurrentRevision(
SurfaceId surfaceId,
RootShadowNode::Shared rootShadowNode) {
capturedRootShadowNodesForConsistency_.emplace(
surfaceId, std::move(rootShadowNode));
}
#pragma mark - ShadowTreeRevisionProvider
RootShadowNode::Shared
LazyShadowTreeRevisionConsistencyManager::getCurrentRevision(
SurfaceId surfaceId) {
auto it = capturedRootShadowNodesForConsistency_.find(surfaceId);
if (it != capturedRootShadowNodesForConsistency_.end()) {
return it->second;
}
RootShadowNode::Shared rootShadowNode;
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) {
rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
});
capturedRootShadowNodesForConsistency_.emplace(surfaceId, rootShadowNode);
return rootShadowNode;
}
#pragma mark - ConsistentShadowTreeRevisionProvider
void LazyShadowTreeRevisionConsistencyManager::lockRevisions() {
if (isLocked_) {
LOG(WARNING)
<< "LazyShadowTreeRevisionConsistencyManager::lockRevisions() called without unlocking a previous lock";
return;
}
// We actually capture the state lazily the first time we access it, so we
// don't need to do anything here.
isLocked_ = true;
}
void LazyShadowTreeRevisionConsistencyManager::unlockRevisions() {
if (!isLocked_) {
LOG(WARNING)
<< "LazyShadowTreeRevisionConsistencyManager::unlockRevisions() called without a previous lock";
// We don't return here because we want to do the cleanup anyway
// to free up resources.
}
isLocked_ = false;
capturedRootShadowNodesForConsistency_.clear();
}
} // namespace facebook::react
@@ -0,0 +1,51 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h>
#include <react/renderer/mounting/ShadowTreeRegistry.h>
#include <react/renderer/uimanager/consistency/ShadowTreeRevisionProvider.h>
#include <memory>
namespace facebook::react {
/**
* This class implements UI consistency for the JavaScript thread.
* This implementation forces JavaScript to see a stable revision of the shadow
* tree for a given surface ID, only updating it when React commits a new tree
* or between JS tasks.
*/
class LazyShadowTreeRevisionConsistencyManager
: public ShadowTreeRevisionConsistencyManager,
public ShadowTreeRevisionProvider {
public:
explicit LazyShadowTreeRevisionConsistencyManager(
ShadowTreeRegistry& shadowTreeRegistry);
void updateCurrentRevision(
SurfaceId surfaceId,
RootShadowNode::Shared rootShadowNode);
#pragma mark - ShadowTreeRevisionProvider
RootShadowNode::Shared getCurrentRevision(SurfaceId surfaceId) override;
#pragma mark - ShadowTreeRevisionConsistencyManager
void lockRevisions() override;
void unlockRevisions() override;
private:
std::unordered_map<SurfaceId, RootShadowNode::Shared>
capturedRootShadowNodesForConsistency_;
ShadowTreeRegistry& shadowTreeRegistry_;
bool isLocked_{false};
};
} // namespace facebook::react
@@ -0,0 +1,27 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <memory>
namespace facebook::react {
/**
* This interface is used for UI consistency, indicating the revision of the
* shadow tree that a caller should have access to.
*/
class ShadowTreeRevisionProvider {
public:
virtual ~ShadowTreeRevisionProvider() = default;
virtual RootShadowNode::Shared getCurrentRevision(SurfaceId surfaceId) = 0;
};
} // namespace facebook::react
@@ -67,6 +67,11 @@ const definitions: FeatureFlagDefinitions = {
description:
'Uses new, deduplicated logic for constructing Android Spannables from text fragments',
},
enableUIConsistency: {
defaultValue: false,
description:
'Ensures that JavaScript always has a consistent view of the state of the UI (e.g.: commits done in other threads are not immediately propagated to JS during its execution).',
},
inspectorEnableCxxInspectorPackagerConnection: {
defaultValue: false,
description:
@@ -147,6 +147,7 @@ def use_react_native! (
pod 'React-runtimeexecutor', :path => "#{prefix}/ReactCommon/runtimeexecutor"
pod 'React-runtimescheduler', :path => "#{prefix}/ReactCommon/react/renderer/runtimescheduler"
pod 'React-rendererdebug', :path => "#{prefix}/ReactCommon/react/renderer/debug"
pod 'React-rendererconsistency', :path => "#{prefix}/ReactCommon/react/renderer/consistency"
pod 'React-perflogger', :path => "#{prefix}/ReactCommon/reactperflogger"
pod 'React-logger', :path => "#{prefix}/ReactCommon/logger"
pod 'ReactCommon/turbomodule/core', :path => "#{prefix}/ReactCommon", :modular_headers => true
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<aade6c478e09fb1f92319c3f46adaf13>>
* @generated SignedSource<<c653f884a1ebb7556d0f59a4fdcf2fed>>
* @flow strict-local
*/
@@ -48,6 +48,7 @@ export type ReactNativeFeatureFlags = {
enableMicrotasks: Getter<boolean>,
enableMountHooksAndroid: Getter<boolean>,
enableSpannableBuildingUnification: Getter<boolean>,
enableUIConsistency: Getter<boolean>,
inspectorEnableCxxInspectorPackagerConnection: Getter<boolean>,
inspectorEnableModernCDPRegistry: Getter<boolean>,
useModernRuntimeScheduler: Getter<boolean>,
@@ -125,6 +126,10 @@ export const enableMountHooksAndroid: Getter<boolean> = createNativeFlagGetter('
* Uses new, deduplicated logic for constructing Android Spannables from text fragments
*/
export const enableSpannableBuildingUnification: Getter<boolean> = createNativeFlagGetter('enableSpannableBuildingUnification', false);
/**
* Ensures that JavaScript always has a consistent view of the state of the UI (e.g.: commits done in other threads are not immediately propagated to JS during its execution).
*/
export const enableUIConsistency: Getter<boolean> = createNativeFlagGetter('enableUIConsistency', false);
/**
* Flag determining if the C++ implementation of InspectorPackagerConnection should be used instead of the per-platform one. This flag is global and should not be changed across React Host lifetimes.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<8fcd655a9837ad155fd71efe8b05e3d0>>
* @generated SignedSource<<2f4c06a0e456d55a4bbd6b8c48c7f22d>>
* @flow strict-local
*/
@@ -31,6 +31,7 @@ export interface Spec extends TurboModule {
+enableMicrotasks?: () => boolean;
+enableMountHooksAndroid?: () => boolean;
+enableSpannableBuildingUnification?: () => boolean;
+enableUIConsistency?: () => boolean;
+inspectorEnableCxxInspectorPackagerConnection?: () => boolean;
+inspectorEnableModernCDPRegistry?: () => boolean;
+useModernRuntimeScheduler?: () => boolean;