UNPKG

pxt-common-packages

Version:
293 lines (256 loc) 9.59 kB
namespace control { /** * Run code when a registered event happens. * @param id the event compoent id * @param value the event value to match */ //% weight=20 blockGap=8 blockId="control_on_event" block="on event|from %src|with value %value" //% blockExternalInputs=1 //% help="control/on-event" export function onEvent(src: number, value: number, handler: () => void, flags = 16) { // EVENT_LISTENER_DEFAULT_FLAGS const ctx = control.eventContext(); if (!ctx) control.internalOnEvent(src, value, handler, flags); else ctx.registerHandler(src, value, handler, flags); } export class FrameCallback { order: number handler: () => void } class EventHandler { constructor( public src: number, public value: number, public handler: () => void, public flags: number ) { } register() { control.internalOnEvent(this.src, this.value, () => { if (this.handler) this.handler(); }, this.flags) } unregister() { control.internalOnEvent(this.src, this.value, doNothing, this.flags); } } function doNothing() { } export class EventContext { private handlers: EventHandler[]; private frameCallbacks: FrameCallback[]; private frameWorker: number; private framesInSample: number; private timeInSample: number; private lastPerfDump: number; public deltaTimeMillis: number; private prevTimeMillis: number; private idleCallbacks: (() => void)[]; static lastStats: string; static onStats: (stats: string) => void; constructor() { this.handlers = []; this.framesInSample = 0; this.timeInSample = 0; this.deltaTimeMillis = 0; this.frameWorker = 0; this.idleCallbacks = undefined; if (!EventContext.lastStats) { EventContext.lastStats = ""; } } get deltaTime() { return this.deltaTimeMillis / 1000; } private runCallbacks() { control.enablePerfCounter("all frame callbacks") let loopStart = control.millis() this.deltaTimeMillis = loopStart - this.prevTimeMillis; this.prevTimeMillis = loopStart; for (let f of this.frameCallbacks) { f.handler() } const now = control.millis() let runtime = now - loopStart this.timeInSample += runtime this.framesInSample++ if (this.timeInSample > 1000 || this.framesInSample > 30) { const realTimeInSample = now - this.lastPerfDump this.lastPerfDump = now const fps = this.framesInSample / (this.timeInSample / 1000); EventContext.lastStats = `fps:${Math.round(fps)}`; if (fps < 99) EventContext.lastStats += "." + (Math.round(fps * 10) % 10) if (control.profilingEnabled()) { control.dmesg(`${(fps * 100) | 0}/100 fps - ${this.framesInSample} frames (${this.timeInSample}ms/${realTimeInSample}ms)`) control.gc() control.dmesgPerfCounters() } this.timeInSample = 0 this.framesInSample = 0 } let delay = Math.max(1, 20 - runtime) return delay } private runningCallbacks: boolean; private registerFrameCallbacks() { if (!this.frameCallbacks) return; const worker = this.frameWorker; control.runInParallel(() => { if (this.runningCallbacks) { // this context is still running in a different fiber; // delay until the other fiber doing so has ceased. pauseUntil(() => !this.runningCallbacks); } this.runningCallbacks = true; this.framesInSample = 0; this.timeInSample = 0; this.deltaTimeMillis = 0; this.prevTimeMillis = control.millis(); while (worker == this.frameWorker) { let delay = this.runCallbacks() pause(delay) } this.runningCallbacks = false; }) } register() { for (const h of this.handlers) h.register(); this.registerFrameCallbacks(); } unregister() { for (const h of this.handlers) h.unregister(); this.frameWorker++; } registerFrameHandler(order: number, handler: () => void): FrameCallback { if (!this.frameCallbacks) { this.frameCallbacks = []; this.registerFrameCallbacks(); } const fn = new FrameCallback() fn.order = order fn.handler = handler for (let i = 0; i < this.frameCallbacks.length; ++i) { if (this.frameCallbacks[i].order > order) { this.frameCallbacks.insertAt(i, fn) return fn; } } this.frameCallbacks.push(fn); return fn; } unregisterFrameHandler(fn: FrameCallback) { if (!fn || !this.frameCallbacks) return; const i = this.frameCallbacks.indexOf(fn); if (i > -1) this.frameCallbacks.splice(i, 1); } registerHandler(src: number, value: number, handler: () => void, flags: number) { // already there? for (const h of this.handlers) { if (h.src == src && h.value == value) { h.flags = flags; h.handler = handler; return; } } // register and push const hn = new EventHandler(src, value, handler, flags); this.handlers.push(hn); hn.register(); } addIdleHandler(handler: () => void) { if (!this.idleCallbacks) { this.idleCallbacks = []; this.registerHandler(15/*DAL.DEVICE_ID_SCHEDULER*/, 2/*DAL.DEVICE_SCHEDULER_EVT_IDLE*/, () => this.runIdleHandler(), 16); } this.idleCallbacks.push(handler); } removeIdleHandler(handler: () => void) { if (handler && this.idleCallbacks) this.idleCallbacks.removeElement(handler); } private runIdleHandler() { if (this.idleCallbacks) { const ics = this.idleCallbacks.slice(0); ics.forEach(ic => ic()); } } } let eventContexts: EventContext[]; /** * Gets the current event context if any */ export function eventContext(): EventContext { return eventContexts ? eventContexts[eventContexts.length - 1] : undefined; } /** * Pushes a new event context and clears all handlers */ export function pushEventContext(): EventContext { if (!eventContexts) eventContexts = []; // unregister previous context const ctx = eventContext(); if (ctx) ctx.unregister(); // register again const n = new EventContext(); eventContexts.push(n); return n; } /** * Pops the current event context and restore handlers if any previous context */ export function popEventContext() { if (!eventContexts) return; // clear current context const ctx = eventContexts.pop(); if (!ctx) return; ctx.unregister(); // register old context again const context = eventContexts[eventContexts.length - 1]; if (context) context.register(); else eventContexts = undefined; } let _idleCallbacks: (() => void)[]; /** * Registers a function to run when the device is idling * @param handler */ export function onIdle(handler: () => void) { if (!handler) return; const ctx = eventContext(); if (ctx) ctx.addIdleHandler(handler); else { if (!_idleCallbacks) { _idleCallbacks = []; control.runInBackground(function () { while (_idleCallbacks) { _idleCallbacks.slice(0).forEach(cb => cb()); pause(20); } }) /* control.internalOnEvent( 15. // DAL.DEVICE_ID_SCHEDULER 2, // DAL.DEVICE_SCHEDULER_EVT_IDLE function() { pins.LED.digitalWrite(on = !on); if (_idleCallbacks) _idleCallbacks.slice(0).forEach(cb => cb()); }, 192); // MESSAGE_BUS_LISTENER_IMMEDIATE */ } _idleCallbacks.push(handler); } } export function removeIdleHandler(handler: () => void) { if (!handler) return; const ctx = eventContext(); if (ctx) ctx.removeIdleHandler(handler); else if (_idleCallbacks) _idleCallbacks.removeElement(handler); } }