EmulationWorkerClient class manages a dedicated Electron utility process that runs the emulation loop with the native libretro addon. It provides an async API for all emulation operations and emits events for video frames, audio samples, and errors.
This client communicates with a worker process via
postMessage and supports zero-copy frame/audio transfer using SharedArrayBuffer when available.Constructor
init() to spawn the utility process and load a game.
Methods
init
Spawn the utility process, load the core and ROM, and start the emulation loop.Absolute path to the libretro core (.dylib, .dll, or .so)
Absolute path to the ROM file
Directory containing BIOS files (e.g.,
userData/BIOS)Directory for game saves
Directory for SRAM/battery saves
Directory for save states
Path to the native libretro addon (.node)
Audio/video information from the loaded core
Example
Input control
setInput
Forward input to the emulation core. Fire-and-forget — no response expected.Controller port (0-based)
Button/input ID (libretro constant, e.g.,
RETRO_DEVICE_ID_JOYPAD_A)Whether the button is pressed (
true) or released (false)Input commands are high-frequency and do not return responses. The worker applies them immediately to the next frame.
Playback control
pause
paused event.
resume
resumed event.
reset
reset event.
setSpeed
Speed multiplier (e.g., 1.0 = normal, 2.0 = 2x speed, 0.5 = half speed)
Save state management
saveState
Save state slot number (0-9)
loadState
Save state slot number (0-9)
saveSram
This is called automatically during shutdown, but you can call it manually to ensure saves are written immediately.
Screenshot
Optional output path. If not provided, a default path will be generated.
Absolute path to the saved screenshot file
Example
Lifecycle management
prepareForQuit
shutdown
destroy
shutdown() — matches the lifecycle naming used elsewhere.
isRunning
true if the worker process is alive and running.
Zero-copy frame transfer
getSharedBuffers
Returns
null if SharedArrayBuffer is unavailableSharedArrayBuffer instances for zero-copy frame/audio transfer. If SharedArrayBuffer is unavailable (or allocation failed), returns null and the worker falls back to copy-based IPC.
When SharedArrayBuffer mode is active, video frames and audio samples are written directly to shared memory instead of being copied through IPC. The renderer process can read frames via
Atomics.load() without blocking the worker.Control buffer layout
The control SAB uses anInt32Array view with the following fields:
| Index | Field | Description |
|---|---|---|
| 0 | activeBuffer | Which video buffer the renderer should read (0 or 1) |
| 1 | frameSequence | Monotonically increasing frame counter |
| 2 | frameWidth | Current frame width in pixels |
| 3 | frameHeight | Current frame height in pixels |
| 4 | audioWritePos | Ring buffer write position (Int16 sample count) |
| 5 | audioReadPos | Ring buffer read position (Int16 sample count) |
| 6 | audioSampleRate | Sample rate reported by core (44100, 48000, etc.) |
| 7 | — | Reserved |
Example renderer usage
Events
Inherits fromEventEmitter and emits the following events:
videoFrame
Emitted when a new video frame is available (copy-based IPC mode only).Not emitted when SharedArrayBuffer mode is active — use
getSharedBuffers() instead.audioSamples
Emitted when audio samples are available (copy-based IPC mode only).error
Emitted when the worker encounters an error.speedChanged
Emitted when emulation speed changes.Lifecycle events
paused— Emulation loop pausedresumed— Emulation loop resumedreset— Core was reset
Worker protocol
The worker communicates viapostMessage with a structured command/event protocol.
Commands (Main → Worker)
All commands are objects with anaction field:
requestId field. The worker replies with a response event containing the same ID.
Events (Worker → Main)
Performance considerations
Request timeout
By default, requests time out after 10 seconds. Theshutdown command uses a 5-second timeout. If a request times out, the Promise rejects and the pending request is cleaned up.
Zero-copy frame transfer
WhenSharedArrayBuffer is available, video frames and audio samples are written directly to shared memory. This eliminates the overhead of copying large buffers through IPC and is critical for 120Hz+ displays.
Without SAB (copy-based IPC):
- Each frame: ~2MB copy (1920×1080 RGBA) + IPC overhead
- At 60fps: ~120MB/s copied through IPC
- Worker writes directly to shared memory
- Renderer reads via
Atomics.load()(no copy) - Double-buffered to prevent tearing
Zero-copy mode is essential for maintaining 60fps+ on high-resolution displays. Always check
getSharedBuffers() and implement a fallback for IPC mode.Related
- LibretroNativeCore — Native core wrapper with path validation
- CoreDownloader — Download and manage libretro cores