Batch client updates (#1448)

This commit is contained in:
Alex Christoffer Rasmussen
2025-01-24 13:52:58 +01:00
committed by GitHub
parent f1f628a48e
commit d175c788c5
3 changed files with 42 additions and 18 deletions
@@ -48,6 +48,7 @@ class RuntimeService {
/** last time we updated the socket */ /** last time we updated the socket */
static previousTimerUpdate: number; static previousTimerUpdate: number;
static previousRuntimeUpdate: number;
static previousTimerValue: MaybeNumber; // previous timer value, could be null static previousTimerValue: MaybeNumber; // previous timer value, could be null
static previousClockUpdate: number; static previousClockUpdate: number;
@@ -58,6 +59,7 @@ class RuntimeService {
this.eventTimer = eventTimer; this.eventTimer = eventTimer;
RuntimeService.previousTimerUpdate = -1; RuntimeService.previousTimerUpdate = -1;
RuntimeService.previousRuntimeUpdate = -1;
RuntimeService.previousTimerValue = -1; RuntimeService.previousTimerValue = -1;
RuntimeService.previousClockUpdate = -1; RuntimeService.previousClockUpdate = -1;
RuntimeService.previousState = {} as RuntimeState; 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 // we do the comparison by explicitly for each property
// to apply custom logic for different datasets // to apply custom logic for different datasets
const shouldForceTimerUpdate = getForceUpdate(RuntimeService.previousTimerUpdate, state.clock); const shouldForceTimerUpdate = getForceUpdate(
RuntimeService.previousTimerUpdate,
state.clock,
state.timer.playback,
);
const shouldUpdateTimer = const shouldUpdateTimer =
shouldForceTimerUpdate || getShouldTimerUpdate(RuntimeService.previousTimerValue, state.timer.current); shouldForceTimerUpdate || getShouldTimerUpdate(RuntimeService.previousTimerValue, state.timer.current);
const shouldRuntimeUpdate = shouldUpdateTimer || getForceUpdate(RuntimeService.previousRuntimeUpdate, state.clock);
// some changes need an immediate update // some changes need an immediate update
const hasNewLoaded = state.eventNow?.id !== RuntimeService.previousState?.eventNow?.id; const hasNewLoaded = state.eventNow?.id !== RuntimeService.previousState?.eventNow?.id;
@@ -696,12 +705,20 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
if (hasImmediateChanges || (shouldUpdateTimer && !deepEqual(RuntimeService.previousState?.timer, state.timer))) { if (hasImmediateChanges || (shouldUpdateTimer && !deepEqual(RuntimeService.previousState?.timer, state.timer))) {
RuntimeService.previousTimerUpdate = state.clock; RuntimeService.previousTimerUpdate = state.clock;
RuntimeService.previousTimerValue = state.timer.current; RuntimeService.previousTimerValue = state.timer.current;
RuntimeService.previousClockUpdate = state.clock;
eventStore.set('clock', state.clock);
eventStore.set('timer', state.timer); eventStore.set('timer', state.timer);
RuntimeService.previousState.timer = { ...state.timer }; 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); eventStore.set('runtime', state.runtime);
RuntimeService.previousClockUpdate = state.clock;
RuntimeService.previousRuntimeUpdate = state.clock;
eventStore.set('clock', state.clock);
RuntimeService.previousState.runtime = { ...state.runtime }; RuntimeService.previousState.runtime = { ...state.runtime };
} }
@@ -711,15 +728,14 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
updateEventIfChanged('eventNext', state); updateEventIfChanged('eventNext', state);
updateEventIfChanged('publicEventNext', state); updateEventIfChanged('publicEventNext', state);
let syncBlockStartAt = false;
if (!deepEqual(RuntimeService?.previousState.currentBlock, state.currentBlock)) { if (!deepEqual(RuntimeService?.previousState.currentBlock, state.currentBlock)) {
eventStore.set('currentBlock', state.currentBlock); eventStore.set('currentBlock', state.currentBlock);
RuntimeService.previousState.currentBlock = { ...state.currentBlock }; RuntimeService.previousState.currentBlock = { ...state.currentBlock };
syncBlockStartAt = true; RuntimeService.previousClockUpdate = state.clock;
eventStore.set('clock', state.clock);
} }
const shouldUpdateClock = syncBlockStartAt || getShouldClockUpdate(RuntimeService.previousClockUpdate, state.clock); const shouldUpdateClock = getShouldClockUpdate(RuntimeService.previousClockUpdate, state.clock);
if (shouldUpdateClock) { if (shouldUpdateClock) {
RuntimeService.previousClockUpdate = state.clock; RuntimeService.previousClockUpdate = state.clock;
@@ -1,7 +1,7 @@
import { millisToSeconds } from 'ontime-utils'; import { millisToSeconds } from 'ontime-utils';
import { timerConfig } from '../../config/config.js'; 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 * 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 * In some cases we want to force an update to the timer
* - if the clock has slid back * - if the clock has slid back
* - if we have escaped the update rate (clock slid forward) * - 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 isClockBehind = now < previousUpdate;
const hasExceededRate = now - previousUpdate >= timerConfig.notificationRate; const hasExceededRate = now - previousUpdate >= timerConfig.notificationRate;
return isClockBehind || hasExceededRate; return isClockBehind || hasExceededRate;
+16 -10
View File
@@ -7,6 +7,8 @@ export type StoreGetter = <T extends keyof RuntimeStore>(key: T) => Partial<Runt
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
@@ -23,16 +25,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-${key}`, // check if the key is already marked for and update otherwise push it onto the update array
payload: value, changedKeys.add(key);
});
}, //if there is already and update pending we don't need to schedule another one
batchSet(values: Partial<RuntimeStore>) { if (!isUpdatePending) {
Object.entries(values).forEach(([key, value]) => { isUpdatePending = setImmediate(() => {
store[key] = value; for (const dataKey of changedKeys) {
}); socket.sendAsJson({ type: `ontime-${dataKey}`, payload: store[dataKey] });
this.broadcast(); }
isUpdatePending = null;
changedKeys.clear();
});
}
}, },
poll() { poll() {
return store; return store;