rewrote assembler backend to support custom executable format (DSE) and also refactored (moving crates around)
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "emu_core"
|
||||
name = "emulator_core"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "emu_core"
|
||||
name = "emulator_core"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
+5
@@ -164,6 +164,11 @@ impl Emulator {
|
||||
self.update();
|
||||
}
|
||||
|
||||
if let x = self.shared_state.goto.load(Ordering::Relaxed) && x != -1 {
|
||||
*self.mut_reg(Register::Pcx) = x as u32;
|
||||
self.shared_state.goto.store(-1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
if self.shared_state.reseting.load(Ordering::Relaxed) {
|
||||
self.internal_state.registers = [0; Register::COUNT as usize];
|
||||
self.internal_state.clock = 0;
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
use std::sync::{Arc, atomic::AtomicBool, mpsc};
|
||||
|
||||
use std::sync::atomic::AtomicIsize;
|
||||
use arc_swap::ArcSwap;
|
||||
|
||||
use crate::processor::processor::ProcessorSnapshot;
|
||||
@@ -9,6 +9,7 @@ use super::interrupts::Interrupt;
|
||||
pub struct SharedState {
|
||||
pub proc: ArcSwap<ProcessorSnapshot>,
|
||||
|
||||
pub goto: AtomicIsize,
|
||||
pub reseting: AtomicBool,
|
||||
pub paused: AtomicBool,
|
||||
pub update_req: AtomicBool,
|
||||
@@ -21,6 +22,7 @@ impl SharedState {
|
||||
reseting: AtomicBool::new(false),
|
||||
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
|
||||
|
||||
goto: AtomicIsize::new(-1),
|
||||
paused: AtomicBool::new(true),
|
||||
interrupt_queue: sender,
|
||||
update_req: AtomicBool::new(false),
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "emulator"
|
||||
name = "emulator_ui"
|
||||
version = "0.3.0"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
@@ -13,7 +13,7 @@ name = "dsa"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5.0", features = ["html_reports"] }
|
||||
criterion = { version = "0.8.2", features = ["html_reports"] }
|
||||
|
||||
[dependencies]
|
||||
arc-swap = "1.8.2"
|
||||
@@ -23,15 +23,15 @@ ron = "0.12.0"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
|
||||
common = { path = "../../common" }
|
||||
egui = "0.33.3"
|
||||
eframe = "0.33.3"
|
||||
egui_tiles = "0.14.1"
|
||||
egui_code_editor = "0.2.21"
|
||||
egui_file = "0.25.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.33.3"
|
||||
emu_core = { version = "0.3.0", path = "../backend" }
|
||||
egui_extras = "0.35.0"
|
||||
emulator_core = { version = "0.3.0", path = "../emulator_core" }
|
||||
|
||||
[features]
|
||||
default = ["mainstore-prealloc"]
|
||||
@@ -2,7 +2,7 @@ use std::{fs, path::PathBuf};
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use emu_core::config::{IoMap, IoMapping, MemoryMap};
|
||||
use emulator_core::config::{IoMap, IoMapping, MemoryMap};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
@@ -4,8 +4,10 @@ use common::prelude::Instruction;
|
||||
use std::thread;
|
||||
use dsa::args::DsaArgs;
|
||||
use dsa::ui::run_app;
|
||||
use emu_core::memory::ram::Page;
|
||||
use emu_core::processor::processor::Emulator;
|
||||
use emulator_core::{
|
||||
memory::ram::Page,
|
||||
processor::processor::Emulator
|
||||
};
|
||||
|
||||
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
||||
|
||||
+5
-3
@@ -2,8 +2,10 @@ use std::sync::{Arc, atomic::Ordering};
|
||||
|
||||
use super::Component;
|
||||
use common::prelude::Register;
|
||||
use emu_core::memory::ram::MemoryBank;
|
||||
use emu_core::processor::state::SharedState;
|
||||
use emulator_core::{
|
||||
memory::ram::MemoryBank,
|
||||
processor::state::SharedState
|
||||
};
|
||||
|
||||
pub struct Controller {
|
||||
state: Arc<SharedState>,
|
||||
@@ -30,7 +32,7 @@ impl Component for Controller {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let state = self.state.proc.load();
|
||||
|
||||
let paused = self.state.paused.load(Ordering::Relaxed);
|
||||
+5
-3
@@ -1,6 +1,8 @@
|
||||
use egui::{Color32, FontId, Vec2};
|
||||
use emu_core::memory::PhysAddr;
|
||||
use emu_core::memory::ram::{MemoryBank, Page};
|
||||
use emulator_core::{
|
||||
memory::PhysAddr,
|
||||
memory::ram::{MemoryBank, Page}
|
||||
};
|
||||
use super::Component;
|
||||
|
||||
pub struct Display {
|
||||
@@ -72,7 +74,7 @@ impl Component for Display {
|
||||
self.visible()
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let display_w = self.width();
|
||||
let display_h = self.height();
|
||||
let data = self.read();
|
||||
+54
-157
@@ -1,7 +1,8 @@
|
||||
use std::fmt::Write;
|
||||
use std::fs::{self, File};
|
||||
use std::sync::Arc;
|
||||
use std::fs::{self};
|
||||
use std::io::Cursor;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
@@ -9,15 +10,18 @@ use std::{
|
||||
|
||||
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 emu_core::memory::ram::{MemoryBank, Page};
|
||||
use emu_core::processor::state::SharedState;
|
||||
use super::Component;
|
||||
use emulator_core::{
|
||||
memory::ram::{MemoryBank, Page},
|
||||
processor::state::SharedState
|
||||
};
|
||||
|
||||
pub struct Editor {
|
||||
state: Arc<SharedState>,
|
||||
@@ -32,16 +36,13 @@ pub struct Editor {
|
||||
// editor widget
|
||||
editor: CodeEditor,
|
||||
completer: Completer,
|
||||
syntax: Syntax,
|
||||
|
||||
// output / loading
|
||||
output: Output,
|
||||
load_offset: u32,
|
||||
offset_str: String,
|
||||
|
||||
// cursor - currently unused
|
||||
cursor_col: usize,
|
||||
cursor_line: usize,
|
||||
|
||||
// file dialogs
|
||||
open_file_dialog: Option<FileDialog>,
|
||||
save_file_dialog: Option<FileDialog>,
|
||||
@@ -61,7 +62,7 @@ impl Component for Editor {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, ctx: &Context) {
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
if self.buffer != self.text {
|
||||
self.unsaved = true;
|
||||
}
|
||||
@@ -73,7 +74,7 @@ impl Component for Editor {
|
||||
self.save();
|
||||
}
|
||||
|
||||
self.render_toolbar(ui, ctx);
|
||||
self.render_toolbar(ui);
|
||||
|
||||
ui.add_space(4.0); // Add some spacing instead of just a separator
|
||||
ui.separator();
|
||||
@@ -85,15 +86,15 @@ impl Component for Editor {
|
||||
Layout::left_to_right(Align::Min),
|
||||
|ui| {
|
||||
if self.show_output {
|
||||
egui::SidePanel::right("Editor Output").resizable(false).show_inside(ui, |ui| {
|
||||
self.output.ui(ui, ctx);
|
||||
egui::Panel::right("Editor Output").resizable(false).show_inside(ui, |ui| {
|
||||
self.output.ui(ui);
|
||||
});
|
||||
}
|
||||
self.render_editor(ui, ctx);
|
||||
self.render_editor(ui);
|
||||
},
|
||||
);
|
||||
|
||||
self.render_bottom_bar(ui, ctx);
|
||||
self.render_bottom_bar(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -108,17 +109,15 @@ impl Editor {
|
||||
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_syntax(Syntax::default())
|
||||
.with_numlines(true),
|
||||
output: Output::new(),
|
||||
unsaved: true,
|
||||
cursor_col: 1,
|
||||
cursor_line: 1,
|
||||
visible: false,
|
||||
load_offset: 0,
|
||||
offset_str: String::new(),
|
||||
@@ -226,21 +225,20 @@ impl Editor {
|
||||
self.open_file_dialog = Some(dialog);
|
||||
}
|
||||
|
||||
let syntax = match self.extension() {
|
||||
self.syntax = match self.extension() {
|
||||
"dsa" => syntax::dsa(),
|
||||
"dsc" => syntax::dsc(),
|
||||
"rs" => Syntax::rust(),
|
||||
_ => Syntax::default(),
|
||||
};
|
||||
|
||||
self.completer = Completer::new_with_syntax(&syntax).with_user_words();
|
||||
self.editor = self.editor.clone().with_syntax(syntax);
|
||||
self.completer = Completer::new_with_syntax(&self.syntax).with_user_words();
|
||||
}
|
||||
|
||||
fn handle_file_dialogs(&mut self, ctx: &egui::Context) {
|
||||
fn handle_file_dialogs(&mut self, ui: &Ui) {
|
||||
// Handle open dialog
|
||||
if let Some(dialog) = &mut self.open_file_dialog
|
||||
&& dialog.show(ctx).selected()
|
||||
&& dialog.show(ui).selected()
|
||||
{
|
||||
if let Some(file) = dialog.path() {
|
||||
// check if the file is a binary file
|
||||
@@ -284,7 +282,7 @@ impl Editor {
|
||||
|
||||
// Handle save dialog
|
||||
if let Some(dialog) = &mut self.save_file_dialog
|
||||
&& dialog.show(ctx).selected()
|
||||
&& dialog.show(ui).selected()
|
||||
{
|
||||
if let Some(file) = dialog.path() {
|
||||
self.buffer = self.text.clone();
|
||||
@@ -324,24 +322,19 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_editor(&mut self, ui: &mut Ui, _ctx: &Context) {
|
||||
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, &mut self.completer);
|
||||
editor.show_with_completer(ui, &mut self.text, &self.syntax, &mut self.completer);
|
||||
}
|
||||
|
||||
fn render_bottom_bar(&self, ui: &mut Ui, _ctx: &Context) {
|
||||
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),
|
||||
);
|
||||
|
||||
// line and col
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(format!("Ln {}, Col {}", self.cursor_line, self.cursor_col));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -399,8 +392,8 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_toolbar(&mut self, ui: &mut Ui, ctx: &Context) {
|
||||
self.handle_file_dialogs(ctx);
|
||||
fn render_toolbar(&mut self, ui: &mut Ui) {
|
||||
self.handle_file_dialogs(ui);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!("File type: {}", self.extension()));
|
||||
@@ -443,13 +436,21 @@ impl Editor {
|
||||
self.error = Some("Can't load program at invalid offset!".to_string());
|
||||
}
|
||||
|
||||
let program: Vec<Page> = Page::paginate(self.output.data()).collect();
|
||||
let mut reader = Cursor::new(self.output.data().clone());
|
||||
let Ok(executable) = DseExecutable::read(&mut reader) else {
|
||||
self.error = Some("Failed to read program!".to_string());
|
||||
return
|
||||
};
|
||||
|
||||
let image: Vec<Page> = Page::paginate(&executable.to_memory_image()).collect();
|
||||
|
||||
self.state.reseting.store(true, Ordering::Relaxed);
|
||||
|
||||
self.memory.clear();
|
||||
for (i, page) in program.iter().enumerate() {
|
||||
for (i, page) in image.iter().enumerate() {
|
||||
self.memory.write_page(i as u32 * 4096, &page);
|
||||
}
|
||||
self.state.goto.store(executable.entry_point(0) as isize, Ordering::Relaxed);
|
||||
self.state.reseting.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
@@ -463,7 +464,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
ui.checkbox(&mut self.show_output, "Show Output")
|
||||
ui.checkbox(&mut self.show_output, "Show Output");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -488,18 +489,18 @@ fn parse_address(address: &str) -> Option<u32> {
|
||||
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
|
||||
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 {
|
||||
@@ -532,7 +533,7 @@ impl Output {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui, _ctx: &Context) {
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
// Output area with synchronized scrolling
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt("output_scroll")
|
||||
@@ -613,8 +614,9 @@ impl Output {
|
||||
for (line_num, chunk) in self.data.chunks(4).enumerate() {
|
||||
body.row(18.0, |mut row| {
|
||||
let address = line_num * 4;
|
||||
|
||||
let value = u32::from_le_bytes(*chunk.as_array().unwrap());
|
||||
let mut buf = [0u8; 4];
|
||||
buf[..chunk.len()].copy_from_slice(chunk);
|
||||
let value = u32::from_le_bytes(buf);
|
||||
|
||||
// Address column
|
||||
row.col(|ui| {
|
||||
@@ -688,111 +690,6 @@ impl Output {
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// egui::Grid::new("output_grid")
|
||||
// .spacing([5.0, 2.0]) // Horizontal and vertical spacing
|
||||
// .num_columns(4)
|
||||
// .striped(false)
|
||||
// .show(ui, |ui| {
|
||||
// ui.label("Address");
|
||||
|
||||
// if self.show_bytes {
|
||||
// ui.label("Bytes");
|
||||
// }
|
||||
// if self.show_words {
|
||||
// ui.label("Word");
|
||||
// }
|
||||
// if self.show_decimal {
|
||||
// ui.label("Decimal");
|
||||
// }
|
||||
// if self.show_ascii {
|
||||
// ui.label("Ascii");
|
||||
// }
|
||||
// if self.show_instruction {
|
||||
// ui.label("Hex");
|
||||
// }
|
||||
// ui.end_row();
|
||||
|
||||
// // Process bytes in chunks of 4
|
||||
// for (line_num, chunk) in self.data.chunks(4).enumerate() {
|
||||
// let address = line_num * 4;
|
||||
|
||||
// // Convert chunk to u32 (little-endian)
|
||||
// let mut bytes = [0u8; 4];
|
||||
// for (i, &byte) in chunk.iter().enumerate() {
|
||||
// if i < 4 {
|
||||
// bytes[i] = byte;
|
||||
// }
|
||||
// }
|
||||
// let value = u32::from_le_bytes(bytes);
|
||||
|
||||
// // Address column
|
||||
// ui.with_layout(
|
||||
// egui::Layout::left_to_right(egui::Align::Center),
|
||||
// |ui| {
|
||||
// ui.set_min_width(80.0);
|
||||
// let style = ui.style_mut();
|
||||
// style.visuals.widgets.inactive.bg_fill =
|
||||
// egui::Color32::from_gray(30);
|
||||
// ui.label(
|
||||
// egui::RichText::new(format!("0x{address:04X}"))
|
||||
// .font(egui::FontId::monospace(12.0)),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
|
||||
// // Individual bytes column
|
||||
// let byte_str = chunk
|
||||
// .iter()
|
||||
// .map(|b| format!("{b:02X}"))
|
||||
// .collect::<Vec<_>>()
|
||||
// .join(" ");
|
||||
|
||||
// if self.show_bytes {
|
||||
// ui.label(
|
||||
// egui::RichText::new(format!("{byte_str:<11}"))
|
||||
// .font(egui::FontId::monospace(12.0))
|
||||
// .color(egui::Color32::from_rgb(200, 200, 255)),
|
||||
// );
|
||||
// }
|
||||
|
||||
// if self.show_words {
|
||||
// // 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 {
|
||||
// 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();
|
||||
// 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 {
|
||||
// ui.label(
|
||||
// egui::RichText::new(format!("{:?}", Instruction(value)))
|
||||
// .font(egui::FontId::monospace(12.0))
|
||||
// .color(egui::Color32::from_rgb(255, 100, 255)),
|
||||
// );
|
||||
// }
|
||||
|
||||
// ui.end_row();
|
||||
// }
|
||||
// });
|
||||
});
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -1,9 +1,10 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use egui_code_editor::Syntax;
|
||||
use egui_code_editor::{Patch, Syntax};
|
||||
|
||||
pub fn dsa() -> Syntax {
|
||||
Syntax {
|
||||
word_start: BTreeSet::new(),
|
||||
quotes: BTreeSet::from(['"', '\'']),
|
||||
language: "Assembly",
|
||||
case_sensitive: false,
|
||||
@@ -22,11 +23,13 @@ pub fn dsa() -> Syntax {
|
||||
"RGC", "RGD", "RGE", "RGF", "ACC", "SPR", "BPR", "IDR", "MMR", "ZERO", "PCX", "STS",
|
||||
"CIR",
|
||||
]),
|
||||
patch: Patch::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dsc() -> Syntax {
|
||||
Syntax {
|
||||
word_start: BTreeSet::from(['_']),
|
||||
quotes: BTreeSet::from(['"', '\'', '`']),
|
||||
language: "Damn Simple Code",
|
||||
case_sensitive: false,
|
||||
@@ -44,5 +47,6 @@ pub fn dsc() -> Syntax {
|
||||
",", ";", ".", ":", "=", "+", "-", "*", "/", "%", "&", "|", "^", "~", "!", "?", "<",
|
||||
">", "<<", ">>", "==", "!=", "<=", ">=", "&&", "||",
|
||||
]),
|
||||
patch: Patch::default(),
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -1,6 +1,8 @@
|
||||
use egui::Vec2;
|
||||
use emu_core::memory::PhysAddr;
|
||||
use emu_core::memory::ram::{MemoryBank, Page};
|
||||
use emulator_core::{
|
||||
memory::PhysAddr,
|
||||
memory::ram::{MemoryBank, Page}
|
||||
};
|
||||
use super::Component;
|
||||
|
||||
pub struct FrameBuffer {
|
||||
@@ -86,12 +88,12 @@ impl Component for FrameBuffer {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let changed = self.changed();
|
||||
|
||||
// get or create the texture
|
||||
let texture = self.texture.get_or_insert_with(|| {
|
||||
ctx.load_texture(
|
||||
ui.load_texture(
|
||||
"framebuffer",
|
||||
egui::ColorImage {
|
||||
size: [self.width, self.height],
|
||||
@@ -0,0 +1,264 @@
|
||||
use std::num::ParseIntError;
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use common::prelude::Instruction;
|
||||
use emulator_core::memory::ram::MemoryBank;
|
||||
use super::Component;
|
||||
|
||||
pub struct MemoryInspector {
|
||||
memory: MemoryBank,
|
||||
|
||||
view_size: u32,
|
||||
view_addr: u32,
|
||||
visible: bool,
|
||||
addr_input: String,
|
||||
|
||||
// settings
|
||||
show_bytes: bool,
|
||||
show_words: bool,
|
||||
show_ascii: bool,
|
||||
show_instruction: bool,
|
||||
show_decimal: bool,
|
||||
}
|
||||
|
||||
impl MemoryInspector {
|
||||
#[must_use]
|
||||
pub fn new(memory: MemoryBank) -> Self {
|
||||
Self {
|
||||
memory,
|
||||
view_size: 1024,
|
||||
view_addr: 0,
|
||||
visible: false,
|
||||
addr_input: String::from("0x00"),
|
||||
|
||||
show_bytes: true,
|
||||
show_words: true,
|
||||
show_ascii: true,
|
||||
show_instruction: true,
|
||||
show_decimal: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for MemoryInspector {
|
||||
fn title(&self) -> &'static str {
|
||||
"Memory Inspector"
|
||||
}
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
// Right column - Memory
|
||||
// ui.vertical(|ui| {
|
||||
// ui.add_space(10.0);
|
||||
// });
|
||||
// Memory table
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.auto_shrink([false; 2])
|
||||
.id_salt("memory_inspector_scroll")
|
||||
.show(ui, |ui| {
|
||||
|
||||
// ui.heading("Memory Inspector");
|
||||
// ui.add_space(10.0);
|
||||
//
|
||||
// // Address input section
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Address:");
|
||||
|
||||
let address_response = ui.add(
|
||||
egui::TextEdit::singleline(&mut self.addr_input)
|
||||
.hint_text("0x1000 or 4096")
|
||||
.desired_width(150.0),
|
||||
);
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Search button
|
||||
let search_clicked = ui.button("🔍 Search").clicked();
|
||||
|
||||
// Handle Enter key in text field
|
||||
let enter_pressed =
|
||||
address_response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
|
||||
|
||||
if search_clicked || enter_pressed {
|
||||
if let Ok(new) = parse_address(&self.addr_input) {
|
||||
if new % 4 == 0 {
|
||||
self.view_addr = new;
|
||||
} else {
|
||||
println!("Address must be 4-byte aligned");
|
||||
}
|
||||
} else {
|
||||
println!("Invalid address");
|
||||
// state.error_log.push("Invalid address".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
ui.label("(hex or decimal)");
|
||||
});
|
||||
|
||||
|
||||
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(75.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, value) in (0..self.view_size / 4)
|
||||
.map(|i| (i, self.memory.read_word(self.view_addr + i * 4))) {
|
||||
body.row(18.0, |mut row| {
|
||||
let address = line_num * 4 * self.view_addr;
|
||||
|
||||
let chunk = value.to_le_bytes();
|
||||
|
||||
// Address column
|
||||
row.col(|ui| {
|
||||
ui.label(
|
||||
egui::RichText::new(format!("0x{address:08X}"))
|
||||
.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::<Vec<_>>()
|
||||
.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)),
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_address(address: &str) -> Result<u32, ParseIntError> {
|
||||
if let Some(hex_part) = address.strip_prefix("0x") {
|
||||
return u32::from_str_radix(hex_part, 16);
|
||||
}
|
||||
|
||||
if let Some(bin_part) = address.strip_prefix("0b") {
|
||||
return u32::from_str_radix(bin_part, 2);
|
||||
}
|
||||
|
||||
if let Some(oct_part) = address.strip_prefix("0o") {
|
||||
return u32::from_str_radix(oct_part, 8);
|
||||
}
|
||||
|
||||
address.parse::<u32>()
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,5 +13,5 @@ pub trait Component {
|
||||
fn visible(&mut self) -> &mut bool;
|
||||
|
||||
/// Draw the component's UI inside the provided `Ui`.
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context);
|
||||
fn ui(&mut self, ui: &mut egui::Ui);
|
||||
}
|
||||
+4
-7
@@ -1,7 +1,7 @@
|
||||
use std::{f32, sync::Arc};
|
||||
|
||||
use common::prelude::Register;
|
||||
use emu_core::processor::state::SharedState;
|
||||
use emulator_core::processor::state::SharedState;
|
||||
|
||||
use super::Component;
|
||||
|
||||
@@ -14,7 +14,6 @@ impl Registers {
|
||||
fn section(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
ctx: &egui::Context,
|
||||
registers: Vec<(Register, u32)>,
|
||||
col_pairs: usize,
|
||||
name: &str,
|
||||
@@ -67,7 +66,7 @@ impl Component for Registers {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.set_min_width(200.0);
|
||||
|
||||
let state = self.state.proc.load();
|
||||
@@ -87,7 +86,6 @@ impl Component for Registers {
|
||||
.collect();
|
||||
self.section(
|
||||
ui,
|
||||
ctx,
|
||||
gp_registers,
|
||||
col_pairs,
|
||||
"General Purpose Registers",
|
||||
@@ -99,7 +97,7 @@ impl Component for Registers {
|
||||
(Register::Spr, state.registers[Register::Spr as usize]),
|
||||
(Register::Bpr, state.registers[Register::Bpr as usize]),
|
||||
];
|
||||
self.section(ui, ctx, stack_registers, col_pairs, "Stack Registers");
|
||||
self.section(ui, stack_registers, col_pairs, "Stack Registers");
|
||||
ui.separator();
|
||||
|
||||
// ── Special Purpose ───────────────────────────────────────
|
||||
@@ -111,7 +109,6 @@ impl Component for Registers {
|
||||
];
|
||||
self.section(
|
||||
ui,
|
||||
ctx,
|
||||
special_registers,
|
||||
col_pairs,
|
||||
"Special Purpose Registers",
|
||||
@@ -124,7 +121,7 @@ impl Component for Registers {
|
||||
(Register::Sts, state.registers[Register::Sts as usize]),
|
||||
(Register::Cir, state.registers[Register::Cir as usize]),
|
||||
];
|
||||
self.section(ui, ctx, system_registers, col_pairs, "System Registers");
|
||||
self.section(ui, system_registers, col_pairs, "System Registers");
|
||||
});
|
||||
}
|
||||
}
|
||||
+11
-5
@@ -6,10 +6,16 @@ use std::{
|
||||
mpsc::{Receiver, Sender},
|
||||
},
|
||||
};
|
||||
use emu_core::memory::PhysAddr;
|
||||
use emu_core::memory::ram::MemoryBank;
|
||||
use emu_core::processor::interrupts::Interrupt;
|
||||
use emu_core::processor::state::SharedState;
|
||||
use emulator_core::{
|
||||
memory::{
|
||||
PhysAddr,
|
||||
ram::MemoryBank
|
||||
},
|
||||
processor::{
|
||||
interrupts::Interrupt,
|
||||
state::SharedState
|
||||
}
|
||||
};
|
||||
use super::Component;
|
||||
|
||||
/// ## SERIAL LAYOUT: (from the perspective of code inside the ui)
|
||||
@@ -100,7 +106,7 @@ impl Component for Serial {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _ctx: &egui::Context) {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
self.update();
|
||||
|
||||
// Text field for input and a send button
|
||||
@@ -8,9 +8,11 @@ use components::{
|
||||
controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer, memory::MemoryInspector,
|
||||
registers::Registers, serial::Serial, Component,
|
||||
};
|
||||
use emu_core::config::VERSION;
|
||||
use emu_core::memory::ram::MemoryBank;
|
||||
use emu_core::processor::state::SharedState;
|
||||
use emulator_core::{
|
||||
config::VERSION,
|
||||
memory::ram::MemoryBank,
|
||||
processor::state::SharedState
|
||||
};
|
||||
|
||||
pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Result<()> {
|
||||
eframe::run_native(
|
||||
@@ -21,12 +23,13 @@ pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Resul
|
||||
}
|
||||
|
||||
impl eframe::App for DsaUi {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
// request an update from ui every cycle
|
||||
self.state.update_req.store(true, Ordering::Relaxed);
|
||||
|
||||
// title panel with dsa title
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
egui::containers::Panel::top("top_panel").show(ui, |ui| {
|
||||
ui.with_layout(
|
||||
egui::Layout::top_down_justified(egui::Align::Center)
|
||||
.with_main_align(egui::Align::Min),
|
||||
@@ -39,7 +42,7 @@ impl eframe::App for DsaUi {
|
||||
});
|
||||
|
||||
// menu bar.
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::Panel::top("menu_bar").show(ui, |ui| {
|
||||
egui::MenuBar::new().ui(ui, |ui| {
|
||||
ui.menu_button("Panels", |ui| {
|
||||
ui.set_max_width(300.0);
|
||||
@@ -54,21 +57,21 @@ impl eframe::App for DsaUi {
|
||||
});
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::CentralPanel::default().show(ui, |ui| {
|
||||
for c in &mut self.components {
|
||||
let mut visible = *c.visible();
|
||||
if visible {
|
||||
egui::Window::new(c.title())
|
||||
.open(&mut visible)
|
||||
.show(ctx, |ui| {
|
||||
c.ui(ui, ctx);
|
||||
.show(ui, |ui| {
|
||||
c.ui(ui);
|
||||
});
|
||||
}
|
||||
*c.visible() = visible;
|
||||
}
|
||||
});
|
||||
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(16)); // ~60fps
|
||||
ui.request_repaint_after(std::time::Duration::from_millis(16)); // ~60fps
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use common::prelude::Instruction;
|
||||
use emu_core::memory::ram::MemoryBank;
|
||||
use super::Component;
|
||||
|
||||
pub struct MemoryInspector {
|
||||
memory: MemoryBank,
|
||||
|
||||
view_size: u32,
|
||||
view_addr: u32,
|
||||
visible: bool,
|
||||
addr_input: String,
|
||||
}
|
||||
|
||||
impl MemoryInspector {
|
||||
#[must_use]
|
||||
pub fn new(memory: MemoryBank) -> Self {
|
||||
Self {
|
||||
memory,
|
||||
view_size: 1024,
|
||||
view_addr: 0,
|
||||
visible: false,
|
||||
addr_input: String::from("0x00"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for MemoryInspector {
|
||||
fn title(&self) -> &'static str {
|
||||
"Memory Inspector"
|
||||
}
|
||||
|
||||
fn visible(&mut self) -> &mut bool {
|
||||
&mut self.visible
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||
// Right column - Memory
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Memory Inspector");
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Address input section
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Address:");
|
||||
|
||||
let address_response = ui.add(
|
||||
egui::TextEdit::singleline(&mut self.addr_input)
|
||||
.hint_text("0x1000 or 4096")
|
||||
.desired_width(150.0),
|
||||
);
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Search button
|
||||
let search_clicked = ui.button("🔍 Search").clicked();
|
||||
|
||||
// Handle Enter key in text field
|
||||
let enter_pressed =
|
||||
address_response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter));
|
||||
|
||||
if search_clicked || enter_pressed {
|
||||
if let Ok(new) = parse_address(&self.addr_input) {
|
||||
if new % 4 == 0 {
|
||||
self.view_addr = new;
|
||||
} else {
|
||||
println!("Address must be 4-byte aligned");
|
||||
}
|
||||
} else {
|
||||
println!("Invalid address");
|
||||
// state.error_log.push("Invalid address".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
ui.label("(hex or decimal)");
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Memory table
|
||||
egui::ScrollArea::vertical()
|
||||
.auto_shrink(true)
|
||||
.id_salt("memory_inspector_scroll")
|
||||
.show(ui, |ui| {
|
||||
egui::Grid::new("memory_grid")
|
||||
.spacing([12.0, 2.0])
|
||||
.min_col_width(5.0)
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
// Header
|
||||
ui.strong("Address");
|
||||
|
||||
for i in 0..4 {
|
||||
ui.strong(format!("{i:X}"));
|
||||
}
|
||||
|
||||
ui.strong("Decimal");
|
||||
ui.strong("Instruction");
|
||||
|
||||
ui.end_row();
|
||||
|
||||
// Memory data (8 bytes per row)
|
||||
for (offset, word) in (0..self.view_size / 4)
|
||||
.map(|i| i * 4)
|
||||
.map(|i| (i, self.memory.read_word(self.view_addr + i)))
|
||||
{
|
||||
let row_address = self.view_addr + offset;
|
||||
ui.monospace(format!("0x{row_address:08X} ({row_address})"));
|
||||
for byte in word.to_le_bytes() {
|
||||
ui.monospace(format!("{byte:02X}"));
|
||||
}
|
||||
|
||||
ui.monospace(format!("{word}"));
|
||||
ui.monospace(format!("{:?}", Instruction(word)));
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_address(address: &str) -> Result<u32, ParseIntError> {
|
||||
if let Some(hex_part) = address.strip_prefix("0x") {
|
||||
return u32::from_str_radix(hex_part, 16);
|
||||
}
|
||||
|
||||
if let Some(bin_part) = address.strip_prefix("0b") {
|
||||
return u32::from_str_radix(bin_part, 2);
|
||||
}
|
||||
|
||||
if let Some(oct_part) = address.strip_prefix("0o") {
|
||||
return u32::from_str_radix(oct_part, 8);
|
||||
}
|
||||
|
||||
address.parse::<u32>()
|
||||
}
|
||||
Reference in New Issue
Block a user