Fabric: Use LRU to cache results of ParagraphShadowNode::measure
Summary: Use a folly LRU implementation to cache results of ParagraphShadowNode::measure, which Yoga asks for repeatedly. Should have a substantial speed improvement on Android and iOS, or at least that's the dream. Reviewed By: mdvacca Differential Revision: D13795808 fbshipit-source-id: 5716af0fe0517a72716e48113c8125bb788735d7
This commit is contained in:
parent
af1f2728a0
commit
a9049442f7
|
@ -10,6 +10,7 @@
|
|||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <folly/Hash.h>
|
||||
#include <folly/Optional.h>
|
||||
#include <react/attributedstring/TextAttributes.h>
|
||||
#include <react/core/Sealable.h>
|
||||
|
@ -89,12 +90,12 @@ template <>
|
|||
struct hash<facebook::react::AttributedString::Fragment> {
|
||||
size_t operator()(
|
||||
const facebook::react::AttributedString::Fragment &fragment) const {
|
||||
return std::hash<decltype(fragment.string)>{}(fragment.string) +
|
||||
std::hash<decltype(fragment.textAttributes)>{}(
|
||||
fragment.textAttributes) +
|
||||
std::hash<decltype(fragment.shadowView)>{}(fragment.shadowView) +
|
||||
std::hash<decltype(fragment.parentShadowView)>{}(
|
||||
fragment.parentShadowView);
|
||||
auto seed = size_t{0};
|
||||
folly::hash::hash_combine(seed, fragment.string);
|
||||
folly::hash::hash_combine(seed, fragment.textAttributes);
|
||||
folly::hash::hash_combine(seed, fragment.shadowView);
|
||||
folly::hash::hash_combine(seed, fragment.parentShadowView);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -102,14 +103,13 @@ template <>
|
|||
struct hash<facebook::react::AttributedString> {
|
||||
size_t operator()(
|
||||
const facebook::react::AttributedString &attributedString) const {
|
||||
auto result = size_t{0};
|
||||
auto seed = size_t{0};
|
||||
|
||||
for (const auto &fragment : attributedString.getFragments()) {
|
||||
result +=
|
||||
std::hash<facebook::react::AttributedString::Fragment>{}(fragment);
|
||||
folly::hash::hash_combine(seed, fragment);
|
||||
}
|
||||
|
||||
return result;
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
|
|
@ -56,6 +56,7 @@ rn_xplat_cxx_library(
|
|||
"xplat//folly:memory",
|
||||
"xplat//folly:molly",
|
||||
"xplat//third-party/glog:glog",
|
||||
react_native_xplat_target("utils:utils"),
|
||||
react_native_xplat_target("fabric/debug:debug"),
|
||||
react_native_xplat_target("fabric/core:core"),
|
||||
react_native_xplat_target("fabric/graphics:graphics"),
|
||||
|
|
|
@ -10,10 +10,25 @@
|
|||
#include <react/attributedstring/conversions.h>
|
||||
#include <react/debug/debugStringConvertibleUtils.h>
|
||||
#include <react/graphics/conversions.h>
|
||||
#include <react/utils/FloatComparison.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
bool ParagraphAttributes::operator==(const ParagraphAttributes &rhs) const {
|
||||
return std::tie(maximumNumberOfLines, ellipsizeMode, adjustsFontSizeToFit) ==
|
||||
std::tie(
|
||||
rhs.maximumNumberOfLines,
|
||||
rhs.ellipsizeMode,
|
||||
rhs.adjustsFontSizeToFit) &&
|
||||
floatEquality(minimumFontSize, rhs.minimumFontSize) &&
|
||||
floatEquality(maximumFontSize, rhs.maximumFontSize);
|
||||
}
|
||||
|
||||
bool ParagraphAttributes::operator!=(const ParagraphAttributes &rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
#pragma mark - DebugStringConvertible
|
||||
|
||||
#if RN_DEBUG_STRING_CONVERTIBLE
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <limits>
|
||||
|
||||
#include <folly/Hash.h>
|
||||
#include <react/attributedstring/primitives.h>
|
||||
#include <react/debug/DebugStringConvertible.h>
|
||||
#include <react/graphics/Geometry.h>
|
||||
|
@ -53,6 +54,9 @@ class ParagraphAttributes : public DebugStringConvertible {
|
|||
Float minimumFontSize{std::numeric_limits<Float>::quiet_NaN()};
|
||||
Float maximumFontSize{std::numeric_limits<Float>::quiet_NaN()};
|
||||
|
||||
bool operator==(const ParagraphAttributes &) const;
|
||||
bool operator!=(const ParagraphAttributes &) const;
|
||||
|
||||
#pragma mark - DebugStringConvertible
|
||||
|
||||
#if RN_DEBUG_STRING_CONVERTIBLE
|
||||
|
@ -62,3 +66,22 @@ class ParagraphAttributes : public DebugStringConvertible {
|
|||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<facebook::react::ParagraphAttributes> {
|
||||
size_t operator()(
|
||||
const facebook::react::ParagraphAttributes &attributes) const {
|
||||
size_t seed = 0;
|
||||
folly::hash::hash_combine(seed, attributes.maximumNumberOfLines);
|
||||
folly::hash::hash_combine(seed, attributes.ellipsizeMode);
|
||||
folly::hash::hash_combine(seed, attributes.adjustsFontSizeToFit);
|
||||
folly::hash::hash_combine(
|
||||
seed, std::hash<float>{}(attributes.minimumFontSize));
|
||||
folly::hash::hash_combine(
|
||||
seed, std::hash<float>{}(attributes.maximumFontSize));
|
||||
return hash<int>()(seed);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <react/attributedstring/conversions.h>
|
||||
#include <react/core/conversions.h>
|
||||
#include <react/graphics/conversions.h>
|
||||
#include <react/utils/FloatComparison.h>
|
||||
#include <cmath>
|
||||
|
||||
#include <react/debug/debugStringConvertibleUtils.h>
|
||||
|
@ -101,16 +102,11 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
|
|||
return std::tie(
|
||||
foregroundColor,
|
||||
backgroundColor,
|
||||
opacity,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontSizeMultiplier,
|
||||
fontWeight,
|
||||
fontStyle,
|
||||
fontVariant,
|
||||
allowFontScaling,
|
||||
letterSpacing,
|
||||
lineHeight,
|
||||
alignment,
|
||||
baseWritingDirection,
|
||||
textDecorationColor,
|
||||
|
@ -118,23 +114,17 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
|
|||
textDecorationLineStyle,
|
||||
textDecorationLinePattern,
|
||||
textShadowOffset,
|
||||
textShadowRadius,
|
||||
textShadowColor,
|
||||
isHighlighted,
|
||||
layoutDirection) ==
|
||||
std::tie(
|
||||
rhs.foregroundColor,
|
||||
rhs.backgroundColor,
|
||||
rhs.opacity,
|
||||
rhs.fontFamily,
|
||||
rhs.fontSize,
|
||||
rhs.fontSizeMultiplier,
|
||||
rhs.fontWeight,
|
||||
rhs.fontStyle,
|
||||
rhs.fontVariant,
|
||||
rhs.allowFontScaling,
|
||||
rhs.letterSpacing,
|
||||
rhs.lineHeight,
|
||||
rhs.alignment,
|
||||
rhs.baseWritingDirection,
|
||||
rhs.textDecorationColor,
|
||||
|
@ -142,10 +132,15 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
|
|||
rhs.textDecorationLineStyle,
|
||||
rhs.textDecorationLinePattern,
|
||||
rhs.textShadowOffset,
|
||||
rhs.textShadowRadius,
|
||||
rhs.textShadowColor,
|
||||
rhs.isHighlighted,
|
||||
rhs.layoutDirection);
|
||||
rhs.layoutDirection) &&
|
||||
floatEquality(opacity, rhs.opacity) &&
|
||||
floatEquality(fontSize, rhs.fontSize) &&
|
||||
floatEquality(fontSizeMultiplier, rhs.fontSizeMultiplier) &&
|
||||
floatEquality(letterSpacing, rhs.letterSpacing) &&
|
||||
floatEquality(lineHeight, rhs.lineHeight) &&
|
||||
floatEquality(textShadowRadius, rhs.textShadowRadius);
|
||||
}
|
||||
|
||||
bool TextAttributes::operator!=(const TextAttributes &rhs) const {
|
||||
|
|
|
@ -56,11 +56,13 @@ rn_xplat_cxx_library(
|
|||
visibility = ["PUBLIC"],
|
||||
deps = [
|
||||
"xplat//fbsystrace:fbsystrace",
|
||||
"xplat//folly:evicting_cache_map",
|
||||
"xplat//folly:headers_only",
|
||||
"xplat//folly:memory",
|
||||
"xplat//folly:molly",
|
||||
"xplat//third-party/glog:glog",
|
||||
"xplat//yoga:yoga",
|
||||
react_native_xplat_target("utils:utils"),
|
||||
react_native_xplat_target("fabric/attributedstring:attributedstring"),
|
||||
react_native_xplat_target("fabric/core:core"),
|
||||
react_native_xplat_target("fabric/debug:debug"),
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <react/components/text/ParagraphShadowNode.h>
|
||||
#include "ParagraphMeasurementCache.h"
|
||||
#include "ParagraphShadowNode.h"
|
||||
|
||||
#include <folly/container/EvictingCacheMap.h>
|
||||
#include <react/core/ConcreteComponentDescriptor.h>
|
||||
#include <react/textlayoutmanager/TextLayoutManager.h>
|
||||
#include <react/uimanager/ContextContainer.h>
|
||||
|
@ -28,6 +31,10 @@ class ParagraphComponentDescriptor final
|
|||
// Every single `ParagraphShadowNode` will have a reference to
|
||||
// a shared `TextLayoutManager`.
|
||||
textLayoutManager_ = std::make_shared<TextLayoutManager>(contextContainer);
|
||||
// Every single `ParagraphShadowNode` will have a reference to
|
||||
// a shared `EvictingCacheMap`, a simple LRU cache for Paragraph
|
||||
// measurements.
|
||||
measureCache_ = std::make_shared<ParagraphMeasurementCache>();
|
||||
}
|
||||
|
||||
void adopt(UnsharedShadowNode shadowNode) const override {
|
||||
|
@ -41,6 +48,10 @@ class ParagraphComponentDescriptor final
|
|||
// and communicate text rendering metrics to mounting layer.
|
||||
paragraphShadowNode->setTextLayoutManager(textLayoutManager_);
|
||||
|
||||
// `ParagraphShadowNode` uses this to cache the results of text rendering
|
||||
// measurements.
|
||||
paragraphShadowNode->setMeasureCache(measureCache_);
|
||||
|
||||
// All `ParagraphShadowNode`s must have leaf Yoga nodes with properly
|
||||
// setup measure function.
|
||||
paragraphShadowNode->enableMeasurement();
|
||||
|
@ -48,6 +59,7 @@ class ParagraphComponentDescriptor final
|
|||
|
||||
private:
|
||||
SharedTextLayoutManager textLayoutManager_;
|
||||
SharedParagraphMeasurementCache measureCache_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 <folly/container/EvictingCacheMap.h>
|
||||
|
||||
#include <react/attributedstring/AttributedString.h>
|
||||
#include <react/attributedstring/ParagraphAttributes.h>
|
||||
#include <react/core/LayoutConstraints.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
using ParagraphMeasurementCacheKey =
|
||||
std::tuple<AttributedString, ParagraphAttributes, LayoutConstraints>;
|
||||
using ParagraphMeasurementCacheValue = Size;
|
||||
|
||||
using ParagraphMeasurementCacheHash = std::hash<ParagraphMeasurementCacheKey>;
|
||||
|
||||
class ParagraphMeasurementCache;
|
||||
using SharedParagraphMeasurementCache =
|
||||
std::shared_ptr<const ParagraphMeasurementCache>;
|
||||
|
||||
class ParagraphMeasurementCache {
|
||||
public:
|
||||
ParagraphMeasurementCache() : cache_{256} {}
|
||||
|
||||
bool exists(ParagraphMeasurementCacheKey &key) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return cache_.exists(key);
|
||||
}
|
||||
|
||||
ParagraphMeasurementCacheValue get(
|
||||
const ParagraphMeasurementCacheKey &key) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return cache_.get(key);
|
||||
}
|
||||
|
||||
void set(
|
||||
const ParagraphMeasurementCacheKey &key,
|
||||
ParagraphMeasurementCacheValue &value) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
cache_.set(key, value);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable folly::EvictingCacheMap<
|
||||
ParagraphMeasurementCacheKey,
|
||||
ParagraphMeasurementCacheValue>
|
||||
cache_;
|
||||
mutable std::mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -6,8 +6,8 @@
|
|||
*/
|
||||
|
||||
#include "ParagraphShadowNode.h"
|
||||
|
||||
#include "ParagraphLocalData.h"
|
||||
#include "ParagraphMeasurementCache.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
@ -32,6 +32,12 @@ void ParagraphShadowNode::setTextLayoutManager(
|
|||
textLayoutManager_ = textLayoutManager;
|
||||
}
|
||||
|
||||
void ParagraphShadowNode::setMeasureCache(
|
||||
SharedParagraphMeasurementCache cache) {
|
||||
ensureUnsealed();
|
||||
measureCache_ = cache;
|
||||
}
|
||||
|
||||
void ParagraphShadowNode::updateLocalDataIfNeeded() {
|
||||
ensureUnsealed();
|
||||
|
||||
|
@ -52,10 +58,23 @@ void ParagraphShadowNode::updateLocalDataIfNeeded() {
|
|||
#pragma mark - LayoutableShadowNode
|
||||
|
||||
Size ParagraphShadowNode::measure(LayoutConstraints layoutConstraints) const {
|
||||
return textLayoutManager_->measure(
|
||||
getAttributedString(),
|
||||
getProps()->paragraphAttributes,
|
||||
layoutConstraints);
|
||||
AttributedString attributedString = getAttributedString();
|
||||
const ParagraphAttributes attributes = getProps()->paragraphAttributes;
|
||||
|
||||
// Cache results of this function so we don't need to call measure()
|
||||
// repeatedly
|
||||
ParagraphMeasurementCacheKey hashValue =
|
||||
std::make_tuple(attributedString, attributes, layoutConstraints);
|
||||
if (measureCache_->exists(hashValue)) {
|
||||
return measureCache_->get(hashValue);
|
||||
}
|
||||
|
||||
Size measuredSize = textLayoutManager_->measure(
|
||||
attributedString, getProps()->paragraphAttributes, layoutConstraints);
|
||||
|
||||
measureCache_->set(hashValue, measuredSize);
|
||||
|
||||
return measuredSize;
|
||||
}
|
||||
|
||||
void ParagraphShadowNode::layout(LayoutContext layoutContext) {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <folly/Optional.h>
|
||||
#include <react/components/text/ParagraphMeasurementCache.h>
|
||||
#include <react/components/text/ParagraphProps.h>
|
||||
#include <react/components/text/TextShadowNode.h>
|
||||
#include <react/components/view/ConcreteViewShadowNode.h>
|
||||
|
@ -48,6 +49,13 @@ class ParagraphShadowNode : public ConcreteViewShadowNode<
|
|||
*/
|
||||
void setTextLayoutManager(SharedTextLayoutManager textLayoutManager);
|
||||
|
||||
/*
|
||||
* Associates a shared LRU cache with the node.
|
||||
* `ParagraphShadowNode` uses this to cache the results of
|
||||
* text rendering measurements.
|
||||
*/
|
||||
void setMeasureCache(SharedParagraphMeasurementCache cache);
|
||||
|
||||
#pragma mark - LayoutableShadowNode
|
||||
|
||||
void layout(LayoutContext layoutContext) override;
|
||||
|
@ -61,6 +69,7 @@ class ParagraphShadowNode : public ConcreteViewShadowNode<
|
|||
void updateLocalDataIfNeeded();
|
||||
|
||||
SharedTextLayoutManager textLayoutManager_;
|
||||
SharedParagraphMeasurementCache measureCache_;
|
||||
|
||||
/*
|
||||
* Cached attributed string that represents the content of the subtree started
|
||||
|
|
|
@ -51,6 +51,7 @@ rn_xplat_cxx_library(
|
|||
"xplat//folly:memory",
|
||||
"xplat//folly:molly",
|
||||
"xplat//third-party/glog:glog",
|
||||
react_native_xplat_target("utils:utils"),
|
||||
react_native_xplat_target("fabric/debug:debug"),
|
||||
react_native_xplat_target("fabric/events:events"),
|
||||
react_native_xplat_target("fabric/graphics:graphics"),
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <folly/Hash.h>
|
||||
#include <react/core/LayoutPrimitives.h>
|
||||
#include <react/graphics/Geometry.h>
|
||||
|
||||
|
@ -22,5 +23,27 @@ struct LayoutConstraints {
|
|||
LayoutDirection layoutDirection{LayoutDirection::Undefined};
|
||||
};
|
||||
|
||||
inline bool operator==(
|
||||
const LayoutConstraints &lhs,
|
||||
const LayoutConstraints &rhs) {
|
||||
return std::tie(lhs.minimumSize, lhs.maximumSize, lhs.layoutDirection) ==
|
||||
std::tie(rhs.minimumSize, rhs.maximumSize, rhs.layoutDirection);
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<facebook::react::LayoutConstraints> {
|
||||
size_t operator()(const facebook::react::LayoutConstraints &v) const {
|
||||
size_t seed = 0;
|
||||
folly::hash::hash_combine(
|
||||
seed, std::hash<facebook::react::Size>()(v.minimumSize));
|
||||
folly::hash::hash_combine(
|
||||
seed, std::hash<facebook::react::Size>()(v.maximumSize));
|
||||
folly::hash::hash_combine(seed, v.layoutDirection);
|
||||
return hash<int>()(seed);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
|
|
@ -34,7 +34,7 @@ struct ShadowView final {
|
|||
|
||||
ComponentName componentName = "";
|
||||
ComponentHandle componentHandle = 0;
|
||||
Tag tag = -1;
|
||||
Tag tag = -1; // Tag does not change during the lifetime of a shadow view.
|
||||
SharedProps props = {};
|
||||
SharedEventEmitter eventEmitter = {};
|
||||
LayoutMetrics layoutMetrics = EmptyLayoutMetrics;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
load("@fbsource//tools/build_defs:glob_defs.bzl", "subdir_glob")
|
||||
load("//tools/build_defs/oss:rn_defs.bzl", "get_apple_compiler_flags", "rn_xplat_cxx_library")
|
||||
|
||||
CXX_LIBRARY_COMPILER_FLAGS = [
|
||||
"-std=c++14",
|
||||
"-Wall",
|
||||
]
|
||||
|
||||
rn_xplat_cxx_library(
|
||||
name = "utils",
|
||||
header_namespace = "",
|
||||
exported_headers = subdir_glob(
|
||||
[
|
||||
("", "FloatComparison.h"),
|
||||
],
|
||||
prefix = "react/utils",
|
||||
),
|
||||
compiler_flags = CXX_LIBRARY_COMPILER_FLAGS + [
|
||||
"-fexceptions",
|
||||
"-frtti",
|
||||
],
|
||||
fbobjc_compiler_flags = get_apple_compiler_flags(),
|
||||
force_static = True,
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 {
|
||||
namespace react {
|
||||
|
||||
inline bool floatEquality (float a, float b, float epsilon = 0.005f) {
|
||||
return (std::isnan(a) && std::isnan(b)) || (!std::isnan(a) && !std::isnan(b) && fabs(a - b) < epsilon);
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
Loading…
Reference in New Issue