From 99f2f47e623f420384248bf4520a2d8553903ba4 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Mon, 28 Apr 2025 14:58:08 +0200 Subject: [PATCH] Support toggling emulation state --- page/public/emulator-worker.js | 2 +- page/src/Playground.tsx | 37 +++++++++++--- page/src/components/status-indicator.tsx | 42 +++++++++++---- page/src/emulator.ts | 65 +++++++++++++++++++++++- src/debugger/event_handler.cpp | 10 ++-- 5 files changed, 130 insertions(+), 26 deletions(-) diff --git a/page/public/emulator-worker.js b/page/public/emulator-worker.js index 91f9725f..feeee17d 100644 --- a/page/public/emulator-worker.js +++ b/page/public/emulator-worker.js @@ -46,7 +46,7 @@ function getMessageFromQueue() { return ""; } - return msgQueue.pop(); + return msgQueue.shift(); } function runEmulation(filesystem, file, options) { diff --git a/page/src/Playground.tsx b/page/src/Playground.tsx index 6e018ef5..ea949959 100644 --- a/page/src/Playground.tsx +++ b/page/src/Playground.tsx @@ -1,4 +1,4 @@ -import { useState, useRef } from "react"; +import { useState, useRef, useReducer } from "react"; import { Output } from "@/components/output"; import { AppSidebar } from "@/components/app-sidebar"; @@ -10,7 +10,7 @@ import { } from "@/components/ui/sidebar"; import { Button } from "./components/ui/button"; -import { Emulator, UserFile } from "./emulator"; +import { Emulator, UserFile, EmulationState } from "./emulator"; import { getFilesystem } from "./filesystem"; import "./App.css"; @@ -23,7 +23,7 @@ import { import { createDefaultSettings } from "./settings"; import { SettingsMenu } from "./components/settings-menu"; -import { PlayFill, StopFill, GearFill } from "react-bootstrap-icons"; +import { PlayFill, StopFill, GearFill, PauseFill } from "react-bootstrap-icons"; import { StatusIndicator } from "./components/status-indicator"; import { Header } from "./Header"; @@ -64,6 +64,7 @@ export function Playground() { const output = useRef(null); const [settings, setSettings] = useState(createDefaultSettings()); const [emulator, setEmulator] = useState(null); + const [, forceUpdate] = useReducer((x) => x + 1, 0); function logLine(line: string) { output.current?.logLine(line); @@ -73,6 +74,18 @@ export function Playground() { output.current?.logLines(lines); } + function isEmulatorPaused() { + return emulator && emulator.getState() == EmulationState.Paused; + } + + function toggleEmulatorState() { + if (isEmulatorPaused()) { + emulator?.resume(); + } else { + emulator?.pause(); + } + } + async function createEmulator(userFile: UserFile | null = null) { emulator?.stop(); output.current?.clear(); @@ -83,7 +96,7 @@ export function Playground() { logLine(`Processing filesystem (${current}/${total}): ${file}`); }); - const new_emulator = new Emulator(fs, logLines); + const new_emulator = new Emulator(fs, logLines, (_) => forceUpdate()); new_emulator.onTerminate().then(() => setEmulator(null)); setEmulator(new_emulator); @@ -116,8 +129,16 @@ export function Playground() { - @@ -131,7 +152,9 @@ export function Playground() {
- +
diff --git a/page/src/components/status-indicator.tsx b/page/src/components/status-indicator.tsx index 4bd469b5..1b627bbd 100644 --- a/page/src/components/status-indicator.tsx +++ b/page/src/components/status-indicator.tsx @@ -1,28 +1,48 @@ import { Badge } from "@/components/ui/badge"; import { CircleFill } from "react-bootstrap-icons"; +import { EmulationState as State } from "@/emulator"; + +function getStateName(state: State) { + switch (state) { + case State.Stopped: + return "Stopped"; + case State.Paused: + return "Paused"; + case State.Running: + return "Running"; + default: + return ""; + } +} + +function getStateColor(state: State) { + switch (state) { + case State.Stopped: + return "bg-orange-600"; + case State.Paused: + return "bg-amber-500"; + case State.Running: + return "bg-lime-600"; + default: + return ""; + } +} export interface StatusIndicatorProps { - running: boolean; + state: State; } export function StatusIndicator(props: StatusIndicatorProps) { - const getText = () => { - return props.running ? " Running" : " Stopped"; - }; - - const getColor = () => { - return props.running ? "bg-lime-600" : "bg-orange-600"; - }; - return ( - {getText()} + {getStateName(props.state)} ); } diff --git a/page/src/emulator.ts b/page/src/emulator.ts index 61a0024f..123dc823 100644 --- a/page/src/emulator.ts +++ b/page/src/emulator.ts @@ -12,6 +12,12 @@ export interface UserFile { data: ArrayBuffer; } +export enum EmulationState { + Stopped, + Paused, + Running, +} + function base64Encode(uint8Array: Uint8Array): string { let binaryString = ""; for (let i = 0; i < uint8Array.byteLength; i++) { @@ -41,17 +47,26 @@ function decodeEvent(data: string) { return event.unpack(); } +type StateChangeHandler = (state: EmulationState) => void; + export class Emulator { filesystem: FileEntry[]; logHandler: LogHandler; + stateChangeHandler: StateChangeHandler; terminatePromise: Promise; terminateResolve: (value: number | null) => void; terminateReject: (reason?: any) => void; worker: Worker; + state: EmulationState = EmulationState.Stopped; - constructor(filesystem: FileEntry[], logHandler: LogHandler) { + constructor( + filesystem: FileEntry[], + logHandler: LogHandler, + stateChangeHandler: StateChangeHandler, + ) { this.filesystem = filesystem; this.logHandler = logHandler; + this.stateChangeHandler = stateChangeHandler; this.terminateResolve = () => {}; this.terminateReject = () => {}; this.terminatePromise = new Promise((resolve, reject) => { @@ -81,6 +96,7 @@ export class Emulator { }); } + this._setState(EmulationState.Running); this.worker.postMessage({ message: "run", data: { @@ -91,6 +107,19 @@ export class Emulator { }); } + updateState() { + this.sendEvent( + new fbDebugger.DebugEventT( + fbDebugger.Event.GetStateRequest, + new fbDebugger.GetStateRequestT(), + ), + ); + } + + getState() { + return this.state; + } + stop() { this.worker.terminate(); this.terminateResolve(null); @@ -119,6 +148,8 @@ export class Emulator { new fbDebugger.PauseRequestT(), ), ); + + this.updateState(); } resume() { @@ -128,6 +159,8 @@ export class Emulator { new fbDebugger.RunRequestT(), ), ); + + this.updateState(); } _onMessage(event: MessageEvent) { @@ -136,11 +169,39 @@ export class Emulator { } else if (event.data.message == "event") { this._onEvent(decodeEvent(event.data.data)); } else if (event.data.message == "end") { + this._setState(EmulationState.Stopped); this.terminateResolve(0); } } _onEvent(event: fbDebugger.DebugEventT) { - console.log(event); + switch (event.eventType) { + case fbDebugger.Event.GetStateResponse: + this._handle_state_response( + event.event as fbDebugger.GetStateResponseT, + ); + break; + } + } + + _setState(state: EmulationState) { + this.state = state; + this.stateChangeHandler(this.state); + } + + _handle_state_response(response: fbDebugger.GetStateResponseT) { + switch (response.state) { + case fbDebugger.State.None: + this._setState(EmulationState.Stopped); + break; + + case fbDebugger.State.Paused: + this._setState(EmulationState.Paused); + break; + + case fbDebugger.State.Running: + this._setState(EmulationState.Running); + break; + } } } diff --git a/src/debugger/event_handler.cpp b/src/debugger/event_handler.cpp index d607d239..72d24bd2 100644 --- a/src/debugger/event_handler.cpp +++ b/src/debugger/event_handler.cpp @@ -125,15 +125,15 @@ namespace debugger void handle_read_register(const event_context& c, const Debugger::ReadRegisterRequestT& request) { - uint8_t buffer[512]{}; - const auto res = - c.win_emu.emu().read_register(static_cast(request.register_), buffer, sizeof(buffer)); + std::array buffer{}; + const auto res = c.win_emu.emu().read_register(static_cast(request.register_), buffer.data(), + buffer.size()); - const auto size = std::min(sizeof(buffer), res); + const auto size = std::min(buffer.size(), res); Debugger::ReadRegisterResponseT response{}; response.register_ = request.register_; - response.data.assign(buffer, buffer + size); + response.data.assign(buffer.data(), buffer.data() + size); send_event(std::move(response)); }