Add pause-resume-stop functionality and UI

This commit is contained in:
Nilay Majorwar
2021-12-15 21:31:25 +05:30
parent 29b243d6f2
commit 13ff6da638
8 changed files with 262 additions and 31 deletions

View File

@ -14,6 +14,8 @@ class ExecutionController<RS> {
private _engine: LanguageEngine<RS>;
private _breakpoints: number[] = [];
private _result: StepExecutionResult<RS> | null;
private _resolvePause: (() => void) | null = null;
private _isPaused: boolean = false;
/**
* Create a new ExecutionController.
@ -51,16 +53,75 @@ class ExecutionController<RS> {
this._breakpoints = points;
}
/**
* Pause the ongoing execution.
* - If already paused, returns immediately
* - Queues up with any existing pause calls
* @returns Promise that resolves when execution is paused
*/
async pauseExecution(): Promise<void> {
// If already paused, return immediately
if (this._isPaused) return;
// If there's another "pause" call waiting, chain up with older resolver.
// This kinda creates a linked list of resolvers, with latest resolver at head.
if (this._resolvePause) {
console.log("Chaining pause calls");
return new Promise((resolve) => {
// Keep a reference to the existing resolver
const oldResolve = this._resolvePause;
// Replace resolver with new chained resolver
this._resolvePause = () => {
oldResolve && oldResolve();
resolve();
};
});
}
// Else, create a callback to be called by the execution loop
// when it finishes current execution step.
return new Promise(
(resolve) =>
(this._resolvePause = () => {
this._resolvePause = null;
this._isPaused = true;
resolve();
})
);
}
/**
* Execute the loaded program until stopped.
* @param param0.interval Interval between two execution steps
* @param param0.onResult Callback called with result on each execution step
* @returns Returns last (already used) execution result
*/
async executeAll({ interval, onResult }: ExecuteAllArgs<RS>) {
while (true) {
this._result = this._engine.executeStep();
onResult && onResult(this._result);
if (!this._result.nextStepLocation) break;
await this.sleep(interval || 0);
console.log("Result: ", this._result);
if (!this._result.nextStepLocation) {
// End of program
onResult && onResult(this._result);
this._resolvePause && this._resolvePause(); // In case pause happens on same cycle
break;
} else if (this._resolvePause) {
// Execution has been paused/stopped
this._result.signal = "paused";
onResult && onResult(this._result);
this._resolvePause();
break;
} else {
onResult && onResult(this._result);
// Sleep for specified interval
await this.sleep(interval || 0);
}
}
return this._result;
}
/** Asynchronously sleep for a period of time */
private async sleep(millis: number) {
return new Promise<void>((resolve) => setTimeout(resolve, millis));
}

View File

@ -35,6 +35,9 @@ export type StepExecutionResult<RS> = {
* Passing `null` indicates reaching the end of program.
*/
nextStepLocation: DocumentRange | null;
/** Signal if execution has been paused/stopped */
signal?: "paused";
};
/**

View File

@ -21,10 +21,19 @@ export type WorkerRequestData =
| {
type: "Execute";
params: { interval?: number };
}
| {
type: "Pause";
params?: null;
};
/** Kinds of acknowledgement responses the worker can send */
export type WorkerAckType = "init" | "reset" | "bp-update" | "prepare";
export type WorkerAckType =
| "init" // on initialization
| "reset" // on state reset
| "bp-update" // on updating breakpoints
| "prepare" // on preparing for execution
| "pause"; // on pausing execution
/** Types of responses the worker can send */
export type WorkerResponseData<RS> =

View File

@ -71,11 +71,20 @@ const execute = (interval?: number) => {
});
};
addEventListener("message", (ev: MessageEvent<WorkerRequestData>) => {
/**
* Trigger pause in program execution
*/
const pauseExecution = async () => {
await _controller!.pauseExecution();
postMessage(ackMessage("pause"));
};
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 === "UpdateBreakpoints")
return updateBreakpoints(ev.data.params.points);
throw new Error("Invalid worker message type");