Skip to content

[codex] Add async loading and performance caches#13

Merged
Friss merged 6 commits into
mainfrom
codex/async-loading-performance-caches
Apr 22, 2026
Merged

[codex] Add async loading and performance caches#13
Friss merged 6 commits into
mainfrom
codex/async-loading-performance-caches

Conversation

@Friss
Copy link
Copy Markdown
Owner

@Friss Friss commented Apr 22, 2026

What changed

  • move session loading, channel decoding, math evaluation, track extraction, and series downsampling behind background jobs
  • add caching and render-path updates across graph and track panels to keep heavy views responsive
  • add performance instrumentation, Criterion benches, CI perf reporting, and regression notes/tests for the async restore and math job flows

Why it changed

Large sessions were doing too much work synchronously on the UI thread, which caused slow loads and expensive redraws. This change shifts the heavy work off the hot path while preserving correctness for workspace restore, math dependencies, and graph rendering.

Impact

  • session open and channel-heavy views should stay responsive while data is prepared
  • saved workspaces with math-backed panels restore correctly after async evaluation completes
  • graph views no longer briefly show stale geometry while waiting on async downsampling

Root cause

The original implementation decoded and derived data synchronously, and the first async refactor still had correctness gaps around deferred workspace restore, index-based math jobs, and stale render caches.

Validation

  • cargo test -q -p i3rs-app
  • cargo test -q -p i3rs-core
  • cargo fmt --all

@Friss Friss marked this pull request as ready for review April 22, 2026 17:02
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements a background job system and multi-layered caching to enhance performance and UI responsiveness, supported by new benchmarks and instrumentation. Key features include asynchronous session loading, a Level of Detail (LOD) system for graph rendering, and spatial indexing for track map interactions. Feedback suggests further optimizations such as offloading LOD generation to background threads, utilizing binary search for visible data filtering, and reducing draw calls in the track map by grouping segments of the same color. Additionally, improvements to memory management for the math expression cache and the removal of redundant Arc usage in background tasks were recommended.

Comment thread crates/i3rs-app/src/state.rs
Comment thread crates/i3rs-app/src/background_jobs.rs
Comment thread crates/i3rs-app/src/background_jobs.rs
Comment thread crates/i3rs-core/src/math_engine.rs
Comment thread crates/i3rs-app/src/panels/graph.rs
Comment thread crates/i3rs-app/src/panels/graph.rs
Comment thread crates/i3rs-app/src/panels/track_map.rs
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 77e593e664

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/i3rs-app/src/app.rs Outdated
Comment thread crates/i3rs-app/src/app.rs
@Friss
Copy link
Copy Markdown
Owner Author

Friss commented Apr 22, 2026

Branch Review

Two commits, 30 files, ~4,300 LOC. A well-scoped performance pass: moves blocking work off the UI thread and adds memoization around the hottest render paths.

What it does

  • background_jobs.rs (new) — Single crossbeam-backed worker on native, spawn_local on wasm. Five job kinds: session load, physical channel decode, math eval, GPS track build, downsample series. UI submits, polls results in process_platform_events, keys results by session_id so stale replies drop.
  • state.rs — Adds DecodedChannel (owns Arc<[f64]> + cached stats + lazy LOD pyramid at 2k/8k/32k/128k buckets), DownsampleSeriesKey, per-cache pending/requested sets with RefCell. invalidate_session_caches / invalidate_derived_caches split cleanly.
  • math_engine.rs — Global PARSED_EXPRESSION_CACHE, per-eval EvaluationContext with resample_cache keyed by (name, src_freq, out_freq, out_len) and a node-pointer output cache. Resolver pre-builds a normalized name index once per call.
  • graph.rsrender_cache / lap_render_cache fingerprinted on (data_ptr, len, zoom bounds, axis, transform, width) so downsampled PlotPoints survive across frames. Uses PlotPoints::Borrowed everywhere (same trick in fft.rs, scatter.rs, track_map.rs). Large channels route through the LOD pyramid; medium ones go to the background downsampler; small ones still downsample inline.
  • track.rsTrackSpatialIndex: sqrt(n/64) grid, ring-expanding nearest-sample search with best-dist pruning. Replaces the O(n) linear scan on every hover.
  • platform.rs / app.rs — Native file picker moved to rfd::AsyncFileDialog on a worker thread (no more blocking pick_file()). Workspace restore waits until all math channels settle before reapplying the snapshot (nice — fixes reload races).
  • perf_metrics.rs (feature-gated) + benches/perf_benches.rs + CI report-only perf job + docs/performance-regression.md with baseline numbers.

Correctness

  1. Physical-decode error leaks the pending flag (app.rs:518-521). On JobResult::DecodePhysicalChannel { result: Err(_) }, the error arm sets load_error but never calls cancel_physical_channel_decode. pending_channel_decodes retains the index, so every subsequent request_physical_channel_decode(same_idx) no-ops forever and that channel silently fails for the rest of the session. Also, the Err variant has no way to recover channel_idx — it's only inside DecodedPhysicalChannel. Hoist channel_idx: usize onto the DecodePhysicalChannel result variant (alongside session_id) and clear the pending entry in both arms. Math-eval and track-build don't have this bug.

  2. BackgroundJobs uses bounded(64) with a blocking send (background_jobs.rs:458). On initial load the default-layouts pass can queue many decode/math/downsample jobs in a single frame; if the worker is mid-decode, submit stalls the UI thread. Options: go unbounded, switch to try_send and re-insert into requested_* on failure so it retries next frame, or run multiple workers.

  3. Single worker serializes all job types. A 2s math eval blocks subsequent physical decodes and downsamples behind it. A small worker pool (or rayon) over the shared request channel would let decode/downsample proceed in parallel with math eval.

Minor

  1. DecodedChannel.freq is annotated #[allow(dead_code)] (state.rs:141) but ensure_lod_levels reads it. Drop the attribute.
  2. resolve_channel_name in math_engine.rs:72 is #[allow(dead_code)] and has no callers — ChannelResolver::resolve replaced it. Delete it rather than keeping a dead helper.
  3. EvaluationContext::node_outputs keys by expr as *const Expr as usize. Parsed ASTs here are trees without shared nodes, so the cache effectively never hits. resample_cache provides the real speedup. Either remove node_outputs or add a CSE pass to build an actual DAG.
  4. normalize_channel_name in ld_parser.rs lowercases and maps ._ → space but doesn't collapse runs of spaces. panels/graph.rs::normalized_name already does split_whitespace().join(" "). Align them so "GPS Speed" and "GPS Speed" collide in both places.
  5. TrackSpatialIndex::build inserts non-finite samples too — they bucket at cell (0,0) via NaN-clamped cell_coords. Not incorrect (the dx²+dy² NaN comparison fails the "less than" check), but it bloats cell 0 and weakens ring pruning. Skip non-finite samples at insertion.
  6. find_distance_channel / find_speed_channel now mutate state (shared.request_physical_channel_decode) from what reads like a query function. A one-line comment would help future readers, since the RefCell interior mutability hides the effect.
  7. Cache-reset logic is duplicated in install_loaded_session (app.rs:326-338) and clear_loaded_session (702-714). Factor into one helper.

Tests

Good coverage for the new machinery: workspace-restore-defers-until-math-settled, fingerprint invalidation for zoom/transform/replacement, math job IDs survive list reorder, track-map cache reset. Core has an integration_tests file now too. Bench harness is wired and the baseline is captured in docs/performance-regression.md.

Recommendation

Fix #1 before shipping — silent channel-decode failure is user-visible. #2 is worth at least a try_send safety net. The rest are cleanup that can land separately.

@Friss Friss merged commit 6f36f05 into main Apr 22, 2026
8 checks passed
@Friss Friss deleted the codex/async-loading-performance-caches branch April 22, 2026 23:28
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