aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAstatin <[email protected]>2025-07-22 22:24:53 +0200
committerAstatin <[email protected]>2025-07-22 22:24:53 +0200
commite47f2eb23b624428323bcab0fb6781cc6d107e38 (patch)
tree99ed6c5f9550ac9f6cb9f3383ad0a565926bdb11
parentd175756707970532f7bb25358e7fca412b596c66 (diff)
Add different verbosity levels
-rw-r--r--src/desktop/input.rs21
-rw-r--r--src/desktop/load_save.rs11
-rw-r--r--src/desktop/window.rs9
-rw-r--r--src/io.rs15
-rw-r--r--src/logs.rs47
-rw-r--r--src/main.rs18
-rw-r--r--src/mmio.rs9
-rw-r--r--src/opcodes.rs13
-rw-r--r--src/state.rs21
9 files changed, 101 insertions, 63 deletions
diff --git a/src/desktop/input.rs b/src/desktop/input.rs
index ed604e2..4653d3c 100644
--- a/src/desktop/input.rs
+++ b/src/desktop/input.rs
@@ -5,6 +5,7 @@ use crate::desktop::window::Keys;
use crate::io::Input;
use gilrs::{Button, GamepadId, Gilrs};
use winit::keyboard::KeyCode;
+use crate::logs::{elog, log, LogLevel};
pub struct Gamepad {
gilrs: Gilrs,
@@ -17,10 +18,10 @@ impl Gamepad {
let gilrs = Gilrs::new().unwrap();
let gamepad_id = if let Some((gamepad_id, _gamepad)) = gilrs.gamepads().next() {
- println!("Found Gamepad id: {:?}", gamepad_id);
+ log(LogLevel::Infos, format!("Gamepad connected: {:?}", gamepad_id));
Some(gamepad_id)
} else {
- println!("No gamepad found");
+ log(LogLevel::Infos, format!("No gamepad found"));
None
};
@@ -44,12 +45,12 @@ impl Input for Gamepad {
fn update_events(&mut self, _cycles: u128) -> Option<u128> {
if let Some(gamepad_id) = self.gamepad_id {
if self.gilrs.connected_gamepad(gamepad_id).is_none() {
- println!("Gamepad (id = {:?}) disconnected", gamepad_id);
+ log(LogLevel::Infos, format!("Gamepad disconnected: {:?}", gamepad_id));
self.gamepad_id = None;
}
} else {
if let Some((gamepad_id, _gamepad)) = self.gilrs.gamepads().next() {
- println!("Found Gamepad id: {:?}", gamepad_id);
+ log(LogLevel::Infos, format!("Gamepad connected: {:?}", gamepad_id));
self.gamepad_id = Some(gamepad_id);
}
}
@@ -226,21 +227,21 @@ impl Input for GamepadRecorder {
let new_direction_reg = self.input.get_direction_gamepad_reg();
if self.action_reg != new_action_reg || self.direction_reg != new_direction_reg {
- println!(
+ log(LogLevel::Debug, format!(
"input update on cycle {} ! 0x{:02x} 0x{:02x}",
cycles, new_action_reg, new_direction_reg
- );
+ ));
if let Err(err) = self.record_file.write_all(&cycles.to_le_bytes()) {
- eprintln!("Failed to write to record file: {}", err);
+ elog(LogLevel::Error, format!("Failed to write to record file: {}", err));
};
if let Err(err) = self
.record_file
.write_all(&[new_action_reg, new_direction_reg])
{
- eprintln!("Failed to write to record file: {}", err);
+ elog(LogLevel::Error, format!("Failed to write to record file: {}", err));
}
if let Err(err) = self.record_file.flush() {
- eprintln!("Failed to flush record file writes: {}", err);
+ elog(LogLevel::Error, format!("Failed to flush record file writes: {}", err));
}
}
@@ -331,8 +332,6 @@ impl Input for GamepadReplay {
return false;
}
if self.next_cycle_update == None {
- println!("SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE SAVE");
-
self.last_save_state = true;
return true;
}
diff --git a/src/desktop/load_save.rs b/src/desktop/load_save.rs
index a7eadfc..ec99d94 100644
--- a/src/desktop/load_save.rs
+++ b/src/desktop/load_save.rs
@@ -2,6 +2,7 @@ use crate::io::{Audio, LoadSave, Serial};
use crate::state::GBState;
use std::fs::File;
use std::io::{Read, Write};
+use crate::logs::{elog, log, LogLevel};
#[derive(Debug)]
pub struct FSLoadSave {
@@ -37,8 +38,8 @@ impl LoadSave for FSLoadSave {
}
fn load_bootrom(&self, boot_rom: &mut [u8]) -> Result<(), std::io::Error> {
- println!("MBC: {:02x}", boot_rom[0x147]);
- println!("CGB: {:02x}", boot_rom[0x143]);
+ log(LogLevel::Debug, format!("MBC: {:02x}", boot_rom[0x147]));
+ log(LogLevel::Debug, format!("CGB: {:02x}", boot_rom[0x143]));
if boot_rom[0x143] == 0x80 || boot_rom[0x143] == 0xc0 {
unimplemented!("CGB Boot rom is not implemented");
@@ -61,7 +62,7 @@ impl LoadSave for FSLoadSave {
f.read(external_ram)?;
- println!("Save file loaded from \"{}\"!", self.save_file);
+ log(LogLevel::Infos, format!("Save file loaded from \"{}\"!", self.save_file));
Ok(())
}
@@ -71,7 +72,7 @@ impl LoadSave for FSLoadSave {
f.write_all(&external_ram)?;
- println!("Save written to \"{}\"!", self.save_file);
+ log(LogLevel::Infos, format!("Save written to \"{}\"!", self.save_file));
Ok(())
}
@@ -146,7 +147,7 @@ impl LoadSave for FSLoadSave {
state_file.write_all(&state.cpu.sp.to_le_bytes())?;
state_file.write_all(&[state.mem.boot_rom_on.into(), state.mem.ime.into()])?;
} else {
- eprintln!("Tried to save state without state_file specified");
+ elog(LogLevel::Error, format!("Tried to save state without state_file specified"));
}
Ok(())
}
diff --git a/src/desktop/window.rs b/src/desktop/window.rs
index 06b672d..93c5aba 100644
--- a/src/desktop/window.rs
+++ b/src/desktop/window.rs
@@ -4,6 +4,7 @@ use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
use crate::io::{Window, WindowSignal};
+use crate::logs::{elog, LogLevel};
use pixels::{Error, Pixels, SurfaceTexture};
use winit::dpi::LogicalSize;
@@ -71,7 +72,7 @@ impl DesktopWindow {
}
draw(pixels.frame_mut(), &fb);
if let Err(err) = pixels.render() {
- eprintln!("Error during render: {}", err);
+ elog(LogLevel::Error, format!("Error during render: {}", err));
return;
}
}
@@ -101,14 +102,14 @@ impl DesktopWindow {
if input.close_requested() {
elwt.exit();
if let Err(err) = signal_send.send(WindowSignal::Exit) {
- eprintln!("window signal send failed with error {}", err);
+ elog(LogLevel::Error, format!("window signal send failed with error {}", err));
}
return;
}
if let Some(size) = input.window_resized() {
if let Err(err) = pixels.resize_surface(size.width, size.height) {
- eprintln!("Error during resize: {}", err);
+ elog(LogLevel::Error, format!("Error during resize: {}", err));
return;
}
}
@@ -130,7 +131,7 @@ impl DesktopWindow {
impl Window for DesktopWindow {
fn update(&mut self, fb: Box<[u32; 160 * 144]>) -> Option<WindowSignal> {
if let Err(err) = self.fb_send.send(fb) {
- eprintln!("Framebuffer channel send failed with error: {}", err);
+ elog(LogLevel::Error, format!("Framebuffer channel send failed with error: {}", err));
}
if let Ok(signal) = self.signal_recv.try_recv() {
diff --git a/src/io.rs b/src/io.rs
index 06775a2..8cc7b22 100644
--- a/src/io.rs
+++ b/src/io.rs
@@ -4,6 +4,7 @@ use std::{thread, time};
use crate::audio::MutableWave;
use crate::consts;
use crate::state::GBState;
+use crate::logs::{log, elog, LogLevel};
pub trait Input {
fn update_events(&mut self, cycles: u128) -> Option<u128>;
@@ -108,10 +109,10 @@ impl<I: Input, W: Window, S: Serial, A: Audio, LS: LoadSave> Gameboy<I, W, S, A,
gb.load_save.load_rom(gb.state.mem.rom.as_mut()).unwrap();
if let Err(err) = gb.load_save.load_external_ram(gb.state.mem.external_ram.as_mut()) {
- println!(
+ log(LogLevel::Infos, format!(
"Loading save failed ({}). Initializing new external ram.",
err
- );
+ ));
}
gb
@@ -128,10 +129,6 @@ impl<I: Input, W: Window, S: Serial, A: Audio, LS: LoadSave> Gameboy<I, W, S, A,
Ok(())
}
- pub fn debug(&mut self) {
- self.state.is_debug = true;
- }
-
pub fn skip_bootrom(&mut self) {
self.state.mem.boot_rom_on = false;
self.state.cpu.pc = 0x100;
@@ -158,7 +155,7 @@ impl<I: Input, W: Window, S: Serial, A: Audio, LS: LoadSave> Gameboy<I, W, S, A,
while !state.is_stopped {
if was_previously_halted && !state.mem.halt {
- println!("Halt cycles {}", halt_time);
+ log(LogLevel::HaltCycles, format!("Halt cycles {}", halt_time));
halt_time = 0;
}
was_previously_halted = state.mem.halt;
@@ -199,7 +196,7 @@ impl<I: Input, W: Window, S: Serial, A: Audio, LS: LoadSave> Gameboy<I, W, S, A,
if save_state {
if let Err(err) = load_save.save_state(&state) {
- eprintln!("FAILED SAVE STATE: {:?}", err);
+ elog(LogLevel::Error, format!("Failed save state: {:?}", err));
}
}
@@ -231,7 +228,7 @@ impl<I: Input, W: Window, S: Serial, A: Audio, LS: LoadSave> Gameboy<I, W, S, A,
if last_ram_bank_enabled && !state.mem.ram_bank_enabled {
if let Err(err) = load_save.save_external_ram(state.mem.external_ram.as_ref()) {
- println!("Failed to save external RAM ({})", err);
+ elog(LogLevel::Error, format!("Failed to save external RAM ({})", err));
}
}
last_ram_bank_enabled = state.mem.ram_bank_enabled;
diff --git a/src/logs.rs b/src/logs.rs
new file mode 100644
index 0000000..dc1dfc1
--- /dev/null
+++ b/src/logs.rs
@@ -0,0 +1,47 @@
+use std::sync::OnceLock;
+use std::collections::HashSet;
+
+#[derive(Debug, PartialEq, Hash, Eq)]
+pub enum LogLevel {
+ Infos,
+ Debug,
+ OpcodeDump,
+ HaltCycles,
+ Error,
+}
+
+static LOG_LEVEL: OnceLock<HashSet<LogLevel>> = OnceLock::new();
+
+pub fn set_log_level(verbosity: String) {
+ let mut set: HashSet<LogLevel> = HashSet::new();
+ for level in verbosity.split(",") {
+ match level {
+ "infos" => { set.insert(LogLevel::Infos); },
+ "debug" => { set.insert(LogLevel::Debug); },
+ "opcode_dump" => { set.insert(LogLevel::OpcodeDump); },
+ "halt_cycles" => { set.insert(LogLevel::HaltCycles); },
+ "errors" => { set.insert(LogLevel::Error); },
+ "none" => {},
+ _ => panic!("Unknown log level \"{}\"", level),
+ }
+ }
+ if let Err(value) = LOG_LEVEL.set(set) {
+ panic!("Log level is already set with value {:?}", value);
+ }
+}
+
+pub fn log(level: LogLevel, s: String) {
+ if let Some(set) = LOG_LEVEL.get() {
+ if set.contains(&level) {
+ println!("[{:?}] {}", level, s);
+ }
+ }
+}
+
+pub fn elog(level: LogLevel, s: String) {
+ if let Some(set) = LOG_LEVEL.get() {
+ if set.contains(&level) {
+ eprintln!("[{:?}] {}", level, s);
+ }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 821a76d..84167aa 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,7 +7,9 @@ pub mod io;
pub mod mmio;
pub mod opcodes;
pub mod state;
+pub mod logs;
+use crate::logs::{log, LogLevel};
use crate::desktop::input::{Gamepad, GamepadRecorder, GamepadReplay, Keyboard};
use crate::desktop::load_save::FSLoadSave;
use crate::io::{Serial, Input};
@@ -51,10 +53,6 @@ struct Cli {
#[arg(short, long, default_value_t = 1.0)]
speed: f32,
- /// Will print all of the opcodes executed (WARNING: THERE ARE MANY)
- #[arg(short, long, default_value_t = false)]
- debug: bool,
-
/// Skip bootrom (will start the execution at 0x100 with all registers empty
#[arg(long, default_value_t = false)]
skip_bootrom: bool,
@@ -78,12 +76,18 @@ struct Cli {
/// Don't send (or expect) a byte as a response to a serial transfer
#[arg(long, default_value_t = false)]
no_response: bool,
+
+ /// Verbosity. Coma separated values (possible values: infos/debug/opcode_dump/halt_cycles/errors,none)
+ #[arg(short, long, default_value = "infos,errors")]
+ verbosity: String,
}
fn main() {
let cli = Cli::parse();
- println!("Starting {:?}...", &cli.rom);
+ logs::set_log_level(cli.verbosity);
+
+ log(LogLevel::Infos, format!("Starting {:?}...", &cli.rom));
let window = desktop::window::DesktopWindow::new(cli.title).unwrap();
@@ -119,10 +123,6 @@ fn main() {
cli.speed as f64,
);
- if cli.debug {
- gameboy.debug();
- }
-
if cli.load_state {
gameboy.load_state().unwrap();
}
diff --git a/src/mmio.rs b/src/mmio.rs
index d9f2a5a..8c8739f 100644
--- a/src/mmio.rs
+++ b/src/mmio.rs
@@ -1,10 +1,11 @@
use crate::io::{Audio, Serial};
use crate::state::Memory;
+use crate::logs::{log, LogLevel};
impl<S: Serial, A: Audio> Memory<S, A> {
pub fn r_io(&self, addr: u8) -> u8 {
if addr > 0x50 {
- println!("Reading from 0xff{:02x} not implemented yet", addr);
+ log(LogLevel::Debug, format!("Reading from 0xff{:02x} not implemented yet", addr));
}
match addr {
0x00 => {
@@ -55,7 +56,7 @@ impl<S: Serial, A: Audio> Memory<S, A> {
}
}
_ => {
- println!("Reading from 0xff{:02x} not implemented yet", addr);
+ log(LogLevel::Debug, format!("Reading from 0xff{:02x} not implemented yet", addr));
self.io[addr as usize]
}
}
@@ -295,10 +296,10 @@ impl<S: Serial, A: Audio> Memory<S, A> {
}
_ => {
if addr != 0x25 && addr != 0x24 && addr != 0x26 && addr < 0x30 && addr > 0x3f {
- println!(
+ log(LogLevel::Debug, format!(
"Writing to 0xff{:02x} not implemented yet ({:02x})",
addr, value
- );
+ ));
}
}
}
diff --git a/src/opcodes.rs b/src/opcodes.rs
index 606634a..6405726 100644
--- a/src/opcodes.rs
+++ b/src/opcodes.rs
@@ -1,5 +1,6 @@
use crate::io::{Audio, Serial};
use crate::state::{flag, reg, GBState};
+use crate::logs::{log, LogLevel};
// The opcodes functions are returning the number of cycles used.
@@ -160,7 +161,7 @@ impl<S: Serial, A: Audio> GBState<S, A> {
let res = self.pop();
if res == 0 {
- println!("DEBUG: {:?}", self.cpu);
+ log(LogLevel::Debug, format!("CPU: {:?}", self.cpu));
panic!("RET to start");
}
@@ -835,12 +836,10 @@ impl<S: Serial, A: Audio> GBState<S, A> {
pub fn exec_opcode(&mut self) -> u64 {
let opcode = self.mem.r(self.cpu.pc);
- if self.is_debug {
- println!(
- "{:02x}:{:04x} = {:02x} (IME: {})",
- self.mem.rom_bank, self.cpu.pc, opcode, self.mem.ime
- );
- }
+ log(LogLevel::OpcodeDump, format!(
+ "{:02x}:{:04x} = {:02x} (IME: {})",
+ self.mem.rom_bank, self.cpu.pc, opcode, self.mem.ime
+ ));
self.cpu.pc += 1;
diff --git a/src/state.rs b/src/state.rs
index 2c54a44..8c0832b 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -2,6 +2,7 @@ use crate::audio::Channels;
use crate::consts::{PROGRAM_START_ADDRESS, STACK_START_ADDRESS};
use crate::display::Display;
use crate::io::{Audio, Serial};
+use crate::logs::{log, LogLevel};
pub mod reg {
pub const B: u8 = 0;
@@ -87,7 +88,7 @@ impl CPU {
}
pub fn print_debug(&mut self) {
- println!(
+ log(LogLevel::Debug, format!(
"PC: 0x{:04x}, SP: 0x{:04x}, A: 0x{:02x}, BC: 0x{:04x}, DE: 0x{:04x}, HL: 0x{:04x}, F: 0x{:02x}. Since last dbg: {} cycles",
self.pc,
self.sp,
@@ -97,7 +98,7 @@ impl CPU {
self.r16(reg::HL),
self.r[reg::F as usize],
self.dbg_cycle_counter,
- );
+ ));
self.dbg_cycle_counter = 0;
}
@@ -240,10 +241,10 @@ impl<S: Serial, A: Audio> Memory<S, A> {
} else if addr == 0xffff {
self.interrupts_register
} else {
- println!(
+ log(LogLevel::Debug, format!(
"Trying to read at address 0x{:04x} which is unimplemented",
addr
- );
+ ));
0
}
}
@@ -274,10 +275,10 @@ impl<S: Serial, A: Audio> Memory<S, A> {
} else if addr == 0xffff {
self.interrupts_register = value;
} else {
- println!(
+ log(LogLevel::Debug, format!(
"Trying to write at address 0x{:04x} which is unimplemented (value: {:02x})",
addr, value
- );
+ ));
}
}
}
@@ -285,7 +286,6 @@ impl<S: Serial, A: Audio> Memory<S, A> {
pub struct GBState<S: Serial, A: Audio> {
pub cpu: CPU,
pub mem: Memory<S, A>,
- pub is_debug: bool,
pub is_stopped: bool,
pub div_cycles: u64,
@@ -299,7 +299,6 @@ impl<S: Serial, A: Audio> GBState<S, A> {
Self {
cpu: CPU::new(),
mem,
- is_debug: false,
is_stopped: false,
div_cycles: 0,
@@ -330,10 +329,4 @@ impl<S: Serial, A: Audio> GBState<S, A> {
panic!("r_i must be a 3 bits register input number")
}
}
-
- pub fn debug(&self, s: &str) -> () {
- if self.is_debug {
- println!("{}", s);
- }
- }
}