chore: imrpove private and public APIs
This commit is contained in:
parent
2405fd027f
commit
c10816c048
|
@ -810,6 +810,7 @@ dependencies = [
|
||||||
"pollster",
|
"pollster",
|
||||||
"rodio",
|
"rodio",
|
||||||
"rtrb",
|
"rtrb",
|
||||||
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
|
|
|
@ -22,6 +22,7 @@ rtrb = "0.2"
|
||||||
directories-next = "2.0"
|
directories-next = "2.0"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
|
||||||
|
thiserror = "1.0.30"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub struct Apu {
|
||||||
fs: FrameSequencer,
|
fs: FrameSequencer,
|
||||||
div_prev: Option<u16>,
|
div_prev: Option<u16>,
|
||||||
|
|
||||||
prod: Option<SampleProducer<f32>>,
|
pub(crate) prod: Option<SampleProducer<f32>>,
|
||||||
sample_counter: u64,
|
sample_counter: u64,
|
||||||
|
|
||||||
cap: f32,
|
cap: f32,
|
||||||
|
@ -188,10 +188,6 @@ impl Apu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attach_producer(&mut self, prod: SampleProducer<f32>) {
|
|
||||||
self.prod = Some(prod);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.ch1.sweep = Default::default();
|
self.ch1.sweep = Default::default();
|
||||||
self.ch1.duty = Default::default();
|
self.ch1.duty = Default::default();
|
||||||
|
|
|
@ -51,14 +51,6 @@ impl Bus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_cart(&mut self, rom: Vec<u8>) {
|
|
||||||
self.cart = Some(Cartridge::new(rom));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn cart_title(&self) -> Option<&str> {
|
|
||||||
self.cart.as_ref()?.title()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn boot_mapped(&self) -> bool {
|
pub(crate) fn boot_mapped(&self) -> bool {
|
||||||
self.boot.is_some()
|
self.boot.is_some()
|
||||||
|
|
|
@ -13,7 +13,7 @@ const ROM_TITLE_MAX_SIZE: usize = 16;
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Cartridge {
|
pub(crate) struct Cartridge {
|
||||||
memory: Vec<u8>,
|
memory: Vec<u8>,
|
||||||
title: Option<String>,
|
pub(crate) title: Option<String>,
|
||||||
mbc: Box<dyn MBCIo>,
|
mbc: Box<dyn MBCIo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,10 +80,6 @@ impl Cartridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn title(&self) -> Option<&str> {
|
|
||||||
self.title.as_deref()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn detect_ram_info(memory: &[u8]) -> RamSize {
|
fn detect_ram_info(memory: &[u8]) -> RamSize {
|
||||||
let id = memory[RAM_SIZE_ADDRESS];
|
let id = memory[RAM_SIZE_ADDRESS];
|
||||||
id.into()
|
id.into()
|
||||||
|
|
93
src/emu.rs
93
src/emu.rs
|
@ -1,5 +1,6 @@
|
||||||
use crate::apu::gen::SampleProducer;
|
use crate::apu::gen::SampleProducer;
|
||||||
use crate::bus::BOOT_SIZE;
|
use crate::bus::BOOT_SIZE;
|
||||||
|
use crate::cartridge::Cartridge;
|
||||||
use crate::cpu::Cpu;
|
use crate::cpu::Cpu;
|
||||||
use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
|
use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
|
||||||
use clap::crate_name;
|
use clap::crate_name;
|
||||||
|
@ -8,7 +9,9 @@ use std::fs::File;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use thiserror::Error;
|
||||||
use winit::event::KeyboardInput;
|
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 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
|
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
|
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] {
|
pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
|
||||||
cpu.bus.ppu.frame_buf.as_ref()
|
cpu.bus.ppu.frame_buf.as_ref()
|
||||||
}
|
}
|
||||||
|
@ -39,50 +48,82 @@ pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Cpu> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_game_rom<P: AsRef<Path>>(cpu: &mut Cpu, path: P) -> std::io::Result<()> {
|
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())?))
|
cpu.bus.cart = Some(Cartridge::new(std::fs::read(path.as_ref())?));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_audio_producer(cpu: &mut Cpu, producer: SampleProducer<f32>) {
|
pub fn set_audio_prod(cpu: &mut Cpu, prod: SampleProducer<f32>) {
|
||||||
cpu.bus.apu.attach_producer(producer);
|
cpu.bus.apu.prod = Some(prod);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rom_title(cpu: &Cpu) -> &str {
|
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<()> {
|
pub fn write_save(cpu: &Cpu) {
|
||||||
if let Some(ext_ram) = cpu.bus.cart.as_ref().map(|c| c.ext_ram()).flatten() {
|
match cpu.bus.cart.as_ref() {
|
||||||
if let Some(title) = cpu.bus.cart_title() {
|
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<PathBuf, SaveError> {
|
||||||
|
match cart.title.as_ref().zip(cart.ext_ram()) {
|
||||||
|
Some((title, ram)) => {
|
||||||
let mut save_path = 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");
|
||||||
|
|
||||||
let mut file = File::create(save_path)?;
|
let mut file = File::create(&save_path)?;
|
||||||
file.write_all(ext_ram)?;
|
file.write_all(ram)?;
|
||||||
|
Ok(save_path)
|
||||||
}
|
}
|
||||||
|
None => Err(SaveError::NotApplicable),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_save(cpu: &mut Cpu) -> std::io::Result<()> {
|
fn read_save_from_file(cart: &mut Cartridge) -> Result<PathBuf, SaveError> {
|
||||||
if let Some(cart) = &mut cpu.bus.cart {
|
match cart.title.as_deref() {
|
||||||
if let Some(title) = cart.title() {
|
Some(title) => {
|
||||||
let mut save_path = 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");
|
||||||
|
|
||||||
if let Ok(mut file) = File::open(&save_path) {
|
let mut file = File::open(&save_path)?;
|
||||||
tracing::info!("Load {:?}", save_path);
|
let mut memory = Vec::new();
|
||||||
|
file.read_to_end(&mut memory)?;
|
||||||
|
|
||||||
let mut memory = Vec::new();
|
// FIXME: We call this whether we can write to Ext RAM or not.
|
||||||
file.read_to_end(&mut memory)?;
|
// We should add a check that ensures that by this point we know whether
|
||||||
cart.write_ext_ram(memory);
|
// the cartridge has external RAM or not.
|
||||||
}
|
cart.write_ext_ram(memory);
|
||||||
|
Ok(save_path)
|
||||||
}
|
}
|
||||||
|
None => Err(SaveError::NotApplicable),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_boot<P: AsRef<Path>>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> {
|
fn read_boot<P: AsRef<Path>>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> {
|
||||||
|
@ -103,3 +144,11 @@ fn data_path() -> Option<PathBuf> {
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum SaveError {
|
||||||
|
#[error("cartridge lacks title and/or external ram")]
|
||||||
|
NotApplicable,
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
}
|
||||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -8,7 +8,7 @@ use gilrs::Gilrs;
|
||||||
use rodio::{OutputStream, Sink};
|
use rodio::{OutputStream, Sink};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use winit::event::{Event, WindowEvent};
|
use winit::event::{Event, WindowEvent};
|
||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
use winit::event_loop::EventLoop;
|
||||||
|
|
||||||
const AUDIO_ENABLED: bool = true;
|
const AUDIO_ENABLED: bool = true;
|
||||||
|
|
||||||
|
@ -86,9 +86,8 @@ fn main() {
|
||||||
emu::read_game_rom(&mut cpu, 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
|
emu::load_save(&mut cpu);
|
||||||
// FIXME: Shouldn't the API be better than this?
|
|
||||||
emu::load_save(&mut cpu).expect("Load save if exists");
|
|
||||||
let rom_title = emu::rom_title(&cpu).to_string();
|
let rom_title = emu::rom_title(&cpu).to_string();
|
||||||
|
|
||||||
tracing::info!("Initialize Gamepad");
|
tracing::info!("Initialize Gamepad");
|
||||||
|
@ -106,7 +105,7 @@ fn main() {
|
||||||
s
|
s
|
||||||
};
|
};
|
||||||
|
|
||||||
emu::set_audio_producer(&mut cpu, prod);
|
emu::set_audio_prod(&mut cpu, prod);
|
||||||
|
|
||||||
tracing::info!("Spawn Audio Thread");
|
tracing::info!("Spawn Audio Thread");
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
|
@ -127,7 +126,7 @@ fn main() {
|
||||||
match event {
|
match event {
|
||||||
Event::MainEventsCleared => {
|
Event::MainEventsCleared => {
|
||||||
if app.quit {
|
if app.quit {
|
||||||
*control_flow = ControlFlow::Exit;
|
emu::save_and_exit(&cpu, control_flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
gb::emu::run_frame(&mut cpu, &mut gamepad, last_key);
|
gb::emu::run_frame(&mut cpu, &mut gamepad, last_key);
|
||||||
|
@ -187,7 +186,7 @@ fn main() {
|
||||||
surface.configure(&device, &config);
|
surface.configure(&device, &config);
|
||||||
}
|
}
|
||||||
WindowEvent::CloseRequested => {
|
WindowEvent::CloseRequested => {
|
||||||
*control_flow = ControlFlow::Exit;
|
emu::save_and_exit(&cpu, control_flow);
|
||||||
}
|
}
|
||||||
WindowEvent::KeyboardInput { input, .. } => last_key = input,
|
WindowEvent::KeyboardInput { input, .. } => last_key = input,
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
Loading…
Reference in New Issue