emulator core struct
This commit is contained in:
parent
4a2c15e5ec
commit
0459390d78
|
@ -11,6 +11,7 @@ use futures::executor;
|
||||||
use gb_emu_lib::{
|
use gb_emu_lib::{
|
||||||
connect::{AudioOutput, EmulatorMessage, JoypadState, Renderer, RomFile},
|
connect::{AudioOutput, EmulatorMessage, JoypadState, Renderer, RomFile},
|
||||||
util::scale_buffer,
|
util::scale_buffer,
|
||||||
|
EmulatorCore,
|
||||||
};
|
};
|
||||||
use gilrs::{
|
use gilrs::{
|
||||||
ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks},
|
ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks},
|
||||||
|
@ -81,7 +82,6 @@ fn main() {
|
||||||
bootrom_path: args.bootrom,
|
bootrom_path: args.bootrom,
|
||||||
connect_serial: args.connect_serial,
|
connect_serial: args.connect_serial,
|
||||||
verbose: args.verbose,
|
verbose: args.verbose,
|
||||||
step_by: args.step_by,
|
|
||||||
cycle_count: args.cycle_count,
|
cycle_count: args.cycle_count,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -97,13 +97,22 @@ fn main() {
|
||||||
|
|
||||||
let (output, _stream) = create_audio_output();
|
let (output, _stream) = create_audio_output();
|
||||||
|
|
||||||
gb_emu_lib::init(
|
let mut core = EmulatorCore::init(
|
||||||
receiver,
|
receiver,
|
||||||
options,
|
options,
|
||||||
Box::new(WindowRenderer::new(factor, Some(Gilrs::new().unwrap()))),
|
Box::new(WindowRenderer::new(factor, Some(Gilrs::new().unwrap()))),
|
||||||
output,
|
output,
|
||||||
tile_window,
|
tile_window,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
match args.step_by {
|
||||||
|
Some(step_size) => loop {
|
||||||
|
core.run_stepped(step_size);
|
||||||
|
},
|
||||||
|
None => loop {
|
||||||
|
core.run();
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_audio_output() -> (AudioOutput, Stream) {
|
fn create_audio_output() -> (AudioOutput, Stream) {
|
||||||
|
|
218
lib/src/lib.rs
218
lib/src/lib.rs
|
@ -6,10 +6,7 @@
|
||||||
bigint_helper_methods
|
bigint_helper_methods
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use crate::{
|
use crate::{processor::memory::Memory, util::pause};
|
||||||
processor::memory::Memory,
|
|
||||||
util::{pause, print_cycles},
|
|
||||||
};
|
|
||||||
use connect::{AudioOutput, EmulatorMessage, Renderer, RomFile};
|
use connect::{AudioOutput, EmulatorMessage, Renderer, RomFile};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use processor::{memory::Rom, Cpu};
|
use processor::{memory::Rom, Cpu};
|
||||||
|
@ -35,7 +32,6 @@ pub struct Options {
|
||||||
pub bootrom_path: Option<String>,
|
pub bootrom_path: Option<String>,
|
||||||
pub connect_serial: bool,
|
pub connect_serial: bool,
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
pub step_by: Option<usize>,
|
|
||||||
pub cycle_count: bool,
|
pub cycle_count: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,113 +43,157 @@ static VERBOSE: OnceCell<bool> = OnceCell::new();
|
||||||
pub const WIDTH: usize = 160;
|
pub const WIDTH: usize = 160;
|
||||||
pub const HEIGHT: usize = 144;
|
pub const HEIGHT: usize = 144;
|
||||||
|
|
||||||
pub fn init(
|
pub struct EmulatorCore {
|
||||||
receiver: Receiver<EmulatorMessage>,
|
receiver: Receiver<EmulatorMessage>,
|
||||||
options: Options,
|
cpu: Cpu,
|
||||||
mut window: Box<dyn Renderer>,
|
cycle_num: usize,
|
||||||
output: AudioOutput,
|
cycle_count: bool,
|
||||||
tile_window: Option<Box<dyn Renderer>>,
|
}
|
||||||
) {
|
|
||||||
VERBOSE.set(options.verbose).unwrap();
|
|
||||||
|
|
||||||
let rom = match options.rom {
|
impl EmulatorCore {
|
||||||
RomFile::Path(path) => {
|
pub fn init(
|
||||||
let maybe_save = if options.no_save {
|
receiver: Receiver<EmulatorMessage>,
|
||||||
None
|
options: Options,
|
||||||
} else {
|
mut window: Box<dyn Renderer>,
|
||||||
Some(if let Some(path) = options.save_path {
|
output: AudioOutput,
|
||||||
PathBuf::from_str(&path).unwrap()
|
tile_window: Option<Box<dyn Renderer>>,
|
||||||
|
) -> Self {
|
||||||
|
VERBOSE.set(options.verbose).unwrap();
|
||||||
|
|
||||||
|
let rom = match options.rom {
|
||||||
|
RomFile::Path(path) => {
|
||||||
|
let maybe_save = if options.no_save {
|
||||||
|
None
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from_str(&path).unwrap().with_extension("sav")
|
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),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error reading ROM: {e}");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RomFile::Raw(data) => Rom::load(data, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
window.prepare(WIDTH, HEIGHT);
|
||||||
|
window.set_title(format!("{} on {}", rom.get_title(), rom.mbc_type()));
|
||||||
|
|
||||||
|
let bootrom_enabled = options.bootrom_path.is_some();
|
||||||
|
let bootrom: Option<Vec<u8>> = if let Some(path) = options.bootrom_path {
|
||||||
match fs::read(path) {
|
match fs::read(path) {
|
||||||
Ok(data) => Rom::load(data, maybe_save),
|
Ok(data) => Some(data),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error reading ROM: {e}");
|
println!("Error reading bootROM: {e}");
|
||||||
return;
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::new(
|
||||||
|
receiver,
|
||||||
|
Cpu::new(
|
||||||
|
Memory::init(
|
||||||
|
bootrom,
|
||||||
|
rom,
|
||||||
|
window,
|
||||||
|
output,
|
||||||
|
options.connect_serial,
|
||||||
|
tile_window,
|
||||||
|
),
|
||||||
|
bootrom_enabled,
|
||||||
|
),
|
||||||
|
options.cycle_count,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(receiver: Receiver<EmulatorMessage>, cpu: Cpu, cycle_count: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
receiver,
|
||||||
|
cpu,
|
||||||
|
cycle_num: 0,
|
||||||
|
cycle_count,
|
||||||
}
|
}
|
||||||
RomFile::Raw(data) => Rom::load(data, None),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
window.prepare(WIDTH, HEIGHT);
|
pub fn run(&mut self) {
|
||||||
window.set_title(format!("{} on {}", rom.get_title(), rom.mbc_type()));
|
self.process_messages();
|
||||||
|
self.cycle_num += 1;
|
||||||
let bootrom_enabled = options.bootrom_path.is_some();
|
if self.cycle_count {
|
||||||
let bootrom: Option<Vec<u8>> = if let Some(path) = options.bootrom_path {
|
self.print_cycles();
|
||||||
match fs::read(path) {
|
|
||||||
Ok(data) => Some(data),
|
|
||||||
Err(e) => {
|
|
||||||
println!("Error reading bootROM: {e}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
self.run_cycle();
|
||||||
None
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let mut cpu = Cpu::new(
|
pub fn run_stepped(&mut self, step_size: usize) {
|
||||||
Memory::init(
|
loop {
|
||||||
bootrom,
|
self.process_messages();
|
||||||
rom,
|
|
||||||
window,
|
|
||||||
output,
|
|
||||||
options.connect_serial,
|
|
||||||
tile_window,
|
|
||||||
),
|
|
||||||
bootrom_enabled,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut cycle_num = 0;
|
|
||||||
|
|
||||||
verbose_println!("\n\n Begin execution...\n");
|
|
||||||
match options.step_by {
|
|
||||||
Some(step_size) => loop {
|
|
||||||
process_messages(&receiver, &mut cpu);
|
|
||||||
for _ in 0..step_size {
|
for _ in 0..step_size {
|
||||||
cycle_num += 1;
|
self.cycle_num += 1;
|
||||||
if options.cycle_count {
|
if self.cycle_count {
|
||||||
print_cycles(&cycle_num);
|
self.print_cycles();
|
||||||
}
|
}
|
||||||
run_cycle(&mut cpu);
|
self.run_cycle();
|
||||||
}
|
}
|
||||||
print!(" ...{cycle_num} cycles - press enter to continue\r");
|
print!(
|
||||||
|
" ...{} cycles - press enter to continue\r",
|
||||||
|
self.cycle_num
|
||||||
|
);
|
||||||
stdout().flush().unwrap();
|
stdout().flush().unwrap();
|
||||||
pause();
|
pause();
|
||||||
},
|
}
|
||||||
None => loop {
|
|
||||||
process_messages(&receiver, &mut cpu);
|
|
||||||
cycle_num += 1;
|
|
||||||
if options.cycle_count {
|
|
||||||
print_cycles(&cycle_num);
|
|
||||||
}
|
|
||||||
run_cycle(&mut cpu);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn process_messages(receiver: &Receiver<EmulatorMessage>, cpu: &mut Cpu) {
|
pub fn run_until_buffer_full(&mut self) {
|
||||||
while let Ok(msg) = receiver.try_recv() {
|
while !self.cpu.memory.is_audio_buffer_full() {
|
||||||
match msg {
|
loop {
|
||||||
EmulatorMessage::Stop => {
|
self.run();
|
||||||
cpu.memory.flush_rom();
|
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn run_cycle(cpu: &mut Cpu) {
|
fn run_cycle(&mut self) {
|
||||||
let will_pause = unsafe { PAUSE_QUEUED };
|
let will_pause = unsafe { PAUSE_QUEUED };
|
||||||
let pause_enabled = unsafe { PAUSE_ENABLED };
|
let pause_enabled = unsafe { PAUSE_ENABLED };
|
||||||
cpu.exec_next();
|
self.cpu.exec_next();
|
||||||
if !pause_enabled && cpu.reg.pc >= 0x100 {
|
if !pause_enabled && self.cpu.reg.pc >= 0x100 {
|
||||||
unsafe { PAUSE_ENABLED = true };
|
unsafe { PAUSE_ENABLED = true };
|
||||||
|
}
|
||||||
|
if will_pause {
|
||||||
|
pause_then_step();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if will_pause {
|
|
||||||
pause_then_step();
|
fn process_messages(&mut self) {
|
||||||
|
while let Ok(msg) = self.receiver.try_recv() {
|
||||||
|
match msg {
|
||||||
|
EmulatorMessage::Stop => {
|
||||||
|
self.cpu.memory.flush_rom();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_cycles(&self) {
|
||||||
|
if self.cycle_num % 45678 != 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let instructions_per_second = 400000;
|
||||||
|
print!(
|
||||||
|
"cycle {} - approx {} seconds on real hardware\r",
|
||||||
|
self.cycle_num,
|
||||||
|
self.cycle_num / instructions_per_second
|
||||||
|
);
|
||||||
|
stdout().flush().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,6 +222,10 @@ impl Memory {
|
||||||
pub fn flush_rom(&mut self) {
|
pub fn flush_rom(&mut self) {
|
||||||
self.rom.flush();
|
self.rom.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_audio_buffer_full(&self) -> bool {
|
||||||
|
self.apu.is_buffer_full()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
|
|
|
@ -124,6 +124,10 @@ impl Apu {
|
||||||
executor::block_on(self.output.send_rb.push_slice(&converted)).unwrap();
|
executor::block_on(self.output.send_rb.push_slice(&converted)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_buffer_full(&self) -> bool {
|
||||||
|
self.output.send_rb.is_full()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_register(&self, addr: Address) -> u8 {
|
pub fn get_register(&self, addr: Address) -> u8 {
|
||||||
if self.apu_enable {
|
if self.apu_enable {
|
||||||
self.make_register(addr)
|
self.make_register(addr)
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
use crate::{processor::Direction, PAUSE_ENABLED, PAUSE_QUEUED, VERBOSE};
|
use crate::{processor::Direction, PAUSE_ENABLED, PAUSE_QUEUED, VERBOSE};
|
||||||
use std::{
|
use std::{io, mem::transmute};
|
||||||
io::{self, stdout, Write},
|
|
||||||
mem::transmute,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! verbose_println {
|
macro_rules! verbose_println {
|
||||||
|
@ -45,19 +42,6 @@ pub(crate) fn pause() -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn print_cycles(cycles: &i32) {
|
|
||||||
if *cycles % 45678 != 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let instructions_per_second = 400000;
|
|
||||||
print!(
|
|
||||||
"cycle {} - approx {} seconds on real hardware\r",
|
|
||||||
cycles,
|
|
||||||
cycles / instructions_per_second
|
|
||||||
);
|
|
||||||
stdout().flush().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_verbose() -> bool {
|
pub(crate) fn is_verbose() -> bool {
|
||||||
match VERBOSE.get() {
|
match VERBOSE.get() {
|
||||||
Some(v) => *v,
|
Some(v) => *v,
|
||||||
|
|
Loading…
Reference in a new issue