gb-emu/lib/src/lib.rs
2023-04-18 18:09:21 +10:00

212 lines
5.6 KiB
Rust

#![feature(exclusive_range_pattern, let_chains, bigint_helper_methods, step_trait)]
use crate::{processor::memory::Memory, util::pause};
use connect::{
AudioOutput, CameraWrapper, CameraWrapperRef, EmulatorMessage, EmulatorOptions, NoCamera,
PocketCamera, Renderer, RomFile, SerialTarget,
};
use once_cell::sync::OnceCell;
use processor::{
memory::{mmio::gpu::Colour, Rom},
Cpu, CpuSaveState,
};
use std::{
fs::{self},
io::{stdout, Write},
marker::PhantomData,
path::PathBuf,
process::exit,
str::FromStr,
sync::{mpsc::Receiver, Arc, Mutex},
};
pub mod connect;
mod constants;
mod processor;
pub mod util;
static VERBOSE: OnceCell<bool> = OnceCell::new();
pub const WIDTH: usize = 160;
pub const HEIGHT: usize = 144;
pub struct EmulatorCore<ColourFormat, R, C>
where
ColourFormat: From<Colour> + Clone,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static,
{
receiver: Receiver<EmulatorMessage>,
cpu: Cpu<ColourFormat, R, C>,
spooky: PhantomData<C>,
}
impl<ColourFormat, R, C> EmulatorCore<ColourFormat, R, C>
where
ColourFormat: From<Colour> + Clone,
R: Renderer<ColourFormat>,
C: PocketCamera + Send + 'static,
{
pub fn init(
receiver: Receiver<EmulatorMessage>,
mut options: EmulatorOptions<ColourFormat, R, C>,
) -> Self {
if options.verbose {
VERBOSE.set(true).unwrap();
}
let camera: CameraWrapperRef<C> = Arc::new(Mutex::new(CameraWrapper::new(options.camera)));
let rom = match options.rom {
RomFile::Path(path) => {
let maybe_save = if options.no_save {
None
} else {
Some(if let Some(path) = options.save_path {
PathBuf::from_str(&path).unwrap()
} else {
PathBuf::from_str(&path).unwrap().with_extension("sav")
})
};
match fs::read(path) {
Ok(data) => Rom::load(data, maybe_save, camera.clone()),
Err(e) => {
println!("Error reading ROM: {e}");
exit(1);
}
}
}
RomFile::Raw(data) => Rom::load(data, None, camera.clone()),
};
options.window.prepare(WIDTH, HEIGHT);
options
.window
.set_title(format!("{} on {}", rom.get_title(), rom.mbc_type()));
let bootrom_enabled = options.bootrom.is_some();
let bootrom: Option<Vec<u8>> = options.bootrom.map(|v| match v {
RomFile::Path(path) => match fs::read(path) {
Ok(data) => data,
Err(e) => {
println!("Error reading bootROM: {e}");
exit(1);
}
},
RomFile::Raw(data) => data,
});
Self::new(
receiver,
Cpu::new(
Memory::init(
bootrom,
rom,
options.window,
options.output,
options.serial_target,
options.tile_window,
camera,
),
bootrom_enabled,
),
)
}
fn new(receiver: Receiver<EmulatorMessage>, cpu: Cpu<ColourFormat, R, C>) -> Self {
Self {
receiver,
cpu,
spooky: PhantomData,
}
}
pub fn replace_output(&mut self, new: AudioOutput) {
self.cpu.memory.replace_output(new);
}
pub fn run(&mut self) {
self.process_messages();
self.run_cycle();
}
pub fn run_stepped(&mut self, step_size: usize) {
loop {
self.process_messages();
for _ in 0..step_size {
self.run_cycle();
}
stdout().flush().unwrap();
pause();
}
}
pub fn run_until_buffer_full(&mut self) {
while !self.cpu.memory.is_audio_buffer_full() {
self.run();
}
}
fn run_cycle(&mut self) {
self.cpu.exec_next();
}
fn process_messages(&mut self) {
while let Ok(msg) = self.receiver.try_recv() {
match msg {
EmulatorMessage::Stop => {
self.cpu.memory.flush_rom();
exit(0);
}
}
}
}
pub fn get_save_state(&self) -> CpuSaveState<ColourFormat, R>
where
ColourFormat: From<Colour> + Clone,
R: Renderer<ColourFormat>,
{
CpuSaveState::create(&self.cpu)
}
}
impl<ColourFormat, R> EmulatorCore<ColourFormat, R, NoCamera>
where
ColourFormat: From<Colour> + Clone,
R: Renderer<ColourFormat>,
{
pub fn from_save_state(
state: CpuSaveState<ColourFormat, R>,
rom: RomFile,
receiver: Receiver<EmulatorMessage>,
window: R,
output: AudioOutput,
serial_target: SerialTarget,
) -> Self {
let data = match rom {
RomFile::Path(path) => match fs::read(path) {
Ok(data) => data,
Err(e) => {
println!("Error reading ROM: {e}");
exit(1);
}
},
RomFile::Raw(data) => data,
};
Self {
receiver,
cpu: Cpu::from_save_state(
state,
data,
window,
output,
serial_target,
Arc::new(Mutex::new(CameraWrapper::new(NoCamera::default()))),
),
spooky: PhantomData,
}
}
}