mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-18 19:23:56 +00:00
Support toggling emulation state
This commit is contained in:
@@ -46,7 +46,7 @@ function getMessageFromQueue() {
|
||||
return "";
|
||||
}
|
||||
|
||||
return msgQueue.pop();
|
||||
return msgQueue.shift();
|
||||
}
|
||||
|
||||
function runEmulation(filesystem, file, options) {
|
||||
|
||||
@@ -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<Output>(null);
|
||||
const [settings, setSettings] = useState(createDefaultSettings());
|
||||
const [emulator, setEmulator] = useState<Emulator | null>(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() {
|
||||
<Button variant="secondary" onClick={() => emulator?.stop()}>
|
||||
<StopFill /> Stop Emulation
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={() => emulator?.pause()}>
|
||||
<StopFill /> Pause Emulation
|
||||
<Button variant="secondary" onClick={toggleEmulatorState}>
|
||||
{isEmulatorPaused() ? (
|
||||
<>
|
||||
<PlayFill /> Resume Emulation
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PauseFill /> Pause Emulation
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Popover>
|
||||
@@ -131,7 +152,9 @@ export function Playground() {
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="text-right flex-1">
|
||||
<StatusIndicator running={!!emulator} />
|
||||
<StatusIndicator
|
||||
state={emulator ? emulator.getState() : EmulationState.Stopped}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<div className="flex flex-1 flex-col gap-4 p-4 overflow-auto">
|
||||
|
||||
@@ -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 (
|
||||
<Badge variant="outline">
|
||||
<CircleFill
|
||||
className={
|
||||
getColor() + " rounded-full mr-1 n duration-200 ease-in-out"
|
||||
getStateColor(props.state) +
|
||||
" rounded-full mr-1 n duration-200 ease-in-out"
|
||||
}
|
||||
color="transparent"
|
||||
/>
|
||||
{getText()}
|
||||
{getStateName(props.state)}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<number | null>;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<x86_register>(request.register_), buffer, sizeof(buffer));
|
||||
std::array<uint8_t, 512> buffer{};
|
||||
const auto res = c.win_emu.emu().read_register(static_cast<x86_register>(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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user