Repository navigation
Send setItems only what JS lacks, and report current sizes in snapshots (3.19.8.2) - #4
Merged
Merged
Conversation
Every change of Items sent the whole key and size list across the interop bridge again. In a Blazor Hybrid app each call is serialized to JSON and copied through the platform's WebView bridge, so a search over 4,010 rows resent ~94K characters per keystroke, and going back to the full list resent all of them once more. Both sides now keep a table of the (key, size estimate) pairs already sent. A change travels as slot ids, with runs of consecutive ids packed into two numbers, plus only the pairs JS has never received; an unchanged set sends a null order. The JS side rebuilds the arrays and runs the existing setItems, so per-key measurements survive a change as before. The table is rebuilt when it outgrows twice the largest set since the last rebuild, and after a failed call. For the picker's typing sequence the setItems payload drops from 293,225 to 21,170 characters; returning to the full list costs 23.
…llToIndex virtual-core keeps its measurements in a field it only brings up to date when getMeasurements runs. Every notification normally makes that happen, but notifyChange holds back one raised inside a ResizeObserver callback until the next task, and InvalidateItemSize raises none. Until something recomputes, the field still holds sizes the virtualizer has stopped using, so TakeSnapshot handed back a row's old size, GetOffsetForIndex an offset from it, and ScrollToIndex landed its first write on the old offset (520 instead of 490) before jumping a frame later. All three now refresh through getMeasurements first, which is memoized and costs nothing when nothing changed.
GetSnapshot read StartIndex and EndIndex from virtual-core's range field, which lags exactly as the measurements did: only calculateRange assigns it, so inside a ResizeObserver delivery it still reported the rows before the resize (0-3 where the virtualizer already used 0-0), and after InvalidateItemSize the range before the eviction. It now asks calculateRange, which is memoized and refreshes the measurements too. Also names TakeSnapshot correctly in the InitialMeasurementsCache doc.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4 +/- ##
==========================================
+ Coverage 71.69% 77.77% +6.07%
==========================================
Files 6 7 +1
Lines 265 333 +68
Branches 41 51 +10
==========================================
+ Hits 190 259 +69
Misses 64 64
+ Partials 11 10 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues block approval of the interop and measurement-refresh changes.
No open issues 🎉
What changed in this PR
This PR reduces AdaptiveVirtualize item-update traffic across the Blazor JavaScript bridge and ensures snapshot and scrolling APIs use current measurements, without changing the public API.
Changes:
- Adds mirrored slot tables so item updates send compact ordering data and only previously unsent key/estimate pairs.
- Refreshes measurements and visible ranges before snapshot, offset, and index-scrolling operations.
- Adds regression coverage and updates the package to version 3.19.8.2.
| File | Description |
|---|---|
| tests/Blazor.TanStack.VirtualCore.Tests/ItemSetInteropTests.cs | Tests serialized payloads, slot-table bounds, and recovery after failed calls. |
| tests/Blazor.TanStack.VirtualCore.E2E/VirtualizationTests.cs | Tests row rendering after collapsing and re-expanding a group. |
| tests/Blazor.TanStack.VirtualCore.E2E/InteropModuleTests.cs | Tests slot decoding, measurement preservation, and current snapshot and scroll values. |
| src/Blazor.TanStack.VirtualCore/wwwroot/adaptive-virtualize-interop.js | Decodes slot updates and refreshes measurements and ranges before queries and scrolling. |
| src/Blazor.TanStack.VirtualCore/VirtualizeOptions.cs | Corrects the snapshot method name in documentation. |
| src/Blazor.TanStack.VirtualCore/ItemSlotTable.cs | Implements compact slot encoding, bounded table growth, and reset support. |
| src/Blazor.TanStack.VirtualCore/Blazor.TanStack.VirtualCore.csproj | Bumps the package version and adds release notes. |
| src/Blazor.TanStack.VirtualCore/AdaptiveVirtualize.razor.cs | Integrates slot encoding into item updates and invalidates the table after failures. |
Review effort: Balanced
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
Profiling the SatisFIT exercise picker on Android (Blazor Hybrid) showed
AdaptiveVirtualizeresending its whole item set across the interop bridge on everyItemschange: about 94K characters for a 4,010-row list, per keystroke of a search, as JSON copied through the WebView bridge. While fixing that,TakeSnapshot,GetOffsetForIndex,ScrollToIndexandGetSnapshotturned out to read virtual-core state that lags behind what the virtualizer actually uses.Changes
Send setItems only the keys the JS side does not hold yet.
ItemSlotTablein C#, its mirror in the interop module). A change travels as slot ids, with runs of consecutive ids packed into two numbers, plus only the pairs JS has never received. An unchanged set sends anullorder.setItems, so per-key measurements survive an item change exactly as before.b/be/ben/benc/benchsetItemscallsRead current measurements in TakeSnapshot, GetOffsetForIndex and ScrollToIndex. virtual-core only refreshes
measurementsCacheinsidegetMeasurements(). Inside a ResizeObserver delivery (the notification is held one task) and afterInvalidateItemSize(which raises none) it still held old sizes, soTakeSnapshotreturned them,GetOffsetForIndexcomputed from them, andScrollToIndexlanded its first write on the old offset (520 instead of 490) before jumping a frame later. All three refresh through the memoizedgetMeasurements()first.Report the current range in GetSnapshot.
StartIndex/EndIndexcame from virtual-core'srangefield, which lags the same way. It now askscalculateRange().Release 3.19.8.2.
Testing
ItemSetInteropTests, recording through a realJSRuntimesubclass): a content-equal list sends no keys, a reordered subset sends none JS already holds, going back to an earlier list sends none, only never-seen keys are sent, a changed estimate for a known key is sent alone, duplicate keys with different estimates both reach JS, a list whose keys keep changing keeps the table bounded, and a failedsetItemsresends the whole set. Six of them fail on the old code on their payload assertion.TakeSnapshot,GetOffsetForIndex,ScrollToIndexandGetSnapshotwindows. Each of the last four fails on the old code with the stale values.After merge