chore: remove Emulator struct
This commit is contained in:
parent
939c25ce1a
commit
2405fd027f
|
@ -19,7 +19,7 @@ pub struct Bus {
|
||||||
var_ram: VariableWorkRam,
|
var_ram: VariableWorkRam,
|
||||||
timer: Timer,
|
timer: Timer,
|
||||||
int: Interrupt,
|
int: Interrupt,
|
||||||
apu: Apu,
|
pub(crate) apu: Apu,
|
||||||
high_ram: HighRam,
|
high_ram: HighRam,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
pub(crate) joyp: Joypad,
|
pub(crate) joyp: Joypad,
|
||||||
|
@ -89,11 +89,6 @@ impl Bus {
|
||||||
self.oam_write_byte(dest_addr, byte);
|
self.oam_write_byte(dest_addr, byte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn apu_mut(&mut self) -> &mut Apu {
|
|
||||||
&mut self.apu
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bus {
|
impl Bus {
|
||||||
|
|
34
src/cpu.rs
34
src/cpu.rs
|
@ -5,7 +5,7 @@ use crate::Cycle;
|
||||||
use bitfield::bitfield;
|
use bitfield::bitfield;
|
||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct Cpu {
|
pub struct Cpu {
|
||||||
pub(crate) bus: Bus,
|
pub(crate) bus: Bus,
|
||||||
reg: Registers,
|
reg: Registers,
|
||||||
|
@ -15,29 +15,13 @@ pub struct Cpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
#[allow(dead_code)]
|
pub(crate) fn new(rom: [u8; BOOT_SIZE]) -> Self {
|
||||||
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 {
|
|
||||||
Self {
|
Self {
|
||||||
bus: Bus::with_boot(rom),
|
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 {
|
impl Cpu {
|
||||||
/// Fetch an [Instruction] from the memory bus
|
/// Fetch an [Instruction] from the memory bus
|
||||||
/// (4 cycles)
|
/// (4 cycles)
|
||||||
|
|
102
src/emu.rs
102
src/emu.rs
|
@ -15,86 +15,45 @@ 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
|
pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz
|
||||||
const DEFAULT_TITLE: &str = "DMG-01 Emulator";
|
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;
|
let mut elapsed = 0;
|
||||||
|
|
||||||
if let Some(event) = gamepad.next_event() {
|
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 {
|
while elapsed < CYCLES_IN_FRAME {
|
||||||
elapsed += emu.step();
|
elapsed += cpu.step();
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed
|
elapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pixel_buf(emu: &Emulator) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
|
pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
|
||||||
emu.cpu.bus.ppu.frame_buf.as_ref()
|
cpu.bus.ppu.frame_buf.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Emulator {
|
pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Cpu> {
|
||||||
cpu: Cpu,
|
Ok(Cpu::new(read_boot(path)?))
|
||||||
timestamp: Cycle,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Emulator {
|
pub fn read_game_rom<P: AsRef<Path>>(cpu: &mut Cpu, path: P) -> std::io::Result<()> {
|
||||||
fn default() -> Self {
|
Ok(cpu.bus.load_cart(std::fs::read(path.as_ref())?))
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Emulator {
|
pub fn set_audio_producer(cpu: &mut Cpu, producer: SampleProducer<f32>) {
|
||||||
pub fn new() -> Self {
|
cpu.bus.apu.attach_producer(producer);
|
||||||
Self {
|
}
|
||||||
cpu: Cpu::with_boot(*include_bytes!("../bin/bootix_dmg.bin")),
|
|
||||||
timestamp: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
|
pub fn rom_title(cpu: &Cpu) -> &str {
|
||||||
Ok(Self {
|
cpu.bus.cart_title().unwrap_or(DEFAULT_TITLE)
|
||||||
cpu: Cpu::with_boot(Self::read_boot(path)?),
|
}
|
||||||
timestamp: Default::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_boot<P: AsRef<Path>>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> {
|
pub fn write_save(cpu: &Cpu) -> std::io::Result<()> {
|
||||||
let mut buf = [0; BOOT_SIZE];
|
if let Some(ext_ram) = cpu.bus.cart.as_ref().map(|c| c.ext_ram()).flatten() {
|
||||||
let mut file = File::open(path.as_ref())?;
|
if let Some(title) = cpu.bus.cart_title() {
|
||||||
|
let mut save_path = data_path().unwrap_or_else(|| PathBuf::from("."));
|
||||||
file.read_exact(&mut buf)?;
|
|
||||||
Ok(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
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.push(title);
|
||||||
save_path.set_extension("sav");
|
save_path.set_extension("sav");
|
||||||
|
|
||||||
|
@ -104,12 +63,12 @@ impl Emulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_load_sav(&mut self) -> std::io::Result<()> {
|
pub fn load_save(cpu: &mut Cpu) -> std::io::Result<()> {
|
||||||
if let Some(cart) = &mut self.cpu.bus.cart {
|
if let Some(cart) = &mut cpu.bus.cart {
|
||||||
if let Some(title) = cart.title() {
|
if let Some(title) = cart.title() {
|
||||||
let mut save_path = Self::data_path().unwrap_or_else(|| PathBuf::from("."));
|
let mut save_path = data_path().unwrap_or_else(|| PathBuf::from("."));
|
||||||
save_path.push(title);
|
save_path.push(title);
|
||||||
save_path.set_extension("sav");
|
save_path.set_extension("sav");
|
||||||
|
|
||||||
|
@ -124,9 +83,17 @@ impl Emulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_path() -> Option<PathBuf> {
|
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())?;
|
||||||
|
|
||||||
|
file.read_exact(&mut buf)?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_path() -> Option<PathBuf> {
|
||||||
match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) {
|
match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) {
|
||||||
Some(dirs) => {
|
Some(dirs) => {
|
||||||
let data_local = dirs.data_local_dir();
|
let data_local = dirs.data_local_dir();
|
||||||
|
@ -135,5 +102,4 @@ impl Emulator {
|
||||||
}
|
}
|
||||||
None => None,
|
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 clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
|
||||||
use egui_wgpu_backend::RenderPass;
|
use egui_wgpu_backend::RenderPass;
|
||||||
use gb::emu::Emulator;
|
use gb::emu;
|
||||||
use gb::gui::GuiState;
|
use gb::gui::GuiState;
|
||||||
use gilrs::Gilrs;
|
use gilrs::Gilrs;
|
||||||
use rodio::{OutputStream, Sink};
|
use rodio::{OutputStream, Sink};
|
||||||
|
@ -63,33 +63,33 @@ fn main() {
|
||||||
|
|
||||||
// We interrupt your boiler plate to initialize the emulator so that
|
// We interrupt your boiler plate to initialize the emulator so that
|
||||||
// we can copy it's empty pixel buffer to the GPU
|
// 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) => {
|
Some(path) => {
|
||||||
tracing::info!("User-provided boot ROM");
|
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 => {
|
None => {
|
||||||
tracing::info!("Built-in boot ROM");
|
tracing::info!("Built-in boot ROM");
|
||||||
Emulator::new()
|
Default::default()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up the WGPU (and then EGUI) texture we'll be working with.
|
// Set up the WGPU (and then EGUI) texture we'll be working with.
|
||||||
let texture_size = gb::gui::texture_size();
|
let texture_size = gb::gui::texture_size();
|
||||||
let texture = gb::gui::create_texture(&device, 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);
|
let texture_id = gb::gui::expose_texture_to_egui(&mut render_pass, &device, &texture);
|
||||||
|
|
||||||
// Load ROM if filepath was provided
|
// Load ROM if filepath was provided
|
||||||
if let Some(path) = m.value_of("rom") {
|
if let Some(path) = m.value_of("rom") {
|
||||||
tracing::info!("User-provided cartridge 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
|
// Load Save File if it exists
|
||||||
// FIXME: Shouldn't the API be better than this?
|
// FIXME: Shouldn't the API be better than this?
|
||||||
emu.try_load_sav().expect("Load save if exists");
|
emu::load_save(&mut cpu).expect("Load save if exists");
|
||||||
let rom_title = emu.title().to_string();
|
let rom_title = emu::rom_title(&cpu).to_string();
|
||||||
|
|
||||||
tracing::info!("Initialize Gamepad");
|
tracing::info!("Initialize Gamepad");
|
||||||
let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
|
let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
|
||||||
|
@ -106,7 +106,7 @@ fn main() {
|
||||||
s
|
s
|
||||||
};
|
};
|
||||||
|
|
||||||
emu.set_prod(prod);
|
emu::set_audio_producer(&mut cpu, prod);
|
||||||
|
|
||||||
tracing::info!("Spawn Audio Thread");
|
tracing::info!("Spawn Audio Thread");
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
|
@ -130,14 +130,14 @@ fn main() {
|
||||||
*control_flow = ControlFlow::Exit;
|
*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();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
Event::RedrawRequested(..) => {
|
Event::RedrawRequested(..) => {
|
||||||
platform.update_time(start_time.elapsed().as_secs_f64());
|
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);
|
gb::gui::write_to_texture(&queue, &texture, data, texture_size);
|
||||||
|
|
||||||
let output_frame = match surface.get_current_texture() {
|
let output_frame = match surface.get_current_texture() {
|
||||||
|
|
Loading…
Reference in New Issue