Skip to content

perf(playback): route playhead updates outside pipeline store#3

Merged
joaner merged 1 commit into
ioai-tech:mainfrom
joaner:main
May 27, 2026
Merged

perf(playback): route playhead updates outside pipeline store#3
joaner merged 1 commit into
ioai-tech:mainfrom
joaner:main

Conversation

@joaner
Copy link
Copy Markdown
Contributor

@joaner joaner commented May 27, 2026

Description

This PR separates high-frequency playhead updates from the low-frequency Zustand pipeline store, reducing React re-render pressure during MCAP playback.

Playback time channel

  • Add Player.getCurrentTime() and PlaybackControlsApi.getCurrentTime() for imperative, real-time playhead reads.
  • IterablePlayer no longer writes activeData.currentTime on every playback tick into useMessagePipelineStore.
  • Discrete events (init, seek, pause, loop/end, close) still refresh activeData.currentTime via _emitState().
  • Periodic pipeline emits (_maybeEmitPipelineState, 200 ms) skip updates when only time would change; they still propagate slow metadata (isPlaying, speed, progress, etc.).

Call-site updates

  • PlaybackBar: initialize playhead ref from player.getCurrentTime().
  • AlignPanel: follow playhead via subscribeCurrentTime() instead of useMessagePipeline currentTime.
  • Extension context: getSnapshot() still includes currentTime as a compatibility snapshot, built from getCurrentTime() at read time.

Render isolation

  • PlaybackOverlayHost: memoize per-overlay items so extension subtree re-renders are bounded.
  • SidebarExtensionHost: memoize host + content wrapper.

Documentation

  • API.md / API.zh.md: document getCurrentTime(), clarify high- vs low-frequency playback APIs.
  • ARCHITECTURE.md / ARCHITECTURE.zh.md: document the dual-channel model (pipeline store vs subscribeCurrentTime / getCurrentTime).

Motivation / related issue

During Studio playback, useMessagePipeline subscribers were waking on every tick because activeData.currentTime changed continuously in the Zustand store. That drove unnecessary React reconciliation (~110 scheduler tasks/s in traces) even for components that only needed slow metadata (bounds, isPlaying, progress).

This change keeps the pipeline store for slowly changing metadata and routes the playhead through subscribeCurrentTime() / getCurrentTime(), matching the pattern already used by 3D/Image panels.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing behavior to change)
  • Documentation update
  • Refactor / internal cleanup (no behavior change)

Checklist

  • npm run lint passes with no errors
  • npm test passes (unit tests)
  • npm run build and npm run build:lib succeed
  • New behavior is covered by tests (or explain why tests aren't applicable)
  • Documentation updated (README, API.md, EMBEDDING.md) if the public API changed
  • Breaking change: all affected call sites updated and migration path described in PR description

API compatibility

Additive, non-breaking

Symbol Change
Player.getCurrentTime() New — returns latest playhead time without subscribing
PlaybackControlsApi.getCurrentTime() New — same, exposed on extension context
PlaybackControlsApi.getSnapshot() Unchanged signaturecurrentTime is now a point-in-time snapshot, not tick-driven
Player.subscribeCurrentTime() Unchanged — still the preferred high-frequency channel

Migration for embedders / extensions

  • Do not drive per-frame UI from getSnapshot().currentTime or useMessagePipeline(s => s.playerState.activeData?.currentTime).
  • Do use subscribeCurrentTime() (refs, canvas, throttled listeners) or getCurrentTime() for one-off reads.
  • Keep useMessagePipeline for slow metadata: presence, topics, bounds, progress, isPlaying, speed.

No exports removed.

Test plan

  • IterablePlayer: getCurrentTime() reflects seek/playhead; pipeline store currentTime does not advance on pure playback ticks
  • MinimalPlayer: getCurrentTime() returns zero time while ready
  • Manual: open Studio, play MCAP — scrubber/playhead updates smoothly; extensions (marker overlay) remain responsive
  • Manual: seek / pause / loop — getSnapshot().currentTime and pipeline store still update on discrete events

Screenshots / recordings

N/A — performance-oriented internal change; no intentional visible UI change. Optional: before/after Performance trace comparing performWorkUntilDeadline rate during playback.

Expose Player.getCurrentTime() and stop writing currentTime on every tick
to the Zustand pipeline store so useMessagePipeline subscribers are not
awoken during playback. Discrete events still refresh activeData.currentTime
via _emitState; periodic emits skip time-only changes. Extension hosts are
memoized and docs describe the high-frequency vs low-frequency APIs.
@joaner joaner merged commit a721db7 into ioai-tech:main May 27, 2026
2 checks passed
joaner added a commit that referenced this pull request Jun 4, 2026
* feat(vite): add custom chunk file naming and adjust asset directory structure

Implement a new function for generating chunk file names for lazy panel UI components, ensuring a consistent naming convention. Additionally, modify the output configuration to place assets directly in the root of the dist-lib directory, streamlining the build output structure.

* refactor(panels): rename lazy-load targets from Component to Panel files

Align panel chunk naming with ThreeD/Pose so dist-lib outputs identifiable
{PanelName}Panel-*.js chunks instead of generic Component-*.js files.

* refactor(threeD): rename foxglove-core directory to core

Shorten the shared 3D rendering module path and update imports across
ThreeD, Pose, UrdfDebug, and urdf-preview entrypoint.

* refactor(panels): unify panel-specific modules under core/ directories

Move align-core, image-core, and audio-core into Align/core, Image/core,
and Audio/core to match the ThreeD panel layout and simplify import paths.

* feat(plot): generic Plot panel with JointState support and legacy deprecation (#1)

* feat(plot): implement Plot panel with dataset handling and configuration

Add a new Plot panel feature, including components for rendering plots, managing datasets, and configuring series. Introduce utilities for reading message ranges and extracting plot path values. Implement settings for customizing plot behavior, such as axis modes and series configurations. Ensure integration with existing message pipeline for real-time data visualization.

* feat(plot): add MCAP topic inspection script and enhance plot dataset handling

Introduce a new script for inspecting MCAP topics to facilitate Plot panel fixture generation. Enhance the plot dataset handling by implementing efficient random access by topic and time, improving the overall performance and usability of the Plot panel. Update related types and configurations to support these features, ensuring better integration with existing data visualization workflows.

* feat(plot): enhance dataset handling and introduce joint state path utilities

Refactor the dataset management in the Plot panel to improve performance and usability. Introduce new utilities for handling joint state paths, including functions for combining and stripping paths. Update the message path extraction logic to support multiple comma-separated and whitespace-separated paths. Enhance the plot configuration actions and selectors for better integration with the new dataset features, ensuring a more efficient data visualization workflow.

* feat(plot): deprecate JointStatePlot and enhance panel definitions

Introduce warnings for deprecated JointStatePlot usage in the layout and runtime components, advising migration to the Plot panel. Update panel definitions to exclude JointStatePlot from addable options and implement new utilities for handling joint state paths. Enhance plot configuration normalization to support legacy series merging and improve dataset handling for better visualization workflows.

* fix(plot): improve error handling and code clarity in dataset tests and plot alignment

Enhance dataset tests by adding error checks for undefined indices, ensuring robust validation of non-null values. Refactor plot alignment logic to handle potential undefined entries gracefully. Additionally, streamline the extraction of y-values in dataset processing for better readability and maintainability. Remove unused type imports to clean up the codebase.

---------

Co-authored-by: joaner <joaner@users.noreply.github.com>

* refactor(WelcomeScreen): simplify layout structure and improve responsiveness

Updated the WelcomeScreen component to enhance layout by removing unnecessary min-height constraints and adjusting flex properties for better responsiveness. This change aims to streamline the component's rendering and improve user experience on various screen sizes.

* feat(plot): implement streaming message retrieval for time ranges

Enhance the IterablePlayer class by introducing a new method, streamMessagesInTimeRange, which allows for streaming messages in specified time ranges with configurable batch sizes and limits. This update improves the efficiency of message retrieval and supports real-time data visualization in the Plot panel. Additionally, refactor the getMessagesInTimeRange method to utilize the new streaming functionality, ensuring compatibility with existing use cases. Introduce tests for the new streaming behavior and update related types to accommodate the new parameters.

* feat: support remote URL loading for db3 and add Next.js guide (#3)

db3/SQLite cannot be Range-streamed (random access over the whole
database), so the db3 worker now downloads a remote URL in full (with
download progress) and opens it in memory. This unifies the
local-File / remote-URL access model across all formats — passing a db3
URL just works, no host-side pre-download needed.

Also:
- bump @ioai/wasm-zstd to ^1.1.2 (inline-worker blob: URL fix)
- add a Next.js (App Router / Turbopack) integration section to the
  embedding docs, plus a db3 troubleshooting note
- update architecture docs for remote db3 behavior

Bump to 1.3.5.

Co-authored-by: joaner <joaner@users.noreply.github.com>

* fix(plot): smooth incremental updates instead of flickering during load

* fix(plot): improve SettingsNumber input UX 
* fix(plot): toggling series visibility no longer triggers a re-fetch 
* fix(plot): smooth incremental updates instead of flickering during load 
* feat(plot): disable X-axis modes that need array Y-paths or an X-path 
* feat(plot): add reset-zoom button + slim load progress bar

* fix(plot): remove reset zoom and add i18n release checks

Remove the non-functional Plot reset zoom control, localize tab context
menu and add-panel submenu strings, and add an npm check:i18n script.

* fix(plot): stabilize bounded Y paths and support hyphen slice syntax

Align Foxglove inclusive slice bounds for paths like position[1:2], accept
position[1-2] as an alias, remount uPlot when incremental series updates
are unsafe, and clear the dataset when plot data config changes.

* chore(release): bump version to 1.5.0

* fix(build): exclude test files from Tailwind content scan

Plot path strings in tests and comments were mistaken for arbitrary
property utilities, producing invalid CSS that broke lightningcss minify.

---------

Co-authored-by: joaner <joaner@users.noreply.github.com>
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