From a3388fae503b131ca56e4849f18b7a82a3dae37e Mon Sep 17 00:00:00 2001 From: Taylor Whatley <32211852+1whatleytay@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:31:16 -0400 Subject: [PATCH] Add display backend implementation --- src-backend/Cargo.lock | 10 +++---- src-backend/Cargo.toml | 2 +- src-backend/src/execution.rs | 4 +++ src-backend/src/syscall.rs | 27 ++++++++++++------- src-wasm/Cargo.lock | 39 ++++++++++++++++++++++----- src-wasm/Cargo.toml | 4 +++ src-wasm/src/lib.rs | 26 +++++++++++++----- src-wasm/src/time.rs | 15 +++++++++-- src/components/console/BitmapTab.vue | 31 +++++++++------------ src/utils/mips/mips.ts | 3 +++ src/utils/mips/tauri-backend.ts | 17 ++++++++++++ src/utils/mips/wasm-backend.ts | 9 +++++++ src/utils/mips/wasm-worker-message.ts | 11 +++++++- src/utils/mips/wasm-worker.ts | 9 ++++++- 14 files changed, 156 insertions(+), 51 deletions(-) diff --git a/src-backend/Cargo.lock b/src-backend/Cargo.lock index 0ab6563..4d00a2c 100644 --- a/src-backend/Cargo.lock +++ b/src-backend/Cargo.lock @@ -380,18 +380,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -427,7 +427,7 @@ dependencies = [ [[package]] name = "titan" version = "0.1.0" -source = "git+https://github.com/1whatleytay/titan.git?branch=main#7332463fd17053b23b402391c7a6c3f13058d51d" +source = "git+https://github.com/1whatleytay/titan.git?branch=main#860de2c31408930d93215c7e5ffcca9a58ba3f93" dependencies = [ "bitflags", "byteorder", diff --git a/src-backend/Cargo.toml b/src-backend/Cargo.toml index aafade7..486039a 100644 --- a/src-backend/Cargo.toml +++ b/src-backend/Cargo.toml @@ -18,6 +18,6 @@ base64 = "0.21.7" rand = "0.8.5" rand_chacha = "0.3.1" async-trait = "0.1.77" +log = "0.4.22" titan = { git = "https://github.com/1whatleytay/titan.git", branch = "main" } -log = "0.4.22" diff --git a/src-backend/src/execution.rs b/src-backend/src/execution.rs index ce770fd..af60b02 100644 --- a/src-backend/src/execution.rs +++ b/src-backend/src/execution.rs @@ -149,6 +149,7 @@ pub trait ExecutionRewindable { pub trait RewindableDevice: ExecutionDevice + ExecutionRewindable { } +#[derive(Debug, Clone)] pub struct BatchOptions { pub count: usize, // if true, the cancelled flag for syscall delegates will be cleared @@ -199,9 +200,12 @@ impl + Send> ExecutionDevice for Executi let state = self.delegate.clone(); let finished_pcs = self.finished_pcs.clone(); + log::info!("Resuming with batch options: {:?}", options.batch); + if let Some(breakpoints) = options.breakpoints { let breakpoints_set = HashSet::from_iter(breakpoints.iter().copied()); + log::info!("Overriding breakpoints: {:?}", breakpoints_set); debugger.set_breakpoints(breakpoints_set); } diff --git a/src-backend/src/syscall.rs b/src-backend/src/syscall.rs index cbf76ec..b211f41 100644 --- a/src-backend/src/syscall.rs +++ b/src-backend/src/syscall.rs @@ -13,6 +13,7 @@ use std::io::{Read, Write}; use std::pin::{Pin, pin}; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use async_trait::async_trait; use futures::future::FusedFuture; use titan::cpu::error::Error; use titan::cpu::error::Error::{CpuSyscall, CpuTrap}; @@ -32,6 +33,7 @@ pub struct MidiRequest { pub volume: u32, // 0 - 127 } +#[derive(Debug)] pub enum SyscallResult { Completed, // Syscall completed successfully. Failure(String), // Failed to complete with message. @@ -52,9 +54,10 @@ pub trait MidiHandler { fn installed(&mut self, instrument: u32) -> bool; } +#[async_trait] pub trait TimeHandler { fn time(&self) -> Option; - fn sleep(&self, duration: Duration) -> oneshot::Receiver<()>; + async fn sleep(&self, duration: Duration); } // Sync problems probably require something like this. @@ -71,7 +74,7 @@ pub struct SyscallState { heap_start: u32, console: Box, midi: Box, - time: Box, + time: Arc, generators: HashMap, next_file: u32, file_map: HashMap, @@ -81,7 +84,7 @@ impl SyscallState { pub fn new( console: Box, midi: Box, - time: Box, + time: Arc, ) -> SyscallState { SyscallState { cancel_token: CancelToken::None, @@ -159,9 +162,9 @@ impl SyscallDelegate { async fn send_print(&self, text: &str) { self.state.lock().unwrap().console.print(text, false); - let future = self.state.lock().unwrap().time.sleep(PRINT_BUFFER_TIME); + let time = self.state.lock().unwrap().time.clone(); - future.await.ok(); + time.sleep(PRINT_BUFFER_TIME).await; } fn play_installed(&self, request: &MidiRequest, sync: bool) -> bool { @@ -604,9 +607,9 @@ impl SyscallDelegate { async fn sleep_for_duration(&self, time: u64) { let duration = Duration::from_millis(time); - let future = self.state.lock().unwrap().time.sleep(duration); + let time = self.state.lock().unwrap().time.clone(); - future.await.ok(); + time.sleep(duration).await; } async fn sleep>(&self, debugger: &Executor) -> SyscallResult { @@ -806,11 +809,15 @@ impl SyscallDelegate { pub async fn run_batch>( &self, debugger: &Executor, batch: usize, should_skip_first: bool, allow_interrupt: bool ) -> Option<(DebugFrame, Option)> { - if !debugger.run_batched(batch, should_skip_first, allow_interrupt) { + log::info!("Running batch (size: {batch}, should_skip_first: {should_skip_first}, allow_interrupt: {allow_interrupt})"); + + if !debugger.run_batched(batch, should_skip_first, allow_interrupt).interrupted { return None // no interruption, batch completed successfully } - let (frame, result) = self.handle_frame(debugger, debugger.frame()).await; + let frame_in = debugger.frame(); + + let (frame, result) = self.handle_frame(debugger, frame_in).await; if let Some(frame) = frame { return Some((frame, result)); @@ -819,6 +826,8 @@ impl SyscallDelegate { // Need to re-check. let frame = debugger.frame(); + log::info!("Resulting debugger frame handled with output mode: {:?}, result: {:?}", frame.mode, result); + if frame.mode != Recovered { return Some((frame, None)) } diff --git a/src-wasm/Cargo.lock b/src-wasm/Cargo.lock index ab2a937..709ccca 100644 --- a/src-wasm/Cargo.lock +++ b/src-wasm/Cargo.lock @@ -161,6 +161,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "itoa" version = "1.0.11" @@ -421,10 +431,13 @@ dependencies = [ name = "saturn_wasm" version = "0.1.0" dependencies = [ + "async-trait", "console_error_panic_hook", "futures", "getrandom", + "gloo-timers", "js-sys", + "log", "saturn_backend", "serde", "serde-wasm-bindgen", @@ -432,6 +445,7 @@ dependencies = [ "titan", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-logger", ] [[package]] @@ -442,9 +456,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -462,9 +476,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -473,9 +487,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -512,7 +526,7 @@ dependencies = [ [[package]] name = "titan" version = "0.1.0" -source = "git+https://github.com/1whatleytay/titan.git?branch=main#7332463fd17053b23b402391c7a6c3f13058d51d" +source = "git+https://github.com/1whatleytay/titan.git?branch=main#860de2c31408930d93215c7e5ffcca9a58ba3f93" dependencies = [ "bitflags", "byteorder", @@ -609,6 +623,17 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[package]] +name = "wasm-logger" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" +dependencies = [ + "log", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.70" diff --git a/src-wasm/Cargo.toml b/src-wasm/Cargo.toml index bc874f0..44241a2 100644 --- a/src-wasm/Cargo.toml +++ b/src-wasm/Cargo.toml @@ -16,8 +16,12 @@ futures = "0.3.29" wasm-bindgen = "0.2.91" wasm-bindgen-futures = "0.4.41" serde-wasm-bindgen = "0.6.4" +async-trait = "0.1.82" js-sys = "0.3.70" +gloo-timers = "0.3.0" +log = "0.4.22" +wasm-logger = "0.2.0" console_error_panic_hook = { version = "0.1.7", optional = true } getrandom = { version = "0.2.12", features = ["js"] } diff --git a/src-wasm/src/lib.rs b/src-wasm/src/lib.rs index 0d31e9e..b62b0fd 100644 --- a/src-wasm/src/lib.rs +++ b/src-wasm/src/lib.rs @@ -28,6 +28,13 @@ use crate::console::WasmConsole; use crate::midi::WasmMidi; use crate::time::WasmTime; +#[wasm_bindgen] +pub fn initialize() { + wasm_logger::init(wasm_logger::Config::default()); + + console_error_panic_hook::set_once(); +} + #[wasm_bindgen] pub fn assemble_regions(text: &str, options: JsValue) -> JsValue { let result = saturn_backend::regions::assemble_regions( @@ -86,7 +93,7 @@ impl Runner { keyboard: Arc>, console: Box, midi: Box, - time: Box, + time: Arc, ) { if let Some(device) = &self.device { device.pause() @@ -110,7 +117,7 @@ impl Runner { keyboard: Arc>, console: Box, midi: Box, - time: Box, + time: Arc, ) { if let Some(device) = &self.device { device.pause() @@ -167,7 +174,7 @@ impl Runner { let console = Box::new(WasmConsole { }); let midi = Box::new(WasmMidi { }); - let time = Box::new(WasmTime { }); + let time = Arc::new(WasmTime { }); let history = HistoryTracker::new(TIME_TRAVEL_HISTORY_SIZE); let mut memory = SectionMemory::new(); @@ -221,7 +228,7 @@ impl Runner { let console = Box::new(WasmConsole { }); let midi = Box::new(WasmMidi { }); - let time = Box::new(WasmTime { }); + let time = Arc::new(WasmTime { }); let history = HistoryTracker::new(TIME_TRAVEL_HISTORY_SIZE); let mut memory = SectionMemory::new(); @@ -300,12 +307,19 @@ impl Runner { } } + pub fn read_display(&self, address: u32, width: u32, height: u32) -> Option> { + if let Some(device) = &self.device { + device.read_display(address, width, height) + } else { + None + } + } + pub async fn resume(&mut self, batch_size: usize, breakpoints: Option>, first_batch: bool, is_step: bool) -> JsValue { let Some(device) = &mut self.device else { return JsValue::NULL }; - // Some(batch_size), breakpoints, Some(self.display.clone()), set_running let result = device.resume(ResumeOptions { batch: Some(BatchOptions { count: batch_size, @@ -347,4 +361,4 @@ impl Runner { serde_wasm_bindgen::to_value(&result).unwrap() } -} \ No newline at end of file +} diff --git a/src-wasm/src/time.rs b/src-wasm/src/time.rs index ef0490e..04aafc6 100644 --- a/src-wasm/src/time.rs +++ b/src-wasm/src/time.rs @@ -1,15 +1,26 @@ use std::time::Duration; use saturn_backend::syscall::TimeHandler; use futures::channel::oneshot; +use async_trait::async_trait; pub struct WasmTime { } +#[async_trait] impl TimeHandler for WasmTime { fn time(&self) -> Option { Some(Duration::from_millis(js_sys::Date::now() as u64)) } - fn sleep(&self, duration: Duration) -> oneshot::Receiver<()> { - + async fn sleep(&self, duration: Duration) { + let (sender, receiver) = oneshot::channel::<()>(); + + { + // Is this going to panic? + gloo_timers::callback::Timeout::new(duration.as_millis() as u32, move || { + sender.send(()).ok(); + }).forget(); + } + + receiver.await.ok(); } } diff --git a/src/components/console/BitmapTab.vue b/src/components/console/BitmapTab.vue index 5550659..328144d 100644 --- a/src/components/console/BitmapTab.vue +++ b/src/components/console/BitmapTab.vue @@ -100,8 +100,9 @@ target="_blank" href="https://github.com/1whatleytay/saturn" class="underline hover:text-gray-300" - >https://github.com/1whatleytay/saturn. + > + https://github.com/1whatleytay/saturn + . @@ -291,9 +292,9 @@ async function renderFrameFallback( context: CanvasRenderingContext2D, execution: MipsExecution ) { - const memory = await execution.memoryAt(0x10008000, 64 * 64 * 4) + const { width, height, address} = config.value - const { width, height } = config.value + const memory = await execution.memoryAt(address, width * height * 4) const pixels = width * height * 4 @@ -317,8 +318,6 @@ async function renderFrameFallback( context.putImageData(data, 0, 0) } -const protocol = convertFileSrc('', 'display') - function renderOrdered( context: CanvasRenderingContext2D, width: number, @@ -335,21 +334,15 @@ function renderOrdered( } async function renderFrameProtocol(context: CanvasRenderingContext2D) { - const { width, height } = config.value - - const result = await fetch(protocol, { - headers: { - width: width.toString(), - height: height.toString(), - address: settings.bitmap.address.toString(), - }, - mode: 'cors', - cache: 'no-cache', - }) + const { width, height, address } = config.value - const memory = new Uint8Array(await result.arrayBuffer()) + if (consoleData.execution) { + const memory = await consoleData.execution.readDisplay(width, height, address) - renderOrdered(context, width, height, memory) + if (memory) { + renderOrdered(context, width, height, memory) + } + } } async function renderLastDisplay(context: CanvasRenderingContext2D) { diff --git a/src/utils/mips/mips.ts b/src/utils/mips/mips.ts index 3c6ed9d..71948da 100644 --- a/src/utils/mips/mips.ts +++ b/src/utils/mips/mips.ts @@ -279,4 +279,7 @@ export interface MipsExecution { ): Promise<(number | null)[] | null> setRegister(register: number, value: number): Promise setMemory(address: number, bytes: number[]): Promise + + // Live display, should generally be more performant on tauri. + readDisplay(width: number, height: number, address: number): Promise } diff --git a/src/utils/mips/tauri-backend.ts b/src/utils/mips/tauri-backend.ts index 76bc986..820d113 100644 --- a/src/utils/mips/tauri-backend.ts +++ b/src/utils/mips/tauri-backend.ts @@ -11,11 +11,14 @@ import { import { ExportRegionsOptions } from '../settings' import { tauri } from '@tauri-apps/api' +import { convertFileSrc } from '@tauri-apps/api/tauri' export class TauriExecution implements MipsExecution { configured: boolean = false public breakpoints: Breakpoints | null + protocol = convertFileSrc('', 'display') + async configure(): Promise { if (this.configured) { return null @@ -154,6 +157,20 @@ export class TauriExecution implements MipsExecution { await tauri.invoke('write_bytes', { address, bytes }) } + async readDisplay(width: number, height: number, address: number): Promise { + const result = await fetch(this.protocol, { + headers: { + width: width.toString(), + height: height.toString(), + address: address.toString(), + }, + mode: 'cors', + cache: 'no-cache', + }) + + return new Uint8Array(await result.arrayBuffer()) + } + public constructor( public text: string, public path: string | null, diff --git a/src/utils/mips/wasm-backend.ts b/src/utils/mips/wasm-backend.ts index e4c9829..6ca7956 100644 --- a/src/utils/mips/wasm-backend.ts +++ b/src/utils/mips/wasm-backend.ts @@ -258,6 +258,15 @@ export class WasmExecution implements MipsExecution { }) } + readDisplay(width: number, height: number, address: number): Promise { + return this.backend.sendRequest({ + op: MessageOp.ReadDisplay, + width, + height, + address + }) + } + constructor( public backend: WasmBackend, public text: string, diff --git a/src/utils/mips/wasm-worker-message.ts b/src/utils/mips/wasm-worker-message.ts index bf607b8..96f9717 100644 --- a/src/utils/mips/wasm-worker-message.ts +++ b/src/utils/mips/wasm-worker-message.ts @@ -24,6 +24,7 @@ export enum MessageOp { PostKey, WakeSync, Rewind, + ReadDisplay, } export interface AssembleRegionsData { @@ -154,6 +155,13 @@ export interface RewindData { count: number } +export interface ReadDisplayData { + op: MessageOp.ReadDisplay + width: number + height: number + address: number +} + export type MessageData = AssembleRegionsData | AssembleTextData | @@ -176,7 +184,8 @@ export type MessageData = PostInputData | PostKeyData | WakeSyncData | - RewindData + RewindData | + ReadDisplayData export interface Message { id: number diff --git a/src/utils/mips/wasm-worker.ts b/src/utils/mips/wasm-worker.ts index 7f62224..8739e7b 100644 --- a/src/utils/mips/wasm-worker.ts +++ b/src/utils/mips/wasm-worker.ts @@ -27,13 +27,15 @@ import { MessageResponse, PostInputData, PostKeyData, - ReadBytesData, + ReadBytesData, ReadDisplayData, ResumeData, RewindData, SetBreakpointsData, SetRegisterData, WriteBytesData } from './wasm-worker-message' +backend.initialize() + // Runner/Execution State (Automatically Freed with the Worker Memory) const runner = new backend.Runner(); @@ -165,6 +167,10 @@ function rewind({ count }: RewindData): ExecutionResult | null { return runner.rewind(count) } +function readDisplay({ width, height, address }: ReadDisplayData) { + return runner.read_display(address, width, height) +} + async function dispatchOp(data: MessageData): Promise { switch (data.op) { case MessageOp.AssembleRegions: return assembleRegions(data) @@ -189,6 +195,7 @@ async function dispatchOp(data: MessageData): Promise { case MessageOp.PostKey: return postKey(data) case MessageOp.WakeSync: return wakeSync() case MessageOp.Rewind: return rewind(data) + case MessageOp.ReadDisplay: return readDisplay(data) } }