gb-emu/lib/src/lib.rs

248 lines
6.6 KiB
Rust

#![feature(exclusive_range_pattern, let_chains, bigint_helper_methods)]
use crate::processor::{memory::Memory, Flags};
use connect::{
AudioOutput, CameraWrapper, EmulatorCoreTrait, EmulatorMessage, EmulatorOptions, PocketCamera,
RomFile,
};
use processor::{
memory::{mmio::gpu::Colour, rom::CgbRomType, OutputTargets},
Cpu,
};
use std::{
marker::PhantomData,
sync::{mpsc::Receiver, Arc, Mutex},
};
#[cfg(all(feature = "vulkan", feature = "pixels"))]
compile_error!("select only one rendering backend!");
#[allow(unused_attributes)]
#[cfg(any(feature = "vulkan-renderer", feature = "pixels-renderer"))]
#[cfg_attr(feature = "pixels-renderer", path = "renderer/pixels.rs")]
#[cfg_attr(feature = "vulkan-renderer", path = "renderer/vulkan/vulkan.rs")]
pub mod renderer;
#[cfg(feature = "config")]
pub mod config;
pub mod connect;
mod constants;
mod processor;
pub mod util;
pub const WIDTH: usize = 160;
pub const HEIGHT: usize = 144;
pub struct EmulatorCore<ColourFormat, C>
where
ColourFormat: From<Colour> + Copy,
C: PocketCamera + Send + 'static,
{
receiver: Receiver<EmulatorMessage>,
cpu: Cpu<ColourFormat, C>,
paused: bool,
spooky: PhantomData<C>,
}
impl<ColourFormat, C> EmulatorCore<ColourFormat, C>
where
ColourFormat: From<Colour> + Copy,
C: PocketCamera + Send + 'static,
{
pub fn init(
paused: bool,
receiver: Receiver<EmulatorMessage>,
options: EmulatorOptions<ColourFormat, C>,
camera: Arc<Mutex<CameraWrapper<C>>>,
) -> Self {
let rom = options.rom;
let is_cgb_mode = rom.rom_type == CgbRomType::CgbOnly || options.cgb_mode;
let bootrom = if is_cgb_mode {
options.cgb_bootrom.unwrap_or(RomFile::Raw(
include_bytes!("../../sameboy-bootroms/cgb_boot.bin").to_vec(),
))
} else {
options.dmg_bootrom.unwrap_or(RomFile::Raw(
include_bytes!("../../sameboy-bootroms/dmg_boot.bin").to_vec(),
))
}
.load_data()
.expect("Error loading bootrom!");
options
.window
.send(connect::RendererMessage::Prepare {
width: WIDTH,
height: HEIGHT,
})
.expect("message error");
options
.window
.send(connect::RendererMessage::SetTitle {
title: format!(
"{} on {} on {}",
rom.get_title(),
rom.mbc_type(),
if is_cgb_mode { "CGB" } else { "DMG" }
),
})
.expect("message error");
Self::new(
paused,
receiver,
Cpu::new(
Memory::init(
is_cgb_mode,
bootrom,
rom,
OutputTargets::new(
options.window,
options.output,
options.serial_target,
options.tile_window,
options.layer_window,
camera,
),
),
options.show_bootrom,
),
)
}
fn new(paused: bool, receiver: Receiver<EmulatorMessage>, cpu: Cpu<ColourFormat, C>) -> Self {
Self {
receiver,
cpu,
paused,
spooky: PhantomData,
}
}
fn print_flags(&self) -> String {
format!(
"{}{}{}{}",
if self.cpu.is_flag(Flags::Zero) {
"Z"
} else {
"-"
},
if self.cpu.is_flag(Flags::NSubtract) {
"N"
} else {
"-"
},
if self.cpu.is_flag(Flags::HalfCarry) {
"H"
} else {
"-"
},
if self.cpu.is_flag(Flags::Carry) {
"C"
} else {
"-"
},
)
}
fn run_cycle(&mut self) {
self.cpu.exec_next();
}
fn process_messages(&mut self) -> bool {
while let Ok(msg) = self.receiver.try_recv() {
if self.process_message(msg) {
return true;
}
}
while self.paused {
match self.receiver.recv() {
Ok(msg) => {
if self.process_message(msg) {
return true;
}
}
Err(e) => panic!("no message sender! error {e:#?}"),
}
}
false
}
fn process_message(&mut self, msg: EmulatorMessage) -> bool {
match msg {
EmulatorMessage::Exit => {
self.cpu.memory.flush_rom();
return true;
}
EmulatorMessage::Start => self.paused = false,
EmulatorMessage::Pause => self.paused = true,
EmulatorMessage::JoypadUpdate(new_state) => {
self.cpu.next_joypad_state = Some(new_state)
}
}
false
}
}
impl<ColourFormat, C> EmulatorCoreTrait for EmulatorCore<ColourFormat, C>
where
ColourFormat: From<Colour> + Copy,
C: PocketCamera + Send + 'static,
{
fn replace_output(&mut self, new: AudioOutput) {
self.cpu.memory.replace_output(new);
}
fn cycle_count(&self) -> usize {
self.cpu.cycle_count
}
fn pc(&self) -> u16 {
self.cpu.reg.pc
}
fn print_reg(&self) -> String {
format!(
"A:{:0>2X}, F:{}, BC:{:0>4X}, DE:{:0>4X}, HL:{:0>4X}, SP:{:0>4X}, PC:{:0>4X}\nLast instruction: {:0>2X} from 0x{:0>4X}",
self.cpu.reg.get_8(processor::Reg8::A),
self.print_flags(),
self.cpu.reg.bc,
self.cpu.reg.de,
self.cpu.reg.hl,
self.cpu.reg.sp,
self.cpu.reg.pc,
self.cpu.last_instruction,
self.cpu.last_instruction_addr
)
}
fn get_memory(&self, address: u16) -> u8 {
self.cpu.memory.get(address)
}
fn run(&mut self, cycles: usize) -> bool {
if self.process_messages() {
return true;
}
if !self.paused {
for _ in 0..cycles {
self.run_cycle();
}
}
false
}
fn run_until_buffer_full(&mut self) {
if self.process_messages() {
return;
}
while !self.cpu.memory.is_audio_buffer_full() {
self.run_cycle();
}
}
fn process_messages(&mut self) -> bool {
self.process_messages()
}
}