diff --git a/apps/client/src/common/utils/socket.ts b/apps/client/src/common/utils/socket.ts index 39652d6eb3..4af7af8167 100644 --- a/apps/client/src/common/utils/socket.ts +++ b/apps/client/src/common/utils/socket.ts @@ -39,7 +39,6 @@ export const connectSocket = () => { } socketSendJson('set-client-type', 'ontime'); - socketSendJson('set-client-use-patch', 'ontime'); setOnlineStatus(true); }; @@ -65,6 +64,7 @@ export const connectSocket = () => { websocket.onmessage = (event) => { try { const data = JSON.parse(event.data); + const { type, payload } = data; if (!type) { @@ -139,12 +139,6 @@ export const connectSocket = () => { updateDevTools(serverPayload); break; } - case 'ontime-patch': { - patchRuntime(payload); - updateDevTools(payload); - break; - } - //TODO: remove all other types as they are now patched case 'ontime-clock': { patchRuntimeProperty('clock', payload); updateDevTools({ clock: payload }); diff --git a/apps/server/src/adapters/WebsocketAdapter.ts b/apps/server/src/adapters/WebsocketAdapter.ts index fb8d29a9a5..f90a8f22ec 100644 --- a/apps/server/src/adapters/WebsocketAdapter.ts +++ b/apps/server/src/adapters/WebsocketAdapter.ts @@ -14,7 +14,7 @@ * Payload: adds necessary payload for the request to be completed */ -import { Client, LogOrigin, RuntimeStore } from 'ontime-types'; +import { Client, LogOrigin } from 'ontime-types'; import { WebSocket, WebSocketServer } from 'ws'; import type { Server } from 'http'; @@ -37,9 +37,6 @@ export class SocketServer implements IAdapter { private lastConnection: Date | null = null; private shouldShowWelcome = true; - private readonly patchClients: Map; - private readonly keyClients: Map; - constructor() { if (instance) { throw new Error('There can be only one'); @@ -48,8 +45,6 @@ export class SocketServer implements IAdapter { // eslint-disable-next-line @typescript-eslint/no-this-alias -- this logic is used to ensure singleton instance = this; this.clients = new Map(); - this.keyClients = new Map(); - this.patchClients = new Map(); this.wss = null; } @@ -72,8 +67,6 @@ export class SocketServer implements IAdapter { path: '', }); - this.keyClients.set(clientId, ws); - this.lastConnection = new Date(); logger.info(LogOrigin.Client, `${this.clients.size} Connections with new: ${clientId}`); @@ -105,9 +98,6 @@ export class SocketServer implements IAdapter { ws.on('close', () => { this.clients.delete(clientId); - this.patchClients.delete(clientId); - this.keyClients.delete(clientId); - logger.info(LogOrigin.Client, `${this.clients.size} Connections with disconnected: ${clientId}`); this.sendClientList(); }); @@ -191,18 +181,6 @@ export class SocketServer implements IAdapter { return; } - if (type === 'set-client-use-patch') { - this.keyClients.delete(clientId); - this.patchClients.set(clientId, ws); - return; - } - - if (type === 'set-client-use-key') { - this.patchClients.delete(clientId); - this.keyClients.set(clientId, ws); - return; - } - // Protocol specific stuff handled above try { const reply = dispatchFromAdapter(type, payload, 'ws'); @@ -285,38 +263,6 @@ export class SocketServer implements IAdapter { } } - public sendRuntimeStoreUpdate(keysToUpdate: (keyof RuntimeStore)[], store: Partial) { - // create a patch object with all the keys marked for updating - const patch = {}; - keysToUpdate.map((key) => { - Object.assign(patch, { [key]: store[key] }); - }); - - //convert to JSON once and reuse - const stringifiedPatch = JSON.stringify({ type: 'ontime-patch', payload: patch }); - - // for each client that have subscribed to the patch method send the patch object - this.patchClients.forEach((ws) => { - if (ws.readyState === WebSocket.OPEN) { - ws.send(stringifiedPatch); - } - }); - - // all the old type clients are sent the normal way - while (keysToUpdate.length) { - // for each key in the list - const key = keysToUpdate.pop(); - // create a reuseble JSON object - const stringifiedMessage = JSON.stringify({ type: `ontime-${key}`, payload: store[key] }); - //and send it to all client then hanve not switch to the patch method - this.keyClients.forEach((ws) => { - if (ws.readyState === WebSocket.OPEN) { - ws.send(stringifiedMessage); - } - }); - } - } - shutdown() { this.wss?.close(); } diff --git a/apps/server/src/services/runtime-service/RuntimeService.ts b/apps/server/src/services/runtime-service/RuntimeService.ts index 9fe4ad6715..a4bded6213 100644 --- a/apps/server/src/services/runtime-service/RuntimeService.ts +++ b/apps/server/src/services/runtime-service/RuntimeService.ts @@ -48,6 +48,7 @@ class RuntimeService { /** last time we updated the socket */ static previousTimerUpdate: number; + static previousRuntimeUpdate: number; static previousTimerValue: MaybeNumber; // previous timer value, could be null static previousClockUpdate: number; @@ -58,6 +59,7 @@ class RuntimeService { this.eventTimer = eventTimer; RuntimeService.previousTimerUpdate = -1; + RuntimeService.previousRuntimeUpdate = -1; RuntimeService.previousTimerValue = -1; RuntimeService.previousClockUpdate = -1; RuntimeService.previousState = {} as RuntimeState; @@ -678,10 +680,17 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert // we do the comparison by explicitly for each property // to apply custom logic for different datasets - const shouldForceTimerUpdate = getForceUpdate(RuntimeService.previousTimerUpdate, state.clock); //FIXME: pause causess an avlache of update if timer is negative + const shouldForceTimerUpdate = getForceUpdate( + RuntimeService.previousTimerUpdate, + state.clock, + state.timer.playback, + ); + const shouldUpdateTimer = shouldForceTimerUpdate || getShouldTimerUpdate(RuntimeService.previousTimerValue, state.timer.current); + const shouldRuntimeUpdate = shouldUpdateTimer || getForceUpdate(RuntimeService.previousRuntimeUpdate, state.clock); + // some changes need an immediate update const hasNewLoaded = state.eventNow?.id !== RuntimeService.previousState?.eventNow?.id; @@ -702,9 +711,13 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert RuntimeService.previousState.timer = { ...state.timer }; } - if (hasChangedPlayback || (shouldUpdateTimer && !deepEqual(RuntimeService.previousState?.runtime, state.runtime))) { + if ( + hasChangedPlayback || + (shouldRuntimeUpdate && !deepEqual(RuntimeService.previousState?.runtime, state.runtime)) + ) { eventStore.set('runtime', state.runtime); RuntimeService.previousClockUpdate = state.clock; + RuntimeService.previousRuntimeUpdate = state.clock; eventStore.set('clock', state.clock); RuntimeService.previousState.runtime = { ...state.runtime }; } diff --git a/apps/server/src/services/runtime-service/rundownService.utils.ts b/apps/server/src/services/runtime-service/rundownService.utils.ts index 9e2e5b1f4e..ca0b851aca 100644 --- a/apps/server/src/services/runtime-service/rundownService.utils.ts +++ b/apps/server/src/services/runtime-service/rundownService.utils.ts @@ -1,7 +1,7 @@ import { millisToSeconds } from 'ontime-utils'; import { timerConfig } from '../../config/config.js'; -import { MaybeNumber } from 'ontime-types'; +import { MaybeNumber, Playback } from 'ontime-types'; /** * Checks whether we should update the clock value @@ -34,8 +34,10 @@ export function getShouldTimerUpdate(previousValue: number, currentValue: MaybeN * In some cases we want to force an update to the timer * - if the clock has slid back * - if we have escaped the update rate (clock slid forward) + * - if we are not playing then there is no need to update the timer */ -export function getForceUpdate(previousUpdate: number, now: number): boolean { +export function getForceUpdate(previousUpdate: number, now: number, playbackState: Playback = Playback.Play): boolean { + if (playbackState !== Playback.Play) return false; const isClockBehind = now < previousUpdate; const hasExceededRate = now - previousUpdate >= timerConfig.notificationRate; return isClockBehind || hasExceededRate; diff --git a/apps/server/src/stores/EventStore.ts b/apps/server/src/stores/EventStore.ts index 5bcea43b9a..7bc2b620e0 100644 --- a/apps/server/src/stores/EventStore.ts +++ b/apps/server/src/stores/EventStore.ts @@ -7,7 +7,7 @@ export type StoreGetter = (key: T) => Partial = {}; -const changedKeys = new Array(); +const changedKeys = new Set(); let isUpdatePending: NodeJS.Immediate | null = null; /** * A runtime store that broadcasts its payload @@ -27,13 +27,16 @@ export const eventStore = { store[key] = value; // check if the key is already marked for and update otherwise push it onto the update array - if (!changedKeys.includes(key)) changedKeys.push(key); + changedKeys.add(key); //if there is already and update pending we don't need to schedule another one if (!isUpdatePending) { isUpdatePending = setImmediate(() => { - socket.sendRuntimeStoreUpdate(changedKeys, store); + for (const dataKey of changedKeys) { + socket.sendAsJson({ type: `ontime-${dataKey}`, payload: store[dataKey] }); + } isUpdatePending = null; + changedKeys.clear(); }); } },