Skip to content

Commit 7c5b2e6

Browse files
Fix for reset/panic overriding the user's stop (#88)
Switch to an enum to make the possible states easier to follow and so we can check we're in the default state before committing to a reset or panic. Closes #86
1 parent 365bad7 commit 7c5b2e6

File tree

1 file changed

+87
-25
lines changed

1 file changed

+87
-25
lines changed

src/board/index.ts

+87-25
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,32 @@ import { Radio } from "./radio";
3535
import { RangeSensor, State } from "./state";
3636
import { ModuleWrapper } from "./wasm";
3737

38+
enum StopKind {
39+
/**
40+
* The main Wasm function returned control to us in a normal way.
41+
*/
42+
Default = "default",
43+
/**
44+
* The program called panic.
45+
*/
46+
Panic = "panic",
47+
/**
48+
* The program requested a reset.
49+
*/
50+
Reset = "reset",
51+
/**
52+
* An internal mode where we do not display the stop state UI as we plan to immediately reset.
53+
* Used for user-requested flash or reset.
54+
*/
55+
BriefStop = "brief",
56+
/**
57+
* The user requested the program be interrupted.
58+
*
59+
* Note the program could finish for other reasons, but should always count as a user stop.
60+
*/
61+
UserStop = "user",
62+
}
63+
3864
export class PanicError extends Error {
3965
constructor(public code: number) {
4066
super("panic");
@@ -74,8 +100,6 @@ export class Board {
74100
radio: Radio;
75101
dataLogging: DataLogging;
76102

77-
private panicTimeout: any;
78-
79103
public serialInputBuffer: number[] = [];
80104

81105
private stoppedOverlay: HTMLDivElement;
@@ -108,10 +132,19 @@ export class Board {
108132
*/
109133
private module: ModuleWrapper | undefined;
110134
/**
111-
* If undefined, then when main finishes we stay stopped.
112-
* Otherwise we perform the action then clear this field.
135+
* Controls the action after the user program completes.
136+
*
137+
* Determined by a combination of user actions (stop, reset etc) and program actions.
113138
*/
114-
private afterStopped: (() => void) | undefined;
139+
private stopKind: StopKind = StopKind.Default;
140+
/**
141+
* Timeout for a pending start call due to StopKind.Reset.
142+
*/
143+
private pendingRestartTimeout: any;
144+
/**
145+
* Timeout for the next frame of the panic animation.
146+
*/
147+
private panicTimeout: any;
115148

116149
constructor(
117150
private notifications: Notifications,
@@ -356,6 +389,8 @@ export class Board {
356389
if (this.modulePromise || this.module) {
357390
throw new Error("Module already exists!");
358391
}
392+
clearTimeout(this.pendingRestartTimeout);
393+
this.pendingRestartTimeout = null;
359394

360395
this.modulePromise = this.createModule();
361396
const module = await this.modulePromise;
@@ -365,11 +400,17 @@ export class Board {
365400
this.displayRunningState();
366401
await module.start();
367402
} catch (e: any) {
403+
// Take care not to overwrite another kind of stop just because the program
404+
// called restart or panic.
368405
if (e instanceof PanicError) {
369-
panicCode = e.code;
406+
if (this.stopKind === StopKind.Default) {
407+
this.stopKind = StopKind.Panic;
408+
panicCode = e.code;
409+
}
370410
} else if (e instanceof ResetError) {
371-
const noChangeRestart = () => {};
372-
this.afterStopped = noChangeRestart;
411+
if (this.stopKind === StopKind.Default) {
412+
this.stopKind = StopKind.Reset;
413+
}
373414
} else {
374415
this.notifications.onInternalError(e);
375416
}
@@ -386,30 +427,52 @@ export class Board {
386427
this.modulePromise = undefined;
387428
this.module = undefined;
388429

389-
if (panicCode !== undefined) {
390-
this.displayPanic(panicCode);
391-
} else {
392-
if (this.afterStopped) {
393-
this.afterStopped();
394-
this.afterStopped = undefined;
395-
setTimeout(() => this.start(), 0);
396-
} else {
430+
switch (this.stopKind) {
431+
case StopKind.Panic: {
432+
if (panicCode === undefined) {
433+
throw new Error("Must be set");
434+
}
435+
this.displayPanic(panicCode);
436+
break;
437+
}
438+
case StopKind.Reset: {
439+
this.pendingRestartTimeout = setTimeout(() => this.start(), 0);
440+
break;
441+
}
442+
case StopKind.BriefStop: {
443+
// Skip the stopped state.
444+
break;
445+
}
446+
case StopKind.UserStop: /* Fall through */
447+
case StopKind.Default: {
397448
this.displayStoppedState();
449+
break;
450+
}
451+
default: {
452+
throw new Error("Unknown stop kind: " + this.stopKind);
398453
}
399454
}
455+
this.stopKind = StopKind.Default;
400456
}
401457

402-
async stop(
403-
afterStopped: (() => void) | undefined = undefined
404-
): Promise<void> {
405-
this.afterStopped = afterStopped;
458+
async stop(brief: boolean = false): Promise<void> {
406459
if (this.panicTimeout) {
407460
clearTimeout(this.panicTimeout);
408461
this.panicTimeout = null;
409462
this.display.clear();
410-
this.displayStoppedState();
463+
if (!brief) {
464+
this.displayStoppedState();
465+
}
466+
}
467+
if (this.pendingRestartTimeout) {
468+
clearTimeout(this.pendingRestartTimeout);
469+
this.pendingRestartTimeout = null;
470+
if (!brief) {
471+
this.displayStoppedState();
472+
}
411473
}
412474
if (this.modulePromise) {
475+
this.stopKind = brief ? StopKind.BriefStop : StopKind.UserStop;
413476
// Avoid this.module as we might still be creating it (async).
414477
const module = await this.modulePromise;
415478
module.requestStop();
@@ -422,11 +485,10 @@ export class Board {
422485

423486
/**
424487
* An external reset.
425-
* reset() in MicroPython code throws ResetError.
426488
*/
427489
async reset(): Promise<void> {
428-
const noChangeRestart = () => {};
429-
this.stop(noChangeRestart);
490+
await this.stop(true);
491+
return this.start();
430492
}
431493

432494
async flash(filesystem: Record<string, Uint8Array>): Promise<void> {
@@ -440,7 +502,7 @@ export class Board {
440502
};
441503
if (this.modulePromise) {
442504
// If it's running then we need to stop before flash.
443-
return this.stop(flashFileSystem);
505+
await this.stop(true);
444506
}
445507
flashFileSystem();
446508
return this.start();

0 commit comments

Comments
 (0)