From 0e77377e342468710e3a99cde53bead3909511f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= Date: Sat, 17 Feb 2024 15:16:43 +0000 Subject: [PATCH 01/10] chore: initial support for step testing --- src/test.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/test.rs b/src/test.rs index ff38d784..a91683b1 100644 --- a/src/test.rs +++ b/src/test.rs @@ -42,6 +42,13 @@ pub fn run_test(rom_path: &str, max_cycles: Option, options: TestOptions) - game_boy } +pub fn run_step_test(rom_path: &str, addr: u16, options: TestOptions) -> GameBoy { + let mut game_boy = build_test(options); + game_boy.load_rom_file(rom_path, None); + game_boy.step_to(addr); + game_boy +} + pub fn run_serial_test(rom_path: &str, max_cycles: Option, options: TestOptions) -> String { let mut game_boy = run_test(rom_path, max_cycles, options); game_boy.serial().device().state() @@ -70,6 +77,16 @@ mod tests { assert_eq!(result, "cpu_instrs\n\n01:ok 02:ok 03:ok 04:ok 05:ok 06:ok 07:ok 08:ok 09:ok 10:ok 11:ok \n\nPassed all tests\n"); } + #[test] + fn test_blargg_cpu_instrs() { + let result = run_serial_test( + "res/roms/test/blargg/cpu/cpu_instrs.gb", + Some(300000000), + TestOptions::default(), + ); + assert_eq!(result, "cpu_instrs\n\n01:ok 02:ok 03:ok 04:ok 05:ok 06:ok 07:ok 08:ok 09:ok 10:ok 11:ok \n\nPassed all tests\n"); + } + #[test] fn test_blargg_instr_timing() { let result = run_serial_test( From 87f308be8f65722b75b2dfdfe00d1539c950b00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= Date: Sat, 17 Feb 2024 16:10:04 +0000 Subject: [PATCH 02/10] chore: initial boot state testing --- src/state.rs | 6 ++++++ src/test.rs | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/state.rs b/src/state.rs index d5972736..6c21430c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,4 +1,10 @@ //! System save state (BOS and [BESS](https://github.com/LIJI32/SameBoy/blob/master/BESS.md) formats) functions and structures. +//! +//! The BOS (Boytacean Save) format is a custom save state format that contains the emulator state and the frame buffer. +//! Its serialization includes header, info, image buffer and then a BESS (Best Effort Save State) footer with the state itself. +//! +//! The [BESS](https://github.com/LIJI32/SameBoy/blob/master/BESS.md) format is a format developed by the [SameBoy](https://sameboy.github.io/) emulator and is used to store the emulator state +//! in agnostic and compatible way. use std::{ convert::TryInto, diff --git a/src/test.rs b/src/test.rs index a91683b1..0c03599d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -65,16 +65,16 @@ pub fn run_image_test( #[cfg(test)] mod tests { - use super::{run_serial_test, TestOptions}; + use super::{run_serial_test, run_step_test, TestOptions}; #[test] - fn test_blargg_cpu_instrs() { - let result = run_serial_test( + fn test_boot_state() { + let result = run_step_test( "res/roms/test/blargg/cpu/cpu_instrs.gb", - Some(300000000), + 0x0100, TestOptions::default(), ); - assert_eq!(result, "cpu_instrs\n\n01:ok 02:ok 03:ok 04:ok 05:ok 06:ok 07:ok 08:ok 09:ok 10:ok 11:ok \n\nPassed all tests\n"); + assert_eq!(result.cpu_i().pc(), 0x0100); } #[test] From 57340842a379b13f44004b198908e0ee8f762c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= Date: Sat, 17 Feb 2024 22:26:02 +0000 Subject: [PATCH 03/10] chore: removed small TS complain --- frontends/web/ts/gb.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontends/web/ts/gb.ts b/frontends/web/ts/gb.ts index f691ce0d..18eb49cc 100644 --- a/frontends/web/ts/gb.ts +++ b/frontends/web/ts/gb.ts @@ -47,6 +47,9 @@ import info from "../package.json"; // eslint-disable-next-line @typescript-eslint/no-explicit-any declare const require: any; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +declare const process: any; + /** * The frequency at which the Game Boy emulator should * run "normally". From f15167e60eca6fea3f11914c1cf8bc1e9f0f5078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= Date: Sat, 17 Feb 2024 22:48:53 +0000 Subject: [PATCH 04/10] test: add more registers validation --- src/test.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test.rs b/src/test.rs index 0c03599d..cdb90449 100644 --- a/src/test.rs +++ b/src/test.rs @@ -75,6 +75,12 @@ mod tests { TestOptions::default(), ); assert_eq!(result.cpu_i().pc(), 0x0100); + assert_eq!(result.cpu_i().sp(), 0xfffe); + assert_eq!(result.cpu_i().af(), 0x01b0); + assert_eq!(result.cpu_i().bc(), 0x0013); + assert_eq!(result.cpu_i().de(), 0x00d8); + assert_eq!(result.cpu_i().hl(), 0x014d); + assert_eq!(result.cpu_i().ime(), false); } #[test] From 823fa0a219ad70014ebfb388e78147f8952f2ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= Date: Sun, 18 Feb 2024 19:54:33 +0000 Subject: [PATCH 05/10] chore: add new constants for ADDR values Also made some initial timer validation. --- src/consts.rs | 5 +++++ src/lib.rs | 1 + src/test.rs | 9 ++++++++- src/timer.rs | 21 ++++++++++++--------- 4 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 src/consts.rs diff --git a/src/consts.rs b/src/consts.rs new file mode 100644 index 00000000..f9479564 --- /dev/null +++ b/src/consts.rs @@ -0,0 +1,5 @@ +// Timer registers +pub const DIV_ADDR: u16 = 0xff04; +pub const TIMA_ADDR: u16 = 0xff05; +pub const TMA_ADDR: u16 = 0xff06; +pub const TAC_ADDR: u16 = 0xff07; diff --git a/src/lib.rs b/src/lib.rs index 720df47f..ea2923d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod apu; pub mod cheats; +pub mod consts; pub mod cpu; pub mod data; pub mod devices; diff --git a/src/test.rs b/src/test.rs index cdb90449..ae82c98b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -65,11 +65,13 @@ pub fn run_image_test( #[cfg(test)] mod tests { + use crate::consts::{DIV_ADDR, TAC_ADDR, TIMA_ADDR, TMA_ADDR}; + use super::{run_serial_test, run_step_test, TestOptions}; #[test] fn test_boot_state() { - let result = run_step_test( + let mut result = run_step_test( "res/roms/test/blargg/cpu/cpu_instrs.gb", 0x0100, TestOptions::default(), @@ -81,6 +83,11 @@ mod tests { assert_eq!(result.cpu_i().de(), 0x00d8); assert_eq!(result.cpu_i().hl(), 0x014d); assert_eq!(result.cpu_i().ime(), false); + + assert_eq!(result.mmu().read(DIV_ADDR), 0xab); + assert_eq!(result.mmu().read(TIMA_ADDR), 0x00); + assert_eq!(result.mmu().read(TMA_ADDR), 0x00); + assert_eq!(result.mmu().read(TAC_ADDR), 0xf8); } #[test] diff --git a/src/timer.rs b/src/timer.rs index b27c7073..3fa6ca26 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,4 +1,7 @@ -use crate::warnln; +use crate::{ + consts::{DIV_ADDR, TAC_ADDR, TIMA_ADDR, TMA_ADDR}, + warnln, +}; pub struct Timer { div: u8, @@ -70,13 +73,13 @@ impl Timer { pub fn read(&mut self, addr: u16) -> u8 { match addr { // 0xFF04 — DIV: Divider register - 0xff04 => self.div, + DIV_ADDR => self.div, // 0xFF05 — TIMA: Timer counter - 0xff05 => self.tima, + TIMA_ADDR => self.tima, // 0xFF06 — TMA: Timer modulo - 0xff06 => self.tma, + TMA_ADDR => self.tma, // 0xFF07 — TAC: Timer control - 0xff07 => self.tac, + TAC_ADDR => self.tac | 0xf8, _ => { warnln!("Reding from unknown Timer location 0x{:04x}", addr); 0xff @@ -87,13 +90,13 @@ impl Timer { pub fn write(&mut self, addr: u16, value: u8) { match addr { // 0xFF04 — DIV: Divider register - 0xff04 => self.div = 0, + DIV_ADDR => self.div = 0, // 0xFF05 — TIMA: Timer counter - 0xff05 => self.tima = value, + TIMA_ADDR => self.tima = value, // 0xFF06 — TMA: Timer modulo - 0xff06 => self.tma = value, + TMA_ADDR => self.tma = value, // 0xFF07 — TAC: Timer control - 0xff07 => { + TAC_ADDR => { self.tac = value; match value & 0x03 { 0x00 => self.tima_ratio = 1024, From cf8131945f6400b559ac5cf544daf8b490f456a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= Date: Sun, 18 Feb 2024 20:52:00 +0000 Subject: [PATCH 06/10] chore: improved clocking methods Made the testing of the timer work. --- src/gb.rs | 70 +++++++++++++++++------------- src/py.rs | 8 +++- src/python/boytacean/boytacean.pyi | 3 +- src/python/boytacean/gb.py | 7 ++- src/timer.rs | 10 +++++ 5 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/gb.rs b/src/gb.rs index 712877c5..6386b228 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -432,51 +432,40 @@ impl GameBoy { pub fn clock(&mut self) -> u16 { let cycles = self.cpu_clock() as u16; let cycles_n = cycles / self.multiplier() as u16; - if self.ppu_enabled { - self.ppu_clock(cycles_n); - } - if self.apu_enabled { - self.apu_clock(cycles_n); - } - if self.dma_enabled { - self.dma_clock(cycles); - } - if self.timer_enabled { - self.timer_clock(cycles); - } - if self.serial_enabled { - self.serial_clock(cycles); - } + self.clock_devices(cycles, cycles_n); cycles } /// Risky function that will clock the CPU multiple times /// allowing an undefined number of cycles to be executed /// in the other Game Boy components. + /// /// This can cause unwanted behaviour in components like /// the PPU where only one mode switch operation is expected /// per each clock call. - pub fn clock_m(&mut self, count: usize) -> u16 { + /// + /// At the end of this execution major synchronization issues + /// may arise, so use with caution. + pub fn clock_many(&mut self, count: usize) -> u16 { let mut cycles = 0u16; for _ in 0..count { cycles += self.cpu_clock() as u16; } let cycles_n = cycles / self.multiplier() as u16; - if self.ppu_enabled { - self.ppu_clock(cycles_n); - } - if self.apu_enabled { - self.apu_clock(cycles_n); - } - if self.dma_enabled { - self.dma_clock(cycles); - } - if self.timer_enabled { - self.timer_clock(cycles); - } - if self.serial_enabled { - self.serial_clock(cycles); + self.clock_devices(cycles, cycles_n); + cycles + } + + /// Function equivalent to `clock()` but that allows pre-emptive + /// breaking of the clock cycle loop if the PC (Program Counter) + /// reaches the provided address. + pub fn clock_step(&mut self, addr: u16) -> u16 { + let cycles = self.cpu_clock() as u16; + if self.cpu_i().pc() == addr { + return cycles; } + let cycles_n = cycles / self.multiplier() as u16; + self.clock_devices(cycles, cycles_n); cycles } @@ -503,7 +492,7 @@ impl GameBoy { pub fn step_to(&mut self, addr: u16) -> u32 { let mut cycles = 0u32; loop { - cycles += self.clock() as u32; + cycles += self.clock_step(addr) as u32; if self.cpu_i().pc() == addr { break; } @@ -511,6 +500,25 @@ impl GameBoy { cycles } + #[inline(always)] + fn clock_devices(&mut self, cycles: u16, cycles_n: u16) { + if self.ppu_enabled { + self.ppu_clock(cycles_n); + } + if self.apu_enabled { + self.apu_clock(cycles_n); + } + if self.dma_enabled { + self.dma_clock(cycles); + } + if self.timer_enabled { + self.timer_clock(cycles); + } + if self.serial_enabled { + self.serial_clock(cycles); + } + } + pub fn key_press(&mut self, key: PadKey) { self.pad().key_press(key); } diff --git a/src/py.rs b/src/py.rs index df4e73dd..2e948c70 100644 --- a/src/py.rs +++ b/src/py.rs @@ -66,8 +66,12 @@ impl GameBoy { self.system.clock() } - pub fn clock_m(&mut self, count: usize) -> u16 { - self.system.clock_m(count) + pub fn clock_many(&mut self, count: usize) -> u16 { + self.system.clock_many(count) + } + + pub fn clock_step(&mut self, addr: u16) -> u16 { + self.system.clock_step(addr) } pub fn clocks(&mut self, count: usize) -> u64 { diff --git a/src/python/boytacean/boytacean.pyi b/src/python/boytacean/boytacean.pyi index a1e4f1ee..b1aaa1e4 100644 --- a/src/python/boytacean/boytacean.pyi +++ b/src/python/boytacean/boytacean.pyi @@ -23,7 +23,8 @@ class GameBoy: def read_memory(self, addr: int) -> int: ... def write_memory(self, addr: int, value: int): ... def clock(self) -> int: ... - def clock_m(self, count: int) -> int: ... + def clock_many(self, count: int) -> int: ... + def clock_step(self, addr: int) -> int: ... def clocks(self, count: int) -> int: ... def next_frame(self) -> int: ... def step_to(self, addr: int) -> int: ... diff --git a/src/python/boytacean/gb.py b/src/python/boytacean/gb.py index 2dd84e68..ecf2292c 100644 --- a/src/python/boytacean/gb.py +++ b/src/python/boytacean/gb.py @@ -98,8 +98,11 @@ def write_memory(self, addr: int, value: int): def clock(self) -> int: return self._system.clock() - def clock_m(self, count: int) -> int: - return self._system.clock_m(count) + def clock_many(self, count: int) -> int: + return self._system.clock_many(count) + + def clock_step(self, addr: int) -> int: + return self._system.clock_step(addr) def clocks(self, count: int) -> int: return self._system.clocks(count) diff --git a/src/timer.rs b/src/timer.rs index 3fa6ca26..057cdeb9 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -135,6 +135,16 @@ impl Timer { pub fn set_div(&mut self, value: u8) { self.div = value; } + + #[inline(always)] + pub fn div_clock(&self) -> u16 { + self.div_clock + } + + #[inline(always)] + pub fn set_div_clock(&mut self, value: u16) { + self.div_clock = value; + } } impl Default for Timer { From 46b26c08545fd7e7fececd0d9eafb4cc54c9da2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= Date: Sun, 18 Feb 2024 23:17:45 +0000 Subject: [PATCH 07/10] chore: much improved error handling Support for some basic Python testing. Handling of loading error with proper error propagation. --- .gitignore | 3 ++ .gitlab-ci.yml | 11 +++++++ README.md | 6 ++++ frontends/libretro/src/lib.rs | 2 +- frontends/sdl/src/main.rs | 17 ++++++----- setup.py | 1 + src/gb.rs | 39 +++++++++++++++++------- src/py.rs | 14 ++++++--- src/python/boytacean/gb.py | 6 +++- src/python/boytacean/test/__init__.py | 0 src/python/boytacean/test/base.py | 21 +++++++++++++ src/test.rs | 43 +++++++++++++++++---------- 12 files changed, 125 insertions(+), 38 deletions(-) create mode 100644 src/python/boytacean/test/__init__.py create mode 100644 src/python/boytacean/test/base.py diff --git a/.gitignore b/.gitignore index c093af01..b61206a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc +*.pyd .DS_Store @@ -6,6 +7,8 @@ Cargo.lock /.vscode/settings.json +/.eggs +/.venv /.idea /ndk diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 40733f41..9b52b50d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -79,6 +79,17 @@ test-rust: - rustc --version - cargo test +test-pyo3: + stage: test + parallel: + matrix: + - RUST_VERSION: ["1.74.0"] + script: + - rustup toolchain install $RUST_VERSION + - rustup override set $RUST_VERSION + - rustc --version + - python setup.py test + deploy-netlify-preview: stage: deploy script: diff --git a/README.md b/README.md index cf6459a7..044f1c28 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,12 @@ cargo build pip install . ``` +or + +```bash +python setup.py install +``` + ### WASM for Node.js ```bash diff --git a/frontends/libretro/src/lib.rs b/frontends/libretro/src/lib.rs index cb8d838f..c1540645 100644 --- a/frontends/libretro/src/lib.rs +++ b/frontends/libretro/src/lib.rs @@ -385,7 +385,7 @@ pub unsafe extern "C" fn retro_load_game(game: *const RetroGameInfo) -> bool { instance.set_mode(mode); instance.reset(); instance.load(true); - instance.load_cartridge(rom); + instance.load_cartridge(rom).unwrap(); update_vars(); true } diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs index f7852aa2..1c073ab5 100644 --- a/frontends/sdl/src/main.rs +++ b/frontends/sdl/src/main.rs @@ -8,6 +8,7 @@ pub mod test; use audio::Audio; use boytacean::{ devices::{printer::PrinterDevice, stdout::StdoutDevice}, + error::Error, gb::{AudioProvider, GameBoy, GameBoyMode}, info::Info, pad::PadKey, @@ -225,7 +226,7 @@ impl Emulator { )); } - pub fn load_rom(&mut self, path: Option<&str>) { + pub fn load_rom(&mut self, path: Option<&str>) -> Result<(), Error> { let rom_path: &str = path.unwrap_or(&self.rom_path); let ram_path = replace_ext(rom_path, "sav").unwrap_or_else(|| "invalid".to_string()); let rom = self.system.load_rom_file( @@ -235,7 +236,7 @@ impl Emulator { } else { None }, - ); + )?; println!( "========= Cartridge =========\n{}\n=============================", rom @@ -253,12 +254,14 @@ impl Emulator { .to_str() .unwrap() .to_string(); + Ok(()) } - pub fn reset(&mut self) { + pub fn reset(&mut self) -> Result<(), Error> { self.system.reset(); self.system.load(true); - self.load_rom(None); + self.load_rom(None)?; + Ok(()) } pub fn apply_cheats(&mut self, cheats: &Vec) { @@ -418,7 +421,7 @@ impl Emulator { Event::KeyDown { keycode: Some(Keycode::R), .. - } => self.reset(), + } => self.reset().unwrap(), Event::KeyDown { keycode: Some(Keycode::B), .. @@ -528,7 +531,7 @@ impl Emulator { } self.system.reset(); self.system.load(true); - self.load_rom(Some(&filename)); + self.load_rom(Some(&filename)).unwrap(); } _ => (), } @@ -989,7 +992,7 @@ fn main() { }; let mut emulator = Emulator::new(game_boy, options); emulator.start(SCREEN_SCALE); - emulator.load_rom(Some(&args.rom_path)); + emulator.load_rom(Some(&args.rom_path)).unwrap(); emulator.apply_cheats(&args.cheats); emulator.toggle_palette(); diff --git a/setup.py b/setup.py index 51b98c15..f4997018 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ keywords="gameboy emulator rust", url="https://boytacean.joao.me", packages=["boytacean"], + test_suite="boytacean.test", package_dir={"": os.path.normpath("src/python")}, rust_extensions=[ setuptools_rust.RustExtension( diff --git a/src/gb.rs b/src/gb.rs index 6386b228..26082896 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -426,9 +426,20 @@ impl GameBoy { let rom = self.rom().clone(); self.reset(); self.load(true); - self.load_cartridge(rom); + self.load_cartridge(rom).unwrap(); } + /// Advance the clock of the system by one tick, this will + /// usually imply executing one CPU instruction and advancing + /// all the other components of the system by the required + /// amount of cycles. + /// + /// This method takes into account the current speed of the + /// system (single or double) and will execute the required + /// amount of cycles in the other components of the system + /// accordingly. + /// + /// The amount of cycles executed by the CPU is returned. pub fn clock(&mut self) -> u16 { let cycles = self.cpu_clock() as u16; let cycles_n = cycles / self.multiplier() as u16; @@ -1081,12 +1092,16 @@ impl GameBoy { Ok(()) } - pub fn load_cartridge(&mut self, rom: Cartridge) -> &mut Cartridge { + pub fn load_cartridge(&mut self, rom: Cartridge) -> Result<&mut Cartridge, Error> { self.mmu().set_rom(rom); - self.mmu().rom() + Ok(self.mmu().rom()) } - pub fn load_rom(&mut self, data: &[u8], ram_data: Option<&[u8]>) -> &mut Cartridge { + pub fn load_rom( + &mut self, + data: &[u8], + ram_data: Option<&[u8]>, + ) -> Result<&mut Cartridge, Error> { let mut rom = Cartridge::from_data(data); if let Some(ram_data) = ram_data { rom.set_ram_data(ram_data) @@ -1094,11 +1109,15 @@ impl GameBoy { self.load_cartridge(rom) } - pub fn load_rom_file(&mut self, path: &str, ram_path: Option<&str>) -> &mut Cartridge { - let data = read_file(path).unwrap(); + pub fn load_rom_file( + &mut self, + path: &str, + ram_path: Option<&str>, + ) -> Result<&mut Cartridge, Error> { + let data = read_file(path)?; match ram_path { Some(ram_path) => { - let ram_data = read_file(ram_path).unwrap(); + let ram_data = read_file(ram_path)?; self.load_rom(&data, Some(&ram_data)) } None => self.load_rom(&data, None), @@ -1184,12 +1203,12 @@ impl GameBoy { })); } - pub fn load_rom_wa(&mut self, data: &[u8]) -> Cartridge { - let rom = self.load_rom(data, None); + pub fn load_rom_wa(&mut self, data: &[u8]) -> Result { + let rom = self.load_rom(data, None).map_err(|e| e.to_string())?; rom.set_rumble_cb(|active| { rumble_callback(active); }); - rom.clone() + Ok(rom.clone()) } pub fn load_callbacks_wa(&mut self) { diff --git a/src/py.rs b/src/py.rs index 2e948c70..a9fb17fb 100644 --- a/src/py.rs +++ b/src/py.rs @@ -46,12 +46,18 @@ impl GameBoy { .map_err(PyErr::new::) } - pub fn load_rom(&mut self, data: &[u8]) { - self.system.load_rom(data, None); + pub fn load_rom(&mut self, data: &[u8]) -> PyResult<()> { + self.system + .load_rom(data, None) + .map(|_| ()) + .map_err(PyErr::new::) } - pub fn load_rom_file(&mut self, path: &str) { - self.system.load_rom_file(path, None); + pub fn load_rom_file(&mut self, path: &str) -> PyResult<()> { + self.system + .load_rom_file(path, None) + .map(|_| ()) + .map_err(PyErr::new::) } pub fn read_memory(&mut self, addr: u16) -> u8 { diff --git a/src/python/boytacean/gb.py b/src/python/boytacean/gb.py index ecf2292c..5e2b3e33 100644 --- a/src/python/boytacean/gb.py +++ b/src/python/boytacean/gb.py @@ -2,7 +2,11 @@ from contextlib import contextmanager from typing import Any, Iterable, Union, cast -from PIL.Image import Image, frombytes +try: + from PIL.Image import Image, frombytes +except ImportError: + Image = Any + frombytes = Any from .palettes import PALETTES from .video import VideoCapture diff --git a/src/python/boytacean/test/__init__.py b/src/python/boytacean/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/python/boytacean/test/base.py b/src/python/boytacean/test/base.py new file mode 100644 index 00000000..487f0230 --- /dev/null +++ b/src/python/boytacean/test/base.py @@ -0,0 +1,21 @@ +import unittest + +from os.path import dirname, realpath, join + +from boytacean import GameBoy + + +class BaseTest(unittest.TestCase): + + def test_pocket(self): + CURRENT_DIR = dirname(realpath(__file__)) + ROM_PATH = join(CURRENT_DIR, "../../../../res/roms/demo/pocket.gb") + FRAME_COUNT = 600 + LOAD_GRAPHICS = False + + gb = GameBoy( + apu_enabled=False, serial_enabled=False, load_graphics=LOAD_GRAPHICS + ) + gb.load_rom(ROM_PATH) + for _ in range(FRAME_COUNT): + gb.next_frame() diff --git a/src/test.rs b/src/test.rs index ae82c98b..6ebd856c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,5 +1,6 @@ use crate::{ devices::buffer::BufferDevice, + error::Error, gb::{GameBoy, GameBoyMode}, ppu::FRAME_BUFFER_SIZE, }; @@ -25,12 +26,16 @@ pub fn build_test(options: TestOptions) -> GameBoy { game_boy } -pub fn run_test(rom_path: &str, max_cycles: Option, options: TestOptions) -> GameBoy { +pub fn run_test( + rom_path: &str, + max_cycles: Option, + options: TestOptions, +) -> Result { let mut cycles = 0u64; let max_cycles = max_cycles.unwrap_or(u64::MAX); let mut game_boy = build_test(options); - game_boy.load_rom_file(rom_path, None); + game_boy.load_rom_file(rom_path, None)?; loop { cycles += game_boy.clock() as u64; @@ -39,28 +44,32 @@ pub fn run_test(rom_path: &str, max_cycles: Option, options: TestOptions) - } } - game_boy + Ok(game_boy) } -pub fn run_step_test(rom_path: &str, addr: u16, options: TestOptions) -> GameBoy { +pub fn run_step_test(rom_path: &str, addr: u16, options: TestOptions) -> Result { let mut game_boy = build_test(options); - game_boy.load_rom_file(rom_path, None); + game_boy.load_rom_file(rom_path, None)?; game_boy.step_to(addr); - game_boy + Ok(game_boy) } -pub fn run_serial_test(rom_path: &str, max_cycles: Option, options: TestOptions) -> String { - let mut game_boy = run_test(rom_path, max_cycles, options); - game_boy.serial().device().state() +pub fn run_serial_test( + rom_path: &str, + max_cycles: Option, + options: TestOptions, +) -> Result { + let mut game_boy = run_test(rom_path, max_cycles, options)?; + Ok(game_boy.serial().device().state()) } pub fn run_image_test( rom_path: &str, max_cycles: Option, options: TestOptions, -) -> [u8; FRAME_BUFFER_SIZE] { - let mut game_boy = run_test(rom_path, max_cycles, options); - *game_boy.frame_buffer() +) -> Result<[u8; FRAME_BUFFER_SIZE], Error> { + let mut game_boy = run_test(rom_path, max_cycles, options)?; + Ok(*game_boy.frame_buffer()) } #[cfg(test)] @@ -75,7 +84,9 @@ mod tests { "res/roms/test/blargg/cpu/cpu_instrs.gb", 0x0100, TestOptions::default(), - ); + ) + .unwrap(); + assert_eq!(result.cpu_i().pc(), 0x0100); assert_eq!(result.cpu_i().sp(), 0xfffe); assert_eq!(result.cpu_i().af(), 0x01b0); @@ -96,7 +107,8 @@ mod tests { "res/roms/test/blargg/cpu/cpu_instrs.gb", Some(300000000), TestOptions::default(), - ); + ) + .unwrap(); assert_eq!(result, "cpu_instrs\n\n01:ok 02:ok 03:ok 04:ok 05:ok 06:ok 07:ok 08:ok 09:ok 10:ok 11:ok \n\nPassed all tests\n"); } @@ -106,7 +118,8 @@ mod tests { "res/roms/test/blargg/instr_timing/instr_timing.gb", Some(50000000), TestOptions::default(), - ); + ) + .unwrap(); assert_eq!(result, "instr_timing\n\n\nPassed\n"); } } From 60e680a179ba14710cdd9a74e380a03af292dd15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= Date: Sun, 18 Feb 2024 23:21:50 +0000 Subject: [PATCH 08/10] chore: improved test code --- src/python/boytacean/test/base.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/python/boytacean/test/base.py b/src/python/boytacean/test/base.py index 487f0230..75973047 100644 --- a/src/python/boytacean/test/base.py +++ b/src/python/boytacean/test/base.py @@ -4,18 +4,17 @@ from boytacean import GameBoy +CURRENT_DIR = dirname(realpath(__file__)) +POCKET_ROM_PATH = join(CURRENT_DIR, "../../../../res/roms/demo/pocket.gb") class BaseTest(unittest.TestCase): def test_pocket(self): - CURRENT_DIR = dirname(realpath(__file__)) - ROM_PATH = join(CURRENT_DIR, "../../../../res/roms/demo/pocket.gb") - FRAME_COUNT = 600 - LOAD_GRAPHICS = False - gb = GameBoy( - apu_enabled=False, serial_enabled=False, load_graphics=LOAD_GRAPHICS + apu_enabled=False, serial_enabled=False, load_graphics=False ) - gb.load_rom(ROM_PATH) - for _ in range(FRAME_COUNT): + gb.load_rom(POCKET_ROM_PATH) + for _ in range(600): gb.next_frame() + + self.assertEqual(gb.rom_title, "POCKET-DEMO") From efd41b675d2568de6e2110e0419f4dab11846ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= Date: Sun, 18 Feb 2024 23:34:45 +0000 Subject: [PATCH 09/10] chore: add bash reference and encoding --- examples/python/bench.py | 3 +++ examples/python/pocket.py | 3 +++ examples/python/pocket_pyboy.py | 3 +++ examples/python/pocket_pyboy_iface.py | 3 +++ examples/python/pyboy/gamewrapper_tetris.py | 3 +++ setup.py | 3 +++ src/python/boytacean/__init__.py | 3 +++ src/python/boytacean/boytacean.pyi | 3 +++ src/python/boytacean/gb.py | 3 +++ src/python/boytacean/graphics.py | 3 +++ src/python/boytacean/palettes.py | 3 +++ src/python/boytacean/pyboy.py | 3 +++ src/python/boytacean/test/__init__.py | 2 ++ src/python/boytacean/test/base.py | 3 +++ src/python/boytacean/video.py | 3 +++ 15 files changed, 44 insertions(+) diff --git a/examples/python/bench.py b/examples/python/bench.py index c40e596b..e730f5e9 100644 --- a/examples/python/bench.py +++ b/examples/python/bench.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + from time import time from boytacean import GameBoy, CPU_FREQ from os.path import dirname, realpath, join diff --git a/examples/python/pocket.py b/examples/python/pocket.py index cc40f344..5c44432d 100644 --- a/examples/python/pocket.py +++ b/examples/python/pocket.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + from time import time from boytacean import GameBoy, VISUAL_FREQ from os.path import dirname, realpath, join, splitext, basename diff --git a/examples/python/pocket_pyboy.py b/examples/python/pocket_pyboy.py index 15438561..0d6f7de9 100644 --- a/examples/python/pocket_pyboy.py +++ b/examples/python/pocket_pyboy.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + from time import time from pyboy import PyBoy from os.path import dirname, realpath, join, splitext, basename diff --git a/examples/python/pocket_pyboy_iface.py b/examples/python/pocket_pyboy_iface.py index 8edc7ccf..58e673d6 100644 --- a/examples/python/pocket_pyboy_iface.py +++ b/examples/python/pocket_pyboy_iface.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + from time import time from boytacean import VISUAL_FREQ from boytacean.pyboy import PyBoy diff --git a/examples/python/pyboy/gamewrapper_tetris.py b/examples/python/pyboy/gamewrapper_tetris.py index dfc40796..197d344c 100644 --- a/examples/python/pyboy/gamewrapper_tetris.py +++ b/examples/python/pyboy/gamewrapper_tetris.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + import os import sys from typing import cast diff --git a/setup.py b/setup.py index f4997018..6d8a5627 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + import os import sys diff --git a/src/python/boytacean/__init__.py b/src/python/boytacean/__init__.py index 8e62b4e5..84ad1616 100644 --- a/src/python/boytacean/__init__.py +++ b/src/python/boytacean/__init__.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + from .gb import GameBoyMode, GameBoy from .palettes import PALETTES from .video import VideoCapture diff --git a/src/python/boytacean/boytacean.pyi b/src/python/boytacean/boytacean.pyi index b1aaa1e4..3c84c4ba 100644 --- a/src/python/boytacean/boytacean.pyi +++ b/src/python/boytacean/boytacean.pyi @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + __version__: str COMPILATION_DATE: str COMPILATION_TIME: str diff --git a/src/python/boytacean/gb.py b/src/python/boytacean/gb.py index 5e2b3e33..2186f6cb 100644 --- a/src/python/boytacean/gb.py +++ b/src/python/boytacean/gb.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + from enum import Enum from contextlib import contextmanager from typing import Any, Iterable, Union, cast diff --git a/src/python/boytacean/graphics.py b/src/python/boytacean/graphics.py index dedf2cb3..5b8a45e8 100644 --- a/src/python/boytacean/graphics.py +++ b/src/python/boytacean/graphics.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + from math import ceil from typing import Union from sdl2 import ( diff --git a/src/python/boytacean/palettes.py b/src/python/boytacean/palettes.py index 7f119777..8b755b6a 100644 --- a/src/python/boytacean/palettes.py +++ b/src/python/boytacean/palettes.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + BASIC_PALETTE = "ffffff,c0c0c0,606060,000000" HOGWARDS_PALETTE = "b6a571,8b7e56,554d35,201d13" CHRISTMAS_PALETTE = "e8e7df,8bab95,9e5c5e,534d57" diff --git a/src/python/boytacean/pyboy.py b/src/python/boytacean/pyboy.py index 41871aea..ca0005ff 100644 --- a/src/python/boytacean/pyboy.py +++ b/src/python/boytacean/pyboy.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + from enum import Enum from sys import modules from typing import IO, Any diff --git a/src/python/boytacean/test/__init__.py b/src/python/boytacean/test/__init__.py index e69de29b..5bcfe9b7 100644 --- a/src/python/boytacean/test/__init__.py +++ b/src/python/boytacean/test/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- diff --git a/src/python/boytacean/test/base.py b/src/python/boytacean/test/base.py index 75973047..0cdd3ebf 100644 --- a/src/python/boytacean/test/base.py +++ b/src/python/boytacean/test/base.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + import unittest from os.path import dirname, realpath, join diff --git a/src/python/boytacean/video.py b/src/python/boytacean/video.py index a1d03f4b..db15a222 100644 --- a/src/python/boytacean/video.py +++ b/src/python/boytacean/video.py @@ -1,3 +1,6 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + from os import remove from glob import glob from math import ceil From c5b615683889b6466fa3b23cbab52072a6758896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= Date: Sun, 18 Feb 2024 23:38:19 +0000 Subject: [PATCH 10/10] fix: changed python version to python3 in CI/CD --- .gitlab-ci.yml | 2 +- examples/python/bench.py | 3 --- examples/python/pocket.py | 3 --- examples/python/pocket_pyboy.py | 3 --- examples/python/pocket_pyboy_iface.py | 3 --- examples/python/pyboy/gamewrapper_tetris.py | 3 --- setup.py | 3 --- src/python/boytacean/__init__.py | 3 --- src/python/boytacean/boytacean.pyi | 3 --- src/python/boytacean/gb.py | 3 --- src/python/boytacean/graphics.py | 3 --- src/python/boytacean/palettes.py | 3 --- src/python/boytacean/pyboy.py | 3 --- src/python/boytacean/test/base.py | 3 --- src/python/boytacean/video.py | 3 --- 15 files changed, 1 insertion(+), 43 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9b52b50d..c7386e02 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -88,7 +88,7 @@ test-pyo3: - rustup toolchain install $RUST_VERSION - rustup override set $RUST_VERSION - rustc --version - - python setup.py test + - python3 setup.py test deploy-netlify-preview: stage: deploy diff --git a/examples/python/bench.py b/examples/python/bench.py index e730f5e9..c40e596b 100644 --- a/examples/python/bench.py +++ b/examples/python/bench.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - from time import time from boytacean import GameBoy, CPU_FREQ from os.path import dirname, realpath, join diff --git a/examples/python/pocket.py b/examples/python/pocket.py index 5c44432d..cc40f344 100644 --- a/examples/python/pocket.py +++ b/examples/python/pocket.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - from time import time from boytacean import GameBoy, VISUAL_FREQ from os.path import dirname, realpath, join, splitext, basename diff --git a/examples/python/pocket_pyboy.py b/examples/python/pocket_pyboy.py index 0d6f7de9..15438561 100644 --- a/examples/python/pocket_pyboy.py +++ b/examples/python/pocket_pyboy.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - from time import time from pyboy import PyBoy from os.path import dirname, realpath, join, splitext, basename diff --git a/examples/python/pocket_pyboy_iface.py b/examples/python/pocket_pyboy_iface.py index 58e673d6..8edc7ccf 100644 --- a/examples/python/pocket_pyboy_iface.py +++ b/examples/python/pocket_pyboy_iface.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - from time import time from boytacean import VISUAL_FREQ from boytacean.pyboy import PyBoy diff --git a/examples/python/pyboy/gamewrapper_tetris.py b/examples/python/pyboy/gamewrapper_tetris.py index 197d344c..dfc40796 100644 --- a/examples/python/pyboy/gamewrapper_tetris.py +++ b/examples/python/pyboy/gamewrapper_tetris.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - import os import sys from typing import cast diff --git a/setup.py b/setup.py index 6d8a5627..f4997018 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - import os import sys diff --git a/src/python/boytacean/__init__.py b/src/python/boytacean/__init__.py index 84ad1616..8e62b4e5 100644 --- a/src/python/boytacean/__init__.py +++ b/src/python/boytacean/__init__.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - from .gb import GameBoyMode, GameBoy from .palettes import PALETTES from .video import VideoCapture diff --git a/src/python/boytacean/boytacean.pyi b/src/python/boytacean/boytacean.pyi index 3c84c4ba..b1aaa1e4 100644 --- a/src/python/boytacean/boytacean.pyi +++ b/src/python/boytacean/boytacean.pyi @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - __version__: str COMPILATION_DATE: str COMPILATION_TIME: str diff --git a/src/python/boytacean/gb.py b/src/python/boytacean/gb.py index 2186f6cb..5e2b3e33 100644 --- a/src/python/boytacean/gb.py +++ b/src/python/boytacean/gb.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - from enum import Enum from contextlib import contextmanager from typing import Any, Iterable, Union, cast diff --git a/src/python/boytacean/graphics.py b/src/python/boytacean/graphics.py index 5b8a45e8..dedf2cb3 100644 --- a/src/python/boytacean/graphics.py +++ b/src/python/boytacean/graphics.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - from math import ceil from typing import Union from sdl2 import ( diff --git a/src/python/boytacean/palettes.py b/src/python/boytacean/palettes.py index 8b755b6a..7f119777 100644 --- a/src/python/boytacean/palettes.py +++ b/src/python/boytacean/palettes.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - BASIC_PALETTE = "ffffff,c0c0c0,606060,000000" HOGWARDS_PALETTE = "b6a571,8b7e56,554d35,201d13" CHRISTMAS_PALETTE = "e8e7df,8bab95,9e5c5e,534d57" diff --git a/src/python/boytacean/pyboy.py b/src/python/boytacean/pyboy.py index ca0005ff..41871aea 100644 --- a/src/python/boytacean/pyboy.py +++ b/src/python/boytacean/pyboy.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - from enum import Enum from sys import modules from typing import IO, Any diff --git a/src/python/boytacean/test/base.py b/src/python/boytacean/test/base.py index 0cdd3ebf..75973047 100644 --- a/src/python/boytacean/test/base.py +++ b/src/python/boytacean/test/base.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - import unittest from os.path import dirname, realpath, join diff --git a/src/python/boytacean/video.py b/src/python/boytacean/video.py index db15a222..a1d03f4b 100644 --- a/src/python/boytacean/video.py +++ b/src/python/boytacean/video.py @@ -1,6 +1,3 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - from os import remove from glob import glob from math import ceil