diff --git a/gb-emu/src/main.rs b/gb-emu/src/main.rs index 8a440fa..5d37b25 100644 --- a/gb-emu/src/main.rs +++ b/gb-emu/src/main.rs @@ -11,6 +11,7 @@ use futures::executor; use gb_emu_lib::{ connect::{AudioOutput, EmulatorMessage, JoypadState, Renderer, RomFile}, util::scale_buffer, + EmulatorCore, }; use gilrs::{ ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks}, @@ -81,7 +82,6 @@ fn main() { bootrom_path: args.bootrom, connect_serial: args.connect_serial, verbose: args.verbose, - step_by: args.step_by, cycle_count: args.cycle_count, }; @@ -97,13 +97,22 @@ fn main() { let (output, _stream) = create_audio_output(); - gb_emu_lib::init( + let mut core = EmulatorCore::init( receiver, options, Box::new(WindowRenderer::new(factor, Some(Gilrs::new().unwrap()))), output, tile_window, ); + + match args.step_by { + Some(step_size) => loop { + core.run_stepped(step_size); + }, + None => loop { + core.run(); + }, + } } fn create_audio_output() -> (AudioOutput, Stream) { diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 8d4b6d0..9edb22f 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -6,10 +6,7 @@ bigint_helper_methods )] -use crate::{ - processor::memory::Memory, - util::{pause, print_cycles}, -}; +use crate::{processor::memory::Memory, util::pause}; use connect::{AudioOutput, EmulatorMessage, Renderer, RomFile}; use once_cell::sync::OnceCell; use processor::{memory::Rom, Cpu}; @@ -35,7 +32,6 @@ pub struct Options { pub bootrom_path: Option, pub connect_serial: bool, pub verbose: bool, - pub step_by: Option, pub cycle_count: bool, } @@ -47,113 +43,157 @@ static VERBOSE: OnceCell = OnceCell::new(); pub const WIDTH: usize = 160; pub const HEIGHT: usize = 144; -pub fn init( +pub struct EmulatorCore { receiver: Receiver, - options: Options, - mut window: Box, - output: AudioOutput, - tile_window: Option>, -) { - VERBOSE.set(options.verbose).unwrap(); + cpu: Cpu, + cycle_num: usize, + cycle_count: bool, +} - let rom = match options.rom { - RomFile::Path(path) => { - let maybe_save = if options.no_save { - None - } else { - Some(if let Some(path) = options.save_path { - PathBuf::from_str(&path).unwrap() +impl EmulatorCore { + pub fn init( + receiver: Receiver, + options: Options, + mut window: Box, + output: AudioOutput, + tile_window: Option>, + ) -> Self { + VERBOSE.set(options.verbose).unwrap(); + + let rom = match options.rom { + RomFile::Path(path) => { + let maybe_save = if options.no_save { + None } else { - PathBuf::from_str(&path).unwrap().with_extension("sav") - }) - }; + Some(if let Some(path) = options.save_path { + PathBuf::from_str(&path).unwrap() + } else { + PathBuf::from_str(&path).unwrap().with_extension("sav") + }) + }; + match fs::read(path) { + Ok(data) => Rom::load(data, maybe_save), + Err(e) => { + println!("Error reading ROM: {e}"); + exit(1); + } + } + } + RomFile::Raw(data) => Rom::load(data, None), + }; + + window.prepare(WIDTH, HEIGHT); + window.set_title(format!("{} on {}", rom.get_title(), rom.mbc_type())); + + let bootrom_enabled = options.bootrom_path.is_some(); + let bootrom: Option> = if let Some(path) = options.bootrom_path { match fs::read(path) { - Ok(data) => Rom::load(data, maybe_save), + Ok(data) => Some(data), Err(e) => { - println!("Error reading ROM: {e}"); - return; + println!("Error reading bootROM: {e}"); + exit(1); } } + } else { + None + }; + + Self::new( + receiver, + Cpu::new( + Memory::init( + bootrom, + rom, + window, + output, + options.connect_serial, + tile_window, + ), + bootrom_enabled, + ), + options.cycle_count, + ) + } + + fn new(receiver: Receiver, cpu: Cpu, cycle_count: bool) -> Self { + Self { + receiver, + cpu, + cycle_num: 0, + cycle_count, } - RomFile::Raw(data) => Rom::load(data, None), - }; + } - window.prepare(WIDTH, HEIGHT); - window.set_title(format!("{} on {}", rom.get_title(), rom.mbc_type())); - - let bootrom_enabled = options.bootrom_path.is_some(); - let bootrom: Option> = if let Some(path) = options.bootrom_path { - match fs::read(path) { - Ok(data) => Some(data), - Err(e) => { - println!("Error reading bootROM: {e}"); - return; - } + pub fn run(&mut self) { + self.process_messages(); + self.cycle_num += 1; + if self.cycle_count { + self.print_cycles(); } - } else { - None - }; + self.run_cycle(); + } - let mut cpu = Cpu::new( - Memory::init( - bootrom, - rom, - window, - output, - options.connect_serial, - tile_window, - ), - bootrom_enabled, - ); - - let mut cycle_num = 0; - - verbose_println!("\n\n Begin execution...\n"); - match options.step_by { - Some(step_size) => loop { - process_messages(&receiver, &mut cpu); + pub fn run_stepped(&mut self, step_size: usize) { + loop { + self.process_messages(); for _ in 0..step_size { - cycle_num += 1; - if options.cycle_count { - print_cycles(&cycle_num); + self.cycle_num += 1; + if self.cycle_count { + self.print_cycles(); } - run_cycle(&mut cpu); + self.run_cycle(); } - print!(" ...{cycle_num} cycles - press enter to continue\r"); + print!( + " ...{} cycles - press enter to continue\r", + self.cycle_num + ); stdout().flush().unwrap(); pause(); - }, - None => loop { - process_messages(&receiver, &mut cpu); - cycle_num += 1; - if options.cycle_count { - print_cycles(&cycle_num); - } - run_cycle(&mut cpu); - }, + } } -} -fn process_messages(receiver: &Receiver, cpu: &mut Cpu) { - while let Ok(msg) = receiver.try_recv() { - match msg { - EmulatorMessage::Stop => { - cpu.memory.flush_rom(); - exit(0); + pub fn run_until_buffer_full(&mut self) { + while !self.cpu.memory.is_audio_buffer_full() { + loop { + self.run(); } } } -} -fn run_cycle(cpu: &mut Cpu) { - let will_pause = unsafe { PAUSE_QUEUED }; - let pause_enabled = unsafe { PAUSE_ENABLED }; - cpu.exec_next(); - if !pause_enabled && cpu.reg.pc >= 0x100 { - unsafe { PAUSE_ENABLED = true }; + fn run_cycle(&mut self) { + let will_pause = unsafe { PAUSE_QUEUED }; + let pause_enabled = unsafe { PAUSE_ENABLED }; + self.cpu.exec_next(); + if !pause_enabled && self.cpu.reg.pc >= 0x100 { + unsafe { PAUSE_ENABLED = true }; + } + if will_pause { + pause_then_step(); + } } - if will_pause { - pause_then_step(); + + fn process_messages(&mut self) { + while let Ok(msg) = self.receiver.try_recv() { + match msg { + EmulatorMessage::Stop => { + self.cpu.memory.flush_rom(); + exit(0); + } + } + } + } + + fn print_cycles(&self) { + if self.cycle_num % 45678 != 0 { + return; + } + let instructions_per_second = 400000; + print!( + "cycle {} - approx {} seconds on real hardware\r", + self.cycle_num, + self.cycle_num / instructions_per_second + ); + stdout().flush().unwrap(); } } diff --git a/lib/src/processor/memory.rs b/lib/src/processor/memory.rs index 72c4d82..5473589 100644 --- a/lib/src/processor/memory.rs +++ b/lib/src/processor/memory.rs @@ -222,6 +222,10 @@ impl Memory { pub fn flush_rom(&mut self) { self.rom.flush(); } + + pub fn is_audio_buffer_full(&self) -> bool { + self.apu.is_buffer_full() + } } impl Cpu { diff --git a/lib/src/processor/memory/mmio/apu.rs b/lib/src/processor/memory/mmio/apu.rs index 70115be..2ce94b8 100644 --- a/lib/src/processor/memory/mmio/apu.rs +++ b/lib/src/processor/memory/mmio/apu.rs @@ -124,6 +124,10 @@ impl Apu { executor::block_on(self.output.send_rb.push_slice(&converted)).unwrap(); } + pub fn is_buffer_full(&self) -> bool { + self.output.send_rb.is_full() + } + pub fn get_register(&self, addr: Address) -> u8 { if self.apu_enable { self.make_register(addr) diff --git a/lib/src/util.rs b/lib/src/util.rs index 098ed2f..0c000b9 100644 --- a/lib/src/util.rs +++ b/lib/src/util.rs @@ -1,8 +1,5 @@ use crate::{processor::Direction, PAUSE_ENABLED, PAUSE_QUEUED, VERBOSE}; -use std::{ - io::{self, stdout, Write}, - mem::transmute, -}; +use std::{io, mem::transmute}; #[macro_export] macro_rules! verbose_println { @@ -45,19 +42,6 @@ pub(crate) fn pause() -> String { } } -pub(crate) fn print_cycles(cycles: &i32) { - if *cycles % 45678 != 0 { - return; - } - let instructions_per_second = 400000; - print!( - "cycle {} - approx {} seconds on real hardware\r", - cycles, - cycles / instructions_per_second - ); - stdout().flush().unwrap(); -} - pub(crate) fn is_verbose() -> bool { match VERBOSE.get() { Some(v) => *v,