Compare commits
3 Commits
adfba876d2
...
708743ef64
| Author | SHA1 | Date | |
|---|---|---|---|
| 708743ef64 | |||
| d73ec47410 | |||
| acf42d56f2 |
@@ -0,0 +1,126 @@
|
|||||||
|
include maths::sqrt
|
||||||
|
|
||||||
|
trait AsUnit {
|
||||||
|
fn unit(self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Point2 {
|
||||||
|
let x: isize,
|
||||||
|
let y: isize,
|
||||||
|
|
||||||
|
fn len(self) -> isize {
|
||||||
|
sqrt(self.x * self.x + self.y * self.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// can inherit from a single class + many traits
|
||||||
|
class Point3: Point2 + AsUnit {
|
||||||
|
let z: isize,`
|
||||||
|
|
||||||
|
fn len(self) -> isize {
|
||||||
|
// implicit return of internal expressions
|
||||||
|
sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn AsUnit.unit(self) -> Self {
|
||||||
|
let len = self.len();
|
||||||
|
Self {
|
||||||
|
x: self.x / len,
|
||||||
|
y: self.y / len,
|
||||||
|
z: self.y / len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_matches(val: AsUnit) -> bool {
|
||||||
|
|
||||||
|
// can use kotlin-style "is" syntax to find sub-classes and implementors.
|
||||||
|
if match val {
|
||||||
|
is Point3 => true, // only Point3 as it has no subclasses.
|
||||||
|
// this also implicitly downcasts us to a Point3 so we can use methods specific to this class!
|
||||||
|
is Point2 => true, // returns true for Point2 and Point3 as it is a subclass
|
||||||
|
is AsUnit => true, // always true.
|
||||||
|
} { return true; }
|
||||||
|
|
||||||
|
unreachable() // this should probably crash the program
|
||||||
|
|
||||||
|
// we can match against any boolean expression. this lazily evaluates each arm one by one.
|
||||||
|
match {
|
||||||
|
do_some_processing() == 1 => println("success!") { style => style.colour = Colour::Red }
|
||||||
|
something_else() == 1 => println!("success") // we can omit the trailing lambda when it's
|
||||||
|
|
||||||
|
// alternative syntax for the same thing as above!
|
||||||
|
val is Point3 => { return true; }
|
||||||
|
val is AsUnit => { return true; }
|
||||||
|
false == true => { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// of course normal rust matches also apply
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* uilib.dscx */
|
||||||
|
|
||||||
|
class UiBuilder {
|
||||||
|
...
|
||||||
|
fn text_entry() -> Interaction
|
||||||
|
}
|
||||||
|
|
||||||
|
class Interaction {
|
||||||
|
fn clicked() -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* app.dscx */
|
||||||
|
|
||||||
|
pub enum Action {
|
||||||
|
SubmitForm(PasswordForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
class PasswordForm {
|
||||||
|
let username: String,
|
||||||
|
let password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class UserInterface {
|
||||||
|
const MESSAGE: str = "Run Emulator";
|
||||||
|
let form: PasswordForm;
|
||||||
|
let state: EmulatorState;
|
||||||
|
|
||||||
|
fn new(state: EmulatorState) -> Self {
|
||||||
|
Self {
|
||||||
|
form: PasswordForm {
|
||||||
|
username: String::new(),
|
||||||
|
password: String::new()
|
||||||
|
},
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui(self, ui: UiBuilder) -> Action {
|
||||||
|
|
||||||
|
ui_lib::Form(self.form, ui, "login") { form, ui ->
|
||||||
|
ui.text_entry(form.username) { view ->
|
||||||
|
view.placeholder = "enter username";
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.text_entry(form.password); // also acceptable syntax. placeholder has a default value!
|
||||||
|
|
||||||
|
if ui.button("Submit").clicked() {
|
||||||
|
return Action::SubmitForm(form);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.vertical {
|
||||||
|
my_lib::StatusIndicator(self.state.status, ui);
|
||||||
|
|
||||||
|
if ui.button("Run Emulator").clicked() {
|
||||||
|
self.state.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use crate::config::default::{DEFAULT_IO_MAP, DEFAULT_MEMORY_MAP};
|
use crate::config::default::{DEFAULT_IO_MAP, DEFAULT_MEMORY_MAP};
|
||||||
use crate::memory::mmu::MMU;
|
|
||||||
use crate::memory::PhysAddr;
|
use crate::memory::PhysAddr;
|
||||||
use crate::processor::processor::Emulator;
|
use crate::memory::mmu::MMU;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::processor::DsaProcessor;
|
||||||
|
|
||||||
mod default;
|
mod default;
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ impl MemoryMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply(&self, cpu: &mut Emulator) {
|
pub fn apply(&self, cpu: &mut DsaProcessor) {
|
||||||
for reg in &self.regions {
|
for reg in &self.regions {
|
||||||
if reg.identity_map_on_boot {
|
if reg.identity_map_on_boot {
|
||||||
Self::identity_map(cpu.mmu_mut(), reg);
|
Self::identity_map(cpu.mmu_mut(), reg);
|
||||||
|
|||||||
@@ -1,5 +1,212 @@
|
|||||||
#![feature(likely_unlikely)]
|
#![feature(likely_unlikely)]
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::thread;
|
||||||
|
use std::thread::{JoinHandle, Thread};
|
||||||
|
use std::time::Duration;
|
||||||
|
use common::prelude::{Instruction, Register};
|
||||||
|
use crate::config::{IoMap, MemoryMap};
|
||||||
|
use crate::InternalImplementation::{Handle, Owned};
|
||||||
|
use crate::memory::PhysAddr;
|
||||||
|
use crate::memory::ram::MemoryBank;
|
||||||
|
use crate::processor::{Interrupt, RunningState, SharedState};
|
||||||
|
|
||||||
pub mod processor;
|
pub mod processor;
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DsaError {
|
||||||
|
BootFailure(String),
|
||||||
|
CommunicationError(String),
|
||||||
|
CriticalException(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This trait should be implemented for any type that can act as a fully functional DSA emulator with the ability to share state
|
||||||
|
pub trait DsaImplementation: Send {
|
||||||
|
/// may be modified in future with extra args like config
|
||||||
|
/// having direct control over the emulator's POST/boot state can be useful for
|
||||||
|
/// integration testing.
|
||||||
|
fn boot(&mut self) -> Result<(), DsaError>;
|
||||||
|
|
||||||
|
/// main run method. the main-loop of the program execution
|
||||||
|
fn run(&mut self) -> Result<(), DsaError>;
|
||||||
|
|
||||||
|
/// memory mapping is required for emulator memory sections
|
||||||
|
fn apply_memory_map(&mut self, map: MemoryMap);
|
||||||
|
|
||||||
|
/// IO mapping also
|
||||||
|
fn apply_io_mapping(&mut self, map: IoMap);
|
||||||
|
|
||||||
|
/// List all memory devices
|
||||||
|
/// returns a copy of the IOmap (cannot be a reference since across-threads)
|
||||||
|
fn get_io_map(&self) -> IoMap;
|
||||||
|
|
||||||
|
/// returns a wrapper around the physical memory used by the emulator
|
||||||
|
fn get_memory_handle(&mut self) -> &mut MemoryBank;
|
||||||
|
|
||||||
|
/// returns a wrapper around the shared state of the emulator
|
||||||
|
fn get_state_handle(&mut self) -> Arc<SharedState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub trait DsaDebugImplementation: DsaImplementation {
|
||||||
|
// fn step()
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EmulatorHandle {
|
||||||
|
memory: MemoryBank,
|
||||||
|
state: Arc<SharedState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmulatorHandle {
|
||||||
|
|
||||||
|
pub fn read_byte(&mut self, addr: PhysAddr) -> u8 {
|
||||||
|
self.memory.read_byte(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_byte(&mut self, addr: PhysAddr, val: u8) {
|
||||||
|
self.memory.write_byte(addr, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mem_read(&mut self, addr: PhysAddr, size: u32) -> Vec<u8> {
|
||||||
|
self.memory.read_bytes(addr, size as usize).to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mem_write(&mut self, addr: PhysAddr, data: &[u8]) {
|
||||||
|
self.memory.write_bytes(addr, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interrupt(&mut self, code: Interrupt) -> Result<(), DsaError> {
|
||||||
|
self.state.interrupt_queue.send(code)
|
||||||
|
.map_err(|_| DsaError::CommunicationError("interrupt failed".into()) )
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_update(&mut self) {
|
||||||
|
self.state.update_req.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status(&self) -> RunningState {
|
||||||
|
self.state.proc.load().running.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pause(&mut self) {
|
||||||
|
self.state.paused.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
pub fn resume(&mut self) {
|
||||||
|
self.state.paused.store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.state.reseting.store(true, Ordering::Relaxed);
|
||||||
|
self.memory.clear();
|
||||||
|
self.state.reseting.store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_register(&self, rg: Register) -> u32 {
|
||||||
|
self.state.proc.load().registers[rg as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn registers(&self) -> [u32; Register::COUNT as usize] {
|
||||||
|
self.state.proc.load().registers.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clock(&self) -> usize {
|
||||||
|
self.state.proc.load().clock
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time(&self) -> Duration {
|
||||||
|
self.state.proc.load().time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InternalImplementation {
|
||||||
|
Owned(Box<dyn DsaImplementation + Send>),
|
||||||
|
Handle(JoinHandle<()>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EmulatorController {
|
||||||
|
// the emulator itself
|
||||||
|
internal: Option<InternalImplementation>,
|
||||||
|
handle: EmulatorHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsMut<EmulatorHandle> for EmulatorController {
|
||||||
|
fn as_mut(&mut self) -> &mut EmulatorHandle {
|
||||||
|
&mut self.handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmulatorController {
|
||||||
|
|
||||||
|
pub fn new(mut emulator: Box<dyn DsaImplementation>) -> Self {
|
||||||
|
Self {
|
||||||
|
handle: EmulatorHandle {
|
||||||
|
memory: emulator.get_memory_handle().clone(),
|
||||||
|
state: emulator.get_state_handle().clone(),
|
||||||
|
},
|
||||||
|
internal: Some(Owned(emulator)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) -> Result<(), DsaError> {
|
||||||
|
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
||||||
|
|
||||||
|
let Some(Owned(mut handle)) = self.internal.take() else {
|
||||||
|
return Err(DsaError::CriticalException(String::from("owned emulator returned none!")))
|
||||||
|
};
|
||||||
|
|
||||||
|
self.internal = Some(Handle(
|
||||||
|
thread::Builder::new()
|
||||||
|
.stack_size(STACK_SIZE)
|
||||||
|
.spawn(move || {handle.run().unwrap() })
|
||||||
|
.unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_handle(&mut self) -> &mut EmulatorHandle {
|
||||||
|
&mut self.handle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_memory(&mut self, map: MemoryMap) -> Result<(), DsaError> {
|
||||||
|
let Some(Owned(t)) = &self.internal else {
|
||||||
|
return Err(DsaError::BootFailure("Emulator already initialised".into()));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(&mut self) {
|
||||||
|
// if let Some(handle) = &self.handle {
|
||||||
|
// handle.request_shutdown(); // sets an atomic the run loop checks each poll
|
||||||
|
// }
|
||||||
|
todo!();
|
||||||
|
if let Some(InternalImplementation::Handle(join_handle)) = self.internal.take() {
|
||||||
|
let _ = join_handle.join(); // wait for the thread to actually exit
|
||||||
|
}
|
||||||
|
self.internal = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,16 @@ impl MemoryBank {
|
|||||||
unsafe { self.heap.add(addr as usize).read_volatile() }
|
unsafe { self.heap.add(addr as usize).read_volatile() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn read_bytes(&self, addr: PhysAddr, size: usize) -> &[u8] {
|
||||||
|
let start = addr as usize;
|
||||||
|
assert!(start + size <= u32::MAX as usize, "read out of bounds");
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
std::slice::from_raw_parts(self.heap.add(start), size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn read_page(&self, addr: PhysAddr) -> &Page {
|
pub fn read_page(&self, addr: PhysAddr) -> &Page {
|
||||||
debug_assert_eq!(addr % 4096, 0);
|
debug_assert_eq!(addr % 4096, 0);
|
||||||
|
|||||||
+127
-324
@@ -1,321 +1,48 @@
|
|||||||
use std::{
|
use common::isa::instructions::Opcode;
|
||||||
hint::unlikely,
|
use common::prelude::{Instruction, Register};
|
||||||
ops::AddAssign,
|
use crate::config::{IoMap, MemoryMap, RegionType};
|
||||||
sync::{
|
use crate::DsaError;
|
||||||
Arc,
|
use crate::DsaError::BootFailure;
|
||||||
atomic::Ordering,
|
use crate::memory::mmu::MMU;
|
||||||
mpsc::{self, TryRecvError},
|
use crate::memory::ram::{MemoryBank, Page};
|
||||||
},
|
use crate::processor::interrupts::Interrupt;
|
||||||
thread,
|
use crate::processor::state::{ProcessorSnapshot, SharedState};
|
||||||
time::{Duration, Instant},
|
use crate::processor::state::RunningState::Running;
|
||||||
};
|
|
||||||
|
|
||||||
use common::{
|
#[derive(Copy, Clone, Debug)]
|
||||||
isa::instructions::{Instruction, Opcode},
|
pub enum ExecutionResult {
|
||||||
isa::register::Register
|
Continue,
|
||||||
};
|
Halt,
|
||||||
use crate::{
|
Interrupt(Interrupt),
|
||||||
memory::{
|
|
||||||
mmu::MMU,
|
|
||||||
ram::MemoryBank
|
|
||||||
},
|
|
||||||
processor::{
|
|
||||||
interrupts::Interrupt,
|
|
||||||
state::SharedState
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
use crate::config::{MemoryMap, RegionType};
|
|
||||||
use crate::memory::ram::Page;
|
|
||||||
use crate::processor::state::{ProcessorSnapshot, RunningState};
|
|
||||||
use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown};
|
|
||||||
|
|
||||||
pub struct Emulator {
|
pub struct DsaProcessor {
|
||||||
internal_state: ProcessorSnapshot,
|
pub internal_state: ProcessorSnapshot,
|
||||||
shared_state: Arc<SharedState>,
|
|
||||||
|
|
||||||
// Interrupts
|
pub(crate) mmu: MMU,
|
||||||
interrupts: mpsc::Receiver<Interrupt>,
|
pub(crate) mainstore: MemoryBank,
|
||||||
pending_fault: Option<Interrupt>,
|
|
||||||
|
|
||||||
// memory
|
mmio_region: Option<(u32, u32)>,
|
||||||
mmu: MMU,
|
|
||||||
mainstore: MemoryBank,
|
|
||||||
|
|
||||||
// IO
|
|
||||||
mmio_region: Option<(u32, u32)>, // start end end of segment
|
|
||||||
|
|
||||||
// optionals - not necessarily set on boot.
|
|
||||||
memory_map: Option<MemoryMap>,
|
memory_map: Option<MemoryMap>,
|
||||||
|
|
||||||
// Config params
|
|
||||||
__mmap_configured: bool,
|
__mmap_configured: bool,
|
||||||
|
|
||||||
time: Instant,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for Emulator {}
|
impl DsaProcessor {
|
||||||
impl Emulator {
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let (sender, receiver) = mpsc::channel::<Interrupt>();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
interrupts: receiver,
|
|
||||||
pending_fault: None,
|
|
||||||
|
|
||||||
internal_state: ProcessorSnapshot::default(),
|
internal_state: ProcessorSnapshot::default(),
|
||||||
shared_state: Arc::new(SharedState::new(sender)),
|
|
||||||
|
|
||||||
memory_map: None,
|
|
||||||
mmu: MMU::new(),
|
mmu: MMU::new(),
|
||||||
mainstore: MemoryBank::new(),
|
mainstore: MemoryBank::new(),
|
||||||
|
|
||||||
// mmio
|
|
||||||
mmio_region: None,
|
mmio_region: None,
|
||||||
|
memory_map: None,
|
||||||
// Config params
|
|
||||||
__mmap_configured: false,
|
__mmap_configured: false,
|
||||||
|
|
||||||
time: Instant::now(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state_handle(&self) -> Arc<SharedState> {
|
|
||||||
self.shared_state.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn mmu_mut(&mut self) -> &mut MMU {
|
pub fn execute(&mut self, word: Instruction) -> ExecutionResult {
|
||||||
&mut self.mmu
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn memory_mut(&mut self) -> &mut MemoryBank {
|
|
||||||
&mut self.mainstore
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
#[inline]
|
|
||||||
pub fn reg(&self, reg: Register) -> u32 {
|
|
||||||
debug_assert!((reg as u8) < Register::COUNT);
|
|
||||||
|
|
||||||
unsafe { *self.internal_state.registers.get_unchecked(reg as usize) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn mut_reg(&mut self, reg: Register) -> &mut u32 {
|
|
||||||
debug_assert!((reg as u8) < Register::COUNT);
|
|
||||||
unsafe {
|
|
||||||
self.internal_state
|
|
||||||
.registers
|
|
||||||
.get_unchecked_mut(reg as usize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
pub fn apply_memory_map(mut self, map: MemoryMap) -> Self {
|
|
||||||
self.mmio_region = map
|
|
||||||
.regions
|
|
||||||
.iter()
|
|
||||||
.find(|r| matches!(r.region_type, RegionType::MMIO))
|
|
||||||
.map(|r| (r.base, r.base + r.size));
|
|
||||||
|
|
||||||
map.apply(&mut self);
|
|
||||||
self.memory_map = Some(map);
|
|
||||||
self.__mmap_configured = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn share_state(&mut self) {
|
|
||||||
self.shared_state
|
|
||||||
.proc
|
|
||||||
.store(Arc::new(self.internal_state.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn boot(&mut self) -> Result<(), String> {
|
|
||||||
if !(self.__mmap_configured) {
|
|
||||||
return Err("Emulator<Mem> not configured".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(map) = &self.memory_map {
|
|
||||||
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.internal_state.running = Running;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
pub fn run_state_halted(&mut self, prior: RunningState) {
|
|
||||||
self.internal_state.running = Halted;
|
|
||||||
|
|
||||||
// Responsibility: keep track of the time spent in the running state across multiple hlt/interrupt cycles
|
|
||||||
self.internal_state.time += self.time.elapsed();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// Transition: interrupt received
|
|
||||||
if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
|
||||||
self.internal_state.running = Running;
|
|
||||||
self.interrupt(int);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transition: pause signal received
|
|
||||||
if self.shared_state.paused.load(Ordering::Relaxed) {
|
|
||||||
self.run_state_paused(Halted);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Responsibility: update UI when requested
|
|
||||||
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
|
|
||||||
self.share_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Responsibility: reset `private.time` on state exit
|
|
||||||
self.time = Instant::now();
|
|
||||||
self.internal_state.running = prior;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cold]
|
|
||||||
pub fn run_state_paused(&mut self, prior: RunningState) {
|
|
||||||
self.internal_state.running = Paused;
|
|
||||||
|
|
||||||
// Responsibility: save the running-time since last pause state.
|
|
||||||
self.internal_state.time = self.time.elapsed();
|
|
||||||
self.share_state();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// Responsibility: update UI when requested
|
|
||||||
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
|
|
||||||
self.share_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Responsibility: reset when requested by the UI
|
|
||||||
if self.shared_state.reseting.load(Ordering::Relaxed) == true {
|
|
||||||
self.internal_state.registers = [0; Register::COUNT as usize];
|
|
||||||
self.internal_state.clock = 0;
|
|
||||||
|
|
||||||
while self.shared_state.reseting.load(Ordering::Relaxed) == true {
|
|
||||||
thread::sleep(Duration::from_millis(100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transition: run signal received
|
|
||||||
if self.shared_state.paused.load(Ordering::Relaxed) == false {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Responsibility: reset private.time, shared.time, shared.cycles on state exit
|
|
||||||
self.time = Instant::now();
|
|
||||||
self.internal_state.time = Duration::new(0, 0);
|
|
||||||
self.internal_state.clock = 0;
|
|
||||||
|
|
||||||
self.internal_state.running = prior;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&mut self) -> Result<(), String> {
|
|
||||||
// wait for UI to initialise.
|
|
||||||
if self.shared_state.paused.load(Ordering::Relaxed) {
|
|
||||||
self.run_state_paused(Running)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.boot()?;
|
|
||||||
|
|
||||||
'emu: loop {
|
|
||||||
// so that it always returns zero. this is more performance friendly than checking
|
|
||||||
// for a zero register each access as it's branchless
|
|
||||||
*self.mut_reg(Register::Zero) = 0;
|
|
||||||
|
|
||||||
// IMPORTANT
|
|
||||||
// do not change anything about this loop. it's fully optimised afaik.
|
|
||||||
// Check for commands or hardware Interrupts every 32k cycles
|
|
||||||
if unlikely(self.internal_state.clock & 0x7FFF == 0) {
|
|
||||||
|
|
||||||
// Responsibility: keep the UI thread up to date periodically
|
|
||||||
// Update UI thread (roughly every 512k cycles targeting 60 UPS)
|
|
||||||
if unlikely(self.internal_state.clock & 0x7FFFF == 0) {
|
|
||||||
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
|
||||||
self.share_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transition: Paused
|
|
||||||
if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
|
|
||||||
self.run_state_paused(Running)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Responsibility: Handle interrupts
|
|
||||||
match self.interrupts.try_recv() {
|
|
||||||
Ok(int) => self.interrupt(int),
|
|
||||||
Err(TryRecvError::Disconnected) => break 'emu,
|
|
||||||
Err(TryRecvError::Empty) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Responsibility: run fetch-decode-execute cycle.
|
|
||||||
let pc = self.reg(Register::Pcx);
|
|
||||||
let instruction = self.mem_read_word(pc);
|
|
||||||
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
println!(
|
|
||||||
"Clock: {} Executing {:?} PCX: {}, reg: {:?}",
|
|
||||||
self.internal_state.clock,
|
|
||||||
Instruction(instruction),
|
|
||||||
pc,
|
|
||||||
self.internal_state.registers
|
|
||||||
);
|
|
||||||
thread::sleep(Duration::from_micros(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the previous instruction caused a fault.
|
|
||||||
if let Some(fault) = self.pending_fault {
|
|
||||||
println!("WARN fault: {:?}", fault);
|
|
||||||
self.pending_fault = None;
|
|
||||||
self.interrupt(fault);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.mut_reg(Register::Pcx).add_assign(4);
|
|
||||||
self.execute(Instruction(instruction));
|
|
||||||
|
|
||||||
// always increment clock.
|
|
||||||
self.internal_state.clock += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.internal_state.running = Shutdown;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn interrupt(&mut self, int: Interrupt) {
|
|
||||||
println!("Interrupt while at address {:04X}", self.reg(Register::Pcx));
|
|
||||||
|
|
||||||
self.push(Register::Pcx);
|
|
||||||
*self.mut_reg(Register::Pcx) =
|
|
||||||
self.mem_read_word(self.reg(Register::Idr) + int.code() as u32 * 4);
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"Jumping to interrupt at address {:04X}",
|
|
||||||
self.reg(Register::Pcx)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn push(&mut self, reg: Register) {
|
|
||||||
*self.mut_reg(Register::Spr) -= 4;
|
|
||||||
self.mem_write_word(self.reg(Register::Spr), self.reg(reg));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn pop(&mut self, reg: Register) {
|
|
||||||
*self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr));
|
|
||||||
*self.mut_reg(Register::Spr) += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn execute(&mut self, word: Instruction) {
|
|
||||||
// This needs to be unsafe as we're using word.xxxx_uc() functions for decoding which are unsafe
|
// This needs to be unsafe as we're using word.xxxx_uc() functions for decoding which are unsafe
|
||||||
// as they perform unchecked transmute operations (performance critical)
|
// as they perform unchecked transmute operations (performance critical)
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -334,8 +61,8 @@ impl Emulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// load
|
// load
|
||||||
Some(Opcode::Ldbs) => todo!(),
|
Some(Opcode::Ldbs) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
|
||||||
Some(Opcode::Ldhs) => todo!(),
|
Some(Opcode::Ldhs) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
|
||||||
Some(Opcode::Ldb) => {
|
Some(Opcode::Ldb) => {
|
||||||
*self.mut_reg(word.dest_uc()) = u32::from(
|
*self.mut_reg(word.dest_uc()) = u32::from(
|
||||||
self.mem_read_byte(self.reg(word.src1_uc()) + word.imm16() as u32),
|
self.mem_read_byte(self.reg(word.src1_uc()) + word.imm16() as u32),
|
||||||
@@ -434,9 +161,8 @@ impl Emulator {
|
|||||||
self.reg(word.dest_uc()) + word.imm16() as u32
|
self.reg(word.dest_uc()) + word.imm16() as u32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Opcode::Jnc) => todo!(),
|
Some(Opcode::Jnc) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
|
||||||
Some(Opcode::Jic) => todo!(),
|
Some(Opcode::Jic) => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
|
||||||
|
|
||||||
// Bitwise
|
// Bitwise
|
||||||
Some(Opcode::And) => {
|
Some(Opcode::And) => {
|
||||||
*self.mut_reg(word.dest_uc()) =
|
*self.mut_reg(word.dest_uc()) =
|
||||||
@@ -512,13 +238,6 @@ impl Emulator {
|
|||||||
self.pop(Register::Ret);
|
self.pop(Register::Ret);
|
||||||
*self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
|
*self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Opcode::Int) => {
|
|
||||||
self.interrupt(Interrupt::Software(word.imm16() as u8));
|
|
||||||
}
|
|
||||||
Some(Opcode::Hlt) => {
|
|
||||||
self.run_state_halted(Running);
|
|
||||||
},
|
|
||||||
Some(Opcode::IRet) => {
|
Some(Opcode::IRet) => {
|
||||||
self.pop(Register::Ret);
|
self.pop(Register::Ret);
|
||||||
*self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
|
*self.mut_reg(Register::Pcx) = self.reg(Register::Ret);
|
||||||
@@ -528,50 +247,134 @@ impl Emulator {
|
|||||||
self.reg(Register::Pcx)
|
self.reg(Register::Pcx)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
None => {}
|
Some(Opcode::Int) => return ExecutionResult::Interrupt(Interrupt::Software(word.imm16() as u8)),
|
||||||
|
Some(Opcode::Hlt) => return ExecutionResult::Halt,
|
||||||
|
None => return ExecutionResult::Interrupt(Interrupt::InvalidOpcode),
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ExecutionResult::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn interrupt(&mut self, int: Interrupt) {
|
||||||
|
self.push(Register::Pcx);
|
||||||
|
*self.mut_reg(Register::Pcx) =
|
||||||
|
self.mem_read_word(self.reg(Register::Idr) + int.code() as u32 * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub fn reg(&self, reg: Register) -> u32 {
|
||||||
|
debug_assert!((reg as u8) < Register::COUNT);
|
||||||
|
unsafe { *self.internal_state.registers.get_unchecked(reg as usize) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn mut_reg(&mut self, reg: Register) -> &mut u32 {
|
||||||
|
debug_assert!((reg as u8) < Register::COUNT);
|
||||||
|
unsafe {
|
||||||
|
self.internal_state
|
||||||
|
.registers
|
||||||
|
.get_unchecked_mut(reg as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_read_byte(&mut self, addr: u32) -> u8 {
|
pub fn push(&mut self, reg: Register) {
|
||||||
|
*self.mut_reg(Register::Spr) -= 4;
|
||||||
|
self.mem_write_word(self.reg(Register::Spr), self.reg(reg));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn pop(&mut self, reg: Register) {
|
||||||
|
*self.mut_reg(reg) = self.mem_read_word(self.reg(Register::Spr));
|
||||||
|
*self.mut_reg(Register::Spr) += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn mem_read_byte(&mut self, addr: u32) -> u8 {
|
||||||
self.mainstore.read_byte(addr)
|
self.mainstore.read_byte(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_write_byte(&mut self, addr: u32, val: u8) {
|
pub fn mem_write_byte(&mut self, addr: u32, val: u8) {
|
||||||
if addr == 0x40000 || addr == 0x40001 || addr == 0x40002 || addr == 0x40003 {
|
// TODO: how can we extract this method for debugging?
|
||||||
eprintln!("ui wrote 0x{:08x} to uart + {}", val, addr - 0x40000);
|
// if addr == 0x40000 || addr == 0x40001 || addr == 0x40002 || addr == 0x40003 {
|
||||||
|
// if addr == 0x40001 || addr == 0x40003 {
|
||||||
if addr == 0x40001 || addr == 0x40003 {
|
// eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000);
|
||||||
eprintln!("writing char {} to uart + {}", val as char, addr - 0x40000);
|
// }
|
||||||
}
|
// }
|
||||||
}
|
|
||||||
self.mainstore.write_byte(addr, val);
|
self.mainstore.write_byte(addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_read_word(&mut self, addr: u32) -> u32 {
|
pub fn mem_read_word(&mut self, addr: u32) -> u32 {
|
||||||
self.mainstore.read_word(addr)
|
self.mainstore.read_word(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_write_word(&mut self, addr: u32, val: u32) {
|
pub fn mem_write_word(&mut self, addr: u32, val: u32) {
|
||||||
self.mainstore.write_word(addr, val);
|
self.mainstore.write_word(addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_read_page(&mut self, addr: u32) -> &Page {
|
pub fn mem_read_page(&mut self, addr: u32) -> &Page {
|
||||||
self.mainstore.read_page(addr)
|
self.mainstore.read_page(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mem_write_page(&mut self, addr: u32, val: &Page) {
|
pub fn mem_write_page(&mut self, addr: u32, val: &Page) {
|
||||||
self.mainstore.write_page(addr, val);
|
self.mainstore.write_page(addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fault(&mut self, interrupt: Interrupt) {
|
#[inline]
|
||||||
self.pending_fault = Some(interrupt);
|
pub fn mmu_mut(&mut self) -> &mut MMU {
|
||||||
|
&mut self.mmu
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn memory_mut(&mut self) -> &mut MemoryBank {
|
||||||
|
&mut self.mainstore
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_memory_map(&mut self, map: MemoryMap) {
|
||||||
|
self.mmio_region = map
|
||||||
|
.regions
|
||||||
|
.iter()
|
||||||
|
.find(|r| matches!(r.region_type, RegionType::MMIO))
|
||||||
|
.map(|r| (r.base, r.base + r.size));
|
||||||
|
|
||||||
|
map.apply(self);
|
||||||
|
self.memory_map = Some(map);
|
||||||
|
self.__mmap_configured = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn boot(&mut self) -> Result<(), DsaError> {
|
||||||
|
if !(self.__mmap_configured) {
|
||||||
|
return Err(BootFailure("Emulator<Mem> not configured".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(map) = &self.memory_map {
|
||||||
|
MemoryMap::identity_map(&mut self.mmu, map.regions.get(0).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.internal_state.running = Running;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_io_mapping(&mut self, map: IoMap) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_io_map(&self) -> IoMap {
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for DsaProcessor {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// drop is only ever called once on an emulator instance, so we can be sure a double free will not happen.
|
||||||
|
unsafe { self.mainstore.dealloc() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
use crate::config::{IoMap, MemoryMap};
|
||||||
|
use crate::memory::ram::MemoryBank;
|
||||||
|
use crate::processor::dsa_core::{DsaProcessor, ExecutionResult};
|
||||||
|
use crate::processor::interrupts::Interrupt;
|
||||||
|
use crate::processor::state::RunningState::{Halted, Paused, Running, Shutdown};
|
||||||
|
use crate::processor::state::{RunningState, SharedState};
|
||||||
|
use crate::{DsaError, DsaImplementation};
|
||||||
|
use common::prelude::{Instruction, Register};
|
||||||
|
use std::hint::unlikely;
|
||||||
|
use std::ops::AddAssign;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::sync::{Arc, mpsc};
|
||||||
|
use std::sync::mpsc::TryRecvError;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
pub struct Emulator {
|
||||||
|
core: DsaProcessor,
|
||||||
|
|
||||||
|
shared_state: Arc<SharedState>,
|
||||||
|
|
||||||
|
// Interrupts
|
||||||
|
interrupts: mpsc::Receiver<Interrupt>,
|
||||||
|
pending_fault: Option<Interrupt>,
|
||||||
|
|
||||||
|
time: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DsaImplementation for Emulator {
|
||||||
|
#[cold]
|
||||||
|
fn boot(&mut self) -> Result<(), DsaError> {
|
||||||
|
self.core.boot()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self) -> Result<(), DsaError> {
|
||||||
|
// wait for UI to initialise.
|
||||||
|
if self.shared_state.paused.load(Ordering::Relaxed) {
|
||||||
|
self.run_state_paused(Running)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.boot()?;
|
||||||
|
|
||||||
|
'emu: loop {
|
||||||
|
// so that it always returns zero. this is more performance friendly than checking
|
||||||
|
// for a zero register each access as it's branchless
|
||||||
|
*self.core.mut_reg(Register::Zero) = 0;
|
||||||
|
|
||||||
|
// IMPORTANT
|
||||||
|
// do not change anything about this loop. it's fully optimised afaik.
|
||||||
|
// Check for commands or hardware Interrupts every 32k cycles
|
||||||
|
if unlikely(self.core.internal_state.clock & 0x7FFF == 0) {
|
||||||
|
|
||||||
|
// Responsibility: keep the UI thread up to date periodically
|
||||||
|
// Update UI thread (roughly every 512k cycles targeting 60 UPS)
|
||||||
|
if unlikely(self.core.internal_state.clock & 0x7FFFF == 0) {
|
||||||
|
if self.shared_state.update_req.load(Ordering::Relaxed) {
|
||||||
|
self.core.internal_state.time = self.time.elapsed();
|
||||||
|
self.share_state();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transition: Paused
|
||||||
|
if unlikely(self.shared_state.paused.load(Ordering::Relaxed)) {
|
||||||
|
self.run_state_paused(Running);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsibility: Handle interrupts
|
||||||
|
match self.interrupts.try_recv() {
|
||||||
|
Ok(int) => self.core.interrupt(int),
|
||||||
|
Err(TryRecvError::Disconnected) => break 'emu,
|
||||||
|
Err(TryRecvError::Empty) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsibility: run fetch-decode-execute cycle.
|
||||||
|
let pc = self.core.reg(Register::Pcx);
|
||||||
|
let instruction = self.core.mem_read_word(pc);
|
||||||
|
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
println!(
|
||||||
|
"Clock: {} Executing {:?} PCX: {}, reg: {:?}",
|
||||||
|
self.core.internal_state.clock,
|
||||||
|
Instruction(instruction),
|
||||||
|
pc,
|
||||||
|
self.core.internal_state.registers
|
||||||
|
);
|
||||||
|
thread::sleep(Duration::from_micros(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the previous instruction caused a fault.
|
||||||
|
if let Some(fault) = self.pending_fault {
|
||||||
|
println!("WARN fault: {:?}", fault);
|
||||||
|
self.pending_fault = None;
|
||||||
|
self.core.interrupt(fault);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.core.mut_reg(Register::Pcx).add_assign(4);
|
||||||
|
|
||||||
|
match self.core.execute(Instruction(instruction)) {
|
||||||
|
ExecutionResult::Continue => {},
|
||||||
|
ExecutionResult::Halt => self.run_state_halted(Running),
|
||||||
|
|
||||||
|
ExecutionResult::Interrupt(interrupt) => {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
println!("Interrupt: {:?}", interrupt);
|
||||||
|
println!("Interrupt while at address {:04X}", self.core.reg(Register::Pcx));
|
||||||
|
}
|
||||||
|
self.core.interrupt(interrupt);
|
||||||
|
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
println!(
|
||||||
|
"Jumping to interrupt at address {:04X}",
|
||||||
|
self.core.reg(Register::Pcx)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.core.internal_state.clock += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.core.internal_state.running = Shutdown;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_memory_map(&mut self, map: MemoryMap) {
|
||||||
|
self.core.apply_memory_map(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_io_mapping(&mut self, map: IoMap) {
|
||||||
|
self.core.apply_io_mapping(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_io_map(&self) -> IoMap {
|
||||||
|
self.core.get_io_map()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_memory_handle(&mut self) -> &mut MemoryBank {
|
||||||
|
self.core.memory_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_state_handle(&mut self) -> Arc<SharedState> {
|
||||||
|
self.shared_state.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Emulator {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (sender, receiver) = mpsc::channel::<Interrupt>();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
core: DsaProcessor::new(),
|
||||||
|
shared_state: Arc::new(SharedState::new(sender)),
|
||||||
|
|
||||||
|
interrupts: receiver,
|
||||||
|
pending_fault: None,
|
||||||
|
|
||||||
|
time: Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state_handle(&self) -> Arc<SharedState> {
|
||||||
|
self.shared_state.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn share_state(&mut self) {
|
||||||
|
self.shared_state
|
||||||
|
.proc
|
||||||
|
.store(Arc::new(self.core.internal_state.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
pub fn run_state_halted(&mut self, prior: RunningState) {
|
||||||
|
self.core.internal_state.running = Halted;
|
||||||
|
|
||||||
|
// Responsibility: keep track of the time spent in the running state across multiple hlt/interrupt cycles
|
||||||
|
self.core.internal_state.time += self.time.elapsed();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Transition: interrupt received
|
||||||
|
if let Ok(int) = self.interrupts.recv_timeout(Duration::from_millis(100)) {
|
||||||
|
self.core.internal_state.running = Running;
|
||||||
|
self.core.interrupt(int);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transition: pause signal received
|
||||||
|
if self.shared_state.paused.load(Ordering::Relaxed) {
|
||||||
|
self.run_state_paused(Halted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsibility: update UI when requested
|
||||||
|
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
|
||||||
|
self.share_state();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsibility: reset `private.time` on state exit
|
||||||
|
self.time = Instant::now();
|
||||||
|
self.core.internal_state.running = prior;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
pub fn run_state_paused(&mut self, prior: RunningState) {
|
||||||
|
self.core.internal_state.running = Paused;
|
||||||
|
|
||||||
|
// Responsibility: save the running-time since last pause state.
|
||||||
|
self.core.internal_state.time = self.time.elapsed();
|
||||||
|
self.share_state();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Responsibility: update UI when requested
|
||||||
|
if self.shared_state.update_req.load(Ordering::Relaxed) == true {
|
||||||
|
self.share_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsibility: reset when requested by the UI
|
||||||
|
if self.shared_state.reseting.load(Ordering::Relaxed) == true {
|
||||||
|
self.core.internal_state.registers = [0; Register::COUNT as usize];
|
||||||
|
self.core.internal_state.clock = 0;
|
||||||
|
|
||||||
|
while self.shared_state.reseting.load(Ordering::Relaxed) == true {
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transition: run signal received
|
||||||
|
if self.shared_state.paused.load(Ordering::Relaxed) == false {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsibility: reset private.time, shared.time, shared.cycles on state exit
|
||||||
|
self.time = Instant::now();
|
||||||
|
self.core.internal_state.time = Duration::new(0, 0);
|
||||||
|
self.core.internal_state.clock = 0;
|
||||||
|
|
||||||
|
self.core.internal_state.running = prior;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
pub mod interrupts;
|
mod state;
|
||||||
pub mod processor;
|
mod dsa_core;
|
||||||
pub mod state;
|
mod dsa_debug;
|
||||||
|
mod dsa_optimised;
|
||||||
|
mod interrupts;
|
||||||
|
|
||||||
|
pub use dsa_core::DsaProcessor;
|
||||||
|
pub use interrupts::Interrupt;
|
||||||
|
pub use dsa_optimised::Emulator;
|
||||||
|
pub use state::{
|
||||||
|
RunningState,
|
||||||
|
SharedState,
|
||||||
|
};
|
||||||
@@ -1,61 +1,50 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use common::prelude::Instruction;
|
|
||||||
use std::thread;
|
|
||||||
use dsa::args::DsaArgs;
|
use dsa::args::DsaArgs;
|
||||||
use dsa::ui::run_app;
|
use dsa::ui::run_app;
|
||||||
use emulator_core::{
|
use emulator_core::{DsaImplementation, EmulatorController, processor::Emulator};
|
||||||
memory::ram::Page,
|
|
||||||
processor::processor::Emulator
|
|
||||||
};
|
|
||||||
|
|
||||||
const STACK_SIZE: usize = 1024 * 1024 * 16;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = DsaArgs::parse();
|
let args = DsaArgs::parse();
|
||||||
|
|
||||||
let mut emulator = Emulator::new().apply_memory_map(args.get_memory_map());
|
let mut emulator = Emulator::new();
|
||||||
|
emulator.apply_memory_map(args.get_memory_map());
|
||||||
|
|
||||||
let mem_handle = emulator.memory_mut().clone();
|
let controller = EmulatorController::new(Box::new(emulator));
|
||||||
let state = emulator.state_handle();
|
|
||||||
|
|
||||||
if let Some(bin) = args.get_binary() {
|
//
|
||||||
for (i, ch) in bin.chunks(4).enumerate() {
|
// if let Some(bin) = args.get_binary() {
|
||||||
let instruction = Instruction(u32::from_le_bytes(ch.try_into().unwrap()));
|
// for (i, ch) in bin.chunks(4).enumerate() {
|
||||||
let hex = ch
|
// let instruction = Instruction(u32::from_le_bytes(ch.try_into().unwrap()));
|
||||||
.iter()
|
// let hex = ch
|
||||||
.map(|b| format!("{:02X}", b))
|
// .iter()
|
||||||
.collect::<Vec<_>>()
|
// .map(|b| format!("{:02X}", b))
|
||||||
.join(" ");
|
// .collect::<Vec<_>>()
|
||||||
let chars = ch
|
// .join(" ");
|
||||||
.iter()
|
// let chars = ch
|
||||||
.map(|b| {
|
// .iter()
|
||||||
if b.is_ascii_graphic() {
|
// .map(|b| {
|
||||||
*b as char
|
// if b.is_ascii_graphic() {
|
||||||
} else {
|
// *b as char
|
||||||
'.'
|
// } else {
|
||||||
}
|
// '.'
|
||||||
})
|
// }
|
||||||
.collect::<String>();
|
// })
|
||||||
println!("{:04X}: {:<12} {:4} {:?}", i * 4, hex, chars, instruction);
|
// .collect::<String>();
|
||||||
}
|
// println!("{:04X}: {:<12} {:4} {:?}", i * 4, hex, chars, instruction);
|
||||||
|
// }
|
||||||
let program: Vec<Page> = Page::paginate(&bin).collect();
|
//
|
||||||
|
// let program: Vec<Page> = Page::paginate(&bin).collect();
|
||||||
for (i, page) in program.iter().enumerate() {
|
//
|
||||||
emulator.memory_mut().write_page(i as u32 * 4096, &page);
|
// for (i, page) in program.iter().enumerate() {
|
||||||
}
|
// emulator.memory_mut().write_page(i as u32 * 4096, &page);
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
let _runner = thread::Builder::new()
|
|
||||||
.stack_size(STACK_SIZE)
|
|
||||||
.spawn(move || emulator.run().unwrap())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if args.headless() {
|
if args.headless() {
|
||||||
run_server(&args);
|
run_server(&args);
|
||||||
} else {
|
} else {
|
||||||
run_app(state, mem_handle).unwrap()
|
run_app(controller).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
use std::any::Any;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::BufReader;
|
|
||||||
use std::path::Path;
|
|
||||||
use egui::{ScrollArea, Ui, WidgetText};
|
|
||||||
use egui_extras::{Size, StripBuilder};
|
|
||||||
use egui_file::FileDialog;
|
|
||||||
use egui_tiles::TileId;
|
|
||||||
use common::formats::binary_dse::DseExecutable;
|
|
||||||
use disassembler::{disassemble, Disassembly};
|
|
||||||
use crate::ui::components::Component;
|
|
||||||
use crate::ui::debugger::Action;
|
|
||||||
use crate::ui::debugger::panels::TilePane;
|
|
||||||
|
|
||||||
pub struct Disassembler {
|
|
||||||
visible: bool,
|
|
||||||
dialog: Option<FileDialog>,
|
|
||||||
error: Option<String>,
|
|
||||||
|
|
||||||
program: Option<DseExecutable>,
|
|
||||||
disassembled: Option<Disassembly>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TilePane for Disassembler {
|
|
||||||
fn title(&self) -> WidgetText {
|
|
||||||
"Disassembler".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
|
||||||
Component::ui(self, ui);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Disassembler {
|
|
||||||
pub(crate) fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
visible: true,
|
|
||||||
dialog: None,
|
|
||||||
error: None,
|
|
||||||
|
|
||||||
program: None,
|
|
||||||
disassembled: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_open(path: &Path) -> Self {
|
|
||||||
let reader = File::open(path).unwrap();
|
|
||||||
let bin = DseExecutable::read(&mut BufReader::new(reader)).unwrap();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
visible: true,
|
|
||||||
dialog: None,
|
|
||||||
error: None,
|
|
||||||
program: Some(bin),
|
|
||||||
disassembled: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open(&mut self) {
|
|
||||||
let Ok(cwd) = std::env::current_dir() else {
|
|
||||||
self.error = Some("Couldn't get CWD to open file!".to_string());
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.dialog.is_none() {
|
|
||||||
let mut dialog = FileDialog::open_file();
|
|
||||||
dialog.set_path(cwd);
|
|
||||||
dialog.open();
|
|
||||||
self.dialog = Some(dialog);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_file_dialog(&mut self, ui: &mut Ui) {
|
|
||||||
|
|
||||||
let Some(dialog) = &mut self.dialog else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if !dialog.show(ui).selected() { return };
|
|
||||||
|
|
||||||
let Some(file) = dialog.path() else {
|
|
||||||
return
|
|
||||||
};
|
|
||||||
|
|
||||||
if !file.extension().is_some_and(|ext| ext == "dsb") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let reader = match File::open(file) {
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(e) => {
|
|
||||||
self.error = Some(e.to_string());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match DseExecutable::read(&mut BufReader::new(reader)) {
|
|
||||||
Ok(bin) => self.program = Some(bin),
|
|
||||||
Err(e) => {
|
|
||||||
self.error = Some(e.to_string());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.dialog = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Disassembler {
|
|
||||||
fn title(&self) -> &'static str {
|
|
||||||
"Disassembler"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visible(&mut self) -> &mut bool {
|
|
||||||
&mut self.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
|
||||||
self.handle_file_dialog(ui);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0);
|
|
||||||
ui.spacing_mut().item_spacing.x = 6.0;
|
|
||||||
|
|
||||||
// Opens a file
|
|
||||||
// ui.add_enabled(false, egui::Button::new("Open"));
|
|
||||||
if ui.button("Open").clicked() {
|
|
||||||
self.open();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
if let Some(binary) = &self.program {
|
|
||||||
if let Some(output) = &self.disassembled {
|
|
||||||
StripBuilder::new(ui)
|
|
||||||
.size(Size::exact(350.0))
|
|
||||||
.size(Size::remainder().at_most(500.0))
|
|
||||||
.horizontal(|mut strip| {
|
|
||||||
|
|
||||||
strip.cell(|ui| {
|
|
||||||
ScrollArea::vertical()
|
|
||||||
.id_salt("symbols")
|
|
||||||
.show(ui, |ui| {
|
|
||||||
egui::Grid::new("symbols_grid")
|
|
||||||
.striped(true)
|
|
||||||
.spacing([12.0, 2.0]) // horizontal, vertical spacing
|
|
||||||
.show(ui, |ui| {
|
|
||||||
// Header
|
|
||||||
ui.strong("Addr");
|
|
||||||
ui.strong("Name");
|
|
||||||
ui.strong("Section");
|
|
||||||
ui.strong("Bind");
|
|
||||||
ui.end_row();
|
|
||||||
|
|
||||||
// Rows
|
|
||||||
for symbol in &output.symbols {
|
|
||||||
ui.monospace(format!("{:#06x}", symbol.address));
|
|
||||||
ui.monospace(&symbol.name);
|
|
||||||
ui.label(format!("{:?}", symbol.section));
|
|
||||||
ui.label(format!("{:?}", symbol.binding));
|
|
||||||
|
|
||||||
ui.end_row();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
strip.cell(|ui| {
|
|
||||||
ScrollArea::vertical()
|
|
||||||
.id_salt("asm")
|
|
||||||
.show(ui, |ui| {
|
|
||||||
egui::Grid::new("asm_grid")
|
|
||||||
.striped(true)
|
|
||||||
.spacing([12.0, 2.0])
|
|
||||||
.show(ui, |ui| {
|
|
||||||
for (addr, line) in output.text_lines.iter().enumerate() {
|
|
||||||
ui.label(
|
|
||||||
egui::RichText::new(format!("{:04X}", addr))
|
|
||||||
.monospace()
|
|
||||||
.weak(),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.monospace(line);
|
|
||||||
|
|
||||||
ui.end_row();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// ui.horizontal(|ui| {
|
|
||||||
//
|
|
||||||
// egui::ScrollArea::vertical()
|
|
||||||
// .id_salt("symbols_scroll")
|
|
||||||
// .show(ui, |ui| {
|
|
||||||
//
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// ui.separator();
|
|
||||||
//
|
|
||||||
// egui::ScrollArea::vertical()
|
|
||||||
// .id_salt("asm_scroll")
|
|
||||||
// .show(ui, |ui| {
|
|
||||||
//
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
} else {
|
|
||||||
self.disassembled = Some(disassemble(&binary))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+33
-53
@@ -1,79 +1,58 @@
|
|||||||
use crate::ui::debugger::ui_helpers::{ PaddedWidget, StatusIndicator};
|
use crate::ui::ui_helpers::StatusIndicator;
|
||||||
use std::sync::{Arc, atomic::Ordering};
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
use eframe::emath::Vec2;
|
use eframe::emath::Vec2;
|
||||||
use eframe::epaint::Color32;
|
use eframe::epaint::Color32;
|
||||||
use egui::Widget;
|
|
||||||
use super::Component;
|
|
||||||
use common::prelude::Register;
|
use common::prelude::Register;
|
||||||
use emulator_core::{
|
use emulator_core::EmulatorHandle;
|
||||||
memory::ram::MemoryBank,
|
use emulator_core::processor::RunningState;
|
||||||
processor::state::SharedState
|
use crate::ui::ui_helpers::PaddedWidget;
|
||||||
};
|
|
||||||
use emulator_core::processor::state::RunningState;
|
|
||||||
|
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
state: Arc<SharedState>,
|
emulator: EmulatorHandle,
|
||||||
mem: MemoryBank,
|
|
||||||
visible: bool,
|
visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Controller {
|
impl Controller {
|
||||||
pub fn new(state: Arc<SharedState>, mem: MemoryBank) -> Self {
|
pub fn new(state: EmulatorHandle) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state,
|
emulator: state,
|
||||||
mem,
|
|
||||||
visible: false,
|
visible: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Controller {
|
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||||
fn title(&self) -> &str {
|
|
||||||
"Control Panel"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visible(&mut self) -> &mut bool {
|
self.emulator.request_update();
|
||||||
&mut self.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
|
||||||
|
|
||||||
self.state.update_req.store(true, Ordering::Relaxed);
|
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(5));
|
|
||||||
|
|
||||||
|
|
||||||
let state = self.state.proc.load();
|
|
||||||
let paused = self.state.paused.load(Ordering::Relaxed);
|
|
||||||
|
|
||||||
egui::Frame::new()
|
egui::Frame::new()
|
||||||
.inner_margin(10.0)
|
.inner_margin(10.0)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
|
||||||
|
let status = self.emulator.status();
|
||||||
let padding = Vec2::new(14.0, 8.0);
|
let padding = Vec2::new(14.0, 8.0);
|
||||||
|
|
||||||
if match state.running {
|
match status {
|
||||||
RunningState::Paused | RunningState::Halted | RunningState::Breakpoint(_)
|
RunningState::Paused | RunningState::Halted | RunningState::Breakpoint(_) => {
|
||||||
=> PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui),
|
if PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui).clicked() {
|
||||||
RunningState::Running
|
self.emulator.resume()
|
||||||
=> PaddedWidget::button("⏸ Pause").color(Color32::YELLOW).ui(ui),
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
RunningState::Running => {
|
||||||
|
if PaddedWidget::button("⏸ Pause").color(Color32::YELLOW).ui(ui).clicked() {
|
||||||
|
self.emulator.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RunningState::Shutdown => {
|
RunningState::Shutdown => {
|
||||||
ui.add_enabled_ui(false, |ui| {
|
ui.add_enabled_ui(false, |ui| {
|
||||||
PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui)
|
PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui);
|
||||||
}).response
|
});
|
||||||
},
|
}
|
||||||
}.clicked()
|
|
||||||
{
|
|
||||||
self.state.paused.store(!paused, Ordering::Relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if PaddedWidget::button("⟳ Reset All").color(Color32::YELLOW).ui(ui).clicked() {
|
if PaddedWidget::button("⟳ Reset All").color(Color32::YELLOW).ui(ui).clicked() {
|
||||||
self.state.reseting.store(true, Ordering::Relaxed);
|
self.emulator.reset();
|
||||||
self.mem.clear();
|
|
||||||
self.state.reseting.store(false, Ordering::Relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step
|
// Step
|
||||||
@@ -100,16 +79,17 @@ impl Component for Controller {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
state.running.clone().ui(ui, padding);
|
|
||||||
|
status.clone().ui(ui, padding);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
let pcx = state.registers[Register::Pcx as usize];
|
let pcx = self.emulator.get_register(Register::Pcx);
|
||||||
let clock = state.clock;
|
let clock = self.emulator.clock();
|
||||||
let time = state.time.as_micros();
|
let time = self.emulator.time().as_micros();
|
||||||
|
|
||||||
let mips = if time != 0 {
|
let mips = if time != 0 {
|
||||||
state.clock as u128 / time
|
clock as u128 / time
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::io::BufReader;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use egui_tiles::TileId;
|
|
||||||
use common::formats::binary_dse::DseExecutable;
|
|
||||||
use emulator_core::memory::ram::MemoryBank;
|
|
||||||
use emulator_core::processor::state::SharedState;
|
|
||||||
use crate::ui::components::controller::Controller;
|
|
||||||
use crate::ui::components::disassembler::Disassembler;
|
|
||||||
use crate::ui::components::memory::MemoryInspector;
|
|
||||||
use crate::ui::DsaUi;
|
|
||||||
use crate::ui::components::Component;
|
|
||||||
use dialog::confirm_dialog::ConfirmDialog;
|
|
||||||
use panels::documentation::Documentation;
|
|
||||||
use panels::editor::Editor;
|
|
||||||
use dialog::file_dialog::DsaFileDialog;
|
|
||||||
use emulator_core::memory::PhysAddr;
|
|
||||||
use panels::PanelFactory;
|
|
||||||
use crate::ui::components::display::Display;
|
|
||||||
use crate::ui::components::framebuffer::FrameBuffer;
|
|
||||||
use crate::ui::components::registers::Registers;
|
|
||||||
use crate::ui::components::serial::Serial;
|
|
||||||
use crate::ui::debugger::dialog::error_dialog::ErrorDialog;
|
|
||||||
use crate::ui::debugger::panels::{CentralTiles, TilePane};
|
|
||||||
use crate::ui::debugger::panels::about::AboutScreen;
|
|
||||||
|
|
||||||
pub mod dialog;
|
|
||||||
pub mod panels;
|
|
||||||
pub mod ui_helpers;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Action {
|
|
||||||
Quit,
|
|
||||||
OpenFile(PathBuf),
|
|
||||||
LoadBinary(PathBuf),
|
|
||||||
SaveFile(PathBuf, TileId),
|
|
||||||
RequestSaveAs(TileId),
|
|
||||||
ShowError(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EmulatorHandle {
|
|
||||||
state: Arc<SharedState>,
|
|
||||||
memory: MemoryBank,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EmulatorHandle {
|
|
||||||
pub fn new(state: Arc<SharedState>, memory: MemoryBank) -> Self {
|
|
||||||
Self { state, memory }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_bytes(&mut self, offset: PhysAddr, data: &[u8]) {
|
|
||||||
self.memory.write_bytes(offset, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub struct DebuggerUi {
|
|
||||||
emulator: EmulatorHandle,
|
|
||||||
|
|
||||||
central_tiles: CentralTiles,
|
|
||||||
|
|
||||||
controller: Controller,
|
|
||||||
confirm_dialog: ConfirmDialog<Action>,
|
|
||||||
file_dialog: DsaFileDialog<Action>,
|
|
||||||
error_dialog: ErrorDialog,
|
|
||||||
|
|
||||||
tools: Vec<PanelFactory>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DebuggerUi {
|
|
||||||
|
|
||||||
fn open_or_focus<T: TilePane + 'static>(
|
|
||||||
central_tiles: &mut CentralTiles,
|
|
||||||
slot: &mut Option<TileId>,
|
|
||||||
make_pane: impl FnOnce() -> T,
|
|
||||||
) {
|
|
||||||
match slot {
|
|
||||||
Some(tile_id) => central_tiles.activate_tab(*tile_id),
|
|
||||||
None => *slot = Some(central_tiles.add_tab(make_pane())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(
|
|
||||||
cc: &eframe::CreationContext,
|
|
||||||
state: Arc<SharedState>,
|
|
||||||
mem_handle: MemoryBank,
|
|
||||||
) -> Self {
|
|
||||||
DsaUi::configure_appearance(&cc.egui_ctx);
|
|
||||||
Self {
|
|
||||||
emulator: EmulatorHandle::new(state.clone(), mem_handle.clone()),
|
|
||||||
central_tiles: CentralTiles::new(),
|
|
||||||
controller: Controller::new(state.clone(), mem_handle.clone()),
|
|
||||||
confirm_dialog: ConfirmDialog::new(),
|
|
||||||
file_dialog: DsaFileDialog::new(),
|
|
||||||
error_dialog: ErrorDialog::default(),
|
|
||||||
|
|
||||||
tools: vec![
|
|
||||||
PanelFactory::register("Memory Inspector", {
|
|
||||||
let mem_handle = mem_handle.clone();
|
|
||||||
move || MemoryInspector::new(mem_handle.clone())
|
|
||||||
}),
|
|
||||||
PanelFactory::register("Documentation", Documentation::default),
|
|
||||||
PanelFactory::register("Display", {
|
|
||||||
let mem_handle = mem_handle.clone();
|
|
||||||
move || Display::new(80, 25, 0x20000, mem_handle.clone())
|
|
||||||
}),
|
|
||||||
PanelFactory::register("Framebuffer", {
|
|
||||||
let mem_handle = mem_handle.clone();
|
|
||||||
move || FrameBuffer::new(320, 240, 0x30000, mem_handle.clone())
|
|
||||||
}),
|
|
||||||
PanelFactory::register("Serial", {
|
|
||||||
let mem_handle = mem_handle.clone();
|
|
||||||
let state = state.clone();
|
|
||||||
move || Serial::new(0x40000, state.clone(), mem_handle.clone())
|
|
||||||
}),
|
|
||||||
PanelFactory::register("Registers", {
|
|
||||||
let state = state.clone();
|
|
||||||
move || Registers::new(state.clone())
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn perform_action(&mut self, action: Action) {
|
|
||||||
match action {
|
|
||||||
Action::Quit => std::process::exit(0),
|
|
||||||
|
|
||||||
// Opens a file (usually constructed by the file dialogue)
|
|
||||||
Action::OpenFile(path) => {
|
|
||||||
match path.extension().map(|s| s.to_str()).flatten() {
|
|
||||||
Some("dsa") => { self.central_tiles.add_tab(Editor::new_open(&path)); },
|
|
||||||
Some("dsb") => { self.central_tiles.add_tab(Disassembler::new_open(&path)); },
|
|
||||||
Some("md") => { self.central_tiles.add_tab(Documentation::open(&path)); },
|
|
||||||
Some(_) => todo!(),
|
|
||||||
None => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action::ShowError(err) => {
|
|
||||||
self.error_dialog.open(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
Action::LoadBinary(path) => {
|
|
||||||
if let Some(ext) = path.extension().map(|s| s.to_str()).flatten() && ext == "dsb" {
|
|
||||||
|
|
||||||
let Ok(file) = File::open(path) else {
|
|
||||||
self.perform_action(Action::ShowError("Failed to open file".into()));
|
|
||||||
return
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(binary) = DseExecutable::read(&mut BufReader::new(file)) else {
|
|
||||||
self.perform_action(Action::ShowError("Failed to read binary".into()));
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.emulator.write_bytes(0, &binary.to_memory_image())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action::SaveFile(path, tile) => {
|
|
||||||
println!("ACTION: saving file {} from tile {tile:?}", path.display());
|
|
||||||
|
|
||||||
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(tile) {
|
|
||||||
if let Err(e) = std::fs::write(path, editor.get_buffer().as_bytes()) {
|
|
||||||
self.perform_action(Action::ShowError(e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action::RequestSaveAs(tile) => {
|
|
||||||
self.file_dialog.open_save(move |path| Action::SaveFile(path, tile));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl eframe::App for DebuggerUi {
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
|
||||||
ui.request_repaint_after(Duration::from_millis(16));
|
|
||||||
|
|
||||||
if let Some(action) = self.confirm_dialog.show(ui) {
|
|
||||||
self.perform_action(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(action) = self.file_dialog.show(ui) {
|
|
||||||
self.perform_action(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.error_dialog.show(ui);
|
|
||||||
|
|
||||||
egui::Panel::top("toolbar").resizable(false).show(ui, |ui| {
|
|
||||||
egui::MenuBar::new().ui(ui, |ui| {
|
|
||||||
|
|
||||||
let save_enabled = self.central_tiles.active_tile()
|
|
||||||
.and_then(|id| self.central_tiles.pane_mut::<Editor>(id))
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
ui.set_height(30.0);
|
|
||||||
ui.menu_button("File", |ui| {
|
|
||||||
|
|
||||||
if ui.button("Open").clicked() {
|
|
||||||
self.file_dialog.open_pick(Action::OpenFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.button("Load Binary").clicked() {
|
|
||||||
self.file_dialog.open_pick(Action::LoadBinary);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.add_enabled(save_enabled, egui::Button::new("Save")).clicked() {
|
|
||||||
if let Some(active) = self.central_tiles.active_tile() {
|
|
||||||
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
|
||||||
match editor.filename().cloned() {
|
|
||||||
Some(path) => std::fs::write(path, editor.get_buffer().as_bytes()).unwrap(),
|
|
||||||
None => self.file_dialog.open_save(move |path| Action::SaveFile(path, active)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.add_enabled(save_enabled, egui::Button::new("Save As")).clicked() {
|
|
||||||
if let Some(active) = self.central_tiles.active_tile() {
|
|
||||||
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
|
||||||
self.file_dialog.open_save(move |path| Action::SaveFile(path, active))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.button("Exit").clicked() {
|
|
||||||
self.confirm_dialog.open("Exit","Exit Application\n(you will lose unsaved work)", Action::Quit)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.menu_button("Tools", |ui| {
|
|
||||||
for PanelFactory(name, constructor) in &self.tools {
|
|
||||||
if ui.button(name).clicked() {
|
|
||||||
self.central_tiles.add_dyn_tab(constructor());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.menu_button("Help", |ui| {
|
|
||||||
if ui.button("About").clicked() {
|
|
||||||
self.central_tiles.add_tab(AboutScreen);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
egui::Panel::top("info_panel").resizable(false).show(ui, |ui| {
|
|
||||||
self.controller.ui(ui);
|
|
||||||
});
|
|
||||||
egui::Panel::left("left_panel").resizable(false).show(ui, |ui| {});
|
|
||||||
|
|
||||||
egui::Panel::right("right_panel").resizable(false).show(ui, |ui| {});
|
|
||||||
egui::CentralPanel::default().show(ui, |ui| {
|
|
||||||
if let Some(action) = self.central_tiles.ui(ui) {
|
|
||||||
self.perform_action(action);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+5
-2
@@ -1,4 +1,5 @@
|
|||||||
use egui::{Modal, Id, Layout, Align, RichText};
|
use egui::{Modal, Id, Layout, Align, RichText};
|
||||||
|
use crate::ui::ui_helpers::PaddedWidget;
|
||||||
|
|
||||||
pub struct ConfirmDialog<T> {
|
pub struct ConfirmDialog<T> {
|
||||||
open: bool,
|
open: bool,
|
||||||
@@ -46,11 +47,13 @@ impl<T> ConfirmDialog<T> {
|
|||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||||
if ui.button(RichText::new("Confirm").strong()).clicked() {
|
|
||||||
|
if PaddedWidget::button("Confirm").ui(ui).clicked() {
|
||||||
result = Some(true);
|
result = Some(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
if ui.button("Cancel").clicked() {
|
if PaddedWidget::button("Cancel").ui(ui).clicked() {
|
||||||
result = Some(false);
|
result = Some(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
-3
@@ -1,8 +1,5 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use egui::Ui;
|
|
||||||
use egui_file_dialog::FileDialog;
|
use egui_file_dialog::FileDialog;
|
||||||
use crate::ui::debugger::Action;
|
|
||||||
|
|
||||||
|
|
||||||
pub struct DsaFileDialog<T> {
|
pub struct DsaFileDialog<T> {
|
||||||
inner: FileDialog, // or whatever backend you're using
|
inner: FileDialog, // or whatever backend you're using
|
||||||
@@ -1,127 +1,306 @@
|
|||||||
|
use std::fmt::Debug;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::Duration;
|
||||||
use eframe::NativeOptions;
|
use eframe::NativeOptions;
|
||||||
use eframe::egui;
|
use egui::{CentralPanel, Panel, Ui};
|
||||||
use std::sync::Arc;
|
use egui_tiles::TileId;
|
||||||
use std::sync::atomic::Ordering;
|
use common::formats::binary_dse::DseExecutable;
|
||||||
use egui::Order::Debug;
|
use dialog::confirm_dialog::ConfirmDialog;
|
||||||
|
use panels::documentation::Documentation;
|
||||||
|
use panels::editor::Editor;
|
||||||
|
use dialog::file_dialog::DsaFileDialog;
|
||||||
|
use emulator_core::{EmulatorController};
|
||||||
|
use panels::PanelFactory;
|
||||||
|
use crate::ui::controller::Controller;
|
||||||
|
use crate::ui::dialog::error_dialog::ErrorDialog;
|
||||||
|
use crate::ui::panels::{CentralTiles, TilePane};
|
||||||
|
use crate::ui::panels::about::AboutScreen;
|
||||||
|
use crate::ui::panels::disassembler::BinaryViewer;
|
||||||
|
use crate::ui::panels::display::Display;
|
||||||
|
use crate::ui::panels::framebuffer::FrameBuffer;
|
||||||
|
use crate::ui::panels::memory::MemoryInspector;
|
||||||
|
use crate::ui::panels::registers::Registers;
|
||||||
|
use crate::ui::panels::serial::Serial;
|
||||||
|
|
||||||
mod components;
|
mod dialog;
|
||||||
pub mod debugger;
|
mod panels;
|
||||||
|
mod ui_helpers;
|
||||||
|
mod controller;
|
||||||
|
|
||||||
use components::{
|
pub fn run_app(mut controller: EmulatorController) -> eframe::Result<()> {
|
||||||
controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer, memory::MemoryInspector,
|
// initialise emulator
|
||||||
registers::Registers, serial::Serial, Component,
|
controller.run().unwrap();
|
||||||
};
|
|
||||||
use emulator_core::{
|
|
||||||
config::VERSION,
|
|
||||||
memory::ram::MemoryBank,
|
|
||||||
processor::state::SharedState
|
|
||||||
};
|
|
||||||
use crate::ui::components::disassembler::Disassembler;
|
|
||||||
use crate::ui::debugger::DebuggerUi;
|
|
||||||
|
|
||||||
pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Result<()> {
|
|
||||||
|
|
||||||
let e = e();
|
|
||||||
|
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"Damn Simple Architecture",
|
"Damn Simple Architecture",
|
||||||
NativeOptions::default(),
|
NativeOptions::default(),
|
||||||
|
Box::new(|cc| Ok(Box::new(DamnSimpleUI::new(cc, controller)))),
|
||||||
// Box::new(|cc| Ok(Box::new(DsaUi::new(cc, state, mem_handle)))),
|
|
||||||
Box::new(|cc| Ok(Box::new(DebuggerUi::new(cc, state, mem_handle)))),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait FileFormat: Debug + Clone {
|
||||||
fn e() -> i32 {
|
fn extension(&self) -> &'static str;
|
||||||
return 5
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for DsaUi {
|
impl FileFormat for DseExecutable {
|
||||||
|
fn extension(&self) -> &'static str {
|
||||||
|
"dse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum DataSource {
|
||||||
|
File(PathBuf),
|
||||||
|
Data(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Action {
|
||||||
|
RequestCloseTab(TileId),
|
||||||
|
CloseTab(TileId),
|
||||||
|
Quit,
|
||||||
|
OpenFile(PathBuf),
|
||||||
|
// OpenCachedFile(Box<dyn FileFormat>),
|
||||||
|
LoadBinary(DataSource),
|
||||||
|
SaveFile(PathBuf, TileId),
|
||||||
|
RequestSaveAs(TileId),
|
||||||
|
ShowError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct DamnSimpleUI {
|
||||||
|
emulator: EmulatorController,
|
||||||
|
central_tiles: CentralTiles,
|
||||||
|
|
||||||
|
controller: Controller,
|
||||||
|
confirm_dialog: ConfirmDialog<Action>,
|
||||||
|
file_dialog: DsaFileDialog<Action>,
|
||||||
|
error_dialog: ErrorDialog,
|
||||||
|
|
||||||
|
tools: Vec<PanelFactory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl eframe::App for DamnSimpleUI {
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||||
// request an update from ui every cycle
|
ui.request_repaint_after(Duration::from_millis(16));
|
||||||
self.state.update_req.store(true, Ordering::Relaxed);
|
|
||||||
|
|
||||||
// title panel with dsa title
|
if let Some(action) = self.confirm_dialog.show(ui) {
|
||||||
egui::containers::Panel::top("top_panel").show(ui, |ui| {
|
self.perform_action(action);
|
||||||
ui.with_layout(
|
}
|
||||||
egui::Layout::top_down_justified(egui::Align::Center)
|
|
||||||
.with_main_align(egui::Align::Min),
|
if let Some(action) = self.file_dialog.show(ui) {
|
||||||
|ui| {
|
self.perform_action(action);
|
||||||
ui.allocate_space(egui::vec2(0.0, 5.0));
|
}
|
||||||
ui.heading(format!("Damn Simple Architecture v{} 🚀", VERSION));
|
|
||||||
ui.allocate_space(egui::vec2(0.0, 5.0));
|
self.error_dialog.show(ui);
|
||||||
},
|
|
||||||
);
|
Panel::top("menu_bar").resizable(false).show(ui, |ui| {
|
||||||
|
self.toolbar(ui);
|
||||||
});
|
});
|
||||||
|
|
||||||
// menu bar.
|
Panel::top("control_panel").resizable(false).show(ui, |ui| {
|
||||||
egui::Panel::top("menu_bar").show(ui, |ui| {
|
self.controller.ui(ui);
|
||||||
egui::MenuBar::new().ui(ui, |ui| {
|
});
|
||||||
ui.menu_button("Panels", |ui| {
|
|
||||||
ui.set_max_width(300.0);
|
|
||||||
ui.set_min_width(300.0);
|
|
||||||
ui.spacing_mut().button_padding = egui::vec2(10.0, 5.0);
|
|
||||||
|
|
||||||
for comp in &mut self.components {
|
Panel::left("left_panel").resizable(false).show(ui, |ui| {
|
||||||
let name = comp.title().to_string();
|
// TODO: File Explorer!
|
||||||
ui.toggle_value(comp.visible(), name);
|
});
|
||||||
|
|
||||||
|
Panel::bottom("bottom_panel").resizable(false).show(ui, |ui| {
|
||||||
|
// self.process_manager.ui(ui);
|
||||||
|
});
|
||||||
|
|
||||||
|
CentralPanel::default().show(ui, |ui| {
|
||||||
|
if let Some(action) = self.central_tiles.ui(ui) {
|
||||||
|
self.perform_action(action);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ui, |ui| {
|
|
||||||
for c in &mut self.components {
|
|
||||||
let mut visible = *c.visible();
|
|
||||||
if visible {
|
|
||||||
egui::Window::new(c.title())
|
|
||||||
.open(&mut visible)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
c.ui(ui);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
*c.visible() = visible;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.request_repaint_after(std::time::Duration::from_millis(16)); // ~60fps
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DsaUi {
|
|
||||||
state: Arc<SharedState>,
|
|
||||||
mem_handle: MemoryBank,
|
|
||||||
components: Vec<Box<dyn Component>>,
|
impl DamnSimpleUI {
|
||||||
|
|
||||||
|
fn open_or_focus<T: TilePane + 'static>(
|
||||||
|
central_tiles: &mut CentralTiles,
|
||||||
|
slot: &mut Option<TileId>,
|
||||||
|
make_pane: impl FnOnce() -> T,
|
||||||
|
) {
|
||||||
|
match slot {
|
||||||
|
Some(tile_id) => central_tiles.activate_tab(*tile_id),
|
||||||
|
None => *slot = Some(central_tiles.add_tab(make_pane())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DsaUi {
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cc: &eframe::CreationContext,
|
cc: &eframe::CreationContext,
|
||||||
state: Arc<SharedState>,
|
mut emulator: EmulatorController,
|
||||||
mem_handle: MemoryBank,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// components
|
configure_appearance(&cc.egui_ctx);
|
||||||
let mut components: Vec<Box<dyn Component>> = vec![
|
let handle = emulator.get_handle().clone();
|
||||||
Box::new(Controller::new(state.clone(), mem_handle.clone())),
|
Self {
|
||||||
Box::new(Display::new(80, 25, 0x20000, mem_handle.clone())),
|
central_tiles: CentralTiles::new(),
|
||||||
Box::new(FrameBuffer::new(320, 240, 0x30000, mem_handle.clone())),
|
controller: Controller::new(handle.clone()),
|
||||||
Box::new(Registers::new(state.clone())),
|
confirm_dialog: ConfirmDialog::new(),
|
||||||
Box::new(Serial::new(0x40000, state.clone(), mem_handle.clone())),
|
file_dialog: DsaFileDialog::new(),
|
||||||
Box::new(Editor::new(state.clone(), mem_handle.clone())),
|
error_dialog: ErrorDialog::default(),
|
||||||
Box::new(MemoryInspector::new(mem_handle.clone())),
|
|
||||||
Box::new(Disassembler::new())
|
|
||||||
];
|
|
||||||
components.iter_mut().for_each(|c| *c.visible() = false);
|
|
||||||
|
|
||||||
Self::configure_appearance(&cc.egui_ctx);
|
tools: vec![
|
||||||
let app = Self {
|
PanelFactory::register("Memory Inspector", {
|
||||||
state,
|
let handle = handle.clone();
|
||||||
mem_handle,
|
move || MemoryInspector::new(handle.clone())
|
||||||
components,
|
}),
|
||||||
|
PanelFactory::register("Documentation", Documentation::default),
|
||||||
|
PanelFactory::register("Display", {
|
||||||
|
let handle = handle.clone();
|
||||||
|
move || Display::new(80, 25, 0x20000, handle.clone())
|
||||||
|
}),
|
||||||
|
PanelFactory::register("Framebuffer", {
|
||||||
|
let handle = handle.clone();
|
||||||
|
move || FrameBuffer::new(320, 240, 0x30000, handle.clone())
|
||||||
|
}),
|
||||||
|
PanelFactory::register("Serial", {
|
||||||
|
let handle = handle.clone();
|
||||||
|
move || Serial::new(0x40000, handle.clone())
|
||||||
|
}),
|
||||||
|
PanelFactory::register("Registers", {
|
||||||
|
let handle = handle.clone();
|
||||||
|
move || Registers::new(handle.clone())
|
||||||
|
})
|
||||||
|
],
|
||||||
|
emulator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn perform_action(&mut self, action: Action) {
|
||||||
|
match action {
|
||||||
|
|
||||||
|
Action::RequestCloseTab(tile_id) => {
|
||||||
|
self.confirm_dialog.open(
|
||||||
|
"Close Tab",
|
||||||
|
"Closing this tab may result in losing unsaved work",
|
||||||
|
Action::CloseTab(tile_id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action::CloseTab(tile_id) => {
|
||||||
|
self.central_tiles.close_tab(tile_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action::Quit => std::process::exit(0),
|
||||||
|
|
||||||
|
// Opens a file (usually constructed by the file dialogue)
|
||||||
|
Action::OpenFile(path) => {
|
||||||
|
match path.extension().map(|s| s.to_str()).flatten() {
|
||||||
|
Some("dsa" | "dsc") => { self.central_tiles.add_tab(Editor::new_open(&path)); },
|
||||||
|
Some("dsb") => { self.central_tiles.add_tab(BinaryViewer::new_open(&path)); },
|
||||||
|
Some("md") => { self.central_tiles.add_tab(Documentation::open(&path)); },
|
||||||
|
Some(_) => todo!(),
|
||||||
|
None => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Action::ShowError(err) => {
|
||||||
|
self.error_dialog.open(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action::LoadBinary(DataSource::Data(exe)) => {
|
||||||
|
self.emulator.get_handle().mem_write(0, &exe);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action::LoadBinary(DataSource::File(path)) => {
|
||||||
|
if let Some(ext) = path.extension().map(|s| s.to_str()).flatten() && ext == "dsb" {
|
||||||
|
|
||||||
|
let Ok(file) = File::open(path) else {
|
||||||
|
self.perform_action(Action::ShowError("Failed to open file".into()));
|
||||||
|
return
|
||||||
};
|
};
|
||||||
app
|
|
||||||
|
let Ok(binary) = DseExecutable::read(&mut BufReader::new(file)) else {
|
||||||
|
self.perform_action(Action::ShowError("Failed to read binary".into()));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.emulator.get_handle().mem_write(0, &binary.to_memory_image())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Action::SaveFile(path, tile) => {
|
||||||
|
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(tile) {
|
||||||
|
if let Err(e) = std::fs::write(path, editor.get_buffer().as_bytes()) {
|
||||||
|
self.perform_action(Action::ShowError(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Action::RequestSaveAs(tile) => {
|
||||||
|
self.file_dialog.open_save(move |path| Action::SaveFile(path, tile));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toolbar(&mut self, ui: &mut Ui) {
|
||||||
|
egui::MenuBar::new().ui(ui, |ui| {
|
||||||
|
|
||||||
|
let save_enabled = self.central_tiles.active_tile()
|
||||||
|
.and_then(|id| self.central_tiles.pane_mut::<Editor>(id))
|
||||||
|
.is_some();
|
||||||
|
|
||||||
|
ui.set_height(30.0);
|
||||||
|
ui.menu_button("File", |ui| {
|
||||||
|
|
||||||
|
if ui.button("Open").clicked() {
|
||||||
|
self.file_dialog.open_pick(Action::OpenFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Load Binary").clicked() {
|
||||||
|
self.file_dialog.open_pick(|path| Action::LoadBinary(DataSource::File(path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.add_enabled(save_enabled, egui::Button::new("Save")).clicked() {
|
||||||
|
if let Some(active) = self.central_tiles.active_tile() {
|
||||||
|
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
||||||
|
match editor.filename().cloned() {
|
||||||
|
Some(path) => std::fs::write(path, editor.get_buffer().as_bytes()).unwrap(),
|
||||||
|
None => self.file_dialog.open_save(move |path| Action::SaveFile(path, active)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.add_enabled(save_enabled, egui::Button::new("Save As")).clicked() {
|
||||||
|
if let Some(active) = self.central_tiles.active_tile() {
|
||||||
|
if let Some(editor) = self.central_tiles.pane_mut::<Editor>(active) {
|
||||||
|
self.file_dialog.open_save(move |path| Action::SaveFile(path, active))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Exit").clicked() {
|
||||||
|
self.confirm_dialog.open("Exit","Exit Application\n(you will lose unsaved work)", Action::Quit)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.menu_button("Tools", |ui| {
|
||||||
|
for PanelFactory(name, constructor) in &self.tools {
|
||||||
|
if ui.button(name).clicked() {
|
||||||
|
self.central_tiles.add_dyn_tab(constructor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.menu_button("Help", |ui| {
|
||||||
|
if ui.button("About").clicked() {
|
||||||
|
self.central_tiles.add_tab(AboutScreen);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configure_appearance(ctx: &egui::Context) {
|
pub fn configure_appearance(ctx: &egui::Context) {
|
||||||
@@ -168,4 +347,3 @@ impl DsaUi {
|
|||||||
|
|
||||||
ctx.set_fonts(fonts);
|
ctx.set_fonts(fonts);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
+5
-8
@@ -1,13 +1,10 @@
|
|||||||
use std::default::Default;
|
use crate::ui::Action;
|
||||||
use std::any::Any;
|
use crate::ui::panels::TilePane;
|
||||||
use std::path::Path;
|
|
||||||
use eframe::epaint::Direction;
|
use eframe::epaint::Direction;
|
||||||
use egui::{Ui, Widget, WidgetText};
|
use egui::{Ui, WidgetText};
|
||||||
use egui_commonmark::{CommonMarkCache, CommonMarkViewer};
|
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
use emulator_core::config::{VERSION};
|
use emulator_core::config::VERSION;
|
||||||
use crate::ui::debugger::Action;
|
use std::any::Any;
|
||||||
use crate::ui::debugger::panels::TilePane;
|
|
||||||
|
|
||||||
pub struct AboutScreen;
|
pub struct AboutScreen;
|
||||||
|
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
use std::any::Any;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use egui::{ScrollArea, Ui, WidgetText};
|
||||||
|
use egui_extras::{Size, StripBuilder};
|
||||||
|
use egui_file::FileDialog;
|
||||||
|
use egui_tiles::TileId;
|
||||||
|
|
||||||
|
use common::formats::binary_dse::DseExecutable;
|
||||||
|
use disassembler::{disassemble, Disassembly};
|
||||||
|
|
||||||
|
use crate::ui::panels::TilePane;
|
||||||
|
use crate::ui::{Action, DataSource};
|
||||||
|
use crate::ui::panels::memory::MemoryViewer;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
|
enum ViewMode {
|
||||||
|
Memory,
|
||||||
|
Disassembly,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DisassemblyState {
|
||||||
|
Idle,
|
||||||
|
Running(mpsc::Receiver<Disassembly>),
|
||||||
|
Done(Disassembly),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BinaryViewer {
|
||||||
|
dialog: Option<FileDialog>,
|
||||||
|
error: Option<String>,
|
||||||
|
|
||||||
|
program: Option<Arc<DseExecutable>>,
|
||||||
|
memory_image: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
view: ViewMode,
|
||||||
|
memory_viewer: MemoryViewer,
|
||||||
|
disassembly: DisassemblyState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinaryViewer {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
dialog: None,
|
||||||
|
error: None,
|
||||||
|
program: None,
|
||||||
|
memory_image: None,
|
||||||
|
view: ViewMode::Memory,
|
||||||
|
memory_viewer: MemoryViewer::new(),
|
||||||
|
disassembly: DisassemblyState::Idle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_open(path: &Path) -> Self {
|
||||||
|
let mut viewer = Self::new();
|
||||||
|
match File::open(path).map(|f| DseExecutable::read(&mut BufReader::new(f))) {
|
||||||
|
Ok(Ok(bin)) => viewer.load_program(bin),
|
||||||
|
Ok(Err(e)) => viewer.error = Some(format!("Failed to load {}: {e}", path.display())),
|
||||||
|
Err(e) => viewer.error = Some(format!("Failed to load {}: {e}", path.display())),
|
||||||
|
}
|
||||||
|
viewer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_program(&mut self, bin: DseExecutable) {
|
||||||
|
// TODO: replace with however DseExecutable actually exposes its raw memory image.
|
||||||
|
// Assuming a method like this exists — swap for the real accessor.
|
||||||
|
self.memory_image = Some(bin.to_memory_image());
|
||||||
|
self.program = Some(Arc::new(bin));
|
||||||
|
self.disassembly = DisassemblyState::Idle;
|
||||||
|
self.view = ViewMode::Memory;
|
||||||
|
self.error = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open(&mut self) {
|
||||||
|
let Ok(cwd) = std::env::current_dir() else {
|
||||||
|
self.error = Some("Couldn't get CWD to open file!".to_string());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.dialog.is_none() {
|
||||||
|
let mut dialog = FileDialog::open_file();
|
||||||
|
dialog.set_path(cwd);
|
||||||
|
dialog.open();
|
||||||
|
self.dialog = Some(dialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_file_dialog(&mut self, ui: &mut Ui) {
|
||||||
|
let Some(dialog) = &mut self.dialog else { return };
|
||||||
|
if !dialog.show(ui).selected() { return }
|
||||||
|
|
||||||
|
let Some(file) = dialog.path() else { return };
|
||||||
|
let file = file.to_path_buf();
|
||||||
|
self.dialog = None;
|
||||||
|
|
||||||
|
if !file.extension().is_some_and(|ext| ext == "dsb") {
|
||||||
|
self.error = Some("Selected file is not a .dsb binary".into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reader = match File::open(&file) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => { self.error = Some(e.to_string()); return; }
|
||||||
|
};
|
||||||
|
|
||||||
|
match DseExecutable::read(&mut BufReader::new(reader)) {
|
||||||
|
Ok(bin) => self.load_program(bin),
|
||||||
|
Err(e) => self.error = Some(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kicks disassembly off on a background thread so a large binary
|
||||||
|
/// doesn't stall the UI. `DseExecutable` is wrapped in `Arc` so the
|
||||||
|
/// thread can borrow it without cloning the whole binary image.
|
||||||
|
fn start_disassembly(&mut self) {
|
||||||
|
let Some(program) = self.program.clone() else { return };
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let result = disassemble(&program);
|
||||||
|
let _ = tx.send(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.disassembly = DisassemblyState::Running(rx);
|
||||||
|
self.view = ViewMode::Disassembly;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_disassembly(&mut self) {
|
||||||
|
if let DisassemblyState::Running(rx) = &self.disassembly {
|
||||||
|
if let Ok(result) = rx.try_recv() {
|
||||||
|
self.disassembly = DisassemblyState::Done(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toolbar(&mut self, ui: &mut Ui) -> Option<Action> {
|
||||||
|
let mut action = None;
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().button_padding = egui::vec2(8.0, 4.0);
|
||||||
|
ui.spacing_mut().item_spacing.x = 6.0;
|
||||||
|
|
||||||
|
if ui.button("Open").clicked() {
|
||||||
|
self.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_program = self.program.is_some();
|
||||||
|
|
||||||
|
if ui.add_enabled(has_program, egui::Button::new("Load")).clicked() {
|
||||||
|
if let Some(data) = &self.memory_image {
|
||||||
|
action = Some(Action::LoadBinary(DataSource::Data(data.clone())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let disassembling = matches!(self.disassembly, DisassemblyState::Running(_));
|
||||||
|
if ui
|
||||||
|
.add_enabled(has_program && !disassembling, egui::Button::new("Disassemble"))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.start_disassembly();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
ui.selectable_value(&mut self.view, ViewMode::Memory, "Memory");
|
||||||
|
let has_disassembly = !matches!(self.disassembly, DisassemblyState::Idle);
|
||||||
|
ui.add_enabled_ui(has_disassembly, |ui| {
|
||||||
|
ui.selectable_value(&mut self.view, ViewMode::Disassembly, "Disassembly");
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(err) = &self.error {
|
||||||
|
ui.separator();
|
||||||
|
ui.colored_label(egui::Color32::from_rgb(220, 80, 80), err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TilePane for BinaryViewer {
|
||||||
|
fn title(&self) -> WidgetText {
|
||||||
|
"Binary Viewer".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||||
|
self.handle_file_dialog(ui);
|
||||||
|
self.poll_disassembly();
|
||||||
|
|
||||||
|
if matches!(self.disassembly, DisassemblyState::Running(_)) {
|
||||||
|
ui.ctx().request_repaint(); // keep polling promptly while the background thread works
|
||||||
|
}
|
||||||
|
|
||||||
|
let action = self.toolbar(ui);
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
match self.view {
|
||||||
|
ViewMode::Memory => match &self.memory_image {
|
||||||
|
Some(memory) => self.memory_viewer.ui(ui, memory, 0),
|
||||||
|
None => {
|
||||||
|
ui.centered_and_justified(|ui| {
|
||||||
|
ui.weak("Open a binary to inspect its memory image.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ViewMode::Disassembly => match &self.disassembly {
|
||||||
|
DisassemblyState::Idle => {
|
||||||
|
ui.centered_and_justified(|ui| {
|
||||||
|
ui.weak("Click Disassemble to generate a disassembly.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
DisassemblyState::Running(_) => {
|
||||||
|
ui.centered_and_justified(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.spinner();
|
||||||
|
ui.label("Disassembling…");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
DisassemblyState::Done(output) => render_disassembly(ui, output),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
action
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_disassembly(ui: &mut Ui, output: &Disassembly) {
|
||||||
|
StripBuilder::new(ui)
|
||||||
|
.size(Size::exact(350.0))
|
||||||
|
.size(Size::remainder().at_most(500.0))
|
||||||
|
.horizontal(|mut strip| {
|
||||||
|
strip.cell(|ui| {
|
||||||
|
ScrollArea::vertical().id_salt("symbols").show(ui, |ui| {
|
||||||
|
egui::Grid::new("symbols_grid")
|
||||||
|
.striped(true)
|
||||||
|
.spacing([12.0, 2.0])
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.strong("Addr");
|
||||||
|
ui.strong("Name");
|
||||||
|
ui.strong("Section");
|
||||||
|
ui.strong("Bind");
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
for symbol in &output.symbols {
|
||||||
|
ui.monospace(format!("{:#06x}", symbol.address));
|
||||||
|
ui.monospace(&symbol.name);
|
||||||
|
ui.label(format!("{:?}", symbol.section));
|
||||||
|
ui.label(format!("{:?}", symbol.binding));
|
||||||
|
ui.end_row();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
strip.cell(|ui| {
|
||||||
|
ScrollArea::vertical().id_salt("asm").show(ui, |ui| {
|
||||||
|
egui::Grid::new("asm_grid")
|
||||||
|
.striped(true)
|
||||||
|
.spacing([12.0, 2.0])
|
||||||
|
.show(ui, |ui| {
|
||||||
|
for (addr, line) in output.text_lines.iter().enumerate() {
|
||||||
|
ui.label(
|
||||||
|
egui::RichText::new(format!("{:04X}", addr))
|
||||||
|
.monospace()
|
||||||
|
.weak(),
|
||||||
|
);
|
||||||
|
ui.monospace(line);
|
||||||
|
ui.end_row();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
+19
-54
@@ -1,62 +1,41 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use egui::{Color32, FontId, Ui, Vec2, WidgetText};
|
use egui::{Color32, FontId, Ui, Vec2, WidgetText};
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
use emulator_core::{
|
use emulator_core::{memory::ram::{MemoryBank, Page}, memory::PhysAddr, EmulatorHandle};
|
||||||
memory::PhysAddr,
|
use crate::ui::Action;
|
||||||
memory::ram::{MemoryBank, Page}
|
use crate::ui::panels::TilePane;
|
||||||
};
|
|
||||||
use crate::ui::debugger::Action;
|
|
||||||
use crate::ui::debugger::panels::TilePane;
|
|
||||||
use super::Component;
|
|
||||||
|
|
||||||
pub struct Display {
|
pub struct Display {
|
||||||
width: usize,
|
width: u32,
|
||||||
height: usize,
|
height: u32,
|
||||||
addr: PhysAddr,
|
addr: PhysAddr,
|
||||||
|
|
||||||
mem: MemoryBank,
|
emulator: EmulatorHandle,
|
||||||
buffer: Vec<u8>,
|
buffer: Vec<u8>,
|
||||||
visible: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display {
|
impl Display {
|
||||||
pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self {
|
pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self {
|
||||||
Self {
|
Self {
|
||||||
width: width as usize,
|
width,
|
||||||
height: height as usize,
|
height,
|
||||||
addr,
|
addr,
|
||||||
mem,
|
emulator,
|
||||||
buffer: Vec::new(),
|
buffer: Vec::new(),
|
||||||
visible: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visible(&mut self) -> &mut bool {
|
|
||||||
&mut self.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn width(&self) -> usize {
|
pub fn width(&self) -> usize {
|
||||||
self.width
|
self.width as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn height(&self) -> usize {
|
pub fn height(&self) -> usize {
|
||||||
self.height
|
self.height as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
fn internal_read(&self) -> Vec<u8> {
|
|
||||||
let pages = (0..((self.width * self.height).div_ceil(Page::SIZE)))
|
|
||||||
.map(|i| {
|
|
||||||
let page = self.mem.read_page(self.addr + i as u32 * 4096);
|
|
||||||
page.as_slice().to_owned()
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.take((self.width * self.height) as usize)
|
|
||||||
.collect::<Vec<u8>>();
|
|
||||||
pages
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn changed(&mut self) -> bool {
|
pub fn changed(&mut self) -> bool {
|
||||||
let temp = self.internal_read();
|
let temp = self.read();
|
||||||
if temp != self.buffer {
|
if temp != self.buffer {
|
||||||
self.buffer = temp;
|
self.buffer = temp;
|
||||||
return true;
|
return true;
|
||||||
@@ -65,7 +44,7 @@ impl Display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(&mut self) -> Vec<u8> {
|
pub fn read(&mut self) -> Vec<u8> {
|
||||||
self.internal_read()
|
self.emulator.mem_read(self.addr, self.width * self.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,25 +55,6 @@ impl TilePane for Display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||||
Component::ui(self, ui);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Display {
|
|
||||||
fn title(&self) -> &str {
|
|
||||||
"Display"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visible(&mut self) -> &mut bool {
|
|
||||||
self.visible()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
|
||||||
let display_w = self.width();
|
let display_w = self.width();
|
||||||
let display_h = self.height();
|
let display_h = self.height();
|
||||||
let data = self.read();
|
let data = self.read();
|
||||||
@@ -143,5 +103,10 @@ impl Component for Display {
|
|||||||
Color32::WHITE,
|
Color32::WHITE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -4,8 +4,8 @@ use std::path::Path;
|
|||||||
use egui::{Ui, Widget, WidgetText};
|
use egui::{Ui, Widget, WidgetText};
|
||||||
use egui_commonmark::{CommonMarkCache, CommonMarkViewer};
|
use egui_commonmark::{CommonMarkCache, CommonMarkViewer};
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
use crate::ui::debugger::Action;
|
use crate::ui::Action;
|
||||||
use crate::ui::debugger::panels::TilePane;
|
use crate::ui::panels::TilePane;
|
||||||
|
|
||||||
pub struct Documentation {
|
pub struct Documentation {
|
||||||
buffer: Option<String>,
|
buffer: Option<String>,
|
||||||
+13
-14
@@ -1,22 +1,18 @@
|
|||||||
|
use crate::ui::Action;
|
||||||
|
use crate::ui::panels::TilePane;
|
||||||
|
use egui::{Key, Ui};
|
||||||
|
use egui_code_editor::CodeEditor;
|
||||||
|
use egui_tiles::TileId;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use egui::accesskit::Role::Code;
|
use theme::THEME;
|
||||||
use egui::{Key, Ui};
|
mod syntax;
|
||||||
use egui_code_editor::{CodeEditor, Syntax};
|
mod theme;
|
||||||
use egui_file_dialog::FileDialog;
|
|
||||||
use egui_tiles::TileId;
|
|
||||||
use crate::ui::components::Component;
|
|
||||||
use crate::ui::components::editor::{syntax, THEME};
|
|
||||||
use crate::ui::debugger::Action;
|
|
||||||
use crate::ui::debugger::panels::TilePane;
|
|
||||||
|
|
||||||
pub struct Editor {
|
pub struct Editor {
|
||||||
buffer: String,
|
buffer: String,
|
||||||
filename: Option<PathBuf>,
|
filename: Option<PathBuf>,
|
||||||
|
|
||||||
editor: CodeEditor,
|
editor: CodeEditor,
|
||||||
dialog: FileDialog,
|
|
||||||
visible: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Editor {
|
impl Default for Editor {
|
||||||
@@ -25,8 +21,6 @@ impl Default for Editor {
|
|||||||
buffer: String::new(),
|
buffer: String::new(),
|
||||||
filename: None,
|
filename: None,
|
||||||
editor: CodeEditor::default().with_fontsize(12.0).with_theme(THEME).with_numlines(true),
|
editor: CodeEditor::default().with_fontsize(12.0).with_theme(THEME).with_numlines(true),
|
||||||
dialog: FileDialog::new(),
|
|
||||||
visible: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,4 +80,9 @@ impl TilePane for Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any { self }
|
fn as_any_mut(&mut self) -> &mut dyn Any { self }
|
||||||
|
|
||||||
|
fn close_requires_confirmation(&mut self, _tile_id: TileId) -> bool {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
-1
@@ -1,7 +1,6 @@
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use egui_code_editor::{Patch, Syntax};
|
use egui_code_editor::{Patch, Syntax};
|
||||||
|
|
||||||
pub fn dsa() -> Syntax {
|
pub fn dsa() -> Syntax {
|
||||||
Syntax {
|
Syntax {
|
||||||
word_start: BTreeSet::new(),
|
word_start: BTreeSet::new(),
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
use egui_code_editor::ColorTheme;
|
||||||
|
|
||||||
|
pub const THEME: ColorTheme = ColorTheme {
|
||||||
|
name: "Theme",
|
||||||
|
dark: true,
|
||||||
|
bg: "1b1b1b",
|
||||||
|
cursor: "de5252", // fg4
|
||||||
|
selection: "28323B", // bg2
|
||||||
|
comments: "444444", // gray1
|
||||||
|
functions: "7CCCC7", // green1
|
||||||
|
keywords: "6C81E0", // red1
|
||||||
|
literals: "A3ABFF", // fg1
|
||||||
|
numerics: "8A46CF", // purple1
|
||||||
|
punctuation: "99C9C9", // orange1
|
||||||
|
strs: "618c84", // aqua1
|
||||||
|
types: "B8B9D4", // yellow1
|
||||||
|
special: "de5252", // blue1
|
||||||
|
};
|
||||||
+20
-54
@@ -1,35 +1,30 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use egui::{Ui, Vec2, WidgetText};
|
use egui::{Ui, Vec2, WidgetText};
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
use emulator_core::{
|
use emulator_core::{memory::PhysAddr, EmulatorHandle};
|
||||||
memory::PhysAddr,
|
use crate::ui::Action;
|
||||||
memory::ram::{MemoryBank, Page}
|
use crate::ui::panels::TilePane;
|
||||||
};
|
|
||||||
use crate::ui::debugger::Action;
|
|
||||||
use crate::ui::debugger::panels::TilePane;
|
|
||||||
use super::Component;
|
|
||||||
|
|
||||||
pub struct FrameBuffer {
|
pub struct FrameBuffer {
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
addr: PhysAddr,
|
addr: PhysAddr,
|
||||||
mem: MemoryBank,
|
emulator: EmulatorHandle,
|
||||||
pub buffer: Vec<u32>, // RGBA pixels
|
pub buffer: Vec<u8>,
|
||||||
visible: bool,
|
|
||||||
|
|
||||||
texture: Option<egui::TextureHandle>,
|
texture: Option<egui::TextureHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrameBuffer {
|
impl FrameBuffer {
|
||||||
pub fn new(width: u32, height: u32, addr: PhysAddr, mem: MemoryBank) -> Self {
|
pub fn new(width: u32, height: u32, addr: PhysAddr, emulator: EmulatorHandle) -> Self {
|
||||||
|
let width = width as usize;
|
||||||
|
let height = height as usize;
|
||||||
Self {
|
Self {
|
||||||
width: width as usize,
|
width,
|
||||||
height: height as usize,
|
height,
|
||||||
addr,
|
addr,
|
||||||
mem,
|
emulator,
|
||||||
buffer: vec![0u32; width as usize * height as usize],
|
buffer: vec![0; width * height * 4],
|
||||||
visible: true,
|
|
||||||
|
|
||||||
texture: None,
|
texture: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,25 +35,10 @@ impl FrameBuffer {
|
|||||||
pub fn height(&self) -> usize {
|
pub fn height(&self) -> usize {
|
||||||
self.height
|
self.height
|
||||||
}
|
}
|
||||||
pub fn visible(&mut self) -> &mut bool {
|
|
||||||
&mut self.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
fn internal_read(&self) -> Vec<u32> {
|
fn internal_read(&mut self) -> Vec<u8> {
|
||||||
let byte_size = self.width * self.height * 4;
|
let byte_size = self.width * self.height * 4;
|
||||||
let page_count = byte_size.div_ceil(Page::SIZE);
|
self.emulator.mem_read(self.addr, byte_size as u32)
|
||||||
|
|
||||||
(0..page_count)
|
|
||||||
.map(|i| {
|
|
||||||
let page = self.mem.read_page(self.addr + i as u32 * 4096);
|
|
||||||
page.as_slice().to_owned()
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.take(byte_size)
|
|
||||||
.collect::<Vec<u8>>()
|
|
||||||
.chunks_exact(4)
|
|
||||||
.map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap()))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn changed(&mut self) -> bool {
|
pub fn changed(&mut self) -> bool {
|
||||||
@@ -70,7 +50,7 @@ impl FrameBuffer {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(&self) -> &[u32] {
|
pub fn read(&self) -> &[u8] {
|
||||||
&self.buffer
|
&self.buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,27 +67,12 @@ impl TilePane for FrameBuffer {
|
|||||||
fn title(&self) -> WidgetText {
|
fn title(&self) -> WidgetText {
|
||||||
"Framebuffer".into()
|
"Framebuffer".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
|
||||||
Component::ui(self, ui);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for FrameBuffer {
|
|
||||||
fn title(&self) -> &str {
|
|
||||||
"Framebuffer"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visible(&mut self) -> &mut bool {
|
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||||
&mut self.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
|
||||||
let changed = self.changed();
|
let changed = self.changed();
|
||||||
|
|
||||||
// get or create the texture
|
// get or create the texture
|
||||||
@@ -126,10 +91,10 @@ impl Component for FrameBuffer {
|
|||||||
if changed {
|
if changed {
|
||||||
let pixels: Vec<egui::Color32> = self
|
let pixels: Vec<egui::Color32> = self
|
||||||
.buffer
|
.buffer
|
||||||
|
.as_chunks::<4>().0
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&p| {
|
.map(|&[red, green, blue, alpha]| {
|
||||||
let bytes = p.to_le_bytes();
|
egui::Color32::from_rgba_premultiplied(red, green, blue, alpha)
|
||||||
egui::Color32::from_rgba_premultiplied(bytes[0], bytes[1], bytes[2], bytes[3])
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -153,5 +118,6 @@ impl Component for FrameBuffer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ui.image(egui::load::SizedTexture::new(texture.id(), size));
|
ui.image(egui::load::SizedTexture::new(texture.id(), size));
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+12
-32
@@ -4,17 +4,16 @@ use egui::{Ui, WidgetText};
|
|||||||
use egui_extras::{Column, TableBuilder};
|
use egui_extras::{Column, TableBuilder};
|
||||||
use egui_tiles::{Tile, TileId};
|
use egui_tiles::{Tile, TileId};
|
||||||
use common::prelude::Instruction;
|
use common::prelude::Instruction;
|
||||||
|
use emulator_core::EmulatorHandle;
|
||||||
use emulator_core::memory::ram::MemoryBank;
|
use emulator_core::memory::ram::MemoryBank;
|
||||||
use crate::ui::debugger::Action;
|
use crate::ui::Action;
|
||||||
use crate::ui::debugger::panels::TilePane;
|
use crate::ui::panels::TilePane;
|
||||||
use super::Component;
|
|
||||||
|
|
||||||
pub struct MemoryInspector {
|
pub struct MemoryInspector {
|
||||||
memory: MemoryBank,
|
emulator: EmulatorHandle,
|
||||||
|
|
||||||
view_size: u32,
|
view_size: u32,
|
||||||
view_addr: u32,
|
view_addr: u32,
|
||||||
visible: bool,
|
|
||||||
addr_input: String,
|
addr_input: String,
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
@@ -23,14 +22,12 @@ pub struct MemoryInspector {
|
|||||||
|
|
||||||
impl MemoryInspector {
|
impl MemoryInspector {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(memory: MemoryBank) -> Self {
|
pub fn new(emulator: EmulatorHandle) -> Self {
|
||||||
Self {
|
Self {
|
||||||
memory,
|
emulator,
|
||||||
view_size: 1024,
|
view_size: 1024,
|
||||||
view_addr: 0,
|
view_addr: 0,
|
||||||
visible: false,
|
|
||||||
addr_input: String::from("0x00"),
|
addr_input: String::from("0x00"),
|
||||||
|
|
||||||
memory_viewer: MemoryViewer::new()
|
memory_viewer: MemoryViewer::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,27 +38,8 @@ impl TilePane for MemoryInspector {
|
|||||||
fn title(&self) -> WidgetText {
|
fn title(&self) -> WidgetText {
|
||||||
"Memory Inspector".into()
|
"Memory Inspector".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||||
Component::ui(self, ui);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for MemoryInspector {
|
|
||||||
fn title(&self) -> &'static str {
|
|
||||||
"Memory Inspector"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visible(&mut self) -> &mut bool {
|
|
||||||
&mut self.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Address:");
|
ui.label("Address:");
|
||||||
|
|
||||||
@@ -96,12 +74,14 @@ impl Component for MemoryInspector {
|
|||||||
ui.label("(hex or decimal)");
|
ui.label("(hex or decimal)");
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut window = vec![0u8; self.view_size as usize];
|
let window = self.emulator.mem_read(self.view_addr, self.view_size);
|
||||||
for i in 0..self.view_size {
|
self.memory_viewer.ui(ui, &window, self.view_addr as usize);
|
||||||
window[i as usize] = self.memory.read_byte(self.view_addr + i as u32);
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
self.memory_viewer.ui(ui, &window, self.view_addr as usize)
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+50
-21
@@ -1,24 +1,41 @@
|
|||||||
pub mod documentation;
|
pub mod documentation;
|
||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub mod about;
|
pub mod about;
|
||||||
|
pub mod disassembler;
|
||||||
|
pub mod display;
|
||||||
|
pub mod framebuffer;
|
||||||
|
pub mod memory;
|
||||||
|
pub mod registers;
|
||||||
|
pub mod serial;
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use egui::{Id, Response, Stroke, StrokeKind, Ui, Widget};
|
|
||||||
use egui_tiles::{Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse};
|
use egui_tiles::{Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse};
|
||||||
use crate::ui::debugger::Action;
|
use crate::ui::Action;
|
||||||
|
use crate::ui::Action::RequestCloseTab;
|
||||||
|
|
||||||
/// Anything that can live inside a tab in the central tiled area.
|
/// 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
|
/// `as_any_mut` lets callers reach back into a specific pane's concrete
|
||||||
/// type after it's been handed off to the tree (e.g. to write into the
|
/// type after it's been handed off to the tree (e.g. to write into the
|
||||||
/// editor's buffer from an `Action` handler).
|
/// editor's buffer from an `Action` handler).
|
||||||
pub trait TilePane: Any {
|
pub trait TilePane: Any {
|
||||||
|
|
||||||
|
/// the title that's shown on the tab in the UI.
|
||||||
fn title(&self) -> egui::WidgetText;
|
fn title(&self) -> egui::WidgetText;
|
||||||
|
|
||||||
|
/// main UI function. returns an action in order to communicate with the parent UI
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, _tile_id: TileId) -> Option<Action>;
|
fn ui(&mut self, ui: &mut egui::Ui, _tile_id: TileId) -> Option<Action>;
|
||||||
|
|
||||||
|
/// required in order to be used with a pane
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
|
|
||||||
|
/// the menu options exposed by the tile. these inform the main application menu based on the current focused tile
|
||||||
fn menu(&mut self, _ui: &mut egui::Ui, _tile_id: TileId) -> Option<Action> {
|
fn menu(&mut self, _ui: &mut egui::Ui, _tile_id: TileId) -> Option<Action> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn close_requires_confirmation(&mut self, _tile_id: TileId) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pane = Box<dyn TilePane>;
|
type Pane = Box<dyn TilePane>;
|
||||||
@@ -29,24 +46,6 @@ struct TileBehavior {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Behavior<Pane> for TileBehavior {
|
impl Behavior<Pane> for TileBehavior {
|
||||||
fn is_tab_closable(&self, _tiles: &Tiles<Pane>, _tile_id: TileId) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_tab_close(&mut self, tiles: &mut Tiles<Pane>, tile_id: TileId) -> bool {
|
|
||||||
tiles.remove(tile_id);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tab_bar_height(&self, _style: &egui::Style) -> f32 {
|
|
||||||
36.0 // taller tab row = more vertical padding around the label
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tab_bg_color(&self, visuals: &egui::Visuals, _tiles: &egui_tiles::Tiles<Box<dyn TilePane>>, _tile_id: egui_tiles::TileId, tab_state: &egui_tiles::TabState) -> egui::Color32 {
|
|
||||||
visuals.widgets.inactive.bg_fill
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn pane_ui(
|
fn pane_ui(
|
||||||
&mut self,
|
&mut self,
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
@@ -65,11 +64,34 @@ impl Behavior<Pane> for TileBehavior {
|
|||||||
UiResponse::None
|
UiResponse::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn tab_title_for_pane(&mut self, pane: &Pane) -> egui::WidgetText {
|
fn tab_title_for_pane(&mut self, pane: &Pane) -> egui::WidgetText {
|
||||||
pane.title()
|
pane.title()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_tab_closable(&self, _tiles: &Tiles<Pane>, _tile_id: TileId) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_tab_close(&mut self, tiles: &mut Tiles<Pane>, tile_id: TileId) -> bool {
|
||||||
|
let Some(Tile::Pane(pane)) = tiles.get_mut(tile_id) else {
|
||||||
|
return false
|
||||||
|
};
|
||||||
|
|
||||||
|
if pane.close_requires_confirmation(tile_id) {
|
||||||
|
self.pending_action = Some(RequestCloseTab(tile_id));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles.remove(tile_id);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn tab_bar_height(&self, _style: &egui::Style) -> f32 {
|
||||||
|
36.0 // taller tab row = more vertical padding around the label
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn simplification_options(&self) -> SimplificationOptions {
|
fn simplification_options(&self) -> SimplificationOptions {
|
||||||
SimplificationOptions {
|
SimplificationOptions {
|
||||||
// Keep the tab bar around even with only one tab, and don't let
|
// Keep the tab bar around even with only one tab, and don't let
|
||||||
@@ -78,6 +100,10 @@ impl Behavior<Pane> for TileBehavior {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tab_bg_color(&self, visuals: &egui::Visuals, _tiles: &Tiles<Box<dyn TilePane>>, _tile_id: TileId, tab_state: &TabState) -> egui::Color32 {
|
||||||
|
visuals.widgets.inactive.bg_fill
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single top-level tab strip that tools can add tabs to at runtime.
|
/// A single top-level tab strip that tools can add tabs to at runtime.
|
||||||
@@ -111,6 +137,9 @@ impl CentralTiles {
|
|||||||
self.add_dyn_tab(Box::new(pane))
|
self.add_dyn_tab(Box::new(pane))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn close_tab(&mut self, tile_id: TileId) {
|
||||||
|
self.tree.tiles.remove(tile_id);
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a new tab and make it active. Returns the `TileId` so the
|
/// Add a new tab and make it active. Returns the `TileId` so the
|
||||||
/// caller can hold onto it for later access via `pane_mut`.
|
/// caller can hold onto it for later access via `pane_mut`.
|
||||||
+27
-44
@@ -1,38 +1,22 @@
|
|||||||
use std::{f32, sync::Arc};
|
use std::f32;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use egui::{Ui, WidgetText};
|
use egui::{Ui, WidgetText};
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
use common::prelude::Register;
|
use common::prelude::Register;
|
||||||
use emulator_core::processor::state::SharedState;
|
use emulator_core::EmulatorHandle;
|
||||||
use crate::ui::components::memory::MemoryInspector;
|
use crate::ui::Action;
|
||||||
use crate::ui::debugger::Action;
|
use crate::ui::panels::TilePane;
|
||||||
use crate::ui::debugger::panels::TilePane;
|
|
||||||
use super::Component;
|
|
||||||
|
|
||||||
pub struct Registers {
|
pub struct Registers {
|
||||||
state: Arc<SharedState>,
|
state: EmulatorHandle,
|
||||||
visible: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TilePane for Registers {
|
|
||||||
fn title(&self) -> WidgetText {
|
|
||||||
"Registers".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
|
||||||
Component::ui(self, ui);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Registers {
|
impl Registers {
|
||||||
fn section(
|
fn section(
|
||||||
&mut self,
|
&mut self,
|
||||||
ui: &mut egui::Ui,
|
ui: &mut Ui,
|
||||||
registers: Vec<(Register, u32)>,
|
registers: Vec<(Register, u32)>,
|
||||||
col_pairs: usize,
|
col_pairs: usize,
|
||||||
name: &str,
|
name: &str,
|
||||||
@@ -54,7 +38,7 @@ impl Registers {
|
|||||||
for chunk in registers.chunks(col_pairs) {
|
for chunk in registers.chunks(col_pairs) {
|
||||||
for (reg, val) in chunk {
|
for (reg, val) in chunk {
|
||||||
ui.label(reg.to_string());
|
ui.label(reg.to_string());
|
||||||
ui.label(format!("0x{:08X} ({})", val, val));
|
ui.label(format!("0x{:08X} ({:012})", val, val));
|
||||||
}
|
}
|
||||||
for _ in chunk.len()..col_pairs {
|
for _ in chunk.len()..col_pairs {
|
||||||
ui.label("");
|
ui.label("");
|
||||||
@@ -68,27 +52,21 @@ impl Registers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Registers {
|
impl Registers {
|
||||||
pub fn new(state: Arc<SharedState>) -> Self {
|
pub fn new(state: EmulatorHandle) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state,
|
state,
|
||||||
visible: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Registers {
|
impl TilePane for Registers {
|
||||||
fn title(&self) -> &str {
|
fn title(&self) -> WidgetText {
|
||||||
"Registers"
|
"Registers".into()
|
||||||
}
|
}
|
||||||
|
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||||
|
|
||||||
fn visible(&mut self) -> &mut bool {
|
|
||||||
&mut self.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.set_min_width(200.0);
|
ui.set_min_width(200.0);
|
||||||
|
|
||||||
let state = self.state.proc.load();
|
|
||||||
let available = ui.available_width();
|
let available = ui.available_width();
|
||||||
let col_pairs = match available {
|
let col_pairs = match available {
|
||||||
0.0..350.0 => 1,
|
0.0..350.0 => 1,
|
||||||
@@ -101,7 +79,7 @@ impl Component for Registers {
|
|||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
// ── General Purpose ──────────────────────────────────────
|
// ── General Purpose ──────────────────────────────────────
|
||||||
let gp_registers: Vec<(Register, u32)> = (0..=15u8)
|
let gp_registers: Vec<(Register, u32)> = (0..=15u8)
|
||||||
.map(|i| (Register::from_u8(i).unwrap(), state.registers[i as usize]))
|
.map(|i| (Register::from_u8(i).unwrap(), self.state.registers()[i as usize]))
|
||||||
.collect();
|
.collect();
|
||||||
self.section(
|
self.section(
|
||||||
ui,
|
ui,
|
||||||
@@ -113,18 +91,18 @@ impl Component for Registers {
|
|||||||
|
|
||||||
// ── Stack ─────────────────────────────────────────────────
|
// ── Stack ─────────────────────────────────────────────────
|
||||||
let stack_registers = vec![
|
let stack_registers = vec![
|
||||||
(Register::Spr, state.registers[Register::Spr as usize]),
|
(Register::Spr, self.state.get_register(Register::Spr)),
|
||||||
(Register::Bpr, state.registers[Register::Bpr as usize]),
|
(Register::Bpr, self.state.get_register(Register::Bpr)),
|
||||||
];
|
];
|
||||||
self.section(ui, stack_registers, col_pairs, "Stack Registers");
|
self.section(ui, stack_registers, col_pairs, "Stack Registers");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
// ── Special Purpose ───────────────────────────────────────
|
// ── Special Purpose ───────────────────────────────────────
|
||||||
let special_registers = vec![
|
let special_registers = vec![
|
||||||
(Register::Acc, state.registers[Register::Acc as usize]),
|
(Register::Acc, self.state.get_register(Register::Acc)),
|
||||||
(Register::Ret, state.registers[Register::Ret as usize]),
|
(Register::Ret, self.state.get_register(Register::Ret)),
|
||||||
(Register::Idr, state.registers[Register::Idr as usize]),
|
(Register::Idr, self.state.get_register(Register::Idr)),
|
||||||
(Register::Mmr, state.registers[Register::Mmr as usize]),
|
(Register::Mmr, self.state.get_register(Register::Mmr)),
|
||||||
];
|
];
|
||||||
self.section(
|
self.section(
|
||||||
ui,
|
ui,
|
||||||
@@ -136,11 +114,16 @@ impl Component for Registers {
|
|||||||
|
|
||||||
// ── System ────────────────────────────────────────────────
|
// ── System ────────────────────────────────────────────────
|
||||||
let system_registers = vec![
|
let system_registers = vec![
|
||||||
(Register::Pcx, state.registers[Register::Pcx as usize]),
|
(Register::Pcx, self.state.get_register(Register::Pcx)),
|
||||||
(Register::Sts, state.registers[Register::Sts as usize]),
|
(Register::Sts, self.state.get_register(Register::Sts)),
|
||||||
(Register::Cir, state.registers[Register::Cir as usize]),
|
(Register::Cir, self.state.get_register(Register::Cir)),
|
||||||
];
|
];
|
||||||
self.section(ui, system_registers, col_pairs, "System Registers");
|
self.section(ui, system_registers, col_pairs, "System Registers");
|
||||||
});
|
});
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+25
-53
@@ -1,28 +1,18 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
sync::{
|
sync::{
|
||||||
Arc,
|
|
||||||
atomic::Ordering,
|
|
||||||
mpsc::{Receiver, Sender},
|
mpsc::{Receiver, Sender},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use egui::{Ui, WidgetText};
|
use egui::{Ui, WidgetText};
|
||||||
use egui_tiles::TileId;
|
use egui_tiles::TileId;
|
||||||
use emulator_core::{
|
use emulator_core::{memory::{
|
||||||
memory::{
|
|
||||||
PhysAddr,
|
PhysAddr,
|
||||||
ram::MemoryBank
|
}, EmulatorHandle};
|
||||||
},
|
use emulator_core::processor::Interrupt;
|
||||||
processor::{
|
use crate::ui::Action;
|
||||||
interrupts::Interrupt,
|
use crate::ui::panels::TilePane;
|
||||||
state::SharedState
|
|
||||||
}
|
|
||||||
};
|
|
||||||
use crate::ui::components::registers::Registers;
|
|
||||||
use crate::ui::debugger::Action;
|
|
||||||
use crate::ui::debugger::panels::TilePane;
|
|
||||||
use super::Component;
|
|
||||||
|
|
||||||
/// ## SERIAL LAYOUT: (from the perspective of code inside the ui)
|
/// ## 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
|
/// - [base+0x00]: ser_out valid flag - set by kernel after writing a byte to serial
|
||||||
@@ -34,29 +24,11 @@ pub struct Serial {
|
|||||||
pub write_tx: Sender<u8>,
|
pub write_tx: Sender<u8>,
|
||||||
pub read_buff: Vec<u8>,
|
pub read_buff: Vec<u8>,
|
||||||
pub write_buff: String,
|
pub write_buff: String,
|
||||||
|
|
||||||
pub visible: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TilePane for Serial {
|
|
||||||
fn title(&self) -> WidgetText {
|
|
||||||
"Serial".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
|
||||||
Component::ui(self, ui);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serial {
|
impl Serial {
|
||||||
fn uart_io_thread(
|
fn uart_io_thread(
|
||||||
mem: MemoryBank,
|
mut handle: EmulatorHandle,
|
||||||
state: Arc<SharedState>,
|
|
||||||
uart_base: PhysAddr,
|
uart_base: PhysAddr,
|
||||||
tx: Sender<u8>,
|
tx: Sender<u8>,
|
||||||
rx: Receiver<u8>,
|
rx: Receiver<u8>,
|
||||||
@@ -68,42 +40,41 @@ impl Serial {
|
|||||||
write_buffer.push_back(byte);
|
write_buffer.push_back(byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ser_out_valid = mem.read_byte(uart_base + 0);
|
let ser_out_valid = handle.read_byte(uart_base + 0);
|
||||||
// Kernel => ui: valid flag in byte 0, data in byte 1
|
// Kernel => ui: valid flag in byte 0, data in byte 1
|
||||||
if ser_out_valid == 0x01 {
|
if ser_out_valid == 0x01 {
|
||||||
let ser_out = mem.read_byte(uart_base + 1);
|
let ser_out = handle.read_byte(uart_base + 1);
|
||||||
let _ = tx.send(ser_out);
|
let _ = tx.send(ser_out);
|
||||||
mem.write_byte(uart_base, 0x00); // clear the whole word
|
handle.write_byte(uart_base, 0x00); // clear the whole word
|
||||||
}
|
}
|
||||||
|
|
||||||
if !write_buffer.is_empty() {
|
if !write_buffer.is_empty() {
|
||||||
// Emulator => kernel: valid flag in byte 2, data in byte 3
|
// Emulator => kernel: valid flag in byte 2, data in byte 3
|
||||||
let ser_in_valid = mem.read_byte(uart_base + 2);
|
let ser_in_valid = handle.read_byte(uart_base + 2);
|
||||||
if ser_in_valid == 0x00 {
|
if ser_in_valid == 0x00 {
|
||||||
if let Some(byte) = write_buffer.pop_front() {
|
if let Some(byte) = write_buffer.pop_front() {
|
||||||
mem.write_byte(uart_base + 3, byte);
|
handle.write_byte(uart_base + 3, byte);
|
||||||
mem.write_byte(uart_base + 2, 0x01);
|
handle.write_byte(uart_base + 2, 0x01);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(2));
|
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||||
|
|
||||||
state.interrupt_queue.send(Interrupt::SerialIn).unwrap();
|
handle.interrupt(Interrupt::SerialIn).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(uart_base: PhysAddr, state: Arc<SharedState>, mem_handle: MemoryBank) -> Self {
|
pub fn new(uart_base: PhysAddr, handle: EmulatorHandle) -> Self {
|
||||||
let (read_tx, read_rx) = std::sync::mpsc::channel();
|
let (read_tx, read_rx) = std::sync::mpsc::channel();
|
||||||
let (write_tx, write_rx) = std::sync::mpsc::channel();
|
let (write_tx, write_rx) = std::sync::mpsc::channel();
|
||||||
let _ = std::thread::spawn(move || {
|
let _ = std::thread::spawn(move || {
|
||||||
Self::uart_io_thread(mem_handle, state, uart_base, read_tx, write_rx)
|
Self::uart_io_thread(handle, uart_base, read_tx, write_rx)
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
visible: false,
|
|
||||||
write_tx,
|
write_tx,
|
||||||
read_rx,
|
read_rx,
|
||||||
read_buff: Vec::new(),
|
read_buff: Vec::new(),
|
||||||
@@ -118,16 +89,11 @@ impl Serial {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Serial {
|
impl TilePane for Serial {
|
||||||
fn title(&self) -> &str {
|
fn title(&self) -> WidgetText {
|
||||||
"Serial"
|
"Serial".into()
|
||||||
}
|
}
|
||||||
|
fn ui(&mut self, ui: &mut Ui, _tile_id: TileId) -> Option<Action> {
|
||||||
fn visible(&mut self) -> &mut bool {
|
|
||||||
&mut self.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
|
||||||
self.update();
|
self.update();
|
||||||
|
|
||||||
// Text field for input and a send button
|
// Text field for input and a send button
|
||||||
@@ -145,5 +111,11 @@ impl Component for Serial {
|
|||||||
if ui.button("Clear").clicked() {
|
if ui.button("Clear").clicked() {
|
||||||
self.read_buff.clear();
|
self.read_buff.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-2
@@ -1,8 +1,7 @@
|
|||||||
use eframe::emath::Vec2;
|
use eframe::emath::Vec2;
|
||||||
use egui::{Color32, Frame, Stroke};
|
use egui::{Color32, Frame, Stroke};
|
||||||
use egui::widget_style::{Classes, WidgetState};
|
use egui::widget_style::{Classes, WidgetState};
|
||||||
use emulator_core::processor::state::RunningState;
|
use emulator_core::processor::RunningState;
|
||||||
|
|
||||||
|
|
||||||
pub struct PaddedWidget {
|
pub struct PaddedWidget {
|
||||||
text: String,
|
text: String,
|
||||||
Reference in New Issue
Block a user