Refactor: WebSocket from flush queue to one patch (#1595)

* change flush to one patch

* remove unused types

* create batch

* merge patch into eventStore

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Alex Christoffer Rasmussen
2025-05-07 11:40:30 +02:00
committed by Carlos Valente
parent 0a596624d5
commit bdcec7a472
4 changed files with 34 additions and 95 deletions
-12
View File
@@ -14,18 +14,6 @@ export const runtimeStore = createWithEqualityFn<RuntimeStore>(
export const useRuntimeStore = <T>(selector: (state: RuntimeStore) => T) => export const useRuntimeStore = <T>(selector: (state: RuntimeStore) => T) =>
useStoreWithEqualityFn(runtimeStore, selector, deepCompare); useStoreWithEqualityFn(runtimeStore, selector, deepCompare);
let batchStore: Partial<RuntimeStore> = {};
export function addToBatchUpdates<K extends keyof RuntimeStore>(key: K, value: RuntimeStore[K]) {
batchStore[key] = value;
}
export function flushBatchUpdates() {
const state = runtimeStore.getState();
runtimeStore.setState({ ...state, ...batchStore });
batchStore = {};
}
/** /**
* Allows patching a property of the runtime store * Allows patching a property of the runtime store
*/ */
+10 -59
View File
@@ -14,7 +14,7 @@ import {
} from '../stores/clientStore'; } from '../stores/clientStore';
import { addDialog } from '../stores/dialogStore'; import { addDialog } from '../stores/dialogStore';
import { addLog } from '../stores/logger'; import { addLog } from '../stores/logger';
import { addToBatchUpdates, flushBatchUpdates, patchRuntime, patchRuntimeProperty } from '../stores/runtime'; import { patchRuntime, patchRuntimeProperty } from '../stores/runtime';
let websocket: WebSocket | null = null; let websocket: WebSocket | null = null;
let reconnectTimeout: NodeJS.Timeout | null = null; let reconnectTimeout: NodeJS.Timeout | null = null;
@@ -50,9 +50,9 @@ export const connectSocket = () => {
// we decide to allows reconnect // we decide to allows reconnect
reconnectTimeout = setTimeout(() => { reconnectTimeout = setTimeout(() => {
if (reconnectAttempts > 2) { if (reconnectAttempts > 2) {
setOnlineStatus(false); setOnlineStatus(false);
} }
console.warn('WebSocket: attempting reconnect'); console.warn('WebSocket: attempting reconnect');
if (websocket && websocket.readyState === WebSocket.CLOSED) { if (websocket && websocket.readyState === WebSocket.CLOSED) {
reconnectAttempts += 1; reconnectAttempts += 1;
@@ -141,59 +141,10 @@ export const connectSocket = () => {
updateDevTools(serverPayload); updateDevTools(serverPayload);
break; break;
} }
case 'ontime-clock': { case 'ontime-patch': {
addToBatchUpdates('clock', payload); const patch = payload as Partial<RuntimeStore>;
updateDevTools({ clock: payload }); patchRuntime(patch);
break; updateDevTools(patch);
}
case 'ontime-timer': {
addToBatchUpdates('timer', payload);
updateDevTools({ timer: payload });
break;
}
case 'ontime-onAir': {
addToBatchUpdates('onAir', payload);
updateDevTools({ onAir: payload });
break;
}
case 'ontime-message': {
addToBatchUpdates('message', payload);
updateDevTools({ message: payload });
break;
}
case 'ontime-runtime': {
addToBatchUpdates('runtime', payload);
updateDevTools({ runtime: payload });
break;
}
case 'ontime-eventNow': {
addToBatchUpdates('eventNow', payload);
updateDevTools({ eventNow: payload });
break;
}
case 'ontime-currentBlock': {
addToBatchUpdates('currentBlock', payload);
updateDevTools({ currentBlock: payload });
break;
}
case 'ontime-publicEventNow': {
addToBatchUpdates('publicEventNow', payload);
updateDevTools({ publicEventNow: payload });
break;
}
case 'ontime-eventNext': {
addToBatchUpdates('eventNext', payload);
updateDevTools({ eventNext: payload });
break;
}
case 'ontime-publicEventNext': {
addToBatchUpdates('publicEventNext', payload);
updateDevTools({ publicEventNext: payload });
break;
}
case 'ontime-auxtimer1': {
addToBatchUpdates('auxtimer1', payload);
updateDevTools({ auxtimer1: payload });
break; break;
} }
case 'ontime-refetch': { case 'ontime-refetch': {
@@ -213,8 +164,8 @@ export const connectSocket = () => {
} }
break; break;
} }
case 'ontime-flush': { default: {
flushBatchUpdates(); console.log('unknown WS message', type);
break; break;
} }
} }
@@ -696,6 +696,8 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
const result = originalMethod.apply(this, args); const result = originalMethod.apply(this, args);
const state = runtimeState.getState(); const state = runtimeState.getState();
const batch = eventStore.createBatch();
// we do the comparison by explicitly for each property // we do the comparison by explicitly for each property
// to apply custom logic for different datasets // to apply custom logic for different datasets
@@ -758,24 +760,24 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
//Now we set all the updates on the eventstore and update the previous value //Now we set all the updates on the eventstore and update the previous value
if (hasChangedPlayback) { if (hasChangedPlayback) {
eventStore.set('onAir', state.timer.playback !== Playback.Stop); batch.add('onAir', state.timer.playback !== Playback.Stop);
} }
if (shouldUpdateTimer) { if (shouldUpdateTimer) {
eventStore.set('timer', state.timer); batch.add('timer', state.timer);
RuntimeService.previousTimerUpdate = state.clock; RuntimeService.previousTimerUpdate = state.clock;
RuntimeService.previousTimerValue = state.timer.current; RuntimeService.previousTimerValue = state.timer.current;
RuntimeService.previousState.timer = { ...state.timer }; RuntimeService.previousState.timer = { ...state.timer };
} }
if (shouldRuntimeUpdate) { if (shouldRuntimeUpdate) {
eventStore.set('runtime', state.runtime); batch.add('runtime', state.runtime);
RuntimeService.previousRuntimeUpdate = state.clock; RuntimeService.previousRuntimeUpdate = state.clock;
RuntimeService.previousState.runtime = { ...state.runtime }; RuntimeService.previousState.runtime = { ...state.runtime };
} }
if (shouldBlockUpdate) { if (shouldBlockUpdate) {
eventStore.set('currentBlock', state.currentBlock); batch.add('currentBlock', state.currentBlock);
RuntimeService.previousState.currentBlock = { ...state.currentBlock }; RuntimeService.previousState.currentBlock = { ...state.currentBlock };
} }
@@ -785,7 +787,7 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
if (shouldUpdateClock) { if (shouldUpdateClock) {
RuntimeService.previousClockUpdate = state.clock; RuntimeService.previousClockUpdate = state.clock;
eventStore.set('clock', state.clock); batch.add('clock', state.clock);
} }
// Update the events if they have changed // Update the events if they have changed
@@ -817,7 +819,7 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
} }
function storeKey(eventKey: RuntimeStateEventKeys) { function storeKey(eventKey: RuntimeStateEventKeys) {
eventStore.set(eventKey, state[eventKey]); batch.add(eventKey, state[eventKey]);
// @ts-expect-error -- not sure how to type this in a sane way // @ts-expect-error -- not sure how to type this in a sane way
RuntimeService.previousState[eventKey] = { ...state[eventKey] }; RuntimeService.previousState[eventKey] = { ...state[eventKey] };
} }
@@ -836,6 +838,7 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
}); });
} }
batch.send();
return result; return result;
}; };
+15 -18
View File
@@ -1,18 +1,16 @@
import { RuntimeStore } from 'ontime-types'; import { RuntimeStore } from 'ontime-types';
import { socket } from '../adapters/WebsocketAdapter.js'; import { socket } from '../adapters/WebsocketAdapter.js';
import { isEmptyObject } from '../utils/parserUtils.js';
export type PublishFn = <T extends keyof RuntimeStore>(key: T, value: RuntimeStore[T]) => void; export type PublishFn = <T extends keyof RuntimeStore>(key: T, value: RuntimeStore[T]) => void;
export type StoreGetter = <T extends keyof RuntimeStore>(key: T) => Partial<RuntimeStore>[T]; export type StoreGetter = <T extends keyof RuntimeStore>(key: T) => Partial<RuntimeStore>[T];
let store: Partial<RuntimeStore> = {}; let store: Partial<RuntimeStore> = {};
const changedKeys = new Set<keyof RuntimeStore>();
let isUpdatePending: NodeJS.Immediate | null = null;
/** /**
* A runtime store that broadcasts its payload * A runtime store that broadcasts its payload
* - init: allows for adding an initial payload to the store * - init: allows for adding an initial payload to the store
* - batchSet: allows setting several keys with a single broadcast
* - poll: utility to return state * - poll: utility to return state
* - broadcast: send its payload as json object * - broadcast: send its payload as json object
*/ */
@@ -25,21 +23,20 @@ export const eventStore = {
}, },
set<T extends keyof RuntimeStore>(key: T, value: RuntimeStore[T]) { set<T extends keyof RuntimeStore>(key: T, value: RuntimeStore[T]) {
store[key] = value; store[key] = value;
socket.sendAsJson({ type: 'ontime-patch', payload: { [key]: value } });
// check if the key is already marked for and update otherwise push it onto the update array },
changedKeys.add(key); createBatch() {
const patch: Partial<RuntimeStore> = {};
//if there is already and update pending we don't need to schedule another one return {
if (!isUpdatePending) { add<T extends keyof RuntimeStore>(key: T, value: RuntimeStore[T]) {
isUpdatePending = setImmediate(() => { patch[key] = value;
for (const dataKey of changedKeys) { },
socket.sendAsJson({ type: `ontime-${dataKey}`, payload: store[dataKey] }); send() {
} if (isEmptyObject(patch)) return;
socket.sendAsJson({ type: 'ontime-flush' }); store = { ...store, ...patch };
isUpdatePending = null; socket.sendAsJson({ type: 'ontime-patch', payload: patch });
changedKeys.clear(); },
}); };
}
}, },
poll() { poll() {
return store as RuntimeStore; return store as RuntimeStore;