[perf] hit testing and scroll#5
Conversation
…cess InsertAt() and GetAt() in the composition visual container iterated from the head of the children collection linearly. Added a parallel vector cache for O(1) indexed access. Also changed anyHitTestHelper() to collect children into a local vector before reverse iteration, avoiding repeated O(n) GetAt() calls through the WinRT COM interface. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Snap point configuration (sort, deduplicate, create InteractionTracker objects) was running on every layout update. Added a cache to skip reconfiguration when the snap points are unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR introduces two performance optimisations for hit-testing and scroll: a Confidence Score: 5/5Safe to merge — all findings are P2 style/hardening suggestions with no blocking defects. Both changes are logically correct: the cache is consistently maintained through the only two mutation paths, and the snap-point guard uses exact equality on a sorted, deduplicated float vector computed from the same inputs. The two P2 notes (unchecked index assertion and complexity-comment accuracy) are hardening suggestions, not correctness issues. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
subgraph CompVisualImpl["CompVisualImpl (updated)"]
IA["InsertAt(visual, index)"]
RM["Remove(visual)"]
GA["GetAt(index)"]
Cache["m_childrenCache (std::vector)"]
WRT["WinRT ContainerVisual.Children()"]
IA -->|"InsertAtBottom / InsertAbove"| WRT
IA -->|"push_back / insert at index"| Cache
RM -->|"Children().Remove()"| WRT
RM -->|"find_if + erase"| Cache
GA -->|"operator[] — O(1)"| Cache
end
subgraph CompScrollerVisual["CompScrollerVisual (updated)"]
CSP["ConfigureSnapPoints()"]
Prev["m_previousSnapPositions"]
IT["InteractionTracker resting values"]
CSP -->|"sort + unique"| Sorted["sorted snapPositions"]
Sorted -->|"== m_previousSnapPositions?"| Prev
Prev -->|"unchanged → early return"| Skip["skip reconfiguration"]
Prev -->|"changed → update + continue"| IT
end
subgraph anyHitTestHelper["ComponentView::anyHitTestHelper (updated)"]
AH["anyHitTestHelper()"]
Vec["local std::vector (children copy)"]
Rev["reverse iterate → hitTest()"]
AH -->|"range-for O(n) copy"| Vec
Vec --> Rev
end
Reviews (1): Last reviewed commit: "Skip snap scroll reconfiguration when sn..." | Re-trigger Greptile |
| // Collect children into a local vector to avoid repeated O(n) IVector::GetAt calls | ||
| std::vector<winrt::Microsoft::ReactNative::ComponentView> children; | ||
| children.reserve(size); | ||
| for (auto const &child : m_children) { | ||
| children.push_back(child); | ||
| } |
There was a problem hiding this comment.
Misleading complexity comment and potential allocation overhead
The comment says "repeated O(n) IVector::GetAt calls", implying the original loop was O(n²). In practice, WinRT IVector<T>::GetAt on a vector-backed collection (the common case) is O(1), so the original loop was already O(n) total. The new code introduces a per-call heap allocation and n COM AddRef/Release operations on a hot hit-testing path. For shallow trees (the common case in React Native views) this could be slower. Consider profiling to confirm this is a net win before committing to the extra overhead.
| winrt::Microsoft::ReactNative::Composition::Experimental::IVisual GetAt(uint32_t index) noexcept { | ||
| auto containerChildren = m_visual.as<typename TTypeRedirects::ContainerVisual>().Children(); | ||
| auto it = containerChildren.First(); | ||
| for (uint32_t i = 0; i < index; i++) | ||
| it.MoveNext(); | ||
| return TTypeRedirects::CompositionContextHelper::CreateVisual(it.Current()); | ||
| return m_childrenCache[index]; | ||
| } |
There was a problem hiding this comment.
Unchecked vector index in
noexcept function
operator[] is undefined behaviour when index >= m_childrenCache.size(). Because the method is marked noexcept you can't use .at() here (that would call std::terminate on OOB), so the right fix is an explicit guard. The original WinRT-iterator path would also crash on an invalid index, so the risk level is unchanged, but since this PR is already touching this method it's a good moment to add an assert:
winrt::Microsoft::ReactNative::Composition::Experimental::IVisual GetAt(uint32_t index) noexcept {
assert(index < m_childrenCache.size());
return m_childrenCache[index];
}
No description provided.