added a simple process manager for the emulator UI. responsible for running and providing log information on build tasks etc.

first implementation so a bit jank still.
This commit is contained in:
2026-07-28 21:55:31 +01:00
parent 708743ef64
commit 8e4b192319
8 changed files with 220 additions and 32 deletions
+14 -5
View File
@@ -15,6 +15,7 @@ use std::collections::HashMap;
use std::io::Cursor; use std::io::Cursor;
use common::formats::binary_dse::{DseExecutable, SymbolEntry}; use common::formats::binary_dse::{DseExecutable, SymbolEntry};
use common::prelude::Instruction; use common::prelude::Instruction;
use common::process::{ProcessAgent, TraceableProcess};
use crate::assembler::resolver2::{resolve_address, resolve_dependencies, resolve_symbols, split_sections, SectionId}; use crate::assembler::resolver2::{resolve_address, resolve_dependencies, resolve_symbols, split_sections, SectionId};
// Module declarations // Module declarations
@@ -31,8 +32,6 @@ pub mod parser;
// pub mod resolver; // pub mod resolver;
pub mod resolver2; pub mod resolver2;
// Re-exports // Re-exports
pub use self::{ pub use self::{
codegen::codegen, codegen::codegen,
@@ -43,6 +42,7 @@ pub use self::{
// resolver::{create_sections, resolve_dependencies, resolve_symbols}, // resolver::{create_sections, resolve_dependencies, resolve_symbols},
}; };
#[derive(Debug)]
pub struct Assembler { pub struct Assembler {
src_path: PathBuf, src_path: PathBuf,
result_tx: mpsc::Sender<Result<Vec<u8>, AssembleError>>, result_tx: mpsc::Sender<Result<Vec<u8>, AssembleError>>,
@@ -50,6 +50,15 @@ pub struct Assembler {
is_running: bool, 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 { impl Assembler {
#[must_use] #[must_use]
pub fn new(src_path: impl Into<PathBuf>) -> Self { pub fn new(src_path: impl Into<PathBuf>) -> Self {
@@ -63,7 +72,7 @@ impl Assembler {
} }
/// Start the compilation process in a separate thread /// Start the compilation process in a separate thread
pub fn start(&mut self, args: ()) { pub fn start(&mut self) {
if self.is_running { if self.is_running {
return; return;
} }
@@ -143,8 +152,6 @@ impl Assembler {
} }
} }
impl Assembler {}
// fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> { // fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
// let mut modules = HashSet::new(); // let mut modules = HashSet::new();
// let mut program = Program::new(); // let mut program = Program::new();
@@ -332,6 +339,8 @@ pub enum AssembleError {
MissingArgument(u8), MissingArgument(u8),
} }
impl std::error::Error for AssembleError {}
impl fmt::Display for AssembleError { impl fmt::Display for AssembleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
+1 -1
View File
@@ -16,7 +16,7 @@ fn main() {
let args = Args::parse(); let args = Args::parse();
let mut engine = Assembler::new(PathBuf::from(args.input_path)); let mut engine = Assembler::new(PathBuf::from(args.input_path));
engine.start(()); engine.start();
let result = engine.output().expect("assembler failed."); let result = engine.output().expect("assembler failed.");
if let Err(e) = fs::write(args.output_path, result) { if let Err(e) = fs::write(args.output_path, result) {
+1
View File
@@ -1,6 +1,7 @@
pub mod asm; pub mod asm;
pub mod isa; pub mod isa;
pub mod formats; pub mod formats;
pub mod process;
pub mod prelude { pub mod prelude {
pub use crate::isa::instructions::Instruction; pub use crate::isa::instructions::Instruction;
+23
View File
@@ -0,0 +1,23 @@
use std::fmt::Debug;
use std::sync::mpsc;
#[derive(Debug, Clone)]
pub struct ProcessAgent {
tx: mpsc::Sender<String>,
}
impl ProcessAgent {
pub fn new(tx: mpsc::Sender<String>) -> Self {
Self { tx }
}
pub fn log(&self, msg: impl Into<String>) {
let _ = self.tx.send(msg.into());
}
}
pub trait TraceableProcess: Debug + Send + 'static {
type Output: Send + 'static;
type Error: std::error::Error + Send + 'static;
fn run(self: Box<Self>, ctx: ProcessAgent) -> Result<Self::Output, Self::Error>;
}
+1 -1
View File
@@ -3,4 +3,4 @@
#![feature(str_as_str)] #![feature(str_as_str)]
pub mod ui; pub mod ui;
pub mod args; pub mod args;
+32 -19
View File
@@ -1,21 +1,5 @@
use std::fmt::Debug;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use std::time::Duration;
use eframe::NativeOptions;
use egui::{CentralPanel, Panel, Ui};
use egui_tiles::TileId;
use common::formats::binary_dse::DseExecutable;
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::controller::Controller;
use crate::ui::dialog::error_dialog::ErrorDialog; use crate::ui::dialog::error_dialog::ErrorDialog;
use crate::ui::panels::{CentralTiles, TilePane};
use crate::ui::panels::about::AboutScreen; use crate::ui::panels::about::AboutScreen;
use crate::ui::panels::disassembler::BinaryViewer; use crate::ui::panels::disassembler::BinaryViewer;
use crate::ui::panels::display::Display; use crate::ui::panels::display::Display;
@@ -23,11 +7,30 @@ use crate::ui::panels::framebuffer::FrameBuffer;
use crate::ui::panels::memory::MemoryInspector; use crate::ui::panels::memory::MemoryInspector;
use crate::ui::panels::registers::Registers; use crate::ui::panels::registers::Registers;
use crate::ui::panels::serial::Serial; use crate::ui::panels::serial::Serial;
use crate::ui::panels::{CentralTiles, TilePane};
use common::formats::binary_dse::DseExecutable;
use common::process::TraceableProcess;
use dialog::confirm_dialog::ConfirmDialog;
use dialog::file_dialog::DsaFileDialog;
use eframe::NativeOptions;
use egui::{CentralPanel, Panel, Ui};
use egui_tiles::TileId;
use emulator_core::EmulatorController;
use panels::PanelFactory;
use panels::documentation::Documentation;
use panels::editor::Editor;
use process_manager::{ProcessManager, ProcessSpawner};
use std::fmt::Debug;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use std::time::Duration;
mod dialog; mod dialog;
mod panels; mod panels;
mod ui_helpers; mod ui_helpers;
mod controller; mod controller;
pub mod process_manager;
pub fn run_app(mut controller: EmulatorController) -> eframe::Result<()> { pub fn run_app(mut controller: EmulatorController) -> eframe::Result<()> {
// initialise emulator // initialise emulator
@@ -56,8 +59,8 @@ pub enum DataSource {
Data(Vec<u8>), Data(Vec<u8>),
} }
#[derive(Debug, Clone)]
pub enum Action { pub enum Action {
None,
RequestCloseTab(TileId), RequestCloseTab(TileId),
CloseTab(TileId), CloseTab(TileId),
Quit, Quit,
@@ -67,6 +70,7 @@ pub enum Action {
SaveFile(PathBuf, TileId), SaveFile(PathBuf, TileId),
RequestSaveAs(TileId), RequestSaveAs(TileId),
ShowError(String), ShowError(String),
SpawnProcess(ProcessSpawner)
} }
@@ -80,6 +84,8 @@ pub struct DamnSimpleUI {
error_dialog: ErrorDialog, error_dialog: ErrorDialog,
tools: Vec<PanelFactory>, tools: Vec<PanelFactory>,
process_manager: ProcessManager
} }
@@ -95,6 +101,10 @@ impl eframe::App for DamnSimpleUI {
self.perform_action(action); self.perform_action(action);
} }
while let Some(action) = self.process_manager.get_action() {
self.perform_action(action);
}
self.error_dialog.show(ui); self.error_dialog.show(ui);
Panel::top("menu_bar").resizable(false).show(ui, |ui| { Panel::top("menu_bar").resizable(false).show(ui, |ui| {
@@ -109,8 +119,8 @@ impl eframe::App for DamnSimpleUI {
// TODO: File Explorer! // TODO: File Explorer!
}); });
Panel::bottom("bottom_panel").resizable(false).show(ui, |ui| { Panel::bottom("bottom_panel").resizable(true).show(ui, |ui| {
// self.process_manager.ui(ui); self.process_manager.ui(ui);
}); });
CentralPanel::default().show(ui, |ui| { CentralPanel::default().show(ui, |ui| {
@@ -174,12 +184,15 @@ impl DamnSimpleUI {
}) })
], ],
emulator, emulator,
process_manager: ProcessManager::new()
} }
} }
pub fn perform_action(&mut self, action: Action) { pub fn perform_action(&mut self, action: Action) {
match action { match action {
Action::None => {},
Action::SpawnProcess(spawner) => spawner(&mut self.process_manager),
Action::RequestCloseTab(tile_id) => { Action::RequestCloseTab(tile_id) => {
self.confirm_dialog.open( self.confirm_dialog.open(
"Close Tab", "Close Tab",
@@ -1,11 +1,18 @@
use crate::ui::Action; use crate::ui::Action;
use crate::ui::panels::TilePane; use crate::ui::panels::TilePane;
use common::process::{ProcessAgent, TraceableProcess};
use egui::{Key, Ui}; use egui::{Key, Ui};
use egui_code_editor::CodeEditor; use egui_code_editor::CodeEditor;
use egui_tiles::TileId; use egui_tiles::TileId;
use std::any::Any; use std::any::Any;
use std::io::Error;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::thread;
use std::time::Duration;
use assembler::prelude::Assembler;
use theme::THEME; use theme::THEME;
use crate::ui::DataSource::Data;
mod syntax; mod syntax;
mod theme; mod theme;
@@ -56,27 +63,54 @@ impl TilePane for Editor {
fn ui(&mut self, ui: &mut Ui, tile_id: TileId) -> Option<Action> { fn ui(&mut self, ui: &mut Ui, tile_id: TileId) -> Option<Action> {
egui::Panel::top("panel").show(ui, |ui| { let response = egui::Panel::top("panel").show(ui, |ui| {
let mut action = None;
if let Some(filename) = self.filename.as_ref() { if let Some(filename) = self.filename.as_ref() {
ui.label(filename.display().to_string()); ui.label(filename.display().to_string());
} else { } else {
ui.label("New File*"); ui.label("New File*");
} }
if ui.button("Build").clicked() {
let Some(filename) = self.filename.clone() else {
println!("error!");
return None;
};
action = Some(Action::SpawnProcess(Box::new(move |pm| {
pm.spawn(
Assembler::new(filename),
move |result| {
return if let Ok(bin) = result {
Action::LoadBinary(Data(bin.to_memory_image()))
} else {
Action::None
}
}
);
})));
};
action
}); });
let mut action = None; if let Some(action) = response.inner {
return Some(action);
}
self.editor.show(ui, &mut self.buffer, &syntax::dsa()); self.editor.show(ui, &mut self.buffer, &syntax::dsa());
if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) { if ui.input(|i| i.key_pressed(Key::S) && i.modifiers.ctrl) {
if let Some(filename) = self.filename.clone() { return if let Some(filename) = self.filename.clone() {
action = Some(Action::SaveFile(filename, tile_id)); Some(Action::SaveFile(filename, tile_id))
} else { } else {
action = Some(Action::RequestSaveAs(tile_id)); Some(Action::RequestSaveAs(tile_id))
} }
}; };
action None
} }
fn as_any_mut(&mut self) -> &mut dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self }
@@ -86,3 +120,20 @@ impl TilePane for Editor {
} }
} }
@@ -0,0 +1,91 @@
use crate::ui::Action;
use common::process::{ProcessAgent, TraceableProcess};
use egui::Ui;
use std::collections::VecDeque;
use std::sync::mpsc;
use std::thread::JoinHandle;
pub type ProcessSpawner = Box<dyn FnOnce(&mut ProcessManager) + Send>;
pub struct ProcessHandle {
id: usize,
name: String,
rx: mpsc::Receiver<String>,
join_handle: Option<JoinHandle<Action>>,
// current process state
logs: Vec<String>,
}
impl ProcessHandle {
pub fn spawn<P: TraceableProcess>(
id: usize,
name: impl Into<String>,
process: P,
on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static
) -> Self {
let (tx, rx) = mpsc::channel::<String>();
let agent = ProcessAgent::new( tx );
let join_handle = std::thread::spawn(move || {
let result = TraceableProcess::run(Box::new(process), agent);
on_complete(result)
});
Self {
rx, id, name: name.into(), join_handle: Some(join_handle), logs: Vec::new()
}
}
}
#[derive(Default)]
pub struct ProcessManager {
_id_counter: usize,
handles: Vec<ProcessHandle>,
terminated_actions: VecDeque<Action>,
}
impl ProcessManager {
pub fn new() -> Self {
Default::default()
}
fn new_id(&mut self) -> usize {
let id = self._id_counter;
self._id_counter += 1;
id
}
pub fn get_action(&mut self) -> Option<Action> {
self.terminated_actions.pop_front()
}
pub fn spawn<P: TraceableProcess>(&mut self, process: P, on_complete: impl FnOnce(Result<P::Output, P::Error>) -> Action + Send + 'static) {
let handle = ProcessHandle::spawn(self.new_id(), "process", process, on_complete);
self.handles.push(handle);
}
pub fn ui(&mut self, ui: &mut Ui) {
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);
}
}
}
}
ui.horizontal(|ui| {
for h in &self.handles {
ui.label(&h.name);
};
});
}
}