Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repository = "https://github.com/ngraf3255/RbGB"
keywords = ["gamedev", "graphics", "emulator"]

[lib]
name = "rbgb_emulator"
name = "rbgb"
path = "src/lib.rs"

[[bin]]
Expand Down
72 changes: 70 additions & 2 deletions src/emulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ mod joypad;
mod mem;
mod sound;

/// High-level Game Boy emulator coordinator.
///
/// Owns the CPU, memory, graphics, and input subsystems and drives the
/// per-frame execution loop. The emulator starts paused and must be unpaused
/// (or a ROM loaded) before it will execute instructions.
///
/// This type exposes the core API used by the frontend to load ROMs, advance
/// frames, provide input, and read the display buffer.
pub struct Emulator {
screen: graphics::Screen,
cpu: cpu::CPU,
Expand All @@ -27,10 +35,23 @@ impl Default for Emulator {
}

impl Emulator {
/// Calls the needed functions once a frame
/// Maximum CPU cycles executed per frame.
///
/// This matches the approximate number of cycles a Game Boy runs in one
/// video frame. The update loop runs until this budget is reached.
const MAXCYCLES: u32 = 69905;
/// Target duration for a single frame (approx. 59.7 Hz).
///
/// The update loop sleeps to align with this duration after processing
/// enough CPU cycles.
const FRAME_DURATION: Duration = Duration::from_nanos(16_741_000);

/// Create a new emulator instance with initialized subsystems.
///
/// Memory is initialized to its startup state and the emulator begins
/// paused until a ROM is loaded or `toggle_pause` is called.
///
/// Returns a ready-to-use `Emulator` in the paused state.
pub fn new() -> Self {
let mem = Arc::new(Mutex::new(mem::Memory::new()));
mem.lock().unwrap().ram_startup();
Expand All @@ -43,6 +64,13 @@ impl Emulator {
}
}

/// Execute one frame of emulation if not paused.
///
/// Runs CPU instructions, updates timers and graphics, and handles
/// interrupts until the frame's cycle budget is consumed. The function then
/// sleeps to maintain the target frame rate.
///
/// Returns `()` and has no effect if the emulator is paused.
pub fn update(&mut self) {
if self.paused {
return;
Expand All @@ -63,14 +91,33 @@ impl Emulator {
}
}

/// Toggle the paused state.
///
/// When paused, `update` returns immediately without advancing emulation.
///
/// Takes `&mut self` and returns `()`.
pub fn toggle_pause(&mut self) {
self.paused = !self.paused;
}

/// Check whether emulation is currently paused.
///
/// Returns `true` if the emulator will skip work in `update`.
///
/// Takes `&self` and returns a `bool` indicating pause state.
pub fn is_paused(&self) -> bool {
self.paused
}

/// Load a ROM from disk and reset the CPU and memory state.
///
/// The ROM contents are copied into memory, memory is reinitialized, and
/// the CPU is reset. On success, the emulator is unpaused.
///
/// Parameters:
/// - `path`: filesystem path to the ROM file.
///
/// Returns `Ok(())` on success or a string I/O error on failure.
pub fn load_rom(&mut self, path: &str) -> Result<(), String> {
let data = std::fs::read(path).map_err(|e| e.to_string())?;
let mut mem = self.memory.lock().unwrap();
Expand All @@ -82,10 +129,22 @@ impl Emulator {
Ok(())
}

/// Borrow the current display buffer for rendering.
///
/// The buffer contains raw pixel data produced by the graphics subsystem.
/// Its length and format are determined by `graphics::Screen`.
///
/// Returns a borrowed `&[u8]` slice tied to the emulator's lifetime.
pub fn get_display_buffer(&self) -> &[u8] {
&self.screen.buffer
}

/// Dump key LCD and input registers for debugging.
///
/// This is only compiled in debug builds and logs directly to stdout via
/// `debug_println`. If not compiled for debug this function does nothing.
///
/// Takes `&self` and returns `()`. All details are dumped to the console
pub fn dump_lcd_mem(&self) {
#[cfg(debug_assertions)]
let mem = self.memory.lock().unwrap();
Expand Down Expand Up @@ -113,7 +172,16 @@ impl Emulator {
);
}

/// Handle input to the emulator
/// Handle input for the emulator's joypad.
///
/// Updates the joypad state and forwards the input to memory-mapped input
/// registers.
///
/// Parameters:
/// - `input`: which Game Boy control was pressed or released.
/// - `val`: the key state for that control.
///
/// Returns `()`.
pub fn game_input(&mut self, input: GameInput, val: KeyState) {
self.joypad.log_input(input, val)
}
Expand Down