From 8c19497bc7a1d61495b3cfbf21cb19bb86e27ed1 Mon Sep 17 00:00:00 2001 From: zxq5 Date: Sat, 1 Aug 2026 00:36:08 +0100 Subject: [PATCH] REFACTOR: - created dsa_formats crate for DSA file formats such as DSE executables (.dsb, will become .dse once relocations/linking are introduced) - moved IO devices from emulator_ui to emulator_core (keeping only the UI trait impls in emulator_ui) - other refactors within emulator_ui to make the project structure a bit more sane --- .idea/dsa2.iml | 1 + Cargo.toml | 18 +- dsa/assembler/Cargo.toml | 2 +- dsa/assembler/src/assembler/mod.rs | 16 +- dsa/common/src/formats/mod.rs | 2 - dsa/common/src/lib.rs | 1 - dsa/disassembler/Cargo.toml | 3 +- dsa/disassembler/src/internal.rs | 350 --------- dsa/disassembler/src/lib.rs | 361 ++++++++- dsa/disassembler/src/main.rs | 19 +- dsa/dsa_formats/Cargo.toml | 9 + .../formats => dsa_formats/src}/binary_dse.rs | 18 +- .../formats => dsa_formats/src}/binary_dso.rs | 0 dsa/dsa_formats/src/lib.rs | 12 + .../emulator_core/src/devices/display.rs | 45 ++ .../emulator_core/src/devices/framebuffer.rs | 35 + dsa/emulator/emulator_core/src/devices/mod.rs | 3 + .../src/devices}/serial.rs | 53 +- dsa/emulator/emulator_core/src/lib.rs | 7 +- dsa/emulator/emulator_core/src/memory/mmu.rs | 2 +- dsa/emulator/emulator_core/src/memory/ram.rs | 35 +- dsa/emulator/emulator_ui/Cargo.toml | 35 +- dsa/emulator/emulator_ui/src/args.rs | 2 +- dsa/emulator/emulator_ui/src/lib.rs | 18 +- dsa/emulator/emulator_ui/src/main.rs | 48 +- dsa/emulator/emulator_ui/src/state.rs | 29 + dsa/emulator/emulator_ui/src/ui/appearance.rs | 43 ++ .../src/ui/components/editor/mod.rs | 697 ------------------ .../emulator_ui/src/ui/components/mod.rs | 18 - dsa/emulator/emulator_ui/src/ui/controller.rs | 7 - .../ui/{dialog => dialogs}/confirm_dialog.rs | 0 .../ui/{dialog => dialogs}/error_dialog.rs | 0 .../src/ui/{dialog => dialogs}/file_dialog.rs | 0 .../src/ui/{dialog => dialogs}/mod.rs | 0 dsa/emulator/emulator_ui/src/ui/mod.rs | 134 +--- .../emulator_ui/src/ui/panels/framebuffer.rs | 123 ---- .../emulator_ui/src/ui/process_manager.rs | 7 +- .../src/ui/{panels => tiles}/about.rs | 4 +- .../src/ui/{panels => tiles}/disassembler.rs | 13 +- .../src/ui/{panels => tiles}/documentation.rs | 12 +- .../src/ui/{panels => tiles}/editor/mod.rs | 35 +- .../src/ui/{panels => tiles}/editor/syntax.rs | 0 .../src/ui/{panels => tiles}/editor/theme.rs | 0 .../{panels => tiles/io_devices}/display.rs | 52 +- .../src/ui/tiles/io_devices/framebuffer.rs | 79 ++ .../src/ui/tiles/io_devices/mod.rs | 3 + .../src/ui/tiles/io_devices/serial.rs | 37 + .../src/ui/{panels => tiles}/memory.rs | 13 +- .../src/ui/{panels => tiles}/mod.rs | 14 +- .../src/ui/{panels => tiles}/registers.rs | 10 +- dsa_resources/animation.dsa | 11 + dsa_resources/animation.dsb | Bin 1160 -> 2151 bytes 52 files changed, 816 insertions(+), 1620 deletions(-) delete mode 100644 dsa/disassembler/src/internal.rs create mode 100644 dsa/dsa_formats/Cargo.toml rename dsa/{common/src/formats => dsa_formats/src}/binary_dse.rs (94%) rename dsa/{common/src/formats => dsa_formats/src}/binary_dso.rs (100%) create mode 100644 dsa/dsa_formats/src/lib.rs create mode 100644 dsa/emulator/emulator_core/src/devices/display.rs create mode 100644 dsa/emulator/emulator_core/src/devices/framebuffer.rs create mode 100644 dsa/emulator/emulator_core/src/devices/mod.rs rename dsa/emulator/{emulator_ui/src/ui/panels => emulator_core/src/devices}/serial.rs (70%) create mode 100644 dsa/emulator/emulator_ui/src/state.rs create mode 100644 dsa/emulator/emulator_ui/src/ui/appearance.rs delete mode 100644 dsa/emulator/emulator_ui/src/ui/components/editor/mod.rs delete mode 100644 dsa/emulator/emulator_ui/src/ui/components/mod.rs rename dsa/emulator/emulator_ui/src/ui/{dialog => dialogs}/confirm_dialog.rs (100%) rename dsa/emulator/emulator_ui/src/ui/{dialog => dialogs}/error_dialog.rs (100%) rename dsa/emulator/emulator_ui/src/ui/{dialog => dialogs}/file_dialog.rs (100%) rename dsa/emulator/emulator_ui/src/ui/{dialog => dialogs}/mod.rs (100%) delete mode 100644 dsa/emulator/emulator_ui/src/ui/panels/framebuffer.rs rename dsa/emulator/emulator_ui/src/ui/{panels => tiles}/about.rs (92%) rename dsa/emulator/emulator_ui/src/ui/{panels => tiles}/disassembler.rs (97%) rename dsa/emulator/emulator_ui/src/ui/{panels => tiles}/documentation.rs (93%) rename dsa/emulator/emulator_ui/src/ui/{panels => tiles}/editor/mod.rs (86%) rename dsa/emulator/emulator_ui/src/ui/{panels => tiles}/editor/syntax.rs (100%) rename dsa/emulator/emulator_ui/src/ui/{panels => tiles}/editor/theme.rs (100%) rename dsa/emulator/emulator_ui/src/ui/{panels => tiles/io_devices}/display.rs (66%) create mode 100644 dsa/emulator/emulator_ui/src/ui/tiles/io_devices/framebuffer.rs create mode 100644 dsa/emulator/emulator_ui/src/ui/tiles/io_devices/mod.rs create mode 100644 dsa/emulator/emulator_ui/src/ui/tiles/io_devices/serial.rs rename dsa/emulator/emulator_ui/src/ui/{panels => tiles}/memory.rs (98%) rename dsa/emulator/emulator_ui/src/ui/{panels => tiles}/mod.rs (96%) rename dsa/emulator/emulator_ui/src/ui/{panels => tiles}/registers.rs (98%) diff --git a/.idea/dsa2.iml b/.idea/dsa2.iml index ae5453d..c4da618 100644 --- a/.idea/dsa2.iml +++ b/.idea/dsa2.iml @@ -14,6 +14,7 @@ + diff --git a/Cargo.toml b/Cargo.toml index 9aa0ce5..6bc7914 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,24 @@ [workspace] -members = ["dsa/common", "dsa/assembler", "dsa/emulator/emulator_ui", "dsa/linker", "dsa/compiler", "dsa/emulator/emulator_core", "dsx/dsx_common", "dsx/dsx_server", "dsx/dsx_client", "dsa/disassembler"] +members = ["dsa/common", "dsa/assembler", "dsa/emulator/emulator_ui", "dsa/linker", "dsa/compiler", "dsa/emulator/emulator_core", "dsx/dsx_common", "dsx/dsx_server", "dsx/dsx_client", "dsa/disassembler", "dsa/dsa_formats"] resolver = "3" [workspace.package] edition = "2024" version = "0.3.0" authors = ["zxq5", "zxq4"] + +#[workspace.lints.clippy] +#all = "warn" +#pedantic = "warn" +#cast_possible_truncation = "warn" +#cast_sign_loss = "warn" +#cast_possible_wrap = "warn" +#as_conversions = "warn" +#unwrap_used = "warn" +#expect_used = "warn" +#indexing_slicing = "warn" +#large_enum_variant = "warn" +#mem_replace_with_default = "warn" +#mutex_atomic = "warn" +#todo = "warn" +#dbg_macro = "warn" diff --git a/dsa/assembler/Cargo.toml b/dsa/assembler/Cargo.toml index f5f1547..7c1e268 100644 --- a/dsa/assembler/Cargo.toml +++ b/dsa/assembler/Cargo.toml @@ -13,9 +13,9 @@ name = "assembler" path = "src/lib.rs" [dependencies] -binrw = "0.15.1" clap = { version = "4.6.0", features = ["derive"] } common = { path = "../common" } +dsa_formats = { path = "../dsa_formats" } num_cpus = "1.17.0" strum = { version = "0.28.0", features = ["derive"] } diff --git a/dsa/assembler/src/assembler/mod.rs b/dsa/assembler/src/assembler/mod.rs index b1709a7..56f13dc 100644 --- a/dsa/assembler/src/assembler/mod.rs +++ b/dsa/assembler/src/assembler/mod.rs @@ -1,22 +1,16 @@ #![allow(dead_code, unused)] +use crate::assembler::resolver2::{SectionId, resolve_address, resolve_dependencies, resolve_symbols, split_sections}; +use common::process::{ProcessAgent, TraceableProcess}; +use dsa_formats::binary_dse::{DseExecutable, SymbolEntry}; use std::{ collections::HashSet, fmt, fs, hash::{DefaultHasher, Hash, Hasher}, path::{Path, PathBuf}, - sync::{ - Arc, Mutex, - mpsc::{self, Receiver, Sender}, - }, - thread, + sync:: + mpsc::{self}, }; -use std::collections::HashMap; -use std::io::Cursor; -use common::formats::binary_dse::{DseExecutable, SymbolEntry}; -use common::prelude::Instruction; -use common::process::{ProcessAgent, TraceableProcess}; -use crate::assembler::resolver2::{resolve_address, resolve_dependencies, resolve_symbols, split_sections, SectionId}; // Module declarations #[macro_use] diff --git a/dsa/common/src/formats/mod.rs b/dsa/common/src/formats/mod.rs index 2c88e30..e69de29 100644 --- a/dsa/common/src/formats/mod.rs +++ b/dsa/common/src/formats/mod.rs @@ -1,2 +0,0 @@ -pub mod binary_dse; -pub mod binary_dso; \ No newline at end of file diff --git a/dsa/common/src/lib.rs b/dsa/common/src/lib.rs index d5638b3..0ef7bb1 100644 --- a/dsa/common/src/lib.rs +++ b/dsa/common/src/lib.rs @@ -1,6 +1,5 @@ pub mod asm; pub mod isa; -pub mod formats; pub mod process; pub mod prelude { diff --git a/dsa/disassembler/Cargo.toml b/dsa/disassembler/Cargo.toml index d24f872..8904194 100644 --- a/dsa/disassembler/Cargo.toml +++ b/dsa/disassembler/Cargo.toml @@ -14,4 +14,5 @@ path = "src/lib.rs" [dependencies] clap = { version = "4.6.0", features = ["derive"] } -common = { path = "../common" } \ No newline at end of file +common = { path = "../common" } +dsa_formats = { path = "../dsa_formats" } \ No newline at end of file diff --git a/dsa/disassembler/src/internal.rs b/dsa/disassembler/src/internal.rs deleted file mode 100644 index cd986d8..0000000 --- a/dsa/disassembler/src/internal.rs +++ /dev/null @@ -1,350 +0,0 @@ -use std::collections::HashMap; -use std::fmt; -use common::formats::binary_dse::DseExecutable; -use common::isa::instructions::Opcode; -use common::prelude::{Instruction, Register}; - -#[derive(Debug)] -pub struct Disassembly { - pub symbols: Vec, - pub text_lines: Vec, - pub data_lines: Vec, -} - -#[derive(Debug)] -pub struct SymbolLine { - pub name: String, - pub address: u32, - pub section: SectionKind, - pub binding: BindingKind, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SectionKind { Data, Text, Abs, Undef } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BindingKind { Local, Global, Weak } - -impl SectionKind { - fn from_raw(v: u8) -> Self { - match v { 1 => Self::Data, 2 => Self::Text, 0 => Self::Abs, _ => Self::Undef } - } -} -impl BindingKind { - fn from_raw(v: u8) -> Self { - match v { 1 => Self::Global, 2 => Self::Weak, _ => Self::Local } - } -} - -pub fn disassemble(exe: &DseExecutable) -> Disassembly { - let symbols = build_symbol_lines(exe); - - let mut text_labels: HashMap = HashMap::new(); - let mut data_labels: HashMap = HashMap::new(); - for sym in &symbols { - match sym.section { - SectionKind::Text => { text_labels.insert(sym.address, sym.name.clone()); } - SectionKind::Data => { data_labels.insert(sym.address, sym.name.clone()); } - _ => {} - } - } - // symbols can refer to either section by full resolved address (per your - // v1 loader convention: data lives right after text), so a single combined - // map is actually what address-resolution needs — keep both, but merge - // for lookups against full 32-bit addresses. - let mut all_labels = text_labels.clone(); - all_labels.extend(data_labels.clone()); - - let text_lines = build_text_lines(&exe.text, &all_labels); - let data_lines = build_data_lines(&exe.data, &data_labels); - - Disassembly { symbols, text_lines, data_lines } -} - -fn build_symbol_lines(exe: &DseExecutable) -> Vec { - exe.symbols - .iter() - .map(|s| SymbolLine { - name: read_string(&exe.string_table, s.name_offset), - address: s.value, - section: SectionKind::from_raw(s.section), - binding: BindingKind::from_raw(s.binding), - }) - .collect() -} - -fn read_string(strtab: &[u8], offset: u32) -> String { - let start = offset as usize; - let end = strtab[start..].iter().position(|&b| b == 0).map(|p| start + p).unwrap_or(strtab.len()); - String::from_utf8_lossy(&strtab[start..end]).into_owned() -} - -/// Address a jump-like instruction actually targets, if statically known. -/// Returns None when the jump is register-relative (addr reg != Zero) since -/// that depends on runtime state and can't be resolved here. -fn static_jump_target(addr_reg: Register, imm: u16) -> Option { - (addr_reg == Register::Zero).then_some(imm as u32) -} - -fn build_text_lines(words: &[u32], labels: &HashMap) -> Vec { - let mut out = Vec::new(); - let mut i = 0; - - while i < words.len() { - let addr = 4 * i as u32; - if let Some(label) = labels.get(&addr) { - out.push(format!("{label}:")); - } - - let instr = Instruction(words[i]); - let opcode = Opcode::from_u8(instr.opcode()); - - // Collapse Lli+Lui address-load pairs back into `lwi` (or a full - // ldx/stx pseudo-op if a matching mem instruction follows), mirroring - // what expand_ldx/expand_stx/expand_lwi originally expanded from. - if let Some(Opcode::Lli) = opcode { - if let Some(collapsed) = try_collapse_addr_load(words, i, labels) { - out.push(format!(" {}", collapsed.text)); - i += collapsed.consumed; - continue; - } - } - - out.push(format!(" {}", render_instruction(instr, opcode, labels))); - i += 1; - } - - out -} - -struct Collapsed { - text: String, - consumed: usize, -} - -fn try_collapse_addr_load( - words: &[u32], - i: usize, - labels: &HashMap, -) -> Option { - let lli = Instruction(words[i]); - let lui = Instruction(*words.get(i + 1)?); - - if Opcode::from_u8(lui.opcode())? as u8 != Opcode::Lui as u8 { - return None; - } - if lui.dest_checked() != lli.dest_checked() { - return None; - } - - let dest = lli.dest_checked(); - let full_addr = (lli.imm16() as u32) | ((lui.imm16() as u32) << 16); - let addr_text = labels.get(&full_addr).cloned().unwrap_or_else(|| format!("{full_addr:#010x}")); - - // Check whether a mem op immediately follows, referencing `dest` — - // that's the ldb/ldh/ldw/stb/sth/stw expansion pattern, not a bare lwi. - if let Some(&mem_word) = words.get(i + 2) { - let mem = Instruction(mem_word); - if let Some(mem_op) = Opcode::from_u8(mem.opcode()) { - let is_mem_op = matches!( - mem_op, - Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw - | Opcode::Stb | Opcode::Sth | Opcode::Stw - ); - if is_mem_op && (mem.src1_checked() == dest || mem.dest_checked() == dest) { - let mnemonic = format!("{mem_op:?}").to_lowercase(); - let other = if mem.src1_checked() == dest { mem.dest_checked() } else { mem.src1_checked() }; - return Some(Collapsed { - text: format!("{mnemonic} {addr_text}, {other:?}, {:#06x}", mem.imm16()), - consumed: 3, - }); - } - } - } - - Some(Collapsed { - text: format!("lwi {addr_text}, {dest:?}"), - consumed: 2, - }) -} - -fn render_instruction(instr: Instruction, opcode: Option, labels: &HashMap) -> String { - let Some(op) = opcode else { - return format!("; INVALID OPCODE {:#04x} (word {:#010x})", instr.opcode(), instr.0); - }; - - let mnemonic = format!("{op:?}").to_lowercase(); - - match op { - Opcode::Nop | Opcode::Hlt | Opcode::Ret | Opcode::IRet => mnemonic, - - Opcode::Push => format!("{mnemonic} {:?}", instr.src1_checked()), - Opcode::Pop => format!("{mnemonic} {:?}", instr.dest_checked()), - - Opcode::Lli | Opcode::Lui => { - format!("{mnemonic} {:?}, {:#06x}", instr.dest_checked(), instr.imm16()) - } - - Opcode::Int => format!("{mnemonic} {:#06x}", instr.imm16()), - - Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw - | Opcode::Stb | Opcode::Sth | Opcode::Stw | Opcode::Addi | Opcode::Subi => format!( - "{mnemonic} {:?}, {:?}, {:#06x}", - instr.src1_checked(), instr.dest_checked(), instr.imm16() - ), - - Opcode::Jez | Opcode::Jnz => { - let addr_reg = instr.dest_checked(); - let target = static_jump_target(addr_reg, instr.imm16()) - .and_then(|a| labels.get(&a).cloned()); - match target { - Some(label) => format!("{mnemonic} {:?}, {label}", instr.src1_checked()), - None if addr_reg == Register::Zero => { - format!("{mnemonic} {:?}, {:#06x}", instr.src1_checked(), instr.imm16()) - } - None => format!( - "{mnemonic} {:?}, {:#06x}, {addr_reg:?}", - instr.src1_checked(), instr.imm16() - ), - } - } - - Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc => { - let addr_reg = instr.dest_checked(); - let target = static_jump_target(addr_reg, instr.imm16()) - .and_then(|a| labels.get(&a).cloned()); - match target { - Some(label) => format!("{mnemonic} {label}"), - None if addr_reg == Register::Zero => format!("{mnemonic} {:#06x}", instr.imm16()), - None => format!("{mnemonic} {:#06x}, {addr_reg:?}", instr.imm16()), - } - } - - Opcode::Mov | Opcode::Not => { - format!("{mnemonic} {:?}, {:?}", instr.src1_checked(), instr.dest_checked()) - } - - Opcode::CMov => format!( - "{mnemonic} {:?}, {:?}, {:?}", - instr.src1_checked(), instr.dest_checked(), instr.src2_checked() - ), - - Opcode::Add | Opcode::Sub | Opcode::And | Opcode::Nand | Opcode::Or | Opcode::Nor - | Opcode::Xor | Opcode::Xnor | Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Ile - | Opcode::Igt | Opcode::Ige => format!( - "{mnemonic} {:?}, {:?}, {:?}", - instr.src1_checked(), instr.src2_checked(), instr.dest_checked() - ), - - Opcode::Shl | Opcode::Shr => format!( - "{mnemonic} {:?}, {:?}, {:?}, {}", - instr.src1_checked(), instr.dest_checked(), instr.src2_checked(), instr.shamt() - ), - } -} - -fn build_data_lines(data: &[u8], data_labels: &HashMap) -> Vec { - let mut out = Vec::new(); - let mut i = 0; - - while i < data.len() { - let addr = i as u32; - if let Some(label) = data_labels.get(&addr) { - out.push(format!("{label}:")); - } - - // Try string detection only when this byte could actually start one. - if data[i] != 0 { - if let Some(end) = data[i..].iter().position(|&b| b == 0) { - let slice = &data[i..i + end]; - if slice.iter().all(|&b| b.is_ascii_graphic() || b == b' ') { - out.push(format!(" db \"{}\"", String::from_utf8_lossy(slice).escape_default())); - i += end + 1; // skip the null terminator too - continue; - } - } - } - - // Not a string start: consume only the run of zero bytes here, - // grouped into 4-byte words, stopping the instant a non-zero byte - // appears so we never swallow the start of the next string/value. - let run_end = data[i..] - .iter() - .position(|&b| b != 0) - .map(|p| i + p) - .unwrap_or(data.len()); - - if run_end == i { - // data[i] is non-zero but didn't form a printable/terminated - // string (e.g. binary constant) — dump one raw word. - let mut buf = [0u8; 4]; - let take = (data.len() - i).min(4); - buf[..take].copy_from_slice(&data[i..i + take]); - out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf))); - i += take; - } else { - let mut j = i; - while j < run_end { - let take = (run_end - j).min(4); - let mut buf = [0u8; 4]; - buf[..take].copy_from_slice(&data[j..j + take]); - out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf))); - j += take; - } - i = j; - } - } - - out -} - -impl fmt::Display for SectionKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Data => write!(f, "DATA"), - Self::Text => write!(f, "TEXT"), - Self::Abs => write!(f, "ABS"), - Self::Undef => write!(f, "UNDEF"), - } - } -} - -impl fmt::Display for BindingKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Local => write!(f, "local"), - Self::Global => write!(f, "global"), - Self::Weak => write!(f, "weak"), - } - } -} - -impl fmt::Display for SymbolLine { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "; {:<24} {:#010x} {:<5} {}", - self.name, self.address, self.section, self.binding - ) - } -} - -impl fmt::Display for Disassembly { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "; ==== symbols ====")?; - for sym in &self.symbols { - writeln!(f, "{sym}")?; - } - - writeln!(f, "\n; ==== data ====")?; - for line in &self.data_lines { - writeln!(f, "{line}")?; - } - - writeln!(f, "\n; ==== text ====")?; - for line in &self.text_lines { - writeln!(f, "{line}")?; - } - - Ok(()) - } -} \ No newline at end of file diff --git a/dsa/disassembler/src/lib.rs b/dsa/disassembler/src/lib.rs index bf72fc0..2bf94b2 100644 --- a/dsa/disassembler/src/lib.rs +++ b/dsa/disassembler/src/lib.rs @@ -1,15 +1,350 @@ -mod internal; - -use std::fs; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - +use common::isa::instructions::Opcode; +use common::prelude::{Instruction, Register}; +use dsa_formats::binary_dse::DseExecutable; use std::collections::HashMap; -use common::formats::binary_dse::DseExecutable; -use common::isa::instructions::{Instruction, Opcode}; -use common::isa::register::Register; +use std::fmt; -pub use internal::{ - disassemble, Disassembly, BindingKind, SectionKind, SymbolLine -}; +#[derive(Debug)] +pub struct Disassembly { + pub symbols: Vec, + pub text_lines: Vec, + pub data_lines: Vec, +} + +#[derive(Debug)] +pub struct SymbolLine { + pub name: String, + pub address: u32, + pub section: SectionKind, + pub binding: BindingKind, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SectionKind { Data, Text, Abs, Undef } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BindingKind { Local, Global, Weak } + +impl SectionKind { + fn from_raw(v: u8) -> Self { + match v { 1 => Self::Data, 2 => Self::Text, 0 => Self::Abs, _ => Self::Undef } + } +} +impl BindingKind { + fn from_raw(v: u8) -> Self { + match v { 1 => Self::Global, 2 => Self::Weak, _ => Self::Local } + } +} + +pub fn disassemble(exe: &DseExecutable) -> Disassembly { + let symbols = build_symbol_lines(exe); + + let mut text_labels: HashMap = HashMap::new(); + let mut data_labels: HashMap = HashMap::new(); + for sym in &symbols { + match sym.section { + SectionKind::Text => { text_labels.insert(sym.address, sym.name.clone()); } + SectionKind::Data => { data_labels.insert(sym.address, sym.name.clone()); } + _ => {} + } + } + // symbols can refer to either section by full resolved address (per your + // v1 loader convention: data lives right after text), so a single combined + // map is actually what address-resolution needs — keep both, but merge + // for lookups against full 32-bit addresses. + let mut all_labels = text_labels.clone(); + all_labels.extend(data_labels.clone()); + + let text_lines = build_text_lines(&exe.text, &all_labels); + let data_lines = build_data_lines(&exe.data, &data_labels); + + Disassembly { symbols, text_lines, data_lines } +} + +fn build_symbol_lines(exe: &DseExecutable) -> Vec { + exe.symbols + .iter() + .map(|s| SymbolLine { + name: read_string(&exe.string_table, s.name_offset), + address: s.value, + section: SectionKind::from_raw(s.section), + binding: BindingKind::from_raw(s.binding), + }) + .collect() +} + +fn read_string(strtab: &[u8], offset: u32) -> String { + let start = offset as usize; + let end = strtab[start..].iter().position(|&b| b == 0).map(|p| start + p).unwrap_or(strtab.len()); + String::from_utf8_lossy(&strtab[start..end]).into_owned() +} + +/// Address a jump-like instruction actually targets, if statically known. +/// Returns None when the jump is register-relative (addr reg != Zero) since +/// that depends on runtime state and can't be resolved here. +fn static_jump_target(addr_reg: Register, imm: u16) -> Option { + (addr_reg == Register::Zero).then_some(imm as u32) +} + +fn build_text_lines(words: &[u32], labels: &HashMap) -> Vec { + let mut out = Vec::new(); + let mut i = 0; + + while i < words.len() { + let addr = 4 * i as u32; + if let Some(label) = labels.get(&addr) { + out.push(format!("{label}:")); + } + + let instr = Instruction(words[i]); + let opcode = Opcode::from_u8(instr.opcode()); + + // Collapse Lli+Lui address-load pairs back into `lwi` (or a full + // ldx/stx pseudo-op if a matching mem instruction follows), mirroring + // what expand_ldx/expand_stx/expand_lwi originally expanded from. + if let Some(Opcode::Lli) = opcode { + if let Some(collapsed) = try_collapse_addr_load(words, i, labels) { + out.push(format!(" {}", collapsed.text)); + i += collapsed.consumed; + continue; + } + } + + out.push(format!(" {}", render_instruction(instr, opcode, labels))); + i += 1; + } + + out +} + +struct Collapsed { + text: String, + consumed: usize, +} + +fn try_collapse_addr_load( + words: &[u32], + i: usize, + labels: &HashMap, +) -> Option { + let lli = Instruction(words[i]); + let lui = Instruction(*words.get(i + 1)?); + + if Opcode::from_u8(lui.opcode())? as u8 != Opcode::Lui as u8 { + return None; + } + if lui.dest_checked() != lli.dest_checked() { + return None; + } + + let dest = lli.dest_checked(); + let full_addr = (lli.imm16() as u32) | ((lui.imm16() as u32) << 16); + let addr_text = labels.get(&full_addr).cloned().unwrap_or_else(|| format!("{full_addr:#010x}")); + + // Check whether a mem op immediately follows, referencing `dest` — + // that's the ldb/ldh/ldw/stb/sth/stw expansion pattern, not a bare lwi. + if let Some(&mem_word) = words.get(i + 2) { + let mem = Instruction(mem_word); + if let Some(mem_op) = Opcode::from_u8(mem.opcode()) { + let is_mem_op = matches!( + mem_op, + Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw + | Opcode::Stb | Opcode::Sth | Opcode::Stw + ); + if is_mem_op && (mem.src1_checked() == dest || mem.dest_checked() == dest) { + let mnemonic = format!("{mem_op:?}").to_lowercase(); + let other = if mem.src1_checked() == dest { mem.dest_checked() } else { mem.src1_checked() }; + return Some(Collapsed { + text: format!("{mnemonic} {addr_text}, {other:?}, {:#06x}", mem.imm16()), + consumed: 3, + }); + } + } + } + + Some(Collapsed { + text: format!("lwi {addr_text}, {dest:?}"), + consumed: 2, + }) +} + +fn render_instruction(instr: Instruction, opcode: Option, labels: &HashMap) -> String { + let Some(op) = opcode else { + return format!("; INVALID OPCODE {:#04x} (word {:#010x})", instr.opcode(), instr.0); + }; + + let mnemonic = format!("{op:?}").to_lowercase(); + + match op { + Opcode::Nop | Opcode::Hlt | Opcode::Ret | Opcode::IRet => mnemonic, + + Opcode::Push => format!("{mnemonic} {:?}", instr.src1_checked()), + Opcode::Pop => format!("{mnemonic} {:?}", instr.dest_checked()), + + Opcode::Lli | Opcode::Lui => { + format!("{mnemonic} {:?}, {:#06x}", instr.dest_checked(), instr.imm16()) + } + + Opcode::Int => format!("{mnemonic} {:#06x}", instr.imm16()), + + Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw + | Opcode::Stb | Opcode::Sth | Opcode::Stw | Opcode::Addi | Opcode::Subi => format!( + "{mnemonic} {:?}, {:?}, {:#06x}", + instr.src1_checked(), instr.dest_checked(), instr.imm16() + ), + + Opcode::Jez | Opcode::Jnz => { + let addr_reg = instr.dest_checked(); + let target = static_jump_target(addr_reg, instr.imm16()) + .and_then(|a| labels.get(&a).cloned()); + match target { + Some(label) => format!("{mnemonic} {:?}, {label}", instr.src1_checked()), + None if addr_reg == Register::Zero => { + format!("{mnemonic} {:?}, {:#06x}", instr.src1_checked(), instr.imm16()) + } + None => format!( + "{mnemonic} {:?}, {:#06x}, {addr_reg:?}", + instr.src1_checked(), instr.imm16() + ), + } + } + + Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc => { + let addr_reg = instr.dest_checked(); + let target = static_jump_target(addr_reg, instr.imm16()) + .and_then(|a| labels.get(&a).cloned()); + match target { + Some(label) => format!("{mnemonic} {label}"), + None if addr_reg == Register::Zero => format!("{mnemonic} {:#06x}", instr.imm16()), + None => format!("{mnemonic} {:#06x}, {addr_reg:?}", instr.imm16()), + } + } + + Opcode::Mov | Opcode::Not => { + format!("{mnemonic} {:?}, {:?}", instr.src1_checked(), instr.dest_checked()) + } + + Opcode::CMov => format!( + "{mnemonic} {:?}, {:?}, {:?}", + instr.src1_checked(), instr.dest_checked(), instr.src2_checked() + ), + + Opcode::Add | Opcode::Sub | Opcode::And | Opcode::Nand | Opcode::Or | Opcode::Nor + | Opcode::Xor | Opcode::Xnor | Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Ile + | Opcode::Igt | Opcode::Ige => format!( + "{mnemonic} {:?}, {:?}, {:?}", + instr.src1_checked(), instr.src2_checked(), instr.dest_checked() + ), + + Opcode::Shl | Opcode::Shr => format!( + "{mnemonic} {:?}, {:?}, {:?}, {}", + instr.src1_checked(), instr.dest_checked(), instr.src2_checked(), instr.shamt() + ), + } +} + +fn build_data_lines(data: &[u8], data_labels: &HashMap) -> Vec { + let mut out = Vec::new(); + let mut i = 0; + + while i < data.len() { + let addr = i as u32; + if let Some(label) = data_labels.get(&addr) { + out.push(format!("{label}:")); + } + + // Try string detection only when this byte could actually start one. + if data[i] != 0 { + if let Some(end) = data[i..].iter().position(|&b| b == 0) { + let slice = &data[i..i + end]; + if slice.iter().all(|&b| b.is_ascii_graphic() || b == b' ') { + out.push(format!(" db \"{}\"", String::from_utf8_lossy(slice).escape_default())); + i += end + 1; // skip the null terminator too + continue; + } + } + } + + // Not a string start: consume only the run of zero bytes here, + // grouped into 4-byte words, stopping the instant a non-zero byte + // appears so we never swallow the start of the next string/value. + let run_end = data[i..] + .iter() + .position(|&b| b != 0) + .map(|p| i + p) + .unwrap_or(data.len()); + + if run_end == i { + // data[i] is non-zero but didn't form a printable/terminated + // string (e.g. binary constant) — dump one raw word. + let mut buf = [0u8; 4]; + let take = (data.len() - i).min(4); + buf[..take].copy_from_slice(&data[i..i + take]); + out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf))); + i += take; + } else { + let mut j = i; + while j < run_end { + let take = (run_end - j).min(4); + let mut buf = [0u8; 4]; + buf[..take].copy_from_slice(&data[j..j + take]); + out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf))); + j += take; + } + i = j; + } + } + + out +} + +impl fmt::Display for SectionKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Data => write!(f, "DATA"), + Self::Text => write!(f, "TEXT"), + Self::Abs => write!(f, "ABS"), + Self::Undef => write!(f, "UNDEF"), + } + } +} + +impl fmt::Display for BindingKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Local => write!(f, "local"), + Self::Global => write!(f, "global"), + Self::Weak => write!(f, "weak"), + } + } +} + +impl fmt::Display for SymbolLine { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "; {:<24} {:#010x} {:<5} {}", + self.name, self.address, self.section, self.binding + ) + } +} + +impl fmt::Display for Disassembly { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "; ==== symbols ====")?; + for sym in &self.symbols { + writeln!(f, "{sym}")?; + } + + writeln!(f, "\n; ==== data ====")?; + for line in &self.data_lines { + writeln!(f, "{line}")?; + } + + writeln!(f, "\n; ==== text ====")?; + for line in &self.text_lines { + writeln!(f, "{line}")?; + } + + Ok(()) + } +} \ No newline at end of file diff --git a/dsa/disassembler/src/main.rs b/dsa/disassembler/src/main.rs index a526d13..a48a4d6 100644 --- a/dsa/disassembler/src/main.rs +++ b/dsa/disassembler/src/main.rs @@ -1,9 +1,10 @@ -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; +use std::fs; use clap::Parser; use disassembler::disassemble; -use common::formats::binary_dse::DseExecutable; +use dsa_formats::binary_dse::DseExecutable; +use std::fs::File; +use std::io::{Cursor, Write}; +use std::path::PathBuf; #[derive(Parser, Debug, Clone)] #[command(version, about, long_about = None)] @@ -17,11 +18,15 @@ struct Args { fn main() { let args = Args::parse(); - let Ok(exec) = DseExecutable::load(args.input_path) else { + let Ok(contents) = fs::read(args.input_path) else { + panic!("Failed to read the input file") + }; + + let Ok(exec) = DseExecutable::read(&mut Cursor::new(contents)) else { panic!("Failed to load the input file") }; let result = disassemble(&exec); - File::create(args.output_path).unwrap() - .write(result.to_string().as_bytes()).unwrap(); + let _bytes_written = File::create(args.output_path).expect("Failed to create the output file") + .write(result.to_string().as_bytes()).expect("Failed to write to output file"); } diff --git a/dsa/dsa_formats/Cargo.toml b/dsa/dsa_formats/Cargo.toml new file mode 100644 index 0000000..10840c8 --- /dev/null +++ b/dsa/dsa_formats/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "dsa_formats" +edition.workspace = true +version.workspace = true +authors.workspace = true + +[dependencies] +binrw = "0.15.2" +common = { path = "../common" } \ No newline at end of file diff --git a/dsa/common/src/formats/binary_dse.rs b/dsa/dsa_formats/src/binary_dse.rs similarity index 94% rename from dsa/common/src/formats/binary_dse.rs rename to dsa/dsa_formats/src/binary_dse.rs index 41ad4ea..85bcf98 100644 --- a/dsa/common/src/formats/binary_dse.rs +++ b/dsa/dsa_formats/src/binary_dse.rs @@ -1,10 +1,9 @@ - use std::fs::File; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::path::PathBuf; -use binrw::{binrw, BinRead, binwrite, NullString, BinReaderExt, BinWriterExt, BinWrite}; +use binrw::{binrw, BinRead, BinReaderExt, BinWriterExt, BinWrite}; use std::fmt; -use crate::prelude::Instruction; +use common::isa::instructions::Instruction; #[binrw] #[brw(magic = b"DSAX", little)] @@ -48,11 +47,6 @@ pub struct DseExecutable { } impl DseExecutable { - pub fn load(path: PathBuf) -> binrw::BinResult { - let mut file = File::open(path).map_err(|e| binrw::Error::Io(e))?; - Self::read(&mut file) - } - pub fn read(reader: &mut R) -> binrw::BinResult { let header = DseHeader::read(reader)?; @@ -76,13 +70,7 @@ impl DseExecutable { Ok(DseExecutable { header, symbols, string_table, data, text }) } - - pub fn save(&self, path: PathBuf) -> binrw::BinResult<()> { - let mut file = File::create(path) - .map_err(|e| binrw::Error::Io(e))?; - self.write(&mut file) - } - + pub fn write(&self, writer: &mut W) -> binrw::BinResult<()> { self.header.write(writer)?; diff --git a/dsa/common/src/formats/binary_dso.rs b/dsa/dsa_formats/src/binary_dso.rs similarity index 100% rename from dsa/common/src/formats/binary_dso.rs rename to dsa/dsa_formats/src/binary_dso.rs diff --git a/dsa/dsa_formats/src/lib.rs b/dsa/dsa_formats/src/lib.rs new file mode 100644 index 0000000..1bc08a6 --- /dev/null +++ b/dsa/dsa_formats/src/lib.rs @@ -0,0 +1,12 @@ +pub mod binary_dse; +pub mod binary_dso; + +#[cfg(test)] +mod tests { + + // #[test] + // fn it_works() { + // let result = add(2, 2); + // assert_eq!(result, 4); + // } +} diff --git a/dsa/emulator/emulator_core/src/devices/display.rs b/dsa/emulator/emulator_core/src/devices/display.rs new file mode 100644 index 0000000..b884584 --- /dev/null +++ b/dsa/emulator/emulator_core/src/devices/display.rs @@ -0,0 +1,45 @@ +use crate::EmulatorHandle; +use crate::memory::PhysAddr; + +pub struct Display { + width: u32, + height: u32, + addr: PhysAddr, + + emulator: EmulatorHandle, + buffer: Vec, +} + +impl Display { + pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self { + Self { + width, + height, + addr, + emulator, + buffer: Vec::new(), + } + } + + pub fn width(&self) -> usize { + self.width as usize + } + + pub fn height(&self) -> usize { + self.height as usize + } + + + pub fn changed(&mut self) -> bool { + let temp = self.read(); + if temp != self.buffer { + self.buffer = temp; + return true; + } + false + } + + pub fn read(&mut self) -> Vec { + self.emulator.mem_read(self.addr, self.width * self.height) + } +} \ No newline at end of file diff --git a/dsa/emulator/emulator_core/src/devices/framebuffer.rs b/dsa/emulator/emulator_core/src/devices/framebuffer.rs new file mode 100644 index 0000000..7f3e692 --- /dev/null +++ b/dsa/emulator/emulator_core/src/devices/framebuffer.rs @@ -0,0 +1,35 @@ +use crate::EmulatorHandle; +use crate::memory::PhysAddr; + +pub struct FrameBuffer { + width: usize, + height: usize, + addr: PhysAddr, + + emulator: EmulatorHandle, +} + +impl FrameBuffer { + pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self { + let width = width as usize; + let height = height as usize; + Self { + width, + height, + addr, + emulator, + } + } + + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } + + pub fn read(&self) -> Vec { + self.emulator.mem_read(self.addr, ( self.width * self.height * 4 ) as u32) + } +} \ No newline at end of file diff --git a/dsa/emulator/emulator_core/src/devices/mod.rs b/dsa/emulator/emulator_core/src/devices/mod.rs new file mode 100644 index 0000000..4df9e2e --- /dev/null +++ b/dsa/emulator/emulator_core/src/devices/mod.rs @@ -0,0 +1,3 @@ +pub mod display; +pub mod framebuffer; +pub mod serial; \ No newline at end of file diff --git a/dsa/emulator/emulator_ui/src/ui/panels/serial.rs b/dsa/emulator/emulator_core/src/devices/serial.rs similarity index 70% rename from dsa/emulator/emulator_ui/src/ui/panels/serial.rs rename to dsa/emulator/emulator_core/src/devices/serial.rs index 99106cf..dfd88ca 100644 --- a/dsa/emulator/emulator_ui/src/ui/panels/serial.rs +++ b/dsa/emulator/emulator_core/src/devices/serial.rs @@ -1,18 +1,8 @@ -use std::{ - collections::VecDeque, - sync::{ - mpsc::{Receiver, Sender}, - }, -}; -use std::any::Any; -use egui::{Ui, WidgetText}; -use egui_tiles::TileId; -use emulator_core::{memory::{ - PhysAddr, -}, EmulatorHandle}; -use emulator_core::processor::Interrupt; -use crate::ui::Action; -use crate::ui::panels::TilePane; +use crate::EmulatorHandle; +use crate::memory::PhysAddr; +use crate::processor::Interrupt; +use std::collections::VecDeque; +use std::sync::mpsc::{Receiver, Sender}; /// ## 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 @@ -97,35 +87,4 @@ impl Serial { self.read_buff.push(byte); } } -} - -impl TilePane for Serial { - fn title(&self) -> WidgetText { - "Serial".into() - } - fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { - self.update(); - - // Text field for input and a send button - ui.text_edit_multiline(&mut self.write_buff); - if ui.button("Send").clicked() { - self.write_buff.push(0 as char); - for byte in self.write_buff.bytes() { - let _ = self.write_tx.send(byte); - } - self.write_buff.clear(); - } - - // Display received data - ui.label(String::from_utf8(self.read_buff.clone()).unwrap()); - if ui.button("Clear").clicked() { - self.read_buff.clear(); - } - - None - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} +} \ No newline at end of file diff --git a/dsa/emulator/emulator_core/src/lib.rs b/dsa/emulator/emulator_core/src/lib.rs index 0be5c7a..90a2c1d 100644 --- a/dsa/emulator/emulator_core/src/lib.rs +++ b/dsa/emulator/emulator_core/src/lib.rs @@ -15,6 +15,7 @@ use crate::processor::{Interrupt, RunningState, SharedState}; pub mod processor; pub mod memory; pub mod config; +pub mod devices; #[derive(Debug)] pub enum DsaError { @@ -41,7 +42,7 @@ pub trait DsaImplementation: Send { /// IO mapping also fn apply_io_mapping(&mut self, map: IoMap); - /// List all memory devices + /// List all memory io_devices /// returns a copy of the IOmap (cannot be a reference since across-threads) fn get_io_map(&self) -> IoMap; @@ -64,7 +65,7 @@ pub struct EmulatorHandle { impl EmulatorHandle { - pub fn read_byte(&mut self, addr: PhysAddr) -> u8 { + pub fn read_byte(&self, addr: PhysAddr) -> u8 { self.memory.read_byte(addr) } @@ -72,7 +73,7 @@ impl EmulatorHandle { self.memory.write_byte(addr, val); } - pub fn mem_read(&mut self, addr: PhysAddr, size: u32) -> Vec { + pub fn mem_read(&self, addr: PhysAddr, size: u32) -> Vec { self.memory.read_bytes(addr, size as usize).to_vec() } diff --git a/dsa/emulator/emulator_core/src/memory/mmu.rs b/dsa/emulator/emulator_core/src/memory/mmu.rs index 761634d..3581589 100644 --- a/dsa/emulator/emulator_core/src/memory/mmu.rs +++ b/dsa/emulator/emulator_core/src/memory/mmu.rs @@ -1,6 +1,6 @@ use crate::memory::{FaultInfo, PhysAddr, VirtAddr, tlb::TLB}; -pub struct MMU { +pub(crate) struct MMU { buffer: TLB, enable_paging: bool, diff --git a/dsa/emulator/emulator_core/src/memory/ram.rs b/dsa/emulator/emulator_core/src/memory/ram.rs index 6776b38..0c616f7 100644 --- a/dsa/emulator/emulator_core/src/memory/ram.rs +++ b/dsa/emulator/emulator_core/src/memory/ram.rs @@ -2,32 +2,6 @@ use std::{ alloc::{Layout, alloc_zeroed, dealloc}, ops::{Deref, DerefMut}, }; - -#[cfg(feature = "mainstore-arraymap")] -pub mod arraymap; -#[cfg(feature = "mainstore-arraymap")] -pub use arraymap::ArrayStore; - -#[cfg(feature = "mainstore-hashmap")] -pub mod hashmap; -#[cfg(feature = "mainstore-hashmap")] -pub use hashmap::HashStore; - -#[cfg(feature = "mainstore-stackarray")] -pub mod stackarray; -#[cfg(feature = "mainstore-stackarray")] -pub use stackarray::StackArrayStore; - -#[cfg(feature = "mainstore-bulkalloc")] -pub mod bulkalloc; -#[cfg(feature = "mainstore-bulkalloc")] -pub use bulkalloc::BulkAllocStore; - -// #[cfg(feature = "mainstore-prealloc")] -// pub mod prealloc; -// #[cfg(feature = "mainstore-prealloc")] -// pub use prealloc::PreAllocStore; - use crate::memory::PhysAddr; #[repr(transparent)] @@ -91,12 +65,7 @@ impl TryFrom> for Page { } } -#[inline(always)] -const fn idx(addr: PhysAddr) -> (usize, usize) { - ((addr >> 12) as usize, (addr & 0xFFF) as usize) -} - -pub struct MemoryBank { +pub(crate) struct MemoryBank { heap: *mut u8, } @@ -106,7 +75,7 @@ impl MemoryBank { pub fn new() -> Self { Self { heap: unsafe { - // allocate the full 32 bit address range. + // allocate the full 32-bit address range. alloc_zeroed(Layout::from_size_align(u32::MAX as usize, 4096).unwrap()) }, } diff --git a/dsa/emulator/emulator_ui/Cargo.toml b/dsa/emulator/emulator_ui/Cargo.toml index 2665186..c4140d6 100644 --- a/dsa/emulator/emulator_ui/Cargo.toml +++ b/dsa/emulator/emulator_ui/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "emulator_ui" -version = "0.3.0" +version.workspace = true edition.workspace = true authors.workspace = true @@ -12,36 +12,21 @@ path = "src/main.rs" name = "dsa" path = "src/lib.rs" -[dev-dependencies] -criterion = { version = "0.8.2", features = ["html_reports"] } - [dependencies] -arc-swap = "1.8.2" -clap = { version = "4.5.60", features = ["derive"] } -fxhash = "0.2.1" -ron = "0.12.0" -serde = { version = "1.0.228", features = ["derive"] } - common = { path = "../../common" } +dsa_formats = { path = "../../dsa_formats" } +assembler = { path = "../../assembler" } +emulator_core = { version = "0.3.0", path = "../emulator_core" } +disassembler = { version = "0.3.0", path = "../../disassembler" } + +clap = { version = "4.5.60", features = ["derive"] } +ron = "0.12.0" egui = "0.35.0" eframe = "0.35.0" egui_tiles = "0.16.0" egui_code_editor = "0.3.7" egui_file = "0.27.0" -dirs = "6.0.0" -assembler = { path = "../../assembler" } egui_extras = "0.35.0" -emulator_core = { version = "0.3.0", path = "../emulator_core" } -disassembler = { version = "0.3.0", path = "../../disassembler" } + egui-file-dialog = "0.14.1" -egui_commonmark = { version = "0.24.0", features = ["better_syntax_highlighting", "macros"] } - -[features] -default = ["mainstore-prealloc"] - -# Memory Bank Features -mainstore-bulkalloc = [] # Fastest for Writes -mainstore-prealloc = [] # Fastest for Reads -mainstore-stackarray = [] # Slightly outperforms ArrayMap but requires a large stack -mainstore-arraymap = [] # Simple implementation -mainstore-hashmap = [] # Old implementation, no raw pointers. Uses a hashmap +egui_commonmark = { version = "0.24.0", features = ["better_syntax_highlighting", "macros"] } \ No newline at end of file diff --git a/dsa/emulator/emulator_ui/src/args.rs b/dsa/emulator/emulator_ui/src/args.rs index 5fe0124..1347e80 100644 --- a/dsa/emulator/emulator_ui/src/args.rs +++ b/dsa/emulator/emulator_ui/src/args.rs @@ -2,7 +2,7 @@ use std::{fs, path::PathBuf}; use clap::Parser; -use emulator_core::config::{IoMap, IoMapping, MemoryMap}; +use emulator_core::config::{IoMap, MemoryMap}; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] diff --git a/dsa/emulator/emulator_ui/src/lib.rs b/dsa/emulator/emulator_ui/src/lib.rs index fbaf689..7c0617e 100644 --- a/dsa/emulator/emulator_ui/src/lib.rs +++ b/dsa/emulator/emulator_ui/src/lib.rs @@ -1,6 +1,18 @@ -#![feature(likely_unlikely)] -#![feature(inherent_associated_types)] -#![feature(str_as_str)] +use crate::ui::DamnSimpleUI; +use eframe::NativeOptions; +use emulator_core::EmulatorController; pub mod ui; pub mod args; +pub mod state; + +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(DamnSimpleUI::new(cc, controller)))), + ) +} \ 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 7a88d75..8488f0c 100644 --- a/dsa/emulator/emulator_ui/src/main.rs +++ b/dsa/emulator/emulator_ui/src/main.rs @@ -1,7 +1,7 @@ use clap::Parser; use dsa::args::DsaArgs; -use dsa::ui::run_app; +use dsa::run_app; use emulator_core::{DsaImplementation, EmulatorController, processor::Emulator}; fn main() { @@ -9,49 +9,7 @@ fn main() { let mut emulator = Emulator::new(); emulator.apply_memory_map(args.get_memory_map()); - 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(controller).unwrap() - } -} - -fn run_server(args: &DsaArgs) { - if args.headless() { - println!("Running in headless mode"); - } else { - println!("Running in GUI mode"); - } -} + run_app(controller).unwrap(); +} \ No newline at end of file diff --git a/dsa/emulator/emulator_ui/src/state.rs b/dsa/emulator/emulator_ui/src/state.rs new file mode 100644 index 0000000..2206533 --- /dev/null +++ b/dsa/emulator/emulator_ui/src/state.rs @@ -0,0 +1,29 @@ +use crate::ui::process_manager::ProcessSpawner; +use egui_tiles::TileId; +use std::fmt::Debug; +use std::path::PathBuf; + + +/// the action enum forms the core of the application's state. +/// - Actions are used as the main way that Tiles inside the application pass state updates back to the parent UI, +/// specifically when those state updates involve other files. for example, pressing "build" in the editor would emit +/// a SpawnProcess action, running some task which on completion emits a LoadBinary action on the result +pub enum Action { + None, + RequestCloseTab(TileId), + CloseTab(TileId), + Quit, + OpenFile(PathBuf), + LoadBinary(DataSource), + SaveFile(PathBuf, TileId), + RequestSaveAs(TileId), + ShowError(String), + SpawnProcess(ProcessSpawner) +} + +#[derive(Debug, Clone)] +pub enum DataSource { + File(PathBuf), + Data(Vec), +} + diff --git a/dsa/emulator/emulator_ui/src/ui/appearance.rs b/dsa/emulator/emulator_ui/src/ui/appearance.rs new file mode 100644 index 0000000..9ecd8c4 --- /dev/null +++ b/dsa/emulator/emulator_ui/src/ui/appearance.rs @@ -0,0 +1,43 @@ +pub fn configure_appearance(ctx: &egui::Context) { + // configure appearance of UI elements + let mut visuals = egui::Visuals::dark(); + + 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); +} \ No newline at end of file diff --git a/dsa/emulator/emulator_ui/src/ui/components/editor/mod.rs b/dsa/emulator/emulator_ui/src/ui/components/editor/mod.rs deleted file mode 100644 index 466d161..0000000 --- a/dsa/emulator/emulator_ui/src/ui/components/editor/mod.rs +++ /dev/null @@ -1,697 +0,0 @@ -use std::fmt::Write; -use std::fs::{self}; -use std::io::Cursor; -use std::sync::atomic::Ordering; -use std::sync::Arc; -use std::{ - ffi::OsStr, - path::{Path, PathBuf}, -}; - -pub mod syntax; - -use super::Component; -use assembler::prelude::Assembler; -use common::formats::binary_dse::DseExecutable; -use common::prelude::Instruction; -use egui::{Align, Context, Key, Layout, Ui}; -use egui_code_editor::{CodeEditor, ColorTheme, Completer, Syntax}; -use egui_extras::{Column, TableBuilder}; -use egui_file::FileDialog; -use emulator_core::{ - memory::ram::{MemoryBank, Page}, - processor::state::SharedState -}; -use crate::ui::components::memory::MemoryViewer; - -pub struct Editor { - state: Arc, - memory: MemoryBank, - - // editor state - path: Option, - unsaved: bool, - text: String, - buffer: String, - - // editor widget - editor: CodeEditor, - completer: Completer, - syntax: Syntax, - - // output / loading - output: Vec, - viewer: MemoryViewer, - load_offset: u32, - offset_str: String, - - // file dialogs - open_file_dialog: Option, - save_file_dialog: Option, - - // other - visible: bool, - show_output: bool, - error: Option, -} - -impl Component for Editor { - fn title(&self) -> &'static str { - "Editor" - } - - fn visible(&mut self) -> &mut bool { - &mut self.visible - } - - fn ui(&mut self, ui: &mut Ui) { - if self.buffer != self.text { - self.unsaved = true; - } - - ui.vertical(|ui| { - // Top bar - - if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) { - self.save(); - } - - self.render_toolbar(ui); - - ui.add_space(4.0); // Add some spacing instead of just a separator - ui.separator(); - - let remaining_height = f32::max(ui.available_height() - 100.0, 100.0); - - ui.allocate_ui_with_layout( - egui::Vec2::new(ui.available_width(), remaining_height), - Layout::left_to_right(Align::Min), - |ui| { - if self.show_output { - egui::Panel::right("Editor Output").resizable(false).show_inside(ui, |ui| { - self.viewer.ui(ui, self.output.as_slice(), 0); - }); - } - self.render_editor(ui); - }, - ); - - self.render_bottom_bar(ui); - }); - } -} - -impl Editor { - #[must_use] - pub fn new(state: Arc, memory: MemoryBank) -> Self { - Self { - memory, - state, - path: None, - text: String::new(), - buffer: String::new(), - completer: Completer::new_with_syntax(&Syntax::default()).with_user_words(), - syntax: Syntax::default(), - editor: CodeEditor::default() - .id_source("editor") - .with_fontsize(12.0) - .with_rows(0) - .with_theme(THEME) - .with_numlines(true), - output: Vec::new(), - viewer: MemoryViewer::new(), - unsaved: true, - visible: false, - load_offset: 0, - offset_str: String::new(), - error: None, - open_file_dialog: None, - save_file_dialog: None, - show_output: false, - } - } - - fn filename(&self) -> &str { - if let Some(path) = &self.path { - return path - .file_name() - .unwrap_or_else(|| OsStr::new("Unnamed!")) - .to_str() - .unwrap_or_else(|| unreachable!("File name should be valid UTF-8.")); - } - "Unnamed!" - } - - fn extension(&self) -> &str { - if let Some(path) = &self.path { - return path - .extension() - .unwrap_or_else(|| OsStr::new("Unknown!")) - .to_str() - .unwrap_or_else(|| unreachable!("File name should be valid UTF-8.")); - } - "Unknown!" - } - - fn save(&mut self) { - if self.open_file_dialog.is_some() { - // TODO: Flash an error stating you can only have one menu open at once. - self.open_file_dialog = None; - } - - if let Some(path) = &self.path { - // Save to existing path - self.buffer = self.text.clone(); - - let text = if path.extension().is_some_and(|ext| ext == "dsb") { - let mut res = Vec::new(); - for line in self.text.lines() { - for line in line.split_whitespace() { - match u32::from_str_radix(line, 16) { - Ok(num) => res.push(num), - Err(e) => { - self.error = Some(format!("Failed to parse file: {e}")); - return; - } - } - } - } - res.into_iter() - .flat_map(u32::to_be_bytes) - .collect::>() - } else { - self.text.as_bytes().to_vec() - }; - - if let Err(why) = std::fs::write(path, text) { - self.error = Some(format!("Failed to save file: {why}")); - } else { - self.unsaved = false; - } - } else { - // Open the save dialog. - let work_dir = std::env::current_dir().unwrap_or_else(|_| { - dirs::home_dir() - .expect("Couldn't get your current working directory or your home directory.") - }); - - if self.save_file_dialog.is_none() { - let mut dialog = FileDialog::save_file().initial_path(work_dir); - dialog.open(); - self.save_file_dialog = Some(dialog); - } - } - } - - fn open(&mut self) { - let work_dir = std::env::current_dir().unwrap_or_else(|_| { - dirs::home_dir() - .expect("Couldn't get your current working directory or your home directory.") - }); - - if self.save_file_dialog.is_some() { - // TODO: Flash an error stating you can only have one menu open at once. - self.save_file_dialog = None; - } - - if self.open_file_dialog.is_none() { - let mut dialog = FileDialog::open_file(); - if let Some(p) = &self.path - && let Some(path) = p.parent().map(Path::to_path_buf) - { - dialog.set_path(path); - } else { - dialog.set_path(work_dir); - } - - dialog.open(); - self.open_file_dialog = Some(dialog); - } - - self.syntax = match self.extension() { - "dsa" => syntax::dsa(), - "dsc" => syntax::dsc(), - "rs" => Syntax::rust(), - _ => Syntax::default(), - }; - - self.completer = Completer::new_with_syntax(&self.syntax).with_user_words(); - } - - fn handle_file_dialogs(&mut self, ui: &Ui) { - // Handle open dialog - if let Some(dialog) = &mut self.open_file_dialog - && dialog.show(ui).selected() - { - if let Some(file) = dialog.path() { - // check if the file is a binary file - if file.extension().is_some_and(|ext| ext == "dsb") { - match std::fs::read(file) { - Ok(content) => { - let mut res = String::new(); - for (i, b) in content.iter().enumerate() { - _ = write!(res, "{b:02x}"); - if i % 4 == 3 { - res.push('\n'); - } - } - self.text = res.clone(); - self.buffer = res; - self.path = Some(file.to_path_buf()); - self.unsaved = false; - self.error = None; - } - Err(e) => { - self.error = Some(format!("Failed to read file: {e}")); - } - } - } else { - match std::fs::read_to_string(file) { - Ok(content) => { - self.text = content.clone(); - self.buffer = content; - self.path = Some(file.to_path_buf()); - self.unsaved = false; - self.error = None; - } - Err(e) => { - self.error = Some(format!("Failed to read file: {e}")); - } - } - } - } - self.open_file_dialog = None; - } - - // Handle save dialog - if let Some(dialog) = &mut self.save_file_dialog - && dialog.show(ui).selected() - { - if let Some(file) = dialog.path() { - self.buffer = self.text.clone(); - - let content = if file.extension().is_some_and(|ext| ext == "dsb") { - let mut res = Vec::new(); - for line in self.text.lines() { - for line in line.split_whitespace() { - match u32::from_str_radix(line, 16) { - Ok(num) => res.push(num), - Err(e) => { - self.error = Some(format!("Failed to parse file: {e}")); - return; - } - } - } - } - res.into_iter() - .flat_map(u32::to_be_bytes) - .collect::>() - } else { - self.text.clone().as_bytes().to_vec() - }; - - match std::fs::write(file, content) { - Ok(()) => { - self.path = Some(file.to_path_buf()); - self.unsaved = false; - self.error = None; - } - Err(e) => { - self.error = Some(format!("Failed to save file: {e}")); - } - } - } - self.save_file_dialog = None; - } - } - - fn render_editor(&mut self, ui: &mut Ui) { - let available_width = ui.available_width(); - let mut editor = self.editor.clone().desired_width(available_width); - editor.show_with_completer(ui, &mut self.text, &self.syntax, &mut self.completer); - } - - fn render_bottom_bar(&self, ui: &mut Ui) { - ui.horizontal(|ui| { - // error display - ui.label( - egui::RichText::new(self.error.clone().unwrap_or_default()) - .color(egui::Color32::RED), - ); - }); - } - - fn build(&mut self) { - if let Some(path) = &self.path { - match path.extension().and_then(|ext| ext.to_str()) { - Some("dsa") => { - let mut assembler = Assembler::new(path); - assembler.start(()); - - // Or block until done - self.output = match assembler.output() { - Ok(instructions) => instructions, - Err(e) => { - self.error = Some(e.to_string()); - return; - } - }; - } - // Some("dsc") => { - // let dsa_path = Path::new(path).with_extension("dsa"); - // let mut compiler = Compiler::new(path); - - // let is_lib = false; - // compiler.start(is_lib); - - // if let Err(e) = compiler.write_result(&dsa_path) { - // self.error = Some(e.to_string()); - // return; - // } - - // let mut assembler = Assembler::new(&dsa_path); - // assembler.start(()); - - // // Or block until done - // self.output = match assembler.output() { - // Ok(instructions) => instructions, - // Err(e) => { - // self.error = Some(format!("Assembler error: {e}")); - // return; - // } - // }; - // } - Some("dsb") => { - if let Ok(bytes) = fs::read(path) { - self.output = bytes; - } else { - self.error = Some("Failed to read file".to_string()); - } - } - _ => { - self.error = Some(format!("Invalid file type: {}", self.filename())); - } - } - } - } - - fn render_toolbar(&mut self, ui: &mut Ui) { - self.handle_file_dialogs(ui); - - ui.horizontal(|ui| { - ui.label(format!("File type: {}", self.extension())); - ui.label(format!("Filename: {}", self.filename())); - ui.label(format!("Unsaved: {}", self.unsaved)); - - // number of lines in the file - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let line_count = self.text.lines().count(); - ui.label(format!("Lines: {line_count}")); - }); - }); - - 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(); - } - - // Saves the current file - // ui.add_enabled(false, egui::Button::new("Save")); - if ui.button("Save").clicked() { - self.save(); - } - - // // builds the current file - // ui.add_enabled(false, egui::Button::new("Build")); - if ui.button("Build").clicked() && !self.unsaved { - self.build(); - } - - // // Loads the generated binary into the assembler at the provided offset - // ui.add_enabled(false, egui::Button::new("Load")); - if ui.button("Load").clicked() { - if self.error.is_some() { - self.error = Some("Can't load program at invalid offset!".to_string()); - } - - let mut reader = Cursor::new(self.output.clone()); - let Ok(executable) = DseExecutable::read(&mut reader) else { - self.error = Some("Failed to read program!".to_string()); - return - }; - - let image: Vec = Page::paginate(&executable.to_memory_image()).collect(); - - self.state.reseting.store(true, Ordering::Relaxed); - - self.memory.clear(); - for (i, page) in image.iter().enumerate() { - self.memory.write_page(i as u32 * 4096, &page); - } - self.state.reseting.store(false, Ordering::Relaxed); - } - - // Entry widget to enter a load offset - if ui.text_edit_singleline(&mut self.offset_str).changed() { - if let Some(offset) = parse_address(&self.offset_str) { - self.load_offset = offset; - self.error = None; - } else { - self.error = Some("Invalid offset".to_string()); - } - } - - ui.checkbox(&mut self.show_output, "Show Output"); - }); - } -} - -fn parse_address(address: &str) -> Option { - address.strip_prefix("0x").map_or_else( - || { - address.strip_prefix("0b").map_or_else( - || { - address.strip_prefix("0o").map_or_else( - || address.parse::().ok(), - |oct| u32::from_str_radix(oct, 8).ok(), - ) - }, - |bin| u32::from_str_radix(bin, 2).ok(), - ) - }, - |hex| u32::from_str_radix(hex, 16).ok(), - ) -} - -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 -}; -// -// struct Output { -// pub data: Vec, -// -// show_bytes: bool, -// show_words: bool, -// show_ascii: bool, -// show_instruction: bool, -// show_decimal: bool, -// } -// -// impl Output { -// fn new() -> Self { -// Self { -// data: vec![], -// show_bytes: true, -// show_words: true, -// show_ascii: true, -// show_instruction: true, -// show_decimal: true, -// } -// } -// -// fn mut_data(&mut self) -> &mut Vec { -// &mut self.data -// } -// -// fn data(&self) -> &Vec { -// &self.data -// } -// -// fn ui(&mut self, ui: &mut Ui) { -// // Output area with synchronized scrolling -// egui::ScrollArea::vertical() -// .id_salt("output_scroll") -// .max_width(400.0) -// .show(ui, |ui| { -// if self.data.is_empty() { -// ui.label( -// egui::RichText::new("No output data") -// .font(egui::FontId::monospace(12.0)) -// .color(egui::Color32::GRAY), -// ); -// return; -// } -// -// ui.horizontal(|ui| { -// ui.toggle_value(&mut self.show_bytes, "Bytes"); -// ui.toggle_value(&mut self.show_words, "Words"); -// ui.toggle_value(&mut self.show_decimal, "Decimal"); -// ui.toggle_value(&mut self.show_ascii, "Ascii"); -// ui.toggle_value(&mut self.show_instruction, "Instructions"); -// }); -// -// ui.separator(); -// -// let mut table = TableBuilder::new(ui) -// .resizable(false) // makes all columns resizable at once -// .column(Column::initial(55.0)); // addr -// -// if self.show_bytes { -// table = table.column(Column::initial(90.0)); -// } -// if self.show_words { -// table = table.column(Column::initial(75.0)); -// } -// if self.show_decimal { -// table = table.column(Column::initial(80.0)); -// } -// if self.show_ascii { -// table = table.column(Column::initial(50.0)); -// } -// if self.show_instruction { -// table = table.column(Column::initial(200.0).clip(true)); -// } -// -// table -// .header(20.0, |mut header| { -// header.col(|ui| { -// ui.label("Addr"); -// }); -// if self.show_bytes { -// header.col(|ui| { -// ui.label("Bytes"); -// }); -// } -// if self.show_words { -// header.col(|ui| { -// ui.label("Word"); -// }); -// } -// if self.show_decimal { -// header.col(|ui| { -// ui.label("Decimal"); -// }); -// } -// if self.show_ascii { -// header.col(|ui| { -// ui.label("Ascii"); -// }); -// } -// if self.show_instruction { -// header.col(|ui| { -// ui.label("Instruction"); -// }); -// } -// }) -// .body(|mut body| { -// // Process bytes in chunks of 4 -// for (line_num, chunk) in self.data.chunks(4).enumerate() { -// body.row(18.0, |mut row| { -// let address = line_num * 4; -// let mut buf = [0u8; 4]; -// buf[..chunk.len()].copy_from_slice(chunk); -// let value = u32::from_le_bytes(buf); -// -// // Address column -// row.col(|ui| { -// ui.label( -// egui::RichText::new(format!("0x{address:04X}")) -// .font(egui::FontId::monospace(12.0)), -// ); -// }); -// -// if self.show_bytes { -// row.col(|ui| { -// // Individual bytes column -// ui.label( -// egui::RichText::new( -// chunk -// .iter() -// .map(|b| format!("{b:02X}")) -// .collect::>() -// .join(" "), -// ) -// .font(egui::FontId::monospace(12.0)) -// .color(egui::Color32::from_rgb(200, 200, 255)), -// ); -// }); -// } -// -// if self.show_words { -// row.col(|ui| { -// // Hex column -// ui.label( -// egui::RichText::new(format!("0x{value:08X}")) -// .font(egui::FontId::monospace(12.0)) -// .color(egui::Color32::from_rgb(255, 100, 100)), -// ); -// }); -// } -// -// if self.show_decimal { -// row.col(|ui| { -// ui.label( -// egui::RichText::new(format!("{value}")) -// .font(egui::FontId::monospace(12.0)) -// .color(egui::Color32::from_rgb(100, 255, 100)), -// ); -// }); -// } -// -// if self.show_ascii { -// let ascii = value.to_le_bytes(); -// row.col(|ui| { -// ui.label( -// egui::RichText::new(String::from_utf8_lossy(&ascii)) -// .font(egui::FontId::monospace(12.0)) -// .color(egui::Color32::from_rgb(100, 255, 255)), -// ); -// }); -// } -// -// if self.show_instruction { -// row.col(|ui| { -// ui.label( -// egui::RichText::new(format!( -// "{:?}", -// Instruction(value) -// )) -// .font(egui::FontId::monospace(12.0)) -// .color(egui::Color32::from_rgb(255, 100, 255)), -// ); -// }); -// } -// }) -// } -// }); -// }); -// } -// } diff --git a/dsa/emulator/emulator_ui/src/ui/components/mod.rs b/dsa/emulator/emulator_ui/src/ui/components/mod.rs deleted file mode 100644 index f777fca..0000000 --- a/dsa/emulator/emulator_ui/src/ui/components/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -pub mod controller; -pub mod display; -pub mod editor; -pub mod framebuffer; -pub mod memory; -pub mod registers; -pub mod serial; -pub mod disassembler; - -pub trait Component { - /// The tab label shown in the tile header. - fn title(&self) -> &str; - - fn visible(&mut self) -> &mut bool; - - /// Draw the component's UI inside the provided `Ui`. - fn ui(&mut self, ui: &mut egui::Ui); -} diff --git a/dsa/emulator/emulator_ui/src/ui/controller.rs b/dsa/emulator/emulator_ui/src/ui/controller.rs index 3e01d33..40eb129 100644 --- a/dsa/emulator/emulator_ui/src/ui/controller.rs +++ b/dsa/emulator/emulator_ui/src/ui/controller.rs @@ -1,5 +1,3 @@ -use crate::ui::ui_helpers::StatusIndicator; -use eframe::emath::Vec2; use eframe::epaint::Color32; use common::prelude::Register; use emulator_core::EmulatorHandle; @@ -8,14 +6,12 @@ use crate::ui::ui_helpers::PaddedWidget; pub struct Controller { emulator: EmulatorHandle, - visible: bool, } impl Controller { pub fn new(state: EmulatorHandle) -> Self { Self { emulator: state, - visible: false, } } @@ -92,8 +88,5 @@ impl Controller { PaddedWidget::label(format!("Freq: {}MHz", mips)).ui(ui); }); }); - - - } } diff --git a/dsa/emulator/emulator_ui/src/ui/dialog/confirm_dialog.rs b/dsa/emulator/emulator_ui/src/ui/dialogs/confirm_dialog.rs similarity index 100% rename from dsa/emulator/emulator_ui/src/ui/dialog/confirm_dialog.rs rename to dsa/emulator/emulator_ui/src/ui/dialogs/confirm_dialog.rs diff --git a/dsa/emulator/emulator_ui/src/ui/dialog/error_dialog.rs b/dsa/emulator/emulator_ui/src/ui/dialogs/error_dialog.rs similarity index 100% rename from dsa/emulator/emulator_ui/src/ui/dialog/error_dialog.rs rename to dsa/emulator/emulator_ui/src/ui/dialogs/error_dialog.rs diff --git a/dsa/emulator/emulator_ui/src/ui/dialog/file_dialog.rs b/dsa/emulator/emulator_ui/src/ui/dialogs/file_dialog.rs similarity index 100% rename from dsa/emulator/emulator_ui/src/ui/dialog/file_dialog.rs rename to dsa/emulator/emulator_ui/src/ui/dialogs/file_dialog.rs diff --git a/dsa/emulator/emulator_ui/src/ui/dialog/mod.rs b/dsa/emulator/emulator_ui/src/ui/dialogs/mod.rs similarity index 100% rename from dsa/emulator/emulator_ui/src/ui/dialog/mod.rs rename to dsa/emulator/emulator_ui/src/ui/dialogs/mod.rs diff --git a/dsa/emulator/emulator_ui/src/ui/mod.rs b/dsa/emulator/emulator_ui/src/ui/mod.rs index 296a28d..fffa146 100644 --- a/dsa/emulator/emulator_ui/src/ui/mod.rs +++ b/dsa/emulator/emulator_ui/src/ui/mod.rs @@ -1,78 +1,34 @@ +use crate::state::{Action, DataSource}; use crate::ui::controller::Controller; -use crate::ui::dialog::error_dialog::ErrorDialog; -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; -use crate::ui::panels::{CentralTiles, TilePane}; -use common::formats::binary_dse::DseExecutable; -use common::process::TraceableProcess; -use dialog::confirm_dialog::ConfirmDialog; -use dialog::file_dialog::DsaFileDialog; -use eframe::NativeOptions; +use crate::ui::dialogs::error_dialog::ErrorDialog; +use crate::ui::tiles::about::AboutScreen; +use crate::ui::tiles::disassembler::BinaryViewer; +use crate::ui::tiles::memory::MemoryInspector; +use crate::ui::tiles::registers::Registers; +use crate::ui::tiles::{CentralTiles, TilePane}; +use dialogs::confirm_dialog::ConfirmDialog; +use dialogs::file_dialog::DsaFileDialog; +use dsa_formats::binary_dse::DseExecutable; use egui::{CentralPanel, Panel, Ui}; use egui_tiles::TileId; use emulator_core::EmulatorController; -use panels::PanelFactory; -use panels::documentation::Documentation; -use panels::editor::Editor; -use process_manager::{ProcessManager, ProcessSpawner}; -use std::fmt::Debug; +use emulator_core::devices::display::Display; +use emulator_core::devices::serial::Serial; +use tiles::PanelFactory; +use tiles::documentation::Documentation; +use tiles::editor::Editor; +use tiles::io_devices::framebuffer::FrameBufferUi; +use process_manager::ProcessManager; use std::fs::File; use std::io::BufReader; -use std::path::PathBuf; use std::time::Duration; -mod dialog; -mod panels; +mod dialogs; +mod tiles; mod ui_helpers; mod controller; pub mod process_manager; - -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(DamnSimpleUI::new(cc, controller)))), - ) -} - -trait FileFormat: Debug + Clone { - fn extension(&self) -> &'static str; -} - -impl FileFormat for DseExecutable { - fn extension(&self) -> &'static str { - "dse" - } -} - -#[derive(Debug, Clone)] -pub enum DataSource { - File(PathBuf), - Data(Vec), -} - -pub enum Action { - None, - RequestCloseTab(TileId), - CloseTab(TileId), - Quit, - OpenFile(PathBuf), - // OpenCachedFile(Box), - LoadBinary(DataSource), - SaveFile(PathBuf, TileId), - RequestSaveAs(TileId), - ShowError(String), - SpawnProcess(ProcessSpawner) -} - +pub mod appearance; pub struct DamnSimpleUI { emulator: EmulatorController, @@ -91,7 +47,7 @@ pub struct DamnSimpleUI { impl eframe::App for DamnSimpleUI { - fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { + fn ui(&mut self, ui: &mut Ui, frame: &mut eframe::Frame) { ui.request_repaint_after(Duration::from_millis(16)); if let Some(action) = self.confirm_dialog.show(ui) { @@ -152,7 +108,7 @@ impl DamnSimpleUI { cc: &eframe::CreationContext, mut emulator: EmulatorController, ) -> Self { - configure_appearance(&cc.egui_ctx); + appearance::configure_appearance(&cc.egui_ctx); let handle = emulator.get_handle().clone(); Self { central_tiles: CentralTiles::new(), @@ -173,7 +129,7 @@ impl DamnSimpleUI { }), PanelFactory::register("Framebuffer", { let handle = handle.clone(); - move || FrameBuffer::new(320, 240, 0x30000, handle.clone()) + move || FrameBufferUi::new(320, 240, 0x30000, handle.clone()) }), PanelFactory::register("Serial", { let handle = handle.clone(); @@ -323,47 +279,3 @@ impl DamnSimpleUI { } } -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/panels/framebuffer.rs b/dsa/emulator/emulator_ui/src/ui/panels/framebuffer.rs deleted file mode 100644 index c4341db..0000000 --- a/dsa/emulator/emulator_ui/src/ui/panels/framebuffer.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::any::Any; -use egui::{Ui, Vec2, WidgetText}; -use egui_tiles::TileId; -use emulator_core::{memory::PhysAddr, EmulatorHandle}; -use crate::ui::Action; -use crate::ui::panels::TilePane; - -pub struct FrameBuffer { - width: usize, - height: usize, - addr: PhysAddr, - emulator: EmulatorHandle, - pub buffer: Vec, - - texture: Option, -} - -impl FrameBuffer { - pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self { - let width = width as usize; - let height = height as usize; - Self { - width, - height, - addr, - emulator, - buffer: vec![0; width * height * 4], - texture: None, - } - } - - pub fn width(&self) -> usize { - self.width - } - pub fn height(&self) -> usize { - self.height - } - - fn internal_read(&mut self) -> Vec { - let byte_size = self.width * self.height * 4; - self.emulator.mem_read(self.addr, byte_size as u32) - } - - pub fn changed(&mut self) -> bool { - let temp = self.internal_read(); - if temp != self.buffer { - self.buffer = temp; - return true; - } - false - } - - pub fn read(&self) -> &[u8] { - &self.buffer - } - - /// Convert buffer to RGBA bytes for use with egui/wgpu textures - pub fn as_rgba_bytes(&self) -> Vec { - self.buffer - .iter() - .flat_map(|&pixel| pixel.to_le_bytes()) - .collect() - } -} - -impl TilePane for FrameBuffer { - fn title(&self) -> WidgetText { - "Framebuffer".into() - } - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - - fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { - let changed = self.changed(); - - // get or create the texture - let texture = self.texture.get_or_insert_with(|| { - ui.load_texture( - "framebuffer", - egui::ColorImage { - size: [self.width, self.height], - source_size: Vec2::from([self.width as f32, self.height as f32]), - pixels: vec![egui::Color32::BLACK; self.width * self.height], - }, - egui::TextureOptions::NEAREST, - ) - }); - - if changed { - let pixels: Vec = self - .buffer - .as_chunks::<4>().0 - .iter() - .map(|&[red, green, blue, alpha]| { - egui::Color32::from_rgba_premultiplied(red, green, blue, alpha) - }) - .collect(); - - texture.set( - egui::ColorImage { - size: [self.width, self.height], - source_size: Vec2::from([self.width as f32, self.height as f32]), - pixels, - }, - egui::TextureOptions::NEAREST, - ); - } - - // scale to fit available space while preserving aspect ratio - let available = ui.available_size(); - let aspect = self.width as f32 / self.height as f32; - let size = if available.x / aspect <= available.y { - egui::vec2(available.x, available.x / aspect) - } else { - egui::vec2(available.y * aspect, available.y) - }; - - ui.image(egui::load::SizedTexture::new(texture.id(), size)); - None - } -} diff --git a/dsa/emulator/emulator_ui/src/ui/process_manager.rs b/dsa/emulator/emulator_ui/src/ui/process_manager.rs index fd9cf44..cbff551 100644 --- a/dsa/emulator/emulator_ui/src/ui/process_manager.rs +++ b/dsa/emulator/emulator_ui/src/ui/process_manager.rs @@ -1,11 +1,10 @@ -use crate::ui::Action; +use crate::state::Action; +use crate::ui::ui_helpers::text::strip_ansi; +use crate::ui::ui_helpers::painted_tab; use common::process::{ProcessAgent, TraceableProcess}; -use egui::Ui; use std::collections::VecDeque; use std::sync::mpsc; use std::thread::JoinHandle; -use crate::ui::ui_helpers::{close_button, painted_tab}; -use crate::ui::ui_helpers::text::strip_ansi; pub type ProcessSpawner = Box; diff --git a/dsa/emulator/emulator_ui/src/ui/panels/about.rs b/dsa/emulator/emulator_ui/src/ui/tiles/about.rs similarity index 92% rename from dsa/emulator/emulator_ui/src/ui/panels/about.rs rename to dsa/emulator/emulator_ui/src/ui/tiles/about.rs index 65d5d02..830cd7a 100644 --- a/dsa/emulator/emulator_ui/src/ui/panels/about.rs +++ b/dsa/emulator/emulator_ui/src/ui/tiles/about.rs @@ -1,5 +1,5 @@ -use crate::ui::Action; -use crate::ui::panels::TilePane; +use crate::state::Action; +use crate::ui::tiles::TilePane; use eframe::epaint::Direction; use egui::{Ui, WidgetText}; use egui_tiles::TileId; diff --git a/dsa/emulator/emulator_ui/src/ui/panels/disassembler.rs b/dsa/emulator/emulator_ui/src/ui/tiles/disassembler.rs similarity index 97% rename from dsa/emulator/emulator_ui/src/ui/panels/disassembler.rs rename to dsa/emulator/emulator_ui/src/ui/tiles/disassembler.rs index 8248c8a..7997a23 100644 --- a/dsa/emulator/emulator_ui/src/ui/panels/disassembler.rs +++ b/dsa/emulator/emulator_ui/src/ui/tiles/disassembler.rs @@ -2,8 +2,8 @@ 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::sync::mpsc; use std::thread; use egui::{ScrollArea, Ui, WidgetText}; @@ -11,12 +11,11 @@ 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; +use crate::state::{Action, DataSource}; +use crate::ui::tiles::TilePane; +use crate::ui::tiles::memory::MemoryViewer; +use disassembler::{Disassembly, disassemble}; +use dsa_formats::binary_dse::DseExecutable; #[derive(PartialEq, Clone, Copy)] enum ViewMode { diff --git a/dsa/emulator/emulator_ui/src/ui/panels/documentation.rs b/dsa/emulator/emulator_ui/src/ui/tiles/documentation.rs similarity index 93% rename from dsa/emulator/emulator_ui/src/ui/panels/documentation.rs rename to dsa/emulator/emulator_ui/src/ui/tiles/documentation.rs index 7b004e6..bfc13ba 100644 --- a/dsa/emulator/emulator_ui/src/ui/panels/documentation.rs +++ b/dsa/emulator/emulator_ui/src/ui/tiles/documentation.rs @@ -1,11 +1,11 @@ -use std::default::Default; -use std::any::Any; -use std::path::Path; -use egui::{Ui, Widget, WidgetText}; +use crate::state::Action; +use crate::ui::tiles::TilePane; +use egui::{Ui, WidgetText}; use egui_commonmark::{CommonMarkCache, CommonMarkViewer}; use egui_tiles::TileId; -use crate::ui::Action; -use crate::ui::panels::TilePane; +use std::any::Any; +use std::default::Default; +use std::path::Path; pub struct Documentation { buffer: Option, diff --git a/dsa/emulator/emulator_ui/src/ui/panels/editor/mod.rs b/dsa/emulator/emulator_ui/src/ui/tiles/editor/mod.rs similarity index 86% rename from dsa/emulator/emulator_ui/src/ui/panels/editor/mod.rs rename to dsa/emulator/emulator_ui/src/ui/tiles/editor/mod.rs index ef5bd44..ac8bb2a 100644 --- a/dsa/emulator/emulator_ui/src/ui/panels/editor/mod.rs +++ b/dsa/emulator/emulator_ui/src/ui/tiles/editor/mod.rs @@ -1,21 +1,18 @@ -use crate::ui::Action; -use crate::ui::panels::TilePane; -use common::process::{ProcessAgent, TraceableProcess}; +use crate::state::Action; +use crate::state::DataSource::Data; +use crate::ui::tiles::TilePane; +use crate::ui::tiles::editor::syntax::SyntaxFrom; +use crate::ui::ui_helpers::PaddedWidget; +use assembler::prelude::Assembler; +use common::process::TraceableProcess; +use dsa_formats::binary_dse::DseExecutable; use egui::{Color32, Key, Ui, Widget}; use egui_code_editor::{CodeEditor, Completer, Syntax}; use egui_tiles::TileId; use std::any::Any; use std::fs::File; -use std::io::Error; use std::path::{Path, PathBuf}; -use std::thread; -use std::time::Duration; -use assembler::prelude::Assembler; -use common::formats::binary_dse::DseExecutable; use theme::THEME; -use crate::ui::DataSource::Data; -use crate::ui::panels::editor::syntax::SyntaxFrom; -use crate::ui::ui_helpers::PaddedWidget; mod syntax; mod theme; @@ -66,7 +63,14 @@ impl Editor { &mut self.buffer } - pub fn build_cmd(&self, filename: PathBuf, on_complete: impl FnOnce(DseExecutable) -> Action + Send + 'static) -> Action { + pub fn get_build_command(filename: PathBuf) -> impl TraceableProcess { + match filename.extension().unwrap().to_str().unwrap() { + "dsa" => Assembler::new(filename), + _ => todo!(), + } + } + + pub fn create_build_action(&self, filename: PathBuf, on_complete: impl FnOnce(DseExecutable) -> Action + Send + 'static) -> Action { Action::SpawnProcess(Box::new(move |pm| { let builder = Assembler::new(filename); pm.spawn( @@ -75,7 +79,6 @@ impl Editor { move |result| match result { Ok(bin) => on_complete(bin), Err(e) => Action::ShowError(e.to_string()), - } ); })) @@ -101,7 +104,7 @@ impl TilePane for Editor { return; }; - action = self.build_cmd( + action = self.create_build_action( filename, |res| { Action::LoadBinary(Data(res.to_memory_image())) } @@ -114,7 +117,7 @@ impl TilePane for Editor { return; }; - action = self.build_cmd( + action = self.create_build_action( filename.clone(), move |res| { let filename = filename.with_extension("dsb"); let mut writer = File::create(&filename).unwrap(); @@ -124,8 +127,6 @@ impl TilePane for Editor { ); } - - PaddedWidget::label( self.filename .as_ref() diff --git a/dsa/emulator/emulator_ui/src/ui/panels/editor/syntax.rs b/dsa/emulator/emulator_ui/src/ui/tiles/editor/syntax.rs similarity index 100% rename from dsa/emulator/emulator_ui/src/ui/panels/editor/syntax.rs rename to dsa/emulator/emulator_ui/src/ui/tiles/editor/syntax.rs diff --git a/dsa/emulator/emulator_ui/src/ui/panels/editor/theme.rs b/dsa/emulator/emulator_ui/src/ui/tiles/editor/theme.rs similarity index 100% rename from dsa/emulator/emulator_ui/src/ui/panels/editor/theme.rs rename to dsa/emulator/emulator_ui/src/ui/tiles/editor/theme.rs diff --git a/dsa/emulator/emulator_ui/src/ui/panels/display.rs b/dsa/emulator/emulator_ui/src/ui/tiles/io_devices/display.rs similarity index 66% rename from dsa/emulator/emulator_ui/src/ui/panels/display.rs rename to dsa/emulator/emulator_ui/src/ui/tiles/io_devices/display.rs index 7632bd3..1098177 100644 --- a/dsa/emulator/emulator_ui/src/ui/panels/display.rs +++ b/dsa/emulator/emulator_ui/src/ui/tiles/io_devices/display.rs @@ -1,53 +1,9 @@ -use std::any::Any; +use crate::state::Action; +use crate::ui::tiles::TilePane; use egui::{Color32, FontId, Ui, Vec2, WidgetText}; use egui_tiles::TileId; -use emulator_core::{memory::ram::{MemoryBank, Page}, memory::PhysAddr, EmulatorHandle}; -use crate::ui::Action; -use crate::ui::panels::TilePane; - -pub struct Display { - width: u32, - height: u32, - addr: PhysAddr, - - emulator: EmulatorHandle, - buffer: Vec, -} - -impl Display { - pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self { - Self { - width, - height, - addr, - emulator, - buffer: Vec::new(), - } - } - - pub fn width(&self) -> usize { - self.width as usize - } - - pub fn height(&self) -> usize { - self.height as usize - } - - - pub fn changed(&mut self) -> bool { - let temp = self.read(); - if temp != self.buffer { - self.buffer = temp; - return true; - } - false - } - - pub fn read(&mut self) -> Vec { - self.emulator.mem_read(self.addr, self.width * self.height) - } -} - +use emulator_core::devices::display::Display; +use std::any::Any; impl TilePane for Display { fn title(&self) -> WidgetText { diff --git a/dsa/emulator/emulator_ui/src/ui/tiles/io_devices/framebuffer.rs b/dsa/emulator/emulator_ui/src/ui/tiles/io_devices/framebuffer.rs new file mode 100644 index 0000000..4d13b5d --- /dev/null +++ b/dsa/emulator/emulator_ui/src/ui/tiles/io_devices/framebuffer.rs @@ -0,0 +1,79 @@ +use crate::state::Action; +use crate::ui::tiles::TilePane; +use egui::{Ui, Vec2, WidgetText}; +use egui_tiles::TileId; +use emulator_core::EmulatorHandle; +use emulator_core::devices::framebuffer::FrameBuffer; +use emulator_core::memory::PhysAddr; +use std::any::Any; + +pub struct FrameBufferUi { + device: FrameBuffer, + texture: Option, +} + +impl FrameBufferUi { + pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self { + Self { + device: FrameBuffer::new(width, height, addr, emulator), + texture: None, + } + } +} + +impl TilePane for FrameBufferUi { + fn title(&self) -> WidgetText { + "Framebuffer".into() + } + fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { + let frame = self.device.read(); + let width = self.device.width(); + let height = self.device.height(); + + // get or create the texture + let texture = self.texture.get_or_insert_with(|| { + ui.load_texture( + "framebuffer", + egui::ColorImage { + size: [width, height], + source_size: Vec2::from([width as f32, height as f32]), + pixels: vec![egui::Color32::BLACK; width * height], + }, + egui::TextureOptions::NEAREST, + ) + }); + + let pixels: Vec = frame + .as_chunks::<4>().0 + .iter() + .map(|&[red, green, blue, _alpha]| { + egui::Color32::from_rgba_premultiplied(red, green, blue, 255) + }) + .collect(); + + texture.set( + egui::ColorImage { + size: [width, height], + source_size: Vec2::from([width as f32, height as f32]), + pixels, + }, + egui::TextureOptions::NEAREST, + ); + + // scale to fit available space while preserving aspect ratio + let available = ui.available_size(); + let aspect = width as f32 / height as f32; + let size = if available.x / aspect <= available.y { + egui::vec2(available.x, available.x / aspect) + } else { + egui::vec2(available.y * aspect, available.y) + }; + + ui.image(egui::load::SizedTexture::new(texture.id(), size)); + None + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} diff --git a/dsa/emulator/emulator_ui/src/ui/tiles/io_devices/mod.rs b/dsa/emulator/emulator_ui/src/ui/tiles/io_devices/mod.rs new file mode 100644 index 0000000..4df9e2e --- /dev/null +++ b/dsa/emulator/emulator_ui/src/ui/tiles/io_devices/mod.rs @@ -0,0 +1,3 @@ +pub mod display; +pub mod framebuffer; +pub mod serial; \ No newline at end of file diff --git a/dsa/emulator/emulator_ui/src/ui/tiles/io_devices/serial.rs b/dsa/emulator/emulator_ui/src/ui/tiles/io_devices/serial.rs new file mode 100644 index 0000000..3ecd43c --- /dev/null +++ b/dsa/emulator/emulator_ui/src/ui/tiles/io_devices/serial.rs @@ -0,0 +1,37 @@ +use crate::state::Action; +use crate::ui::tiles::TilePane; +use egui::{Ui, WidgetText}; +use egui_tiles::TileId; +use emulator_core::devices::serial::Serial; +use std::any::Any; + +impl TilePane for Serial { + fn title(&self) -> WidgetText { + "Serial".into() + } + fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option { + self.update(); + + // Text field for input and a send button + ui.text_edit_multiline(&mut self.write_buff); + if ui.button("Send").clicked() { + self.write_buff.push(0 as char); + for byte in self.write_buff.bytes() { + let _ = self.write_tx.send(byte); + } + self.write_buff.clear(); + } + + // Display received data + ui.label(String::from_utf8(self.read_buff.clone()).unwrap()); + 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/panels/memory.rs b/dsa/emulator/emulator_ui/src/ui/tiles/memory.rs similarity index 98% rename from dsa/emulator/emulator_ui/src/ui/panels/memory.rs rename to dsa/emulator/emulator_ui/src/ui/tiles/memory.rs index a547494..ceb17fa 100644 --- a/dsa/emulator/emulator_ui/src/ui/panels/memory.rs +++ b/dsa/emulator/emulator_ui/src/ui/tiles/memory.rs @@ -1,13 +1,12 @@ -use std::any::Any; -use std::num::ParseIntError; +use crate::state::Action; +use crate::ui::tiles::TilePane; +use common::prelude::Instruction; use egui::{Ui, WidgetText}; use egui_extras::{Column, TableBuilder}; -use egui_tiles::{Tile, TileId}; -use common::prelude::Instruction; +use egui_tiles::TileId; use emulator_core::EmulatorHandle; -use emulator_core::memory::ram::MemoryBank; -use crate::ui::Action; -use crate::ui::panels::TilePane; +use std::any::Any; +use std::num::ParseIntError; pub struct MemoryInspector { emulator: EmulatorHandle, diff --git a/dsa/emulator/emulator_ui/src/ui/panels/mod.rs b/dsa/emulator/emulator_ui/src/ui/tiles/mod.rs similarity index 96% rename from dsa/emulator/emulator_ui/src/ui/panels/mod.rs rename to dsa/emulator/emulator_ui/src/ui/tiles/mod.rs index c7fc067..6cee663 100644 --- a/dsa/emulator/emulator_ui/src/ui/panels/mod.rs +++ b/dsa/emulator/emulator_ui/src/ui/tiles/mod.rs @@ -2,19 +2,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; +pub mod io_devices; -use std::any::Any; +use crate::state::Action; +use crate::state::Action::RequestCloseTab; +use crate::ui::ui_helpers::painted_tab; use eframe::epaint::{Color32, CornerRadius}; -use egui::{Id, Response, Sense, Stroke, TextStyle, Ui, WidgetInfo, WidgetType}; +use egui::{Id, Response, Stroke, Ui}; use egui_tiles::{Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse}; -use crate::ui::Action; -use crate::ui::Action::RequestCloseTab; -use crate::ui::ui_helpers::{close_button_at, PaddedWidget, painted_tab}; +use std::any::Any; /// 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/panels/registers.rs b/dsa/emulator/emulator_ui/src/ui/tiles/registers.rs similarity index 98% rename from dsa/emulator/emulator_ui/src/ui/panels/registers.rs rename to dsa/emulator/emulator_ui/src/ui/tiles/registers.rs index 2b32b6f..0d6b1a5 100644 --- a/dsa/emulator/emulator_ui/src/ui/panels/registers.rs +++ b/dsa/emulator/emulator_ui/src/ui/tiles/registers.rs @@ -1,11 +1,11 @@ -use std::f32; -use std::any::Any; +use crate::state::Action; +use crate::ui::tiles::TilePane; +use common::prelude::Register; use egui::{Ui, WidgetText}; use egui_tiles::TileId; -use common::prelude::Register; use emulator_core::EmulatorHandle; -use crate::ui::Action; -use crate::ui::panels::TilePane; +use std::any::Any; +use std::f32; pub struct Registers { state: EmulatorHandle, diff --git a/dsa_resources/animation.dsa b/dsa_resources/animation.dsa index c2fc1c0..5ab9cfb 100644 --- a/dsa_resources/animation.dsa +++ b/dsa_resources/animation.dsa @@ -1,3 +1,6 @@ +include print: "./print.dsa" + + dw DISPLAY: 0x30000 dw stack: 0x10000 @@ -16,17 +19,25 @@ dw COL_IDX: 0 // rg3 = y direction (1 = down, 0 = up) // rg5 = current colour +db startup: "Starting animation..." + _init: ldw stack, bpr mov bpr, spr start: + lwi startup, rg0 + push rg0 + call print::print + pop zero + lli 0, rg0 lli 0, rg1 lli 1, rg2 lli 1, rg3 lli 0, rg8 stw rg8, COL_IDX + ldw COL0, rg5 frame_loop: diff --git a/dsa_resources/animation.dsb b/dsa_resources/animation.dsb index b7aa9755cc6ce4ed65cc8008625cae91d509078b..06c0011a68bc47634e137e7dd30e0745a86fa9f0 100644 GIT binary patch literal 2151 zcmZ{lQD{?F9L9eqH@Y~+R+x&gRvMAfid?!ZWDGj6YdY+xwb3dL9M?3tw!t(B>8(wM z!eP|mLk2CB!h*QHh<(}1#$_*iNNpXd77;074;}b2=3d4g_E1=vwS(fB;Dt$n}J{i)u*2HLd#9iUzF zzXdil{sU0b^@_r^P90d)^&{Y>_WcFe)cCK8qkC3!+0yuy($rr607IJdx_}K`?-wwt zIY$J{>Dm(mAOsOv}S5b6416ycd65t zt?7av-CK{P{d_B{C6L$7bV2(xJ)+Gl-eA&st7K*E%R33ZRNk1&RyseE^R2urrItCB zRJ-q}uZY&4naDWyR3Vwt$xFJSW_y$Qv~pAJ`w@3`Q`!4AO=GpQQ~D~EFXrqbV0h=_ zxq88K@9kajT>Wy-s@Z39~R6$|Orr@3_STUh)ZW@nGcpQPY?z(Z`g!>V=(_S-HO<0YBZW2B6g(_kaXhW?oU|vBKZ=8iO{1=68G%rTAoS%P)=YV7-{37z_vIO@>~)>p z&%LKZ&eqMt)FXn>{Hi#3ZUmL5@tt(fFG@GESM8y6V++zfzaZVK3n5M%-ZqxiUPd6? z4uRLC+aEm?xH<}bo$9G32v@d6V|aUKe|x&5yHb_zy{dGVtJ1Ai)j5&wlPd7dZm<7( zh)z%QCB6UG*}Z-#(73&1G=|^co~iQ8y4;ipxU?QnXR}sq)~c6RSutftu=iPDar8a! z3GI8Udp*0Zn|-&=qtiR*{u<@k02AQ6rtTg;|}L@YtxBk0&sGTxatv3FqjKHvF%zt8i$%+Q^i_l5BE0qwx5 zK&QZF6LbQ86U-Y;6=)JTr+EPpI1dyJe+l?%c$`yggRcP@gKq%u3{C)t22TK6 z5_%s3dnR*A=Nrue&}KBxfVjb{K)=DSfhmJGfK7wn0Q)9$i*R2(=QdC?ybrXRUY~(U z<81)njOM$!FYWya!Q2}E)?TCk1N<^MnyVV`MS%l@Gx^k_opI+J2FFLPhGo&tx$e9s zu48BCvIQRHQ?pLU28N{*xER`J-KUOSB;({#=Ih}2$TiZ_PI|^J+WG9`>4LqO%H<5T z6ch`gZ|RtPGJjH(cJp&iJ`B})*0C<*F3yF@g+eMlqu==g8P{I2p9G^vStz)9$4P8Qaw>I*$s+)9E5i8%o@n7`gdCH?tE%_kmM@aHJ0$1y4|Mr2ny+d{OYl zg4-QlC3yy_3f?PWTM0V~e03oE3id5%?e*n@f)5sKT&_w@LCu2FW%@oT;gbcmE6tS3 z!mafQ?%nk&-OX+I${X%k_J->NRbol6a@KH>WpDWBz&m2e)rwzwS*e%2(a4H-BeL?m z5_wUTx`Mg|z3tv`&kAhz2tUe28e_u8a)!nj>?+t(67)z!e-TUeiSE}DKO~kU*ke~6 z(irn9Em6&dyX_u(T19h|+eGKG7G`Cm{Sr$a1i8KOxah1MCG4rV=&atT=JXwD?w@Be z;JV6FK~~qA=S-(I