diff --git a/src/renderer/runtime/runtime.ts b/src/renderer/runtime/runtime.ts index 7fa75a89c..61b70e6e8 100644 --- a/src/renderer/runtime/runtime.ts +++ b/src/renderer/runtime/runtime.ts @@ -210,6 +210,16 @@ export interface ReplaceActionsResult extends GridOperationResult { abstract class RuntimeNode implements Writable { protected _internal: Writable; + private static _batchActive = false; + + public static batch(fn: () => R): R { + RuntimeNode._batchActive = true; + try { + return fn(); + } finally { + RuntimeNode._batchActive = false; + } + } public subscribe( run: Subscriber, @@ -264,6 +274,7 @@ abstract class RuntimeNode implements Writable { } protected notifyParent() { + if (RuntimeNode._batchActive) return; if (!this.parent) { return; } @@ -1910,6 +1921,11 @@ export class GridRuntime extends RuntimeNode { private aliveModules: Writable>; public elementPositionStore: Writable = writable({}); public ledColorStore: Writable = writable({}); + private _pendingHeartbeats = new Map< + string, + { rot: number; portstate: any; memorystat: any } + >(); + private _heartbeatRafPending = false; constructor( connection: GridConnection = undefined, @@ -1921,6 +1937,33 @@ export class GridRuntime extends RuntimeNode { this.aliveModules = writable([]); } + private _flushHeartbeats() { + this._heartbeatRafPending = false; + for (const [id, updates] of this._pendingHeartbeats) { + const module = this.modules.find((m) => m.id === id); + if (!module) continue; + if (module.rot !== updates.rot) module.rot = updates.rot; + if (module.portstate !== updates.portstate) + module.portstate = updates.portstate; + if (module.memorystat !== updates.memorystat) + module.memorystat = updates.memorystat; + } + this._pendingHeartbeats.clear(); + } + + private _stageHeartbeat( + module: GridModule, + rot: number, + portstate: any, + memorystat: any, + ) { + this._pendingHeartbeats.set(module.id, { rot, portstate, memorystat }); + if (!this._heartbeatRafPending) { + this._heartbeatRafPending = true; + requestAnimationFrame(() => this._flushHeartbeats()); + } + } + public countX() { return this.data.countX(); } @@ -2026,18 +2069,6 @@ export class GridRuntime extends RuntimeNode { const module = this.findModule(sx, sy); if (module) { - if (module.rot != descr.brc_parameters.ROT) { - module.rot = descr.brc_parameters.ROT; - } - - if (module.portstate != descr.class_parameters.PORTSTATE) { - module.portstate = descr.class_parameters.PORTSTATE; - } - - if (module.memorystat != descr.class_parameters.GCCOUNT) { - module.memorystat = descr.class_parameters.GCCOUNT; - } - this.aliveModules.update((store) => { const obj = store.find((e) => e.id === module.id); const lastDate = obj.last; @@ -2050,13 +2081,22 @@ export class GridRuntime extends RuntimeNode { } return store; }); + + this._stageHeartbeat( + module, + descr.brc_parameters.ROT, + descr.class_parameters.PORTSTATE, + descr.class_parameters.GCCOUNT, + ); } // device not found, add it to runtime and get page count from grid else { - const controller = this.create_module( - descr.brc_parameters, - descr.class_parameters, - false, + const controller = RuntimeNode.batch(() => + this.create_module( + descr.brc_parameters, + descr.class_parameters, + false, + ), ); // check if the firmware version of the newly connected device is acceptable console.log( diff --git a/src/renderer/runtime/user-input.store.ts b/src/renderer/runtime/user-input.store.ts index 04c1df8d3..cdd88c419 100644 --- a/src/renderer/runtime/user-input.store.ts +++ b/src/renderer/runtime/user-input.store.ts @@ -31,6 +31,8 @@ export class UserInput implements Writable { private _internal: Writable; private _changed_timestamp = 0; + private _pendingValue: UserInputValue | null = null; + private _rafPending = false; constructor() { this._internal = writable(UserInput.defaultValue); @@ -104,6 +106,23 @@ export class UserInput implements Writable { selected_actions.set([]); } + private _flushPending() { + this._rafPending = false; + if (this._pendingValue !== null) { + this._internal.set(this._pendingValue); + this._pendingValue = null; + selected_actions.set([]); + } + } + + private _stageSet(value: UserInputValue) { + this._pendingValue = value; + if (!this._rafPending) { + this._rafPending = true; + requestAnimationFrame(() => this._flushPending()); + } + } + // Process incoming events public process_incoming_event_from_grid(descr: any) { if (get(modalManager).windows.some((e) => e.target === Modal.Snap.Full)) { @@ -164,7 +183,7 @@ export class UserInput implements Writable { eventtype = descr.class_parameters.EVENTTYPE; } } - this.set({ + this._stageSet({ dx: descr.brc_parameters.SX, dy: descr.brc_parameters.SY, pagenumber: ui.pagenumber,