diff --git a/engines/execution-controller.ts b/engines/execution-controller.ts index 8f89fc0..e00de83 100644 --- a/engines/execution-controller.ts +++ b/engines/execution-controller.ts @@ -134,6 +134,16 @@ class ExecutionController<RS> { return this._result; } + /** + * Run a single step of execution + * @returns Result of execution + */ + executeStep(): StepExecutionResult<RS> { + this._result = this._engine.executeStep(); + this._result.signal = "paused"; + return this._result; + } + /** Asynchronously sleep for a period of time */ private async sleep(millis: number) { return new Promise<void>((resolve) => setTimeout(resolve, millis)); diff --git a/engines/worker-constants.ts b/engines/worker-constants.ts index cec0a74..0091899 100644 --- a/engines/worker-constants.ts +++ b/engines/worker-constants.ts @@ -22,6 +22,10 @@ export type WorkerRequestData = type: "Execute"; params: { interval?: number }; } + | { + type: "ExecuteStep"; + params?: null; + } | { type: "Pause"; params?: null; diff --git a/engines/worker.ts b/engines/worker.ts index 94826f1..3840fb4 100644 --- a/engines/worker.ts +++ b/engines/worker.ts @@ -64,7 +64,6 @@ const updateBreakpoints = (points: number[]) => { * and return result of execution. */ const execute = (interval?: number) => { - console.info(`Executing at interval ${interval}`); _controller!.executeAll({ interval, onResult: (res) => postMessage(resultMessage(res)), @@ -79,12 +78,21 @@ const pauseExecution = async () => { postMessage(ackMessage("pause")); }; +/** + * Run a single execution step + */ +const executeStep = () => { + const result = _controller!.executeStep(); + postMessage(resultMessage(result)); +}; + addEventListener("message", async (ev: MessageEvent<WorkerRequestData>) => { if (ev.data.type === "Init") return initController(); if (ev.data.type === "Reset") return resetController(); if (ev.data.type === "Prepare") return prepare(ev.data.params); if (ev.data.type === "Execute") return execute(ev.data.params.interval); if (ev.data.type === "Pause") return await pauseExecution(); + if (ev.data.type === "ExecuteStep") return executeStep(); if (ev.data.type === "UpdateBreakpoints") return updateBreakpoints(ev.data.params.points); throw new Error("Invalid worker message type"); diff --git a/ui/Mainframe.tsx b/ui/Mainframe.tsx index c586a45..dc7c1b6 100644 --- a/ui/Mainframe.tsx +++ b/ui/Mainframe.tsx @@ -57,6 +57,21 @@ export const Mainframe = () => { await execController.pauseExecution(); }; + /** Run a single step of execution */ + const executeStep = async () => { + // Check if controller is paused + if (execController.state !== "paused") { + console.error("Controller not paused"); + return; + } + + // Run and update execution states + const result = await execController.executeStep(); + setRendererState(result.rendererState); + setCodeHighlights(result.nextStepLocation || undefined); + setOutput((o) => (o || "") + (result.output || "")); + }; + /** Resume the currently paused execution */ const resumeExecution = async () => { // Check if controller is indeed paused @@ -125,6 +140,7 @@ export const Mainframe = () => { onRun={runProgram} onPause={pauseExecution} onResume={resumeExecution} + onStep={executeStep} onStop={stopExecution} /> )} diff --git a/ui/execution-controls.tsx b/ui/execution-controls.tsx index 3942bc4..983054d 100644 --- a/ui/execution-controls.tsx +++ b/ui/execution-controls.tsx @@ -24,6 +24,7 @@ const DebugControls = (props: { paused: boolean; onPause: () => void; onResume: () => void; + onStep: () => void; onStop: () => void; }) => { return ( @@ -37,6 +38,7 @@ const DebugControls = (props: { <Button small title="Step" + onClick={props.onStep} disabled={!props.paused} icon={<Icon icon="step-forward" intent="warning" />} /> @@ -55,6 +57,7 @@ type Props = { onRun: () => void; onPause: () => void; onResume: () => void; + onStep: () => void; onStop: () => void; }; @@ -68,6 +71,7 @@ export const ExecutionControls = (props: Props) => { paused={props.state === "paused"} onPause={props.onPause} onResume={props.onResume} + onStep={props.onStep} onStop={props.onStop} /> )} diff --git a/ui/use-exec-controller.ts b/ui/use-exec-controller.ts index d4f6adc..cb39d8f 100644 --- a/ui/use-exec-controller.ts +++ b/ui/use-exec-controller.ts @@ -140,6 +140,20 @@ export const useExecController = <RS>() => { }); }, []); + /** + * Run a single step of execution + * @return Execution result + */ + const executeStep = React.useCallback(async () => { + const res = await requestWorker( + { type: "ExecuteStep" }, + (res) => res.type !== "result" + ); + if (res.type !== "result") throw new Error("Something unexpected happened"); + if (!res.data.nextStepLocation) setWorkerState("done"); + return res.data; + }, []); + /** * Execute the code loaded into the engine * @param onResult Callback used when an execution result is received @@ -172,6 +186,7 @@ export const useExecController = <RS>() => { prepare, pauseExecution, execute, + executeStep, updateBreakpoints, }; };