Skip to content

Use a virtual accessor instead of a dynamic_cast when building ShadowView layout metrics - #59290

Closed
hryhoriiK97 wants to merge 2 commits into
react:mainfrom
hryhoriiK97:differ-viewkind-static-cast
Closed

hryhoriiK97 wants to merge 2 commits into
react:mainfrom
hryhoriiK97:differ-viewkind-static-cast

Conversation

@hryhoriiK97

@hryhoriiK97 hryhoriiK97 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

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 Time Profiler traces of scrolling, __dynamic_cast took 19–21% of the differ's time.

With this change. ShadowNode gets a virtual asLayoutable() that returns nullptr, and LayoutableShadowNode overrides it to return this. layoutMetricsFromShadowNode calls it instead of the dynamic_cast, so every node, not only Views, gets its layout metrics without a runtime type check, and the compiler guarantees the result. LayoutableShadowNode::getDebugProps is marked override, since clang requires it once the class has another override.

Changelog:

[GENERAL] [CHANGED] - Building a ShadowView no longer uses a dynamic_cast to read a node's layout metrics

Test Plan:

  • The pinned clang-format passes on the changed files. The C++ API snapshots in scripts/cxx-api/api-snapshots list the new method.
  • yarn fantom (full suite, with the tester rebuilt from this change): 3,289 tests pass (110 skipped by their configuration). In that run the tester crashed (SIGSEGV) in two other files, AnimatedBackend-itest and StaleEventHandlersFromInterruptedRender-itest; neither crashed in 30 runs each on their own, and the same kind of crash happens locally without this change.
  • Measurement. RNTester built with xcodebuild in Release and run on a physical iPhone 15 Pro (iOS 26.6.1). 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 on the physical iPhone. Timed: the diff phase of each mount transaction from TransactionTelemetry, on the main thread.
RNTester Playground before this PR change
A FlatList of 500 multi-line messages, 20 flings down and 20 up per launch (~8,700 transactions): diff time per launch 1,140 ms 1,006 ms −11.8%
Same, diff time per 1–2-mutation transaction (the per-frame scroll updates) 111.0 µs 98.3 µs −11.4%
1000 labels mounted and unmounted, 10 times per arm: diff time per mount or unmount (median) 2.61 ms 2.47 ms −5.4%

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 21, 2026
@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Oct 21, 2026
@javache

javache commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

There's callers like https://github.com/software-mansion/react-native-svg/blob/main/common/cpp/react/renderer/components/rnsvg/RNSVGConcreteShadowNode.h#L31 which set this trait, while not being a ConcreteViewShadowNode. They happen to inherit from LayoutableShadowNode, but there's no strict enforcement here.

`ViewKind` is only a convention: a component can set the trait without
inheriting `ConcreteViewShadowNode`. Instead of relying on it, add a virtual
`ShadowNode::asLayoutable()` that `LayoutableShadowNode` overrides, and use it
in `layoutMetricsFromShadowNode` in place of the `dynamic_cast` for every node.

Mark `LayoutableShadowNode::getDebugProps` as `override`, which clang requires
once the class has another `override`, and add the new method to the C++ API
snapshots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hryhoriiK97 hryhoriiK97 changed the title Use a static cast for View nodes when building ShadowView layout metrics Use a virtual accessor instead of a dynamic_cast when building ShadowView layout metrics Oct 21, 2026
@hryhoriiK97

hryhoriiK97 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

@javache you're right, ViewKind doesn't guarantee this. I replaced it with a virtual asLayoutable() method on ShadowNode, so the compiler enforces it now and there's no cast at all. I re-measured and updated the numbers in the description.

@meta-codesync

meta-codesync Bot commented Oct 21, 2026

Copy link
Copy Markdown

@javache has imported this pull request. If you are a Meta employee, you can view this in D126687891.

@meta-codesync meta-codesync Bot closed this in 79a5629 Oct 21, 2026
@meta-codesync meta-codesync Bot added the Merged This PR has been merged. label Oct 21, 2026
@meta-codesync

meta-codesync Bot commented Oct 21, 2026

Copy link
Copy Markdown

@javache merged this pull request in 79a5629.

@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @hryhoriiK97 in 79a5629

When will my fix make it into a release? | How to file a pick request?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants