This document describes the current crate layout, the main runtime data structures, and the high-level execution flow of Oxide.
flowchart TD
main[main.rs]
app[app.rs]
cpu[cpu.rs]
display[display.rs]
keypad[keypad.rs]
audio[audio.rs]
gamepad[gamepad.rs]
i18n[i18n.rs]
debug[debug.rs]
types[types.rs]
utils[utils.rs]
ui[ui/*]
main --> app
main --> debug
main --> ui
app --> cpu
app --> display
app --> keypad
app --> audio
app --> gamepad
app --> i18n
app --> types
app --> utils
app --> ui
Main modules:
src/main.rs: process entry point, window/bootstrap setup, splash initialization, Windows single-instance guardsrc/app.rs:Oxideapplication state,eframe::Appimplementation, runtime orchestrationsrc/cpu.rs: CHIP-8 fetch/decode/execute loop and quirk-aware opcode behaviorsrc/display.rs: 64x32 framebuffer state and pixel operationssrc/keypad.rs: CHIP-8 keypad state abstractionsrc/audio.rs: buzzer backend driven byrodiosrc/gamepad.rs: gamepad polling integration throughgilrssrc/i18n.rs: UI translations and shared translation loadingsrc/debug.rs: low-level debug loggingsrc/debug/i18n.rs: debug-string localization layersrc/types.rs: enums and persistent configuration modelssrc/utils.rs: mapping helpers, shortcuts, input translation helperssrc/constants.rs: version string and UI/display constantssrc/ui/: top bar, bottom bar, main panel, settings window, debug terminal
Oxide is the root application state.
It contains:
- emulation state:
cpu,display,keypad,rom_data,rom_path - presentation state:
theme,langue,video_scale,fullscreen,vsync - settings temp/snapshot state for deferred apply/cancel flows
- runtime flags for detached windows, focus handling, overlays, splash, cursor visibility
- debug terminal buffers and file handles
- save-state metadata and snapshots
- audio backend instance
Oxide also owns the frame loop through impl eframe::App.
The CPU struct contains the CHIP-8 machine state:
v[16]ipcspstack[16]delay_timersound_timermemory[4096]
Execution is quirk-aware via CpuQuirks.
Display stores the logical framebuffer used by the main panel.
- logical size:
64 x 32 - pixel storage: flat vector of on/off values
- helpers: clear, read, and write pixels
AudioEngine is a minimal lazy backend:
- opens a default output stream only when needed
- plays a continuous
880 Hzsine wave while the CHIP-8 sound timer is active - maps UI volume
0..100to a reduced output gain
flowchart TD
A[Process start] --> B{Windows build?}
B -- Yes --> C[Acquire single-instance mutex]
B -- No --> D[Skip mutex]
C --> E[Log early debug message]
D --> E
E --> F[Build splash viewport]
F --> G[Load app icon]
G --> H[run_native]
H --> I[Restore persisted settings if available]
I --> J[Re-arm splash state]
J --> K[Reset runtime-only emulation state]
K --> L[Log terminal ready]
L --> M[Enter Oxide::update loop]
main.rs currently does the following:
- On Windows, acquire a named mutex to prevent multiple instances.
- Log early startup debug messages.
- Build a compact splash viewport using the bundled logo size.
- Load the main window icon from bundled
.icoassets. - Start
eframe::run_nativewith the splash viewport. - Re-arm splash state even when persistent settings were restored.
- Reset ROM/runtime state while preserving persisted settings.
- Hand control to
Oxide::update().
flowchart TD
A[Frame begin] --> B{Splash active?}
B -- Yes --> C[Render splash]
C --> D[Request repaint]
D --> Z[Return]
B -- No --> E[Handle pending file open]
E --> F[Sync viewport and fullscreen state]
F --> G[Handle global shortcuts]
G --> H[Update keypad from input sources]
H --> I[Run emulator step]
I --> J[Tick timers and audio]
J --> K[Seed terminal boot logs]
K --> L[Log config changes]
L --> M[Apply theme visuals]
M --> N[Render top bar / main panel / bottom bar]
N --> O[Render settings viewport if open]
O --> P[Render debug terminal if open]
P --> Q[Request repaint if needed]
Q --> Z
Every frame, Oxide::update() drives the app in this rough order:
- Render splash screen and early-return while splash is active.
- Handle pending file open requests (
.state,.ch8,.rom,.bin). - Synchronize window metrics, fullscreen state, and focus state.
- Resize the main viewport when needed according to
video_scale. - Process global shortcuts.
- Update keypad state from keyboard, mouse, gamepad, and debug terminal input.
- Advance emulation based on
stable_dt. - Tick timers and drive the audio engine.
- Seed debug terminal boot logs once.
- Log runtime configuration changes.
- Apply theme visuals.
- Render top bar, bottom bar, display panel, settings window, and debug terminal.
- Repaint continuously while emulation is active or overlays are visible.
Oxide uses multiple viewports:
- main window: root application viewport
- settings window: detached configuration viewport
- debug terminal: detached logging/diagnostics viewport
Both detached windows are coordinated from app.rs and rendered in src/ui/settings.rs and src/ui/debug_terminal.rs.
The settings system uses a deferred-apply model.
Persistent live values:
themelanguevsyncvideo_scaletouchesraccourciscycles_par_secondeson_activesound_volumequirksquirks_presetterminal_active
Temporary editing values:
temp_*
Rollback snapshot values:
snapshot_*
This allows:
Apply: commit temp values without closingOK: commit and closeCancel: restore snapshotsDefaults: reset current tab temp valuesReset all: restore all defaults
Save states are stored separately from UI persistence.
Persistent user settings use eframe::Storage, while save states are written as .state files on disk.
Important save-state types:
EmuSnapshotSaveStateMetaPersistedSaveState
Save states are organized per ROM using a sanitized ROM name plus a stable FNV-1a hash.
UI translations are loaded from:
src/i18n/common.jsonsrc/i18n/<lang>.json
Debug translations are loaded from:
src/debug/i18n/common.jsonsrc/debug/i18n/<lang>.json
The common.json layer stores identical strings shared by all languages to reduce duplication.
There are two log layers:
- console/debug lifecycle logs via
src/debug.rs - in-app debug terminal logs managed by
Oxide
Rotating on-disk log folders:
logs/applogs/emulator
Previous latest.logs files are zipped on startup before the new session begins.
The codebase is currently structured around these goals:
- keeping emulation logic separated from UI rendering
- supporting configurable UX without losing deterministic CPU behavior
- preserving user settings while clearing runtime-only state at startup
- providing practical diagnostics for emulator development and ROM testing