From c10816c04822ff726d6382196bb8b3ac17f0db2e Mon Sep 17 00:00:00 2001 From: Rekai Musuka Date: Wed, 24 Nov 2021 22:12:26 -0400 Subject: [PATCH] chore: imrpove private and public APIs --- Cargo.lock | 1 + Cargo.toml | 1 + src/apu.rs | 6 +--- src/bus.rs | 8 ----- src/cartridge.rs | 6 +--- src/emu.rs | 93 ++++++++++++++++++++++++++++++++++++------------ src/main.rs | 13 ++++--- 7 files changed, 81 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef5a2da..6b439e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -810,6 +810,7 @@ dependencies = [ "pollster", "rodio", "rtrb", + "thiserror", "tracing", "tracing-subscriber", "wgpu", diff --git a/Cargo.toml b/Cargo.toml index e2faa5c..34c994a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ rtrb = "0.2" directories-next = "2.0" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } +thiserror = "1.0.30" [profile.release] debug = true diff --git a/src/apu.rs b/src/apu.rs index 1f455df..f581233 100644 --- a/src/apu.rs +++ b/src/apu.rs @@ -30,7 +30,7 @@ pub struct Apu { fs: FrameSequencer, div_prev: Option, - prod: Option>, + pub(crate) prod: Option>, sample_counter: u64, cap: f32, @@ -188,10 +188,6 @@ impl Apu { } } - pub fn attach_producer(&mut self, prod: SampleProducer) { - self.prod = Some(prod); - } - fn reset(&mut self) { self.ch1.sweep = Default::default(); self.ch1.duty = Default::default(); diff --git a/src/bus.rs b/src/bus.rs index 3e2059c..a994344 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -51,14 +51,6 @@ impl Bus { } } - pub(crate) fn load_cart(&mut self, rom: Vec) { - self.cart = Some(Cartridge::new(rom)); - } - - pub(crate) fn cart_title(&self) -> Option<&str> { - self.cart.as_ref()?.title() - } - #[allow(dead_code)] pub(crate) fn boot_mapped(&self) -> bool { self.boot.is_some() diff --git a/src/cartridge.rs b/src/cartridge.rs index d2cf3ca..35376ea 100644 --- a/src/cartridge.rs +++ b/src/cartridge.rs @@ -13,7 +13,7 @@ const ROM_TITLE_MAX_SIZE: usize = 16; #[derive(Debug, Default)] pub(crate) struct Cartridge { memory: Vec, - title: Option, + pub(crate) title: Option, mbc: Box, } @@ -80,10 +80,6 @@ impl Cartridge { } } - pub(crate) fn title(&self) -> Option<&str> { - self.title.as_deref() - } - fn detect_ram_info(memory: &[u8]) -> RamSize { let id = memory[RAM_SIZE_ADDRESS]; id.into() diff --git a/src/emu.rs b/src/emu.rs index a3fb4d8..df194fe 100644 --- a/src/emu.rs +++ b/src/emu.rs @@ -1,5 +1,6 @@ use crate::apu::gen::SampleProducer; use crate::bus::BOOT_SIZE; +use crate::cartridge::Cartridge; use crate::cpu::Cpu; use crate::{Cycle, GB_HEIGHT, GB_WIDTH}; use clap::crate_name; @@ -8,7 +9,9 @@ use std::fs::File; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use std::time::Duration; +use thiserror::Error; use winit::event::KeyboardInput; +use winit::event_loop::ControlFlow; pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED); pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines @@ -30,6 +33,12 @@ pub fn run_frame(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput) -> Cycl elapsed } +pub fn save_and_exit(cpu: &Cpu, control_flow: &mut ControlFlow) { + write_save(cpu); + *control_flow = ControlFlow::Exit; +} + +#[inline] pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { cpu.bus.ppu.frame_buf.as_ref() } @@ -39,50 +48,82 @@ pub fn from_boot_rom>(path: P) -> std::io::Result { } pub fn read_game_rom>(cpu: &mut Cpu, path: P) -> std::io::Result<()> { - Ok(cpu.bus.load_cart(std::fs::read(path.as_ref())?)) + cpu.bus.cart = Some(Cartridge::new(std::fs::read(path.as_ref())?)); + Ok(()) } -pub fn set_audio_producer(cpu: &mut Cpu, producer: SampleProducer) { - cpu.bus.apu.attach_producer(producer); +pub fn set_audio_prod(cpu: &mut Cpu, prod: SampleProducer) { + cpu.bus.apu.prod = Some(prod); } pub fn rom_title(cpu: &Cpu) -> &str { - cpu.bus.cart_title().unwrap_or(DEFAULT_TITLE) + cpu.bus + .cart + .as_ref() + .map(|c| c.title.as_deref()) + .flatten() + .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() { +pub fn write_save(cpu: &Cpu) { + match cpu.bus.cart.as_ref() { + Some(cart) => match write_save_to_file(cart) { + Ok(path) => tracing::info!("Wrote to save at {:?}", path), + Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to Save: {:?}", err), + Err(SaveError::Io(err)) => tracing::error!("{:?}", err), + }, + None => tracing::error!("No cartridge is currently present"), + } +} + +pub fn load_save(cpu: &mut Cpu) { + match cpu.bus.cart.as_mut() { + Some(cart) => match read_save_from_file(cart) { + Ok(path) => tracing::info!("Loaded save from {:?}", path), + Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to load save: {:?}", err), + Err(SaveError::Io(err)) => match err.kind() { + std::io::ErrorKind::NotFound => tracing::warn!("Save not found"), + _ => tracing::error!("{:?}", err), + }, + }, + None => tracing::error!("No cartridge is currently present"), + } +} + +fn write_save_to_file(cart: &Cartridge) -> Result { + match cart.title.as_ref().zip(cart.ext_ram()) { + Some((title, ram)) => { 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)?; + let mut file = File::create(&save_path)?; + file.write_all(ram)?; + Ok(save_path) } + None => Err(SaveError::NotApplicable), } - - Ok(()) } -pub fn load_save(cpu: &mut Cpu) -> std::io::Result<()> { - if let Some(cart) = &mut cpu.bus.cart { - if let Some(title) = cart.title() { +fn read_save_from_file(cart: &mut Cartridge) -> Result { + match cart.title.as_deref() { + Some(title) => { let mut save_path = data_path().unwrap_or_else(|| PathBuf::from(".")); save_path.push(title); save_path.set_extension("sav"); - if let Ok(mut file) = File::open(&save_path) { - tracing::info!("Load {:?}", save_path); + let mut file = File::open(&save_path)?; + let mut memory = Vec::new(); + file.read_to_end(&mut memory)?; - let mut memory = Vec::new(); - file.read_to_end(&mut memory)?; - cart.write_ext_ram(memory); - } + // FIXME: We call this whether we can write to Ext RAM or not. + // We should add a check that ensures that by this point we know whether + // the cartridge has external RAM or not. + cart.write_ext_ram(memory); + Ok(save_path) } + None => Err(SaveError::NotApplicable), } - - Ok(()) } fn read_boot>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> { @@ -103,3 +144,11 @@ fn data_path() -> Option { None => None, } } + +#[derive(Debug, Error)] +pub enum SaveError { + #[error("cartridge lacks title and/or external ram")] + NotApplicable, + #[error(transparent)] + Io(#[from] std::io::Error), +} diff --git a/src/main.rs b/src/main.rs index 29765fb..1497d38 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use gilrs::Gilrs; use rodio::{OutputStream, Sink}; use tracing_subscriber::EnvFilter; use winit::event::{Event, WindowEvent}; -use winit::event_loop::{ControlFlow, EventLoop}; +use winit::event_loop::EventLoop; const AUDIO_ENABLED: bool = true; @@ -86,9 +86,8 @@ fn main() { 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::load_save(&mut cpu).expect("Load save if exists"); + emu::load_save(&mut cpu); + let rom_title = emu::rom_title(&cpu).to_string(); tracing::info!("Initialize Gamepad"); @@ -106,7 +105,7 @@ fn main() { s }; - emu::set_audio_producer(&mut cpu, prod); + emu::set_audio_prod(&mut cpu, prod); tracing::info!("Spawn Audio Thread"); std::thread::spawn(move || { @@ -127,7 +126,7 @@ fn main() { match event { Event::MainEventsCleared => { if app.quit { - *control_flow = ControlFlow::Exit; + emu::save_and_exit(&cpu, control_flow); } gb::emu::run_frame(&mut cpu, &mut gamepad, last_key); @@ -187,7 +186,7 @@ fn main() { surface.configure(&device, &config); } WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; + emu::save_and_exit(&cpu, control_flow); } WindowEvent::KeyboardInput { input, .. } => last_key = input, _ => {}