Skip to content

Commit d41e95f

Browse files
sammy-SCfacebook-github-bot
authored andcommitted
Cache NSTextStorage
Summary: changelog: [internal] This diff introduces a mechanism to cache NSTextStorage on Paragraph's state. The old renderer already has caching for NSTextStorage: https://github.com/facebook/react-native/blob/main/Libraries/Text/Text/RCTTextShadowView.m#L21 Fabric will store `NSTextStorage` inside `ParagraphLayoutManager` which is owned by state. There is one notable change which is not gated: Paragraph's state strongly owns `TextLayoutManager`, not `ParagraphShadowNode` like previously. This shouldn't change anything Reviewed By: mdvacca Differential Revision: D43692171 fbshipit-source-id: efe6077222a30ab6d89abd9916534b9a96e745d4
1 parent a5bc6f0 commit d41e95f

File tree

20 files changed

+278
-89
lines changed

20 files changed

+278
-89
lines changed

React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,8 @@ - (void)drawRect:(CGRect)rect
116116
return;
117117
}
118118

119-
auto textLayoutManager = _state->getData().layoutManager.lock();
120-
121-
if (!textLayoutManager) {
122-
return;
123-
}
119+
auto textLayoutManager = _state->getData().paragraphLayoutManager.getTextLayoutManager();
120+
auto nsTextStorage = _state->getData().paragraphLayoutManager.getHostTextStorage();
124121

125122
RCTTextLayoutManager *nativeTextLayoutManager =
126123
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
@@ -129,7 +126,8 @@ - (void)drawRect:(CGRect)rect
129126

130127
[nativeTextLayoutManager drawAttributedString:_state->getData().attributedString
131128
paragraphAttributes:_paragraphAttributes
132-
frame:frame];
129+
frame:frame
130+
textStorage:unwrapManagedObject(nsTextStorage)];
133131
}
134132

135133
#pragma mark - Accessibility
@@ -161,18 +159,15 @@ - (NSArray *)accessibilityElements
161159
auto &data = _state->getData();
162160

163161
if (![_accessibilityProvider isUpToDate:data.attributedString]) {
164-
auto textLayoutManager = data.layoutManager.lock();
165-
if (textLayoutManager) {
166-
RCTTextLayoutManager *nativeTextLayoutManager =
167-
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
168-
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
169-
_accessibilityProvider =
170-
[[RCTParagraphComponentAccessibilityProvider alloc] initWithString:data.attributedString
171-
layoutManager:nativeTextLayoutManager
172-
paragraphAttributes:data.paragraphAttributes
173-
frame:frame
174-
view:self];
175-
}
162+
auto textLayoutManager = data.paragraphLayoutManager.getTextLayoutManager();
163+
RCTTextLayoutManager *nativeTextLayoutManager =
164+
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());
165+
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
166+
_accessibilityProvider = [[RCTParagraphComponentAccessibilityProvider alloc] initWithString:data.attributedString
167+
layoutManager:nativeTextLayoutManager
168+
paragraphAttributes:data.paragraphAttributes
169+
frame:frame
170+
view:self];
176171
}
177172

178173
return _accessibilityProvider.accessibilityElements;
@@ -191,11 +186,7 @@ - (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point
191186
return _eventEmitter;
192187
}
193188

194-
auto textLayoutManager = _state->getData().layoutManager.lock();
195-
196-
if (!textLayoutManager) {
197-
return _eventEmitter;
198-
}
189+
auto textLayoutManager = _state->getData().paragraphLayoutManager.getTextLayoutManager();
199190

200191
RCTTextLayoutManager *nativeTextLayoutManager =
201192
(RCTTextLayoutManager *)unwrapManagedObject(textLayoutManager->getNativeTextLayoutManager());

React/Fabric/RCTSurfacePresenter.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@ - (RCTScheduler *)_createScheduler
281281
CoreFeatures::useNativeState = true;
282282
}
283283

284+
if (reactNativeConfig && reactNativeConfig->getBool("react_fabric:enable_nstextstorage_caching")) {
285+
CoreFeatures::cacheNSTextStorage = true;
286+
}
287+
284288
auto componentRegistryFactory =
285289
[factory = wrapManagedObject(_mountingManager.componentViewRegistry.componentViewFactory)](
286290
EventDispatcher::Weak const &eventDispatcher, ContextContainer::Shared const &contextContainer) {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "ParagraphLayoutManager.h"
9+
#include <folly/Hash.h>
10+
#include <react/renderer/core/CoreFeatures.h>
11+
12+
namespace facebook::react {
13+
14+
TextMeasurement ParagraphLayoutManager::measure(
15+
AttributedString const &attributedString,
16+
ParagraphAttributes const &paragraphAttributes,
17+
LayoutConstraints layoutConstraints) const {
18+
if (CoreFeatures::cacheNSTextStorage) {
19+
size_t newHash = folly::hash::hash_combine(
20+
0,
21+
textAttributedStringHashLayoutWise(attributedString),
22+
paragraphAttributes);
23+
24+
if (!hostTextStorage_ || newHash != hash_) {
25+
hostTextStorage_ = textLayoutManager_->getHostTextStorage(
26+
attributedString, paragraphAttributes, layoutConstraints);
27+
hash_ = newHash;
28+
}
29+
}
30+
31+
return textLayoutManager_->measure(
32+
AttributedStringBox(attributedString),
33+
paragraphAttributes,
34+
layoutConstraints,
35+
hostTextStorage_);
36+
}
37+
38+
LinesMeasurements ParagraphLayoutManager::measureLines(
39+
AttributedString const &attributedString,
40+
ParagraphAttributes const &paragraphAttributes,
41+
Size size) const {
42+
return textLayoutManager_->measureLines(
43+
attributedString, paragraphAttributes, size);
44+
}
45+
46+
void ParagraphLayoutManager::setTextLayoutManager(
47+
std::shared_ptr<TextLayoutManager const> textLayoutManager) const {
48+
textLayoutManager_ = std::move(textLayoutManager);
49+
}
50+
51+
std::shared_ptr<TextLayoutManager const>
52+
ParagraphLayoutManager::getTextLayoutManager() const {
53+
return textLayoutManager_;
54+
}
55+
56+
std::shared_ptr<void> ParagraphLayoutManager::getHostTextStorage() const {
57+
return hostTextStorage_;
58+
}
59+
} // namespace facebook::react
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/renderer/attributedstring/AttributedString.h>
11+
#include <react/renderer/attributedstring/ParagraphAttributes.h>
12+
#include <react/renderer/core/LayoutConstraints.h>
13+
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
14+
15+
namespace facebook::react {
16+
17+
/*
18+
* Serves as a middle man between `ParagraphShadowNode` and `TextLayoutManager`.
19+
* On iOS, caches `NSTextStorage` for individual `ParagraphShadowNode` to make
20+
* sure only one `NSTextStorage` is created for every string. `NSTextStorage`
21+
* can be re created on native views layer but it is expensive. On Android, this
22+
* class does not cache anything.
23+
*/
24+
class ParagraphLayoutManager {
25+
public:
26+
TextMeasurement measure(
27+
AttributedString const &attributedString,
28+
ParagraphAttributes const &paragraphAttributes,
29+
LayoutConstraints layoutConstraints) const;
30+
31+
LinesMeasurements measureLines(
32+
AttributedString const &attributedString,
33+
ParagraphAttributes const &paragraphAttributes,
34+
Size size) const;
35+
36+
void setTextLayoutManager(
37+
std::shared_ptr<TextLayoutManager const> textLayoutManager) const;
38+
39+
/*
40+
* Returns an opaque pointer to platform-specific `TextLayoutManager`.
41+
* Is used on a native views layer to delegate text rendering to the manager.
42+
*/
43+
std::shared_ptr<TextLayoutManager const> getTextLayoutManager() const;
44+
45+
/*
46+
* Returns opaque shared_ptr holding `NSTextStorage`.
47+
* May be nullptr.
48+
* Is used on a native views layer to prevent `NSTextStorage` from being
49+
* created twice.
50+
*/
51+
std::shared_ptr<void> getHostTextStorage() const;
52+
53+
private:
54+
std::shared_ptr<TextLayoutManager const> mutable textLayoutManager_{};
55+
std::shared_ptr<void> mutable hostTextStorage_{};
56+
57+
size_t mutable hash_{};
58+
};
59+
} // namespace facebook::react

ReactCommon/react/renderer/components/text/ParagraphShadowNode.cpp

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,16 @@ Content ParagraphShadowNode::getContentWithMeasuredAttachments(
9595
void ParagraphShadowNode::setTextLayoutManager(
9696
std::shared_ptr<TextLayoutManager const> textLayoutManager) {
9797
ensureUnsealed();
98-
textLayoutManager_ = std::move(textLayoutManager);
98+
getStateData().paragraphLayoutManager.setTextLayoutManager(
99+
std::move(textLayoutManager));
99100
}
100101

101102
void ParagraphShadowNode::updateStateIfNeeded(Content const &content) {
102103
ensureUnsealed();
103104

104105
auto &state = getStateData();
105106

106-
react_native_assert(textLayoutManager_);
107+
react_native_assert(state.paragraphLayoutManager.getTextLayoutManager());
107108

108109
if (state.attributedString == content.attributedString) {
109110
return;
@@ -112,7 +113,7 @@ void ParagraphShadowNode::updateStateIfNeeded(Content const &content) {
112113
setStateData(ParagraphState{
113114
content.attributedString,
114115
content.paragraphAttributes,
115-
textLayoutManager_});
116+
state.paragraphLayoutManager});
116117
}
117118

118119
#pragma mark - LayoutableShadowNode
@@ -136,11 +137,9 @@ Size ParagraphShadowNode::measureContent(
136137
attributedString.appendFragment({string, textAttributes, {}});
137138
}
138139

139-
return textLayoutManager_
140-
->measure(
141-
AttributedStringBox{attributedString},
142-
content.paragraphAttributes,
143-
layoutConstraints)
140+
return getStateData()
141+
.paragraphLayoutManager
142+
.measure(attributedString, content.paragraphAttributes, layoutConstraints)
144143
.size;
145144
}
146145

@@ -157,13 +156,11 @@ void ParagraphShadowNode::layout(LayoutContext layoutContext) {
157156

158157
updateStateIfNeeded(content);
159158

160-
auto measurement = textLayoutManager_->measure(
161-
AttributedStringBox{content.attributedString},
162-
content.paragraphAttributes,
163-
layoutConstraints);
159+
auto measurement = getStateData().paragraphLayoutManager.measure(
160+
content.attributedString, content.paragraphAttributes, layoutConstraints);
164161

165162
if (getConcreteProps().onTextLayout) {
166-
auto linesMeasurements = textLayoutManager_->measureLines(
163+
auto linesMeasurements = getStateData().paragraphLayoutManager.measureLines(
167164
content.attributedString,
168165
content.paragraphAttributes,
169166
measurement.size);

ReactCommon/react/renderer/components/text/ParagraphShadowNode.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77

88
#pragma once
99

10+
#include <react/renderer/components/text/BaseTextShadowNode.h>
1011
#include <react/renderer/components/text/ParagraphEventEmitter.h>
12+
#include <react/renderer/components/text/ParagraphLayoutManager.h>
1113
#include <react/renderer/components/text/ParagraphProps.h>
1214
#include <react/renderer/components/text/ParagraphState.h>
13-
#include <react/renderer/components/text/TextShadowNode.h>
1415
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
15-
#include <react/renderer/core/ConcreteShadowNode.h>
1616
#include <react/renderer/core/LayoutContext.h>
1717
#include <react/renderer/core/ShadowNode.h>
18-
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
1918

2019
namespace facebook {
2120
namespace react {
@@ -96,8 +95,6 @@ class ParagraphShadowNode final : public ConcreteViewShadowNode<
9695
*/
9796
void updateStateIfNeeded(Content const &content);
9897

99-
std::shared_ptr<TextLayoutManager const> textLayoutManager_;
100-
10198
/*
10299
* Cached content of the subtree started from the node.
103100
*/

ReactCommon/react/renderer/components/text/ParagraphState.h

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,14 @@
1010
#include <react/debug/react_native_assert.h>
1111
#include <react/renderer/attributedstring/AttributedString.h>
1212
#include <react/renderer/attributedstring/ParagraphAttributes.h>
13-
#include <react/renderer/textlayoutmanager/TextLayoutManager.h>
13+
#include <react/renderer/components/text/ParagraphLayoutManager.h>
1414

1515
#ifdef ANDROID
1616
#include <folly/dynamic.h>
1717
#include <react/renderer/mapbuffer/MapBuffer.h>
1818
#endif
1919

20-
namespace facebook {
21-
namespace react {
20+
namespace facebook::react {
2221

2322
#ifdef ANDROID
2423
// constants for Text State serialization
@@ -33,8 +32,7 @@ constexpr static MapBuffer::Key TX_STATE_KEY_MOST_RECENT_EVENT_COUNT = 3;
3332
* State for <Paragraph> component.
3433
* Represents what to render and how to render.
3534
*/
36-
class ParagraphState final {
37-
public:
35+
struct ParagraphState {
3836
/*
3937
* All content of <Paragraph> component represented as an `AttributedString`.
4038
*/
@@ -47,22 +45,22 @@ class ParagraphState final {
4745
ParagraphAttributes paragraphAttributes;
4846

4947
/*
50-
* `TextLayoutManager` provides a connection to platform-specific
48+
* `ParagraphLayoutManager` provides a connection to platform-specific
5149
* text rendering infrastructure which is capable to render the
5250
* `AttributedString`.
5351
* This is not on every platform. This is not used on Android, but is
5452
* used on the iOS mounting layer.
5553
*/
56-
std::weak_ptr<TextLayoutManager const> layoutManager;
54+
ParagraphLayoutManager paragraphLayoutManager;
5755

5856
#ifdef ANDROID
5957
ParagraphState(
6058
AttributedString const &attributedString,
6159
ParagraphAttributes const &paragraphAttributes,
62-
std::weak_ptr<const TextLayoutManager> const &layoutManager)
60+
ParagraphLayoutManager const &paragraphLayoutManager)
6361
: attributedString(attributedString),
6462
paragraphAttributes(paragraphAttributes),
65-
layoutManager(layoutManager) {}
63+
paragraphLayoutManager(paragraphLayoutManager) {}
6664
ParagraphState() = default;
6765
ParagraphState(
6866
ParagraphState const &previousState,
@@ -74,5 +72,4 @@ class ParagraphState final {
7472
#endif
7573
};
7674

77-
} // namespace react
78-
} // namespace facebook
75+
} // namespace facebook::react

ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputShadowNode.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ Size AndroidTextInputShadowNode::measureContent(
197197
->measure(
198198
AttributedStringBox{attributedString},
199199
getConcreteProps().paragraphAttributes,
200-
layoutConstraints)
200+
layoutConstraints,
201+
nullptr)
201202
.size;
202203
}
203204

ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputShadowNode.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ Size TextInputShadowNode::measureContent(
110110
->measure(
111111
attributedStringBoxToMeasure(layoutContext),
112112
getConcreteProps().getEffectiveParagraphAttributes(),
113-
layoutConstraints)
113+
layoutConstraints,
114+
nullptr)
114115
.size;
115116
}
116117

ReactCommon/react/renderer/core/CoreFeatures.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ bool CoreFeatures::enablePropIteratorSetter = false;
1414
bool CoreFeatures::enableMapBuffer = false;
1515
bool CoreFeatures::blockPaintForUseLayoutEffect = false;
1616
bool CoreFeatures::useNativeState = false;
17+
bool CoreFeatures::cacheNSTextStorage = false;
1718

1819
} // namespace react
1920
} // namespace facebook

ReactCommon/react/renderer/core/CoreFeatures.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ class CoreFeatures {
3434
// Whether to use Hermes' NativeState instead of HostObject
3535
// in simple data passing scenarios with JS
3636
static bool useNativeState;
37+
38+
// Creating NSTextStorage is relatively expensive operation and we were
39+
// creating it twice. Once when measuring text and once when rendering it.
40+
// This flag caches it inside ParagraphState.
41+
static bool cacheNSTextStorage;
3742
};
3843

3944
} // namespace react

ReactCommon/react/renderer/textlayoutmanager/TextMeasureCache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#pragma once
99

10+
#include <folly/Hash.h>
1011
#include <react/renderer/attributedstring/AttributedString.h>
1112
#include <react/renderer/attributedstring/ParagraphAttributes.h>
1213
#include <react/renderer/core/LayoutConstraints.h>

0 commit comments

Comments
 (0)