Make properties editable

This commit is contained in:
Simon Gellis 2025-02-15 12:56:58 -05:00
parent 7356287030
commit b888d1140a
6 changed files with 136 additions and 90 deletions

View File

@ -18,7 +18,7 @@ use crate::{
controller::ControllerManager, controller::ControllerManager,
emulator::{EmulatorClient, EmulatorCommand, SimId}, emulator::{EmulatorClient, EmulatorCommand, SimId},
input::MappingProvider, input::MappingProvider,
memory::MemoryMonitor, memory::MemoryClient,
persistence::Persistence, persistence::Persistence,
vram::VramProcessor, vram::VramProcessor,
window::{ window::{
@ -45,7 +45,7 @@ pub struct Application {
proxy: EventLoopProxy<UserEvent>, proxy: EventLoopProxy<UserEvent>,
mappings: MappingProvider, mappings: MappingProvider,
controllers: ControllerManager, controllers: ControllerManager,
memory: MemoryMonitor, memory: Arc<MemoryClient>,
vram: VramProcessor, vram: VramProcessor,
persistence: Persistence, persistence: Persistence,
viewports: HashMap<ViewportId, Viewport>, viewports: HashMap<ViewportId, Viewport>,
@ -64,7 +64,7 @@ impl Application {
let persistence = Persistence::new(); let persistence = Persistence::new();
let mappings = MappingProvider::new(persistence.clone()); let mappings = MappingProvider::new(persistence.clone());
let controllers = ControllerManager::new(client.clone(), &mappings); 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 vram = VramProcessor::new();
{ {
let mappings = mappings.clone(); let mappings = mappings.clone();
@ -214,15 +214,15 @@ impl ApplicationHandler<UserEvent> for Application {
self.open(event_loop, Box::new(about)); self.open(event_loop, Box::new(about));
} }
UserEvent::OpenCharacterData(sim_id) => { 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)); self.open(event_loop, Box::new(vram));
} }
UserEvent::OpenBgMap(sim_id) => { 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)); self.open(event_loop, Box::new(bgmap));
} }
UserEvent::OpenObjects(sim_id) => { 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)); self.open(event_loop, Box::new(objects));
} }
UserEvent::OpenDebugger(sim_id) => { UserEvent::OpenDebugger(sim_id) => {

View File

@ -1,7 +1,7 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
fmt::Debug, fmt::Debug,
sync::{atomic::AtomicU64, Arc, RwLock, RwLockReadGuard, TryLockError, Weak}, sync::{atomic::AtomicU64, Arc, Mutex, RwLock, RwLockReadGuard, TryLockError, Weak},
}; };
use bytemuck::BoxBytes; use bytemuck::BoxBytes;
@ -10,34 +10,41 @@ use tracing::warn;
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId}; use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
pub struct MemoryMonitor { pub struct MemoryClient {
client: EmulatorClient, client: EmulatorClient,
regions: HashMap<MemoryRange, Weak<MemoryRegion>>, regions: Mutex<HashMap<MemoryRange, Weak<MemoryRegion>>>,
} }
impl MemoryMonitor { impl MemoryClient {
pub fn new(client: EmulatorClient) -> Self { pub fn new(client: EmulatorClient) -> Self {
Self { Self {
client, 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 range = MemoryRange { sim, start, length };
let region = self let mut regions = self.regions.lock().unwrap_or_else(|e| e.into_inner());
.regions let region = regions
.get(&range) .get(&range)
.and_then(|r| r.upgrade()) .and_then(|r| r.upgrade())
.unwrap_or_else(|| { .unwrap_or_else(|| {
let region = Arc::new(MemoryRegion::new(start, length)); let region = Arc::new(MemoryRegion::new(start, length));
self.regions.insert(range, Arc::downgrade(&region)); regions.insert(range, Arc::downgrade(&region));
self.client self.client
.send_command(EmulatorCommand::WatchMemory(range, Arc::downgrade(&region))); .send_command(EmulatorCommand::WatchMemory(range, Arc::downgrade(&region)));
region region
}); });
MemoryView { region } MemoryView { region }
} }
pub fn write<T: bytemuck::NoUninit>(&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 { fn aligned_memory(start: u32, length: usize) -> BoxBytes {

View File

@ -1,14 +1,14 @@
use std::sync::Arc; use std::sync::Arc;
use egui::{ 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, TextureOptions, Ui, ViewportBuilder, ViewportId,
}; };
use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use egui_extras::{Column, Size, StripBuilder, TableBuilder};
use crate::{ use crate::{
emulator::SimId, emulator::SimId,
memory::{MemoryMonitor, MemoryView}, memory::{MemoryClient, MemoryView},
vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader}, vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
window::{ window::{
utils::{NumberEdit, UiExt}, utils::{NumberEdit, UiExt},
@ -21,6 +21,7 @@ use super::utils::{self, CellData, CharacterGrid};
pub struct BgMapWindow { pub struct BgMapWindow {
sim_id: SimId, sim_id: SimId,
loader: Arc<VramTextureLoader>, loader: Arc<VramTextureLoader>,
memory: Arc<MemoryClient>,
bgmaps: MemoryView, bgmaps: MemoryView,
cell_index: usize, cell_index: usize,
generic_palette: bool, generic_palette: bool,
@ -30,7 +31,7 @@ pub struct BgMapWindow {
} }
impl BgMapWindow { impl BgMapWindow {
pub fn new(sim_id: SimId, memory: &mut MemoryMonitor, vram: &mut VramProcessor) -> Self { pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, vram: &mut VramProcessor) -> Self {
let renderer = BgMapRenderer::new(sim_id, memory); let renderer = BgMapRenderer::new(sim_id, memory);
let ([cell, bgmap], params) = vram.add(renderer); let ([cell, bgmap], params) = vram.add(renderer);
let loader = let loader =
@ -38,7 +39,8 @@ impl BgMapWindow {
Self { Self {
sim_id, sim_id,
loader: Arc::new(loader), loader: Arc::new(loader),
bgmaps: memory.view(sim_id, 0x00020000, 0x1d800), memory: memory.clone(),
bgmaps: memory.watch(sim_id, 0x00020000, 0x1d800),
cell_index: 0, cell_index: 0,
generic_palette: false, generic_palette: false,
params, params,
@ -93,13 +95,8 @@ impl BgMapWindow {
.texture_options(TextureOptions::NEAREST); .texture_options(TextureOptions::NEAREST);
ui.add(image); ui.add(image);
ui.section("Cell", |ui| { ui.section("Cell", |ui| {
let cell = self.bgmaps.borrow().read::<u16>(self.cell_index); let mut data = self.bgmaps.borrow().read::<u16>(self.cell_index);
let CellData { let mut cell = CellData::parse(data);
char_index,
mut vflip,
mut hflip,
palette_index,
} = CellData::parse(cell);
TableBuilder::new(ui) TableBuilder::new(ui)
.column(Column::remainder()) .column(Column::remainder())
.column(Column::remainder()) .column(Column::remainder())
@ -109,12 +106,7 @@ impl BgMapWindow {
ui.label("Character"); ui.label("Character");
}); });
row.col(|ui| { row.col(|ui| {
let mut character_str = char_index.to_string(); ui.add(NumberEdit::new(&mut cell.char_index).range(0..2048));
ui.add_enabled(
false,
TextEdit::singleline(&mut character_str)
.horizontal_align(Align::Max),
);
}); });
}); });
body.row(row_height, |mut row| { body.row(row_height, |mut row| {
@ -122,24 +114,33 @@ impl BgMapWindow {
ui.label("Palette"); ui.label("Palette");
}); });
row.col(|ui| { row.col(|ui| {
let mut palette = format!("BG {}", palette_index); ComboBox::from_id_salt("palette")
ui.add_enabled( .selected_text(format!("BG {}", cell.palette_index))
false, .width(ui.available_width())
TextEdit::singleline(&mut palette).horizontal_align(Align::Max), .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| { body.row(row_height, |mut row| {
row.col(|ui| { row.col(|ui| {
let checkbox = Checkbox::new(&mut hflip, "H-flip"); ui.add(Checkbox::new(&mut cell.hflip, "H-flip"));
ui.add_enabled(false, checkbox);
}); });
row.col(|ui| { row.col(|ui| {
let checkbox = Checkbox::new(&mut vflip, "V-flip"); ui.add(Checkbox::new(&mut cell.vflip, "V-flip"));
ui.add_enabled(false, checkbox);
}); });
}); });
}); });
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.section("Display", |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -223,12 +224,12 @@ struct BgMapRenderer {
} }
impl BgMapRenderer { impl BgMapRenderer {
pub fn new(sim_id: SimId, memory: &mut MemoryMonitor) -> Self { pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self {
Self { Self {
chardata: memory.view(sim_id, 0x00078000, 0x8000), chardata: memory.watch(sim_id, 0x00078000, 0x8000),
bgmaps: memory.view(sim_id, 0x00020000, 0x1d800), bgmaps: memory.watch(sim_id, 0x00020000, 0x1d800),
brightness: memory.view(sim_id, 0x0005f824, 8), brightness: memory.watch(sim_id, 0x0005f824, 8),
palettes: memory.view(sim_id, 0x0005f860, 16), palettes: memory.watch(sim_id, 0x0005f860, 16),
} }
} }

View File

@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
emulator::SimId, emulator::SimId,
memory::{MemoryMonitor, MemoryView}, memory::{MemoryClient, MemoryView},
vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader}, vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
window::{ window::{
utils::{NumberEdit, UiExt as _}, utils::{NumberEdit, UiExt as _},
@ -92,7 +92,7 @@ pub struct CharacterDataWindow {
} }
impl 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 renderer = CharDataRenderer::new(sim_id, memory);
let ([char, chardata], params) = vram.add(renderer); let ([char, chardata], params) = vram.add(renderer);
let loader = VramTextureLoader::new([ let loader = VramTextureLoader::new([
@ -102,8 +102,8 @@ impl CharacterDataWindow {
Self { Self {
sim_id, sim_id,
loader: Arc::new(loader), loader: Arc::new(loader),
brightness: memory.view(sim_id, 0x0005f824, 8), brightness: memory.watch(sim_id, 0x0005f824, 8),
palettes: memory.view(sim_id, 0x0005f860, 16), palettes: memory.watch(sim_id, 0x0005f860, 16),
palette: params.palette, palette: params.palette,
index: params.index, index: params.index,
params, params,
@ -306,11 +306,11 @@ impl VramRenderer<2> for CharDataRenderer {
} }
impl CharDataRenderer { impl CharDataRenderer {
pub fn new(sim_id: SimId, memory: &mut MemoryMonitor) -> Self { pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self {
Self { Self {
chardata: memory.view(sim_id, 0x00078000, 0x8000), chardata: memory.watch(sim_id, 0x00078000, 0x8000),
brightness: memory.view(sim_id, 0x0005f824, 8), brightness: memory.watch(sim_id, 0x0005f824, 8),
palettes: memory.view(sim_id, 0x0005f860, 16), palettes: memory.watch(sim_id, 0x0005f860, 16),
} }
} }

View File

@ -1,14 +1,14 @@
use std::sync::Arc; use std::sync::Arc;
use egui::{ 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, TextureOptions, Ui, ViewportBuilder, ViewportId,
}; };
use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use egui_extras::{Column, Size, StripBuilder, TableBuilder};
use crate::{ use crate::{
emulator::SimId, emulator::SimId,
memory::{MemoryMonitor, MemoryView}, memory::{MemoryClient, MemoryView},
vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader}, vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
window::{ window::{
utils::{NumberEdit, UiExt as _}, utils::{NumberEdit, UiExt as _},
@ -21,6 +21,7 @@ use super::utils::{self, Object};
pub struct ObjectWindow { pub struct ObjectWindow {
sim_id: SimId, sim_id: SimId,
loader: Arc<VramTextureLoader>, loader: Arc<VramTextureLoader>,
memory: Arc<MemoryClient>,
objects: MemoryView, objects: MemoryView,
index: usize, index: usize,
generic_palette: bool, generic_palette: bool,
@ -29,7 +30,7 @@ pub struct ObjectWindow {
} }
impl ObjectWindow { impl ObjectWindow {
pub fn new(sim_id: SimId, memory: &mut MemoryMonitor, vram: &mut VramProcessor) -> Self { pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, vram: &mut VramProcessor) -> Self {
let renderer = ObjectRenderer::new(sim_id, memory); let renderer = ObjectRenderer::new(sim_id, memory);
let ([zoom, full], params) = vram.add(renderer); let ([zoom, full], params) = vram.add(renderer);
let loader = let loader =
@ -37,7 +38,8 @@ impl ObjectWindow {
Self { Self {
sim_id, sim_id,
loader: Arc::new(loader), loader: Arc::new(loader),
objects: memory.view(sim_id, 0x0003e000, 0x2000), memory: memory.clone(),
objects: memory.watch(sim_id, 0x0003e000, 0x2000),
index: 0, index: 0,
generic_palette: false, generic_palette: false,
params, params,
@ -79,7 +81,7 @@ impl ObjectWindow {
.texture_options(TextureOptions::NEAREST); .texture_options(TextureOptions::NEAREST);
ui.add(image); ui.add(image);
ui.section("Properties", |ui| { 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); let mut obj = Object::parse(object);
TableBuilder::new(ui) TableBuilder::new(ui)
.column(Column::remainder()) .column(Column::remainder())
@ -90,10 +92,7 @@ impl ObjectWindow {
ui.label("Character"); ui.label("Character");
}); });
row.col(|ui| { row.col(|ui| {
ui.add_enabled( ui.add(NumberEdit::new(&mut obj.data.char_index).range(0..2048));
false,
NumberEdit::new(&mut obj.data.char_index).range(0..2048),
);
}); });
}); });
body.row(row_height, |mut row| { body.row(row_height, |mut row| {
@ -101,11 +100,18 @@ impl ObjectWindow {
ui.label("Palette"); ui.label("Palette");
}); });
row.col(|ui| { row.col(|ui| {
let mut palette = format!("OBJ {}", obj.data.palette_index); ComboBox::from_id_salt("palette")
ui.add_enabled( .selected_text(format!("OBJ {}", obj.data.palette_index))
false, .width(ui.available_width())
TextEdit::singleline(&mut palette).horizontal_align(Align::Max), .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| { body.row(row_height, |mut row| {
@ -113,7 +119,7 @@ impl ObjectWindow {
ui.label("X"); ui.label("X");
}); });
row.col(|ui| { 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| { body.row(row_height, |mut row| {
@ -121,7 +127,7 @@ impl ObjectWindow {
ui.label("Y"); ui.label("Y");
}); });
row.col(|ui| { 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| { body.row(row_height, |mut row| {
@ -129,33 +135,30 @@ impl ObjectWindow {
ui.label("Parallax"); ui.label("Parallax");
}); });
row.col(|ui| { row.col(|ui| {
ui.add_enabled( ui.add(NumberEdit::new(&mut obj.parallax).range(-512..512));
false,
NumberEdit::new(&mut obj.parallax).range(-512..512),
);
}); });
}); });
body.row(row_height, |mut row| { body.row(row_height, |mut row| {
row.col(|ui| { row.col(|ui| {
let checkbox = Checkbox::new(&mut obj.data.hflip, "H-flip"); ui.add(Checkbox::new(&mut obj.data.hflip, "H-flip"));
ui.add_enabled(false, checkbox);
}); });
row.col(|ui| { row.col(|ui| {
let checkbox = Checkbox::new(&mut obj.data.vflip, "V-flip"); ui.add(Checkbox::new(&mut obj.data.vflip, "V-flip"));
ui.add_enabled(false, checkbox);
}); });
}); });
body.row(row_height, |mut row| { body.row(row_height, |mut row| {
row.col(|ui| { row.col(|ui| {
let checkbox = Checkbox::new(&mut obj.lon, "Left"); ui.add(Checkbox::new(&mut obj.lon, "Left"));
ui.add_enabled(false, checkbox);
}); });
row.col(|ui| { row.col(|ui| {
let checkbox = Checkbox::new(&mut obj.ron, "Right"); ui.add(Checkbox::new(&mut obj.ron, "Right"));
ui.add_enabled(false, checkbox);
}); });
}); });
}); });
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.section("Display", |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -254,12 +257,12 @@ struct ObjectRenderer {
} }
impl ObjectRenderer { impl ObjectRenderer {
pub fn new(sim_id: SimId, memory: &mut MemoryMonitor) -> Self { pub fn new(sim_id: SimId, memory: &MemoryClient) -> Self {
Self { Self {
chardata: memory.view(sim_id, 0x00078000, 0x8000), chardata: memory.watch(sim_id, 0x00078000, 0x8000),
objects: memory.view(sim_id, 0x0003e000, 0x2000), objects: memory.watch(sim_id, 0x0003e000, 0x2000),
brightness: memory.view(sim_id, 0x0005f824, 8), brightness: memory.watch(sim_id, 0x0005f824, 8),
palettes: memory.view(sim_id, 0x0005f860, 16), palettes: memory.watch(sim_id, 0x0005f860, 16),
} }
} }

View File

@ -39,11 +39,11 @@ pub struct Object {
impl Object { impl Object {
pub fn parse(object: [u16; 4]) -> Self { pub fn parse(object: [u16; 4]) -> Self {
let x = ((object[0] & 0x3ff) << 6 >> 6) as i16; let x = ((object[0] & 0x03ff) << 6 >> 6) as i16;
let parallax = ((object[1] & 0x3ff) << 6 >> 6) as i16; let parallax = ((object[1] & 0x03ff) << 6 >> 6) as i16;
let lon = object[1] & 0x8000 != 0; let lon = object[1] & 0x8000 != 0;
let ron = object[1] & 0x4000 != 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, // Y is stored as the bottom 8 bits of an i16,
// so only sign extend if it's out of range. // so only sign extend if it's out of range.
let y = if y > 224 { y << 8 >> 8 } else { y }; let y = if y > 224 { y << 8 >> 8 } else { y };
@ -57,6 +57,30 @@ impl Object {
data, 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 { pub struct CellData {
@ -79,6 +103,17 @@ impl CellData {
palette_index, 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( pub fn read_char_row(