setup an experiment to prevent double measure of text (#44120)

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

changelog: [internal]

Prevent measuring the same text twice in `ParagraphShadowNode`.

The current implementation calls `TextLayoutManager::measure` twice for a single `ParagraphShadowNode`. The first time to measure the node for Yoga. The second time inside `ParagraphShadowNode::layout`. I think the original author counted on the cache inside of `TextLayoutManager` to deal with this, but this is not always the case and `TextLayoutManager::measure` is called with two different available widths, leading to cache miss and fills the cache faster.

Reviewed By: NickGerleman

Differential Revision: D55757264

fbshipit-source-id: 0bf8b49f062f802a4e2f04cad1bf1d4bf001b870
This commit is contained in:
Samuel Susla
2024-04-16 12:19:11 -07:00
committed by Facebook GitHub Bot
parent e47297967c
commit 8c53ac6078
21 changed files with 148 additions and 31 deletions
@@ -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<<7c2d825e15fc7e442034a598eebb6e25>>
* @generated SignedSource<<61b929b9624eeaedfa12d2f390299447>>
*/
/**
@@ -106,6 +106,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun inspectorEnableModernCDPRegistry(): Boolean = accessor.inspectorEnableModernCDPRegistry()
/**
* When enabled, ParagraphShadowNode will no longer call measure twice.
*/
@JvmStatic
public fun preventDoubleTextMeasure(): Boolean = accessor.preventDoubleTextMeasure()
/**
* When enabled, it uses the modern fork of RuntimeScheduler that allows scheduling tasks with priorities from any thread.
*/
@@ -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<<eb3ae68166b3ec7f797a0db4b2ec1f5b>>
* @generated SignedSource<<46e1a8dd088d588ea9eec703d7155710>>
*/
/**
@@ -33,6 +33,7 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
private var forceBatchingMountItemsOnAndroidCache: Boolean? = null
private var inspectorEnableCxxInspectorPackagerConnectionCache: Boolean? = null
private var inspectorEnableModernCDPRegistryCache: Boolean? = null
private var preventDoubleTextMeasureCache: Boolean? = null
private var useModernRuntimeSchedulerCache: Boolean? = null
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useStateAlignmentMechanismCache: Boolean? = null
@@ -154,6 +155,15 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
return cached
}
override fun preventDoubleTextMeasure(): Boolean {
var cached = preventDoubleTextMeasureCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.preventDoubleTextMeasure()
preventDoubleTextMeasureCache = cached
}
return cached
}
override fun useModernRuntimeScheduler(): Boolean {
var cached = useModernRuntimeSchedulerCache
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<<0b1377acc1a7a2bed8bd6448a54ec91a>>
* @generated SignedSource<<56ac4a5eaf65775fdb757fc10ed82908>>
*/
/**
@@ -54,6 +54,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun inspectorEnableModernCDPRegistry(): Boolean
@DoNotStrip @JvmStatic public external fun preventDoubleTextMeasure(): Boolean
@DoNotStrip @JvmStatic public external fun useModernRuntimeScheduler(): Boolean
@DoNotStrip @JvmStatic public external fun useNativeViewConfigsInBridgelessMode(): 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<<545ef5d7c16c2d6599f15a747c53cf3f>>
* @generated SignedSource<<6cc4fccd13a1e426a2d6a2a24530e5e6>>
*/
/**
@@ -49,6 +49,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun inspectorEnableModernCDPRegistry(): Boolean = false
override fun preventDoubleTextMeasure(): Boolean = false
override fun useModernRuntimeScheduler(): Boolean = false
override fun useNativeViewConfigsInBridgelessMode(): 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<<aa359470a22488b45abfa18b0edadb18>>
* @generated SignedSource<<c1c7546acade83cf63f3c06e78352982>>
*/
/**
@@ -37,6 +37,7 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
private var forceBatchingMountItemsOnAndroidCache: Boolean? = null
private var inspectorEnableCxxInspectorPackagerConnectionCache: Boolean? = null
private var inspectorEnableModernCDPRegistryCache: Boolean? = null
private var preventDoubleTextMeasureCache: Boolean? = null
private var useModernRuntimeSchedulerCache: Boolean? = null
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useStateAlignmentMechanismCache: Boolean? = null
@@ -171,6 +172,16 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun preventDoubleTextMeasure(): Boolean {
var cached = preventDoubleTextMeasureCache
if (cached == null) {
cached = currentProvider.preventDoubleTextMeasure()
accessedFeatureFlags.add("preventDoubleTextMeasure")
preventDoubleTextMeasureCache = cached
}
return cached
}
override fun useModernRuntimeScheduler(): Boolean {
var cached = useModernRuntimeSchedulerCache
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<<e2eef1313051abddc31e4a3340e72c1c>>
* @generated SignedSource<<a5cbe59693a5f7d64af6e004951ab971>>
*/
/**
@@ -49,6 +49,8 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun inspectorEnableModernCDPRegistry(): Boolean
@DoNotStrip public fun preventDoubleTextMeasure(): Boolean
@DoNotStrip public fun useModernRuntimeScheduler(): Boolean
@DoNotStrip public fun useNativeViewConfigsInBridgelessMode(): 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<<82163a04d1e3cb54d6c5125173ab807f>>
* @generated SignedSource<<f8ac6f7e857808ade5d0f26ec7924604>>
*/
/**
@@ -117,6 +117,12 @@ class ReactNativeFeatureFlagsProviderHolder
return method(javaProvider_);
}
bool preventDoubleTextMeasure() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("preventDoubleTextMeasure");
return method(javaProvider_);
}
bool useModernRuntimeScheduler() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("useModernRuntimeScheduler");
@@ -204,6 +210,11 @@ bool JReactNativeFeatureFlagsCxxInterop::inspectorEnableModernCDPRegistry(
return ReactNativeFeatureFlags::inspectorEnableModernCDPRegistry();
}
bool JReactNativeFeatureFlagsCxxInterop::preventDoubleTextMeasure(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::preventDoubleTextMeasure();
}
bool JReactNativeFeatureFlagsCxxInterop::useModernRuntimeScheduler(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::useModernRuntimeScheduler();
@@ -275,6 +286,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"inspectorEnableModernCDPRegistry",
JReactNativeFeatureFlagsCxxInterop::inspectorEnableModernCDPRegistry),
makeNativeMethod(
"preventDoubleTextMeasure",
JReactNativeFeatureFlagsCxxInterop::preventDoubleTextMeasure),
makeNativeMethod(
"useModernRuntimeScheduler",
JReactNativeFeatureFlagsCxxInterop::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<<48e337d37416ab988ea747050bf207f3>>
* @generated SignedSource<<a6b8839cf6f9e87848289a8c5c37a5c8>>
*/
/**
@@ -69,6 +69,9 @@ class JReactNativeFeatureFlagsCxxInterop
static bool inspectorEnableModernCDPRegistry(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool preventDoubleTextMeasure(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool useModernRuntimeScheduler(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -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<<ddb65722ccc0d584d105b07a621e6871>>
* @generated SignedSource<<0fe73e0526dbc2f860029b8cb0fbcfe3>>
*/
/**
@@ -73,6 +73,10 @@ bool ReactNativeFeatureFlags::inspectorEnableModernCDPRegistry() {
return getAccessor().inspectorEnableModernCDPRegistry();
}
bool ReactNativeFeatureFlags::preventDoubleTextMeasure() {
return getAccessor().preventDoubleTextMeasure();
}
bool ReactNativeFeatureFlags::useModernRuntimeScheduler() {
return getAccessor().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<<8804fc52ad516ba82c0db6fcdea5f110>>
* @generated SignedSource<<d6a33b997461fd186c58a956f6854aca>>
*/
/**
@@ -102,6 +102,11 @@ class ReactNativeFeatureFlags {
*/
RN_EXPORT static bool inspectorEnableModernCDPRegistry();
/**
* When enabled, ParagraphShadowNode will no longer call measure twice.
*/
RN_EXPORT static bool preventDoubleTextMeasure();
/**
* When enabled, it uses the modern fork of RuntimeScheduler that allows scheduling tasks with priorities from any thread.
*/
@@ -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<<9f5c5313e139b450449e2716735c26e7>>
* @generated SignedSource<<f77452d93caa28c074c6ba8595572e6c>>
*/
/**
@@ -263,6 +263,24 @@ bool ReactNativeFeatureFlagsAccessor::inspectorEnableModernCDPRegistry() {
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::preventDoubleTextMeasure() {
auto flagValue = preventDoubleTextMeasure_.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(13, "preventDoubleTextMeasure");
flagValue = currentProvider_->preventDoubleTextMeasure();
preventDoubleTextMeasure_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::useModernRuntimeScheduler() {
auto flagValue = useModernRuntimeScheduler_.load();
@@ -272,7 +290,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(13, "useModernRuntimeScheduler");
markFlagAsAccessed(14, "useModernRuntimeScheduler");
flagValue = currentProvider_->useModernRuntimeScheduler();
useModernRuntimeScheduler_ = flagValue;
@@ -290,7 +308,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(14, "useNativeViewConfigsInBridgelessMode");
markFlagAsAccessed(15, "useNativeViewConfigsInBridgelessMode");
flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode();
useNativeViewConfigsInBridgelessMode_ = flagValue;
@@ -308,7 +326,7 @@ bool ReactNativeFeatureFlagsAccessor::useStateAlignmentMechanism() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(15, "useStateAlignmentMechanism");
markFlagAsAccessed(16, "useStateAlignmentMechanism");
flagValue = currentProvider_->useStateAlignmentMechanism();
useStateAlignmentMechanism_ = 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<<de8d2a8416c3ad914df01b765c8df907>>
* @generated SignedSource<<2ac50c51832dfbcebfc8875c02738727>>
*/
/**
@@ -44,6 +44,7 @@ class ReactNativeFeatureFlagsAccessor {
bool forceBatchingMountItemsOnAndroid();
bool inspectorEnableCxxInspectorPackagerConnection();
bool inspectorEnableModernCDPRegistry();
bool preventDoubleTextMeasure();
bool useModernRuntimeScheduler();
bool useNativeViewConfigsInBridgelessMode();
bool useStateAlignmentMechanism();
@@ -57,7 +58,7 @@ class ReactNativeFeatureFlagsAccessor {
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
bool wasOverridden_;
std::array<std::atomic<const char*>, 16> accessedFeatureFlags_;
std::array<std::atomic<const char*>, 17> accessedFeatureFlags_;
std::atomic<std::optional<bool>> commonTestFlag_;
std::atomic<std::optional<bool>> batchRenderingUpdatesInEventLoop_;
@@ -72,6 +73,7 @@ class ReactNativeFeatureFlagsAccessor {
std::atomic<std::optional<bool>> forceBatchingMountItemsOnAndroid_;
std::atomic<std::optional<bool>> inspectorEnableCxxInspectorPackagerConnection_;
std::atomic<std::optional<bool>> inspectorEnableModernCDPRegistry_;
std::atomic<std::optional<bool>> preventDoubleTextMeasure_;
std::atomic<std::optional<bool>> useModernRuntimeScheduler_;
std::atomic<std::optional<bool>> useNativeViewConfigsInBridgelessMode_;
std::atomic<std::optional<bool>> useStateAlignmentMechanism_;
@@ -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<<008818e7f2d8b9e295704cccc33758e7>>
* @generated SignedSource<<b638a8d33a5b542e93b1bfa784ff500d>>
*/
/**
@@ -79,6 +79,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
return false;
}
bool preventDoubleTextMeasure() override {
return false;
}
bool useModernRuntimeScheduler() 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<<270291da73cb3e4eddc7bd5cdc9a9470>>
* @generated SignedSource<<2ffedc198d97593ae8c5d5300aa640a2>>
*/
/**
@@ -38,6 +38,7 @@ class ReactNativeFeatureFlagsProvider {
virtual bool forceBatchingMountItemsOnAndroid() = 0;
virtual bool inspectorEnableCxxInspectorPackagerConnection() = 0;
virtual bool inspectorEnableModernCDPRegistry() = 0;
virtual bool preventDoubleTextMeasure() = 0;
virtual bool useModernRuntimeScheduler() = 0;
virtual bool useNativeViewConfigsInBridgelessMode() = 0;
virtual bool useStateAlignmentMechanism() = 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<<1abf19f9d1352105511e86c0f217de7a>>
* @generated SignedSource<<a24c9972eebff849abd9b0e7ef4ff525>>
*/
/**
@@ -102,6 +102,11 @@ bool NativeReactNativeFeatureFlags::inspectorEnableModernCDPRegistry(
return ReactNativeFeatureFlags::inspectorEnableModernCDPRegistry();
}
bool NativeReactNativeFeatureFlags::preventDoubleTextMeasure(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::preventDoubleTextMeasure();
}
bool NativeReactNativeFeatureFlags::useModernRuntimeScheduler(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::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<<7a2a41f80549322a7f4255d193dc0b7b>>
* @generated SignedSource<<92830e364788db00fd06d4e152ef7727>>
*/
/**
@@ -61,6 +61,8 @@ class NativeReactNativeFeatureFlags
bool inspectorEnableModernCDPRegistry(jsi::Runtime& runtime);
bool preventDoubleTextMeasure(jsi::Runtime& runtime);
bool useModernRuntimeScheduler(jsi::Runtime& runtime);
bool useNativeViewConfigsInBridgelessMode(jsi::Runtime& runtime);
@@ -10,6 +10,7 @@
#include <cmath>
#include <react/debug/react_native_assert.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/attributedstring/AttributedStringBox.h>
#include <react/renderer/components/view/ViewShadowNode.h>
#include <react/renderer/components/view/conversions.h>
@@ -166,10 +167,10 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) {
ensureUnsealed();
auto layoutMetrics = getLayoutMetrics();
auto availableSize = layoutMetrics.getContentFrame().size;
auto size = layoutMetrics.getContentFrame().size;
auto layoutConstraints = LayoutConstraints{
availableSize, availableSize, layoutMetrics.layoutDirection};
auto layoutConstraints =
LayoutConstraints{size, size, layoutMetrics.layoutDirection};
auto content =
getContentWithMeasuredAttachments(layoutContext, layoutConstraints);
@@ -177,15 +178,19 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) {
TextLayoutContext textLayoutContext{};
textLayoutContext.pointScaleFactor = layoutContext.pointScaleFactor;
auto measurement = textLayoutManager_->measure(
AttributedStringBox{content.attributedString},
content.paragraphAttributes,
textLayoutContext,
layoutConstraints);
auto measurement = TextMeasurement{};
if (!ReactNativeFeatureFlags::preventDoubleTextMeasure()) {
measurement = textLayoutManager_->measure(
AttributedStringBox{content.attributedString},
content.paragraphAttributes,
textLayoutContext,
layoutConstraints);
}
if (getConcreteProps().onTextLayout) {
auto linesMeasurements = textLayoutManager_->measureLines(
content.attributedString, content.paragraphAttributes, availableSize);
content.attributedString, content.paragraphAttributes, size);
getConcreteEventEmitter().onTextLayout(linesMeasurements);
}
@@ -194,6 +199,15 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) {
return;
}
if (ReactNativeFeatureFlags::preventDoubleTextMeasure()) {
// Only measure if attachments are not empty.
measurement = textLayoutManager_->measure(
AttributedStringBox{content.attributedString},
content.paragraphAttributes,
textLayoutContext,
layoutConstraints);
}
// Iterating on attachments, we clone shadow nodes and moving
// `paragraphShadowNode` that represents clones of `this` object.
auto paragraphShadowNode = static_cast<ParagraphShadowNode*>(this);
@@ -63,6 +63,7 @@ class ParagraphShadowNode final : public ConcreteViewShadowNode<
#pragma mark - LayoutableShadowNode
void layout(LayoutContext layoutContext) override;
Size measureContent(
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const override;
@@ -91,6 +91,11 @@ const definitions: FeatureFlagDefinitions = {
description:
'Flag determining if the modern CDP backend should be enabled. This flag is global and should not be changed across React Host lifetimes.',
},
preventDoubleTextMeasure: {
defaultValue: false,
description:
'When enabled, ParagraphShadowNode will no longer call measure twice.',
},
useModernRuntimeScheduler: {
defaultValue: false,
description:
@@ -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<<39fd96ed670e0acae857e7fce3377452>>
* @generated SignedSource<<0e0858557eb27f3eebfecae18ee13c0b>>
* @flow strict-local
*/
@@ -53,6 +53,7 @@ export type ReactNativeFeatureFlags = {
forceBatchingMountItemsOnAndroid: Getter<boolean>,
inspectorEnableCxxInspectorPackagerConnection: Getter<boolean>,
inspectorEnableModernCDPRegistry: Getter<boolean>,
preventDoubleTextMeasure: Getter<boolean>,
useModernRuntimeScheduler: Getter<boolean>,
useNativeViewConfigsInBridgelessMode: Getter<boolean>,
useStateAlignmentMechanism: Getter<boolean>,
@@ -150,6 +151,10 @@ export const inspectorEnableCxxInspectorPackagerConnection: Getter<boolean> = cr
* Flag determining if the modern CDP backend should be enabled. This flag is global and should not be changed across React Host lifetimes.
*/
export const inspectorEnableModernCDPRegistry: Getter<boolean> = createNativeFlagGetter('inspectorEnableModernCDPRegistry', false);
/**
* When enabled, ParagraphShadowNode will no longer call measure twice.
*/
export const preventDoubleTextMeasure: Getter<boolean> = createNativeFlagGetter('preventDoubleTextMeasure', false);
/**
* When enabled, it uses the modern fork of RuntimeScheduler that allows scheduling tasks with priorities from any thread.
*/
@@ -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<<99ddb55dadff7adfb02c105984d47cbf>>
* @generated SignedSource<<4ba909c3b360c6a4fc9c6ed5996b6a13>>
* @flow strict-local
*/
@@ -36,6 +36,7 @@ export interface Spec extends TurboModule {
+forceBatchingMountItemsOnAndroid?: () => boolean;
+inspectorEnableCxxInspectorPackagerConnection?: () => boolean;
+inspectorEnableModernCDPRegistry?: () => boolean;
+preventDoubleTextMeasure?: () => boolean;
+useModernRuntimeScheduler?: () => boolean;
+useNativeViewConfigsInBridgelessMode?: () => boolean;
+useStateAlignmentMechanism?: () => boolean;