Skip to content

Cache client steady-state draw and hover work#193

Draft
Alek99 wants to merge 1 commit into
mainfrom
agent/client-steady-state-caches
Draft

Cache client steady-state draw and hover work#193
Alek99 wants to merge 1 commit into
mainfrom
agent/client-steady-state-caches

Conversation

@Alek99

@Alek99 Alek99 commented Jul 22, 2026

Copy link
Copy Markdown
Member

Closes #171

Summary

Removes the four client-side steady-state costs identified in the issue:

  • memoizes CSS color expressions per ChartView and invalidates them only on refreshTheme()
  • binary-searches sorted line x columns, skips scatter's CPU scan after an operational GPU-pick miss, and reuses a hit/miss while the pointer remains in the same device pixel
  • caches dashed-line and dashed-segment screen-space geometry/uploads behind exact geometry, view, axes, plot, and DPR signatures
  • preserves axis/annotation label DOM behind a layout signature and throttles label rebuilding during active gestures while forcing a final settled refresh

The committed interaction benchmark now exercises dashed lines and reports a no-state-change steady redraw metric. Focused real-browser tests cover cache invalidation, transition and context-loss fallbacks, GPU misses, pixel reuse, buffer cleanup, view/geometry invalidation, and label-DOM lifecycle.

Performance evidence

Fresh apples-to-apples Chromium/SwiftShader runs used the same updated harness on untouched main and this branch, 8 repetitions per interaction:

scenario / p95 main this PR change
dashed line 120k steady redraw 33.1 ms 27.4 ms -17.2%
dashed line 120k hover 67.0 ms 50.3 ms -24.9%
dashed line 120k wheel zoom 59.0 ms 52.2 ms -11.5%
dashed line 120k crosshair 58.0 ms 50.4 ms -13.1%
direct scatter hover 3.8 ms 2.7 ms -28.9%

All five interaction scenarios remained within their budgets with zero blank interaction frames and zero tick-label overlaps. Tooltip stability, box-zoom change/narrow/restore, brush selection/clear, and GL readback sanity checks all passed.

Verification

  • pytest -q tests/test_client_steady_state.py: 3 passed in real Chromium
  • broader focused client coverage: 70 passed
  • full suite: 2211 passed, 66 skipped, plus the known pre-existing tests/reflex_adapter/test_assets.py::test_client_source_is_the_installed_bundle stale literal-marker assertion; the same assertion was reproduced on untouched main
  • node js/build.mjs --check: committed bundles fresh
  • tsc -p js/tsconfig.json --noEmit: passed
  • ruff check .: passed
  • ruff format --check .: passed
  • git diff --check: passed

This PR is intentionally draft while the related performance-issue sweep is still in progress.

@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 97 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing agent/client-steady-state-caches (4244837) with main (2a7d898)

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.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR reduces steady-state client rendering and hover work. The main changes are:

  • Per-chart caching for resolved CSS colors.
  • Cached dashed-line and segment geometry and GPU uploads.
  • Binary-search and GPU-pick optimizations for hover handling.
  • Device-pixel reuse for repeated pointer events.
  • Signature-based label DOM preservation and gesture throttling.
  • Expanded browser tests and interaction benchmarks.

Confidence Score: 4/5

The label invalidation and decoded-order hover paths need fixes before merging.

In-place label configuration changes can leave stale DOM visible. Negative column scales can send descending decoded values through an ascending binary search. No security or destructive-operation issue was identified.

js/src/50_chartview.ts

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex ran the requested verification, but its local artifact references were not uploaded.
  • The encoded-order hover verification was executed against the ChartView, exercising the hover pipeline and tooltip with encoded and decoded values, confirming the correct row is highlighted and the tooltip shows the expected coordinates.
  • The focused pytest run completed with exit code 0; accompanying pre- and post-state posters and videos document the steady-state changes and confirm rendering consistency.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
js/src/20_theme.ts Adds optional per-view caching to CSS color and theme resolution.
js/src/45_lod.ts Routes LOD color parsing through the owning chart's color cache.
js/src/50_chartview.ts Adds the main rendering caches, hover fast paths, label lifecycle changes, and related cleanup.
js/src/53_interaction.ts Adds hover-cache invalidation and settled refreshes after gesture termination.
js/src/55_marks.ts Moves mark color refreshes to the per-chart cached parser.
js/src/57_viewstate.ts Forces a settled label refresh when an axis-band gesture is cancelled.
benchmarks/bench_interaction.py Adds dashed-line coverage and a no-state-change redraw metric.
tests/test_client_steady_state.py Adds browser coverage for cache invalidation, hover reuse, fallbacks, cleanup, and label lifecycle.

Reviews (1): Last reviewed commit: "Cache client steady-state draw and hover..." | Re-trigger Greptile

Comment thread js/src/50_chartview.ts
Comment on lines +4078 to +4089
_labelDomStateSignature() {
const p = this.plot;
const signature = [
this.spec, this.axes, this._themeEpoch,
this.size.w, this.size.h, p.x, p.y, p.w, p.h,
];
for (const axisId of this._axisIds()) {
const [lo, hi] = this._axisRange(axisId);
signature.push(axisId, lo, hi);
}
return signature;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Label Signature Misses Content Changes

When an incremental update mutates axis or annotation settings in place, changes to label text, tick formatting, rotation, or annotation content can preserve the object identities and axis ranges in this signature. labelsDirty then remains false, so the chart keeps stale label DOM until another captured property changes.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

T-Rex Ran code and verified through T-Rex

Comment thread js/src/50_chartview.ts
Comment on lines +2505 to +2511
g._cpuXSorted = true;
for (let i = 1; i < g.n; i++) {
if (!Number.isFinite(x[i]) || x[i] < x[i - 1]) {
g._cpuXSorted = false;
break;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Encoded Order Hides Reversed Values

This checks the encoded x column, but the new binary search orders decoded values using value / scale + offset. With a negative column scale, ascending encoded values become descending decoded values while _cpuXSorted remains true, so line hover can select the wrong row.

Suggested change
g._cpuXSorted = true;
for (let i = 1; i < g.n; i++) {
if (!Number.isFinite(x[i]) || x[i] < x[i - 1]) {
g._cpuXSorted = false;
break;
}
}
g._cpuXSorted = (g.xMeta?.scale ?? 1) > 0;
for (let i = 1; g._cpuXSorted && i < g.n; i++) {
if (!Number.isFinite(x[i]) || x[i] < x[i - 1]) {
g._cpuXSorted = false;
}
}

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.

Client per-frame waste: parseColor style recalc, _hoverAt O(N) scan, dash recompute, label DOM rebuild

1 participant