diff --git a/page/public/emulator-worker.js b/page/public/emulator-worker.js index 483f26ad..b4184fa3 100644 --- a/page/public/emulator-worker.js +++ b/page/public/emulator-worker.js @@ -75,7 +75,9 @@ function runEmulation(file, options, persist) { FS.mkdir("/root"); FS.mount(IDBFS, {}, "/root"); FS.syncfs(true, function (_) { - Module.callMain(mainArguments); + setTimeout(() => { + Module.callMain(mainArguments); + }, 0); }); }, print: logLine, diff --git a/page/src/components/status-indicator.tsx b/page/src/components/status-indicator.tsx index b4eeee94..dc50020e 100644 --- a/page/src/components/status-indicator.tsx +++ b/page/src/components/status-indicator.tsx @@ -10,6 +10,10 @@ function getStateName(state: State) { return "Paused"; case State.Running: return "Running"; + case State.Failed: + return "Failed"; + case State.Success: + return "Success"; default: return ""; } @@ -17,12 +21,16 @@ function getStateName(state: State) { function getStateColor(state: State) { switch (state) { - case State.Stopped: + case State.Failed: return "bg-orange-600"; case State.Paused: return "bg-amber-500"; - case State.Running: + case State.Success: return "bg-lime-600"; + case State.Stopped: + return "bg-yellow-800"; + case State.Running: + return "bg-sky-500"; default: return ""; } @@ -31,10 +39,14 @@ function getStateColor(state: State) { function getStateEmoji(state: State) { switch (state) { case State.Stopped: - return "🔴"; + return "🟤"; case State.Paused: return "🟡"; case State.Running: + return "🔵"; + case State.Failed: + return "🔴"; + case State.Success: return "🟢"; default: return ""; diff --git a/page/src/emulator.ts b/page/src/emulator.ts index 4499b935..851f4709 100644 --- a/page/src/emulator.ts +++ b/page/src/emulator.ts @@ -9,6 +9,20 @@ export enum EmulationState { Stopped, Paused, Running, + Success, + Failed, +} + +export function isFinalState(state: EmulationState) { + switch (state) { + case EmulationState.Stopped: + case EmulationState.Success: + case EmulationState.Failed: + return true; + + default: + return false; + } } function base64Encode(uint8Array: Uint8Array): string { @@ -50,6 +64,7 @@ export class Emulator { terminateReject: (reason?: any) => void; worker: Worker; state: EmulationState = EmulationState.Stopped; + exit_status: number | null = null; constructor(logHandler: LogHandler, stateChangeHandler: StateChangeHandler) { this.logHandler = logHandler; @@ -142,8 +157,10 @@ 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); + this._setState( + this.exit_status === 0 ? EmulationState.Success : EmulationState.Failed, + ); + this.terminateResolve(this.exit_status); } } @@ -154,6 +171,11 @@ export class Emulator { event.event as fbDebugger.GetStateResponseT, ); break; + case fbDebugger.Event.ApplicationExit: + this._handle_application_exit( + event.event as fbDebugger.ApplicationExitT, + ); + break; } } @@ -162,6 +184,10 @@ export class Emulator { this.stateChangeHandler(this.state); } + _handle_application_exit(info: fbDebugger.ApplicationExitT) { + this.exit_status = info.exitStatus; + } + _handle_state_response(response: fbDebugger.GetStateResponseT) { switch (response.state) { case fbDebugger.State.None: diff --git a/page/src/fb/debugger.ts b/page/src/fb/debugger.ts index c5f2befb..e181f189 100644 --- a/page/src/fb/debugger.ts +++ b/page/src/fb/debugger.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ +export { ApplicationExit, ApplicationExitT } from './debugger/application-exit.js'; export { DebugEvent, DebugEventT } from './debugger/debug-event.js'; export { Event } from './debugger/event.js'; export { GetStateRequest, GetStateRequestT } from './debugger/get-state-request.js'; diff --git a/page/src/fb/debugger/application-exit.ts b/page/src/fb/debugger/application-exit.ts new file mode 100644 index 00000000..5b5afbf5 --- /dev/null +++ b/page/src/fb/debugger/application-exit.ts @@ -0,0 +1,86 @@ +// 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 ApplicationExit implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|null = null; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ApplicationExit { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsApplicationExit(bb:flatbuffers.ByteBuffer, obj?:ApplicationExit):ApplicationExit { + return (obj || new ApplicationExit()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsApplicationExit(bb:flatbuffers.ByteBuffer, obj?:ApplicationExit):ApplicationExit { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ApplicationExit()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +exitStatus():number|null { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.readUint32(this.bb_pos + offset) : null; +} + +mutate_exit_status(value:number):boolean { + const offset = this.bb!.__offset(this.bb_pos, 4); + + if (offset === 0) { + return false; + } + + this.bb!.writeUint32(this.bb_pos + offset, value); + return true; +} + +static startApplicationExit(builder:flatbuffers.Builder) { + builder.startObject(1); +} + +static addExitStatus(builder:flatbuffers.Builder, exitStatus:number) { + builder.addFieldInt32(0, exitStatus, null); +} + +static endApplicationExit(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createApplicationExit(builder:flatbuffers.Builder, exitStatus:number|null):flatbuffers.Offset { + ApplicationExit.startApplicationExit(builder); + if (exitStatus !== null) + ApplicationExit.addExitStatus(builder, exitStatus); + return ApplicationExit.endApplicationExit(builder); +} + +unpack(): ApplicationExitT { + return new ApplicationExitT( + this.exitStatus() + ); +} + + +unpackTo(_o: ApplicationExitT): void { + _o.exitStatus = this.exitStatus(); +} +} + +export class ApplicationExitT implements flatbuffers.IGeneratedObject { +constructor( + public exitStatus: number|null = null +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + return ApplicationExit.createApplicationExit(builder, + this.exitStatus + ); +} +} diff --git a/page/src/fb/debugger/debug-event.ts b/page/src/fb/debugger/debug-event.ts index 9be99bfa..cb43a59f 100644 --- a/page/src/fb/debugger/debug-event.ts +++ b/page/src/fb/debugger/debug-event.ts @@ -4,6 +4,7 @@ import * as flatbuffers from 'flatbuffers'; +import { ApplicationExit, ApplicationExitT } from '../debugger/application-exit.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'; @@ -104,7 +105,7 @@ unpackTo(_o: DebugEventT): void { export class DebugEventT implements flatbuffers.IGeneratedObject { constructor( public eventType: Event = Event.NONE, - public event: GetStateRequestT|GetStateResponseT|PauseRequestT|ReadMemoryRequestT|ReadMemoryResponseT|ReadRegisterRequestT|ReadRegisterResponseT|RunRequestT|WriteMemoryRequestT|WriteMemoryResponseT|WriteRegisterRequestT|WriteRegisterResponseT|null = null + public event: ApplicationExitT|GetStateRequestT|GetStateResponseT|PauseRequestT|ReadMemoryRequestT|ReadMemoryResponseT|ReadRegisterRequestT|ReadRegisterResponseT|RunRequestT|WriteMemoryRequestT|WriteMemoryResponseT|WriteRegisterRequestT|WriteRegisterResponseT|null = null ){} diff --git a/page/src/fb/debugger/event.ts b/page/src/fb/debugger/event.ts index fde2a752..a9b010a5 100644 --- a/page/src/fb/debugger/event.ts +++ b/page/src/fb/debugger/event.ts @@ -2,6 +2,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 { 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'; @@ -29,13 +30,14 @@ export enum Event { WriteRegisterRequest = 9, WriteRegisterResponse = 10, ReadRegisterRequest = 11, - ReadRegisterResponse = 12 + ReadRegisterResponse = 12, + ApplicationExit = 13 } export function unionToEvent( type: Event, - accessor: (obj:GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse) => GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null -): GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null { + 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 { switch(Event[type]) { case 'NONE': return null; case 'PauseRequest': return accessor(new PauseRequest())! as PauseRequest; @@ -50,15 +52,16 @@ export function unionToEvent( case 'WriteRegisterResponse': return accessor(new WriteRegisterResponse())! as WriteRegisterResponse; case 'ReadRegisterRequest': return accessor(new ReadRegisterRequest())! as ReadRegisterRequest; case 'ReadRegisterResponse': return accessor(new ReadRegisterResponse())! as ReadRegisterResponse; + case 'ApplicationExit': return accessor(new ApplicationExit())! as ApplicationExit; default: return null; } } export function unionListToEvent( type: Event, - accessor: (index: number, obj:GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse) => GetStateRequest|GetStateResponse|PauseRequest|ReadMemoryRequest|ReadMemoryResponse|ReadRegisterRequest|ReadRegisterResponse|RunRequest|WriteMemoryRequest|WriteMemoryResponse|WriteRegisterRequest|WriteRegisterResponse|null, + 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, index: number -): 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 { switch(Event[type]) { case 'NONE': return null; case 'PauseRequest': return accessor(index, new PauseRequest())! as PauseRequest; @@ -73,6 +76,7 @@ export function unionListToEvent( case 'WriteRegisterResponse': return accessor(index, new WriteRegisterResponse())! as WriteRegisterResponse; 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; default: return null; } } diff --git a/page/src/playground.tsx b/page/src/playground.tsx index 70484712..d13e4243 100644 --- a/page/src/playground.tsx +++ b/page/src/playground.tsx @@ -2,7 +2,7 @@ import React from "react"; import { Output } from "@/components/output"; -import { Emulator, EmulationState } from "./emulator"; +import { Emulator, EmulationState, isFinalState } from "./emulator"; import { Filesystem, setupFilesystem } from "./filesystem"; import "./App.css"; @@ -108,7 +108,7 @@ export class Playground extends React.Component< } _onEmulatorStateChanged(s: EmulationState, persistFs: boolean) { - if (s == EmulationState.Stopped && persistFs) { + if (isFinalState(s) && persistFs) { this.setState({ filesystemPromise: null, filesystem: null }); this.initFilesys(true); } else { @@ -192,7 +192,7 @@ export class Playground extends React.Component< (l) => this.logLines(l), (s) => this._onEmulatorStateChanged(s, persistFs), ); - new_emulator.onTerminate().then(() => this.setState({ emulator: null })); + //new_emulator.onTerminate().then(() => this.setState({ emulator: null })); this.setState({ emulator: new_emulator, application: userFile }); @@ -213,7 +213,10 @@ export class Playground extends React.Component<