-
-
Notifications
You must be signed in to change notification settings - Fork 1
Architecture
ViceSharp is a library-first emulator: the emulation engine is a set of composable .NET libraries. UI shells (console, Avalonia desktop, and the platform host shells) are thin consumers.
- POCO model: All emulator state lives in plain C# structs and records. No base classes, no ORM, no serialization attributes on hot-path types.
- Zero-allocation hot path: The per-cycle emulation loop allocates zero managed objects. All transient data uses stack allocation, spans, or arena-pooled buffers.
- Deterministic: Given identical initial state and input sequence, execution is bit-exact reproducible. This enables snapshot comparison, replay, and regression testing.
- Trim-aware managed runtime: No reflection on the hot path. Source generators replace runtime discovery where useful. Supported desktop packaging is self-contained JIT + ReadyToRun rather than native ahead-of-time publishing.
-
MVVM: ViewModels reference only
ViceSharp.Abstractions. Views contain zero logic. The emulation engine has no UI dependencies. -
Host/UI boundary: UI control, media, session, input, snapshot, capture, and diagnostic operations communicate with
ViceSharp.Hostthrough versioned gRPC services or narrow gRPC-backed client abstractions. The host owns emulator sessions, devices, media, snapshots, diagnostics, and local render-source composition.
ViceSharp.Abstractions 50 interface files: interfaces, value types, attributes
|
+-- ViceSharp.Core Bus, clock, mutation queue, pub/sub, snapshots, media recorders
| |
| +-- ViceSharp.Chips CPU, VIC-II, SID, CIA, VIA, PLA implementations
| |
| +-- ViceSharp.Architectures Machine definitions (C64, C1541 drive, ad-hoc/multisystem)
|
+-- ViceSharp.SourceGen Roslyn source generator (device registration)
|
+-- ViceSharp.Monitor Debugger/monitor engine
|
+-- ViceSharp.Host Composition boundary: emulator sessions, service registration, gRPC host surface
| |
| +-- ViceSharp.Host.Android / .iOS / .MacOS / .Xbox Platform host shells
|
+-- ViceSharp.Protocol gRPC/protobuf contracts and generated client/server types
|
+-- ViceSharp.Launcher VICE-style command-line parsing and machine topology building
|
+-- ViceSharp.Console CLI/reference shell
|
+-- ViceSharp.Avalonia Desktop UI (Avalonia 12.x)
|
+-- ViceSharp.AdhocHelper Ad-hoc machine configuration helper UI (Avalonia)
|
+-- ViceSharp.RomFetch ROM download/validation tool
|
+-- ViceSharp.Core.Package Packaging shell for the ViceSharp.Core NuGet bundle
(Abstractions + Chips + RomFetch + Core + Architectures)
ViceSharp.Host is the composition boundary for UI-facing emulator sessions. It creates machines from architecture descriptors, owns media and state services, and exposes control, remote output, input, media, snapshot, capture, and diagnostic operations through TR-GRPC-BOUNDARY-001.
UI control clients consume the nine proto services defined in src/ViceSharp.Protocol/Protos/emulator_host.proto:
-
EmulatorHost: session lifecycle and execution control (create session, status, start/pause/resume, cold/warm reset, autostart, step/rewind by cycle or frame, limiter rate, shutdown). -
SettingsService: settings profiles (list, get, update) and resource validation. -
MediaService: disk/tape/cartridge attach, eject, listing, and media status streaming. -
VideoService: video status, single-frame fetch, and frame streaming for remote UIs. -
InputService: normalized keyboard/joystick state, input state queries, and keyboard-map selection. -
MonitorService: debugger surface (monitor commands, registers, disassembly, breakpoints, memory read/write, tick-history and at-tick state queries). -
SnapshotService: snapshot capture and restore. -
CaptureService: screenshots, sound recording, and video recording (see Media Capture below). -
DiagnosticsService: host info, session enumeration, and performance snapshots/streams.
The in-process Avalonia renderer is a narrow host-owned exception for frame presentation: it may bind directly to a local emulator/frame source so local rendering does not have to route frame buffers through gRPC. That binding belongs in the host/composition or render-surface layer, not in ViewModels, and it does not allow UI code to mutate emulator devices.
External or remote UIs use the gRPC video service/stream APIs where direct in-process rendering is unavailable. The UI control layer does not hold direct references to live core devices. Generated gRPC clients are adapted behind ViewModel-facing abstractions so TR-MVVM-001 remains enforceable.
Every hardware component is an IDevice with a unique DeviceId. Devices are registered with the bus and optionally subscribe to the system clock.
IDevice
IClockedDevice - receives clock ticks
IAddressSpace - maps address ranges on the bus
IInterruptSource - can raise IRQ/NMI
IPeripheral - external device (drive, datasette)
Devices are wired together by an IArchitecture which describes:
- Which devices exist
- Address space mappings (including bank-switched overlays)
- Clock divisors and phase relationships
- Interrupt routing
The IBus provides a flat 64KB address space. Address decoding is performed by the architecture's PLA/banking logic, which routes reads and writes to the correct IAddressSpace implementor.
For the C64:
- RAM underlays the entire 64KB
- ROM (BASIC, KERNAL, CHARGEN) overlays configurable regions
- I/O area ($D000-$DFFF) maps to VIC-II, SID, CIA1, CIA2, color RAM
- PLA control lines (from CPU port at $0000/$0001) select the active configuration
All state changes flow through an IMutationQueue. Each mutation is a small struct describing: source device, target address/field, old value, new value, cycle timestamp.
Benefits:
- Auditing: full history of every state change
- Undo: reverse mutations for debugging
- Determinism: replay mutations to reproduce exact state
- Networking: serialize mutation stream for netplay
The queue is double-buffered: the emulation thread writes to the active buffer while consumers (UI, debugger, recorder) read the committed buffer.
High-frequency inter-device communication (interrupt signals, DMA requests, bus contention notifications) uses a lock-free pub/sub system.
-
IMessagePool: pre-allocates message slots to avoid allocation -
PayloadArena: bump allocator for variable-size payloads within a frame -
MessageHandle: reference-counted handle into the pool -
IPubSub: topic-based publish/subscribe with zero-copy delivery
The pool and arena reset at frame boundaries, making per-frame allocation effectively free.
The ISnapshot interface captures the complete machine state as a flat byte array. Snapshots are:
- Serializable: save/load to disk
- Comparable: byte-exact comparison for determinism testing
The StateWindow concept (configurable snapshot interval, history depth, and memory budget) is an unimplemented design proposal; see docs/StateWindow.md. The shipped rewind surface is the tick-history write-delta capture (TickHistoryRecorder in ViceSharp.Host, exposed through MonitorService).
The system clock drives all cycle-accurate behavior. Each IClockedDevice receives ticks at its configured rate (which may differ from the master clock via divisors).
For the C64:
- Master clock: ~985,248 Hz (PAL) or ~1,022,727 Hz (NTSC)
- CPU: master / 1 (same as master)
- VIC-II: master / 1 (interleaved with CPU via bus phases)
- SID: master / 1 (but updates at its own internal rate)
- CIA timers: count CPU cycles or external events
Architectures are defined as IArchitectureDescriptor implementations. The IArchitectureBuilder is the assembly boundary between the selected system core and concrete chip instances. It constructs a running IMachine from a descriptor:
- Select the machine profile's system-core definition
- Instantiate devices listed in the descriptor
- Wire chips, address spaces, buses, interrupt lines, clocks, ROM, and peripherals according to that system-core policy
- Register the system core and chips in the machine device registry
- Validate (no overlapping address ranges, required devices present)
The IArchitectureValidator catches configuration errors at build time, not runtime.
ViceSharp exports emulator output through one gRPC CaptureService surface
(GetCaptureCapabilities, CaptureFrame, StartCapture, StopCapture,
ListCaptures). The host implementation (CaptureServiceHost) routes each request
to a concrete recorder in ViceSharp.Core.Media:
| Facility | Format(s) | Recorder | Tee point |
|---|---|---|---|
| Screenshot (one-shot) |
png, bmp
|
FrameCapture.CaptureBgraAsync |
reads the committed frame buffer |
| Video (frame sequence) |
bmpseq (numbered 24-bit BMPs) |
FrameSequenceCapture (AllFrames / UniqueFrames) |
EmulatorRuntimeSession.CommitFrame |
| Video (muxed, with sound) |
mp4, mkv, avi
|
FfmpegVideoRecorder (external ffmpeg) |
CommitFrame (video) + CaptureAudioTap (audio) |
| Sound |
wav (16-bit PCM) |
WavAudioRecorder |
CaptureAudioTap in the SID -> output path |
Key design points:
-
Two tee points, one worker thread. Completed frames are teed from
CommitFrameand SID samples fromCaptureAudioTap, both on the emulation worker. A capture-only lock (_captureSync) keeps this off the lock-free UI frame-read path, and a recorder fault can never propagate onto the worker. -
IVideoCaptureSinkunifies the BMP-sequence and ffmpeg recorders so the session drives either through one surface. The ffmpeg recorder also implementsIAudioRecorder, so a single object receives both video frames and audio. -
External ffmpeg, not libav.
FfmpegVideoRecordermirrors VICE'sffmpegexedrv: it opens two loopback TCP servers, launchesffmpegas a client of both, and streams raw BGRA video + s16le PCM for muxing into the chosen container.FfmpegLocatorfinds the binary (PATH orVICESHARP_FFMPEG), and the muxed formats are advertised byGetCaptureCapabilitiesonly when ffmpeg is present. Launch happens off the session lock. -
Parity-preserving audio tap.
CaptureAudioTapis installed in the SID audio path only when a real audio device exists, so headless and test hosts keep the SID silent (no timing perturbation) and simply advertise no sound/muxed-video capture.
The capture client surface is exposed to the Avalonia UI through
IHostProtocolClient (Snapshot menu: Save screenshot, Record sound, Record video).
Generated from MCP requirements wiki export.
- Home
- Getting Started
- Architecture
- Requirements
- Reference