Fabric: Text Measuring: TextMeasureCache the new, improved text measure cache
Summary: Special thanks for Joshua Gross for flagging this problem! This diff implements a custom evicting hash map designed specially to hold text measurement information. The key feature of this is custom equality checks and hashing functions. They are designed around the following principals: * Decorative text attributes (such as color, shadows, etc) has no effect on layout, therefore they should not be taking into account; * `minimum height`, `minimum width`, and `maximum height` don't affect the measurement; * the value of `layout metrics` are important only for `attachment` fragments. After I redo all tests, I will enable this for Android-specific TextLayoutManager in separate diff. Changelog: [Internal] Fabric-specific internal change. Reviewed By: sammy-SC Differential Revision: D18848583 fbshipit-source-id: 46c2fc445fbd1823afc5e7498e37de75381258b1
This commit is contained in:
Родитель
1a12919dea
Коммит
e08412d9a1
|
@ -19,15 +19,21 @@ APPLE_COMPILER_FLAGS = get_apple_compiler_flags()
|
||||||
rn_xplat_cxx_library(
|
rn_xplat_cxx_library(
|
||||||
name = "textlayoutmanager",
|
name = "textlayoutmanager",
|
||||||
srcs = glob(
|
srcs = glob(
|
||||||
[],
|
[
|
||||||
|
"*.cpp",
|
||||||
|
],
|
||||||
),
|
),
|
||||||
headers = subdir_glob(
|
headers = subdir_glob(
|
||||||
[],
|
[
|
||||||
|
("", "*.h"),
|
||||||
|
],
|
||||||
prefix = "",
|
prefix = "",
|
||||||
),
|
),
|
||||||
header_namespace = "",
|
header_namespace = "",
|
||||||
exported_headers = subdir_glob(
|
exported_headers = subdir_glob(
|
||||||
[],
|
[
|
||||||
|
("", "*.h"),
|
||||||
|
],
|
||||||
prefix = "react/textlayoutmanager",
|
prefix = "react/textlayoutmanager",
|
||||||
),
|
),
|
||||||
compiler_flags = [
|
compiler_flags = [
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TextMeasureCache.h"
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace react {} // namespace react
|
||||||
|
} // namespace facebook
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* 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 <react/attributedstring/AttributedString.h>
|
||||||
|
#include <react/attributedstring/ParagraphAttributes.h>
|
||||||
|
#include <react/core/LayoutConstraints.h>
|
||||||
|
#include <react/utils/FloatComparison.h>
|
||||||
|
#include <react/utils/SimpleThreadSafeCache.h>
|
||||||
|
|
||||||
|
namespace facebook {
|
||||||
|
namespace react {
|
||||||
|
|
||||||
|
// The Key type that is used for Text Measure Cache.
|
||||||
|
// The equivalence and hashing operations of this are defined to respect the
|
||||||
|
// nature of text measuring.
|
||||||
|
class TextMeasureCacheKey final {
|
||||||
|
public:
|
||||||
|
AttributedString attributedString{};
|
||||||
|
ParagraphAttributes paragraphAttributes{};
|
||||||
|
LayoutConstraints layoutConstraints{};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Maximum size of the Cache.
|
||||||
|
* The number was empirically chosen based on approximation of an average amount
|
||||||
|
* of meaningful measures per surface.
|
||||||
|
*/
|
||||||
|
constexpr auto kSimpleThreadSafeCacheSizeCap = size_t{256};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Thread-safe, evicting hash table designed to store text measurement
|
||||||
|
* information.
|
||||||
|
*/
|
||||||
|
using TextMeasureCache = SimpleThreadSafeCache<
|
||||||
|
TextMeasureCacheKey,
|
||||||
|
Size,
|
||||||
|
kSimpleThreadSafeCacheSizeCap>;
|
||||||
|
|
||||||
|
inline bool areTextAttributesEquivalentLayoutWise(
|
||||||
|
TextAttributes const &lhs,
|
||||||
|
TextAttributes const &rhs) {
|
||||||
|
// Here we check all attributes that affect layout metrics and don't check any
|
||||||
|
// attributes that affect only a decorative aspect of displayed text (like
|
||||||
|
// colors).
|
||||||
|
return std::tie(
|
||||||
|
lhs.fontFamily,
|
||||||
|
lhs.fontWeight,
|
||||||
|
lhs.fontStyle,
|
||||||
|
lhs.fontVariant,
|
||||||
|
lhs.allowFontScaling,
|
||||||
|
lhs.alignment) ==
|
||||||
|
std::tie(
|
||||||
|
rhs.fontFamily,
|
||||||
|
rhs.fontWeight,
|
||||||
|
rhs.fontStyle,
|
||||||
|
rhs.fontVariant,
|
||||||
|
rhs.allowFontScaling,
|
||||||
|
rhs.alignment) &&
|
||||||
|
floatEquality(lhs.fontSize, rhs.fontSize) &&
|
||||||
|
floatEquality(lhs.fontSizeMultiplier, rhs.fontSizeMultiplier) &&
|
||||||
|
floatEquality(lhs.letterSpacing, rhs.letterSpacing) &&
|
||||||
|
floatEquality(lhs.lineHeight, rhs.lineHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t textAttributesHashLayoutWise(
|
||||||
|
TextAttributes const &textAttributes) {
|
||||||
|
// Taking into account the same props as
|
||||||
|
// `areTextAttributesEquivalentLayoutWise` mentions.
|
||||||
|
return folly::hash::hash_combine(
|
||||||
|
0,
|
||||||
|
textAttributes.fontFamily,
|
||||||
|
textAttributes.fontSize,
|
||||||
|
textAttributes.fontSizeMultiplier,
|
||||||
|
textAttributes.fontWeight,
|
||||||
|
textAttributes.fontStyle,
|
||||||
|
textAttributes.fontVariant,
|
||||||
|
textAttributes.allowFontScaling,
|
||||||
|
textAttributes.letterSpacing,
|
||||||
|
textAttributes.lineHeight,
|
||||||
|
textAttributes.alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool areAttributedStringFragmentsEquivalentLayoutWise(
|
||||||
|
AttributedString::Fragment const &lhs,
|
||||||
|
AttributedString::Fragment const &rhs) {
|
||||||
|
return lhs.string == rhs.string &&
|
||||||
|
areTextAttributesEquivalentLayoutWise(
|
||||||
|
lhs.textAttributes, rhs.textAttributes) &&
|
||||||
|
// LayoutMetrics of an attachment fragment affects the size of a measured
|
||||||
|
// attributed string.
|
||||||
|
(!lhs.isAttachment() ||
|
||||||
|
(lhs.parentShadowView.layoutMetrics ==
|
||||||
|
rhs.parentShadowView.layoutMetrics));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t textAttributesHashLayoutWise(
|
||||||
|
AttributedString::Fragment const &fragment) {
|
||||||
|
// Here we are not taking `isAttachment` and `layoutMetrics` into account
|
||||||
|
// because they are logically interdependent and this can break an invariant
|
||||||
|
// between hash and equivalence functions (and cause cache misses).
|
||||||
|
return folly::hash::hash_combine(
|
||||||
|
0,
|
||||||
|
fragment.string,
|
||||||
|
textAttributesHashLayoutWise(fragment.textAttributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool areAttributedStringsEquivalentLayoutWise(
|
||||||
|
AttributedString const &lhs,
|
||||||
|
AttributedString const &rhs) {
|
||||||
|
auto &lhsFragment = lhs.getFragments();
|
||||||
|
auto &rhsFragment = rhs.getFragments();
|
||||||
|
|
||||||
|
if (lhsFragment.size() != rhsFragment.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto size = lhsFragment.size();
|
||||||
|
for (auto i = size_t{0}; i < size; i++) {
|
||||||
|
if (!areAttributedStringFragmentsEquivalentLayoutWise(
|
||||||
|
lhsFragment.at(i), rhsFragment.at(i))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t textAttributedStringHashLayoutWise(
|
||||||
|
AttributedString const &attributedString) {
|
||||||
|
auto seed = size_t{0};
|
||||||
|
|
||||||
|
for (auto const &fragment : attributedString.getFragments()) {
|
||||||
|
seed =
|
||||||
|
folly::hash::hash_combine(seed, textAttributesHashLayoutWise(fragment));
|
||||||
|
}
|
||||||
|
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator==(
|
||||||
|
TextMeasureCacheKey const &lhs,
|
||||||
|
TextMeasureCacheKey const &rhs) {
|
||||||
|
return areAttributedStringsEquivalentLayoutWise(
|
||||||
|
lhs.attributedString, rhs.attributedString) &&
|
||||||
|
lhs.paragraphAttributes == rhs.paragraphAttributes &&
|
||||||
|
lhs.layoutConstraints.maximumSize.width ==
|
||||||
|
rhs.layoutConstraints.maximumSize.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool operator!=(
|
||||||
|
TextMeasureCacheKey const &lhs,
|
||||||
|
TextMeasureCacheKey const &rhs) {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace react
|
||||||
|
} // namespace facebook
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct hash<facebook::react::TextMeasureCacheKey> {
|
||||||
|
size_t operator()(facebook::react::TextMeasureCacheKey const &key) const {
|
||||||
|
return folly::hash::hash_combine(
|
||||||
|
0,
|
||||||
|
textAttributedStringHashLayoutWise(key.attributedString),
|
||||||
|
key.paragraphAttributes,
|
||||||
|
key.layoutConstraints.maximumSize.width);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std
|
|
@ -12,8 +12,8 @@
|
||||||
#include <react/attributedstring/AttributedStringBox.h>
|
#include <react/attributedstring/AttributedStringBox.h>
|
||||||
#include <react/attributedstring/ParagraphAttributes.h>
|
#include <react/attributedstring/ParagraphAttributes.h>
|
||||||
#include <react/core/LayoutConstraints.h>
|
#include <react/core/LayoutConstraints.h>
|
||||||
|
#include <react/textlayoutmanager/TextMeasureCache.h>
|
||||||
#include <react/utils/ContextContainer.h>
|
#include <react/utils/ContextContainer.h>
|
||||||
#include <react/utils/SimpleThreadSafeCache.h>
|
|
||||||
|
|
||||||
namespace facebook {
|
namespace facebook {
|
||||||
namespace react {
|
namespace react {
|
||||||
|
@ -45,12 +45,8 @@ class TextLayoutManager {
|
||||||
void *getNativeTextLayoutManager() const;
|
void *getNativeTextLayoutManager() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using MeasureCacheKey =
|
|
||||||
std::tuple<AttributedString, ParagraphAttributes, LayoutConstraints>;
|
|
||||||
using MeasureCache = SimpleThreadSafeCache<MeasureCacheKey, Size, 256>;
|
|
||||||
|
|
||||||
void *self_;
|
void *self_;
|
||||||
MeasureCache measureCache_{};
|
TextMeasureCache measureCache_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace react
|
} // namespace react
|
||||||
|
|
Загрузка…
Ссылка в новой задаче