From 8c309d14d1d1ed714e6e7cfbcbe8442de13723fd Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 1 May 2026 04:04:51 -0700 Subject: [PATCH] Use MutableIntObjectMap for SurfaceMountingManager view registry (#56646) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Replace `ConcurrentHashMap` with `MutableIntObjectMap` + `ReentrantReadWriteLock` in `SurfaceMountingManager`, behind the `useOptimizedViewRegistryOnAndroid` feature flag. `ConcurrentHashMap` boxes every `Int` key to `java.lang.Integer` (16 bytes) and allocates a `Node` object per entry (~32 bytes). `MutableIntObjectMap` from `androidx.collection` uses a Swiss Table layout with primitive `IntArray` keys — ~9 bytes/entry vs ~56-64 bytes, a ~6x reduction in map overhead. For a complex surface with 2000+ views, this saves ~90KB per surface in map overhead alone, plus reduced GC pressure from eliminating `Integer` boxing. The `ReentrantReadWriteLock` replaces CHM's built-in concurrency. This matches the access pattern: reads from any thread (`getEventEmitter`, `enqueuePendingEvent`), writes almost exclusively from the UI thread. Readers never block each other. `stopSurface` snapshots entries under the write lock and processes `onViewStateDeleted` outside the lock to minimize reader blocking. Changelog: [Internal] Reviewed By: sammy-SC Differential Revision: D102797904 --- .../fabric/mounting/SurfaceMountingManager.kt | 131 ++++++++++++++---- .../featureflags/ReactNativeFeatureFlags.kt | 8 +- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 ++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../featureflags/ReactNativeFeatureFlags.cpp | 6 +- .../featureflags/ReactNativeFeatureFlags.h | 7 +- .../ReactNativeFeatureFlagsAccessor.cpp | 36 +++-- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 +- .../NativeReactNativeFeatureFlags.h | 4 +- .../ReactNativeFeatureFlags.config.js | 11 ++ .../featureflags/ReactNativeFeatureFlags.js | 7 +- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 21 files changed, 249 insertions(+), 55 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt index b535c10ce5aa..9e53dc8c365b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt @@ -15,6 +15,7 @@ import android.view.ViewGroup import android.view.ViewParent import androidx.annotation.AnyThread import androidx.annotation.UiThread +import androidx.collection.MutableIntObjectMap import androidx.collection.SparseArrayCompat import androidx.core.graphics.drawable.toDrawable import com.facebook.common.logging.FLog @@ -56,7 +57,10 @@ import java.util.ArrayDeque import java.util.LinkedList import java.util.Queue import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.Volatile +import kotlin.concurrent.read +import kotlin.concurrent.write /** Returns true if the collection contains [key]. */ private operator fun SparseArrayCompat.contains(key: Int): Boolean = containsKey(key) @@ -84,7 +88,20 @@ internal constructor( public var context: ThemedReactContext? = reactContext private set - private val tagToViewState: ConcurrentHashMap = ConcurrentHashMap() // any thread + private val tagToViewState: ConcurrentHashMap? + private val optimizedTagToViewState: MutableIntObjectMap? + private val registryLock = ReentrantReadWriteLock() + + init { + if (ReactNativeFeatureFlags.useOptimizedViewRegistryOnAndroid()) { + tagToViewState = null + optimizedTagToViewState = MutableIntObjectMap() + } else { + tagToViewState = ConcurrentHashMap() + optimizedTagToViewState = null + } + } + private val onViewAttachMountItems: Queue = ArrayDeque() // These are all non-null, until StopSurface is called @@ -126,7 +143,7 @@ internal constructor( return } - tagToViewState[surfaceId] = ViewState(surfaceId, rootView, rootViewManager, true) + registryPut(surfaceId, ViewState(surfaceId, rootView, rootViewManager, true)) val runnable: Runnable = object : GuardedRunnable(checkNotNull(context)) { @@ -190,7 +207,7 @@ internal constructor( if (tagSetForStoppedSurface?.containsKey(tag) == true) { return true } - return tagToViewState.containsKey(tag) + return registryContains(tag) } @UiThread @@ -236,7 +253,7 @@ internal constructor( // Reset all StateWrapper objects // Since this can happen on any thread, is it possible to race between StateWrapper destruction // and some accesses from View classes in the UI thread? - for (viewState in tagToViewState.values) { + registryForEachValue { viewState -> viewState.stateWrapper?.destroyState() viewState.stateWrapper = null @@ -248,15 +265,30 @@ internal constructor( if (ReactNativeFeatureFlags.enableViewRecycling()) { viewManagerRegistry?.onSurfaceStopped(surfaceId) } - val tagSetForStoppedSurface = - SparseArrayCompat().also { this.tagSetForStoppedSurface = it } - for ((key, value) in tagToViewState) { - // Using this as a placeholder value in the map. We're using SparseArrayCompat - // since it can efficiently represent the list of pending tags - tagSetForStoppedSurface[key] = this - - // We must call `onDropViewInstance` on all remaining Views - onViewStateDeleted(value) + + if (optimizedTagToViewState != null) { + val viewStatesToDelete: ArrayList + registryLock.write { + val tagSetForStoppedSurface = + SparseArrayCompat().also { this.tagSetForStoppedSurface = it } + viewStatesToDelete = ArrayList(optimizedTagToViewState.size) + optimizedTagToViewState.forEach { key, value -> + tagSetForStoppedSurface[key] = this@SurfaceMountingManager + viewStatesToDelete.add(value) + } + optimizedTagToViewState.clear() + } + for (viewState in viewStatesToDelete) { + onViewStateDeleted(viewState) + } + } else { + val tagSetForStoppedSurface = + SparseArrayCompat().also { this.tagSetForStoppedSurface = it } + for ((key, value) in tagToViewState!!) { + tagSetForStoppedSurface[key] = this + onViewStateDeleted(value) + } + tagToViewState!!.clear() } // Evict all views from cache and memory @@ -264,7 +296,6 @@ internal constructor( rootViewManager = null mountItemExecutor = null context = null - tagToViewState.clear() onViewAttachMountItems.clear() tagToSynchronousMountProps.clear() FLog.e(TAG, "Surface [$surfaceId] was stopped on SurfaceMountingManager.") @@ -572,7 +603,7 @@ internal constructor( this.stateWrapper = stateWrapper this.eventEmitter = eventEmitterWrapper } - tagToViewState[reactTag] = viewState + registryPut(reactTag, viewState) if (isLayoutable) { @Suppress("UNCHECKED_CAST") @@ -921,13 +952,19 @@ internal constructor( return } - var viewState = tagToViewState[reactTag] - if (viewState == null) { - // TODO T62717437 - Use a flag to determine that these event emitters belong to virtual nodes - // only. - viewState = ViewState(reactTag) - tagToViewState[reactTag] = viewState - } + // TODO T62717437 - Use a flag to determine that these event emitters belong to virtual nodes + // only. + val viewState: ViewState = + if (optimizedTagToViewState != null) { + registryLock.write { optimizedTagToViewState.getOrPut(reactTag) { ViewState(reactTag) } } + } else { + var vs = tagToViewState!![reactTag] + if (vs == null) { + vs = ViewState(reactTag) + tagToViewState!![reactTag] = vs + } + vs + } val previousEventEmitterWrapper = viewState.eventEmitter viewState.eventEmitter = eventEmitter @@ -1039,7 +1076,7 @@ internal constructor( // To delete we simply remove the tag from the registry. // We want to rely on the correct set of MountInstructions being sent to the platform, // or StopSurface being called, so we do not handle deleting descendants of the View. - tagToViewState.remove(reactTag) + registryRemove(reactTag) onViewStateDeleted(viewState) } @@ -1084,12 +1121,52 @@ internal constructor( } private fun getViewState(reactTag: Int): ViewState = - tagToViewState[reactTag] + registryGet(reactTag) ?: throw RetryableMountingLayerException( "Unable to find viewState for tag $reactTag. Surface stopped: $isStopped" ) - private fun getNullableViewState(reactTag: Int): ViewState? = tagToViewState[reactTag] + private fun getNullableViewState(reactTag: Int): ViewState? = registryGet(reactTag) + + private fun registryGet(tag: Int): ViewState? { + return if (optimizedTagToViewState != null) { + registryLock.read { optimizedTagToViewState[tag] } + } else { + tagToViewState!![tag] + } + } + + private fun registryPut(tag: Int, state: ViewState) { + if (optimizedTagToViewState != null) { + registryLock.write { optimizedTagToViewState[tag] = state } + } else { + tagToViewState!![tag] = state + } + } + + private fun registryRemove(tag: Int) { + if (optimizedTagToViewState != null) { + registryLock.write { optimizedTagToViewState.remove(tag) } + } else { + tagToViewState!!.remove(tag) + } + } + + private fun registryContains(tag: Int): Boolean { + return if (optimizedTagToViewState != null) { + registryLock.read { optimizedTagToViewState.containsKey(tag) } + } else { + tagToViewState!!.containsKey(tag) + } + } + + private inline fun registryForEachValue(action: (ViewState) -> Unit) { + if (optimizedTagToViewState != null) { + registryLock.read { optimizedTagToViewState.forEachValue(action) } + } else { + tagToViewState!!.values.forEach(action) + } + } /** Applies a bitmap as the background of the view with the given tag, if it exists. */ @UiThread @@ -1100,7 +1177,7 @@ internal constructor( public fun printSurfaceState(): Unit { FLog.e(TAG, "Views created for surface $surfaceId:") - for (viewState in tagToViewState.values) { + registryForEachValue { viewState -> val viewManagerName = viewState.viewManager?.name val view = viewState.view val parent = if (view != null) view.parent as View? else null @@ -1142,7 +1219,7 @@ internal constructor( ): Unit { // When the surface stopped we will reset the view state map. We are not going to enqueue // pending events as they are not expected to be dispatched anyways. - val viewState = tagToViewState[reactTag] + val viewState = registryGet(reactTag) if (viewState == null) { // Cannot queue event without view state. Do nothing here. diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 66c05902b4ee..b4f98727365a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -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<> + * @generated SignedSource<<6ad566ffaa8330c696fa2088ff696a2b>> */ /** @@ -534,6 +534,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun useNestedScrollViewAndroid(): Boolean = accessor.useNestedScrollViewAndroid() + /** + * Use MutableIntObjectMap with ReadWriteLock instead of ConcurrentHashMap for the view registry in SurfaceMountingManager to reduce memory overhead and GC pressure. + */ + @JvmStatic + public fun useOptimizedViewRegistryOnAndroid(): Boolean = accessor.useOptimizedViewRegistryOnAndroid() + /** * Use shared animation backend in C++ Animated */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index f5c895d05ab6..38ee2e2c268d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -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<<4f9f5c1c46217ed6802abd5f786aac19>> + * @generated SignedSource<> */ /** @@ -104,6 +104,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var useLISAlgorithmInDifferentiatorCache: Boolean? = null private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null private var useNestedScrollViewAndroidCache: Boolean? = null + private var useOptimizedViewRegistryOnAndroidCache: Boolean? = null private var useSharedAnimatedBackendCache: Boolean? = null private var useTraitHiddenOnAndroidCache: Boolean? = null private var useTurboModuleInteropCache: Boolean? = null @@ -869,6 +870,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun useOptimizedViewRegistryOnAndroid(): Boolean { + var cached = useOptimizedViewRegistryOnAndroidCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.useOptimizedViewRegistryOnAndroid() + useOptimizedViewRegistryOnAndroidCache = cached + } + return cached + } + override fun useSharedAnimatedBackend(): Boolean { var cached = useSharedAnimatedBackendCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 028421437717..cd96914eb900 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -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<<8916e9f4a938a69ff175c806db9835d4>> + * @generated SignedSource<<817790c7ceb9112376b4ab4ee338ff43>> */ /** @@ -196,6 +196,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun useNestedScrollViewAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun useOptimizedViewRegistryOnAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun useSharedAnimatedBackend(): Boolean @DoNotStrip @JvmStatic public external fun useTraitHiddenOnAndroid(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 7136c0a4758a..14f7a230e3b6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -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<> + * @generated SignedSource<<1d764e41252e2709e574da5e0ac64bd7>> */ /** @@ -191,6 +191,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun useNestedScrollViewAndroid(): Boolean = false + override fun useOptimizedViewRegistryOnAndroid(): Boolean = false + override fun useSharedAnimatedBackend(): Boolean = false override fun useTraitHiddenOnAndroid(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 698723ec7ef8..65e40ecba524 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -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<> + * @generated SignedSource<<299507458fe84339ddf816dd58671fd3>> */ /** @@ -108,6 +108,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var useLISAlgorithmInDifferentiatorCache: Boolean? = null private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null private var useNestedScrollViewAndroidCache: Boolean? = null + private var useOptimizedViewRegistryOnAndroidCache: Boolean? = null private var useSharedAnimatedBackendCache: Boolean? = null private var useTraitHiddenOnAndroidCache: Boolean? = null private var useTurboModuleInteropCache: Boolean? = null @@ -957,6 +958,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun useOptimizedViewRegistryOnAndroid(): Boolean { + var cached = useOptimizedViewRegistryOnAndroidCache + if (cached == null) { + cached = currentProvider.useOptimizedViewRegistryOnAndroid() + accessedFeatureFlags.add("useOptimizedViewRegistryOnAndroid") + useOptimizedViewRegistryOnAndroidCache = cached + } + return cached + } + override fun useSharedAnimatedBackend(): Boolean { var cached = useSharedAnimatedBackendCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 3bcc2f82ed22..add0e3f972f6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -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<<536a5156deea17740dd24782bf79feb4>> + * @generated SignedSource<<8f887fb839df553b23886a61537e6991>> */ /** @@ -191,6 +191,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun useNestedScrollViewAndroid(): Boolean + @DoNotStrip public fun useOptimizedViewRegistryOnAndroid(): Boolean + @DoNotStrip public fun useSharedAnimatedBackend(): Boolean @DoNotStrip public fun useTraitHiddenOnAndroid(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 67a3144ccd0f..227cbe59e077 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -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<> + * @generated SignedSource<<5cd1b7223dd852a97280f6dd9bd3e559>> */ /** @@ -543,6 +543,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool useOptimizedViewRegistryOnAndroid() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useOptimizedViewRegistryOnAndroid"); + return method(javaProvider_); + } + bool useSharedAnimatedBackend() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useSharedAnimatedBackend"); @@ -1015,6 +1021,11 @@ bool JReactNativeFeatureFlagsCxxInterop::useNestedScrollViewAndroid( return ReactNativeFeatureFlags::useNestedScrollViewAndroid(); } +bool JReactNativeFeatureFlagsCxxInterop::useOptimizedViewRegistryOnAndroid( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::useOptimizedViewRegistryOnAndroid(); +} + bool JReactNativeFeatureFlagsCxxInterop::useSharedAnimatedBackend( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::useSharedAnimatedBackend(); @@ -1338,6 +1349,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "useNestedScrollViewAndroid", JReactNativeFeatureFlagsCxxInterop::useNestedScrollViewAndroid), + makeNativeMethod( + "useOptimizedViewRegistryOnAndroid", + JReactNativeFeatureFlagsCxxInterop::useOptimizedViewRegistryOnAndroid), makeNativeMethod( "useSharedAnimatedBackend", JReactNativeFeatureFlagsCxxInterop::useSharedAnimatedBackend), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index aff905ed7d73..6f8d7e2c9cf5 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -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<<4be32bad403baeca1a28f19ad181c42c>> + * @generated SignedSource<> */ /** @@ -282,6 +282,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool useNestedScrollViewAndroid( facebook::jni::alias_ref); + static bool useOptimizedViewRegistryOnAndroid( + facebook::jni::alias_ref); + static bool useSharedAnimatedBackend( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index c2a07d570235..073f90cdde1f 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -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<> + * @generated SignedSource<<13f834a8abe75fd71b75aa7d94a40303>> */ /** @@ -362,6 +362,10 @@ bool ReactNativeFeatureFlags::useNestedScrollViewAndroid() { return getAccessor().useNestedScrollViewAndroid(); } +bool ReactNativeFeatureFlags::useOptimizedViewRegistryOnAndroid() { + return getAccessor().useOptimizedViewRegistryOnAndroid(); +} + bool ReactNativeFeatureFlags::useSharedAnimatedBackend() { return getAccessor().useSharedAnimatedBackend(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 423e77658798..55d643ee4060 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -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<<8e9b09843bf4a0312b254559a975f612>> + * @generated SignedSource<> */ /** @@ -459,6 +459,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool useNestedScrollViewAndroid(); + /** + * Use MutableIntObjectMap with ReadWriteLock instead of ConcurrentHashMap for the view registry in SurfaceMountingManager to reduce memory overhead and GC pressure. + */ + RN_EXPORT static bool useOptimizedViewRegistryOnAndroid(); + /** * Use shared animation backend in C++ Animated */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 948a6241e535..2b2d2a22d7fa 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -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<> + * @generated SignedSource<<631de9950d2702721e0f926967e77a3e>> */ /** @@ -1541,6 +1541,24 @@ bool ReactNativeFeatureFlagsAccessor::useNestedScrollViewAndroid() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::useOptimizedViewRegistryOnAndroid() { + auto flagValue = useOptimizedViewRegistryOnAndroid_.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(84, "useOptimizedViewRegistryOnAndroid"); + + flagValue = currentProvider_->useOptimizedViewRegistryOnAndroid(); + useOptimizedViewRegistryOnAndroid_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::useSharedAnimatedBackend() { auto flagValue = useSharedAnimatedBackend_.load(); @@ -1550,7 +1568,7 @@ bool ReactNativeFeatureFlagsAccessor::useSharedAnimatedBackend() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(84, "useSharedAnimatedBackend"); + markFlagAsAccessed(85, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1568,7 +1586,7 @@ bool ReactNativeFeatureFlagsAccessor::useTraitHiddenOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(85, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(86, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1586,7 +1604,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(86, "useTurboModuleInterop"); + markFlagAsAccessed(87, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1604,7 +1622,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(87, "useTurboModules"); + markFlagAsAccessed(88, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1622,7 +1640,7 @@ bool ReactNativeFeatureFlagsAccessor::useUnorderedMapInDifferentiator() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(88, "useUnorderedMapInDifferentiator"); + markFlagAsAccessed(89, "useUnorderedMapInDifferentiator"); flagValue = currentProvider_->useUnorderedMapInDifferentiator(); useUnorderedMapInDifferentiator_ = flagValue; @@ -1640,7 +1658,7 @@ double ReactNativeFeatureFlagsAccessor::viewCullingOutsetRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(89, "viewCullingOutsetRatio"); + markFlagAsAccessed(90, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1658,7 +1676,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(90, "viewTransitionEnabled"); + markFlagAsAccessed(91, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1676,7 +1694,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(91, "virtualViewPrerenderRatio"); + markFlagAsAccessed(92, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index cf2ca1f872e8..8e25db0821b2 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -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<<9334675799ea378311b8c675ed419b1d>> + * @generated SignedSource<> */ /** @@ -116,6 +116,7 @@ class ReactNativeFeatureFlagsAccessor { bool useLISAlgorithmInDifferentiator(); bool useNativeViewConfigsInBridgelessMode(); bool useNestedScrollViewAndroid(); + bool useOptimizedViewRegistryOnAndroid(); bool useSharedAnimatedBackend(); bool useTraitHiddenOnAndroid(); bool useTurboModuleInterop(); @@ -135,7 +136,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 92> accessedFeatureFlags_; + std::array, 93> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -221,6 +222,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> useLISAlgorithmInDifferentiator_; std::atomic> useNativeViewConfigsInBridgelessMode_; std::atomic> useNestedScrollViewAndroid_; + std::atomic> useOptimizedViewRegistryOnAndroid_; std::atomic> useSharedAnimatedBackend_; std::atomic> useTraitHiddenOnAndroid_; std::atomic> useTurboModuleInterop_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 10df0490068d..8788decc3dfd 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -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<> + * @generated SignedSource<<278c24a1e5dc5fc24f9f9349fe6d09b4>> */ /** @@ -363,6 +363,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool useOptimizedViewRegistryOnAndroid() override { + return false; + } + bool useSharedAnimatedBackend() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 1f9a7c1307f6..a48b2c54ddda 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -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<<7853bedb8a11b20a633eb6579257b4bc>> + * @generated SignedSource<<30df75f444340e662066500c20e3614f>> */ /** @@ -801,6 +801,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::useNestedScrollViewAndroid(); } + bool useOptimizedViewRegistryOnAndroid() override { + auto value = values_["useOptimizedViewRegistryOnAndroid"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::useOptimizedViewRegistryOnAndroid(); + } + bool useSharedAnimatedBackend() override { auto value = values_["useSharedAnimatedBackend"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 2acdef336041..d463e98800c1 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -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<> + * @generated SignedSource<<2262c1ed8ff7983caf65dc2975d52642>> */ /** @@ -109,6 +109,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool useLISAlgorithmInDifferentiator() = 0; virtual bool useNativeViewConfigsInBridgelessMode() = 0; virtual bool useNestedScrollViewAndroid() = 0; + virtual bool useOptimizedViewRegistryOnAndroid() = 0; virtual bool useSharedAnimatedBackend() = 0; virtual bool useTraitHiddenOnAndroid() = 0; virtual bool useTurboModuleInterop() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 816c333b1415..912c18bad649 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -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<> + * @generated SignedSource<<0d2290e74b0e522d15403896a40955cc>> */ /** @@ -464,6 +464,11 @@ bool NativeReactNativeFeatureFlags::useNestedScrollViewAndroid( return ReactNativeFeatureFlags::useNestedScrollViewAndroid(); } +bool NativeReactNativeFeatureFlags::useOptimizedViewRegistryOnAndroid( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::useOptimizedViewRegistryOnAndroid(); +} + bool NativeReactNativeFeatureFlags::useSharedAnimatedBackend( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::useSharedAnimatedBackend(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 9097a2a90c49..6efae6f2f3ec 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -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<<3f44b628a681f0d005827f51f4ad885e>> + * @generated SignedSource<<0afa249e55277f5029eec0a740ad00b2>> */ /** @@ -204,6 +204,8 @@ class NativeReactNativeFeatureFlags bool useNestedScrollViewAndroid(jsi::Runtime& runtime); + bool useOptimizedViewRegistryOnAndroid(jsi::Runtime& runtime); + bool useSharedAnimatedBackend(jsi::Runtime& runtime); bool useTraitHiddenOnAndroid(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 6008e3209f1b..15414fa5b818 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -945,6 +945,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + useOptimizedViewRegistryOnAndroid: { + defaultValue: false, + metadata: { + dateAdded: '2026-04-28', + description: + 'Use MutableIntObjectMap with ReadWriteLock instead of ConcurrentHashMap for the view registry in SurfaceMountingManager to reduce memory overhead and GC pressure.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, useSharedAnimatedBackend: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 8f5f75e62a14..5aaa3fab8af3 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -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<<1dd51a152bb30c0e2073a14566c8368d>> + * @generated SignedSource<<01348c36a6888d3ca4863a37ca70d150>> * @flow strict * @noformat */ @@ -131,6 +131,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ useLISAlgorithmInDifferentiator: Getter, useNativeViewConfigsInBridgelessMode: Getter, useNestedScrollViewAndroid: Getter, + useOptimizedViewRegistryOnAndroid: Getter, useSharedAnimatedBackend: Getter, useTraitHiddenOnAndroid: Getter, useTurboModuleInterop: Getter, @@ -541,6 +542,10 @@ export const useNativeViewConfigsInBridgelessMode: Getter = createNativ * When enabled, ReactScrollView will extend NestedScrollView instead of ScrollView on Android for improved nested scrolling support. */ export const useNestedScrollViewAndroid: Getter = createNativeFlagGetter('useNestedScrollViewAndroid', false); +/** + * Use MutableIntObjectMap with ReadWriteLock instead of ConcurrentHashMap for the view registry in SurfaceMountingManager to reduce memory overhead and GC pressure. + */ +export const useOptimizedViewRegistryOnAndroid: Getter = createNativeFlagGetter('useOptimizedViewRegistryOnAndroid', false); /** * Use shared animation backend in C++ Animated */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index c9116e01e890..f53713c5553b 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -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<<5ab72b9af228bc7e591bc0addaf6150e>> + * @generated SignedSource<<68f7919d416dce9a9b6bc0959bfc259b>> * @flow strict * @noformat */ @@ -109,6 +109,7 @@ export interface Spec extends TurboModule { +useLISAlgorithmInDifferentiator?: () => boolean; +useNativeViewConfigsInBridgelessMode?: () => boolean; +useNestedScrollViewAndroid?: () => boolean; + +useOptimizedViewRegistryOnAndroid?: () => boolean; +useSharedAnimatedBackend?: () => boolean; +useTraitHiddenOnAndroid?: () => boolean; +useTurboModuleInterop?: () => boolean;