- added run_sync to TraceableProcess
refactored assembler to use the run_sync method defined by the TraceableProcess trait for standard running. - refactored assembler logging to use the ProcessAgent
This commit is contained in:
@@ -1,22 +1,18 @@
|
||||
use common::prelude::*;
|
||||
|
||||
use common::process::ProcessAgent;
|
||||
use crate::assembler::Token;
|
||||
use crate::assembler::model::{Node, Opcode};
|
||||
use crate::{assembler::AssembleError, expect_token};
|
||||
|
||||
fn log(message: &str) {
|
||||
println!("\x1b[32mINFO:\x1b[0m {message}");
|
||||
}
|
||||
|
||||
pub fn codegen(nodes: Vec<Node>) -> Result<Vec<Instruction>, AssembleError> {
|
||||
pub fn codegen(nodes: Vec<Node>, ctx: &ProcessAgent) -> Result<Vec<Instruction>, AssembleError> {
|
||||
let mut instructions = vec![];
|
||||
|
||||
for node in nodes {
|
||||
println!("{:?}", node);
|
||||
ctx.log(format!("{}", node));
|
||||
instructions.push(build_instruction(&node)?);
|
||||
}
|
||||
|
||||
log("Assembly Successful ✅");
|
||||
ctx.log("Assembly Successful ✅");
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
@@ -65,8 +65,6 @@ pub fn lexer(mut program: String, module: u64) -> Result<Vec<Token>, AssembleErr
|
||||
}
|
||||
}
|
||||
|
||||
// println!("{:#?}", tokens);
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
pub fn parse_register(token: &str) -> Result<Option<Token>, AssembleError> {
|
||||
|
||||
@@ -50,15 +50,6 @@ pub struct Assembler {
|
||||
is_running: bool,
|
||||
}
|
||||
|
||||
impl TraceableProcess for Assembler {
|
||||
type Output = DseExecutable;
|
||||
type Error = AssembleError;
|
||||
|
||||
fn run(mut self: Box<Self>, ctx: ProcessAgent) -> Result<Self::Output, Self::Error> {
|
||||
assemble(&self.src_path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Assembler {
|
||||
#[must_use]
|
||||
pub fn new(src_path: impl Into<PathBuf>) -> Self {
|
||||
@@ -70,137 +61,37 @@ impl Assembler {
|
||||
is_running: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the compilation process in a separate thread
|
||||
pub fn start(&mut self) {
|
||||
if self.is_running {
|
||||
return;
|
||||
}
|
||||
impl TraceableProcess for Assembler {
|
||||
type Output = DseExecutable;
|
||||
type Error = AssembleError;
|
||||
|
||||
let src = self.src_path.clone();
|
||||
let tx = self.result_tx.clone();
|
||||
|
||||
thread::spawn(move || match assemble(&src) {
|
||||
Ok(exe) => {
|
||||
let mut buffer = Cursor::new(Vec::new());
|
||||
match exe.write(&mut buffer) {
|
||||
Ok(()) => {
|
||||
tx.send(Ok(buffer.into_inner()))
|
||||
.expect("Failed to send compilation result from worker thread");
|
||||
}
|
||||
Err(e) => {
|
||||
tx.send(Err(AssembleError::Generic))
|
||||
.expect("...");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => { /* unchanged */ }
|
||||
});
|
||||
|
||||
self.is_running = true;
|
||||
}
|
||||
|
||||
/// Polls the result channel to check if a compilation result is ready.
|
||||
///
|
||||
/// This method attempts a non-blocking read (`try_recv`) from the result receiver.
|
||||
/// It returns `Some(Ok(Vec<u8>))` if the compilation successfully finished and
|
||||
/// the binary output is available, or `None` if no result was received yet.
|
||||
///
|
||||
/// If it receives an error via `mpsc::TryRecvError::Disconnected`, it indicates that
|
||||
/// the worker thread terminated unexpectedly, returning a specific error string.
|
||||
pub fn poll(&mut self) -> Option<Result<Vec<u8>, String>> {
|
||||
if !self.is_running {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self
|
||||
.result_rx
|
||||
.as_ref()
|
||||
.expect("result_rx should be Some while compilation is running")
|
||||
.try_recv()
|
||||
{
|
||||
Ok(result) => {
|
||||
self.is_running = false;
|
||||
Some(result.map_err(|e| e.to_string()))
|
||||
}
|
||||
Err(mpsc::TryRecvError::Empty) => None,
|
||||
Err(mpsc::TryRecvError::Disconnected) => {
|
||||
self.is_running = false;
|
||||
Some(Err(String::from(
|
||||
"Compilation terminated before a result was returned",
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Block until compilation is complete and return the result
|
||||
pub fn output(&mut self) -> Result<Vec<u8>, String> {
|
||||
if let Ok(result) = self
|
||||
.result_rx
|
||||
.take()
|
||||
.expect("result_rx should be Some while waiting for compilation result")
|
||||
.recv()
|
||||
{
|
||||
self.is_running = false;
|
||||
result.map_err(|e| e.to_string())
|
||||
} else {
|
||||
self.is_running = false;
|
||||
Err(String::from(
|
||||
"Compilation terminated before a result was returned",
|
||||
))
|
||||
}
|
||||
fn run(mut self: Box<Self>, ctx: ProcessAgent) -> Result<Self::Output, Self::Error> {
|
||||
assemble(&self.src_path, &ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
|
||||
// let mut modules = HashSet::new();
|
||||
// let mut program = Program::new();
|
||||
//
|
||||
// let hash = quick_hash(src);
|
||||
//
|
||||
// if modules.contains(&hash) {
|
||||
// return Ok(vec![]);
|
||||
// }
|
||||
//
|
||||
// prepare_dependency(src, &mut modules, &mut program)?;
|
||||
//
|
||||
// let mut nodes = program.nodes.clone();
|
||||
//
|
||||
// println!("PRE LINK");
|
||||
// for node in &nodes {
|
||||
// println!("{:?}", node);
|
||||
// }
|
||||
//
|
||||
// create_sections(&mut nodes)?;
|
||||
// resolve_symbols(&mut nodes)?;
|
||||
//
|
||||
// println!("Generating assembly output...");
|
||||
//
|
||||
// let instructions = codegen(nodes)?;
|
||||
//
|
||||
// println!("Compilation Successful");
|
||||
// Ok(instructions)
|
||||
// }
|
||||
|
||||
fn assemble(src: &Path) -> Result<DseExecutable, AssembleError> {
|
||||
fn assemble(src: &Path, ctx: &ProcessAgent) -> Result<DseExecutable, AssembleError> {
|
||||
let mut modules = HashSet::new();
|
||||
let mut program = Program::new();
|
||||
|
||||
prepare_dependency(src, &mut modules, &mut program)?;
|
||||
prepare_dependency(src, &mut modules, &mut program, &ctx)?;
|
||||
|
||||
let nodes = program.nodes.clone();
|
||||
|
||||
println!("PRE LINK");
|
||||
for node in &nodes {
|
||||
println!("{:?}", node);
|
||||
}
|
||||
// for node in &nodes {
|
||||
// ctx.log(format!("{:?}", node));
|
||||
// }
|
||||
|
||||
let (data_nodes, mut text_nodes) = split_sections(nodes);
|
||||
let symbol_table = resolve_symbols(&data_nodes, &mut text_nodes)?;
|
||||
let text_len = text_nodes.len() as u32;
|
||||
|
||||
println!("Generating assembly output...");
|
||||
let instructions = codegen(text_nodes)?;
|
||||
ctx.log("Generating assembly output...");
|
||||
|
||||
// println!("Generating assembly output...");
|
||||
let instructions = codegen(text_nodes, ctx)?;
|
||||
let text: Vec<u32> = instructions
|
||||
.iter()
|
||||
.map(|i| i.0).collect();
|
||||
@@ -237,7 +128,7 @@ fn assemble(src: &Path) -> Result<DseExecutable, AssembleError> {
|
||||
module: Module::Resolved(0),
|
||||
}))?;
|
||||
|
||||
println!("Compilation Successful");
|
||||
ctx.log("Compilation Successful");
|
||||
|
||||
Ok(DseExecutable::new(
|
||||
symbols.as_slice(),
|
||||
@@ -264,6 +155,7 @@ fn prepare_dependency(
|
||||
path: &Path,
|
||||
modules: &mut HashSet<u64>,
|
||||
program: &mut Program,
|
||||
ctx: &ProcessAgent,
|
||||
) -> Result<(), AssembleError> {
|
||||
let filename = path
|
||||
.file_name()
|
||||
@@ -271,21 +163,21 @@ fn prepare_dependency(
|
||||
.expect("Failed to get file name from path");
|
||||
|
||||
if let Ok(path) = path.canonicalize() {
|
||||
println!("{:20} {:20} [{}]", "Building", filename, path.display());
|
||||
ctx.log(format!("{:20} {:20} [{}]", "Building", filename, path.display()));
|
||||
}
|
||||
|
||||
let src =
|
||||
fs::read_to_string(path).map_err(|_| AssembleError::InvalidFile(path.to_path_buf()))?;
|
||||
let file_hash = quick_hash(path);
|
||||
|
||||
println!("{:20} {:20}", "Tokenising", filename);
|
||||
ctx.log(format!("{:20} {:20}", "Tokenising", filename));
|
||||
let tokens = lexer::lexer(src, file_hash)?;
|
||||
// let tokens = Lexer::new(src, file_hash).run()?;
|
||||
|
||||
println!("{:20} {:20}", "Parsing", filename);
|
||||
ctx.log(format!("{:20} {:20}", "Parsing", filename));
|
||||
let parsed = Parser::parse_nodes(tokens)?;
|
||||
|
||||
println!("{:20} {:20}", "Resolving Deps", filename);
|
||||
ctx.log(format!("{:20} {:20}", "Resolving Deps", filename));
|
||||
// Get the parent directory of the source file to use as the base directory
|
||||
let base_dir = path
|
||||
.parent()
|
||||
@@ -295,32 +187,28 @@ fn prepare_dependency(
|
||||
|
||||
let deps = Parser::get_dependencies(&nodes, path)?;
|
||||
|
||||
println!("{:20} {:20}", "Expanding Pseudo-ops", filename);
|
||||
ctx.log(format!("{:20} {:20}", "Expanding Pseudo-ops", filename));
|
||||
|
||||
// add a section instruction
|
||||
nodes.insert(
|
||||
0,
|
||||
node!(None, Opcode::Segment, Token::Immediate(file_hash as u32)),
|
||||
);
|
||||
|
||||
// for n in &nodes {
|
||||
// println!("{n}");
|
||||
// }
|
||||
|
||||
|
||||
program.add_module(nodes);
|
||||
|
||||
for dep in deps {
|
||||
println!(
|
||||
ctx.log(format!(
|
||||
"{:20} {:20}",
|
||||
"Including",
|
||||
dep.file_name()
|
||||
.and_then(|f| f.to_str())
|
||||
.expect("Dependency path has no file name or is not valid UTF-8")
|
||||
);
|
||||
));
|
||||
|
||||
let dep_hash = quick_hash(&dep);
|
||||
if modules.insert(dep_hash) {
|
||||
prepare_dependency(dep.as_path(), modules, program)?;
|
||||
prepare_dependency(dep.as_path(), modules, program, &ctx)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::canonicalize,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use common::prelude::Register;
|
||||
|
||||
use crate::assembler::model::{Module, Node, Opcode, Symbol, Token};
|
||||
use crate::assembler::quick_hash;
|
||||
use crate::{assembler::AssembleError, node};
|
||||
|
||||
pub fn resolve_symbols(nodes: &mut [Node]) -> Result<(), AssembleError> {
|
||||
let symbol_table = generate_symbol_table(nodes);
|
||||
|
||||
for node in nodes.iter_mut() {
|
||||
match node.opcode() {
|
||||
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc | Opcode::Lli | Opcode::Lui => {
|
||||
if let Token::Symbol(symbol) = node
|
||||
.arg(0)
|
||||
.expect("Expected argument 0 for jump-like opcode")
|
||||
{
|
||||
if let Some(address) = symbol_table.get(&symbol) {
|
||||
node.tokens[0] = Token::Immediate(*address);
|
||||
} else {
|
||||
return Err(AssembleError::UndefinedSymbol(symbol));
|
||||
}
|
||||
}
|
||||
}
|
||||
Opcode::Jez | Opcode::Jnz => {
|
||||
if let Token::Symbol(symbol) = node
|
||||
.arg(1)
|
||||
.expect("Expected argument 0 for jump-like opcode")
|
||||
{
|
||||
if let Some(address) = symbol_table.get(&symbol) {
|
||||
node.tokens[1] = Token::Immediate(*address);
|
||||
} else {
|
||||
return Err(AssembleError::UndefinedSymbol(symbol));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_symbol_table(nodes: &[Node]) -> HashMap<Symbol, u32> {
|
||||
let mut table = HashMap::new();
|
||||
|
||||
for (i, node) in nodes.iter().enumerate() {
|
||||
if let Some(symbol) = node.label() {
|
||||
table.insert(symbol, 4 * i as u32);
|
||||
}
|
||||
}
|
||||
|
||||
table
|
||||
}
|
||||
|
||||
pub fn resolve_dependencies(
|
||||
mut nodes: Vec<Node>,
|
||||
base_dir: &Path,
|
||||
) -> Result<Vec<Node>, AssembleError> {
|
||||
// First we get a list of imports.
|
||||
let mut dependencies = Vec::new();
|
||||
for node in &nodes {
|
||||
if node.opcode() == Opcode::Include {
|
||||
// we want the path, and the name
|
||||
let name = if let Token::Symbol(name) = node
|
||||
.arg(0)
|
||||
.expect("Expected argument #0 for Include directive.")
|
||||
{
|
||||
name.name.clone()
|
||||
} else {
|
||||
unreachable!()
|
||||
}; //node.2.get(0).unwrap()
|
||||
|
||||
let Ok(Token::StringLit(path)) = node.arg(1) else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let full_path = base_dir.join(path);
|
||||
let canonical_path = full_path
|
||||
.canonicalize()
|
||||
.map_err(|_| AssembleError::InvalidFile(full_path.clone()))?;
|
||||
|
||||
let hash = quick_hash(&canonical_path);
|
||||
|
||||
dependencies.push((name, hash));
|
||||
}
|
||||
}
|
||||
|
||||
let mut changes = Vec::<(u32, u32, Symbol)>::new();
|
||||
// now we resolve the symbols on all the nodes
|
||||
// we need to check all operands for unresolved signals
|
||||
for (i, node) in nodes.clone().iter().enumerate() {
|
||||
let Node {
|
||||
tokens: operands, ..
|
||||
} = node;
|
||||
for (j, token) in operands.iter().enumerate() {
|
||||
if let Token::Symbol(symbol) = token {
|
||||
for d in &dependencies {
|
||||
if let Module::Unresolved(name) = symbol.module.clone() {
|
||||
if name != d.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let symbol = Symbol {
|
||||
name: symbol.name.clone(),
|
||||
module: Module::Resolved(d.1),
|
||||
};
|
||||
changes.push((i as u32, j as u32, symbol));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, j, symbol) in changes {
|
||||
nodes[i as usize].tokens[j as usize] = Token::Symbol(symbol);
|
||||
}
|
||||
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
pub fn create_sections(nodes: &mut Vec<Node>) -> Result<(), AssembleError> {
|
||||
let mut res = Vec::<Node>::with_capacity(nodes.len());
|
||||
|
||||
res.push(node!(None, Opcode::Segment, Token::Immediate(0)));
|
||||
|
||||
for n in nodes.iter() {
|
||||
if n.opcode() == Opcode::Data {
|
||||
res.push(n.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let start = res.len() + 2;
|
||||
res.insert(
|
||||
0,
|
||||
node!(
|
||||
None,
|
||||
Opcode::Jmp,
|
||||
Token::Immediate(start as u32 * 4),
|
||||
Token::Register(Register::Zero)
|
||||
),
|
||||
);
|
||||
for n in nodes.iter() {
|
||||
if !matches!(n.opcode(), Opcode::Data | Opcode::Include) {
|
||||
res.push(n.clone());
|
||||
}
|
||||
}
|
||||
|
||||
*nodes = res;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
use assembler::prelude::*;
|
||||
use clap::{Parser, arg};
|
||||
use std::{fs, path::PathBuf};
|
||||
use std::io::Cursor;
|
||||
use assembler::assembler::AssembleError;
|
||||
use common::process::TraceableProcess;
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
#[command(version, about, long_about = None)]
|
||||
@@ -15,11 +18,12 @@ fn main() {
|
||||
// Parse command line arguments
|
||||
let args = Args::parse();
|
||||
|
||||
let mut engine = Assembler::new(PathBuf::from(args.input_path));
|
||||
engine.start();
|
||||
let result = engine.output().expect("assembler failed.");
|
||||
let assembler = Assembler::new(PathBuf::from(args.input_path));
|
||||
let result = assembler.run_sync().expect("assembler failed.");
|
||||
let mut buffer = Cursor::new(Vec::new());
|
||||
result.write(&mut buffer).unwrap();
|
||||
|
||||
if let Err(e) = fs::write(args.output_path, result) {
|
||||
if let Err(e) = fs::write(args.output_path, buffer.into_inner()) {
|
||||
eprintln!("Failed to write to output file: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,53 @@
|
||||
use std::fmt::Debug;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use crate::process::AgentSink::{Callback, Channel};
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessAgent {
|
||||
tx: mpsc::Sender<String>,
|
||||
sink: Arc<AgentSink>,
|
||||
}
|
||||
|
||||
impl ProcessAgent {
|
||||
pub fn new(tx: mpsc::Sender<String>) -> Self {
|
||||
Self { tx }
|
||||
Self { sink: Arc::new(Channel(tx)) }
|
||||
}
|
||||
|
||||
fn new_sync(f: impl Fn(&str) + Send + Sync + 'static) -> Self {
|
||||
Self { sink: Arc::new(Callback(Box::new(f))) }
|
||||
}
|
||||
|
||||
pub fn log(&self, msg: impl Into<String>) {
|
||||
let _ = self.tx.send(msg.into());
|
||||
let msg = msg.into();
|
||||
match &*self.sink {
|
||||
Channel(tx) => { let _ = tx.send(msg); }
|
||||
Callback(f) => f(&msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AgentSink {
|
||||
Channel(mpsc::Sender<String>),
|
||||
Callback(Box<dyn Fn(&str) + Send + Sync>)
|
||||
}
|
||||
|
||||
pub trait TraceableProcess: Debug + Send + 'static {
|
||||
type Output: Send + 'static;
|
||||
type Error: std::error::Error + Send + 'static;
|
||||
|
||||
fn label(&self) -> String {
|
||||
format!("{self:?}")
|
||||
}
|
||||
|
||||
fn run(self: Box<Self>, ctx: ProcessAgent) -> Result<Self::Output, Self::Error>;
|
||||
|
||||
///
|
||||
fn run_sync(self) -> Result<Self::Output, Self::Error>
|
||||
where
|
||||
Self: Sized
|
||||
{
|
||||
Self::run(
|
||||
Box::new(self),
|
||||
ProcessAgent::new_sync(|line| println!("{line}") )
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ pub trait DsaImplementation: Send {
|
||||
/// integration testing.
|
||||
fn boot(&mut self) -> Result<(), DsaError>;
|
||||
|
||||
fn reset(&mut self);
|
||||
|
||||
/// main run method. the main-loop of the program execution
|
||||
fn run(&mut self) -> Result<(), DsaError>;
|
||||
|
||||
@@ -99,9 +101,10 @@ impl EmulatorHandle {
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.state.reseting.store(true, Ordering::Relaxed);
|
||||
self.state.resetting.store(true, Ordering::Relaxed);
|
||||
self.memory.clear();
|
||||
self.state.reseting.store(false, Ordering::Relaxed);
|
||||
self.state.proc.store(Arc::new(Default::default()));
|
||||
self.state.resetting.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn get_register(&self, rg: Register) -> u32 {
|
||||
|
||||
@@ -28,6 +28,18 @@ pub struct DsaProcessor {
|
||||
}
|
||||
|
||||
impl DsaProcessor {
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.internal_state = ProcessorSnapshot::default();
|
||||
self.mmu = MMU::new();
|
||||
|
||||
// deallocates the old memory
|
||||
self.mainstore.clear();
|
||||
self.mmio_region = None;
|
||||
self.memory_map = None;
|
||||
self.__mmap_configured = false;
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
internal_state: ProcessorSnapshot::default(),
|
||||
|
||||
@@ -32,6 +32,10 @@ impl DsaImplementation for Emulator {
|
||||
self.core.boot()
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.core.reset();
|
||||
}
|
||||
|
||||
fn run(&mut self) -> Result<(), DsaError> {
|
||||
// wait for UI to initialise.
|
||||
if self.shared_state.paused.load(Ordering::Relaxed) {
|
||||
@@ -88,9 +92,8 @@ impl DsaImplementation for Emulator {
|
||||
}
|
||||
|
||||
// Check if the previous instruction caused a fault.
|
||||
if let Some(fault) = self.pending_fault {
|
||||
if let Some(fault) = self.pending_fault.take() {
|
||||
println!("WARN fault: {:?}", fault);
|
||||
self.pending_fault = None;
|
||||
self.core.interrupt(fault);
|
||||
}
|
||||
|
||||
@@ -216,11 +219,10 @@ impl Emulator {
|
||||
}
|
||||
|
||||
// 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;
|
||||
if self.shared_state.resetting.load(Ordering::Relaxed) == true {
|
||||
self.reset();
|
||||
|
||||
while self.shared_state.reseting.load(Ordering::Relaxed) == true {
|
||||
while self.shared_state.resetting.load(Ordering::Relaxed) == true {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use super::interrupts::Interrupt;
|
||||
pub struct SharedState {
|
||||
pub proc: ArcSwap<ProcessorSnapshot>,
|
||||
|
||||
pub reseting: AtomicBool,
|
||||
pub resetting: AtomicBool,
|
||||
pub paused: AtomicBool,
|
||||
pub update_req: AtomicBool,
|
||||
pub interrupt_queue: mpsc::Sender<Interrupt>,
|
||||
@@ -17,7 +17,7 @@ pub struct SharedState {
|
||||
impl SharedState {
|
||||
pub fn new(sender: mpsc::Sender<Interrupt>) -> Self {
|
||||
Self {
|
||||
reseting: AtomicBool::new(false),
|
||||
resetting: AtomicBool::new(false),
|
||||
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
|
||||
|
||||
paused: AtomicBool::new(true),
|
||||
|
||||
@@ -32,13 +32,13 @@ impl Controller {
|
||||
let padding = Vec2::new(14.0, 8.0);
|
||||
|
||||
match status {
|
||||
RunningState::Paused | RunningState::Halted | RunningState::Breakpoint(_) => {
|
||||
RunningState::Paused | RunningState::Breakpoint(_) => {
|
||||
if PaddedWidget::button("▶ Run").color(Color32::GREEN).ui(ui).clicked() {
|
||||
self.emulator.resume()
|
||||
}
|
||||
},
|
||||
|
||||
RunningState::Running => {
|
||||
RunningState::Running | RunningState::Halted => {
|
||||
if PaddedWidget::button("⏸ Pause").color(Color32::YELLOW).ui(ui).clicked() {
|
||||
self.emulator.pause()
|
||||
}
|
||||
@@ -51,6 +51,8 @@ impl Controller {
|
||||
}
|
||||
}
|
||||
|
||||
status.clone().ui(ui, padding);
|
||||
|
||||
if PaddedWidget::button("⟳ Reset All").color(Color32::YELLOW).ui(ui).clicked() {
|
||||
self.emulator.reset();
|
||||
}
|
||||
@@ -79,9 +81,6 @@ impl Controller {
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
status.clone().ui(ui, padding);
|
||||
|
||||
ui.separator();
|
||||
|
||||
let pcx = self.emulator.get_register(Register::Pcx);
|
||||
|
||||
@@ -44,6 +44,7 @@ pub struct ProcessManager {
|
||||
|
||||
handles: Vec<ProcessHandle>,
|
||||
terminated_actions: VecDeque<Action>,
|
||||
active_tab: Option<usize>,
|
||||
}
|
||||
|
||||
impl ProcessManager {
|
||||
@@ -66,26 +67,91 @@ impl ProcessManager {
|
||||
self.handles.push(handle);
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
fn drain_logs(&mut self) {
|
||||
for handle in &mut self.handles {
|
||||
let is_finished = handle.join_handle
|
||||
.as_ref()
|
||||
.is_some_and(|jh| jh.is_finished());
|
||||
while let Ok(line) = handle.rx.try_recv() {
|
||||
handle.logs.push(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for finished processes and queues their resulting Actions.
|
||||
fn poll_finished(&mut self) {
|
||||
for handle in &mut self.handles {
|
||||
let is_finished = handle.join_handle.as_ref().is_some_and(|jh| jh.is_finished());
|
||||
if is_finished {
|
||||
if let Some(jh) = handle.join_handle.take() {
|
||||
println!("finished! {}", handle.name);
|
||||
if let Ok(action) = jh.join() {
|
||||
self.terminated_actions.push_back(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
self.drain_logs();
|
||||
self.poll_finished();
|
||||
|
||||
if self.handles.is_empty() {
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.weak("No processes running.");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// keep active_tab pointing at something valid
|
||||
if !self.active_tab.map_or(false, |id| self.handles.iter().any(|h| h.id == id)) {
|
||||
self.active_tab = self.handles.first().map(|h| h.id);
|
||||
}
|
||||
|
||||
// --- tab bar ---
|
||||
ui.horizontal(|ui| {
|
||||
for h in &self.handles {
|
||||
ui.label(&h.name);
|
||||
};
|
||||
for handle in &self.handles {
|
||||
let selected = self.active_tab == Some(handle.id);
|
||||
let running = handle.join_handle.is_some();
|
||||
|
||||
let label = if running {
|
||||
format!("● {}", handle.name)
|
||||
} else {
|
||||
format!("○ {}", handle.name)
|
||||
};
|
||||
|
||||
if ui.selectable_label(selected, label).clicked() {
|
||||
self.active_tab = Some(handle.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// --- close button for the active tab ---
|
||||
if let Some(active_id) = self.active_tab {
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(handle) = self.handles.iter().find(|h| h.id == active_id) {
|
||||
ui.label(egui::RichText::new(&handle.name).strong());
|
||||
}
|
||||
|
||||
if ui.small_button("✕ Close").clicked() {
|
||||
self.handles.retain(|h| h.id != active_id);
|
||||
self.active_tab = self.handles.first().map(|h| h.id);
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// --- log view for the active tab ---
|
||||
if let Some(handle) = self.handles.iter().find(|h| h.id == active_id) {
|
||||
egui::ScrollArea::vertical()
|
||||
.id_salt(("process_log", active_id))
|
||||
.stick_to_bottom(true)
|
||||
.auto_shrink([false, false])
|
||||
.show(ui, |ui| {
|
||||
for line in &handle.logs {
|
||||
ui.monospace(line);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user