use super::disasm::{DisasmLine, DisasmWindowState};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemoryWatchEntry {
pub address: u16,
pub value: u8,
}
impl MemoryWatchEntry {
pub fn new(address: u16, value: u8) -> Self {
Self { address, value }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CpuTraceLine {
pub addr: u16,
pub bytes: Vec<u8>,
pub text: String,
}
impl CpuTraceLine {
pub fn new(addr: u16, bytes: Vec<u8>, text: String) -> Self {
Self { addr, bytes, text }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HexdumpRegionState {
pub base: u16,
pub bytes: Vec<u8>,
}
impl Default for HexdumpRegionState {
fn default() -> Self {
Self {
base: 0,
bytes: vec![0; 256],
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DebuggerViewState {
pub disasm_state: DisasmWindowState,
pub show_ppu_viewer: bool,
pub watch_addresses: Vec<u16>,
pub primary_hexdump_base: Option<u16>,
pub secondary_hexdump_base: Option<u16>,
}
impl DebuggerViewState {
pub fn new() -> Self {
Self::default()
}
pub fn toggle_ppu_viewer(&mut self) {
self.show_ppu_viewer = !self.show_ppu_viewer;
}
pub fn is_ppu_viewer_visible(&self) -> bool {
self.show_ppu_viewer
}
pub fn watch_addresses(&self) -> Vec<u16> {
self.watch_addresses.clone()
}
pub fn clear_watch_addresses(&mut self) {
self.watch_addresses.clear();
}
pub fn add_watch_address(&mut self, address: u16) {
if !self.watch_addresses.contains(&address) {
self.watch_addresses.push(address);
}
}
pub fn remove_watch_address(&mut self, index: usize) {
if index < self.watch_addresses.len() {
self.watch_addresses.remove(index);
}
}
pub fn update_watch_address(&mut self, index: usize, address: u16) {
if index < self.watch_addresses.len() {
self.watch_addresses[index] = address;
let mut current_index = 0usize;
self.watch_addresses.retain(|&entry| {
let keep = current_index == index || entry != address;
current_index += 1;
keep
});
}
}
pub fn set_primary_hexdump_base(&mut self, base: u16) {
self.primary_hexdump_base = Some(base);
}
pub fn nudge_primary_hexdump_base(&mut self, visible_base: u16, delta: i16) {
let current = self.primary_hexdump_base.unwrap_or(visible_base);
let nudged = if delta >= 0 {
current.saturating_add(delta as u16)
} else {
current.saturating_sub((-delta) as u16)
};
self.primary_hexdump_base = Some(nudged);
}
pub fn set_secondary_hexdump_base(&mut self, base: u16) {
self.secondary_hexdump_base = Some(base);
}
pub fn nudge_secondary_hexdump_base(&mut self, visible_base: u16, delta: i16) {
let current = self.secondary_hexdump_base.unwrap_or(visible_base);
let nudged = if delta >= 0 {
current.saturating_add(delta as u16)
} else {
current.saturating_sub((-delta) as u16)
};
self.secondary_hexdump_base = Some(nudged);
}
pub fn primary_hexdump_base(&self) -> Option<u16> {
self.primary_hexdump_base
}
pub fn secondary_hexdump_base(&self) -> Option<u16> {
self.secondary_hexdump_base
}
pub fn disasm_state_mut(&mut self) -> &mut DisasmWindowState {
&mut self.disasm_state
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DebuggerSnapshot<CpuRegs> {
pub cpu_regs: CpuRegs,
pub cpu_disasm: Vec<DisasmLine>,
pub cpu: String,
pub watch_values: Vec<MemoryWatchEntry>,
pub recent_trace: Vec<CpuTraceLine>,
}
impl<CpuRegs: Default> Default for DebuggerSnapshot<CpuRegs> {
fn default() -> Self {
Self {
cpu_regs: CpuRegs::default(),
cpu_disasm: Vec::new(),
cpu: String::new(),
watch_values: Vec::new(),
recent_trace: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_view_state_watch_addresses() {
let mut state = DebuggerViewState::new();
state.add_watch_address(0x0010);
state.add_watch_address(0x0020);
state.add_watch_address(0x0010); assert_eq!(state.watch_addresses(), vec![0x0010, 0x0020]);
state.update_watch_address(1, 0x0030);
assert_eq!(state.watch_addresses(), vec![0x0010, 0x0030]);
state.remove_watch_address(0);
assert_eq!(state.watch_addresses(), vec![0x0030]);
state.clear_watch_addresses();
assert!(state.watch_addresses().is_empty());
}
#[test]
fn test_view_state_ppu_viewer_toggle() {
let mut state = DebuggerViewState::new();
assert!(!state.is_ppu_viewer_visible());
state.toggle_ppu_viewer();
assert!(state.is_ppu_viewer_visible());
state.toggle_ppu_viewer();
assert!(!state.is_ppu_viewer_visible());
}
#[test]
fn test_view_state_hexdump_base() {
let mut state = DebuggerViewState::new();
state.set_primary_hexdump_base(0xC000);
assert_eq!(state.primary_hexdump_base(), Some(0xC000));
state.nudge_primary_hexdump_base(0xC000, 16);
assert_eq!(state.primary_hexdump_base(), Some(0xC010));
state.nudge_primary_hexdump_base(0xC000, -16);
assert_eq!(state.primary_hexdump_base(), Some(0xC000));
state.set_secondary_hexdump_base(0x8000);
assert_eq!(state.secondary_hexdump_base(), Some(0x8000));
}
#[test]
fn test_update_watch_address_removes_duplicates() {
let mut state = DebuggerViewState::new();
state.add_watch_address(0x0010);
state.add_watch_address(0x0020);
state.add_watch_address(0x0030);
state.update_watch_address(0, 0x0030);
assert_eq!(state.watch_addresses(), vec![0x0030, 0x0020]);
}
}