diff --git a/src/bus.rs b/src/bus.rs index 66bb7ff..3e2059c 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -19,7 +19,7 @@ pub struct Bus { var_ram: VariableWorkRam, timer: Timer, int: Interrupt, - apu: Apu, + pub(crate) apu: Apu, high_ram: HighRam, serial: Serial, pub(crate) joyp: Joypad, @@ -89,11 +89,6 @@ impl Bus { self.oam_write_byte(dest_addr, byte); } } - - #[inline] - pub(crate) fn apu_mut(&mut self) -> &mut Apu { - &mut self.apu - } } impl Bus { diff --git a/src/cpu.rs b/src/cpu.rs index 358339c..b516054 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -5,7 +5,7 @@ use crate::Cycle; use bitfield::bitfield; use std::fmt::{Display, Formatter, Result as FmtResult}; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct Cpu { pub(crate) bus: Bus, reg: Registers, @@ -15,29 +15,13 @@ pub struct Cpu { } impl Cpu { - #[allow(dead_code)] - pub(crate) fn without_boot() -> Self { - Self { - reg: Registers { - a: 0x01, - b: 0x00, - c: 0x13, - d: 0x00, - e: 0xD8, - h: 0x01, - l: 0x4D, - sp: 0xFFFE, - pc: 0x0100, - }, - flags: 0xb0.into(), - ..Default::default() - } - } - - pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self { + pub(crate) fn new(rom: [u8; BOOT_SIZE]) -> Self { Self { bus: Bus::with_boot(rom), - ..Default::default() + reg: Default::default(), + flags: Default::default(), + ime: Default::default(), + state: Default::default(), } } @@ -69,6 +53,12 @@ impl Cpu { } } +impl Default for Cpu { + fn default() -> Self { + Cpu::new(*include_bytes!("../bin/bootix_dmg.bin")) + } +} + impl Cpu { /// Fetch an [Instruction] from the memory bus /// (4 cycles) diff --git a/src/emu.rs b/src/emu.rs index 5342de0..a3fb4d8 100644 --- a/src/emu.rs +++ b/src/emu.rs @@ -15,125 +15,91 @@ pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz const DEFAULT_TITLE: &str = "DMG-01 Emulator"; -pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: KeyboardInput) -> Cycle { +pub fn run_frame(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput) -> Cycle { let mut elapsed = 0; if let Some(event) = gamepad.next_event() { - crate::joypad::handle_gamepad_input(&mut emu.cpu.bus.joyp, event); + crate::joypad::handle_gamepad_input(&mut cpu.bus.joyp, event); } - crate::joypad::handle_keyboard_input(&mut emu.cpu.bus.joyp, key); + crate::joypad::handle_keyboard_input(&mut cpu.bus.joyp, key); while elapsed < CYCLES_IN_FRAME { - elapsed += emu.step(); + elapsed += cpu.step(); } elapsed } -pub fn pixel_buf(emu: &Emulator) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { - emu.cpu.bus.ppu.frame_buf.as_ref() +pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { + cpu.bus.ppu.frame_buf.as_ref() } -pub struct Emulator { - cpu: Cpu, - timestamp: Cycle, +pub fn from_boot_rom>(path: P) -> std::io::Result { + Ok(Cpu::new(read_boot(path)?)) } -impl Default for Emulator { - fn default() -> Self { - Self::new() - } +pub fn read_game_rom>(cpu: &mut Cpu, path: P) -> std::io::Result<()> { + Ok(cpu.bus.load_cart(std::fs::read(path.as_ref())?)) } -impl Emulator { - pub fn new() -> Self { - Self { - cpu: Cpu::with_boot(*include_bytes!("../bin/bootix_dmg.bin")), - timestamp: Default::default(), +pub fn set_audio_producer(cpu: &mut Cpu, producer: SampleProducer) { + cpu.bus.apu.attach_producer(producer); +} + +pub fn rom_title(cpu: &Cpu) -> &str { + cpu.bus.cart_title().unwrap_or(DEFAULT_TITLE) +} + +pub fn write_save(cpu: &Cpu) -> std::io::Result<()> { + if let Some(ext_ram) = cpu.bus.cart.as_ref().map(|c| c.ext_ram()).flatten() { + if let Some(title) = cpu.bus.cart_title() { + let mut save_path = data_path().unwrap_or_else(|| PathBuf::from(".")); + save_path.push(title); + save_path.set_extension("sav"); + + let mut file = File::create(save_path)?; + file.write_all(ext_ram)?; } } - pub fn from_boot_rom>(path: P) -> std::io::Result { - Ok(Self { - cpu: Cpu::with_boot(Self::read_boot(path)?), - timestamp: Default::default(), - }) - } + Ok(()) +} - fn read_boot>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> { - let mut buf = [0; BOOT_SIZE]; - let mut file = File::open(path.as_ref())?; +pub fn load_save(cpu: &mut Cpu) -> std::io::Result<()> { + if let Some(cart) = &mut cpu.bus.cart { + if let Some(title) = cart.title() { + let mut save_path = data_path().unwrap_or_else(|| PathBuf::from(".")); + save_path.push(title); + save_path.set_extension("sav"); - file.read_exact(&mut buf)?; - Ok(buf) - } + if let Ok(mut file) = File::open(&save_path) { + tracing::info!("Load {:?}", save_path); - fn step(&mut self) -> Cycle { - let cycles = self.cpu.step(); - self.timestamp += cycles; - cycles - } - - pub fn read_game_rom>(&mut self, path: P) -> std::io::Result<()> { - self.load_rom(std::fs::read(path.as_ref())?); - Ok(()) - } - - fn load_rom(&mut self, rom: Vec) { - self.cpu.bus.load_cart(rom); - } - - pub fn set_prod(&mut self, prod: SampleProducer) { - self.cpu.bus.apu_mut().attach_producer(prod) - } - - pub fn title(&self) -> &str { - self.cpu.bus.cart_title().unwrap_or(DEFAULT_TITLE) - } - - pub fn try_write_sav(&self) -> std::io::Result<()> { - if let Some(ext_ram) = self.cpu.bus.cart.as_ref().map(|c| c.ext_ram()).flatten() { - if let Some(title) = self.cpu.bus.cart_title() { - let mut save_path = Self::data_path().unwrap_or_else(|| PathBuf::from(".")); - save_path.push(title); - save_path.set_extension("sav"); - - let mut file = File::create(save_path)?; - file.write_all(ext_ram)?; + let mut memory = Vec::new(); + file.read_to_end(&mut memory)?; + cart.write_ext_ram(memory); } } - - Ok(()) } - pub fn try_load_sav(&mut self) -> std::io::Result<()> { - if let Some(cart) = &mut self.cpu.bus.cart { - if let Some(title) = cart.title() { - let mut save_path = Self::data_path().unwrap_or_else(|| PathBuf::from(".")); - save_path.push(title); - save_path.set_extension("sav"); + Ok(()) +} - if let Ok(mut file) = File::open(&save_path) { - tracing::info!("Load {:?}", save_path); +fn read_boot>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> { + let mut buf = [0; BOOT_SIZE]; + let mut file = File::open(path.as_ref())?; - let mut memory = Vec::new(); - file.read_to_end(&mut memory)?; - cart.write_ext_ram(memory); - } - } - } - - Ok(()) - } - - fn data_path() -> Option { - match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) { - Some(dirs) => { - let data_local = dirs.data_local_dir(); - std::fs::create_dir_all(data_local).ok()?; - Some(data_local.to_path_buf()) - } - None => None, + file.read_exact(&mut buf)?; + Ok(buf) +} + +fn data_path() -> Option { + match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) { + Some(dirs) => { + let data_local = dirs.data_local_dir(); + std::fs::create_dir_all(data_local).ok()?; + Some(data_local.to_path_buf()) } + None => None, } } diff --git a/src/main.rs b/src/main.rs index cdf3d37..29765fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::time::Instant; use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; use egui_wgpu_backend::RenderPass; -use gb::emu::Emulator; +use gb::emu; use gb::gui::GuiState; use gilrs::Gilrs; use rodio::{OutputStream, Sink}; @@ -63,33 +63,33 @@ fn main() { // We interrupt your boiler plate to initialize the emulator so that // we can copy it's empty pixel buffer to the GPU - let mut emu = match m.value_of("boot") { + let mut cpu = match m.value_of("boot") { Some(path) => { tracing::info!("User-provided boot ROM"); - Emulator::from_boot_rom(path).expect("initialize emulator with custom boot rom") + emu::from_boot_rom(path).expect("initialize emulator with custom boot rom") } None => { tracing::info!("Built-in boot ROM"); - Emulator::new() + Default::default() } }; // Set up the WGPU (and then EGUI) texture we'll be working with. let texture_size = gb::gui::texture_size(); let texture = gb::gui::create_texture(&device, texture_size); - gb::gui::write_to_texture(&queue, &texture, gb::emu::pixel_buf(&emu), texture_size); + gb::gui::write_to_texture(&queue, &texture, gb::emu::pixel_buf(&cpu), texture_size); let texture_id = gb::gui::expose_texture_to_egui(&mut render_pass, &device, &texture); // Load ROM if filepath was provided if let Some(path) = m.value_of("rom") { tracing::info!("User-provided cartridge ROM"); - emu.read_game_rom(path).expect("read game rom from path"); + emu::read_game_rom(&mut cpu, path).expect("read game rom from path"); } // Load Save File if it exists // FIXME: Shouldn't the API be better than this? - emu.try_load_sav().expect("Load save if exists"); - let rom_title = emu.title().to_string(); + emu::load_save(&mut cpu).expect("Load save if exists"); + let rom_title = emu::rom_title(&cpu).to_string(); tracing::info!("Initialize Gamepad"); let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); @@ -106,7 +106,7 @@ fn main() { s }; - emu.set_prod(prod); + emu::set_audio_producer(&mut cpu, prod); tracing::info!("Spawn Audio Thread"); std::thread::spawn(move || { @@ -130,14 +130,14 @@ fn main() { *control_flow = ControlFlow::Exit; } - gb::emu::run_frame(&mut emu, &mut gamepad, last_key); + gb::emu::run_frame(&mut cpu, &mut gamepad, last_key); window.request_redraw(); } Event::RedrawRequested(..) => { platform.update_time(start_time.elapsed().as_secs_f64()); - let data = gb::emu::pixel_buf(&emu); + let data = gb::emu::pixel_buf(&cpu); gb::gui::write_to_texture(&queue, &texture, data, texture_size); let output_frame = match surface.get_current_texture() {