Skip to content

Commit 333d0f0

Browse files
j-piaseckimeta-codesync[bot]
authored andcommitted
Add SurfaceHandlerCommitHook responsible for preserving layout constraints (#54836)
Summary: Pull Request resolved: #54836 Changelog: [General][Added] Added a `SurfaceHandlerCommitHook` to preserve layout constraints during commits from different threads Since layout constraints and layout context are being kept in the props of the `RootShadowNode`, it was possible for the JS revision merge to lose an update if it was being commited concurrently. `SurfaceHandlerCommitHook` fixes this by ensuring that the root node always uses the latest context and constraints. Differential Revision: D88839901
1 parent 418cd8c commit 333d0f0

File tree

5 files changed

+237
-93
lines changed

5 files changed

+237
-93
lines changed

packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,18 @@ void Scheduler::registerSurface(
224224
const SurfaceHandler& surfaceHandler) const noexcept {
225225
surfaceHandler.setContextContainer(getContextContainer());
226226
surfaceHandler.setUIManager(uiManager_.get());
227+
228+
if (ReactNativeFeatureFlags::enableFabricCommitBranching()) {
229+
surfaceHandler.registerCommitHook(uiManager_.get());
230+
}
227231
}
228232

229233
void Scheduler::unregisterSurface(
230234
const SurfaceHandler& surfaceHandler) const noexcept {
235+
if (ReactNativeFeatureFlags::enableFabricCommitBranching()) {
236+
surfaceHandler.unregisterCommitHook(uiManager_.get());
237+
}
238+
231239
surfaceHandler.setUIManager(nullptr);
232240
}
233241

packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.cpp

Lines changed: 19 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include <react/featureflags/ReactNativeFeatureFlags.h>
1313
#include <react/renderer/uimanager/UIManager.h>
1414

15+
#include "SurfaceHandlerCommitHook.h"
16+
1517
namespace facebook::react {
1618

1719
using Status = SurfaceHandler::Status;
@@ -21,13 +23,15 @@ SurfaceHandler::SurfaceHandler(
2123
SurfaceId surfaceId) noexcept {
2224
parameters_.moduleName = moduleName;
2325
parameters_.surfaceId = surfaceId;
26+
commitHook_ = std::make_shared<SurfaceHandlerCommitHook>(surfaceId);
2427
}
2528

2629
#pragma mark - Surface Life-Cycle Management
2730

2831
void SurfaceHandler::setContextContainer(
2932
std::shared_ptr<const ContextContainer> contextContainer) const noexcept {
30-
parameters_.contextContainer = std::move(contextContainer);
33+
parameters_.contextContainer = contextContainer;
34+
commitHook_->setContextContainer(contextContainer);
3135
}
3236

3337
Status SurfaceHandler::getStatus() const noexcept {
@@ -142,6 +146,7 @@ SurfaceId SurfaceHandler::getSurfaceId() const noexcept {
142146
void SurfaceHandler::setSurfaceId(SurfaceId surfaceId) const noexcept {
143147
std::unique_lock lock(parametersMutex_);
144148
parameters_.surfaceId = surfaceId;
149+
commitHook_->setSurfaceId(surfaceId);
145150
}
146151

147152
std::string SurfaceHandler::getModuleName() const noexcept {
@@ -213,78 +218,6 @@ Size SurfaceHandler::measure(
213218
return rootShadowNode->getLayoutMetrics().frame.size;
214219
}
215220

216-
std::shared_ptr<const ShadowNode> SurfaceHandler::dirtyMeasurableNodesRecursive(
217-
std::shared_ptr<const ShadowNode> node) const {
218-
const auto nodeHasChildren = !node->getChildren().empty();
219-
const auto isMeasurableYogaNode =
220-
node->getTraits().check(ShadowNodeTraits::Trait::MeasurableYogaNode);
221-
222-
// Node is not measurable and has no children, its layout will not be affected
223-
if (!nodeHasChildren && !isMeasurableYogaNode) {
224-
return nullptr;
225-
}
226-
227-
ShadowNode::SharedListOfShared newChildren =
228-
ShadowNodeFragment::childrenPlaceholder();
229-
230-
if (nodeHasChildren) {
231-
std::shared_ptr<std::vector<std::shared_ptr<const ShadowNode>>>
232-
newChildrenMutable = nullptr;
233-
for (size_t i = 0; i < node->getChildren().size(); i++) {
234-
const auto& child = node->getChildren()[i];
235-
236-
if (const auto& layoutableNode =
237-
std::dynamic_pointer_cast<const YogaLayoutableShadowNode>(
238-
child)) {
239-
auto newChild = dirtyMeasurableNodesRecursive(layoutableNode);
240-
241-
if (newChild != nullptr) {
242-
if (newChildrenMutable == nullptr) {
243-
newChildrenMutable = std::make_shared<
244-
std::vector<std::shared_ptr<const ShadowNode>>>(
245-
node->getChildren());
246-
newChildren = newChildrenMutable;
247-
}
248-
249-
(*newChildrenMutable)[i] = newChild;
250-
}
251-
}
252-
}
253-
254-
// Node is not measurable and its children were not dirtied, its layout will
255-
// not be affected
256-
if (!isMeasurableYogaNode && newChildrenMutable == nullptr) {
257-
return nullptr;
258-
}
259-
}
260-
261-
const auto newNode = node->getComponentDescriptor().cloneShadowNode(
262-
*node,
263-
{
264-
.children = newChildren,
265-
// Preserve the original state of the node
266-
.state = node->getState(),
267-
});
268-
269-
if (isMeasurableYogaNode) {
270-
std::static_pointer_cast<YogaLayoutableShadowNode>(newNode)->dirtyLayout();
271-
}
272-
273-
return newNode;
274-
}
275-
276-
void SurfaceHandler::dirtyMeasurableNodes(ShadowNode& root) const {
277-
for (const auto& child : root.getChildren()) {
278-
if (const auto& layoutableNode =
279-
std::dynamic_pointer_cast<const YogaLayoutableShadowNode>(child)) {
280-
const auto newChild = dirtyMeasurableNodesRecursive(layoutableNode);
281-
if (newChild != nullptr) {
282-
root.replaceChild(*child, newChild);
283-
}
284-
}
285-
}
286-
}
287-
288221
void SurfaceHandler::constraintLayout(
289222
const LayoutConstraints& layoutConstraints,
290223
const LayoutContext& layoutContext) const {
@@ -308,26 +241,16 @@ void SurfaceHandler::constraintLayout(
308241
return;
309242
}
310243

311-
PropsParserContext propsParserContext{
312-
parameters_.surfaceId, *parameters_.contextContainer};
244+
commitHook_->setLayoutConstraints(layoutConstraints, layoutContext);
313245

314246
react_native_assert(
315247
link_.shadowTree && "`link_.shadowTree` must not be null.");
316248
link_.shadowTree->commit(
317249
[&](const RootShadowNode& oldRootShadowNode) {
318-
auto newRoot = oldRootShadowNode.clone(
319-
propsParserContext, layoutConstraints, layoutContext);
320-
321-
// Dirty all measurable nodes when the fontSizeMultiplier changes to
322-
// trigger re-measurement.
323-
if (ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout() &&
324-
layoutContext.fontSizeMultiplier !=
325-
oldRootShadowNode.getConcreteProps()
326-
.layoutContext.fontSizeMultiplier) {
327-
dirtyMeasurableNodes(*newRoot);
328-
}
329-
330-
return newRoot;
250+
// Commit hook will update the layout constraints and context on the
251+
// root node
252+
return std::make_shared<RootShadowNode>(
253+
oldRootShadowNode, ShadowNodeFragment{});
331254
},
332255
{/* default commit options */});
333256
}
@@ -394,6 +317,14 @@ void SurfaceHandler::setUIManager(const UIManager* uiManager) const noexcept {
394317
uiManager != nullptr ? Status::Registered : Status::Unregistered;
395318
}
396319

320+
void SurfaceHandler::registerCommitHook(UIManager* uiManager) const noexcept {
321+
uiManager->registerCommitHook(*commitHook_);
322+
}
323+
324+
void SurfaceHandler::unregisterCommitHook(UIManager* uiManager) const noexcept {
325+
uiManager->unregisterCommitHook(*commitHook_);
326+
}
327+
397328
SurfaceHandler::~SurfaceHandler() noexcept {
398329
react_native_assert(
399330
link_.status == Status::Unregistered &&

packages/react-native/ReactCommon/react/renderer/scheduler/SurfaceHandler.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Scheduler;
2222
class ShadowTree;
2323
class MountingCoordinator;
2424
class UIManager;
25+
class SurfaceHandlerCommitHook;
2526

2627
/*
2728
* Represents a running React Native surface and provides control over it.
@@ -148,14 +149,12 @@ class SurfaceHandler {
148149
* Must be called by `Scheduler` during registration process.
149150
*/
150151
void setUIManager(const UIManager *uiManager) const noexcept;
152+
void registerCommitHook(UIManager *uiManager) const noexcept;
153+
void unregisterCommitHook(UIManager *uiManager) const noexcept;
151154

152155
void applyDisplayMode(DisplayMode displayMode) const;
153156

154-
/*
155-
* An utility for dirtying all measurable shadow nodes present in the tree.
156-
*/
157-
void dirtyMeasurableNodes(ShadowNode &root) const;
158-
std::shared_ptr<const ShadowNode> dirtyMeasurableNodesRecursive(std::shared_ptr<const ShadowNode> node) const;
157+
std::shared_ptr<SurfaceHandlerCommitHook> commitHook_;
159158

160159
#pragma mark - Link & Parameters
161160

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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 "SurfaceHandlerCommitHook.h"
9+
10+
namespace facebook::react {
11+
12+
SurfaceHandlerCommitHook::SurfaceHandlerCommitHook(SurfaceId surfaceId)
13+
: surfaceId_(surfaceId) {}
14+
15+
RootShadowNode::Unshared SurfaceHandlerCommitHook::shadowTreeWillCommit(
16+
const ShadowTree& shadowTree,
17+
const RootShadowNode::Shared& /*oldRootShadowNode*/,
18+
const RootShadowNode::Unshared& newRootShadowNode,
19+
const ShadowTreeCommitOptions& /*commitOptions*/) noexcept {
20+
// Commit for another surface
21+
if (surfaceId_ != shadowTree.getSurfaceId()) {
22+
return newRootShadowNode;
23+
}
24+
25+
LayoutConstraints layoutConstraints;
26+
LayoutContext layoutContext;
27+
28+
{
29+
std::shared_lock lock(mutex_);
30+
31+
// Layout constraints were not set
32+
if (!layoutConstraints_.has_value() || !layoutContext_.has_value()) {
33+
return newRootShadowNode;
34+
}
35+
36+
layoutConstraints = layoutConstraints_.value();
37+
layoutContext = layoutContext_.value();
38+
}
39+
40+
// Don't clone the root node if the layout constraints and layout context
41+
// are not changed
42+
if (newRootShadowNode->getConcreteProps().layoutConstraints ==
43+
layoutConstraints &&
44+
newRootShadowNode->getConcreteProps().layoutContext == layoutContext) {
45+
return newRootShadowNode;
46+
}
47+
48+
PropsParserContext propsParserContext{surfaceId_, *contextContainer_};
49+
50+
auto clonedRoot = newRootShadowNode->clone(
51+
propsParserContext, layoutConstraints, layoutContext);
52+
53+
// Dirty all measurable nodes when the fontSizeMultiplier changes to
54+
// trigger re-measurement.
55+
if (ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout() &&
56+
layoutContext.fontSizeMultiplier !=
57+
newRootShadowNode->getConcreteProps()
58+
.layoutContext.fontSizeMultiplier) {
59+
dirtyMeasurableNodes(*clonedRoot);
60+
}
61+
62+
return clonedRoot;
63+
}
64+
65+
std::shared_ptr<const ShadowNode>
66+
SurfaceHandlerCommitHook::dirtyMeasurableNodesRecursive(
67+
std::shared_ptr<const ShadowNode> node) const {
68+
const auto nodeHasChildren = !node->getChildren().empty();
69+
const auto isMeasurableYogaNode =
70+
node->getTraits().check(ShadowNodeTraits::Trait::MeasurableYogaNode);
71+
72+
// Node is not measurable and has no children, its layout will not be affected
73+
if (!nodeHasChildren && !isMeasurableYogaNode) {
74+
return nullptr;
75+
}
76+
77+
ShadowNode::SharedListOfShared newChildren =
78+
ShadowNodeFragment::childrenPlaceholder();
79+
80+
if (nodeHasChildren) {
81+
std::shared_ptr<std::vector<std::shared_ptr<const ShadowNode>>>
82+
newChildrenMutable = nullptr;
83+
for (size_t i = 0; i < node->getChildren().size(); i++) {
84+
const auto& child = node->getChildren()[i];
85+
86+
if (const auto& layoutableNode =
87+
std::dynamic_pointer_cast<const YogaLayoutableShadowNode>(
88+
child)) {
89+
auto newChild = dirtyMeasurableNodesRecursive(layoutableNode);
90+
91+
if (newChild != nullptr) {
92+
if (newChildrenMutable == nullptr) {
93+
newChildrenMutable = std::make_shared<
94+
std::vector<std::shared_ptr<const ShadowNode>>>(
95+
node->getChildren());
96+
newChildren = newChildrenMutable;
97+
}
98+
99+
(*newChildrenMutable)[i] = newChild;
100+
}
101+
}
102+
}
103+
104+
// Node is not measurable and its children were not dirtied, its layout will
105+
// not be affected
106+
if (!isMeasurableYogaNode && newChildrenMutable == nullptr) {
107+
return nullptr;
108+
}
109+
}
110+
111+
const auto newNode = node->getComponentDescriptor().cloneShadowNode(
112+
*node,
113+
{
114+
.children = newChildren,
115+
// Preserve the original state of the node
116+
.state = node->getState(),
117+
});
118+
119+
if (isMeasurableYogaNode) {
120+
std::static_pointer_cast<YogaLayoutableShadowNode>(newNode)->dirtyLayout();
121+
}
122+
123+
return newNode;
124+
}
125+
126+
void SurfaceHandlerCommitHook::dirtyMeasurableNodes(ShadowNode& root) const {
127+
for (const auto& child : root.getChildren()) {
128+
if (const auto& layoutableNode =
129+
std::dynamic_pointer_cast<const YogaLayoutableShadowNode>(child)) {
130+
const auto newChild = dirtyMeasurableNodesRecursive(layoutableNode);
131+
if (newChild != nullptr) {
132+
root.replaceChild(*child, newChild);
133+
}
134+
}
135+
}
136+
}
137+
138+
void SurfaceHandlerCommitHook::setContextContainer(
139+
std::shared_ptr<const ContextContainer> contextContainer) {
140+
contextContainer_ = std::move(contextContainer);
141+
}
142+
143+
void SurfaceHandlerCommitHook::setLayoutConstraints(
144+
const LayoutConstraints& layoutConstraints,
145+
const LayoutContext& layoutContext) {
146+
std::unique_lock lock(mutex_);
147+
layoutConstraints_ = layoutConstraints;
148+
layoutContext_ = layoutContext;
149+
}
150+
151+
void SurfaceHandlerCommitHook::setSurfaceId(SurfaceId surfaceId) {
152+
surfaceId_ = surfaceId;
153+
}
154+
155+
} // namespace facebook::react

0 commit comments

Comments
 (0)