/** * GBA System Coordinator * * Wires up all subsystems and runs the main emulation loop. * The CPU runs until the next scheduled event, then the event fires * and may schedule further events. * * Execution is owned here, not by the CPU: timers, DMA, the PPU's scanline * chain, IRQ delivery and HALT all advance together. A debugger stops the * machine through `StopPredicate` — checked before each instruction and while * the CPU is halted — so a stop never charges a cycle for an instruction that * did not run, and the next `runFrame` finishes the same hardware frame. */ import { ArmCpu } from '@gba-kit/arm-emulator/arm-cpu'; import { Apu } from './apu/apu.js'; import { DmaController, type DmaTransferInfo } from './dma.js'; import { InputController } from './input.js'; import { InterruptController } from './interrupts.js'; import { Ppu } from './ppu/ppu.js'; import type { GbaSnapshot } from './savestate.js'; import { Scheduler } from './scheduler.js'; import { GbaSystemBus } from './system-bus.js'; import { TimerController } from './timers.js'; import { GbaButton } from './types.js'; /** PPU rendering interface */ export interface PpuInterface { /** Render a single scanline */ renderScanline(line: number, bus: GbaSystemBus): void; /** Called at VBlank start */ onVBlank?(): void; /** Get the framebuffer */ getFramebuffer(): Uint32Array; /** Reset */ reset(): void; } /** * Asked before every instruction, and while the CPU is halted before advancing to * the next event. Return true to stop: the instruction at `armCpu.registers[15]` * has NOT executed and no cycle has been charged. */ export type StopPredicate = () => boolean; /** * How a run ended: * - `done`: the requested extent (a frame, a scanline) completed; * - `stopped`: the predicate or a CPU debug hook stopped it first; * - `halted`: the CPU stopped itself at the sentinel return address and cannot continue; * - `stalled`: the CPU is halted and no event is scheduled to wake it. */ export type RunOutcome = 'done' | 'stopped' | 'halted' | 'stalled'; /** * A hardware event, delivered as it happens and carrying no timestamp: a sink that needs one * reads the machine's cycle, frame and scanline when it fires. */ export type HardwareEvent = { kind: 'irq-request'; flag: number; } | { kind: 'irq-enter'; pc: number; } | { kind: 'dma'; channel: number; info: DmaTransferInfo; } | { kind: 'mmio-write'; address: number; value: number; size: 1 | 2 | 4; } | { kind: 'vblank'; } | { kind: 'hblank'; scanline: number; } | { kind: 'halt'; }; export declare class Gba { #private; readonly scheduler: Scheduler; readonly interrupts: InterruptController; readonly timers: TimerController; readonly dma: DmaController; readonly input: InputController; readonly bus: GbaSystemBus; readonly ppu: Ppu; readonly apu: Apu; readonly armCpu: ArmCpu; constructor(); /** Load a ROM into the system */ loadRom(data: Uint8Array): void; /** Press a button */ pressButton(button: GbaButton): void; /** Release a button */ releaseButton(button: GbaButton): void; /** * Observe hardware events (interrupt requests and entries, DMA transfers, I/O * writes, VBlank/HBlank, halts). One sink, or null to stop observing; the * subsystems' hooks stay unset when nobody listens so the hot paths pay nothing. */ set onHardwareEvent(sink: ((event: HardwareEvent) => void) | null); get onHardwareEvent(): ((event: HardwareEvent) => void) | null; /** Hardware frames completed since reset (a frame ends when the scanline wraps to 0). */ get frameCount(): number; /** The scanline the PPU is on (0–227; 160–227 is VBlank). */ get scanline(): number; /** * Run until the current hardware frame ends (the scanline wraps to 0), or until * `shouldStop` says so. After a stop, the next call finishes the SAME frame: frames * stay aligned to the hardware however often the debugger interrupts them. */ runFrame(shouldStop?: StopPredicate): RunOutcome; /** Run until the PPU moves to the next scanline (or the frame ends), or until `shouldStop`. */ runScanline(shouldStop?: StopPredicate): RunOutcome; /** Serialize the entire emulator state to a snapshot. */ serialize(): GbaSnapshot; /** * Restore from a snapshot. ROM/BIOS must already be loaded. * * Scheduled events come back at exactly the cycles the snapshot recorded — only * their callbacks (which cannot be serialized) are reattached. Running K frames * from a restored snapshot therefore yields the same machine as running K frames * from the original — what replay-based rewind relies on. */ deserialize(snap: GbaSnapshot): void; /** Stop emulation */ stop(): void; /** Reset the entire system */ reset(): void; } //# sourceMappingURL=gba.d.ts.map