From 708743ef6403efae2bbaddecc669b6d0ecefbd10 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sun, 26 Jul 2026 19:55:06 +0100 Subject: [PATCH] refactor: fully moved to tile-based UI layout and removed most remnants of old layout (aside from editor which needs to be implemented properly before it's fully replaced. --- dsa/emulator/emulator_core/src/config/mod.rs | 8 +- dsa/emulator/emulator_core/src/lib.rs | 209 +++++++- dsa/emulator/emulator_core/src/memory/ram.rs | 12 +- .../processor/{processor.rs => dsa_core.rs} | 463 +++++------------- .../emulator_core/src/processor/dsa_debug.rs | 0 .../src/processor/dsa_optimised.rs | 241 +++++++++ .../emulator_core/src/processor/mod.rs | 16 +- dsa/emulator/emulator_ui/src/lib.rs | 2 +- dsa/emulator/emulator_ui/src/main.rs | 79 ++- .../src/ui/components/disassembler.rs | 219 --------- .../src/ui/{components => }/controller.rs | 86 ++-- .../emulator_ui/src/ui/debugger/mod.rs | 299 ----------- .../{debugger => }/dialog/confirm_dialog.rs | 2 +- .../ui/{debugger => }/dialog/error_dialog.rs | 0 .../ui/{debugger => }/dialog/file_dialog.rs | 3 - .../src/ui/{debugger => }/dialog/mod.rs | 0 dsa/emulator/emulator_ui/src/ui/mod.rs | 442 ++++++++++++----- .../src/ui/{debugger => }/panels/about.rs | 13 +- .../emulator_ui/src/ui/panels/disassembler.rs | 283 +++++++++++ .../src/ui/{components => panels}/display.rs | 73 +-- .../ui/{debugger => }/panels/documentation.rs | 4 +- .../panels/editor.rs => panels/editor/mod.rs} | 25 +- .../syntax/mod.rs => panels/editor/syntax.rs} | 1 - .../emulator_ui/src/ui/panels/editor/theme.rs | 18 + .../ui/{components => panels}/framebuffer.rs | 74 +-- .../src/ui/{components => panels}/memory.rs | 46 +- .../src/ui/{debugger => }/panels/mod.rs | 11 +- .../ui/{components => panels}/registers.rs | 71 +-- .../src/ui/{components => panels}/serial.rs | 80 +-- .../src/ui/{debugger => }/ui_helpers/mod.rs | 3 +- 30 files changed, 1421 insertions(+), 1362 deletions(-) rename dsa/emulator/emulator_core/src/processor/{processor.rs => dsa_core.rs} (55%) create mode 100644 dsa/emulator/emulator_core/src/processor/dsa_debug.rs create mode 100644 dsa/emulator/emulator_core/src/processor/dsa_optimised.rs delete mode 100644 dsa/emulator/emulator_ui/src/ui/components/disassembler.rs rename dsa/emulator/emulator_ui/src/ui/{components => }/controller.rs (55%) delete mode 100644 dsa/emulator/emulator_ui/src/ui/debugger/mod.rs rename dsa/emulator/emulator_ui/src/ui/{debugger => }/dialog/confirm_dialog.rs (97%) rename dsa/emulator/emulator_ui/src/ui/{debugger => }/dialog/error_dialog.rs (100%) rename dsa/emulator/emulator_ui/src/ui/{debugger => }/dialog/file_dialog.rs (96%) rename dsa/emulator/emulator_ui/src/ui/{debugger => }/dialog/mod.rs (100%) rename dsa/emulator/emulator_ui/src/ui/{debugger => }/panels/about.rs (72%) create mode 100644 dsa/emulator/emulator_ui/src/ui/panels/disassembler.rs rename dsa/emulator/emulator_ui/src/ui/{components => panels}/display.rs (65%) rename dsa/emulator/emulator_ui/src/ui/{debugger => }/panels/documentation.rs (95%) rename dsa/emulator/emulator_ui/src/ui/{debugger/panels/editor.rs => panels/editor/mod.rs} (84%) rename dsa/emulator/emulator_ui/src/ui/{components/editor/syntax/mod.rs => panels/editor/syntax.rs} (99%) create mode 100644 dsa/emulator/emulator_ui/src/ui/panels/editor/theme.rs rename dsa/emulator/emulator_ui/src/ui/{components => panels}/framebuffer.rs (63%) rename dsa/emulator/emulator_ui/src/ui/{components => panels}/memory.rs (93%) rename dsa/emulator/emulator_ui/src/ui/{debugger => }/panels/mod.rs (97%) rename dsa/emulator/emulator_ui/src/ui/{components => panels}/registers.rs (69%) rename dsa/emulator/emulator_ui/src/ui/{components => panels}/serial.rs (68%) rename dsa/emulator/emulator_ui/src/ui/{debugger => }/ui_helpers/mod.rs (99%) diff --git a/dsa/emulator/emulator_core/src/config/mod.rs b/dsa/emulator/emulator_core/src/config/mod.rs index b86b866..14658d2 100644 --- a/dsa/emulator/emulator_core/src/config/mod.rs +++ b/dsa/emulator/emulator_core/src/config/mod.rs @@ -1,8 +1,8 @@ -use serde::{Deserialize, Serialize}; use crate::config::default::{DEFAULT_IO_MAP, DEFAULT_MEMORY_MAP}; -use crate::memory::mmu::MMU; use crate::memory::PhysAddr; -use crate::processor::processor::Emulator; +use crate::memory::mmu::MMU; +use serde::{Deserialize, Serialize}; +use crate::processor::DsaProcessor; mod default; @@ -68,7 +68,7 @@ impl MemoryMap { } } - pub fn apply(&self, cpu: &mut Emulator) { + pub fn apply(&self, cpu: &mut DsaProcessor) { for reg in &self.regions { if reg.identity_map_on_boot { Self::identity_map(cpu.mmu_mut(), reg); diff --git a/dsa/emulator/emulator_core/src/lib.rs b/dsa/emulator/emulator_core/src/lib.rs index 330a163..afcf8a4 100644 --- a/dsa/emulator/emulator_core/src/lib.rs +++ b/dsa/emulator/emulator_core/src/lib.rs @@ -1,5 +1,212 @@ #![feature(likely_unlikely)] +use std::sync::Arc; +use std::sync::atomic::Ordering; +use std::thread; +use std::thread::{JoinHandle, Thread}; +use std::time::Duration; +use common::prelude::{Instruction, Register}; +use crate::config::{IoMap, MemoryMap}; +use crate::InternalImplementation::{Handle, Owned}; +use crate::memory::PhysAddr; +use crate::memory::ram::MemoryBank; +use crate::processor::{Interrupt, RunningState, SharedState}; + pub mod processor; pub mod memory; -pub mod config; \ No newline at end of file +pub mod config; + +#[derive(Debug)] +pub enum DsaError { + BootFailure(String), + CommunicationError(String), + CriticalException(String), +} + +/// This trait should be implemented for any type that can act as a fully functional DSA emulator with the ability to share state +pub trait DsaImplementation: Send { + /// may be modified in future with extra args like config + /// having direct control over the emulator's POST/boot state can be useful for + /// integration testing. + fn boot(&mut self) -> Result<(), DsaError>; + + /// main run method. the main-loop of the program execution + fn run(&mut self) -> Result<(), DsaError>; + + /// memory mapping is required for emulator memory sections + fn apply_memory_map(&mut self, map: MemoryMap); + + /// IO mapping also + fn apply_io_mapping(&mut self, map: IoMap); + + /// List all memory devices + /// returns a copy of the IOmap (cannot be a reference since across-threads) + fn get_io_map(&self) -> IoMap; + + /// returns a wrapper around the physical memory used by the emulator + fn get_memory_handle(&mut self) -> &mut MemoryBank; + + /// returns a wrapper around the shared state of the emulator + fn get_state_handle(&mut self) -> Arc; +} + +// pub trait DsaDebugImplementation: DsaImplementation { +// fn step() +// } + +#[derive(Clone)] +pub struct EmulatorHandle { + memory: MemoryBank, + state: Arc, +} + +impl EmulatorHandle { + + pub fn read_byte(&mut self, addr: PhysAddr) -> u8 { + self.memory.read_byte(addr) + } + + pub fn write_byte(&mut self, addr: PhysAddr, val: u8) { + self.memory.write_byte(addr, val); + } + + pub fn mem_read(&mut self, addr: PhysAddr, size: u32) -> Vec { + self.memory.read_bytes(addr, size as usize).to_vec() + } + + pub fn mem_write(&mut self, addr: PhysAddr, data: &[u8]) { + self.memory.write_bytes(addr, data); + } + + pub fn interrupt(&mut self, code: Interrupt) -> Result<(), DsaError> { + self.state.interrupt_queue.send(code) + .map_err(|_| DsaError::CommunicationError("interrupt failed".into()) ) + } + + pub fn request_update(&mut self) { + self.state.update_req.store(true, Ordering::Relaxed); + } + + pub fn status(&self) -> RunningState { + self.state.proc.load().running.clone() + } + + pub fn pause(&mut self) { + self.state.paused.store(true, Ordering::Relaxed); + } + pub fn resume(&mut self) { + self.state.paused.store(false, Ordering::Relaxed); + } + + pub fn reset(&mut self) { + self.state.reseting.store(true, Ordering::Relaxed); + self.memory.clear(); + self.state.reseting.store(false, Ordering::Relaxed); + } + + pub fn get_register(&self, rg: Register) -> u32 { + self.state.proc.load().registers[rg as usize] + } + + pub fn registers(&self) -> [u32; Register::COUNT as usize] { + self.state.proc.load().registers.clone() + } + + pub fn clock(&self) -> usize { + self.state.proc.load().clock + } + + pub fn time(&self) -> Duration { + self.state.proc.load().time + } +} + +enum InternalImplementation { + Owned(Box), + Handle(JoinHandle<()>), +} + +pub struct EmulatorController { + // the emulator itself + internal: Option, + handle: EmulatorHandle, +} + +impl AsMut for EmulatorController { + fn as_mut(&mut self) -> &mut EmulatorHandle { + &mut self.handle + } +} + +impl EmulatorController { + + pub fn new(mut emulator: Box) -> Self { + Self { + handle: EmulatorHandle { + memory: emulator.get_memory_handle().clone(), + state: emulator.get_state_handle().clone(), + }, + internal: Some(Owned(emulator)), + } + } + + pub fn run(&mut self) -> Result<(), DsaError> { + const STACK_SIZE: usize = 1024 * 1024 * 16; + + let Some(Owned(mut handle)) = self.internal.take() else { + return Err(DsaError::CriticalException(String::from("owned emulator returned none!"))) + }; + + self.internal = Some(Handle( + thread::Builder::new() + .stack_size(STACK_SIZE) + .spawn(move || {handle.run().unwrap() }) + .unwrap() + )); + + Ok(()) + } + + pub fn get_handle(&mut self) -> &mut EmulatorHandle { + &mut self.handle + } + + pub fn map_memory(&mut self, map: MemoryMap) -> Result<(), DsaError> { + let Some(Owned(t)) = &self.internal else { + return Err(DsaError::BootFailure("Emulator already initialised".into())); + }; + + Ok(()) + } + + pub fn shutdown(&mut self) { + // if let Some(handle) = &self.handle { + // handle.request_shutdown(); // sets an atomic the run loop checks each poll + // } + todo!(); + if let Some(InternalImplementation::Handle(join_handle)) = self.internal.take() { + let _ = join_handle.join(); // wait for the thread to actually exit + } + self.internal = None; + } + +} + + + + + + + + + + + + + + + + + + + diff --git a/dsa/emulator/emulator_core/src/memory/ram.rs b/dsa/emulator/emulator_core/src/memory/ram.rs index e74259c..6776b38 100644 --- a/dsa/emulator/emulator_core/src/memory/ram.rs +++ b/dsa/emulator/emulator_core/src/memory/ram.rs @@ -136,7 +136,17 @@ impl MemoryBank { pub fn read_byte(&self, addr: PhysAddr) -> u8 { unsafe { self.heap.add(addr as usize).read_volatile() } } - + + #[inline] + pub fn read_bytes(&self, addr: PhysAddr, size: usize) -> &[u8] { + let start = addr as usize; + assert!(start + size <= u32::MAX as usize, "read out of bounds"); + + unsafe { + std::slice::from_raw_parts(self.heap.add(start), size) + } + } + #[inline(always)] pub fn read_page(&self, addr: PhysAddr) -> &Page { debug_assert_eq!(addr % 4096, 0); diff --git a/dsa/emulator/emulator_core/src/processor/processor.rs b/dsa/emulator/emulator_core/src/processor/dsa_core.rs similarity index 55% rename from dsa/emulator/emulator_core/src/processor/processor.rs rename to dsa/emulator/emulator_core/src/processor/dsa_core.rs index 4618dd7..e3e783f 100644 --- a/dsa/emulator/emulator_core/src/processor/processor.rs +++ b/dsa/emulator/emulator_core/src/processor/dsa_core.rs @@ -1,321 +1,48 @@ -use std::{ - hint::unlikely, - ops::AddAssign, - sync::{ - Arc, - atomic::Ordering, - mpsc::{self, TryRecvError}, - }, - thread, - time::{Duration, Instant}, -}; +use common::isa::instructions::Opcode; +use common::prelude::{Instruction, Register}; +use crate::config::{IoMap, MemoryMap, RegionType}; +use crate::DsaError; +use crate::DsaError::BootFailure; +use crate::memory::mmu::MMU; +use crate::memory::ram::{MemoryBank, Page}; +use crate::processor::interrupts::Interrupt; +use crate::processor::state::{ProcessorSnapshot, SharedState}; +use crate::processor::state::RunningState::Running; -use common::{ - isa::instructions::{Instruction, Opcode}, - isa::register::Register -}; -use crate::{ - memory::{ - mmu::MMU, - ram::MemoryBank - }, - processor::{ - interrupts::Interrupt, - state::SharedState - } -}; -use crate::config::{MemoryMap, RegionType}; -use crate::memory::ram::Page; -use crate::processor::state::{ProcessorSnapshot, RunningState}; -use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown}; - -pub struct Emulator { - internal_state: ProcessorSnapshot, - shared_state: Arc, - - // Interrupts - interrupts: mpsc::Receiver, - pending_fault: Option, - - // memory - mmu: MMU, - mainstore: MemoryBank, - - // IO - mmio_region: Option<(u32, u32)>, // start end end of segment - - // optionals - not necessarily set on boot. - memory_map: Option, - - // Config params - __mmap_configured: bool, - - time: Instant, +#[derive(Copy, Clone, Debug)] +pub enum ExecutionResult { + Continue, + Halt, + Interrupt(Interrupt), } -unsafe impl Send for Emulator {} -impl Emulator { +pub struct DsaProcessor { + pub internal_state: ProcessorSnapshot, + + pub(crate) mmu: MMU, + pub(crate) mainstore: MemoryBank, + + mmio_region: Option<(u32, u32)>, + memory_map: Option, + __mmap_configured: bool, +} + +impl DsaProcessor { pub fn new() -> Self { - let (sender, receiver) = mpsc::channel::(); - Self { - interrupts: receiver, - pending_fault: None, - internal_state: ProcessorSnapshot::default(), - shared_state: Arc::new(SharedState::new(sender)), - memory_map: None, mmu: MMU::new(), mainstore: MemoryBank::new(), - // mmio mmio_region: None, - - // Config params + memory_map: None, __mmap_configured: false, - - time: Instant::now(), } } - pub fn state_handle(&self) -> Arc { - self.shared_state.clone() - } - #[inline] - pub fn mmu_mut(&mut self) -> &mut MMU { - &mut self.mmu - } - - #[inline] - pub fn memory_mut(&mut self) -> &mut MemoryBank { - &mut self.mainstore - } - - #[must_use] - #[inline] - pub fn reg(&self, reg: Register) -> u32 { - debug_assert!((reg as u8) < Register::COUNT); - - unsafe { *self.internal_state.registers.get_unchecked(reg as usize) } - } - - #[inline] - pub fn mut_reg(&mut self, reg: Register) -> &mut u32 { - debug_assert!((reg as u8) < Register::COUNT); - unsafe { - self.internal_state - .registers - .get_unchecked_mut(reg as usize) - } - } - - #[cold] - pub fn apply_memory_map(mut self, map: MemoryMap) -> Self { - self.mmio_region = map - .regions - .iter() - .find(|r| matches!(r.region_type, RegionType::MMIO)) - .map(|r| (r.base, r.base + r.size)); - - map.apply(&mut self); - self.memory_map = Some(map); - self.__mmap_configured = true; - self - } - - #[cold] - fn share_state(&mut self) { - self.shared_state - .proc - .store(Arc::new(self.internal_state.clone())); - } - - #[cold] - fn boot(&mut self) -> Result<(), String> { - if !(self.__mmap_configured) { - return Err("Emulator not configured".to_string()); - } - - if let Some(map) = &self.memory_map { - MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap()); - } - - self.internal_state.running = Running; - Ok(()) - } - - - #[cold] - pub fn run_state_halted(&mut self, prior: RunningState) { - self.internal_state.running = Halted; - - // Responsibility: keep track of the time spent in the running state across multiple hlt/interrupt cycles - self.internal_state.time += self.time.elapsed(); - - loop { - // Transition: interrupt received - if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) { - self.internal_state.running = Running; - self.interrupt(int); - break; - } - - // Transition: pause signal received - if self.shared_state.paused.load(Ordering::Relaxed) { - self.run_state_paused(Halted); - } - - // Responsibility: update UI when requested - if self.shared_state.update_req.load(Ordering::Relaxed) == true { - self.share_state(); - } - } - - // Responsibility: reset `private.time` on state exit - self.time = Instant::now(); - self.internal_state.running = prior; - } - - #[cold] - pub fn run_state_paused(&mut self, prior: RunningState) { - self.internal_state.running = Paused; - - // Responsibility: save the running-time since last pause state. - self.internal_state.time = self.time.elapsed(); - self.share_state(); - - loop { - // Responsibility: update UI when requested - if self.shared_state.update_req.load(Ordering::Relaxed) == true { - self.share_state(); - } - - // Responsibility: reset when requested by the UI - if self.shared_state.reseting.load(Ordering::Relaxed) == true { - self.internal_state.registers = [0; Register::COUNT as usize]; - self.internal_state.clock = 0; - - while self.shared_state.reseting.load(Ordering::Relaxed) == true { - thread::sleep(Duration::from_millis(100)); - } - } - - // Transition: run signal received - if self.shared_state.paused.load(Ordering::Relaxed) == false { - break; - } - } - - // Responsibility: reset private.time, shared.time, shared.cycles on state exit - self.time = Instant::now(); - self.internal_state.time = Duration::new(0, 0); - self.internal_state.clock = 0; - - self.internal_state.running = prior; - } - - pub fn run(&mut self) -> Result<(), String> { - // wait for UI to initialise. - if self.shared_state.paused.load(Ordering::Relaxed) { - self.run_state_paused(Running) - } - - self.boot()?; - - 'emu: loop { - // so that it always returns zero. this is more performance friendly than checking - // for a zero register each access as it's branchless - *self.mut_reg(Register::Zero) = 0; - - // IMPORTANT - // do not change anything about this loop. it's fully optimised afaik. - // Check for commands or hardware Interrupts every 32k cycles - if unlikely(self.internal_state.clock & 0x7FFF == 0) { - - // Responsibility: keep the UI thread up to date periodically - // Update UI thread (roughly every 512k cycles targeting 60 UPS) - if unlikely(self.internal_state.clock & 0x7FFFF == 0) { - if self.shared_state.update_req.load(Ordering::Relaxed) { - self.share_state(); - } - } - - // Transition: Paused - if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) { - self.run_state_paused(Running) - } - - // Responsibility: Handle interrupts - match self.interrupts.try_recv() { - Ok(int) => self.interrupt(int), - Err(TryRecvError::Disconnected) => break 'emu, - Err(TryRecvError::Empty) => {} - } - } - - // Responsibility: run fetch-decode-execute cycle. - let pc = self.reg(Register::Pcx); - let instruction = self.mem_read_word(pc); - - if cfg!(debug_assertions) { - println!( - "Clock: {} Executing {:?} PCX: {}, reg: {:?}", - self.internal_state.clock, - Instruction(instruction), - pc, - self.internal_state.registers - ); - thread::sleep(Duration::from_micros(10)); - } - - // Check if the previous instruction caused a fault. - if let Some(fault) = self.pending_fault { - println!("WARN fault: {:?}", fault); - self.pending_fault = None; - self.interrupt(fault); - } - - self.mut_reg(Register::Pcx).add_assign(4); - self.execute(Instruction(instruction)); - - // always increment clock. - self.internal_state.clock += 1; - } - - self.internal_state.running = Shutdown; - Ok(()) - } - - #[inline] - fn interrupt(&mut self, int: Interrupt) { - println!("Interrupt while at address {:04X}", self.reg(Register::Pcx)); - - self.push(Register::Pcx); - *self.mut_reg(Register::Pcx) = - self.mem_read_word(self.reg(Register::Idr) + int.code() as u32 * 4); - - println!( - "Jumping to interrupt at address {:04X}", - self.reg(Register::Pcx) - ); - } - - #[inline] - fn push(&mut self, reg: Register) { - *self.mut_reg(Register::Spr) -= 4; - self.mem_write_word(self.reg(Register::Spr), self.reg(reg)); - } - - #[inline] - fn pop(&mut self, reg: Register) { - *self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr)); - *self.mut_reg(Register::Spr) += 4; - } - - #[inline] - fn execute(&mut self, word: Instruction) { + pub fn execute(&mut self, word: Instruction) -> ExecutionResult { // This needs to be unsafe as we're using word.xxxx_uc() functions for decoding which are unsafe // as they perform unchecked transmute operations (performance critical) unsafe { @@ -334,8 +61,8 @@ impl Emulator { } // load - Some(Opcode::Ldbs) => todo!(), - Some(Opcode::Ldhs) => todo!(), + Some(Opcode::Ldbs) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode), + Some(Opcode::Ldhs) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode), Some(Opcode::Ldb) => { *self.mut_reg(word.dest_uc()) = u32::from( self.mem_read_byte(self.reg(word.src1_uc()) + word.imm16() as u32), @@ -434,9 +161,8 @@ impl Emulator { self.reg(word.dest_uc()) + word.imm16() as u32 } } - Some(Opcode::Jnc) => todo!(), - Some(Opcode::Jic) => todo!(), - + Some(Opcode::Jnc) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode), + Some(Opcode::Jic) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode), // Bitwise Some(Opcode::And) => { *self.mut_reg(word.dest_uc()) = @@ -512,13 +238,6 @@ impl Emulator { self.pop(Register::Ret); *self.mut_reg(Register::Pcx) = self.reg(Register::Ret); } - - Some(Opcode::Int) => { - self.interrupt(Interrupt::Software(word.imm16() as u8)); - } - Some(Opcode::Hlt) => { - self.run_state_halted(Running); - }, Some(Opcode::IRet) => { self.pop(Register::Ret); *self.mut_reg(Register::Pcx) = self.reg(Register::Ret); @@ -528,50 +247,134 @@ impl Emulator { self.reg(Register::Pcx) ); } - None => {} + Some(Opcode::Int) => return ExecutionResult::Interrupt(Interrupt::Software(word.imm16() as u8)), + Some(Opcode::Hlt) => return ExecutionResult::Halt, + None => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode), } + }; + + ExecutionResult::Continue + } + + #[inline] + pub fn interrupt(&mut self, int: Interrupt) { + self.push(Register::Pcx); + *self.mut_reg(Register::Pcx) = + self.mem_read_word(self.reg(Register::Idr) + int.code() as u32 * 4); + } + + #[must_use] + #[inline] + pub fn reg(&self, reg: Register) -> u32 { + debug_assert!((reg as u8) < Register::COUNT); + unsafe { *self.internal_state.registers.get_unchecked(reg as usize) } + } + + #[inline] + pub fn mut_reg(&mut self, reg: Register) -> &mut u32 { + debug_assert!((reg as u8) < Register::COUNT); + unsafe { + self.internal_state + .registers + .get_unchecked_mut(reg as usize) } } #[inline] - fn mem_read_byte(&mut self, addr: u32) -> u8 { + pub fn push(&mut self, reg: Register) { + *self.mut_reg(Register::Spr) -= 4; + self.mem_write_word(self.reg(Register::Spr), self.reg(reg)); + } + + #[inline] + pub fn pop(&mut self, reg: Register) { + *self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr)); + *self.mut_reg(Register::Spr) += 4; + } + + #[inline] + pub fn mem_read_byte(&mut self, addr: u32) -> u8 { self.mainstore.read_byte(addr) } #[inline] - fn mem_write_byte(&mut self, addr: u32, val: u8) { - if addr == 0x40000 || addr == 0x40001 || addr == 0x40002 || addr == 0x40003 { - eprintln!("ui wrote 0x{:08x} to uart + {}", val, addr - 0x40000); - - if addr == 0x40001 || addr == 0x40003 { - eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000); - } - } + pub fn mem_write_byte(&mut self, addr: u32, val: u8) { + // TODO: how can we extract this method for debugging? + // if addr == 0x40000 || addr == 0x40001 || addr == 0x40002 || addr == 0x40003 { + // if addr == 0x40001 || addr == 0x40003 { + // eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000); + // } + // } self.mainstore.write_byte(addr, val); } #[inline] - fn mem_read_word(&mut self, addr: u32) -> u32 { + pub fn mem_read_word(&mut self, addr: u32) -> u32 { self.mainstore.read_word(addr) } #[inline] - fn mem_write_word(&mut self, addr: u32, val: u32) { + pub fn mem_write_word(&mut self, addr: u32, val: u32) { self.mainstore.write_word(addr, val); } #[inline] - fn mem_read_page(&mut self, addr: u32) -> &Page { + pub fn mem_read_page(&mut self, addr: u32) -> &Page { self.mainstore.read_page(addr) } #[inline] - fn mem_write_page(&mut self, addr: u32, val: &Page) { + pub fn mem_write_page(&mut self, addr: u32, val: &Page) { self.mainstore.write_page(addr, val); } - fn fault(&mut self, interrupt: Interrupt) { - self.pending_fault = Some(interrupt); + #[inline] + pub fn mmu_mut(&mut self) -> &mut MMU { + &mut self.mmu + } + + #[inline] + pub fn memory_mut(&mut self) -> &mut MemoryBank { + &mut self.mainstore + } + + pub fn apply_memory_map(&mut self, map: MemoryMap) { + self.mmio_region = map + .regions + .iter() + .find(|r| matches!(r.region_type, RegionType::MMIO)) + .map(|r| (r.base, r.base + r.size)); + + map.apply(self); + self.memory_map = Some(map); + self.__mmap_configured = true; + } + + pub fn boot(&mut self) -> Result<(), DsaError> { + if !(self.__mmap_configured) { + return Err(BootFailure("Emulator not configured".to_string())); + } + + if let Some(map) = &self.memory_map { + MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap()); + } + + self.internal_state.running = Running; + Ok(()) + } + + pub fn apply_io_mapping(&mut self, map: IoMap) { + todo!() + } + + pub fn get_io_map(&self) -> IoMap { + todo!() } } +impl Drop for DsaProcessor { + fn drop(&mut self) { + // drop is only ever called once on an emulator instance, so we can be sure a double free will not happen. + unsafe { self.mainstore.dealloc() } + } +} \ No newline at end of file diff --git a/dsa/emulator/emulator_core/src/processor/dsa_debug.rs b/dsa/emulator/emulator_core/src/processor/dsa_debug.rs new file mode 100644 index 0000000..e69de29 diff --git a/dsa/emulator/emulator_core/src/processor/dsa_optimised.rs b/dsa/emulator/emulator_core/src/processor/dsa_optimised.rs new file mode 100644 index 0000000..3db53c7 --- /dev/null +++ b/dsa/emulator/emulator_core/src/processor/dsa_optimised.rs @@ -0,0 +1,241 @@ +use crate::config::{IoMap, MemoryMap}; +use crate::memory::ram::MemoryBank; +use crate::processor::dsa_core::{DsaProcessor, ExecutionResult}; +use crate::processor::interrupts::Interrupt; +use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown}; +use crate::processor::state::{RunningState, SharedState}; +use crate::{DsaError, DsaImplementation}; +use common::prelude::{Instruction, Register}; +use std::hint::unlikely; +use std::ops::AddAssign; +use std::sync::atomic::Ordering; +use std::sync::{Arc, mpsc}; +use std::sync::mpsc::TryRecvError; +use std::thread; +use std::time::{Duration, Instant}; + +pub struct Emulator { + core: DsaProcessor, + + shared_state: Arc, + + // Interrupts + interrupts: mpsc::Receiver, + pending_fault: Option, + + time: Instant, +} + +impl DsaImplementation for Emulator { + #[cold] + fn boot(&mut self) -> Result<(), DsaError> { + self.core.boot() + } + + fn run(&mut self) -> Result<(), DsaError> { + // wait for UI to initialise. + if self.shared_state.paused.load(Ordering::Relaxed) { + self.run_state_paused(Running) + } + + self.boot()?; + + 'emu: loop { + // so that it always returns zero. this is more performance friendly than checking + // for a zero register each access as it's branchless + *self.core.mut_reg(Register::Zero) = 0; + + // IMPORTANT + // do not change anything about this loop. it's fully optimised afaik. + // Check for commands or hardware Interrupts every 32k cycles + if unlikely(self.core.internal_state.clock & 0x7FFF == 0) { + + // Responsibility: keep the UI thread up to date periodically + // Update UI thread (roughly every 512k cycles targeting 60 UPS) + if unlikely(self.core.internal_state.clock & 0x7FFFF == 0) { + if self.shared_state.update_req.load(Ordering::Relaxed) { + self.core.internal_state.time = self.time.elapsed(); + self.share_state(); + } + } + + // Transition: Paused + if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) { + self.run_state_paused(Running); + } + + // Responsibility: Handle interrupts + match self.interrupts.try_recv() { + Ok(int) => self.core.interrupt(int), + Err(TryRecvError::Disconnected) => break 'emu, + Err(TryRecvError::Empty) => {} + } + } + + // Responsibility: run fetch-decode-execute cycle. + let pc = self.core.reg(Register::Pcx); + let instruction = self.core.mem_read_word(pc); + + if cfg!(debug_assertions) { + println!( + "Clock: {} Executing {:?} PCX: {}, reg: {:?}", + self.core.internal_state.clock, + Instruction(instruction), + pc, + self.core.internal_state.registers + ); + thread::sleep(Duration::from_micros(10)); + } + + // Check if the previous instruction caused a fault. + if let Some(fault) = self.pending_fault { + println!("WARN fault: {:?}", fault); + self.pending_fault = None; + self.core.interrupt(fault); + } + + self.core.mut_reg(Register::Pcx).add_assign(4); + + match self.core.execute(Instruction(instruction)) { + ExecutionResult::Continue => {}, + ExecutionResult::Halt => self.run_state_halted(Running), + + ExecutionResult::Interrupt(interrupt) => { + if cfg!(debug_assertions) { + println!("Interrupt: {:?}", interrupt); + println!("Interrupt while at address {:04X}", self.core.reg(Register::Pcx)); + } + self.core.interrupt(interrupt); + + if cfg!(debug_assertions) { + println!( + "Jumping to interrupt at address {:04X}", + self.core.reg(Register::Pcx) + ); + } + } + } + + self.core.internal_state.clock += 1; + } + + self.core.internal_state.running = Shutdown; + Ok(()) + } + + fn apply_memory_map(&mut self, map: MemoryMap) { + self.core.apply_memory_map(map) + } + + fn apply_io_mapping(&mut self, map: IoMap) { + self.core.apply_io_mapping(map) + } + + fn get_io_map(&self) -> IoMap { + self.core.get_io_map() + } + + fn get_memory_handle(&mut self) -> &mut MemoryBank { + self.core.memory_mut() + } + + fn get_state_handle(&mut self) -> Arc { + self.shared_state.clone() + } +} + +impl Emulator { + pub fn new() -> Self { + let (sender, receiver) = mpsc::channel::(); + + Self { + core: DsaProcessor::new(), + shared_state: Arc::new(SharedState::new(sender)), + + interrupts: receiver, + pending_fault: None, + + time: Instant::now(), + } + } + + pub fn state_handle(&self) -> Arc { + self.shared_state.clone() + } + + #[cold] + fn share_state(&mut self) { + self.shared_state + .proc + .store(Arc::new(self.core.internal_state.clone())); + } + + #[cold] + pub fn run_state_halted(&mut self, prior: RunningState) { + self.core.internal_state.running = Halted; + + // Responsibility: keep track of the time spent in the running state across multiple hlt/interrupt cycles + self.core.internal_state.time += self.time.elapsed(); + + loop { + // Transition: interrupt received + if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) { + self.core.internal_state.running = Running; + self.core.interrupt(int); + break; + } + + // Transition: pause signal received + if self.shared_state.paused.load(Ordering::Relaxed) { + self.run_state_paused(Halted); + } + + // Responsibility: update UI when requested + if self.shared_state.update_req.load(Ordering::Relaxed) == true { + self.share_state(); + } + } + + // Responsibility: reset `private.time` on state exit + self.time = Instant::now(); + self.core.internal_state.running = prior; + } + + #[cold] + pub fn run_state_paused(&mut self, prior: RunningState) { + self.core.internal_state.running = Paused; + + // Responsibility: save the running-time since last pause state. + self.core.internal_state.time = self.time.elapsed(); + self.share_state(); + + loop { + // Responsibility: update UI when requested + if self.shared_state.update_req.load(Ordering::Relaxed) == true { + self.share_state(); + } + + // Responsibility: reset when requested by the UI + if self.shared_state.reseting.load(Ordering::Relaxed) == true { + self.core.internal_state.registers = [0; Register::COUNT as usize]; + self.core.internal_state.clock = 0; + + while self.shared_state.reseting.load(Ordering::Relaxed) == true { + thread::sleep(Duration::from_millis(100)); + } + } + + // Transition: run signal received + if self.shared_state.paused.load(Ordering::Relaxed) == false { + break; + } + } + + // Responsibility: reset private.time, shared.time, shared.cycles on state exit + self.time = Instant::now(); + self.core.internal_state.time = Duration::new(0, 0); + self.core.internal_state.clock = 0; + + self.core.internal_state.running = prior; + } +} \ No newline at end of file diff --git a/dsa/emulator/emulator_core/src/processor/mod.rs b/dsa/emulator/emulator_core/src/processor/mod.rs index 4a4f492..19f1683 100644 --- a/dsa/emulator/emulator_core/src/processor/mod.rs +++ b/dsa/emulator/emulator_core/src/processor/mod.rs @@ -1,3 +1,13 @@ -pub mod interrupts; -pub mod processor; -pub mod state; +mod state; +mod dsa_core; +mod dsa_debug; +mod dsa_optimised; +mod interrupts; + +pub use dsa_core::DsaProcessor; +pub use interrupts::Interrupt; +pub use dsa_optimised::Emulator; +pub use state::{ + RunningState, + SharedState, +}; \ No newline at end of file diff --git a/dsa/emulator/emulator_ui/src/lib.rs b/dsa/emulator/emulator_ui/src/lib.rs index fbaf689..74e79a4 100644 --- a/dsa/emulator/emulator_ui/src/lib.rs +++ b/dsa/emulator/emulator_ui/src/lib.rs @@ -3,4 +3,4 @@ #![feature(str_as_str)] pub mod ui; -pub mod args; +pub mod args; \ No newline at end of file diff --git a/dsa/emulator/emulator_ui/src/main.rs b/dsa/emulator/emulator_ui/src/main.rs index 58184f1..7a88d75 100644 --- a/dsa/emulator/emulator_ui/src/main.rs +++ b/dsa/emulator/emulator_ui/src/main.rs @@ -1,61 +1,50 @@ use clap::Parser; -use common::prelude::Instruction; -use std::thread; use dsa::args::DsaArgs; use dsa::ui::run_app; -use emulator_core::{ - memory::ram::Page, - processor::processor::Emulator -}; - -const STACK_SIZE: usize = 1024 * 1024 * 16; +use emulator_core::{DsaImplementation, EmulatorController, processor::Emulator}; fn main() { let args = DsaArgs::parse(); - let mut emulator = Emulator::new().apply_memory_map(args.get_memory_map()); + let mut emulator = Emulator::new(); + emulator.apply_memory_map(args.get_memory_map()); - let mem_handle = emulator.memory_mut().clone(); - let state = emulator.state_handle(); - - if let Some(bin) = args.get_binary() { - for (i, ch) in bin.chunks(4).enumerate() { - let instruction = Instruction(u32::from_le_bytes(ch.try_into().unwrap())); - let hex = ch - .iter() - .map(|b| format!("{:02X}", b)) - .collect::>() - .join(" "); - let chars = ch - .iter() - .map(|b| { - if b.is_ascii_graphic() { - *b as char - } else { - '.' - } - }) - .collect::(); - println!("{:04X}: {:<12} {:4} {:?}", i * 4, hex, chars, instruction); - } - - let program: Vec = Page::paginate(&bin).collect(); - - for (i, page) in program.iter().enumerate() { - emulator.memory_mut().write_page(i as u32 * 4096, &page); - } - } - - let _runner = thread::Builder::new() - .stack_size(STACK_SIZE) - .spawn(move || emulator.run().unwrap()) - .unwrap(); + let controller = EmulatorController::new(Box::new(emulator)); + // + // if let Some(bin) = args.get_binary() { + // for (i, ch) in bin.chunks(4).enumerate() { + // let instruction = Instruction(u32::from_le_bytes(ch.try_into().unwrap())); + // let hex = ch + // .iter() + // .map(|b| format!("{:02X}", b)) + // .collect::>() + // .join(" "); + // let chars = ch + // .iter() + // .map(|b| { + // if b.is_ascii_graphic() { + // *b as char + // } else { + // '.' + // } + // }) + // .collect::(); + // println!("{:04X}: {:<12} {:4} {:?}", i * 4, hex, chars, instruction); + // } + // + // let program: Vec = Page::paginate(&bin).collect(); + // + // for (i, page) in program.iter().enumerate() { + // emulator.memory_mut().write_page(i as u32 * 4096, &page); + // } + // } + if args.headless() { run_server(&args); } else { - run_app(state, mem_handle).unwrap() + run_app(controller).unwrap() } } diff --git a/dsa/emulator/emulator_ui/src/ui/components/disassembler.rs b/dsa/emulator/emulator_ui/src/ui/components/disassembler.rs deleted file mode 100644 index 91e7ef7..0000000 --- a/dsa/emulator/emulator_ui/src/ui/components/disassembler.rs +++ /dev/null @@ -1,219 +0,0 @@ -use std::any::Any; -use std::fs::File; -use std::io::BufReader; -use std::path::Path; -use egui::{ScrollArea, Ui, WidgetText}; -use egui_extras::{Size, StripBuilder}; -use egui_file::FileDialog; -use egui_tiles::TileId; -use common::formats::binary_dse::DseExecutable; -use disassembler::{disassemble, Disassembly}; -use crate::ui::components::Component; -use crate::ui::debugger::Action; -use crate::ui::debugger::panels::TilePane; - -pub struct Disassembler { - visible: bool, - dialog: Option, - error: Option, - - program: Option, - disassembled: Option, -} - -impl TilePane for Disassembler { - fn title(&self) -> WidgetText { - "Disassembler".into() - } - - fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { - Component::ui(self, ui); - None - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} - -impl Disassembler { - pub(crate) fn new() -> Self { - Self { - visible: true, - dialog: None, - error: None, - - program: None, - disassembled: None, - } - } - - pub fn new_open(path: &Path) -> Self { - let reader = File::open(path).unwrap(); - let bin = DseExecutable::read(&mut BufReader::new(reader)).unwrap(); - - Self { - visible: true, - dialog: None, - error: None, - program: Some(bin), - disassembled: None, - } - } - - fn open(&mut self) { - let Ok(cwd) = std::env::current_dir() else { - self.error = Some("Couldn't get CWD to open file!".to_string()); - return; - }; - - if self.dialog.is_none() { - let mut dialog = FileDialog::open_file(); - dialog.set_path(cwd); - dialog.open(); - self.dialog = Some(dialog); - } - } - - fn handle_file_dialog(&mut self, ui: &mut Ui) { - - let Some(dialog) = &mut self.dialog else { - return; - }; - - if !dialog.show(ui).selected() { return }; - - let Some(file) = dialog.path() else { - return - }; - - if !file.extension().is_some_and(|ext| ext == "dsb") { - return - } - - let reader = match File::open(file) { - Ok(f) => f, - Err(e) => { - self.error = Some(e.to_string()); - return; - } - }; - - match DseExecutable::read(&mut BufReader::new(reader)) { - Ok(bin) => self.program = Some(bin), - Err(e) => { - self.error = Some(e.to_string()); - return; - } - }; - - self.dialog = None; - } -} - -impl Component for Disassembler { - fn title(&self) -> &'static str { - "Disassembler" - } - - fn visible(&mut self) -> &mut bool { - &mut self.visible - } - - fn ui(&mut self, ui: &mut egui::Ui) { - self.handle_file_dialog(ui); - - ui.horizontal(|ui| { - ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0); - ui.spacing_mut().item_spacing.x = 6.0; - - // Opens a file - // ui.add_enabled(false, egui::Button::new("Open")); - if ui.button("Open").clicked() { - self.open(); - } - }); - - - if let Some(binary) = &self.program { - if let Some(output) = &self.disassembled { - StripBuilder::new(ui) - .size(Size::exact(350.0)) - .size(Size::remainder().at_most(500.0)) - .horizontal(|mut strip| { - - strip.cell(|ui| { - ScrollArea::vertical() - .id_salt("symbols") - .show(ui, |ui| { - egui::Grid::new("symbols_grid") - .striped(true) - .spacing([12.0, 2.0]) // horizontal, vertical spacing - .show(ui, |ui| { - // Header - ui.strong("Addr"); - ui.strong("Name"); - ui.strong("Section"); - ui.strong("Bind"); - ui.end_row(); - - // Rows - for symbol in &output.symbols { - ui.monospace(format!("{:#06x}", symbol.address)); - ui.monospace(&symbol.name); - ui.label(format!("{:?}", symbol.section)); - ui.label(format!("{:?}", symbol.binding)); - - ui.end_row(); - } - }); - }); - }); - - strip.cell(|ui| { - ScrollArea::vertical() - .id_salt("asm") - .show(ui, |ui| { - egui::Grid::new("asm_grid") - .striped(true) - .spacing([12.0, 2.0]) - .show(ui, |ui| { - for (addr, line) in output.text_lines.iter().enumerate() { - ui.label( - egui::RichText::new(format!("{:04X}", addr)) - .monospace() - .weak(), - ); - - ui.monospace(line); - - ui.end_row(); - } - }); - }); - }); - }); - - - // ui.horizontal(|ui| { - // - // egui::ScrollArea::vertical() - // .id_salt("symbols_scroll") - // .show(ui, |ui| { - // - // }); - // - // ui.separator(); - // - // egui::ScrollArea::vertical() - // .id_salt("asm_scroll") - // .show(ui, |ui| { - // - // }); - // }); - } else { - self.disassembled = Some(disassemble(&binary)) - } - } - } -} \ No newline at end of file diff --git a/dsa/emulator/emulator_ui/src/ui/components/controller.rs b/dsa/emulator/emulator_ui/src/ui/controller.rs similarity index 55% rename from dsa/emulator/emulator_ui/src/ui/components/controller.rs rename to dsa/emulator/emulator_ui/src/ui/controller.rs index 63095ba..a6784c9 100644 --- a/dsa/emulator/emulator_ui/src/ui/components/controller.rs +++ b/dsa/emulator/emulator_ui/src/ui/controller.rs @@ -1,79 +1,58 @@ -use crate::ui::debugger::ui_helpers::{ PaddedWidget, StatusIndicator}; -use std::sync::{Arc, atomic::Ordering}; -use std::thread; -use std::time::Duration; +use crate::ui::ui_helpers::StatusIndicator; use eframe::emath::Vec2; use eframe::epaint::Color32; -use egui::Widget; -use super::Component; use common::prelude::Register; -use emulator_core::{ - memory::ram::MemoryBank, - processor::state::SharedState -}; -use emulator_core::processor::state::RunningState; +use emulator_core::EmulatorHandle; +use emulator_core::processor::RunningState; +use crate::ui::ui_helpers::PaddedWidget; pub struct Controller { - state: Arc, - mem: MemoryBank, + emulator: EmulatorHandle, visible: bool, } impl Controller { - pub fn new(state: Arc, mem: MemoryBank) -> Self { + pub fn new(state: EmulatorHandle) -> Self { Self { - state, - mem, + emulator: state, visible: false, } } -} -impl Component for Controller { - fn title(&self) -> &str { - "Control Panel" - } + pub fn ui(&mut self, ui: &mut egui::Ui) { - fn visible(&mut self) -> &mut bool { - &mut self.visible - } - - fn ui(&mut self, ui: &mut egui::Ui) { - - self.state.update_req.store(true, Ordering::Relaxed); - - thread::sleep(Duration::from_millis(5)); - - - let state = self.state.proc.load(); - let paused = self.state.paused.load(Ordering::Relaxed); + self.emulator.request_update(); egui::Frame::new() .inner_margin(10.0) .show(ui, |ui| { ui.horizontal(|ui| { + let status = self.emulator.status(); let padding = Vec2::new(14.0, 8.0); - if match state.running { - RunningState::Paused | RunningState::Halted | RunningState::Breakpoint(_) - => PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui), - RunningState::Running - => PaddedWidget::button("⏸ Pause").color(Color32::YELLOW).ui(ui), + match status { + RunningState::Paused | RunningState::Halted | RunningState::Breakpoint(_) => { + if PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui).clicked() { + self.emulator.resume() + } + }, + + RunningState::Running => { + if PaddedWidget::button("⏸ Pause").color(Color32::YELLOW).ui(ui).clicked() { + self.emulator.pause() + } + } + RunningState::Shutdown => { ui.add_enabled_ui(false, |ui| { - PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui) - }).response - }, - }.clicked() - { - self.state.paused.store(!paused, Ordering::Relaxed); + PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui); + }); + } } if PaddedWidget::button("⟳ Reset All").color(Color32::YELLOW).ui(ui).clicked() { - self.state.reseting.store(true, Ordering::Relaxed); - self.mem.clear(); - self.state.reseting.store(false, Ordering::Relaxed); + self.emulator.reset(); } // Step @@ -100,16 +79,17 @@ impl Component for Controller { // } // } - state.running.clone().ui(ui, padding); + + status.clone().ui(ui, padding); ui.separator(); - let pcx = state.registers[Register::Pcx as usize]; - let clock = state.clock; - let time = state.time.as_micros(); + let pcx = self.emulator.get_register(Register::Pcx); + let clock = self.emulator.clock(); + let time = self.emulator.time().as_micros(); let mips = if time != 0 { - state.clock as u128 / time + clock as u128 / time } else { 0 }; diff --git a/dsa/emulator/emulator_ui/src/ui/debugger/mod.rs b/dsa/emulator/emulator_ui/src/ui/debugger/mod.rs deleted file mode 100644 index d9483f5..0000000 --- a/dsa/emulator/emulator_ui/src/ui/debugger/mod.rs +++ /dev/null @@ -1,299 +0,0 @@ -use std::fmt::Debug; -use std::fs::File; -use std::io::BufReader; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Duration; -use egui::{CentralPanel, Panel, Ui}; -use egui_tiles::TileId; -use common::formats::binary_dse::DseExecutable; -use emulator_core::memory::ram::MemoryBank; -use emulator_core::processor::state::SharedState; -use crate::ui::components::controller::Controller; -use crate::ui::components::disassembler::Disassembler; -use crate::ui::components::memory::MemoryInspector; -use crate::ui::DsaUi; -use crate::ui::components::Component; -use dialog::confirm_dialog::ConfirmDialog; -use panels::documentation::Documentation; -use panels::editor::Editor; -use dialog::file_dialog::DsaFileDialog; -use emulator_core::memory::PhysAddr; -use panels::PanelFactory; -use crate::ui::components::display::Display; -use crate::ui::components::framebuffer::FrameBuffer; -use crate::ui::components::registers::Registers; -use crate::ui::components::serial::Serial; -use crate::ui::debugger::dialog::error_dialog::ErrorDialog; -use crate::ui::debugger::panels::{CentralTiles, TilePane}; -use crate::ui::debugger::panels::about::AboutScreen; - -pub mod dialog; -pub mod panels; -pub mod ui_helpers; - -trait FileFormat: Debug + Clone { - fn extension(&self) -> &'static str; -} - -impl FileFormat for DseExecutable { - fn extension(&self) -> &'static str { - "dse" - } -} - -#[derive(Debug, Clone)] -pub enum Action { - RequestCloseTab(TileId), - CloseTab(TileId), - Quit, - OpenFile(PathBuf), - // OpenCachedFile(Box), - LoadBinary(PathBuf), - SaveFile(PathBuf, TileId), - RequestSaveAs(TileId), - ShowError(String), -} - -struct EmulatorHandle { - state: Arc, - memory: MemoryBank, -} - -impl EmulatorHandle { - pub fn new(state: Arc, memory: MemoryBank) -> Self { - Self { state, memory } - } - - pub fn write_bytes(&mut self, offset: PhysAddr, data: &[u8]) { - self.memory.write_bytes(offset, data); - } -} - - -pub struct DebuggerUi { - emulator: EmulatorHandle, - - central_tiles: CentralTiles, - - controller: Controller, - confirm_dialog: ConfirmDialog, - file_dialog: DsaFileDialog, - error_dialog: ErrorDialog, - - tools: Vec -} - -impl DebuggerUi { - - fn open_or_focus( - central_tiles: &mut CentralTiles, - slot: &mut Option, - make_pane: impl FnOnce() -> T, - ) { - match slot { - Some(tile_id) => central_tiles.activate_tab(*tile_id), - None => *slot = Some(central_tiles.add_tab(make_pane())), - } - } - - pub fn new( - cc: &eframe::CreationContext, - state: Arc, - mem_handle: MemoryBank, - ) -> Self { - DsaUi::configure_appearance(&cc.egui_ctx); - Self { - emulator: EmulatorHandle::new(state.clone(), mem_handle.clone()), - central_tiles: CentralTiles::new(), - controller: Controller::new(state.clone(), mem_handle.clone()), - confirm_dialog: ConfirmDialog::new(), - file_dialog: DsaFileDialog::new(), - error_dialog: ErrorDialog::default(), - - tools: vec![ - PanelFactory::register("Memory Inspector", { - let mem_handle = mem_handle.clone(); - move || MemoryInspector::new(mem_handle.clone()) - }), - PanelFactory::register("Documentation", Documentation::default), - PanelFactory::register("Display", { - let mem_handle = mem_handle.clone(); - move || Display::new(80, 25, 0x20000, mem_handle.clone()) - }), - PanelFactory::register("Framebuffer", { - let mem_handle = mem_handle.clone(); - move || FrameBuffer::new(320, 240, 0x30000, mem_handle.clone()) - }), - PanelFactory::register("Serial", { - let mem_handle = mem_handle.clone(); - let state = state.clone(); - move || Serial::new(0x40000, state.clone(), mem_handle.clone()) - }), - PanelFactory::register("Registers", { - let state = state.clone(); - move || Registers::new(state.clone()) - }) - ] - } - } - - pub fn perform_action(&mut self, action: Action) { - match action { - - Action::RequestCloseTab(tile_id) => { - self.confirm_dialog.open( - "Close Tab", - "Closing this tab may result in losing unsaved work", - Action::CloseTab(tile_id) - ); - } - - Action::CloseTab(tile_id) => { - self.central_tiles.close_tab(tile_id); - } - - Action::Quit => std::process::exit(0), - - // Opens a file (usually constructed by the file dialogue) - Action::OpenFile(path) => { - match path.extension().map(|s| s.to_str()).flatten() { - Some("dsa" | "dsc") => { self.central_tiles.add_tab(Editor::new_open(&path)); }, - Some("dsb") => { self.central_tiles.add_tab(Disassembler::new_open(&path)); }, - Some("md") => { self.central_tiles.add_tab(Documentation::open(&path)); }, - Some(_) => todo!(), - None => {}, - } - } - - Action::ShowError(err) => { - self.error_dialog.open(err); - } - - Action::LoadBinary(path) => { - if let Some(ext) = path.extension().map(|s| s.to_str()).flatten() && ext == "dsb" { - - let Ok(file) = File::open(path) else { - self.perform_action(Action::ShowError("Failed to open file".into())); - return - }; - - let Ok(binary) = DseExecutable::read(&mut BufReader::new(file)) else { - self.perform_action(Action::ShowError("Failed to read binary".into())); - return; - }; - - self.emulator.write_bytes(0, &binary.to_memory_image()) - } - } - - Action::SaveFile(path, tile) => { - if let Some(editor) = self.central_tiles.pane_mut::(tile) { - if let Err(e) = std::fs::write(path, editor.get_buffer().as_bytes()) { - self.perform_action(Action::ShowError(e.to_string())) - } - } - } - - Action::RequestSaveAs(tile) => { - self.file_dialog.open_save(move |path| Action::SaveFile(path, tile)); - } - }; - } - - fn toolbar(&mut self, ui: &mut Ui) { - egui::MenuBar::new().ui(ui, |ui| { - - let save_enabled = self.central_tiles.active_tile() - .and_then(|id| self.central_tiles.pane_mut::(id)) - .is_some(); - - ui.set_height(30.0); - ui.menu_button("File", |ui| { - - if ui.button("Open").clicked() { - self.file_dialog.open_pick(Action::OpenFile); - } - - if ui.button("Load Binary").clicked() { - self.file_dialog.open_pick(Action::LoadBinary); - } - - if ui.add_enabled(save_enabled, egui::Button::new("Save")).clicked() { - if let Some(active) = self.central_tiles.active_tile() { - if let Some(editor) = self.central_tiles.pane_mut::(active) { - match editor.filename().cloned() { - Some(path) => std::fs::write(path, editor.get_buffer().as_bytes()).unwrap(), - None => self.file_dialog.open_save(move |path| Action::SaveFile(path, active)), - } - } - } - } - - if ui.add_enabled(save_enabled, egui::Button::new("Save As")).clicked() { - if let Some(active) = self.central_tiles.active_tile() { - if let Some(editor) = self.central_tiles.pane_mut::(active) { - self.file_dialog.open_save(move |path| Action::SaveFile(path, active)) - } - } - } - - if ui.button("Exit").clicked() { - self.confirm_dialog.open("Exit","Exit Application\n(you will lose unsaved work)", Action::Quit) - } - }); - - ui.menu_button("Tools", |ui| { - for PanelFactory(name, constructor) in &self.tools { - if ui.button(name).clicked() { - self.central_tiles.add_dyn_tab(constructor()); - } - } - }); - - ui.menu_button("Help", |ui| { - if ui.button("About").clicked() { - self.central_tiles.add_tab(AboutScreen); - } - }); - }); - } -} - -impl eframe::App for DebuggerUi { - fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { - ui.request_repaint_after(Duration::from_millis(16)); - - if let Some(action) = self.confirm_dialog.show(ui) { - self.perform_action(action); - } - - if let Some(action) = self.file_dialog.show(ui) { - self.perform_action(action); - } - - self.error_dialog.show(ui); - - Panel::top("menu_bar").resizable(false).show(ui, |ui| { - self.toolbar(ui); - }); - - Panel::top("control_panel").resizable(false).show(ui, |ui| { - self.controller.ui(ui); - }); - - Panel::left("left_panel").resizable(false).show(ui, |ui| { - // TODO: File Explorer! - }); - - // Panel::right("right_panel").resizable(false).show(ui, |ui| { - // // TODO: not sure what to put here! - // }); - - CentralPanel::default().show(ui, |ui| { - if let Some(action) = self.central_tiles.ui(ui) { - self.perform_action(action); - } - }); - } -} diff --git a/dsa/emulator/emulator_ui/src/ui/debugger/dialog/confirm_dialog.rs b/dsa/emulator/emulator_ui/src/ui/dialog/confirm_dialog.rs similarity index 97% rename from dsa/emulator/emulator_ui/src/ui/debugger/dialog/confirm_dialog.rs rename to dsa/emulator/emulator_ui/src/ui/dialog/confirm_dialog.rs index 9b29e26..695e35d 100644 --- a/dsa/emulator/emulator_ui/src/ui/debugger/dialog/confirm_dialog.rs +++ b/dsa/emulator/emulator_ui/src/ui/dialog/confirm_dialog.rs @@ -1,5 +1,5 @@ use egui::{Modal, Id, Layout, Align, RichText}; -use crate::ui::debugger::ui_helpers::PaddedWidget; +use crate::ui::ui_helpers::PaddedWidget; pub struct ConfirmDialog { open: bool, diff --git a/dsa/emulator/emulator_ui/src/ui/debugger/dialog/error_dialog.rs b/dsa/emulator/emulator_ui/src/ui/dialog/error_dialog.rs similarity index 100% rename from dsa/emulator/emulator_ui/src/ui/debugger/dialog/error_dialog.rs rename to dsa/emulator/emulator_ui/src/ui/dialog/error_dialog.rs diff --git a/dsa/emulator/emulator_ui/src/ui/debugger/dialog/file_dialog.rs b/dsa/emulator/emulator_ui/src/ui/dialog/file_dialog.rs similarity index 96% rename from dsa/emulator/emulator_ui/src/ui/debugger/dialog/file_dialog.rs rename to dsa/emulator/emulator_ui/src/ui/dialog/file_dialog.rs index 3fca216..740d741 100644 --- a/dsa/emulator/emulator_ui/src/ui/debugger/dialog/file_dialog.rs +++ b/dsa/emulator/emulator_ui/src/ui/dialog/file_dialog.rs @@ -1,8 +1,5 @@ use std::path::PathBuf; -use egui::Ui; use egui_file_dialog::FileDialog; -use crate::ui::debugger::Action; - pub struct DsaFileDialog { inner: FileDialog, // or whatever backend you're using diff --git a/dsa/emulator/emulator_ui/src/ui/debugger/dialog/mod.rs b/dsa/emulator/emulator_ui/src/ui/dialog/mod.rs similarity index 100% rename from dsa/emulator/emulator_ui/src/ui/debugger/dialog/mod.rs rename to dsa/emulator/emulator_ui/src/ui/dialog/mod.rs diff --git a/dsa/emulator/emulator_ui/src/ui/mod.rs b/dsa/emulator/emulator_ui/src/ui/mod.rs index 6e78b44..6dc1193 100644 --- a/dsa/emulator/emulator_ui/src/ui/mod.rs +++ b/dsa/emulator/emulator_ui/src/ui/mod.rs @@ -1,171 +1,349 @@ +use std::fmt::Debug; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; +use std::time::Duration; use eframe::NativeOptions; -use eframe::egui; -use std::sync::Arc; -use std::sync::atomic::Ordering; -use egui::Order::Debug; +use egui::{CentralPanel, Panel, Ui}; +use egui_tiles::TileId; +use common::formats::binary_dse::DseExecutable; +use dialog::confirm_dialog::ConfirmDialog; +use panels::documentation::Documentation; +use panels::editor::Editor; +use dialog::file_dialog::DsaFileDialog; +use emulator_core::{EmulatorController}; +use panels::PanelFactory; +use crate::ui::controller::Controller; +use crate::ui::dialog::error_dialog::ErrorDialog; +use crate::ui::panels::{CentralTiles, TilePane}; +use crate::ui::panels::about::AboutScreen; +use crate::ui::panels::disassembler::BinaryViewer; +use crate::ui::panels::display::Display; +use crate::ui::panels::framebuffer::FrameBuffer; +use crate::ui::panels::memory::MemoryInspector; +use crate::ui::panels::registers::Registers; +use crate::ui::panels::serial::Serial; -mod components; -pub mod debugger; +mod dialog; +mod panels; +mod ui_helpers; +mod controller; -use components::{ - controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer, memory::MemoryInspector, - registers::Registers, serial::Serial, Component, -}; -use emulator_core::{ - config::VERSION, - memory::ram::MemoryBank, - processor::state::SharedState -}; -use crate::ui::components::disassembler::Disassembler; -use crate::ui::debugger::DebuggerUi; - -pub fn run_app(state: Arc, mem_handle: MemoryBank) -> eframe::Result<()> { - - let e = e(); +pub fn run_app(mut controller: EmulatorController) -> eframe::Result<()> { + // initialise emulator + controller.run().unwrap(); eframe::run_native( "Damn Simple Architecture", NativeOptions::default(), - - // Box::new(|cc| Ok(Box::new(DsaUi::new(cc, state, mem_handle)))), - Box::new(|cc| Ok(Box::new(DebuggerUi::new(cc, state, mem_handle)))), + Box::new(|cc| Ok(Box::new(DamnSimpleUI::new(cc, controller)))), ) } - -fn e() -> i32 { - return 5 +trait FileFormat: Debug + Clone { + fn extension(&self) -> &'static str; } -impl eframe::App for DsaUi { +impl FileFormat for DseExecutable { + fn extension(&self) -> &'static str { + "dse" + } +} +#[derive(Debug, Clone)] +pub enum DataSource { + File(PathBuf), + Data(Vec), +} + +#[derive(Debug, Clone)] +pub enum Action { + RequestCloseTab(TileId), + CloseTab(TileId), + Quit, + OpenFile(PathBuf), + // OpenCachedFile(Box), + LoadBinary(DataSource), + SaveFile(PathBuf, TileId), + RequestSaveAs(TileId), + ShowError(String), +} + + +pub struct DamnSimpleUI { + emulator: EmulatorController, + central_tiles: CentralTiles, + + controller: Controller, + confirm_dialog: ConfirmDialog, + file_dialog: DsaFileDialog, + error_dialog: ErrorDialog, + + tools: Vec, +} + + +impl eframe::App for DamnSimpleUI { fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { - // request an update from ui every cycle - self.state.update_req.store(true, Ordering::Relaxed); + ui.request_repaint_after(Duration::from_millis(16)); - // title panel with dsa title - egui::containers::Panel::top("top_panel").show(ui, |ui| { - ui.with_layout( - egui::Layout::top_down_justified(egui::Align::Center) - .with_main_align(egui::Align::Min), - |ui| { - ui.allocate_space(egui::vec2(0.0, 5.0)); - ui.heading(format!("Damn Simple Architecture v{} 🚀", VERSION)); - ui.allocate_space(egui::vec2(0.0, 5.0)); - }, - ); + if let Some(action) = self.confirm_dialog.show(ui) { + self.perform_action(action); + } + + if let Some(action) = self.file_dialog.show(ui) { + self.perform_action(action); + } + + self.error_dialog.show(ui); + + Panel::top("menu_bar").resizable(false).show(ui, |ui| { + self.toolbar(ui); }); - // menu bar. - egui::Panel::top("menu_bar").show(ui, |ui| { - egui::MenuBar::new().ui(ui, |ui| { - ui.menu_button("Panels", |ui| { - ui.set_max_width(300.0); - ui.set_min_width(300.0); - ui.spacing_mut().button_padding = egui::vec2(10.0, 5.0); - - for comp in &mut self.components { - let name = comp.title().to_string(); - ui.toggle_value(comp.visible(), name); - } - }); - }); + Panel::top("control_panel").resizable(false).show(ui, |ui| { + self.controller.ui(ui); }); - egui::CentralPanel::default().show(ui, |ui| { - for c in &mut self.components { - let mut visible = *c.visible(); - if visible { - egui::Window::new(c.title()) - .open(&mut visible) - .show(ui, |ui| { - c.ui(ui); - }); - } - *c.visible() = visible; + Panel::left("left_panel").resizable(false).show(ui, |ui| { + // TODO: File Explorer! + }); + + Panel::bottom("bottom_panel").resizable(false).show(ui, |ui| { + // self.process_manager.ui(ui); + }); + + CentralPanel::default().show(ui, |ui| { + if let Some(action) = self.central_tiles.ui(ui) { + self.perform_action(action); } }); - - ui.request_repaint_after(std::time::Duration::from_millis(16)); // ~60fps } } -pub struct DsaUi { - state: Arc, - mem_handle: MemoryBank, - components: Vec>, -} -impl DsaUi { + + +impl DamnSimpleUI { + + fn open_or_focus( + central_tiles: &mut CentralTiles, + slot: &mut Option, + make_pane: impl FnOnce() -> T, + ) { + match slot { + Some(tile_id) => central_tiles.activate_tab(*tile_id), + None => *slot = Some(central_tiles.add_tab(make_pane())), + } + } + pub fn new( cc: &eframe::CreationContext, - state: Arc, - mem_handle: MemoryBank, + mut emulator: EmulatorController, ) -> Self { - // components - let mut components: Vec> = vec![ - Box::new(Controller::new(state.clone(), mem_handle.clone())), - Box::new(Display::new(80, 25, 0x20000, mem_handle.clone())), - Box::new(FrameBuffer::new(320, 240, 0x30000, mem_handle.clone())), - Box::new(Registers::new(state.clone())), - Box::new(Serial::new(0x40000, state.clone(), mem_handle.clone())), - Box::new(Editor::new(state.clone(), mem_handle.clone())), - Box::new(MemoryInspector::new(mem_handle.clone())), - Box::new(Disassembler::new()) - ]; - components.iter_mut().for_each(|c| *c.visible() = false); - - Self::configure_appearance(&cc.egui_ctx); - let app = Self { - state, - mem_handle, - components, - }; - app + configure_appearance(&cc.egui_ctx); + let handle = emulator.get_handle().clone(); + Self { + central_tiles: CentralTiles::new(), + controller: Controller::new(handle.clone()), + confirm_dialog: ConfirmDialog::new(), + file_dialog: DsaFileDialog::new(), + error_dialog: ErrorDialog::default(), + + tools: vec![ + PanelFactory::register("Memory Inspector", { + let handle = handle.clone(); + move || MemoryInspector::new(handle.clone()) + }), + PanelFactory::register("Documentation", Documentation::default), + PanelFactory::register("Display", { + let handle = handle.clone(); + move || Display::new(80, 25, 0x20000, handle.clone()) + }), + PanelFactory::register("Framebuffer", { + let handle = handle.clone(); + move || FrameBuffer::new(320, 240, 0x30000, handle.clone()) + }), + PanelFactory::register("Serial", { + let handle = handle.clone(); + move || Serial::new(0x40000, handle.clone()) + }), + PanelFactory::register("Registers", { + let handle = handle.clone(); + move || Registers::new(handle.clone()) + }) + ], + emulator, + } } - pub fn configure_appearance(ctx: &egui::Context) { - // configure appearance of UI elements - let mut visuals = egui::Visuals::dark(); + pub fn perform_action(&mut self, action: Action) { + match action { - // --- your existing settings --- - visuals.window_fill = egui::Color32::from_rgb(20, 20, 20); - visuals.panel_fill = egui::Color32::from_rgb(20, 20, 20); - visuals.widgets.inactive.fg_stroke = - egui::Stroke::from((1.0, egui::Color32::from_rgb(255, 255, 255))); - visuals.widgets.inactive.bg_stroke = - egui::Stroke::from((1.0, egui::Color32::from_rgb(60, 60, 60))); - visuals.widgets.inactive.corner_radius = egui::CornerRadius::from(4); - visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(20, 20, 20); - visuals.widgets.inactive.weak_bg_fill = egui::Color32::from_rgb(20, 20, 20); - visuals.widgets.inactive.expansion = 1.0; + Action::RequestCloseTab(tile_id) => { + self.confirm_dialog.open( + "Close Tab", + "Closing this tab may result in losing unsaved work", + Action::CloseTab(tile_id) + ); + } - // tile resize handle - visuals.window_stroke = egui::Stroke::new(1.0, egui::Color32::from_rgb(45, 45, 45)); - visuals.extreme_bg_color = egui::Color32::from_rgb(20, 20, 20); + Action::CloseTab(tile_id) => { + self.central_tiles.close_tab(tile_id); + } - ctx.set_visuals(visuals); + Action::Quit => std::process::exit(0), - let mut fonts = egui::FontDefinitions::default(); - fonts.font_data.insert( - "JetBrains Mono Nerd Font".to_string(), - std::sync::Arc::new(egui::FontData::from_static(include_bytes!( - "../../font/JetBrainsMonoNerdFontMono_Regular.ttf", - ))), - ); + // Opens a file (usually constructed by the file dialogue) + Action::OpenFile(path) => { + match path.extension().map(|s| s.to_str()).flatten() { + Some("dsa" | "dsc") => { self.central_tiles.add_tab(Editor::new_open(&path)); }, + Some("dsb") => { self.central_tiles.add_tab(BinaryViewer::new_open(&path)); }, + Some("md") => { self.central_tiles.add_tab(Documentation::open(&path)); }, + Some(_) => todo!(), + None => {}, + } + } - fonts - .families - .entry(egui::FontFamily::Proportional) - .or_default() - .insert(0, "JetBrains Mono Nerd Font".to_string()); + Action::ShowError(err) => { + self.error_dialog.open(err); + } - fonts - .families - .entry(egui::FontFamily::Monospace) - .or_default() - .insert(0, "JetBrains Mono Nerd Font".to_string()); + Action::LoadBinary(DataSource::Data(exe)) => { + self.emulator.get_handle().mem_write(0, &exe); + } - ctx.set_fonts(fonts); + Action::LoadBinary(DataSource::File(path)) => { + if let Some(ext) = path.extension().map(|s| s.to_str()).flatten() && ext == "dsb" { + + let Ok(file) = File::open(path) else { + self.perform_action(Action::ShowError("Failed to open file".into())); + return + }; + + let Ok(binary) = DseExecutable::read(&mut BufReader::new(file)) else { + self.perform_action(Action::ShowError("Failed to read binary".into())); + return; + }; + + self.emulator.get_handle().mem_write(0, &binary.to_memory_image()) + } + } + + Action::SaveFile(path, tile) => { + if let Some(editor) = self.central_tiles.pane_mut::(tile) { + if let Err(e) = std::fs::write(path, editor.get_buffer().as_bytes()) { + self.perform_action(Action::ShowError(e.to_string())) + } + } + } + + Action::RequestSaveAs(tile) => { + self.file_dialog.open_save(move |path| Action::SaveFile(path, tile)); + } + }; + } + + fn toolbar(&mut self, ui: &mut Ui) { + egui::MenuBar::new().ui(ui, |ui| { + + let save_enabled = self.central_tiles.active_tile() + .and_then(|id| self.central_tiles.pane_mut::(id)) + .is_some(); + + ui.set_height(30.0); + ui.menu_button("File", |ui| { + + if ui.button("Open").clicked() { + self.file_dialog.open_pick(Action::OpenFile); + } + + if ui.button("Load Binary").clicked() { + self.file_dialog.open_pick(|path| Action::LoadBinary(DataSource::File(path))); + } + + if ui.add_enabled(save_enabled, egui::Button::new("Save")).clicked() { + if let Some(active) = self.central_tiles.active_tile() { + if let Some(editor) = self.central_tiles.pane_mut::(active) { + match editor.filename().cloned() { + Some(path) => std::fs::write(path, editor.get_buffer().as_bytes()).unwrap(), + None => self.file_dialog.open_save(move |path| Action::SaveFile(path, active)), + } + } + } + } + + if ui.add_enabled(save_enabled, egui::Button::new("Save As")).clicked() { + if let Some(active) = self.central_tiles.active_tile() { + if let Some(editor) = self.central_tiles.pane_mut::(active) { + self.file_dialog.open_save(move |path| Action::SaveFile(path, active)) + } + } + } + + if ui.button("Exit").clicked() { + self.confirm_dialog.open("Exit","Exit Application\n(you will lose unsaved work)", Action::Quit) + } + }); + + ui.menu_button("Tools", |ui| { + for PanelFactory(name, constructor) in &self.tools { + if ui.button(name).clicked() { + self.central_tiles.add_dyn_tab(constructor()); + } + } + }); + + ui.menu_button("Help", |ui| { + if ui.button("About").clicked() { + self.central_tiles.add_tab(AboutScreen); + } + }); + }); } } + +pub fn configure_appearance(ctx: &egui::Context) { + // configure appearance of UI elements + let mut visuals = egui::Visuals::dark(); + + // --- your existing settings --- + visuals.window_fill = egui::Color32::from_rgb(20, 20, 20); + visuals.panel_fill = egui::Color32::from_rgb(20, 20, 20); + visuals.widgets.inactive.fg_stroke = + egui::Stroke::from((1.0, egui::Color32::from_rgb(255, 255, 255))); + visuals.widgets.inactive.bg_stroke = + egui::Stroke::from((1.0, egui::Color32::from_rgb(60, 60, 60))); + visuals.widgets.inactive.corner_radius = egui::CornerRadius::from(4); + visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(20, 20, 20); + visuals.widgets.inactive.weak_bg_fill = egui::Color32::from_rgb(20, 20, 20); + visuals.widgets.inactive.expansion = 1.0; + + // tile resize handle + visuals.window_stroke = egui::Stroke::new(1.0, egui::Color32::from_rgb(45, 45, 45)); + visuals.extreme_bg_color = egui::Color32::from_rgb(20, 20, 20); + + ctx.set_visuals(visuals); + + let mut fonts = egui::FontDefinitions::default(); + fonts.font_data.insert( + "JetBrains Mono Nerd Font".to_string(), + std::sync::Arc::new(egui::FontData::from_static(include_bytes!( + "../../font/JetBrainsMonoNerdFontMono_Regular.ttf", + ))), + ); + + fonts + .families + .entry(egui::FontFamily::Proportional) + .or_default() + .insert(0, "JetBrains Mono Nerd Font".to_string()); + + fonts + .families + .entry(egui::FontFamily::Monospace) + .or_default() + .insert(0, "JetBrains Mono Nerd Font".to_string()); + + ctx.set_fonts(fonts); +} diff --git a/dsa/emulator/emulator_ui/src/ui/debugger/panels/about.rs b/dsa/emulator/emulator_ui/src/ui/panels/about.rs similarity index 72% rename from dsa/emulator/emulator_ui/src/ui/debugger/panels/about.rs rename to dsa/emulator/emulator_ui/src/ui/panels/about.rs index 400bd23..65d5d02 100644 --- a/dsa/emulator/emulator_ui/src/ui/debugger/panels/about.rs +++ b/dsa/emulator/emulator_ui/src/ui/panels/about.rs @@ -1,13 +1,10 @@ -use std::default::Default; -use std::any::Any; -use std::path::Path; +use crate::ui::Action; +use crate::ui::panels::TilePane; use eframe::epaint::Direction; -use egui::{Ui, Widget, WidgetText}; -use egui_commonmark::{CommonMarkCache, CommonMarkViewer}; +use egui::{Ui, WidgetText}; use egui_tiles::TileId; -use emulator_core::config::{VERSION}; -use crate::ui::debugger::Action; -use crate::ui::debugger::panels::TilePane; +use emulator_core::config::VERSION; +use std::any::Any; pub struct AboutScreen; diff --git a/dsa/emulator/emulator_ui/src/ui/panels/disassembler.rs b/dsa/emulator/emulator_ui/src/ui/panels/disassembler.rs new file mode 100644 index 0000000..8248c8a --- /dev/null +++ b/dsa/emulator/emulator_ui/src/ui/panels/disassembler.rs @@ -0,0 +1,283 @@ +use std::any::Any; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; +use std::sync::mpsc; +use std::sync::Arc; +use std::thread; + +use egui::{ScrollArea, Ui, WidgetText}; +use egui_extras::{Size, StripBuilder}; +use egui_file::FileDialog; +use egui_tiles::TileId; + +use common::formats::binary_dse::DseExecutable; +use disassembler::{disassemble, Disassembly}; + +use crate::ui::panels::TilePane; +use crate::ui::{Action, DataSource}; +use crate::ui::panels::memory::MemoryViewer; + +#[derive(PartialEq, Clone, Copy)] +enum ViewMode { + Memory, + Disassembly, +} + +enum DisassemblyState { + Idle, + Running(mpsc::Receiver), + Done(Disassembly), +} + +pub struct BinaryViewer { + dialog: Option, + error: Option, + + program: Option>, + memory_image: Option>, + + view: ViewMode, + memory_viewer: MemoryViewer, + disassembly: DisassemblyState, +} + +impl BinaryViewer { + pub fn new() -> Self { + Self { + dialog: None, + error: None, + program: None, + memory_image: None, + view: ViewMode::Memory, + memory_viewer: MemoryViewer::new(), + disassembly: DisassemblyState::Idle, + } + } + + pub fn new_open(path: &Path) -> Self { + let mut viewer = Self::new(); + match File::open(path).map(|f| DseExecutable::read(&mut BufReader::new(f))) { + Ok(Ok(bin)) => viewer.load_program(bin), + Ok(Err(e)) => viewer.error = Some(format!("Failed to load {}: {e}", path.display())), + Err(e) => viewer.error = Some(format!("Failed to load {}: {e}", path.display())), + } + viewer + } + + fn load_program(&mut self, bin: DseExecutable) { + // TODO: replace with however DseExecutable actually exposes its raw memory image. + // Assuming a method like this exists — swap for the real accessor. + self.memory_image = Some(bin.to_memory_image()); + self.program = Some(Arc::new(bin)); + self.disassembly = DisassemblyState::Idle; + self.view = ViewMode::Memory; + self.error = None; + } + + fn open(&mut self) { + let Ok(cwd) = std::env::current_dir() else { + self.error = Some("Couldn't get CWD to open file!".to_string()); + return; + }; + + if self.dialog.is_none() { + let mut dialog = FileDialog::open_file(); + dialog.set_path(cwd); + dialog.open(); + self.dialog = Some(dialog); + } + } + + fn handle_file_dialog(&mut self, ui: &mut Ui) { + let Some(dialog) = &mut self.dialog else { return }; + if !dialog.show(ui).selected() { return } + + let Some(file) = dialog.path() else { return }; + let file = file.to_path_buf(); + self.dialog = None; + + if !file.extension().is_some_and(|ext| ext == "dsb") { + self.error = Some("Selected file is not a .dsb binary".into()); + return; + } + + let reader = match File::open(&file) { + Ok(f) => f, + Err(e) => { self.error = Some(e.to_string()); return; } + }; + + match DseExecutable::read(&mut BufReader::new(reader)) { + Ok(bin) => self.load_program(bin), + Err(e) => self.error = Some(e.to_string()), + } + } + + /// Kicks disassembly off on a background thread so a large binary + /// doesn't stall the UI. `DseExecutable` is wrapped in `Arc` so the + /// thread can borrow it without cloning the whole binary image. + fn start_disassembly(&mut self) { + let Some(program) = self.program.clone() else { return }; + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let result = disassemble(&program); + let _ = tx.send(result); + }); + + self.disassembly = DisassemblyState::Running(rx); + self.view = ViewMode::Disassembly; + } + + fn poll_disassembly(&mut self) { + if let DisassemblyState::Running(rx) = &self.disassembly { + if let Ok(result) = rx.try_recv() { + self.disassembly = DisassemblyState::Done(result); + } + } + } + + fn toolbar(&mut self, ui: &mut Ui) -> Option { + let mut action = None; + + ui.horizontal(|ui| { + ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0); + ui.spacing_mut().item_spacing.x = 6.0; + + if ui.button("Open").clicked() { + self.open(); + } + + let has_program = self.program.is_some(); + + if ui.add_enabled(has_program, egui::Button::new("Load")).clicked() { + if let Some(data) = &self.memory_image { + action = Some(Action::LoadBinary(DataSource::Data(data.clone()))); + } + } + + let disassembling = matches!(self.disassembly, DisassemblyState::Running(_)); + if ui + .add_enabled(has_program && !disassembling, egui::Button::new("Disassemble")) + .clicked() + { + self.start_disassembly(); + } + + ui.separator(); + + ui.selectable_value(&mut self.view, ViewMode::Memory, "Memory"); + let has_disassembly = !matches!(self.disassembly, DisassemblyState::Idle); + ui.add_enabled_ui(has_disassembly, |ui| { + ui.selectable_value(&mut self.view, ViewMode::Disassembly, "Disassembly"); + }); + + if let Some(err) = &self.error { + ui.separator(); + ui.colored_label(egui::Color32::from_rgb(220, 80, 80), err); + } + }); + + action + } +} + +impl TilePane for BinaryViewer { + fn title(&self) -> WidgetText { + "Binary Viewer".into() + } + + fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { + self.handle_file_dialog(ui); + self.poll_disassembly(); + + if matches!(self.disassembly, DisassemblyState::Running(_)) { + ui.ctx().request_repaint(); // keep polling promptly while the background thread works + } + + let action = self.toolbar(ui); + ui.separator(); + + match self.view { + ViewMode::Memory => match &self.memory_image { + Some(memory) => self.memory_viewer.ui(ui, memory, 0), + None => { + ui.centered_and_justified(|ui| { + ui.weak("Open a binary to inspect its memory image."); + }); + } + }, + ViewMode::Disassembly => match &self.disassembly { + DisassemblyState::Idle => { + ui.centered_and_justified(|ui| { + ui.weak("Click Disassemble to generate a disassembly."); + }); + } + DisassemblyState::Running(_) => { + ui.centered_and_justified(|ui| { + ui.horizontal(|ui| { + ui.spinner(); + ui.label("Disassembling…"); + }); + }); + } + DisassemblyState::Done(output) => render_disassembly(ui, output), + }, + } + + action + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} + +fn render_disassembly(ui: &mut Ui, output: &Disassembly) { + StripBuilder::new(ui) + .size(Size::exact(350.0)) + .size(Size::remainder().at_most(500.0)) + .horizontal(|mut strip| { + strip.cell(|ui| { + ScrollArea::vertical().id_salt("symbols").show(ui, |ui| { + egui::Grid::new("symbols_grid") + .striped(true) + .spacing([12.0, 2.0]) + .show(ui, |ui| { + ui.strong("Addr"); + ui.strong("Name"); + ui.strong("Section"); + ui.strong("Bind"); + ui.end_row(); + + for symbol in &output.symbols { + ui.monospace(format!("{:#06x}", symbol.address)); + ui.monospace(&symbol.name); + ui.label(format!("{:?}", symbol.section)); + ui.label(format!("{:?}", symbol.binding)); + ui.end_row(); + } + }); + }); + }); + + strip.cell(|ui| { + ScrollArea::vertical().id_salt("asm").show(ui, |ui| { + egui::Grid::new("asm_grid") + .striped(true) + .spacing([12.0, 2.0]) + .show(ui, |ui| { + for (addr, line) in output.text_lines.iter().enumerate() { + ui.label( + egui::RichText::new(format!("{:04X}", addr)) + .monospace() + .weak(), + ); + ui.monospace(line); + ui.end_row(); + } + }); + }); + }); + }); +} \ No newline at end of file diff --git a/dsa/emulator/emulator_ui/src/ui/components/display.rs b/dsa/emulator/emulator_ui/src/ui/panels/display.rs similarity index 65% rename from dsa/emulator/emulator_ui/src/ui/components/display.rs rename to dsa/emulator/emulator_ui/src/ui/panels/display.rs index 017b084..7632bd3 100644 --- a/dsa/emulator/emulator_ui/src/ui/components/display.rs +++ b/dsa/emulator/emulator_ui/src/ui/panels/display.rs @@ -1,62 +1,41 @@ use std::any::Any; use egui::{Color32, FontId, Ui, Vec2, WidgetText}; use egui_tiles::TileId; -use emulator_core::{ - memory::PhysAddr, - memory::ram::{MemoryBank, Page} -}; -use crate::ui::debugger::Action; -use crate::ui::debugger::panels::TilePane; -use super::Component; +use emulator_core::{memory::ram::{MemoryBank, Page}, memory::PhysAddr, EmulatorHandle}; +use crate::ui::Action; +use crate::ui::panels::TilePane; pub struct Display { - width: usize, - height: usize, + width: u32, + height: u32, addr: PhysAddr, - mem: MemoryBank, + emulator: EmulatorHandle, buffer: Vec, - visible: bool, } impl Display { - pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self { + pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self { Self { - width: width as usize, - height: height as usize, + width, + height, addr, - mem, + emulator, buffer: Vec::new(), - visible: true, } } - pub fn visible(&mut self) -> &mut bool { - &mut self.visible - } - pub fn width(&self) -> usize { - self.width + self.width as usize } pub fn height(&self) -> usize { - self.height + self.height as usize } - fn internal_read(&self) -> Vec { - let pages = (0..((self.width * self.height).div_ceil(Page::SIZE))) - .map(|i| { - let page = self.mem.read_page(self.addr + i as u32 * 4096); - page.as_slice().to_owned() - }) - .flatten() - .take((self.width * self.height) as usize) - .collect::>(); - pages - } pub fn changed(&mut self) -> bool { - let temp = self.internal_read(); + let temp = self.read(); if temp != self.buffer { self.buffer = temp; return true; @@ -65,7 +44,7 @@ impl Display { } pub fn read(&mut self) -> Vec { - self.internal_read() + self.emulator.mem_read(self.addr, self.width * self.height) } } @@ -76,25 +55,6 @@ impl TilePane for Display { } fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { - Component::ui(self, ui); - None - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} - -impl Component for Display { - fn title(&self) -> &str { - "Display" - } - - fn visible(&mut self) -> &mut bool { - self.visible() - } - - fn ui(&mut self, ui: &mut egui::Ui) { let display_w = self.width(); let display_h = self.height(); let data = self.read(); @@ -143,5 +103,10 @@ impl Component for Display { Color32::WHITE, ); } + None + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self } } diff --git a/dsa/emulator/emulator_ui/src/ui/debugger/panels/documentation.rs b/dsa/emulator/emulator_ui/src/ui/panels/documentation.rs similarity index 95% rename from dsa/emulator/emulator_ui/src/ui/debugger/panels/documentation.rs rename to dsa/emulator/emulator_ui/src/ui/panels/documentation.rs index 13b7bbc..7b004e6 100644 --- a/dsa/emulator/emulator_ui/src/ui/debugger/panels/documentation.rs +++ b/dsa/emulator/emulator_ui/src/ui/panels/documentation.rs @@ -4,8 +4,8 @@ use std::path::Path; use egui::{Ui, Widget, WidgetText}; use egui_commonmark::{CommonMarkCache, CommonMarkViewer}; use egui_tiles::TileId; -use crate::ui::debugger::Action; -use crate::ui::debugger::panels::TilePane; +use crate::ui::Action; +use crate::ui::panels::TilePane; pub struct Documentation { buffer: Option, diff --git a/dsa/emulator/emulator_ui/src/ui/debugger/panels/editor.rs b/dsa/emulator/emulator_ui/src/ui/panels/editor/mod.rs similarity index 84% rename from dsa/emulator/emulator_ui/src/ui/debugger/panels/editor.rs rename to dsa/emulator/emulator_ui/src/ui/panels/editor/mod.rs index 1376a0d..721a178 100644 --- a/dsa/emulator/emulator_ui/src/ui/debugger/panels/editor.rs +++ b/dsa/emulator/emulator_ui/src/ui/panels/editor/mod.rs @@ -1,22 +1,18 @@ +use crate::ui::Action; +use crate::ui::panels::TilePane; +use egui::{Key, Ui}; +use egui_code_editor::CodeEditor; +use egui_tiles::TileId; use std::any::Any; use std::path::{Path, PathBuf}; -use egui::accesskit::Role::Code; -use egui::{Key, Ui}; -use egui_code_editor::{CodeEditor, Syntax}; -use egui_file_dialog::FileDialog; -use egui_tiles::TileId; -use crate::ui::components::Component; -use crate::ui::components::editor::{syntax, THEME}; -use crate::ui::debugger::Action; -use crate::ui::debugger::panels::TilePane; +use theme::THEME; +mod syntax; +mod theme; pub struct Editor { buffer: String, filename: Option, - editor: CodeEditor, - dialog: FileDialog, - visible: bool, } impl Default for Editor { @@ -25,8 +21,6 @@ impl Default for Editor { buffer: String::new(), filename: None, editor: CodeEditor::default().with_fontsize(12.0).with_theme(THEME).with_numlines(true), - dialog: FileDialog::new(), - visible: false, } } } @@ -90,4 +84,5 @@ impl TilePane for Editor { fn close_requires_confirmation(&mut self, _tile_id: TileId) -> bool { true } -} \ No newline at end of file +} + diff --git a/dsa/emulator/emulator_ui/src/ui/components/editor/syntax/mod.rs b/dsa/emulator/emulator_ui/src/ui/panels/editor/syntax.rs similarity index 99% rename from dsa/emulator/emulator_ui/src/ui/components/editor/syntax/mod.rs rename to dsa/emulator/emulator_ui/src/ui/panels/editor/syntax.rs index 8583a7a..1755831 100644 --- a/dsa/emulator/emulator_ui/src/ui/components/editor/syntax/mod.rs +++ b/dsa/emulator/emulator_ui/src/ui/panels/editor/syntax.rs @@ -1,7 +1,6 @@ use std::collections::BTreeSet; use egui_code_editor::{Patch, Syntax}; - pub fn dsa() -> Syntax { Syntax { word_start: BTreeSet::new(), diff --git a/dsa/emulator/emulator_ui/src/ui/panels/editor/theme.rs b/dsa/emulator/emulator_ui/src/ui/panels/editor/theme.rs new file mode 100644 index 0000000..4aa15ec --- /dev/null +++ b/dsa/emulator/emulator_ui/src/ui/panels/editor/theme.rs @@ -0,0 +1,18 @@ +use egui_code_editor::ColorTheme; + +pub const THEME: ColorTheme = ColorTheme { + name: "Theme", + dark: true, + bg: "1b1b1b", + cursor: "de5252", // fg4 + selection: "28323B", // bg2 + comments: "444444", // gray1 + functions: "7CCCC7", // green1 + keywords: "6C81E0", // red1 + literals: "A3ABFF", // fg1 + numerics: "8A46CF", // purple1 + punctuation: "99C9C9", // orange1 + strs: "618c84", // aqua1 + types: "B8B9D4", // yellow1 + special: "de5252", // blue1 +}; \ No newline at end of file diff --git a/dsa/emulator/emulator_ui/src/ui/components/framebuffer.rs b/dsa/emulator/emulator_ui/src/ui/panels/framebuffer.rs similarity index 63% rename from dsa/emulator/emulator_ui/src/ui/components/framebuffer.rs rename to dsa/emulator/emulator_ui/src/ui/panels/framebuffer.rs index 7fc784b..c4341db 100644 --- a/dsa/emulator/emulator_ui/src/ui/components/framebuffer.rs +++ b/dsa/emulator/emulator_ui/src/ui/panels/framebuffer.rs @@ -1,35 +1,30 @@ use std::any::Any; use egui::{Ui, Vec2, WidgetText}; use egui_tiles::TileId; -use emulator_core::{ - memory::PhysAddr, - memory::ram::{MemoryBank, Page} -}; -use crate::ui::debugger::Action; -use crate::ui::debugger::panels::TilePane; -use super::Component; +use emulator_core::{memory::PhysAddr, EmulatorHandle}; +use crate::ui::Action; +use crate::ui::panels::TilePane; pub struct FrameBuffer { width: usize, height: usize, addr: PhysAddr, - mem: MemoryBank, - pub buffer: Vec, // RGBA pixels - visible: bool, + emulator: EmulatorHandle, + pub buffer: Vec, texture: Option, } impl FrameBuffer { - pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self { + pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self { + let width = width as usize; + let height = height as usize; Self { - width: width as usize, - height: height as usize, + width, + height, addr, - mem, - buffer: vec![0u32; width as usize * height as usize], - visible: true, - + emulator, + buffer: vec![0; width * height * 4], texture: None, } } @@ -40,25 +35,10 @@ impl FrameBuffer { pub fn height(&self) -> usize { self.height } - pub fn visible(&mut self) -> &mut bool { - &mut self.visible - } - fn internal_read(&self) -> Vec { + fn internal_read(&mut self) -> Vec { let byte_size = self.width * self.height * 4; - let page_count = byte_size.div_ceil(Page::SIZE); - - (0..page_count) - .map(|i| { - let page = self.mem.read_page(self.addr + i as u32 * 4096); - page.as_slice().to_owned() - }) - .flatten() - .take(byte_size) - .collect::>() - .chunks_exact(4) - .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) - .collect() + self.emulator.mem_read(self.addr, byte_size as u32) } pub fn changed(&mut self) -> bool { @@ -70,7 +50,7 @@ impl FrameBuffer { false } - pub fn read(&self) -> &[u32] { + pub fn read(&self) -> &[u8] { &self.buffer } @@ -87,27 +67,12 @@ impl TilePane for FrameBuffer { fn title(&self) -> WidgetText { "Framebuffer".into() } - - fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { - Component::ui(self, ui); - None - } - fn as_any_mut(&mut self) -> &mut dyn Any { self } -} -impl Component for FrameBuffer { - fn title(&self) -> &str { - "Framebuffer" - } - fn visible(&mut self) -> &mut bool { - &mut self.visible - } - - fn ui(&mut self, ui: &mut egui::Ui) { + fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { let changed = self.changed(); // get or create the texture @@ -126,10 +91,10 @@ impl Component for FrameBuffer { if changed { let pixels: Vec = self .buffer + .as_chunks::<4>().0 .iter() - .map(|&p| { - let bytes = p.to_le_bytes(); - egui::Color32::from_rgba_premultiplied(bytes[0], bytes[1], bytes[2], bytes[3]) + .map(|&[red, green, blue, alpha]| { + egui::Color32::from_rgba_premultiplied(red, green, blue, alpha) }) .collect(); @@ -153,5 +118,6 @@ impl Component for FrameBuffer { }; ui.image(egui::load::SizedTexture::new(texture.id(), size)); + None } } diff --git a/dsa/emulator/emulator_ui/src/ui/components/memory.rs b/dsa/emulator/emulator_ui/src/ui/panels/memory.rs similarity index 93% rename from dsa/emulator/emulator_ui/src/ui/components/memory.rs rename to dsa/emulator/emulator_ui/src/ui/panels/memory.rs index 05ace73..a547494 100644 --- a/dsa/emulator/emulator_ui/src/ui/components/memory.rs +++ b/dsa/emulator/emulator_ui/src/ui/panels/memory.rs @@ -4,17 +4,16 @@ use egui::{Ui, WidgetText}; use egui_extras::{Column, TableBuilder}; use egui_tiles::{Tile, TileId}; use common::prelude::Instruction; +use emulator_core::EmulatorHandle; use emulator_core::memory::ram::MemoryBank; -use crate::ui::debugger::Action; -use crate::ui::debugger::panels::TilePane; -use super::Component; +use crate::ui::Action; +use crate::ui::panels::TilePane; pub struct MemoryInspector { - memory: MemoryBank, + emulator: EmulatorHandle, view_size: u32, view_addr: u32, - visible: bool, addr_input: String, // settings @@ -23,14 +22,12 @@ pub struct MemoryInspector { impl MemoryInspector { #[must_use] - pub fn new(memory: MemoryBank) -> Self { + pub fn new(emulator: EmulatorHandle) -> Self { Self { - memory, + emulator, view_size: 1024, view_addr: 0, - visible: false, addr_input: String::from("0x00"), - memory_viewer: MemoryViewer::new() } } @@ -41,27 +38,8 @@ impl TilePane for MemoryInspector { fn title(&self) -> WidgetText { "Memory Inspector".into() } - fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { - Component::ui(self, ui); - None - } - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} - -impl Component for MemoryInspector { - fn title(&self) -> &'static str { - "Memory Inspector" - } - - fn visible(&mut self) -> &mut bool { - &mut self.visible - } - - fn ui(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { ui.label("Address:"); @@ -96,12 +74,14 @@ impl Component for MemoryInspector { ui.label("(hex or decimal)"); }); - let mut window = vec![0u8; self.view_size as usize]; - for i in 0..self.view_size { - window[i as usize] = self.memory.read_byte(self.view_addr + i as u32); - } + let window = self.emulator.mem_read(self.view_addr, self.view_size); + self.memory_viewer.ui(ui, &window, self.view_addr as usize); + None + } - self.memory_viewer.ui(ui, &window, self.view_addr as usize) + + fn as_any_mut(&mut self) -> &mut dyn Any { + self } } diff --git a/dsa/emulator/emulator_ui/src/ui/debugger/panels/mod.rs b/dsa/emulator/emulator_ui/src/ui/panels/mod.rs similarity index 97% rename from dsa/emulator/emulator_ui/src/ui/debugger/panels/mod.rs rename to dsa/emulator/emulator_ui/src/ui/panels/mod.rs index 23db5c8..6d824d2 100644 --- a/dsa/emulator/emulator_ui/src/ui/debugger/panels/mod.rs +++ b/dsa/emulator/emulator_ui/src/ui/panels/mod.rs @@ -1,12 +1,17 @@ pub mod documentation; pub mod editor; pub mod about; +pub mod disassembler; +pub mod display; +pub mod framebuffer; +pub mod memory; +pub mod registers; +pub mod serial; use std::any::Any; -use egui::{Id, Response, Stroke, StrokeKind, Ui, Widget}; use egui_tiles::{Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse}; -use crate::ui::debugger::Action; -use crate::ui::debugger::Action::RequestCloseTab; +use crate::ui::Action; +use crate::ui::Action::RequestCloseTab; /// Anything that can live inside a tab in the central tiled area. /// `as_any_mut` lets callers reach back into a specific pane's concrete diff --git a/dsa/emulator/emulator_ui/src/ui/components/registers.rs b/dsa/emulator/emulator_ui/src/ui/panels/registers.rs similarity index 69% rename from dsa/emulator/emulator_ui/src/ui/components/registers.rs rename to dsa/emulator/emulator_ui/src/ui/panels/registers.rs index 33cfa40..2b32b6f 100644 --- a/dsa/emulator/emulator_ui/src/ui/components/registers.rs +++ b/dsa/emulator/emulator_ui/src/ui/panels/registers.rs @@ -1,38 +1,22 @@ -use std::{f32, sync::Arc}; +use std::f32; use std::any::Any; use egui::{Ui, WidgetText}; use egui_tiles::TileId; use common::prelude::Register; -use emulator_core::processor::state::SharedState; -use crate::ui::components::memory::MemoryInspector; -use crate::ui::debugger::Action; -use crate::ui::debugger::panels::TilePane; -use super::Component; +use emulator_core::EmulatorHandle; +use crate::ui::Action; +use crate::ui::panels::TilePane; pub struct Registers { - state: Arc, - visible: bool, + state: EmulatorHandle, } -impl TilePane for Registers { - fn title(&self) -> WidgetText { - "Registers".into() - } - fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { - Component::ui(self, ui); - None - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} impl Registers { fn section( &mut self, - ui: &mut egui::Ui, + ui: &mut Ui, registers: Vec<(Register, u32)>, col_pairs: usize, name: &str, @@ -54,7 +38,7 @@ impl Registers { for chunk in registers.chunks(col_pairs) { for (reg, val) in chunk { ui.label(reg.to_string()); - ui.label(format!("0x{:08X} ({})", val, val)); + ui.label(format!("0x{:08X} ({:012})", val, val)); } for _ in chunk.len()..col_pairs { ui.label(""); @@ -68,27 +52,21 @@ impl Registers { } impl Registers { - pub fn new(state: Arc) -> Self { + pub fn new(state: EmulatorHandle) -> Self { Self { state, - visible: false, } } } -impl Component for Registers { - fn title(&self) -> &str { - "Registers" +impl TilePane for Registers { + fn title(&self) -> WidgetText { + "Registers".into() } + fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { - fn visible(&mut self) -> &mut bool { - &mut self.visible - } - - fn ui(&mut self, ui: &mut egui::Ui) { ui.set_min_width(200.0); - let state = self.state.proc.load(); let available = ui.available_width(); let col_pairs = match available { 0.0..350.0 => 1, @@ -101,7 +79,7 @@ impl Component for Registers { ui.vertical(|ui| { // ── General Purpose ────────────────────────────────────── let gp_registers: Vec<(Register, u32)> = (0..=15u8) - .map(|i| (Register::from_u8(i).unwrap(), state.registers[i as usize])) + .map(|i| (Register::from_u8(i).unwrap(), self.state.registers()[i as usize])) .collect(); self.section( ui, @@ -113,18 +91,18 @@ impl Component for Registers { // ── Stack ───────────────────────────────────────────────── let stack_registers = vec![ - (Register::Spr, state.registers[Register::Spr as usize]), - (Register::Bpr, state.registers[Register::Bpr as usize]), + (Register::Spr, self.state.get_register(Register::Spr)), + (Register::Bpr, self.state.get_register(Register::Bpr)), ]; self.section(ui, stack_registers, col_pairs, "Stack Registers"); ui.separator(); // ── Special Purpose ─────────────────────────────────────── let special_registers = vec![ - (Register::Acc, state.registers[Register::Acc as usize]), - (Register::Ret, state.registers[Register::Ret as usize]), - (Register::Idr, state.registers[Register::Idr as usize]), - (Register::Mmr, state.registers[Register::Mmr as usize]), + (Register::Acc, self.state.get_register(Register::Acc)), + (Register::Ret, self.state.get_register(Register::Ret)), + (Register::Idr, self.state.get_register(Register::Idr)), + (Register::Mmr, self.state.get_register(Register::Mmr)), ]; self.section( ui, @@ -136,11 +114,16 @@ impl Component for Registers { // ── System ──────────────────────────────────────────────── let system_registers = vec![ - (Register::Pcx, state.registers[Register::Pcx as usize]), - (Register::Sts, state.registers[Register::Sts as usize]), - (Register::Cir, state.registers[Register::Cir as usize]), + (Register::Pcx, self.state.get_register(Register::Pcx)), + (Register::Sts, self.state.get_register(Register::Sts)), + (Register::Cir, self.state.get_register(Register::Cir)), ]; self.section(ui, system_registers, col_pairs, "System Registers"); }); + None + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self } } diff --git a/dsa/emulator/emulator_ui/src/ui/components/serial.rs b/dsa/emulator/emulator_ui/src/ui/panels/serial.rs similarity index 68% rename from dsa/emulator/emulator_ui/src/ui/components/serial.rs rename to dsa/emulator/emulator_ui/src/ui/panels/serial.rs index f833c8e..0ee14aa 100644 --- a/dsa/emulator/emulator_ui/src/ui/components/serial.rs +++ b/dsa/emulator/emulator_ui/src/ui/panels/serial.rs @@ -1,28 +1,18 @@ use std::{ collections::VecDeque, sync::{ - Arc, - atomic::Ordering, mpsc::{Receiver, Sender}, }, }; use std::any::Any; use egui::{Ui, WidgetText}; use egui_tiles::TileId; -use emulator_core::{ - memory::{ - PhysAddr, - ram::MemoryBank - }, - processor::{ - interrupts::Interrupt, - state::SharedState - } -}; -use crate::ui::components::registers::Registers; -use crate::ui::debugger::Action; -use crate::ui::debugger::panels::TilePane; -use super::Component; +use emulator_core::{memory::{ + PhysAddr, +}, EmulatorHandle}; +use emulator_core::processor::Interrupt; +use crate::ui::Action; +use crate::ui::panels::TilePane; /// ## SERIAL LAYOUT: (from the perspective of code inside the ui) /// - [base+0x00]: ser_out valid flag - set by kernel after writing a byte to serial @@ -34,29 +24,11 @@ pub struct Serial { pub write_tx: Sender, pub read_buff: Vec, pub write_buff: String, - - pub visible: bool, -} - -impl TilePane for Serial { - fn title(&self) -> WidgetText { - "Serial".into() - } - - fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { - Component::ui(self, ui); - None - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } } impl Serial { fn uart_io_thread( - mem: MemoryBank, - state: Arc, + mut handle: EmulatorHandle, uart_base: PhysAddr, tx: Sender, rx: Receiver, @@ -68,42 +40,41 @@ impl Serial { write_buffer.push_back(byte); } - let ser_out_valid = mem.read_byte(uart_base + 0); + let ser_out_valid = handle.read_byte(uart_base + 0); // Kernel => ui: valid flag in byte 0, data in byte 1 if ser_out_valid == 0x01 { - let ser_out = mem.read_byte(uart_base + 1); + let ser_out = handle.read_byte(uart_base + 1); let _ = tx.send(ser_out); - mem.write_byte(uart_base, 0x00); // clear the whole word + handle.write_byte(uart_base, 0x00); // clear the whole word } if !write_buffer.is_empty() { // Emulator => kernel: valid flag in byte 2, data in byte 3 - let ser_in_valid = mem.read_byte(uart_base + 2); + let ser_in_valid = handle.read_byte(uart_base + 2); if ser_in_valid == 0x00 { if let Some(byte) = write_buffer.pop_front() { - mem.write_byte(uart_base + 3, byte); - mem.write_byte(uart_base + 2, 0x01); + handle.write_byte(uart_base + 3, byte); + handle.write_byte(uart_base + 2, 0x01); } } std::thread::sleep(std::time::Duration::from_millis(2)); - state.interrupt_queue.send(Interrupt::SerialIn).unwrap(); + handle.interrupt(Interrupt::SerialIn).unwrap(); } std::thread::sleep(std::time::Duration::from_millis(1)); } } - pub fn new(uart_base: PhysAddr, state: Arc, mem_handle: MemoryBank) -> Self { + pub fn new(uart_base: PhysAddr, handle: EmulatorHandle) -> Self { let (read_tx, read_rx) = std::sync::mpsc::channel(); let (write_tx, write_rx) = std::sync::mpsc::channel(); let _ = std::thread::spawn(move || { - Self::uart_io_thread(mem_handle, state, uart_base, read_tx, write_rx) + Self::uart_io_thread(handle, uart_base, read_tx, write_rx) }); Self { - visible: false, write_tx, read_rx, read_buff: Vec::new(), @@ -118,16 +89,11 @@ impl Serial { } } -impl Component for Serial { - fn title(&self) -> &str { - "Serial" +impl TilePane for Serial { + fn title(&self) -> WidgetText { + "Serial".into() } - - fn visible(&mut self) -> &mut bool { - &mut self.visible - } - - fn ui(&mut self, ui: &mut egui::Ui) { + fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { self.update(); // Text field for input and a send button @@ -145,5 +111,11 @@ impl Component for Serial { if ui.button("Clear").clicked() { self.read_buff.clear(); } + + None + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self } } diff --git a/dsa/emulator/emulator_ui/src/ui/debugger/ui_helpers/mod.rs b/dsa/emulator/emulator_ui/src/ui/ui_helpers/mod.rs similarity index 99% rename from dsa/emulator/emulator_ui/src/ui/debugger/ui_helpers/mod.rs rename to dsa/emulator/emulator_ui/src/ui/ui_helpers/mod.rs index 0120819..fb53d15 100644 --- a/dsa/emulator/emulator_ui/src/ui/debugger/ui_helpers/mod.rs +++ b/dsa/emulator/emulator_ui/src/ui/ui_helpers/mod.rs @@ -1,8 +1,7 @@ use eframe::emath::Vec2; use egui::{Color32, Frame, Stroke}; use egui::widget_style::{Classes, WidgetState}; -use emulator_core::processor::state::RunningState; - +use emulator_core::processor::RunningState; pub struct PaddedWidget { text: String,