Skip to content

Rebuild streamed traces when the dpr moved under them - #266

Merged
Alek99 merged 1 commit into
mainfrom
alek/append-fastpath-dpr-and-smoke-hardening
Jul 24, 2026
Merged

Rebuild streamed traces when the dpr moved under them#266
Alek99 merged 1 commit into
mainfrom
alek/append-fastpath-dpr-and-smoke-hardening

Conversation

@Alek99

@Alek99 Alek99 commented Jul 24, 2026

Copy link
Copy Markdown
Member

Follow-up to #188. One real defect, two latent traps in the same path, and a
round of hardening on the smoke that was covering it.

The bug

The tail-only append path extends the interleaved style buffer in place. Those
rows bake stroke_width in device pixels, at the dpr in force when they
were written (_buildInstanceStyleChannels). _resize updates this.dpr on
browser zoom or a monitor swap and does not rebuild traces. So:

  1. Chart builds at dpr 1 — prefix rows hold stroke_width * 1.
  2. User zooms the browser; _resize sets dpr = 2, traces untouched.
  3. A streaming append tail-uploads new rows at stroke_width * 2.

Result: a visible outline-width step partway through a single trace. Before
#188 the append rebuilt the trace, which renormalized every row, so this is
newly reachable.

Fix: record g._styleDpr when the style buffer is built, and refuse the fast
path when it no longer matches this.dpr. The rebuild is always correct, and
a dpr change is rare enough that paying for one is fine. As a side effect this
also clears the pre-existing whole-trace staleness for streaming charts.

Two latent traps (neither reachable today)

  • The per-point buffer guards all sit inside the t.kind === "scatter" branch.
    A line has no per-point buffers today, so the path is correct — but by
    coincidence, not by construction. Now any non-scatter mark carrying one is
    refused.
  • tier_update reallocates the x/y/base data stores via bufferData without
    clearing _fcCapBytes. Unreachable because decimate_view skips exactly the
    traces the fast path accepts (tier === "direct"), but if that ever drifts a
    stale capacity means a tail bufferSubData past the end of the store —
    silently dropped by WebGL. Zeroed at each realloc.

Smoke hardening

scripts/append_stream_smoke.py was passing partly for the wrong reasons:

  • Phase B replayed phase-A payloads. Phase A ends at 21,000 rows; phase B
    fed back 20,200-row payloads. Shipping fewer rows than are resident fails
    the len >= old_len prefix check, so all six ticks took the rebuild path —
    it was measuring rebuild-path coalescing, not fast-path coalescing. Now
    streams six fresh monotonic payloads (21,200 → 22,200). Raised by CodeRabbit
    on Streaming appends: tail-only GPU uploads, coalesced tier refines, throttled reopen re-sync #188.
  • DECIMATED_PX was 2048 against a ~620 px plot. The at-home skip
    predicate decimation_px >= plot.w held with ~3x of slack, so a unit error
    or a dpr-scaled comparison on either side would still have passed. Tightened
    to 640, just above the real plot width, making the assertion load-bearing.
  • New phase C covers the bug above: a chart with a per-point
    stroke_width channel appends once at a fixed dpr (asserting the fast path
    is taken), then moves devicePixelRatio through _resize and appends
    again (asserting a rebuild).

Tidy-up

lod.LOG_FAMILY_SCALES + pins_offset_to_zero() replace the duplicated
("log", "symlog") test in _payload.ship and geometry_offset. If those
drifted, ship would start pinning a sticky midpoint on an axis whose shader
transform requires the zero origin — silent, and wrong exactly where a log
scale earns its keep. Pinned by a test.

Verification

  • Full pytest: 2227 passed, 4 skipped; collection 2230 → 2231, the +1 being
    the new test.
  • ruff check, ruff format --check, node js/build.mjs (tsc + bundle): clean.
  • append_stream_smoke.py: passes, with the previous run's numbers unchanged
    (4800 B steady tail, 0 reallocs, home 0/5, zoomed 2/6, 0 pixel mismatches).
  • The new probe was verified against the bug: with the dpr guard deleted and
    the client rebuilt it reports dprChangeRebuilds: 0 and fails with the
    intended message, then passes once restored. It is a regression test, not a
    test that merely agrees with the current code.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed streaming chart appends after browser zoom or device-pixel-ratio changes, preventing inconsistent point outline scaling.
    • Improved streaming updates to safely rebuild chart data when fast-path conditions are no longer valid.
    • Corrected geometry offset handling across logarithmic-style axis scales.
  • Documentation

    • Clarified when streaming appends require an unchanged device pixel ratio.

The tail-only append path (#188) extends an existing interleaved style
buffer, whose stroke_width rows are baked in device pixels at the dpr in
force when they were written. `_resize` moves `this.dpr` on browser zoom or
a monitor swap *without* rebuilding traces, so points appended after such a
change were scaled at the new dpr while the prefix kept the old one — a
visible outline-width step inside a single trace. Record the dpr the style
buffer was built at and fall back to the full rebuild when it no longer
matches; the rebuild renormalizes every row.

Two latent traps in the same path, neither reachable today:

- The per-point buffer guards live inside the `kind === "scatter"` branch,
  so a future per-point channel on a line would be silently skipped rather
  than rejected. Refuse any non-scatter mark that carries one.
- `tier_update` reallocates x/y/base data stores without clearing the
  append path's capacity bookkeeping. Unreachable because `decimate_view`
  skips exactly the traces the fast path accepts, but a stale cap would
  mean a tail `bufferSubData` past the end of the store if that drifts.

Harden the append smoke, which was passing partly for the wrong reasons:

- Phase B replayed phase-A payloads, shipping *fewer* rows than were
  already resident. That fails the `len >= old_len` prefix check, so it
  measured rebuild-path coalescing, not fast-path coalescing. Stream six
  fresh monotonic payloads instead.
- DECIMATED_PX was 2048 against a ~620 px plot, so the at-home skip
  predicate `decimation_px >= plot.w` held with ~3x of slack — a unit or
  dpr error on either side would not have been caught. Tighten to 640.
- Add a probe for the bug above: append once at a fixed dpr (asserting the
  fast path is taken), then move devicePixelRatio and append again
  (asserting a rebuild). Verified to fail with the fix reverted.

Finally, give the log-family scale test one home (`lod.pins_offset_to_zero`)
instead of a copy in `_payload.ship` and another in `geometry_offset`; if
those drift, `ship` starts pinning a sticky midpoint on an axis whose shader
transform requires the zero origin.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f2b5147d-e0c8-4621-b52a-7177ae86ae9a

📥 Commits

Reviewing files that changed from the base of the PR and between 9b08085 and 3d6bdad.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • js/src/50_chartview.ts
  • js/src/54_kernel.ts
  • python/xy/_payload.py
  • python/xy/lod.py
  • scripts/append_stream_smoke.py
  • spec/design/wire-protocol.md
  • tests/test_lod.py

📝 Walkthrough

Walkthrough

Streaming append logic now tracks style-buffer DPR, rejects unsafe tail uploads, and resets capacity metadata after reallocations. Geometry offset pinning uses a shared log-family policy. Browser smoke coverage and protocol documentation validate DPR changes, zoomed-history requests, and rebuild behavior.

Changes

Streaming and geometry correctness

Layer / File(s) Summary
Centralize geometry offset scale policy
python/xy/lod.py, python/xy/_payload.py, tests/test_lod.py
Log-family scale handling is shared by geometry offset calculation and payload encoding, with tests covering pinning and non-pinning scales.
Guard DPR-sensitive append updates
js/src/50_chartview.ts, js/src/54_kernel.ts
Style buffers record their DPR; append extensions reject incompatible traces or DPR changes, and buffer reallocations clear cached capacities.
Validate streaming and document preconditions
scripts/append_stream_smoke.py, spec/design/wire-protocol.md, CHANGELOG.md
Smoke tests cover zoomed-history coalescing and DPR-sensitive stroke widths, while documentation and the changelog describe rebuild behavior across DPR changes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant ChartView
  participant WebGLBuffers
  Browser->>ChartView: Change devicePixelRatio and resize
  Browser->>ChartView: Request streaming append
  ChartView->>ChartView: Compare stored and current DPR
  ChartView->>WebGLBuffers: Rebuild trace when DPR differs
Loading

Possibly related PRs

  • reflex-dev/xy#188: Both changes modify streaming append fast-path behavior and post-append handling in js/src/54_kernel.ts.

Suggested reviewers: masenf

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main fix: streamed traces are rebuilt when device pixel ratio changes under them.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch alek/append-fastpath-dpr-and-smoke-hardening

Comment @coderabbitai help to get the list of available commands.

@codspeed-hq

codspeed-hq Bot commented Jul 24, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 29%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 102 untouched benchmarks
⏩ 1 skipped benchmark1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_select_lasso_message_1m 89.3 ms 125.7 ms -29%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing alek/append-fastpath-dpr-and-smoke-hardening (3d6bdad) with main (9b08085)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@Alek99
Alek99 merged commit f16e9b9 into main Jul 24, 2026
27 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant