Skip to content

Frontend Architecture

DoubleGate edited this page Jul 8, 2026 · 1 revision

Frontend Architecture

rustysnes-frontend is the desktop + wasm shell: winit + wgpu + cpal + egui, pure Rust. It is an always-on egui shell, not a bare window — egui runs every single frame, drawing a persistent menu bar (File / Emulation / Tools / View / Debug / Help), a status bar, and a tabbed Settings window, with toggleable CPU/PPU/APU/memory debugger panels layered on top.

Status: playable native

A real commercial ROM boots in a window with picture (PPU BGR555 → RGBA8, aspect-correct 4:3 letterbox blit), sound (S-DSP 32 kHz FIFO → a producer-side dynamic-rate-control resampler → a lock-free ring → cpal stereo output), and control (keyboard + gilrs gamepad). ROM load auto-resolves coprocessor firmware and .srm SRAM; Reset, Power-Cycle, and Pause are wired.

The wasm32 target also builds today, but the deployed demo is a bootstrap scaffold only (sets a panic hook, logs one console message) — a real playable winit+wgpu+egui build in the browser is planned but not yet implemented; see Deferred-Features.

The load-bearing rule

The shell never holds the emu lock inside the egui closure. Menu interactions return a MenuAction value that the app dispatches after the egui pass completes; the present path copies the framebuffer out under one brief lock, drops it, then blits and renders. On native, the emulator can run on a dedicated thread (an Arc<Mutex<EmuCore>> handle plus a lock-free SharedInput), leaving the winit thread free to only handle UI and present.

The determinism boundary

Rate control (the dynamic-rate-control resampler) and run-ahead (snapshot/restore orchestration) live in the frontend, never in core synthesis — this is what keeps the core's bit-identical determinism contract intact. Netplay rollback (not yet implemented — see Netplay-and-Rollback) is designed to be frontend-orchestrated against the same deterministic core the same way.

Save-states, rewind, run-ahead

All three are implemented and shipped:

  • Save-states (v0.2.0 "Persistence") serialize the entire deterministic core state into one versioned envelope. The Emulation menu's Save State / Load State items drive a single quick-save slot.
  • Rewind (v0.3.0 "Continuum") is a bounded ring buffer of full save-state snapshots, recorded at a configurable interval; capacity: 0 is the shipped default, meaning it is off unless explicitly configured.
  • Run-ahead (v0.3.0 "Continuum") peeks a configurable number of frames ahead using the currently-latched input, presents that peeked video, then rolls back so the persisted state only ever advances by exactly one real frame per call. frames: 0 is the shipped default (off).

Both rewind and run-ahead are pure re-simulation of the same deterministic core — no injected timing or randomness, just running the existing frame-step/save/load functions extra times.

See Architecture-Overview for how the core itself is structured, and Lockstep-Scheduler for the master-clock timing model the frontend never bypasses.

Clone this wiki locally