refactor: timer update

This commit is contained in:
Carlos Valente
2024-07-16 21:29:31 +02:00
committed by Carlos Valente
parent cb773ded9f
commit edc7f20d7a
7 changed files with 195 additions and 397 deletions
@@ -29,7 +29,9 @@ import {
getEventWithId,
getPlayableEvents,
} from '../rundown-service/rundownUtils.js';
import { skippedOutOfEvent } from '../timerUtils.js';
import { integrationService } from '../integration-service/IntegrationService.js';
import { getForceUpdate, getShouldClockUpdate, getShouldTimerUpdate } from './rundownService.utils.js';
/**
@@ -37,18 +39,21 @@ import { getForceUpdate, getShouldClockUpdate, getShouldTimerUpdate } from './ru
* Coordinating with necessary services
*/
class RuntimeService {
private eventTimer: TimerService | null = null;
private eventTimer: TimerService;
private lastIntegrationClockUpdate = -1;
private lastIntegrationTimerValue = -1;
/** last time we updated the socket */
static previousTimerUpdate: number;
static previousTimerValue: MaybeNumber;
static previousTimerValue: MaybeNumber; // previous timer value, could be null
static previousClockUpdate: number;
/** last known state */
static previousState: RuntimeState;
constructor() {
constructor(timerService: TimerService) {
this.eventTimer = timerService;
RuntimeService.previousTimerUpdate = -1;
RuntimeService.previousTimerValue = -1;
RuntimeService.previousClockUpdate = -1;
@@ -59,7 +64,45 @@ class RuntimeService {
@broadcastResult
checkTimerUpdate({ shouldCallRoll, hasTimerFinished }: runtimeState.UpdateResult) {
const newState = runtimeState.getState();
if (hasTimerFinished) {
// 1. find if we need to dispatch integrations related to the phase
const timerPhaseChanged = RuntimeService.previousState.timer?.phase !== newState.timer.phase;
if (timerPhaseChanged) {
if (newState.timer.phase === TimerPhase.Warning) {
process.nextTick(() => {
integrationService.dispatch(TimerLifeCycle.onWarning);
});
} else if (newState.timer.phase === TimerPhase.Danger) {
process.nextTick(() => {
integrationService.dispatch(TimerLifeCycle.onDanger);
});
}
}
// 2. handle edge cases related to roll
if (newState.timer.playback === Playback.Roll) {
// check if we need to call roll again
const needsEvent =
newState.eventNow === null
? true
: skippedOutOfEvent(newState, this.lastIntegrationClockUpdate, timerConfig.skipLimit);
const hasFinishedRoll = hasTimerFinished && shouldCallRoll;
if (shouldCallRoll || needsEvent) {
if (hasFinishedRoll) {
process.nextTick(() => {
integrationService.dispatch(TimerLifeCycle.onFinish);
});
}
// we dont call this.roll because we need to bypass the checks
const rundown = getPlayableEvents();
// TODO: by not calling roll, we dont get the events
this.eventTimer.roll(rundown);
}
}
// 3. find if we need to process actions related to the timer finishing
if (newState.timer.playback !== Playback.Roll && hasTimerFinished) {
process.nextTick(() => {
integrationService.dispatch(TimerLifeCycle.onFinish);
});
@@ -77,10 +120,8 @@ class RuntimeService {
}
}
const hasRunningTimer = Boolean(newState.eventNow) && newState.timer.playback === Playback.Play;
const shouldUpdateTimer =
hasRunningTimer && getShouldTimerUpdate(this.lastIntegrationTimerValue, newState.timer.current);
// 4. find if we need to update the timer
const shouldUpdateTimer = getShouldTimerUpdate(this.lastIntegrationTimerValue, newState.timer.current);
if (shouldUpdateTimer) {
process.nextTick(() => {
integrationService.dispatch(TimerLifeCycle.onUpdate);
@@ -89,6 +130,7 @@ class RuntimeService {
this.lastIntegrationTimerValue = newState.timer.current ?? -1;
}
// 5. find if we need to update the clock
const shouldUpdateClock = getShouldClockUpdate(this.lastIntegrationClockUpdate, newState.clock);
if (shouldUpdateClock) {
process.nextTick(() => {
@@ -97,42 +139,12 @@ class RuntimeService {
this.lastIntegrationClockUpdate = newState.clock;
}
if (shouldCallRoll) {
// we dont call this.roll because we need to bypass the checks
const rundown = getPlayableEvents();
this.eventTimer.roll(rundown);
}
const timerPhaseChanged = RuntimeService.previousState.timer?.phase !== newState.timer.phase;
if (timerPhaseChanged) {
switch (newState.timer.phase) {
case TimerPhase.Warning:
process.nextTick(() => {
integrationService.dispatch(TimerLifeCycle.onWarning);
});
break;
case TimerPhase.Danger:
process.nextTick(() => {
integrationService.dispatch(TimerLifeCycle.onDanger);
});
break;
default:
break;
}
}
}
/** delay initialisation until we have a restore point */
init(resumable: RestorePoint | null) {
logger.info(LogOrigin.Server, 'Runtime service started');
// calculate at 30fps, refresh at 1fps
this.eventTimer = new TimerService({
refresh: timerConfig.updateRate,
updateInterval: timerConfig.notificationRate,
onUpdateCallback: (updateResult) => this.checkTimerUpdate(updateResult),
});
this.eventTimer.setOnUpdateCallback((updateResult) => this.checkTimerUpdate(updateResult));
if (resumable) {
this.resume(resumable);
@@ -553,8 +565,16 @@ class RuntimeService {
}
}
export const runtimeService = new RuntimeService();
// calculate at 30fps, refresh at 1fps
const eventTimer = new TimerService({
refresh: timerConfig.updateRate,
updateInterval: timerConfig.notificationRate,
});
export const runtimeService = new RuntimeService(eventTimer);
/**
* Decorator manages side effects from updating the runtime
*/
function broadcastResult(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;