mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Combine iOS and Android optimisations (#37317)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37317 changelog: [internal] This diff does three things: 1. Combines Android's mobile config `react_fabric:enable_text_measure_cache` with iOS mobile config `react_fabric:enable_nstextstorage_caching`. We will get into why later. 2. Fixes cache `ParagraphLayoutManager::cachedTextMeasurement_` invalidation logic for iOS and Android. 3. Fixes cache invalidation logic for `ParagraphLayoutManager::hostTextStorage_`. Initially, Android's text measure cache (D44221170) and iOS's NSTextStorage (D43692171) were design as two separate optimisations. But they overlap and NSTextStorage actually needs some parts of text measure cache to work correctly. That's why I decided to merge them together. Previously, `ParagraphLayoutManager::cachedTextMeasurement_` was only invalidated if maximum width for the node changed. But node can change in different ways, for example input string changes or attributes change. This diff accounts for that by computing a hash for AttributedString+ParagraphAttributes to check if anything has changed. Previously, `ParagraphLayoutManager::hostTextStorage_` was not correctly invalidated if maximum width changed. To my surprise, this happens less frequently than one expects. bypass-github-export-checks Reviewed By: mdvacca Differential Revision: D45637797 fbshipit-source-id: 1840b51cdb1423de0560a35fd17c24a71ad115e1
This commit is contained in:
committed by
Facebook GitHub Bot
parent
a941d067f5
commit
93ce46113c
@@ -281,10 +281,6 @@ static BackgroundExecutor RCTGetBackgroundExecutor()
|
||||
CoreFeatures::useNativeState = true;
|
||||
}
|
||||
|
||||
if (reactNativeConfig && reactNativeConfig->getBool("react_fabric:enable_nstextstorage_caching")) {
|
||||
CoreFeatures::cacheNSTextStorage = true;
|
||||
}
|
||||
|
||||
if (reactNativeConfig && reactNativeConfig->getBool("react_fabric:cancel_image_downloads_on_recycle")) {
|
||||
CoreFeatures::cancelImageDownloadsOnRecycle = true;
|
||||
}
|
||||
|
||||
-3
@@ -97,9 +97,6 @@ public class ReactFeatureFlags {
|
||||
/** Feature Flag to enable the pending event queue in fabric before mounting views */
|
||||
public static boolean enableFabricPendingEventQueue = false;
|
||||
|
||||
/** Feature Flag to enable caching mechanism of text measurement at shadow node level */
|
||||
public static boolean enableTextMeasureCachePerShadowNode = false;
|
||||
|
||||
/**
|
||||
* Feature flag that controls how turbo modules are exposed to JS
|
||||
*
|
||||
|
||||
@@ -421,8 +421,6 @@ void Binding::installFabricUIManager(
|
||||
"CalculateTransformedFramesEnabled",
|
||||
getFeatureFlagValue("calculateTransformedFramesEnabled"));
|
||||
|
||||
CoreFeatures::cacheLastTextMeasurement =
|
||||
getFeatureFlagValue("enableTextMeasureCachePerShadowNode");
|
||||
CoreFeatures::enablePropIteratorSetter =
|
||||
getFeatureFlagValue("enableCppPropsIteratorSetter");
|
||||
CoreFeatures::useNativeState = getFeatureFlagValue("useNativeState");
|
||||
|
||||
+45
-33
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "ParagraphLayoutManager.h"
|
||||
#include <folly/Hash.h>
|
||||
#include <glog/logging.h>
|
||||
#include <react/renderer/core/CoreFeatures.h>
|
||||
|
||||
namespace facebook::react {
|
||||
@@ -15,40 +16,18 @@ TextMeasurement ParagraphLayoutManager::measure(
|
||||
AttributedString const &attributedString,
|
||||
ParagraphAttributes const ¶graphAttributes,
|
||||
LayoutConstraints layoutConstraints) const {
|
||||
bool cacheLastTextMeasurement = CoreFeatures::cacheLastTextMeasurement;
|
||||
if (cacheLastTextMeasurement &&
|
||||
(layoutConstraints.maximumSize.width == availableWidth_ ||
|
||||
layoutConstraints.maximumSize.width ==
|
||||
cachedTextMeasurement_.size.width)) {
|
||||
/* Yoga has requested measurement for this size before. Let's use cached
|
||||
* value. `TextLayoutManager` might not have cached this because it could be
|
||||
* using different width to generate cache key. This happens because Yoga
|
||||
* switches between available width and exact width but since we already
|
||||
* know exact width, it is wasteful to calculate it again.
|
||||
*/
|
||||
return cachedTextMeasurement_;
|
||||
}
|
||||
if (CoreFeatures::cacheNSTextStorage) {
|
||||
size_t newHash = folly::hash::hash_combine(
|
||||
0,
|
||||
textAttributedStringHashLayoutWise(attributedString),
|
||||
paragraphAttributes);
|
||||
if (CoreFeatures::cacheLastTextMeasurement) {
|
||||
bool shouldMeasure = shoudMeasureString(
|
||||
attributedString, paragraphAttributes, layoutConstraints);
|
||||
|
||||
if (!hostTextStorage_ || newHash != hash_) {
|
||||
hostTextStorage_ = textLayoutManager_->getHostTextStorage(
|
||||
attributedString, paragraphAttributes, layoutConstraints);
|
||||
hash_ = newHash;
|
||||
if (shouldMeasure) {
|
||||
cachedTextMeasurement_ = textLayoutManager_->measure(
|
||||
AttributedStringBox(attributedString),
|
||||
paragraphAttributes,
|
||||
layoutConstraints,
|
||||
hostTextStorage_);
|
||||
lastAvailableWidth_ = layoutConstraints.maximumSize.width;
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheLastTextMeasurement) {
|
||||
cachedTextMeasurement_ = textLayoutManager_->measure(
|
||||
AttributedStringBox(attributedString),
|
||||
paragraphAttributes,
|
||||
layoutConstraints,
|
||||
hostTextStorage_);
|
||||
|
||||
availableWidth_ = layoutConstraints.maximumSize.width;
|
||||
|
||||
return cachedTextMeasurement_;
|
||||
} else {
|
||||
@@ -56,10 +35,43 @@ TextMeasurement ParagraphLayoutManager::measure(
|
||||
AttributedStringBox(attributedString),
|
||||
paragraphAttributes,
|
||||
layoutConstraints,
|
||||
hostTextStorage_);
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool ParagraphLayoutManager::shoudMeasureString(
|
||||
AttributedString const &attributedString,
|
||||
ParagraphAttributes const ¶graphAttributes,
|
||||
LayoutConstraints layoutConstraints) const {
|
||||
size_t newHash = folly::hash::hash_combine(
|
||||
0,
|
||||
textAttributedStringHashLayoutWise(attributedString),
|
||||
paragraphAttributes);
|
||||
|
||||
if (newHash != paragraphInputHash_) {
|
||||
// AttributedString or ParagraphAttributes have changed.
|
||||
// Must create new host text storage and trigger measure.
|
||||
hostTextStorage_ = textLayoutManager_->getHostTextStorage(
|
||||
attributedString, paragraphAttributes, layoutConstraints);
|
||||
paragraphInputHash_ = newHash;
|
||||
return true; // Must measure again.
|
||||
}
|
||||
|
||||
bool hasMaximumSizeChanged =
|
||||
layoutConstraints.maximumSize.width != lastAvailableWidth_;
|
||||
Float threshold = 0.01;
|
||||
bool doesMaximumSizeMatchLastMeasurement =
|
||||
std::abs(
|
||||
layoutConstraints.maximumSize.width -
|
||||
cachedTextMeasurement_.size.width) < threshold;
|
||||
if (hasMaximumSizeChanged && !doesMaximumSizeMatchLastMeasurement) {
|
||||
hostTextStorage_ = textLayoutManager_->getHostTextStorage(
|
||||
attributedString, paragraphAttributes, layoutConstraints);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
LinesMeasurements ParagraphLayoutManager::measureLines(
|
||||
AttributedString const &attributedString,
|
||||
ParagraphAttributes const ¶graphAttributes,
|
||||
|
||||
+25
-4
@@ -52,20 +52,41 @@ class ParagraphLayoutManager {
|
||||
|
||||
private:
|
||||
std::shared_ptr<TextLayoutManager const> mutable textLayoutManager_{};
|
||||
|
||||
/*
|
||||
* Stores opaque pointer to `NSTextStorage` on iOS. nullptr on Android.
|
||||
* TODO: In the future, we may want to cache Android's text storage.
|
||||
*/
|
||||
std::shared_ptr<void> mutable hostTextStorage_{};
|
||||
|
||||
/*
|
||||
* Hash of AttributedString and ParagraphAttributes last used to
|
||||
* measure. Result of that measure is stored in cachedTextMeasurement_.
|
||||
* The available width defined for the measurement is stored in
|
||||
* lastAvailableWidth_.
|
||||
*/
|
||||
size_t mutable paragraphInputHash_{};
|
||||
|
||||
/* The width Yoga set as maximum width.
|
||||
* Yoga sometimes calls measure twice with two
|
||||
* different maximum width. One if available space.
|
||||
* Yoga calls measure twice with two
|
||||
* different maximum width. One of available space.
|
||||
* The other one is exact space needed for the string.
|
||||
* This happens when node is dirtied but its size is not affected.
|
||||
* To deal with this inefficiency, we cache `TextMeasurement` for each
|
||||
* `ParagraphShadowNode`. If Yoga tries to re-measure with available width
|
||||
* or exact width, we provide it with the cached value.
|
||||
*/
|
||||
Float mutable availableWidth_{};
|
||||
Float mutable lastAvailableWidth_{};
|
||||
TextMeasurement mutable cachedTextMeasurement_{};
|
||||
|
||||
size_t mutable hash_{};
|
||||
/*
|
||||
* Checks whether the inputs into text measurement meaningfully affect
|
||||
* text measurement result. Returns true if inputs have changed and measure is
|
||||
* needed.
|
||||
*/
|
||||
bool shoudMeasureString(
|
||||
AttributedString const &attributedString,
|
||||
ParagraphAttributes const ¶graphAttributes,
|
||||
LayoutConstraints layoutConstraints) const;
|
||||
};
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -13,7 +13,6 @@ bool CoreFeatures::enablePropIteratorSetter = false;
|
||||
bool CoreFeatures::enableMapBuffer = false;
|
||||
bool CoreFeatures::blockPaintForUseLayoutEffect = false;
|
||||
bool CoreFeatures::useNativeState = false;
|
||||
bool CoreFeatures::cacheNSTextStorage = false;
|
||||
bool CoreFeatures::cacheLastTextMeasurement = false;
|
||||
bool CoreFeatures::cancelImageDownloadsOnRecycle = false;
|
||||
|
||||
|
||||
@@ -34,14 +34,10 @@ class CoreFeatures {
|
||||
// in simple data passing scenarios with JS
|
||||
static bool useNativeState;
|
||||
|
||||
// Creating NSTextStorage is relatively expensive operation and we were
|
||||
// creating it twice. Once when measuring text and once when rendering it.
|
||||
// This flag caches it inside ParagraphState.
|
||||
static bool cacheNSTextStorage;
|
||||
|
||||
// Yoga might measure multiple times the same Text with the same constraints
|
||||
// This flag enables a caching mechanism to avoid subsequents measurements
|
||||
// of the same Text with the same constrainst.
|
||||
// On iOS, we also cache NSTextStorage.
|
||||
static bool cacheLastTextMeasurement;
|
||||
|
||||
// Fabric was not cancelling image downloads when <ImageView /> was removed
|
||||
|
||||
@@ -131,6 +131,9 @@ Scheduler::Scheduler(
|
||||
CoreFeatures::blockPaintForUseLayoutEffect = reactNativeConfig_->getBool(
|
||||
"react_fabric:block_paint_for_use_layout_effect");
|
||||
|
||||
CoreFeatures::cacheLastTextMeasurement =
|
||||
reactNativeConfig_->getBool("react_fabric:enable_text_measure_cache");
|
||||
|
||||
if (animationDelegate != nullptr) {
|
||||
animationDelegate->setComponentDescriptorRegistry(
|
||||
componentDescriptorRegistry_);
|
||||
|
||||
Reference in New Issue
Block a user