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 b7aa975..06c0011 100644
Binary files a/dsa_resources/animation.dsb and b/dsa_resources/animation.dsb differ