Add direct-event fast path to EventTarget dispatch (rnIsDirect)#57448
Open
rubennorte wants to merge 1 commit into
Open
Add direct-event fast path to EventTarget dispatch (rnIsDirect)#57448rubennorte wants to merge 1 commit into
rubennorte wants to merge 1 commit into
Conversation
Summary: Direct events (those that neither bubble nor capture, such as `onLayout`) were still dispatched through the full W3C event-path algorithm when using the EventTarget-based dispatcher: the path was built by walking every ancestor up to the root, and the capture phase then iterated over all of them, even though a direct event only ever fires on its target. This made direct-event dispatch scale with tree depth (O(depth)). This introduces a "direct" event mode on `Event`: - A new RN-specific `rnIsDirect` option on `EventInit` (with a matching `rnIsDirect` getter). A direct event has only an `AT_TARGET` phase and an event path containing just the target node. - Construction validation: an event cannot be both `rnIsDirect` and `bubbles`; the constructor throws a `TypeError` for that combination. - When `rnIsDirect` is set, the event path is restricted to the target, so dispatch skips the ancestor walk and capture-phase traversal (O(1)). The renderer opts native direct events into this fast path (behind a runtime gate) by tagging them with `rnIsDirect` at the dispatch site. Bubbling events that merely skip bubbling (e.g. `onPointerEnter`) still have a capture phase and are intentionally not treated as direct. This is gated and defaults off, so there is no behavior change by default. Benchmark results (median dispatch latency for the new `onLayout` direct-event benchmarks; all use the same stable-tree setup so depth is directly comparable): | onLayout dispatch | EventTarget off (baseline) | EventTarget on, direct off | EventTarget on, direct on | | --- | --- | --- | --- | | flat (1 handler) | 12.7us | 20.7us | 18.2us | | nested 10 deep | 12.7us | 33.4us | 18.1us | | nested 50 deep | 12.7us | 82.2us | 18.2us | Without the fast path, EventTarget dispatch of a direct event scales with tree depth (20.7us -> 33.4us -> 82.2us). The fast path restores O(1): dispatch cost is flat across depth (~18us) and the nested-50-deep case drops from ~82.2us to ~18.2us (~4.5x faster), approaching the legacy baseline (~12.7us). The EventTarget-off numbers are unchanged by the direct-events flag, confirming the flag is correctly gated. Changelog: [Internal] Differential Revision: D110759162
|
@rubennorte has exported this pull request. If you are a Meta employee, you can view the originating Diff in D110759162. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Direct events (those that neither bubble nor capture, such as
onLayout) were still dispatched through the full W3C event-path algorithm when using the EventTarget-based dispatcher: the path was built by walking every ancestor up to the root, and the capture phase then iterated over all of them, even though a direct event only ever fires on its target. This made direct-event dispatch scale with tree depth (O(depth)).This introduces a "direct" event mode on
Event:rnIsDirectoption onEventInit(with a matchingrnIsDirectgetter). A direct event has only anAT_TARGETphase and an event path containing just the target node.rnIsDirectandbubbles; the constructor throws aTypeErrorfor that combination.rnIsDirectis set, the event path is restricted to the target, so dispatch skips the ancestor walk and capture-phase traversal (O(1)).The renderer opts native direct events into this fast path (behind a runtime gate) by tagging them with
rnIsDirectat the dispatch site. Bubbling events that merely skip bubbling (e.g.onPointerEnter) still have a capture phase and are intentionally not treated as direct.This is gated and defaults off, so there is no behavior change by default.
Benchmark results (median dispatch latency for the new
onLayoutdirect-event benchmarks; all use the same stable-tree setup so depth is directly comparable):Without the fast path, EventTarget dispatch of a direct event scales with tree depth (20.7us -> 33.4us -> 82.2us). The fast path restores O(1): dispatch cost is flat across depth (~18us) and the nested-50-deep case drops from ~82.2us to ~18.2us (~4.5x faster), approaching the legacy baseline (~12.7us). The EventTarget-off numbers are unchanged by the direct-events flag, confirming the flag is correctly gated.
Changelog: [Internal]
Differential Revision: D110759162