Display emulation status

This commit is contained in:
momo5502
2025-07-11 13:40:11 +02:00
parent f00aac744f
commit 0794c1038b
13 changed files with 372 additions and 20 deletions

View File

@@ -1,6 +1,6 @@
import { Badge } from "@/components/ui/badge";
import { CircleFill } from "react-bootstrap-icons";
import { EmulationState as State } from "@/emulator";
import { EmulationStatus, EmulationState as State } from "@/emulator";
function getStateName(state: State) {
switch (state) {

View File

@@ -13,6 +13,18 @@ export enum EmulationState {
Failed,
}
export interface EmulationStatus {
executedInstructions: BigInt;
activeThreads: number;
}
function createDefaultEmulationStatus(): EmulationStatus {
return {
executedInstructions: BigInt(0),
activeThreads: 0,
};
}
export function isFinalState(state: EmulationState) {
switch (state) {
case EmulationState.Stopped:
@@ -55,10 +67,12 @@ function decodeEvent(data: string) {
}
type StateChangeHandler = (state: EmulationState) => void;
type StatusUpdateHandler = (status: EmulationStatus) => void;
export class Emulator {
logHandler: LogHandler;
stateChangeHandler: StateChangeHandler;
stautsUpdateHandler: StatusUpdateHandler;
terminatePromise: Promise<number | null>;
terminateResolve: (value: number | null) => void;
terminateReject: (reason?: any) => void;
@@ -66,9 +80,14 @@ export class Emulator {
state: EmulationState = EmulationState.Stopped;
exit_status: number | null = null;
constructor(logHandler: LogHandler, stateChangeHandler: StateChangeHandler) {
constructor(
logHandler: LogHandler,
stateChangeHandler: StateChangeHandler,
stautsUpdateHandler: StatusUpdateHandler,
) {
this.logHandler = logHandler;
this.stateChangeHandler = stateChangeHandler;
this.stautsUpdateHandler = stautsUpdateHandler;
this.terminateResolve = () => {};
this.terminateReject = () => {};
this.terminatePromise = new Promise((resolve, reject) => {
@@ -84,11 +103,13 @@ export class Emulator {
);
this.worker.onerror = this._onError.bind(this);
this.worker.onmessage = this._onMessage.bind(this);
this.worker.onmessage = (e) => queueMicrotask(() => this._onMessage(e));
}
async start(settings: Settings, file: string) {
this._setState(EmulationState.Running);
this.stautsUpdateHandler(createDefaultEmulationStatus());
this.worker.postMessage({
message: "run",
data: {
@@ -197,6 +218,11 @@ export class Emulator {
event.event as fbDebugger.ApplicationExitT,
);
break;
case fbDebugger.Event.EmulationStatus:
this._handle_emulation_status(
event.event as fbDebugger.EmulationStatusT,
);
break;
}
}
@@ -209,6 +235,13 @@ export class Emulator {
this.exit_status = info.exitStatus;
}
_handle_emulation_status(info: fbDebugger.EmulationStatusT) {
this.stautsUpdateHandler({
activeThreads: info.activeThreads,
executedInstructions: info.executedInstructions,
});
}
_handle_state_response(response: fbDebugger.GetStateResponseT) {
switch (response.state) {
case fbDebugger.State.None:

View File

@@ -4,6 +4,7 @@
export { ApplicationExit, ApplicationExitT } from './debugger/application-exit.js';
export { DebugEvent, DebugEventT } from './debugger/debug-event.js';
export { EmulationStatus, EmulationStatusT } from './debugger/emulation-status.js';
export { Event } from './debugger/event.js';
export { GetStateRequest, GetStateRequestT } from './debugger/get-state-request.js';
export { GetStateResponse, GetStateResponseT } from './debugger/get-state-response.js';

View File

@@ -5,6 +5,7 @@
import * as flatbuffers from 'flatbuffers';
import { ApplicationExit, ApplicationExitT } from '../debugger/application-exit.js';
import { EmulationStatus, EmulationStatusT } from '../debugger/emulation-status.js';
import { Event, unionToEvent, unionListToEvent } from '../debugger/event.js';
import { GetStateRequest, GetStateRequestT } from '../debugger/get-state-request.js';
import { GetStateResponse, GetStateResponseT } from '../debugger/get-state-response.js';
@@ -105,7 +106,7 @@ unpackTo(_o: DebugEventT): void {
export class DebugEventT implements flatbuffers.IGeneratedObject {
constructor(
public eventType: Event = Event.NONE,
public event: ApplicationExitT|GetStateRequestT|GetStateResponseT|PauseRequestT|ReadMemoryRequestT|ReadMemoryResponseT|ReadRegisterRequestT|ReadRegisterResponseT|RunRequestT|WriteMemoryRequestT|WriteMemoryResponseT|WriteRegisterRequestT|WriteRegisterResponseT|null = null
public event: ApplicationExitT|EmulationStatusT|GetStateRequestT|GetStateResponseT|PauseRequestT|ReadMemoryRequestT|ReadMemoryResponseT|ReadRegisterRequestT|ReadRegisterResponseT|RunRequestT|WriteMemoryRequestT|WriteMemoryResponseT|WriteRegisterRequestT|WriteRegisterResponseT|null = null
){}

View File

@@ -0,0 +1,110 @@
// automatically generated by the FlatBuffers compiler, do not modify
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import * as flatbuffers from 'flatbuffers';
export class EmulationStatus implements flatbuffers.IUnpackableObject<EmulationStatusT> {
bb: flatbuffers.ByteBuffer|null = null;
bb_pos = 0;
__init(i:number, bb:flatbuffers.ByteBuffer):EmulationStatus {
this.bb_pos = i;
this.bb = bb;
return this;
}
static getRootAsEmulationStatus(bb:flatbuffers.ByteBuffer, obj?:EmulationStatus):EmulationStatus {
return (obj || new EmulationStatus()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
static getSizePrefixedRootAsEmulationStatus(bb:flatbuffers.ByteBuffer, obj?:EmulationStatus):EmulationStatus {
bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH);
return (obj || new EmulationStatus()).__init(bb.readInt32(bb.position()) + bb.position(), bb);
}
executedInstructions():bigint {
const offset = this.bb!.__offset(this.bb_pos, 4);
return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0');
}
mutate_executed_instructions(value:bigint):boolean {
const offset = this.bb!.__offset(this.bb_pos, 4);
if (offset === 0) {
return false;
}
this.bb!.writeUint64(this.bb_pos + offset, value);
return true;
}
activeThreads():number {
const offset = this.bb!.__offset(this.bb_pos, 6);
return offset ? this.bb!.readUint32(this.bb_pos + offset) : 0;
}
mutate_active_threads(value:number):boolean {
const offset = this.bb!.__offset(this.bb_pos, 6);
if (offset === 0) {
return false;
}
this.bb!.writeUint32(this.bb_pos + offset, value);
return true;
}
static startEmulationStatus(builder:flatbuffers.Builder) {
builder.startObject(2);
}
static addExecutedInstructions(builder:flatbuffers.Builder, executedInstructions:bigint) {
builder.addFieldInt64(0, executedInstructions, BigInt('0'));
}
static addActiveThreads(builder:flatbuffers.Builder, activeThreads:number) {
builder.addFieldInt32(1, activeThreads, 0);
}
static endEmulationStatus(builder:flatbuffers.Builder):flatbuffers.Offset {
const offset = builder.endObject();
return offset;
}
static createEmulationStatus(builder:flatbuffers.Builder, executedInstructions:bigint, activeThreads:number):flatbuffers.Offset {
EmulationStatus.startEmulationStatus(builder);
EmulationStatus.addExecutedInstructions(builder, executedInstructions);
EmulationStatus.addActiveThreads(builder, activeThreads);
return EmulationStatus.endEmulationStatus(builder);
}
unpack(): EmulationStatusT {
return new EmulationStatusT(
this.executedInstructions(),
this.activeThreads()
);
}
unpackTo(_o: EmulationStatusT): void {
_o.executedInstructions = this.executedInstructions();
_o.activeThreads = this.activeThreads();
}
}
export class EmulationStatusT implements flatbuffers.IGeneratedObject {
constructor(
public executedInstructions: bigint = BigInt('0'),
public activeThreads: number = 0
){}
pack(builder:flatbuffers.Builder): flatbuffers.Offset {
return EmulationStatus.createEmulationStatus(builder,
this.executedInstructions,
this.activeThreads
);
}
}

View File

@@ -3,6 +3,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
import { ApplicationExit, ApplicationExitT } from '../debugger/application-exit.js';
import { EmulationStatus, EmulationStatusT } from '../debugger/emulation-status.js';
import { GetStateRequest, GetStateRequestT } from '../debugger/get-state-request.js';
import { GetStateResponse, GetStateResponseT } from '../debugger/get-state-response.js';
import { PauseRequest, PauseRequestT } from '../debugger/pause-request.js';
@@ -31,13 +32,14 @@ export enum Event {
WriteRegisterResponse = 10,
ReadRegisterRequest = 11,
ReadRegisterResponse = 12,
ApplicationExit = 13
ApplicationExit = 13,
EmulationStatus = 14
}
export function unionToEvent(
type: Event,
accessor: (obj:ApplicationExit|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse) => ApplicationExit|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null
): ApplicationExit|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null {
accessor: (obj:ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse) => ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null
): ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null {
switch(Event[type]) {
case 'NONE': return null;
case 'PauseRequest': return accessor(new PauseRequest())! as PauseRequest;
@@ -53,15 +55,16 @@ export function unionToEvent(
case 'ReadRegisterRequest': return accessor(new ReadRegisterRequest())! as ReadRegisterRequest;
case 'ReadRegisterResponse': return accessor(new ReadRegisterResponse())! as ReadRegisterResponse;
case 'ApplicationExit': return accessor(new ApplicationExit())! as ApplicationExit;
case 'EmulationStatus': return accessor(new EmulationStatus())! as EmulationStatus;
default: return null;
}
}
export function unionListToEvent(
type: Event,
accessor: (index: number, obj:ApplicationExit|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse) => ApplicationExit|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null,
accessor: (index: number, obj:ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse) => ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null,
index: number
): ApplicationExit|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null {
): ApplicationExit|EmulationStatus|GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null {
switch(Event[type]) {
case 'NONE': return null;
case 'PauseRequest': return accessor(index, new PauseRequest())! as PauseRequest;
@@ -77,6 +80,7 @@ export function unionListToEvent(
case 'ReadRegisterRequest': return accessor(index, new ReadRegisterRequest())! as ReadRegisterRequest;
case 'ReadRegisterResponse': return accessor(index, new ReadRegisterResponse())! as ReadRegisterResponse;
case 'ApplicationExit': return accessor(index, new ApplicationExit())! as ApplicationExit;
case 'EmulationStatus': return accessor(index, new EmulationStatus())! as EmulationStatus;
default: return null;
}
}

View File

@@ -23,6 +23,8 @@ import {
GearFill,
PauseFill,
HouseFill,
BarChartSteps,
CpuFill,
} from "react-bootstrap-icons";
import { StatusIndicator } from "@/components/status-indicator";
import { Header } from "./Header";
@@ -38,6 +40,7 @@ import {
DrawerTitle,
} from "@/components/ui/drawer";
import { FilesystemExplorer } from "./filesystem-explorer";
import { EmulationStatus } from "./emulator";
interface PlaygroundProps {}
interface PlaygroundState {
@@ -45,6 +48,7 @@ interface PlaygroundState {
filesystemPromise: Promise<Filesystem> | null;
filesystem: Filesystem | null;
emulator: Emulator | null;
emulationStatus: EmulationStatus | null;
application: string | undefined;
drawerOpen: boolean;
allowWasm64: boolean;
@@ -94,6 +98,10 @@ export class Playground extends React.Component<
filesystemPromise: null,
filesystem: null,
emulator: null,
emulationStatus: {
activeThreads: 5,
executedInstructions: BigInt(1233252643),
},
drawerOpen: false,
application: undefined,
allowWasm64: false,
@@ -124,6 +132,10 @@ export class Playground extends React.Component<
location.reload();
}
_onEmulatorStatusChanged(s: EmulationStatus) {
this.setState({ emulationStatus: s });
}
_onEmulatorStateChanged(s: EmulationState, persistFs: boolean) {
if (isFinalState(s) && persistFs) {
this.setState({ filesystemPromise: null, filesystem: null });
@@ -208,6 +220,7 @@ export class Playground extends React.Component<
const new_emulator = new Emulator(
(l) => this.logLines(l),
(s) => this._onEmulatorStateChanged(s, persistFs),
(s) => this._onEmulatorStatusChanged(s),
);
//new_emulator.onTerminate().then(() => this.setState({ emulator: null }));
@@ -320,7 +333,9 @@ export class Playground extends React.Component<
</Drawer>
)}
<div className="text-right flex-1">
<div className="flex-1"></div>
<div className="text-right items-center">
<StatusIndicator
application={this.state.application}
state={
@@ -331,8 +346,23 @@ export class Playground extends React.Component<
/>
</div>
</header>
<div className="flex flex-1 flex-col pl-1 overflow-auto">
<Output ref={this.output} />
<div className="flex flex-1">
<div className="items-center absolute z-100 right-0 rounded-bl-lg min-w-[140px] p-2 bg-[var(--background)] pointer-events-none font-medium text-right text-xs whitespace-nowrap leading-6">
{!this.state.emulationStatus ? (
<></>
) : (
<>
{this.state.emulationStatus.activeThreads}
<BarChartSteps className="inline ml-3" />
<br />
{this.state.emulationStatus.executedInstructions.toLocaleString()}
<CpuFill className="inline ml-3" />
</>
)}
</div>
<div className="flex flex-1 flex-col pl-1 overflow-auto">
<Output ref={this.output} />
</div>
</div>
</div>
</>