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 */
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);
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;
@@ -696,12 +705,20 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
if (hasImmediateChanges || (shouldUpdateTimer && !deepEqual(RuntimeService.previousState?.timer, state.timer))) {
RuntimeService.previousTimerUpdate = state.clock;
RuntimeService.previousTimerValue = state.timer.current;
RuntimeService.previousClockUpdate = state.clock;
eventStore.set('clock', state.clock);
eventStore.set('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);
RuntimeService.previousClockUpdate = state.clock;
RuntimeService.previousRuntimeUpdate = state.clock;
eventStore.set('clock', state.clock);
RuntimeService.previousState.runtime = { ...state.runtime };
}
@@ -711,15 +728,14 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
updateEventIfChanged('eventNext', state);
updateEventIfChanged('publicEventNext', state);
let syncBlockStartAt = false;
if (!deepEqual(RuntimeService?.previousState.currentBlock, state.currentBlock)) {
eventStore.set('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) {
RuntimeService.previousClockUpdate = state.clock;
@@ -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;
+16 -10
View File
@@ -7,6 +7,8 @@ export type StoreGetter = <T extends keyof RuntimeStore>(key: T) => Partial<Runt
let store: Partial<RuntimeStore> = {};
const changedKeys = new Set<keyof RuntimeStore>();
let isUpdatePending: NodeJS.Immediate | null = null;
/**
* A runtime store that broadcasts its payload
* - 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]) {
store[key] = value;
socket.sendAsJson({
type: `ontime-${key}`,
payload: value,
});
},
batchSet(values: Partial<RuntimeStore>) {
Object.entries(values).forEach(([key, value]) => {
store[key] = value;
});
this.broadcast();
// check if the key is already marked for and update otherwise push it onto the update array
changedKeys.add(key);
//if there is already and update pending we don't need to schedule another one
if (!isUpdatePending) {
isUpdatePending = setImmediate(() => {
for (const dataKey of changedKeys) {
socket.sendAsJson({ type: `ontime-${dataKey}`, payload: store[dataKey] });
}
isUpdatePending = null;
changedKeys.clear();
});
}
},
poll() {
return store;