Skip to content

Commit 79a5629

Browse files
hryhoriiK97meta-codesync[bot]
authored andcommitted
Use a static cast for View nodes when building ShadowView layout metrics (#59290)
Summary: `ShadowView`'s constructor reads the node's layout metrics through `layoutMetricsFromShadowNode`, which uses `dynamic_cast<const LayoutableShadowNode *>` to find out whether the node has layout. The differ builds a `ShadowView` for every child it visits when it slices a node's children, so this runs for every visited node on every commit, including the per-frame commits that update a ScrollView's content offset. **Current behavior.** Every call pays for a `dynamic_cast`, which walks the type's inheritance information at runtime; for a `View`, the node's type is `ConcreteViewShadowNode<...>`, several levels below `LayoutableShadowNode`. In the profiles below, `__dynamic_cast` takes 19–21% of the differ's time while scrolling. **With this change.** A node with the `ViewKind` trait takes a `static_cast`. `ShadowNodeTraits` documents `ViewKind` as "Inherits `ConcreteViewShadowNode<>` template", and `ConcreteViewShadowNode::BaseTraits()` is the only place that sets it; `ConcreteViewShadowNode` derives from `YogaLayoutableShadowNode`, so such a node is always a `LayoutableShadowNode` and the result is the same as the `dynamic_cast`'s. Other nodes (for example raw text) keep the checked cast. Core code already relies on the trait the same way: `PointerEventsProcessor`, `YogaLayoutableShadowNode` and `LayoutAnimationKeyFrameManager` check `ViewKind` and then `static_cast` the props to `ViewProps`. ## Changelog: [GENERAL] [CHANGED] - Building a `ShadowView` for a View node no longer uses a `dynamic_cast` to read its layout metrics Pull Request resolved: #59290 Test Plan: - `clang-format --dry-run --Werror` on the changed file passes. - `yarn fantom` (full suite, with the tester rebuilt from this change): 188 test suites and 3,288 tests pass (108 skipped by their configuration). - RNTester built with `xcodebuild` in Release and run on a physical iPhone 15 Pro (iOS 26.6.1). - **Measurement.** The two arms are the same binary with the change switched by a launch argument, in the order before, after, after, before. Interactions were driven by [Argent](https://github.com/software-mansion/argent) on the physical iPhone, under Instruments' Time Profiler. Values are main-thread sampled time per launch inside `calculateShadowViewMutations` (the differ), averaged over the 2 launches of each arm. iPhone 15 Pro, Release: | RNTester screen, 10 flings (Playground chat: 40) | differ before | this PR | change | `__dynamic_cast` in the differ, before → this PR | |---|--:|--:|--:|--:| | Playground, chat of 500 messages | 338 ms | 280 ms | −17% | 64 → 14 ms | | FlatList basic | 187 ms | 150 ms | −20% | 38 → 0 ms | | SectionList scrollable | 296 ms | 290 ms | −2% | 63 → 10 ms | SectionList is mostly bound by the JS thread and renders a different amount in each launch, so its differ time varied from 237 to 356 ms between launches of the same arm; the time in `__dynamic_cast` is the stable number there. The differ is 7–9% of the main thread's time on these screens. Mounting 1000 views five times took 24–31 ms of differ time per launch in both arms, too little for the profiler's 1 ms samples to show a difference. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: christophpurrer Differential Revision: D126687891 Pulled By: javache fbshipit-source-id: 83440573d6fdb644843f3961a8e8ceade7875624
1 parent c1d8139 commit 79a5629

15 files changed

Lines changed: 41 additions & 3 deletions

‎packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.h‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ class LayoutableShadowNode : public ShadowNode {
117117
*/
118118
LayoutMetrics getLayoutMetrics() const;
119119

120+
const LayoutableShadowNode *asLayoutable() const override
121+
{
122+
return this;
123+
}
124+
120125
/*
121126
* Returns a transform object that represents transformations that will/should
122127
* be applied on top of regular layout metrics by mounting layer.
@@ -170,7 +175,7 @@ class LayoutableShadowNode : public ShadowNode {
170175
#pragma mark - DebugStringConvertible
171176

172177
#if RN_DEBUG_STRING_CONVERTIBLE
173-
SharedDebugStringConvertibleList getDebugProps() const;
178+
SharedDebugStringConvertibleList getDebugProps() const override;
174179
#endif
175180

176181
LayoutMetrics layoutMetrics_;

‎packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
namespace facebook::react {
2828

2929
class ComponentDescriptor;
30+
class LayoutableShadowNode;
3031
struct ShadowNodeFragment;
3132
struct ShadowNodeWrapper;
3233

@@ -122,6 +123,15 @@ class ShadowNode : public Sealable, public DebugStringConvertible, public jsi::N
122123
*/
123124
virtual void completeClone(const ShadowNode & /* sourceShadowNode */, const ShadowNodeFragment & /* fragment */) {}
124125

126+
/**
127+
* Returns this node as a `LayoutableShadowNode`, or `nullptr` if it isn't
128+
* one. Cheaper than a `dynamic_cast`.
129+
*/
130+
virtual const LayoutableShadowNode *asLayoutable() const
131+
{
132+
return nullptr;
133+
}
134+
125135
#pragma mark - Getters
126136

127137
ComponentName getComponentName() const;

‎packages/react-native/ReactCommon/react/renderer/mounting/ShadowView.cpp‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
namespace facebook::react {
1414

1515
static LayoutMetrics layoutMetricsFromShadowNode(const ShadowNode& shadowNode) {
16-
auto layoutableShadowNode =
17-
dynamic_cast<const LayoutableShadowNode*>(&shadowNode);
16+
auto layoutableShadowNode = shadowNode.asLayoutable();
1817
return layoutableShadowNode != nullptr
1918
? layoutableShadowNode->getLayoutMetrics()
2019
: EmptyLayoutMetrics;

‎scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,6 +1719,7 @@ class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode
17191719
public virtual bool canBeTouchTarget() const;
17201720
public virtual bool canChildrenBeTouchTarget() const;
17211721
public virtual bool getIsLayoutClean() const = 0;
1722+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const override;
17221723
public virtual facebook::react::Float baseline(const facebook::react::LayoutContext& layoutContext, facebook::react::Size size) const;
17231724
public virtual facebook::react::Point getContentOriginOffset(bool includeTransform) const;
17241725
public virtual facebook::react::Size measure(const facebook::react::LayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const;
@@ -2250,6 +2251,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac
22502251
public std::shared_ptr<facebook::react::ShadowNode> cloneMultiple(const std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>& familiesToUpdate, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode, const facebook::react::ShadowNodeFragment& fragment)>& callback) const;
22512252
public std::shared_ptr<facebook::react::ShadowNode> cloneTree(const facebook::react::ShadowNodeFamily& shadowNodeFamily, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode)>& callback) const;
22522253
public using AncestorList = std::vector<std::pair<std::reference_wrapper<const facebook::react::ShadowNode>, int>>;
2254+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const;
22532255
public virtual void appendChild(const std::shared_ptr<const facebook::react::ShadowNode>& child);
22542256
public virtual void completeClone(const facebook::react::ShadowNode&, const facebook::react::ShadowNodeFragment&);
22552257
public virtual void replaceChild(const facebook::react::ShadowNode& oldChild, const std::shared_ptr<const facebook::react::ShadowNode>& newChild, size_t suggestedIndex = std::numeric_limits<size_t>::max());

‎scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2595,6 +2595,7 @@ class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode
25952595
public virtual bool canBeTouchTarget() const;
25962596
public virtual bool canChildrenBeTouchTarget() const;
25972597
public virtual bool getIsLayoutClean() const = 0;
2598+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const override;
25982599
public virtual facebook::react::Float baseline(const facebook::react::LayoutContext& layoutContext, facebook::react::Size size) const;
25992600
public virtual facebook::react::Point getContentOriginOffset(bool includeTransform) const;
26002601
public virtual facebook::react::Size measure(const facebook::react::LayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const;
@@ -3642,6 +3643,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac
36423643
public std::shared_ptr<facebook::react::ShadowNode> cloneMultiple(const std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>& familiesToUpdate, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode, const facebook::react::ShadowNodeFragment& fragment)>& callback) const;
36433644
public std::shared_ptr<facebook::react::ShadowNode> cloneTree(const facebook::react::ShadowNodeFamily& shadowNodeFamily, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode)>& callback) const;
36443645
public using AncestorList = std::vector<std::pair<std::reference_wrapper<const facebook::react::ShadowNode>, int>>;
3646+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const;
36453647
public virtual void appendChild(const std::shared_ptr<const facebook::react::ShadowNode>& child);
36463648
public virtual void completeClone(const facebook::react::ShadowNode&, const facebook::react::ShadowNodeFragment&);
36473649
public virtual void replaceChild(const facebook::react::ShadowNode& oldChild, const std::shared_ptr<const facebook::react::ShadowNode>& newChild, size_t suggestedIndex = std::numeric_limits<size_t>::max());

‎scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,6 +1718,7 @@ class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode
17181718
public virtual bool canBeTouchTarget() const;
17191719
public virtual bool canChildrenBeTouchTarget() const;
17201720
public virtual bool getIsLayoutClean() const = 0;
1721+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const override;
17211722
public virtual facebook::react::Float baseline(const facebook::react::LayoutContext& layoutContext, facebook::react::Size size) const;
17221723
public virtual facebook::react::Point getContentOriginOffset(bool includeTransform) const;
17231724
public virtual facebook::react::Size measure(const facebook::react::LayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const;
@@ -2243,6 +2244,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac
22432244
public std::shared_ptr<facebook::react::ShadowNode> cloneMultiple(const std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>& familiesToUpdate, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode, const facebook::react::ShadowNodeFragment& fragment)>& callback) const;
22442245
public std::shared_ptr<facebook::react::ShadowNode> cloneTree(const facebook::react::ShadowNodeFamily& shadowNodeFamily, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode)>& callback) const;
22452246
public using AncestorList = std::vector<std::pair<std::reference_wrapper<const facebook::react::ShadowNode>, int>>;
2247+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const;
22462248
public virtual void appendChild(const std::shared_ptr<const facebook::react::ShadowNode>& child);
22472249
public virtual void completeClone(const facebook::react::ShadowNode&, const facebook::react::ShadowNodeFragment&);
22482250
public virtual void replaceChild(const facebook::react::ShadowNode& oldChild, const std::shared_ptr<const facebook::react::ShadowNode>& newChild, size_t suggestedIndex = std::numeric_limits<size_t>::max());

‎scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,6 +1718,7 @@ class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode
17181718
public virtual bool canBeTouchTarget() const;
17191719
public virtual bool canChildrenBeTouchTarget() const;
17201720
public virtual bool getIsLayoutClean() const = 0;
1721+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const override;
17211722
public virtual facebook::react::Float baseline(const facebook::react::LayoutContext& layoutContext, facebook::react::Size size) const;
17221723
public virtual facebook::react::Point getContentOriginOffset(bool includeTransform) const;
17231724
public virtual facebook::react::Size measure(const facebook::react::LayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const;
@@ -2243,6 +2244,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac
22432244
public std::shared_ptr<facebook::react::ShadowNode> cloneMultiple(const std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>& familiesToUpdate, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode, const facebook::react::ShadowNodeFragment& fragment)>& callback) const;
22442245
public std::shared_ptr<facebook::react::ShadowNode> cloneTree(const facebook::react::ShadowNodeFamily& shadowNodeFamily, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode)>& callback) const;
22452246
public using AncestorList = std::vector<std::pair<std::reference_wrapper<const facebook::react::ShadowNode>, int>>;
2247+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const;
22462248
public virtual void appendChild(const std::shared_ptr<const facebook::react::ShadowNode>& child);
22472249
public virtual void completeClone(const facebook::react::ShadowNode&, const facebook::react::ShadowNodeFragment&);
22482250
public virtual void replaceChild(const facebook::react::ShadowNode& oldChild, const std::shared_ptr<const facebook::react::ShadowNode>& newChild, size_t suggestedIndex = std::numeric_limits<size_t>::max());

‎scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4002,6 +4002,7 @@ class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode
40024002
public virtual bool canBeTouchTarget() const;
40034003
public virtual bool canChildrenBeTouchTarget() const;
40044004
public virtual bool getIsLayoutClean() const = 0;
4005+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const override;
40054006
public virtual facebook::react::Float baseline(const facebook::react::LayoutContext& layoutContext, facebook::react::Size size) const;
40064007
public virtual facebook::react::Point getContentOriginOffset(bool includeTransform) const;
40074008
public virtual facebook::react::Size measure(const facebook::react::LayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const;
@@ -4510,6 +4511,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac
45104511
public std::shared_ptr<facebook::react::ShadowNode> cloneMultiple(const std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>& familiesToUpdate, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode, const facebook::react::ShadowNodeFragment& fragment)>& callback) const;
45114512
public std::shared_ptr<facebook::react::ShadowNode> cloneTree(const facebook::react::ShadowNodeFamily& shadowNodeFamily, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode)>& callback) const;
45124513
public using AncestorList = std::vector<std::pair<std::reference_wrapper<const facebook::react::ShadowNode>, int>>;
4514+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const;
45134515
public virtual void appendChild(const std::shared_ptr<const facebook::react::ShadowNode>& child);
45144516
public virtual void completeClone(const facebook::react::ShadowNode&, const facebook::react::ShadowNodeFragment&);
45154517
public virtual void replaceChild(const facebook::react::ShadowNode& oldChild, const std::shared_ptr<const facebook::react::ShadowNode>& newChild, size_t suggestedIndex = std::numeric_limits<size_t>::max());

‎scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4811,6 +4811,7 @@ class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode
48114811
public virtual bool canBeTouchTarget() const;
48124812
public virtual bool canChildrenBeTouchTarget() const;
48134813
public virtual bool getIsLayoutClean() const = 0;
4814+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const override;
48144815
public virtual facebook::react::Float baseline(const facebook::react::LayoutContext& layoutContext, facebook::react::Size size) const;
48154816
public virtual facebook::react::Point getContentOriginOffset(bool includeTransform) const;
48164817
public virtual facebook::react::Size measure(const facebook::react::LayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const;
@@ -5849,6 +5850,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac
58495850
public std::shared_ptr<facebook::react::ShadowNode> cloneMultiple(const std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>& familiesToUpdate, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode, const facebook::react::ShadowNodeFragment& fragment)>& callback) const;
58505851
public std::shared_ptr<facebook::react::ShadowNode> cloneTree(const facebook::react::ShadowNodeFamily& shadowNodeFamily, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode)>& callback) const;
58515852
public using AncestorList = std::vector<std::pair<std::reference_wrapper<const facebook::react::ShadowNode>, int>>;
5853+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const;
58525854
public virtual void appendChild(const std::shared_ptr<const facebook::react::ShadowNode>& child);
58535855
public virtual void completeClone(const facebook::react::ShadowNode&, const facebook::react::ShadowNodeFragment&);
58545856
public virtual void replaceChild(const facebook::react::ShadowNode& oldChild, const std::shared_ptr<const facebook::react::ShadowNode>& newChild, size_t suggestedIndex = std::numeric_limits<size_t>::max());

‎scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3993,6 +3993,7 @@ class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode
39933993
public virtual bool canBeTouchTarget() const;
39943994
public virtual bool canChildrenBeTouchTarget() const;
39953995
public virtual bool getIsLayoutClean() const = 0;
3996+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const override;
39963997
public virtual facebook::react::Float baseline(const facebook::react::LayoutContext& layoutContext, facebook::react::Size size) const;
39973998
public virtual facebook::react::Point getContentOriginOffset(bool includeTransform) const;
39983999
public virtual facebook::react::Size measure(const facebook::react::LayoutContext& layoutContext, const facebook::react::LayoutConstraints& layoutConstraints) const;
@@ -4495,6 +4496,7 @@ class facebook::react::ShadowNode : public facebook::react::Sealable, public fac
44954496
public std::shared_ptr<facebook::react::ShadowNode> cloneMultiple(const std::unordered_set<std::shared_ptr<const facebook::react::ShadowNodeFamily>>& familiesToUpdate, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode, const facebook::react::ShadowNodeFragment& fragment)>& callback) const;
44964497
public std::shared_ptr<facebook::react::ShadowNode> cloneTree(const facebook::react::ShadowNodeFamily& shadowNodeFamily, const std::function<std::shared_ptr<facebook::react::ShadowNode>(const facebook::react::ShadowNode& oldShadowNode)>& callback) const;
44974498
public using AncestorList = std::vector<std::pair<std::reference_wrapper<const facebook::react::ShadowNode>, int>>;
4499+
public virtual const facebook::react::LayoutableShadowNode* asLayoutable() const;
44984500
public virtual void appendChild(const std::shared_ptr<const facebook::react::ShadowNode>& child);
44994501
public virtual void completeClone(const facebook::react::ShadowNode&, const facebook::react::ShadowNodeFragment&);
45004502
public virtual void replaceChild(const facebook::react::ShadowNode& oldChild, const std::shared_ptr<const facebook::react::ShadowNode>& newChild, size_t suggestedIndex = std::numeric_limits<size_t>::max());

0 commit comments

Comments
 (0)