From 20bcf283319856f38d02e0f982adb444a3cbda08 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Fri, 26 Jul 2024 11:04:45 -0700 Subject: [PATCH 1/2] Add support for different codal message bus listener flags --- pxtsim/runtime.ts | 117 ++++++++++++++++++++++++++++++++-------------- pxtsim/simlib.ts | 12 +++-- 2 files changed, 89 insertions(+), 40 deletions(-) diff --git a/pxtsim/runtime.ts b/pxtsim/runtime.ts index 8c667cefba5e..3a5102f3c6cc 100644 --- a/pxtsim/runtime.ts +++ b/pxtsim/runtime.ts @@ -3,6 +3,14 @@ namespace pxsim { const MIN_MESSAGE_WAIT_MS = 200; let tracePauseMs = 0; + + enum MessageListenerFlags { + MESSAGE_BUS_LISTENER_REENTRANT = 8, + MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY = 16, + MESSAGE_BUS_LISTENER_DROP_IF_BUSY = 32, + MESSAGE_BUS_LISTENER_IMMEDIATE = 192 + } + export namespace U { // Keep these helpers unified with pxtlib/browserutils.ts export function containsClass(el: SVGElement | HTMLElement, classes: string) { @@ -563,13 +571,43 @@ namespace pxsim { export type EventIDType = number | string; + class EventHandler { + private busy = 0; + constructor(public handler: RefAction, public flags: number) {} + + async runAsync(eventValue: EventIDType, runtime: Runtime, valueToArgs?: EventValueToActionArgs) { + // The default behavior can technically be configujred in codal, but we always set it to queue if busy + const flags = this.flags || MessageListenerFlags.MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY; + + if (flags === MessageListenerFlags.MESSAGE_BUS_LISTENER_IMMEDIATE) { + U.userError("MESSAGE_BUS_LISTENER_IMMEDIATE is not supported!"); + return; + } + + if (flags === MessageListenerFlags.MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY) { + return this.runFiberAsync(eventValue, runtime, valueToArgs); + } + else if (flags === MessageListenerFlags.MESSAGE_BUS_LISTENER_DROP_IF_BUSY && this.busy) { + return; + } + + void this.runFiberAsync(eventValue, runtime, valueToArgs); + } + + private async runFiberAsync(eventValue: EventIDType, runtime: Runtime, valueToArgs?: EventValueToActionArgs) { + this.busy ++; + await runtime.runFiberAsync(this.handler, ...(valueToArgs ? valueToArgs(eventValue) : [eventValue])); + this.busy --; + } + } + export class EventQueue { max: number = 5; events: EventIDType[] = []; private awaiters: ((v?: any) => void)[] = []; private lock: boolean; - private _handlers: RefAction[] = []; - private _addRemoveLog: { act: RefAction, log: LogType }[] = []; + private _handlers: EventHandler[] = []; + private _addRemoveLog: { act: RefAction, log: LogType, flags: number }[] = []; constructor(public runtime: Runtime, private valueToArgs?: EventValueToActionArgs) { } @@ -596,66 +634,75 @@ namespace pxsim { return Promise.resolve() } - private poke(): Promise { + private async poke(): Promise { this.lock = true; let events = this.events; // all events will be processed by concurrent promisified code below, so start afresh this.events = [] + // in order semantics for events and handlers - return U.promiseMapAllSeries(events, (value) => { - return U.promiseMapAllSeries(this.handlers, (handler) => { - return this.runtime.runFiberAsync(handler, ...(this.valueToArgs ? this.valueToArgs(value) : [value])) - }) - }).then(() => { - // if some events arrived while processing above then keep processing - if (this.events.length > 0) { - return this.poke() - } else { - this.lock = false - // process the log (synchronous) - this._addRemoveLog.forEach(l => { - if (l.log === LogType.BackAdd) { this.addHandler(l.act) } - else if (l.log === LogType.BackRemove) { this.removeHandler(l.act) } - else this.setHandler(l.act) - }); - this._addRemoveLog = []; - return Promise.resolve() + for (const value of events) { + for (const handler of this.handlers) { + await handler.runAsync(value, this.runtime, this.valueToArgs); } - }) + } + + // if some events arrived while processing above then keep processing + if (this.events.length > 0) { + return this.poke() + } + else { + this.lock = false + // process the log (synchronous) + for (const logger of this._addRemoveLog) { + if (logger.log === LogType.BackAdd) { + this.addHandler(logger.act, logger.flags) + } + else if (logger.log === LogType.BackRemove) { + this.removeHandler(logger.act) + } + else { + this.setHandler(logger.act, logger.flags) + } + } + this._addRemoveLog = []; + } } get handlers() { return this._handlers; } - setHandler(a: RefAction) { + setHandler(a: RefAction, flags = 0) { if (!this.lock) { - this._handlers = [a]; - } else { - this._addRemoveLog.push({ act: a, log: LogType.UserSet }); + this._handlers = [new EventHandler(a, flags)]; + } + else { + this._addRemoveLog.push({ act: a, log: LogType.UserSet, flags}); } } - addHandler(a: RefAction) { + addHandler(a: RefAction, flags = 0) { if (!this.lock) { - let index = this._handlers.indexOf(a) // only add if new, just like CODAL - if (index == -1) { - this._handlers.push(a); + if (!this._handlers.some(h => h.handler === a)) { + this._handlers.push(new EventHandler(a, flags)); } - } else { - this._addRemoveLog.push({ act: a, log: LogType.BackAdd }); + } + else { + this._addRemoveLog.push({ act: a, log: LogType.BackAdd, flags }); } } removeHandler(a: RefAction) { if (!this.lock) { - let index = this._handlers.indexOf(a) + let index = this._handlers.findIndex(h => h.handler === a) if (index != -1) { this._handlers.splice(index, 1) } - } else { - this._addRemoveLog.push({ act: a, log: LogType.BackRemove }); + } + else { + this._addRemoveLog.push({ act: a, log: LogType.BackRemove, flags: 0 }); } } diff --git a/pxtsim/simlib.ts b/pxtsim/simlib.ts index b40eb472f1c9..3b71ee77ee00 100644 --- a/pxtsim/simlib.ts +++ b/pxtsim/simlib.ts @@ -71,16 +71,18 @@ namespace pxsim { return this.queues[key]; } - listen(id: EventIDType, evid: EventIDType, handler: RefAction) { + listen(id: EventIDType, evid: EventIDType, handler: RefAction, flags = 0) { // special handle for idle, start the idle timeout if (id == this.schedulerID && evid == this.idleEventID) this.runtime.startIdle(); let q = this.start(id, evid, this.backgroundHandlerFlag, true); - if (this.backgroundHandlerFlag) - q.addHandler(handler); - else - q.setHandler(handler); + if (this.backgroundHandlerFlag) { + q.addHandler(handler, flags); + } + else { + q.setHandler(handler, flags); + } this.backgroundHandlerFlag = false; } From 3a8881b4916bfc45e886732c26084db215a5b7e2 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Fri, 26 Jul 2024 12:42:31 -0700 Subject: [PATCH 2/2] Update pxtsim/runtime.ts Co-authored-by: Sarah Rietkerk <49178322+srietkerk@users.noreply.github.com> --- pxtsim/runtime.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pxtsim/runtime.ts b/pxtsim/runtime.ts index 3a5102f3c6cc..b9fa8144914b 100644 --- a/pxtsim/runtime.ts +++ b/pxtsim/runtime.ts @@ -576,7 +576,7 @@ namespace pxsim { constructor(public handler: RefAction, public flags: number) {} async runAsync(eventValue: EventIDType, runtime: Runtime, valueToArgs?: EventValueToActionArgs) { - // The default behavior can technically be configujred in codal, but we always set it to queue if busy + // The default behavior can technically be configured in codal, but we always set it to queue if busy const flags = this.flags || MessageListenerFlags.MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY; if (flags === MessageListenerFlags.MESSAGE_BUS_LISTENER_IMMEDIATE) {