chore: remove Emulator struct
This commit is contained in:
parent
939c25ce1a
commit
2405fd027f
|
@ -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 {
|
||||
|
|
34
src/cpu.rs
34
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)
|
||||
|
|
146
src/emu.rs
146
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<P: AsRef<Path>>(path: P) -> std::io::Result<Cpu> {
|
||||
Ok(Cpu::new(read_boot(path)?))
|
||||
}
|
||||
|
||||
impl Default for Emulator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
pub fn read_game_rom<P: AsRef<Path>>(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<f32>) {
|
||||
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<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
|
||||
Ok(Self {
|
||||
cpu: Cpu::with_boot(Self::read_boot(path)?),
|
||||
timestamp: Default::default(),
|
||||
})
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_boot<P: AsRef<Path>>(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<P: AsRef<Path>>(&mut self, path: P) -> std::io::Result<()> {
|
||||
self.load_rom(std::fs::read(path.as_ref())?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_rom(&mut self, rom: Vec<u8>) {
|
||||
self.cpu.bus.load_cart(rom);
|
||||
}
|
||||
|
||||
pub fn set_prod(&mut self, prod: SampleProducer<f32>) {
|
||||
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<P: AsRef<Path>>(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<PathBuf> {
|
||||
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<PathBuf> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
22
src/main.rs
22
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() {
|
||||
|
|
Loading…
Reference in New Issue