chore: begin refactor of public api
This commit is contained in:
		
							
								
								
									
										43
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								src/bus.rs
									
									
									
									
									
								
							| @@ -7,14 +7,13 @@ use crate::ppu::{Ppu, PpuMode}; | ||||
| use crate::serial::Serial; | ||||
| use crate::timer::Timer; | ||||
| use crate::work_ram::{VariableWorkRam, WorkRam}; | ||||
| use std::{fs::File, io::Read}; | ||||
|  | ||||
| const BOOT_ROM_SIZE: usize = 0x100; | ||||
| pub(crate) const BOOT_SIZE: usize = 0x100; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Bus { | ||||
|     boot: Option<[u8; BOOT_ROM_SIZE]>, // Boot ROM is 256b long | ||||
|     cartridge: Option<Cartridge>, | ||||
|     boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long | ||||
|     cart: Option<Cartridge>, | ||||
|     pub(crate) ppu: Ppu, | ||||
|     work_ram: WorkRam, | ||||
|     var_ram: VariableWorkRam, | ||||
| @@ -30,7 +29,7 @@ impl Default for Bus { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             boot: None, | ||||
|             cartridge: None, | ||||
|             cart: None, | ||||
|             ppu: Default::default(), | ||||
|             work_ram: Default::default(), | ||||
|             var_ram: Default::default(), | ||||
| @@ -45,25 +44,19 @@ impl Default for Bus { | ||||
| } | ||||
|  | ||||
| impl Bus { | ||||
|     pub(crate) fn with_boot(path: &str) -> anyhow::Result<Self> { | ||||
|         let mut file = File::open(path)?; | ||||
|         let mut boot_rom = [0u8; 256]; | ||||
|  | ||||
|         file.read_exact(&mut boot_rom)?; | ||||
|  | ||||
|         Ok(Self { | ||||
|             boot: Some(boot_rom), | ||||
|     pub(crate) fn with_boot(rom: [u8; 256]) -> Self { | ||||
|         Self { | ||||
|             boot: Some(rom), | ||||
|             ..Default::default() | ||||
|         }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { | ||||
|         self.cartridge = Some(Cartridge::new(path)?); | ||||
|         Ok(()) | ||||
|     pub(crate) fn load_cart(&mut self, rom: Vec<u8>) { | ||||
|         self.cart = Some(Cartridge::new(rom)); | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn rom_title(&self) -> Option<&str> { | ||||
|         self.cartridge.as_ref()?.title() | ||||
|     pub(crate) fn cart_title(&self) -> Option<&str> { | ||||
|         self.cart.as_ref()?.title() | ||||
|     } | ||||
|  | ||||
|     #[allow(dead_code)] | ||||
| @@ -104,13 +97,13 @@ impl Bus { | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 match self.cartridge.as_ref() { | ||||
|                 match self.cart.as_ref() { | ||||
|                     Some(cart) => cart.read_byte(addr), | ||||
|                     None => panic!("Tried to read from a non-existent cartridge"), | ||||
|                 } | ||||
|             } | ||||
|             0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM | ||||
|             0xA000..=0xBFFF => match self.cartridge.as_ref() { | ||||
|             0xA000..=0xBFFF => match self.cart.as_ref() { | ||||
|                 // 8KB External RAM | ||||
|                 Some(cart) => cart.read_byte(addr), | ||||
|                 None => panic!("Tried to read from a non-existent cartridge"), | ||||
| @@ -157,7 +150,7 @@ impl BusIo for Bus { | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 match self.cartridge.as_ref() { | ||||
|                 match self.cart.as_ref() { | ||||
|                     Some(cart) => cart.read_byte(addr), | ||||
|                     None => panic!("Tried to read from a non-existent cartridge"), | ||||
|                 } | ||||
| @@ -169,7 +162,7 @@ impl BusIo for Bus { | ||||
|                     _ => self.ppu.read_byte(addr), | ||||
|                 } | ||||
|             } | ||||
|             0xA000..=0xBFFF => match self.cartridge.as_ref() { | ||||
|             0xA000..=0xBFFF => match self.cart.as_ref() { | ||||
|                 // 8KB External RAM | ||||
|                 Some(cart) => cart.read_byte(addr), | ||||
|                 None => panic!("Tried to read from a non-existent cartridge"), | ||||
| @@ -262,7 +255,7 @@ impl BusIo for Bus { | ||||
|             0x0000..=0x7FFF => { | ||||
|                 // 16KB ROM bank 00 (ends at 0x3FFF) | ||||
|                 // and 16KB ROM Bank 01 -> NN (switchable via MB) | ||||
|                 match self.cartridge.as_mut() { | ||||
|                 match self.cart.as_mut() { | ||||
|                     Some(cart) => cart.write_byte(addr, byte), | ||||
|                     None => panic!("Tried to write into non-existent cartridge"), | ||||
|                 } | ||||
| @@ -276,7 +269,7 @@ impl BusIo for Bus { | ||||
|             } | ||||
|             0xA000..=0xBFFF => { | ||||
|                 // 8KB External RAM | ||||
|                 match self.cartridge.as_mut() { | ||||
|                 match self.cart.as_mut() { | ||||
|                     Some(cart) => cart.write_byte(addr, byte), | ||||
|                     None => panic!("Tried to write into non-existent cartridge"), | ||||
|                 } | ||||
|   | ||||
| @@ -1,7 +1,3 @@ | ||||
| use std::fs::File; | ||||
| use std::io::{self, Read}; | ||||
| use std::path::Path; | ||||
|  | ||||
| use crate::bus::BusIo; | ||||
|  | ||||
| const RAM_SIZE_ADDRESS: usize = 0x0149; | ||||
| @@ -17,19 +13,15 @@ pub(crate) struct Cartridge { | ||||
| } | ||||
|  | ||||
| impl Cartridge { | ||||
|     pub(crate) fn new<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<Self> { | ||||
|         let mut memory = vec![]; | ||||
|         let mut rom = File::open(path)?; | ||||
|         rom.read_to_end(&mut memory)?; | ||||
|  | ||||
|     pub(crate) fn new(memory: Vec<u8>) -> Self { | ||||
|         let title = Self::find_title(&memory); | ||||
|         eprintln!("Cartridge Title: {:?}", title); | ||||
|  | ||||
|         Ok(Self { | ||||
|         Self { | ||||
|             mbc: Self::detect_mbc(&memory), | ||||
|             title, | ||||
|             memory, | ||||
|         }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> { | ||||
|   | ||||
							
								
								
									
										35
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/cpu.rs
									
									
									
									
									
								
							| @@ -1,9 +1,6 @@ | ||||
| use crate::apu::Apu; | ||||
| use crate::bus::{Bus, BusIo}; | ||||
| use crate::bus::{Bus, BusIo, BOOT_SIZE}; | ||||
| use crate::instruction::Instruction; | ||||
| use crate::interrupt::{InterruptEnable, InterruptFlag}; | ||||
| use crate::joypad::Joypad; | ||||
| use crate::ppu::Ppu; | ||||
| use crate::Cycle; | ||||
| use bitfield::bitfield; | ||||
| use std::fmt::{Display, Formatter, Result as FmtResult}; | ||||
| @@ -18,7 +15,7 @@ pub struct Cpu { | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     pub fn new() -> Self { | ||||
|     pub(crate) fn without_boot() -> Self { | ||||
|         Self { | ||||
|             reg: Registers { | ||||
|                 a: 0x01, | ||||
| @@ -36,11 +33,11 @@ impl Cpu { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn boot_new(path: &str) -> anyhow::Result<Self> { | ||||
|         Ok(Self { | ||||
|             bus: Bus::with_boot(path)?, | ||||
|     pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self { | ||||
|         Self { | ||||
|             bus: Bus::with_boot(rom), | ||||
|             ..Default::default() | ||||
|         }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn ime(&self) -> ImeState { | ||||
| @@ -72,14 +69,6 @@ impl Cpu { | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { | ||||
|         self.bus.load_cartridge(path) | ||||
|     } | ||||
|  | ||||
|     pub fn rom_title(&self) -> Option<&str> { | ||||
|         self.bus.rom_title() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
| @@ -167,16 +156,12 @@ impl BusIo for Cpu { | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     pub fn ppu(&mut self) -> &Ppu { | ||||
|         &self.bus.ppu | ||||
|     pub(crate) fn bus(&self) -> &Bus { | ||||
|         &self.bus | ||||
|     } | ||||
|  | ||||
|     pub fn apu_mut(&mut self) -> &mut Apu { | ||||
|         &mut self.bus.apu | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn joypad_mut(&mut self) -> &mut Joypad { | ||||
|         &mut self.bus.joypad | ||||
|     pub(crate) fn bus_mut(&mut self) -> &mut Bus { | ||||
|         &mut self.bus | ||||
|     } | ||||
|  | ||||
|     fn handle_ei(&mut self) { | ||||
|   | ||||
							
								
								
									
										146
									
								
								src/emu.rs
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								src/emu.rs
									
									
									
									
									
								
							| @@ -1,8 +1,7 @@ | ||||
| use crate::cpu::Cpu as SM83; | ||||
| use crate::joypad; | ||||
| use crate::ppu::Ppu; | ||||
| use crate::Cycle; | ||||
| use anyhow::Result; | ||||
| use crate::apu::gen::SampleProducer; | ||||
| use crate::cpu::Cpu; | ||||
| use crate::joypad::{self, Joypad}; | ||||
| use crate::{Cycle, GB_HEIGHT, GB_WIDTH}; | ||||
| use gilrs::Gilrs; | ||||
| use std::time::Duration; | ||||
| use winit_input_helper::WinitInputHelper; | ||||
| @@ -12,56 +11,111 @@ 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 init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> { | ||||
|     let mut cpu = match boot_path { | ||||
|         Some(path) => SM83::boot_new(path)?, | ||||
|         None => SM83::new(), | ||||
|     }; | ||||
|  | ||||
|     eprintln!("Initialized GB Emulator"); | ||||
|  | ||||
|     cpu.load_cartridge(rom_path)?; | ||||
|     Ok(cpu) | ||||
| } | ||||
|  | ||||
| pub fn rom_title(game_boy: &SM83) -> &str { | ||||
|     game_boy.rom_title().unwrap_or(DEFAULT_TITLE) | ||||
| } | ||||
|  | ||||
| pub fn run( | ||||
|     game_boy: &mut SM83, | ||||
|     gamepad: &mut Gilrs, | ||||
|     input: &WinitInputHelper, | ||||
|     target: Cycle, | ||||
| ) -> Cycle { | ||||
| pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle { | ||||
|     let mut elapsed = 0; | ||||
|  | ||||
|     if let Some(event) = gamepad.next_event() { | ||||
|         joypad::handle_gamepad_input(game_boy.joypad_mut(), event); | ||||
|     } | ||||
|     joypad::handle_keyboard_input(game_boy.joypad_mut(), input); | ||||
|  | ||||
|     while elapsed < target { | ||||
|         elapsed += game_boy.step(); | ||||
|         joypad::handle_gamepad_input(emu.joyp_mut(), event); | ||||
|     } | ||||
|  | ||||
|     elapsed | ||||
| } | ||||
|  | ||||
| pub fn run_frame(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle { | ||||
|     let mut elapsed = 0; | ||||
|  | ||||
|     if let Some(event) = gamepad.next_event() { | ||||
|         joypad::handle_gamepad_input(game_boy.joypad_mut(), event); | ||||
|     } | ||||
|     joypad::handle_keyboard_input(game_boy.joypad_mut(), input); | ||||
|     joypad::handle_keyboard_input(emu.joyp_mut(), key); | ||||
|     while elapsed < CYCLES_IN_FRAME { | ||||
|         elapsed += game_boy.step(); | ||||
|         elapsed += emu.step(); | ||||
|     } | ||||
|  | ||||
|     elapsed | ||||
| } | ||||
|  | ||||
| pub fn draw(ppu: &Ppu, frame: &mut [u8]) { | ||||
|     ppu.copy_to_gui(frame); | ||||
| pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) { | ||||
|     buf.copy_from_slice(emu.cpu.bus().ppu.frame_buf()); | ||||
| } | ||||
|  | ||||
| pub struct Emulator { | ||||
|     cpu: Cpu, | ||||
|     timestamp: Cycle, | ||||
| } | ||||
|  | ||||
| impl Emulator { | ||||
|     fn new(cpu: Cpu) -> Self { | ||||
|         Self { | ||||
|             cpu, | ||||
|             timestamp: Default::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn step(&mut self) -> Cycle { | ||||
|         self.cpu.step() | ||||
|     } | ||||
|  | ||||
|     fn load_cart(&mut self, rom: Vec<u8>) { | ||||
|         self.cpu.bus_mut().load_cart(rom) | ||||
|     } | ||||
|  | ||||
|     fn joyp_mut(&mut self) -> &mut Joypad { | ||||
|         &mut self.cpu.bus_mut().joypad | ||||
|     } | ||||
|  | ||||
|     pub fn set_prod(&mut self, prod: SampleProducer<f32>) { | ||||
|         self.cpu.bus_mut().apu.attach_producer(prod) | ||||
|     } | ||||
|  | ||||
|     pub fn title(&self) -> &str { | ||||
|         self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub mod build { | ||||
|     use std::fs::File; | ||||
|     use std::io::{Read, Result}; | ||||
|     use std::path::Path; | ||||
|  | ||||
|     use crate::bus::BOOT_SIZE; | ||||
|     use crate::cpu::Cpu; | ||||
|  | ||||
|     use super::Emulator; | ||||
|  | ||||
|     #[derive(Debug, Default)] | ||||
|     pub struct EmulatorBuilder { | ||||
|         boot: Option<[u8; BOOT_SIZE]>, | ||||
|         cart: Option<Vec<u8>>, | ||||
|     } | ||||
|  | ||||
|     impl EmulatorBuilder { | ||||
|         pub fn new() -> Self { | ||||
|             Default::default() | ||||
|         } | ||||
|  | ||||
|         pub fn with_boot<P: AsRef<Path>>(mut self, path: P) -> Result<Self> { | ||||
|             let mut file = File::open(path.as_ref())?; | ||||
|  | ||||
|             let mut buf = [0x00; BOOT_SIZE]; | ||||
|             file.read_exact(&mut buf)?; | ||||
|  | ||||
|             self.boot = Some(buf); | ||||
|             Ok(self) | ||||
|         } | ||||
|  | ||||
|         pub fn with_cart<P: AsRef<Path>>(mut self, path: P) -> Result<Self> { | ||||
|             let mut file = File::open(path.as_ref())?; | ||||
|  | ||||
|             let mut buf = Vec::new(); | ||||
|             file.read_to_end(&mut buf)?; | ||||
|  | ||||
|             self.cart = Some(buf); | ||||
|             Ok(self) | ||||
|         } | ||||
|  | ||||
|         pub fn finish(mut self) -> Emulator { | ||||
|             let mut emu = Emulator::new(match self.boot { | ||||
|                 Some(rom) => Cpu::with_boot(rom), | ||||
|                 None => Cpu::without_boot(), | ||||
|             }); | ||||
|  | ||||
|             if let Some(rom) = self.cart.take() { | ||||
|                 emu.load_cart(rom) | ||||
|             } | ||||
|  | ||||
|             emu | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										38
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,5 +1,8 @@ | ||||
| use std::convert::TryInto; | ||||
|  | ||||
| use anyhow::{anyhow, Result}; | ||||
| use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; | ||||
| use gb::emu::build::EmulatorBuilder; | ||||
| use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH}; | ||||
| use gilrs::Gilrs; | ||||
| use pixels::{PixelsBuilder, SurfaceTexture}; | ||||
| @@ -38,13 +41,15 @@ fn main() -> Result<()> { | ||||
|         ) | ||||
|         .get_matches(); | ||||
|  | ||||
|     let rom_path = m | ||||
|         .value_of("rom") | ||||
|         .expect("Required value 'rom' was provided"); | ||||
|     let mut emu_build = | ||||
|         EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?; | ||||
|  | ||||
|     let mut game_boy = | ||||
|         gb::emu::init(m.value_of("boot"), rom_path).expect("Initialize DMG-01 Emulator"); | ||||
|     let rom_title = gb::emu::rom_title(&game_boy); | ||||
|     if let Some(path) = m.value_of("boot") { | ||||
|         emu_build = emu_build.with_boot(path)?; | ||||
|     } | ||||
|  | ||||
|     let mut emu = emu_build.finish(); | ||||
|     let rom_title = emu.title(); | ||||
|  | ||||
|     let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); | ||||
|  | ||||
| @@ -68,10 +73,14 @@ fn main() -> Result<()> { | ||||
|     if AUDIO_ENABLED { | ||||
|         let spsc: AudioSPSC<f32> = Default::default(); | ||||
|         let (prod, cons) = spsc.init(); | ||||
|         let sink = Sink::try_new(&stream_handle)?; | ||||
|         sink.append(cons); | ||||
|         sink.set_volume(0.1); // TODO: Is this the right way to go about this? | ||||
|         game_boy.apu_mut().attach_producer(prod); | ||||
|         let sink = { | ||||
|             let s = Sink::try_new(&stream_handle)?; | ||||
|             s.append(cons); | ||||
|             s.set_volume(0.1); | ||||
|             s | ||||
|         }; | ||||
|  | ||||
|         emu.set_prod(prod); | ||||
|  | ||||
|         std::thread::spawn(move || { | ||||
|             sink.sleep_until_end(); | ||||
| @@ -102,12 +111,17 @@ fn main() -> Result<()> { | ||||
|                 pixels.resize_surface(size.width, size.height); | ||||
|             } | ||||
|  | ||||
|             cycle_count += gb::emu::run_frame(&mut game_boy, &mut gamepad, &input); | ||||
|             cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input); | ||||
|  | ||||
|             if cycle_count >= gb::emu::CYCLES_IN_FRAME { | ||||
|                 cycle_count %= gb::emu::CYCLES_IN_FRAME; | ||||
|  | ||||
|                 gb::emu::draw(game_boy.ppu(), pixels.get_frame()); | ||||
|                 let buf: &mut [u8; GB_WIDTH * GB_HEIGHT * 4] = pixels | ||||
|                     .get_frame() | ||||
|                     .try_into() | ||||
|                     .expect("Size of Pixel Buffer is GB_WIDTH * GB_HEIGHT * 4"); | ||||
|  | ||||
|                 gb::emu::draw_frame(&emu, buf); | ||||
|                 window.request_redraw(); | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -418,8 +418,8 @@ impl Ppu { | ||||
|         self.frame_buf.swap_with_slice(&mut blank); | ||||
|     } | ||||
|  | ||||
|     pub fn copy_to_gui(&self, frame: &mut [u8]) { | ||||
|         frame.copy_from_slice(self.frame_buf.as_ref()); | ||||
|     pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { | ||||
|         &self.frame_buf | ||||
|     } | ||||
|  | ||||
|     fn clock_fifo(&mut self) -> Option<GrayShade> { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user