diff --git a/src/app.rs b/src/app.rs index 6197675..7566d91 100644 --- a/src/app.rs +++ b/src/app.rs @@ -18,7 +18,7 @@ use crate::{ controller::ControllerManager, emulator::{EmulatorClient, EmulatorCommand, SimId}, input::MappingProvider, - memory::MemoryMonitor, + memory::MemoryClient, persistence::Persistence, vram::VramProcessor, window::{ @@ -45,7 +45,7 @@ pub struct Application { proxy: EventLoopProxy, mappings: MappingProvider, controllers: ControllerManager, - memory: MemoryMonitor, + memory: Arc, vram: VramProcessor, persistence: Persistence, viewports: HashMap, @@ -64,7 +64,7 @@ impl Application { let persistence = Persistence::new(); let mappings = MappingProvider::new(persistence.clone()); let controllers = ControllerManager::new(client.clone(), &mappings); - let memory = MemoryMonitor::new(client.clone()); + let memory = Arc::new(MemoryClient::new(client.clone())); let vram = VramProcessor::new(); { let mappings = mappings.clone(); @@ -214,15 +214,15 @@ impl ApplicationHandler for Application { self.open(event_loop, Box::new(about)); } UserEvent::OpenCharacterData(sim_id) => { - let vram = CharacterDataWindow::new(sim_id, &mut self.memory, &mut self.vram); + let vram = CharacterDataWindow::new(sim_id, &self.memory, &mut self.vram); self.open(event_loop, Box::new(vram)); } UserEvent::OpenBgMap(sim_id) => { - let bgmap = BgMapWindow::new(sim_id, &mut self.memory, &mut self.vram); + let bgmap = BgMapWindow::new(sim_id, &self.memory, &mut self.vram); self.open(event_loop, Box::new(bgmap)); } UserEvent::OpenObjects(sim_id) => { - let objects = ObjectWindow::new(sim_id, &mut self.memory, &mut self.vram); + let objects = ObjectWindow::new(sim_id, &self.memory, &mut self.vram); self.open(event_loop, Box::new(objects)); } UserEvent::OpenDebugger(sim_id) => { diff --git a/src/memory.rs b/src/memory.rs index 6c20f37..9b54c8a 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,7 +1,7 @@ use std::{ collections::HashMap, fmt::Debug, - sync::{atomic::AtomicU64, Arc, RwLock, RwLockReadGuard, TryLockError, Weak}, + sync::{atomic::AtomicU64, Arc, Mutex, RwLock, RwLockReadGuard, TryLockError, Weak}, }; use bytemuck::BoxBytes; @@ -10,34 +10,41 @@ use tracing::warn; use crate::emulator::{EmulatorClient, EmulatorCommand, SimId}; -pub struct MemoryMonitor { +pub struct MemoryClient { client: EmulatorClient, - regions: HashMap>, + regions: Mutex>>, } -impl MemoryMonitor { +impl MemoryClient { pub fn new(client: EmulatorClient) -> Self { Self { client, - regions: HashMap::new(), + regions: Mutex::new(HashMap::new()), } } - pub fn view(&mut self, sim: SimId, start: u32, length: usize) -> MemoryView { + pub fn watch(&self, sim: SimId, start: u32, length: usize) -> MemoryView { let range = MemoryRange { sim, start, length }; - let region = self - .regions + let mut regions = self.regions.lock().unwrap_or_else(|e| e.into_inner()); + let region = regions .get(&range) .and_then(|r| r.upgrade()) .unwrap_or_else(|| { let region = Arc::new(MemoryRegion::new(start, length)); - self.regions.insert(range, Arc::downgrade(®ion)); + regions.insert(range, Arc::downgrade(®ion)); self.client .send_command(EmulatorCommand::WatchMemory(range, Arc::downgrade(®ion))); region }); MemoryView { region } } + + pub fn write(&self, sim: SimId, address: u32, data: &T) { + let data = bytemuck::bytes_of(data).to_vec(); + let (tx, _) = oneshot::channel(); + self.client + .send_command(EmulatorCommand::WriteMemory(sim, address, data, tx)); + } } fn aligned_memory(start: u32, length: usize) -> BoxBytes { diff --git a/src/window/vram/bgmap.rs b/src/window/vram/bgmap.rs index 550933c..93bca1a 100644 --- a/src/window/vram/bgmap.rs +++ b/src/window/vram/bgmap.rs @@ -1,14 +1,14 @@ use std::sync::Arc; use egui::{ - Align, CentralPanel, Checkbox, Color32, Context, Image, ScrollArea, Slider, TextEdit, + Align, CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit, TextureOptions, Ui, ViewportBuilder, ViewportId, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use crate::{ emulator::SimId, - memory::{MemoryMonitor, MemoryView}, + memory::{MemoryClient, MemoryView}, vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader}, window::{ utils::{NumberEdit, UiExt}, @@ -21,6 +21,7 @@ use super::utils::{self, CellData, CharacterGrid}; pub struct BgMapWindow { sim_id: SimId, loader: Arc, + memory: Arc, bgmaps: MemoryView, cell_index: usize, generic_palette: bool, @@ -30,7 +31,7 @@ pub struct BgMapWindow { } impl BgMapWindow { - pub fn new(sim_id: SimId, memory: &mut MemoryMonitor, vram: &mut VramProcessor) -> Self { + pub fn new(sim_id: SimId, memory: &Arc, vram: &mut VramProcessor) -> Self { let renderer = BgMapRenderer::new(sim_id, memory); let ([cell, bgmap], params) = vram.add(renderer); let loader = @@ -38,7 +39,8 @@ impl BgMapWindow { Self { sim_id, loader: Arc::new(loader), - bgmaps: memory.view(sim_id, 0x00020000, 0x1d800), + memory: memory.clone(), + bgmaps: memory.watch(sim_id, 0x00020000, 0x1d800), cell_index: 0, generic_palette: false, params, @@ -93,13 +95,8 @@ impl BgMapWindow { .texture_options(TextureOptions::NEAREST); ui.add(image); ui.section("Cell", |ui| { - let cell = self.bgmaps.borrow().read::(self.cell_index); - let CellData { - char_index, - mut vflip, - mut hflip, - palette_index, - } = CellData::parse(cell); + let mut data = self.bgmaps.borrow().read::(self.cell_index); + let mut cell = CellData::parse(data); TableBuilder::new(ui) .column(Column::remainder()) .column(Column::remainder()) @@ -109,12 +106,7 @@ impl BgMapWindow { ui.label("Character"); }); row.col(|ui| { - let mut character_str = char_index.to_string(); - ui.add_enabled( - false, - TextEdit::singleline(&mut character_str) - .horizontal_align(Align::Max), - ); + ui.add(NumberEdit::new(&mut cell.char_index).range(0..2048)); }); }); body.row(row_height, |mut row| { @@ -122,24 +114,33 @@ impl BgMapWindow { ui.label("Palette"); }); row.col(|ui| { - let mut palette = format!("BG {}", palette_index); - ui.add_enabled( - false, - TextEdit::singleline(&mut palette).horizontal_align(Align::Max), - ); + ComboBox::from_id_salt("palette") + .selected_text(format!("BG {}", cell.palette_index)) + .width(ui.available_width()) + .show_ui(ui, |ui| { + for palette in 0..4 { + ui.selectable_value( + &mut cell.palette_index, + palette, + format!("BG {palette}"), + ); + } + }); }); }); body.row(row_height, |mut row| { row.col(|ui| { - let checkbox = Checkbox::new(&mut hflip, "H-flip"); - ui.add_enabled(false, checkbox); + ui.add(Checkbox::new(&mut cell.hflip, "H-flip")); }); row.col(|ui| { - let checkbox = Checkbox::new(&mut vflip, "V-flip"); - ui.add_enabled(false, checkbox); + ui.add(Checkbox::new(&mut cell.vflip, "V-flip")); }); }); }); + if cell.update(&mut data) { + let address = 0x00020000 + (self.cell_index * 2); + self.memory.write(self.sim_id, address as u32, &data); + } }); ui.section("Display", |ui| { ui.horizontal(|ui| { @@ -223,12 +224,12 @@ struct BgMapRenderer { } impl BgMapRenderer { - pub fn new(sim_id: SimId, memory: &mut MemoryMonitor) -> Self { + pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self { Self { - chardata: memory.view(sim_id, 0x00078000, 0x8000), - bgmaps: memory.view(sim_id, 0x00020000, 0x1d800), - brightness: memory.view(sim_id, 0x0005f824, 8), - palettes: memory.view(sim_id, 0x0005f860, 16), + chardata: memory.watch(sim_id, 0x00078000, 0x8000), + bgmaps: memory.watch(sim_id, 0x00020000, 0x1d800), + brightness: memory.watch(sim_id, 0x0005f824, 8), + palettes: memory.watch(sim_id, 0x0005f860, 16), } } diff --git a/src/window/vram/chardata.rs b/src/window/vram/chardata.rs index 00ee780..dc102d3 100644 --- a/src/window/vram/chardata.rs +++ b/src/window/vram/chardata.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use crate::{ emulator::SimId, - memory::{MemoryMonitor, MemoryView}, + memory::{MemoryClient, MemoryView}, vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader}, window::{ utils::{NumberEdit, UiExt as _}, @@ -92,7 +92,7 @@ pub struct CharacterDataWindow { } impl CharacterDataWindow { - pub fn new(sim_id: SimId, memory: &mut MemoryMonitor, vram: &mut VramProcessor) -> Self { + pub fn new(sim_id: SimId, memory: &MemoryClient, vram: &mut VramProcessor) -> Self { let renderer = CharDataRenderer::new(sim_id, memory); let ([char, chardata], params) = vram.add(renderer); let loader = VramTextureLoader::new([ @@ -102,8 +102,8 @@ impl CharacterDataWindow { Self { sim_id, loader: Arc::new(loader), - brightness: memory.view(sim_id, 0x0005f824, 8), - palettes: memory.view(sim_id, 0x0005f860, 16), + brightness: memory.watch(sim_id, 0x0005f824, 8), + palettes: memory.watch(sim_id, 0x0005f860, 16), palette: params.palette, index: params.index, params, @@ -306,11 +306,11 @@ impl VramRenderer<2> for CharDataRenderer { } impl CharDataRenderer { - pub fn new(sim_id: SimId, memory: &mut MemoryMonitor) -> Self { + pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self { Self { - chardata: memory.view(sim_id, 0x00078000, 0x8000), - brightness: memory.view(sim_id, 0x0005f824, 8), - palettes: memory.view(sim_id, 0x0005f860, 16), + chardata: memory.watch(sim_id, 0x00078000, 0x8000), + brightness: memory.watch(sim_id, 0x0005f824, 8), + palettes: memory.watch(sim_id, 0x0005f860, 16), } } diff --git a/src/window/vram/object.rs b/src/window/vram/object.rs index 0b543d7..6313779 100644 --- a/src/window/vram/object.rs +++ b/src/window/vram/object.rs @@ -1,14 +1,14 @@ use std::sync::Arc; use egui::{ - Align, CentralPanel, Checkbox, Color32, Context, Image, ScrollArea, Slider, TextEdit, + Align, CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit, TextureOptions, Ui, ViewportBuilder, ViewportId, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use crate::{ emulator::SimId, - memory::{MemoryMonitor, MemoryView}, + memory::{MemoryClient, MemoryView}, vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader}, window::{ utils::{NumberEdit, UiExt as _}, @@ -21,6 +21,7 @@ use super::utils::{self, Object}; pub struct ObjectWindow { sim_id: SimId, loader: Arc, + memory: Arc, objects: MemoryView, index: usize, generic_palette: bool, @@ -29,7 +30,7 @@ pub struct ObjectWindow { } impl ObjectWindow { - pub fn new(sim_id: SimId, memory: &mut MemoryMonitor, vram: &mut VramProcessor) -> Self { + pub fn new(sim_id: SimId, memory: &Arc, vram: &mut VramProcessor) -> Self { let renderer = ObjectRenderer::new(sim_id, memory); let ([zoom, full], params) = vram.add(renderer); let loader = @@ -37,7 +38,8 @@ impl ObjectWindow { Self { sim_id, loader: Arc::new(loader), - objects: memory.view(sim_id, 0x0003e000, 0x2000), + memory: memory.clone(), + objects: memory.watch(sim_id, 0x0003e000, 0x2000), index: 0, generic_palette: false, params, @@ -79,7 +81,7 @@ impl ObjectWindow { .texture_options(TextureOptions::NEAREST); ui.add(image); ui.section("Properties", |ui| { - let object = self.objects.borrow().read::<[u16; 4]>(self.index); + let mut object = self.objects.borrow().read::<[u16; 4]>(self.index); let mut obj = Object::parse(object); TableBuilder::new(ui) .column(Column::remainder()) @@ -90,10 +92,7 @@ impl ObjectWindow { ui.label("Character"); }); row.col(|ui| { - ui.add_enabled( - false, - NumberEdit::new(&mut obj.data.char_index).range(0..2048), - ); + ui.add(NumberEdit::new(&mut obj.data.char_index).range(0..2048)); }); }); body.row(row_height, |mut row| { @@ -101,11 +100,18 @@ impl ObjectWindow { ui.label("Palette"); }); row.col(|ui| { - let mut palette = format!("OBJ {}", obj.data.palette_index); - ui.add_enabled( - false, - TextEdit::singleline(&mut palette).horizontal_align(Align::Max), - ); + ComboBox::from_id_salt("palette") + .selected_text(format!("OBJ {}", obj.data.palette_index)) + .width(ui.available_width()) + .show_ui(ui, |ui| { + for palette in 0..4 { + ui.selectable_value( + &mut obj.data.palette_index, + palette, + format!("OBJ {palette}"), + ); + } + }); }); }); body.row(row_height, |mut row| { @@ -113,7 +119,7 @@ impl ObjectWindow { ui.label("X"); }); row.col(|ui| { - ui.add_enabled(false, NumberEdit::new(&mut obj.x).range(-512..512)); + ui.add(NumberEdit::new(&mut obj.x).range(-512..512)); }); }); body.row(row_height, |mut row| { @@ -121,7 +127,7 @@ impl ObjectWindow { ui.label("Y"); }); row.col(|ui| { - ui.add_enabled(false, NumberEdit::new(&mut obj.y).range(-8..=224)); + ui.add(NumberEdit::new(&mut obj.y).range(-8..=224)); }); }); body.row(row_height, |mut row| { @@ -129,33 +135,30 @@ impl ObjectWindow { ui.label("Parallax"); }); row.col(|ui| { - ui.add_enabled( - false, - NumberEdit::new(&mut obj.parallax).range(-512..512), - ); + ui.add(NumberEdit::new(&mut obj.parallax).range(-512..512)); }); }); body.row(row_height, |mut row| { row.col(|ui| { - let checkbox = Checkbox::new(&mut obj.data.hflip, "H-flip"); - ui.add_enabled(false, checkbox); + ui.add(Checkbox::new(&mut obj.data.hflip, "H-flip")); }); row.col(|ui| { - let checkbox = Checkbox::new(&mut obj.data.vflip, "V-flip"); - ui.add_enabled(false, checkbox); + ui.add(Checkbox::new(&mut obj.data.vflip, "V-flip")); }); }); body.row(row_height, |mut row| { row.col(|ui| { - let checkbox = Checkbox::new(&mut obj.lon, "Left"); - ui.add_enabled(false, checkbox); + ui.add(Checkbox::new(&mut obj.lon, "Left")); }); row.col(|ui| { - let checkbox = Checkbox::new(&mut obj.ron, "Right"); - ui.add_enabled(false, checkbox); + ui.add(Checkbox::new(&mut obj.ron, "Right")); }); }); }); + if obj.update(&mut object) { + let address = 0x3e000 + self.index * 8; + self.memory.write(self.sim_id, address as u32, &object); + } }); ui.section("Display", |ui| { ui.horizontal(|ui| { @@ -254,12 +257,12 @@ struct ObjectRenderer { } impl ObjectRenderer { - pub fn new(sim_id: SimId, memory: &mut MemoryMonitor) -> Self { + pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self { Self { - chardata: memory.view(sim_id, 0x00078000, 0x8000), - objects: memory.view(sim_id, 0x0003e000, 0x2000), - brightness: memory.view(sim_id, 0x0005f824, 8), - palettes: memory.view(sim_id, 0x0005f860, 16), + chardata: memory.watch(sim_id, 0x00078000, 0x8000), + objects: memory.watch(sim_id, 0x0003e000, 0x2000), + brightness: memory.watch(sim_id, 0x0005f824, 8), + palettes: memory.watch(sim_id, 0x0005f860, 16), } } diff --git a/src/window/vram/utils.rs b/src/window/vram/utils.rs index c605302..6b0b469 100644 --- a/src/window/vram/utils.rs +++ b/src/window/vram/utils.rs @@ -39,11 +39,11 @@ pub struct Object { impl Object { pub fn parse(object: [u16; 4]) -> Self { - let x = ((object[0] & 0x3ff) << 6 >> 6) as i16; - let parallax = ((object[1] & 0x3ff) << 6 >> 6) as i16; + let x = ((object[0] & 0x03ff) << 6 >> 6) as i16; + let parallax = ((object[1] & 0x03ff) << 6 >> 6) as i16; let lon = object[1] & 0x8000 != 0; let ron = object[1] & 0x4000 != 0; - let y = (object[2] & 0x0ff) as i16; + let y = (object[2] & 0x00ff) as i16; // Y is stored as the bottom 8 bits of an i16, // so only sign extend if it's out of range. let y = if y > 224 { y << 8 >> 8 } else { y }; @@ -57,6 +57,30 @@ impl Object { data, } } + + pub fn update(&self, source: &mut [u16; 4]) -> bool { + let mut changed = false; + + let new_x = (self.x as u16 & 0x03ff) | (source[0] & 0xfc00); + changed |= source[0] != new_x; + source[0] = new_x; + + let new_p = if self.lon { 0x8000 } else { 0x0000 } + | if self.ron { 0x4000 } else { 0x0000 } + | (self.parallax as u16 & 0x3ff) + | (source[1] & 0x3c00); + changed |= source[1] != new_p; + source[1] = new_p; + + let new_y = (self.y as u16 & 0x00ff) | (source[2] & 0xff00); + changed |= source[2] != new_y; + source[2] = new_y; + + if self.data.update(&mut source[3]) { + changed = true; + } + changed + } } pub struct CellData { @@ -79,6 +103,17 @@ impl CellData { palette_index, } } + + pub fn update(&self, source: &mut u16) -> bool { + let new_value = (self.palette_index as u16) << 14 + | if self.hflip { 0x2000 } else { 0x0000 } + | if self.vflip { 0x1000 } else { 0x0000 } + | (self.char_index as u16 & 0x07ff) + | (*source & 0x0800); + let changed = *source != new_value; + *source = new_value; + changed + } } pub fn read_char_row(