mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-10 08:39:34 +00:00
refactor: leading updates in timers
This commit is contained in:
committed by
Carlos Valente
parent
52145d5e1e
commit
c57b0423af
@@ -2,5 +2,5 @@ export const timerConfig = {
|
|||||||
skipLimit: 1000, // threshold of skip for recalculating
|
skipLimit: 1000, // threshold of skip for recalculating
|
||||||
updateRate: 32, // how often do we update the timer
|
updateRate: 32, // how often do we update the timer
|
||||||
notificationRate: 1000, // how often do we notify clients and integrations
|
notificationRate: 1000, // how often do we notify clients and integrations
|
||||||
triggerAhead: 16, // how far ahead do we trigger the end event
|
triggerAhead: 10, // how far ahead do we trigger the end event
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { OntimeEvent } from 'ontime-types';
|
|||||||
|
|
||||||
import * as runtimeState from '../stores/runtimeState.js';
|
import * as runtimeState from '../stores/runtimeState.js';
|
||||||
import type { UpdateResult } from '../stores/runtimeState.js';
|
import type { UpdateResult } from '../stores/runtimeState.js';
|
||||||
|
import { timerConfig } from '../config/config.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service manages Ontime's main timer
|
* Service manages Ontime's main timer
|
||||||
@@ -41,7 +42,7 @@ export class TimerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const state = runtimeState.getState();
|
const state = runtimeState.getState();
|
||||||
const endTime = state.timer.current - 10;
|
const endTime = state.timer.current - timerConfig.triggerAhead;
|
||||||
this.endCallback = setTimeout(() => this.update(), endTime);
|
this.endCallback = setTimeout(() => this.update(), endTime);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EndAction, LogOrigin, OntimeEvent, Playback, RuntimeStore, TimerLifeCycle } from 'ontime-types';
|
import { EndAction, LogOrigin, MaybeNumber, OntimeEvent, Playback, RuntimeStore, TimerLifeCycle } from 'ontime-types';
|
||||||
import { millisToString, validatePlayback } from 'ontime-utils';
|
import { millisToString, validatePlayback } from 'ontime-utils';
|
||||||
|
|
||||||
import { deepEqual } from 'fast-equals';
|
import { deepEqual } from 'fast-equals';
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
getPlayableEvents,
|
getPlayableEvents,
|
||||||
} from '../rundown-service/rundownUtils.js';
|
} from '../rundown-service/rundownUtils.js';
|
||||||
import { integrationService } from '../integration-service/IntegrationService.js';
|
import { integrationService } from '../integration-service/IntegrationService.js';
|
||||||
|
import { getShouldClockUpdate, getShouldTimerUpdate } from './rundownService.utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service manages runtime status of app
|
* Service manages runtime status of app
|
||||||
@@ -30,12 +31,16 @@ class RuntimeService {
|
|||||||
private lastOnUpdate = -1;
|
private lastOnUpdate = -1;
|
||||||
|
|
||||||
/** last time we updated the socket */
|
/** last time we updated the socket */
|
||||||
static previousUpdate: number;
|
static previousTimerUpdate: number;
|
||||||
|
static previousTimerValue: MaybeNumber;
|
||||||
|
static previousClockUpdate: number;
|
||||||
/** last known state */
|
/** last known state */
|
||||||
static previousState: RuntimeState;
|
static previousState: RuntimeState;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
RuntimeService.previousUpdate = -1;
|
RuntimeService.previousTimerUpdate = -1;
|
||||||
|
RuntimeService.previousTimerValue = -1;
|
||||||
|
RuntimeService.previousClockUpdate = -1;
|
||||||
RuntimeService.previousState = {} as RuntimeState;
|
RuntimeService.previousState = {} as RuntimeState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,29 +521,33 @@ 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
|
||||||
|
|
||||||
// some of the data, we only update at intervals
|
const shouldUpdateClock = getShouldClockUpdate(RuntimeService.previousClockUpdate, state.clock);
|
||||||
const isTimeToUpdate =
|
const shouldUpdateTimer = getShouldTimerUpdate(
|
||||||
state.clock < RuntimeService.previousUpdate ||
|
RuntimeService.previousTimerValue,
|
||||||
state.clock - RuntimeService.previousUpdate >= timerConfig.notificationRate;
|
state.timer.current,
|
||||||
|
RuntimeService.previousTimerUpdate,
|
||||||
|
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;
|
||||||
|
|
||||||
const hasSkippedBack = state.clock < RuntimeService.previousUpdate;
|
|
||||||
const justStarted = !RuntimeService.previousState?.timer;
|
const justStarted = !RuntimeService.previousState?.timer;
|
||||||
const hasChangedPlayback = RuntimeService.previousState.timer?.playback !== state.timer.playback;
|
const hasChangedPlayback = RuntimeService.previousState.timer?.playback !== state.timer.playback;
|
||||||
const hasImmediateChanges = hasNewLoaded || hasSkippedBack || justStarted || hasChangedPlayback;
|
const hasImmediateChanges = hasNewLoaded || justStarted || hasChangedPlayback;
|
||||||
|
|
||||||
if (hasChangedPlayback) {
|
if (hasChangedPlayback) {
|
||||||
eventStore.set('onAir', state.timer.playback !== Playback.Stop);
|
eventStore.set('onAir', state.timer.playback !== Playback.Stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasImmediateChanges || (isTimeToUpdate && !deepEqual(RuntimeService.previousState?.timer, state.timer))) {
|
if (hasImmediateChanges || (shouldUpdateTimer && !deepEqual(RuntimeService.previousState?.timer, state.timer))) {
|
||||||
|
RuntimeService.previousTimerUpdate = state.clock;
|
||||||
|
RuntimeService.previousTimerValue = state.timer.current;
|
||||||
eventStore.set('timer', state.timer);
|
eventStore.set('timer', state.timer);
|
||||||
RuntimeService.previousState.timer = { ...state.timer };
|
RuntimeService.previousState.timer = { ...state.timer };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasChangedPlayback || (isTimeToUpdate && !deepEqual(RuntimeService.previousState?.runtime, state.runtime))) {
|
if (hasChangedPlayback || (shouldUpdateTimer && !deepEqual(RuntimeService.previousState?.runtime, state.runtime))) {
|
||||||
eventStore.set('runtime', state.runtime);
|
eventStore.set('runtime', state.runtime);
|
||||||
RuntimeService.previousState.runtime = { ...state.runtime };
|
RuntimeService.previousState.runtime = { ...state.runtime };
|
||||||
}
|
}
|
||||||
@@ -549,8 +558,8 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
|||||||
updateEventIfChanged('eventNext', state);
|
updateEventIfChanged('eventNext', state);
|
||||||
updateEventIfChanged('publicEventNext', state);
|
updateEventIfChanged('publicEventNext', state);
|
||||||
|
|
||||||
if (isTimeToUpdate) {
|
if (shouldUpdateClock) {
|
||||||
RuntimeService.previousUpdate = state.clock;
|
RuntimeService.previousClockUpdate = state.clock;
|
||||||
eventStore.set('clock', state.clock);
|
eventStore.set('clock', state.clock);
|
||||||
saveRestoreState(state);
|
saveRestoreState(state);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||||
|
import { getShouldClockUpdate, getShouldTimerUpdate } from '../rundownService.utils.js';
|
||||||
|
|
||||||
|
describe('getShouldClockUpdate()', () => {
|
||||||
|
it('should return true when we slid forwards', () => {
|
||||||
|
const previousUpdate = Date.now() - 2000; // 2 seconds ago
|
||||||
|
const now = Date.now();
|
||||||
|
const result = getShouldClockUpdate(previousUpdate, now);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when we slid backwards', () => {
|
||||||
|
const previousUpdate = Date.now();
|
||||||
|
const now = Date.now() - 2000; // 2 seconds ago
|
||||||
|
const result = getShouldClockUpdate(previousUpdate, now);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when clock is a second ahead', () => {
|
||||||
|
const previousUpdate = MILLIS_PER_MINUTE - 100;
|
||||||
|
const now = MILLIS_PER_MINUTE;
|
||||||
|
const result = getShouldClockUpdate(previousUpdate, now);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when clock is not a second ahead and force update is not required', () => {
|
||||||
|
const previousUpdate = Date.now() - 32;
|
||||||
|
const now = Date.now();
|
||||||
|
const result = getShouldClockUpdate(previousUpdate, now);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getShouldTimerUpdate', () => {
|
||||||
|
it('should return false when currentValue is null', () => {
|
||||||
|
const previousValue = 0;
|
||||||
|
const currentValue = null;
|
||||||
|
const previousUpdate = 0;
|
||||||
|
const now = 0;
|
||||||
|
const result = getShouldTimerUpdate(previousValue, currentValue, previousUpdate, now);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when timer is a second ahead', () => {
|
||||||
|
const previousValue = 5000;
|
||||||
|
const currentValue = 6500;
|
||||||
|
const previousUpdate = Date.now();
|
||||||
|
const now = Date.now();
|
||||||
|
const result = getShouldTimerUpdate(previousValue, currentValue, previousUpdate, now);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when timer is not a second ahead and force update is not required', () => {
|
||||||
|
const previousValue = 5000;
|
||||||
|
const currentValue = 5500;
|
||||||
|
const previousUpdate = Date.now();
|
||||||
|
const now = Date.now();
|
||||||
|
const result = getShouldTimerUpdate(previousValue, currentValue, previousUpdate, now);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { millisToSeconds } from 'ontime-utils';
|
||||||
|
|
||||||
|
import { timerConfig } from '../../config/config.js';
|
||||||
|
import { MaybeNumber } from 'ontime-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether we should update the clock value
|
||||||
|
* - clock has slid
|
||||||
|
* - we have rolled into a new seconds unit
|
||||||
|
*/
|
||||||
|
export function getShouldClockUpdate(previousUpdate: number, now: number): boolean {
|
||||||
|
const shouldForceUpdate = getForceUpdate(previousUpdate, now);
|
||||||
|
if (shouldForceUpdate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const isClockSecondAhead = millisToSeconds(now) !== millisToSeconds(previousUpdate + timerConfig.triggerAhead);
|
||||||
|
return isClockSecondAhead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether we should update the timer value
|
||||||
|
* - timer has slid (?)
|
||||||
|
* - we have rolled into a new seconds unit
|
||||||
|
*/
|
||||||
|
export function getShouldTimerUpdate(
|
||||||
|
previousValue: number,
|
||||||
|
currentValue: MaybeNumber,
|
||||||
|
previousUpdate: number,
|
||||||
|
now: number,
|
||||||
|
): boolean {
|
||||||
|
if (currentValue === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldForceUpdate = getForceUpdate(previousUpdate, now);
|
||||||
|
if (shouldForceUpdate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldUpdateTimer = millisToSeconds(currentValue) !== millisToSeconds(previousValue + timerConfig.triggerAhead);
|
||||||
|
|
||||||
|
return shouldUpdateTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
*/
|
||||||
|
function getForceUpdate(previousUpdate: number, now: number): boolean {
|
||||||
|
const isClockBehind = now < previousUpdate;
|
||||||
|
const hasExceededRate = now - previousUpdate >= timerConfig.notificationRate;
|
||||||
|
return isClockBehind || hasExceededRate;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user