@@ -35,6 +35,32 @@ import { Radio } from "./radio";
3535import { RangeSensor , State } from "./state" ;
3636import { 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+
3864export 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 ;
@@ -99,19 +123,32 @@ export class Board {
99123 return result ?? id ;
100124 } ;
101125
126+ /**
127+ * Defined for the duration of start().
128+ */
129+ private runningPromise : Promise < void > | undefined ;
102130 /**
103131 * Defined during start().
104132 */
105133 private modulePromise : Promise < ModuleWrapper > | undefined ;
106134 /**
107- * Defined by start but async .
135+ * Defined during start() .
108136 */
109137 private module : ModuleWrapper | undefined ;
110138 /**
111- * If undefined, then when main finishes we stay stopped.
112- * Otherwise we perform the action then clear this field.
139+ * Controls the action after the user program completes.
140+ *
141+ * Determined by a combination of user actions (stop, reset etc) and program actions.
142+ */
143+ private stopKind : StopKind = StopKind . Default ;
144+ /**
145+ * Timeout for a pending start call due to StopKind.Reset.
146+ */
147+ private pendingRestartTimeout : any ;
148+ /**
149+ * Timeout for the next frame of the panic animation.
113150 */
114- private afterStopped : ( ( ) => void ) | undefined ;
151+ private panicTimeout : any ;
115152
116153 constructor (
117154 private notifications : Notifications ,
@@ -352,10 +389,24 @@ export class Board {
352389 this . stoppedOverlay . style . display = "flex" ;
353390 }
354391
355- private async start ( ) {
392+ /**
393+ * Start the simulator.
394+ *
395+ * @returns a promise that resolves when the simulator has stopped.
396+ */
397+ private start ( ) : void {
398+ if ( this . runningPromise ) {
399+ throw new Error ( "Already running!" ) ;
400+ }
401+ this . runningPromise = this . createRunningPromise ( ) ;
402+ }
403+
404+ private async createRunningPromise ( ) {
356405 if ( this . modulePromise || this . module ) {
357406 throw new Error ( "Module already exists!" ) ;
358407 }
408+ clearTimeout ( this . pendingRestartTimeout ) ;
409+ this . pendingRestartTimeout = null ;
359410
360411 this . modulePromise = this . createModule ( ) ;
361412 const module = await this . modulePromise ;
@@ -365,11 +416,17 @@ export class Board {
365416 this . displayRunningState ( ) ;
366417 await module . start ( ) ;
367418 } catch ( e : any ) {
419+ // Take care not to overwrite another kind of stop just because the program
420+ // called restart or panic.
368421 if ( e instanceof PanicError ) {
369- panicCode = e . code ;
422+ if ( this . stopKind === StopKind . Default ) {
423+ this . stopKind = StopKind . Panic ;
424+ panicCode = e . code ;
425+ }
370426 } else if ( e instanceof ResetError ) {
371- const noChangeRestart = ( ) => { } ;
372- this . afterStopped = noChangeRestart ;
427+ if ( this . stopKind === StopKind . Default ) {
428+ this . stopKind = StopKind . Reset ;
429+ }
373430 } else {
374431 this . notifications . onInternalError ( e ) ;
375432 }
@@ -386,30 +443,61 @@ export class Board {
386443 this . modulePromise = undefined ;
387444 this . module = undefined ;
388445
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 {
446+ switch ( this . stopKind ) {
447+ case StopKind . Panic : {
448+ if ( panicCode === undefined ) {
449+ throw new Error ( "Must be set" ) ;
450+ }
451+ this . displayPanic ( panicCode ) ;
452+ break ;
453+ }
454+ case StopKind . Reset : {
455+ this . pendingRestartTimeout = setTimeout ( ( ) => this . start ( ) , 0 ) ;
456+ break ;
457+ }
458+ case StopKind . BriefStop : {
459+ // Skip the stopped state.
460+ break ;
461+ }
462+ case StopKind . UserStop : /* Fall through */
463+ case StopKind . Default : {
397464 this . displayStoppedState ( ) ;
465+ break ;
466+ }
467+ default : {
468+ throw new Error ( "Unknown stop kind: " + this . stopKind ) ;
398469 }
399470 }
471+ this . stopKind = StopKind . Default ;
472+ this . runningPromise = undefined ;
400473 }
401474
402- async stop (
403- afterStopped : ( ( ) => void ) | undefined = undefined
404- ) : Promise < void > {
405- this . afterStopped = afterStopped ;
475+ /**
476+ * Stop the simulator.
477+ *
478+ * This cancels any pending restart or panic requested by the program.
479+ *
480+ * @param brief If true the stopped UI is not shown.
481+ * @returns A promise that resolves when the simulator is stopped.
482+ */
483+ async stop ( brief : boolean = false ) : Promise < void > {
406484 if ( this . panicTimeout ) {
407485 clearTimeout ( this . panicTimeout ) ;
408486 this . panicTimeout = null ;
409487 this . display . clear ( ) ;
410- this . displayStoppedState ( ) ;
488+ if ( ! brief ) {
489+ this . displayStoppedState ( ) ;
490+ }
491+ }
492+ if ( this . pendingRestartTimeout ) {
493+ clearTimeout ( this . pendingRestartTimeout ) ;
494+ this . pendingRestartTimeout = null ;
495+ if ( ! brief ) {
496+ this . displayStoppedState ( ) ;
497+ }
411498 }
412499 if ( this . modulePromise ) {
500+ this . stopKind = brief ? StopKind . BriefStop : StopKind . UserStop ;
413501 // Avoid this.module as we might still be creating it (async).
414502 const module = await this . modulePromise ;
415503 module . requestStop ( ) ;
@@ -418,15 +506,15 @@ export class Board {
418506 // Ctrl-C, Ctrl-D to interrupt the main loop.
419507 this . writeSerialInput ( "\x03\x04" ) ;
420508 }
509+ return this . runningPromise ;
421510 }
422511
423512 /**
424513 * An external reset.
425- * reset() in MicroPython code throws ResetError.
426514 */
427515 async reset ( ) : Promise < void > {
428- const noChangeRestart = ( ) => { } ;
429- this . stop ( noChangeRestart ) ;
516+ await this . stop ( true ) ;
517+ this . start ( ) ;
430518 }
431519
432520 async flash ( filesystem : Record < string , Uint8Array > ) : Promise < void > {
@@ -438,10 +526,8 @@ export class Board {
438526 } ) ;
439527 this . dataLogging . delete ( ) ;
440528 } ;
441- if ( this . modulePromise ) {
442- // If it's running then we need to stop before flash.
443- return this . stop ( flashFileSystem ) ;
444- }
529+ // Ensure it's stopped before flash.
530+ await this . stop ( true ) ;
445531 flashFileSystem ( ) ;
446532 return this . start ( ) ;
447533 }
@@ -586,7 +672,7 @@ export class Board {
586672
587673 writeRadioRxBuffer ( packet : Uint8Array ) : number {
588674 if ( ! this . module ) {
589- throw new Error ( "Must be running" ) ;
675+ throw new Error ( "Must be running as called via HAL " ) ;
590676 }
591677 return this . module . writeRadioRxBuffer ( packet ) ;
592678 }
0 commit comments