-
-
Notifications
You must be signed in to change notification settings - Fork 0
Lockstep Scheduler
References: docs/scheduler.md
The scheduler is the heart of RustyNES. It lives in crates/rustynes-core and owns the primary emulation run loop. The scheduler is what makes RustyNES cycle-accurate and capable of passing the most rigorous testing suites (Mesen2 / higan / ares bar).
In most simpler emulators, the CPU runs an instruction, counts the cycles, and then tells the PPU to "catch up." This creates race conditions when PPU events (like the Sprite Zero Hit) happen in the middle of a CPU instruction.
In RustyNES, one tick equals one PPU dot.
- The scheduler advances one PPU dot per
tick_one_dot(). - The CPU advances on every third dot (NTSC).
- The APU advances every other CPU cycle.
fn tick_one_dot(&mut self) {
self.ppu.tick(&mut self.ppu_bus);
self.dot_count += 1;
// ... CPU and APU scheduling based on dot_count ...
}To avoid complex borrow-checker issues, RustyNES uses a single mutable Bus struct that owns the PPU, APU, Mappers, WRAM, and Controllers. The CPU merely borrows a mutable reference to the Bus during its tick.
This guarantees:
- Safe memory access without
RefCelloverhead. - Perfect deterministic execution.
- Mid-instruction PPU events (like MMC3 IRQs at PPU dot 260 or mid-scanline scroll writes) work automatically without per-game hacks.
The forward roadmap includes a migration from the current "3 dots per CPU cycle" model to an integer master clock (fractional timebase) to close the remaining esoteric edge cases (e.g. PAL 3.2:1 ratios), cementing a 100% accurate phase alignment model.
RustyNES is a cycle-accurate Nintendo Entertainment System emulator written in pure Rust. Licensed under MIT OR Apache-2.0. | GitHub Repository | Web Demo