feat: per-unit sync pipeline with incremental shortcut delivery#216
Open
christopherblaisdell wants to merge 3 commits intodanielcopper:mainfrom
Open
feat: per-unit sync pipeline with incremental shortcut delivery#216christopherblaisdell wants to merge 3 commits intodanielcopper:mainfrom
christopherblaisdell wants to merge 3 commits intodanielcopper:mainfrom
Conversation
added 3 commits
April 5, 2026 16:38
Add PerfCollector and ETAEstimator to measure sync performance across all phases: per-phase timing, HTTP request counting and byte tracking, per-platform ROM fetch timing, artwork download progress logging at 10 percent intervals, shortcut and stale ROM gauges, get_perf_report RPC endpoint, and auto-save perf_report.json after each sync. Tested on Steam Deck with 373 ROMs. Includes 35 unit tests.
Replaces the monolithic sync_apply event with a per-unit pipeline that processes each platform/collection independently and registers shortcuts in Steam as each unit completes — not all at the end. Backend changes (library.py): - _build_work_queue(): Phase 0 builds sync plan from enabled platforms/ collections without fetching ROM lists (fast startup) - _sync_one_platform() / _sync_one_collection(): fetch ROMs, diff against state, emit sync_apply_unit event, wait for frontend callback - _wait_for_unit_results(): 60s heartbeat-based timeout with cancellation - Incremental skip: unchanged units emit shortcuts from cache (no re-fetch) Frontend changes (syncManager.ts): - processUnit(): handles sync_apply_unit events, creates/updates shortcuts with 50ms delay (safe for CEF), fetches artwork, reports back to backend - processStale(): handles stale shortcut cleanup - processLegacySyncApply(): preserves backward compat with old sync path UI changes (MainPage.tsx): - Skip Preview toggle now triggers per-unit pipeline (startSync) directly - Preview/Apply path unchanged when Skip Preview is off Tested on Steam Deck: 3 units (Dreamcast 362, N64 329, Metroid 11), 702 total shortcuts, incremental delivery confirmed, no CEF crash.
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.
Motivation
The current sync pipeline processes all platforms and collections as a single monolithic operation. Nothing appears in Steam until the entire sync finishes. For users with small libraries this is fine, but for users working with complete platform sets in RomM (hundreds or thousands of ROMs per platform across many platforms), the existing approach has serious problems:
Long wait with no feedback. A sync covering 5+ platforms with 300-700 ROMs each can take several minutes. The user sees a spinner the entire time with no indication of progress, no way to tell if it's working, and nothing usable until the very end.
All-or-nothing failure mode. If Steam's CEF browser crashes, the network drops, or the user cancels mid-sync, all work is lost - even platforms that were fully processed. The user has to start over from scratch.
CEF memory pressure. Emitting hundreds of shortcut-creation calls in a single burst can overwhelm Steam's embedded Chromium browser (steamwebhelper), causing crashes that wipe the entire shortcuts.vdf. We hit this in testing with just 362 Dreamcast ROMs at 20ms intervals.
No partial usability. Users managing large RomM libraries (retro gaming preservation, full MAME sets, complete console libraries) can't start playing games from Platform A while Platform B is still syncing.
The per-unit pipeline solves all of these by delivering value incrementally: each platform/collection is processed independently, shortcuts are registered in Steam as soon as that unit completes, and the backend moves to the next unit. If something fails on unit 3, units 1 and 2 are already in Steam and playable.
Architecture: Phase 0 as a minimally invasive approach
The key design decision was to introduce a Phase 0 (work queue construction) that runs before the existing sync logic, rather than rewriting the sync pipeline from scratch. Phase 0 calls
_build_work_queue()to enumerate which platforms and collections need syncing - without fetching any ROM data - and produces a lightweight plan (unit name, type, ROM count estimate). The actual per-unit processing (ROM fetching, diffing, shortcut creation) reuses the same core logic that already existed; we're just scoping each invocation to a single platform or collection instead of processing everything at once.This means:
sync_preview->sync_apply_deltaflow runs exactly as before. The per-unit pipeline is an additive code path, not a replacement.Performance implications
This architecture sets up a foundation for future performance work:
sync_apply_unitevents with unit index, total count, and ROM count. This enables real progress bars (unit 2/5, 64% of ROMs processed) instead of an indeterminate spinner.What changed
Backend (library.py)
_build_work_queue(): Phase 0 builds the sync plan from enabled platforms/collections without fetching ROM lists - fast startup, no wasted API calls_sync_one_platform()/_sync_one_collection(): fetch ROMs, diff against state, emitsync_apply_unitevent, wait for frontend callback before proceeding_wait_for_unit_results(): 60s heartbeat-based timeout with cancellation support - recovers gracefully if the frontend disconnectsFrontend (syncManager.ts)
processUnit(): handlessync_apply_unitevents, creates/updates shortcuts with 50ms delay (CEF-safe), fetches artwork, reports results back to backendprocessStale(): handles stale shortcut cleanup after all units completeprocessLegacySyncApply(): preserves full backward compatibility with the existing preview/apply pathUI (MainPage.tsx)
startSync) directlyTesting
Tested on Steam Deck with 3 units (2 platforms + 1 collection):
702 total shortcuts registered. Each unit appeared in Steam the moment it completed. No CEF crash at 50ms shortcut delay. 13 unit tests all passing.
New files
docs/per-unit-pipeline.md- design document covering architecture, event flow, and open questionstests/services/test_per_unit_pipeline.py- 13 unit testsDependencies
This PR includes the changes from #215 (perf instrumentation). Recommend merging #215 first for a clean diff.