mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 06:59:09 +00:00
refactor: runtime side effects
This commit is contained in:
committed by
Carlos Valente
parent
accdf2c396
commit
6e61a7ecf9
@@ -1,27 +1,15 @@
|
|||||||
import { OntimeEvent, Playback, RuntimeStore } from 'ontime-types';
|
import { OntimeEvent } from 'ontime-types';
|
||||||
|
|
||||||
import { deepEqual } from 'fast-equals';
|
|
||||||
|
|
||||||
import { eventStore } from '../stores/EventStore.js';
|
|
||||||
import * as runtimeState from '../stores/runtimeState.js';
|
import * as runtimeState from '../stores/runtimeState.js';
|
||||||
import type { RuntimeState, UpdateResult } from '../stores/runtimeState.js';
|
import type { UpdateResult } from '../stores/runtimeState.js';
|
||||||
|
|
||||||
import { restoreService } from './RestoreService.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service manages Ontime's main timer
|
* Service manages Ontime's main timer
|
||||||
* It is responsible for streaming the data to the event store
|
|
||||||
*/
|
*/
|
||||||
export class TimerService {
|
export class TimerService {
|
||||||
private readonly _interval: NodeJS.Timer;
|
private readonly _interval: NodeJS.Timer;
|
||||||
/** how often we update the socket */
|
|
||||||
static _updateInterval: number;
|
|
||||||
/** how often we recalculate */
|
/** how often we recalculate */
|
||||||
static _refreshInterval: number;
|
static _refreshInterval: number;
|
||||||
/** last time we updated the socket */
|
|
||||||
static previousUpdate: number;
|
|
||||||
/** last known state */
|
|
||||||
static previousState: RuntimeState;
|
|
||||||
|
|
||||||
/** when timer will be finished */
|
/** when timer will be finished */
|
||||||
private endCallback: NodeJS.Timer;
|
private endCallback: NodeJS.Timer;
|
||||||
@@ -39,10 +27,6 @@ export class TimerService {
|
|||||||
updateInterval: number;
|
updateInterval: number;
|
||||||
onUpdateCallback: (updateResult: UpdateResult) => void;
|
onUpdateCallback: (updateResult: UpdateResult) => void;
|
||||||
}) {
|
}) {
|
||||||
TimerService.previousUpdate = -1;
|
|
||||||
TimerService.previousState = {} as RuntimeState;
|
|
||||||
|
|
||||||
TimerService._updateInterval = timerConfig.updateInterval;
|
|
||||||
TimerService._refreshInterval = timerConfig.refresh;
|
TimerService._refreshInterval = timerConfig.refresh;
|
||||||
|
|
||||||
this.onUpdateCallback = timerConfig.onUpdateCallback;
|
this.onUpdateCallback = timerConfig.onUpdateCallback;
|
||||||
@@ -51,7 +35,6 @@ export class TimerService {
|
|||||||
}, TimerService._refreshInterval);
|
}, TimerService._refreshInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
@broadcastResult
|
|
||||||
start() {
|
start() {
|
||||||
if (!runtimeState.start()) {
|
if (!runtimeState.start()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -63,7 +46,6 @@ export class TimerService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@broadcastResult
|
|
||||||
pause() {
|
pause() {
|
||||||
if (!runtimeState.pause()) {
|
if (!runtimeState.pause()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -74,7 +56,6 @@ export class TimerService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@broadcastResult
|
|
||||||
stop() {
|
stop() {
|
||||||
if (!runtimeState.stop()) {
|
if (!runtimeState.stop()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -89,7 +70,6 @@ export class TimerService {
|
|||||||
* Adds time to running timer by given amount
|
* Adds time to running timer by given amount
|
||||||
* @param {number} amount
|
* @param {number} amount
|
||||||
*/
|
*/
|
||||||
@broadcastResult
|
|
||||||
addTime(amount: number): boolean {
|
addTime(amount: number): boolean {
|
||||||
if (!runtimeState.addTime(amount)) {
|
if (!runtimeState.addTime(amount)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -105,7 +85,6 @@ export class TimerService {
|
|||||||
/**
|
/**
|
||||||
* Update the app at regular intervals
|
* Update the app at regular intervals
|
||||||
*/
|
*/
|
||||||
@broadcastResult
|
|
||||||
update() {
|
update() {
|
||||||
const updateResult = runtimeState.update();
|
const updateResult = runtimeState.update();
|
||||||
// pass the result to the parent
|
// pass the result to the parent
|
||||||
@@ -116,7 +95,6 @@ export class TimerService {
|
|||||||
* Loads roll information into timer service
|
* Loads roll information into timer service
|
||||||
* @param {OntimeEvent[]} rundown -- list of events to run
|
* @param {OntimeEvent[]} rundown -- list of events to run
|
||||||
*/
|
*/
|
||||||
@broadcastResult
|
|
||||||
roll(rundown: OntimeEvent[]) {
|
roll(rundown: OntimeEvent[]) {
|
||||||
runtimeState.roll(rundown);
|
runtimeState.roll(rundown);
|
||||||
}
|
}
|
||||||
@@ -126,99 +104,3 @@ export class TimerService {
|
|||||||
clearTimeout(this.endCallback);
|
clearTimeout(this.endCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function broadcastResult(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
|
||||||
const originalMethod = descriptor.value;
|
|
||||||
|
|
||||||
descriptor.value = function (...args: any[]) {
|
|
||||||
// call the original method and get the state
|
|
||||||
const result = originalMethod.apply(this, args);
|
|
||||||
const state = runtimeState.getState();
|
|
||||||
|
|
||||||
// we do the comparison by explicitly for each property
|
|
||||||
// to apply custom logic for different datasets
|
|
||||||
|
|
||||||
// some of the data, we only update at intervals
|
|
||||||
const isTimeToUpdate =
|
|
||||||
state.clock < TimerService.previousUpdate ||
|
|
||||||
state.clock - TimerService.previousUpdate >= TimerService._updateInterval;
|
|
||||||
|
|
||||||
// some changes need an immediate update
|
|
||||||
const hasNewLoaded = state.eventNow?.id !== TimerService.previousState?.eventNow?.id;
|
|
||||||
|
|
||||||
const hasSkippedBack = state.clock < TimerService.previousUpdate;
|
|
||||||
const justStarted = !TimerService.previousState?.timer;
|
|
||||||
const hasChangedPlayback = TimerService.previousState.timer?.playback !== state.timer.playback;
|
|
||||||
const hasImmediateChanges = hasNewLoaded || hasSkippedBack || justStarted || hasChangedPlayback;
|
|
||||||
|
|
||||||
if (hasChangedPlayback) {
|
|
||||||
eventStore.set('onAir', state.timer.playback !== Playback.Stop);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasImmediateChanges || (isTimeToUpdate && !deepEqual(TimerService.previousState?.timer, state.timer))) {
|
|
||||||
eventStore.set('timer', state.timer);
|
|
||||||
TimerService.previousState.timer = { ...state.timer };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasChangedPlayback || (isTimeToUpdate && !deepEqual(TimerService.previousState?.runtime, state.runtime))) {
|
|
||||||
eventStore.set('runtime', state.runtime);
|
|
||||||
TimerService.previousState.runtime = { ...state.runtime };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the events if they have changed
|
|
||||||
updateEventIfChanged('eventNow', state);
|
|
||||||
updateEventIfChanged('publicEventNow', state);
|
|
||||||
updateEventIfChanged('eventNext', state);
|
|
||||||
updateEventIfChanged('publicEventNext', state);
|
|
||||||
|
|
||||||
if (isTimeToUpdate) {
|
|
||||||
TimerService.previousUpdate = state.clock;
|
|
||||||
eventStore.set('clock', state.clock);
|
|
||||||
saveRestoreState(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to update an event if it has changed
|
|
||||||
function updateEventIfChanged(eventKey: keyof RuntimeStore, state: RuntimeState) {
|
|
||||||
const previous = TimerService.previousState?.[eventKey];
|
|
||||||
const now = state[eventKey];
|
|
||||||
|
|
||||||
// if there was nothing, and there is nothing, noop
|
|
||||||
if (!previous?.id && !now?.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if load status changed, save new
|
|
||||||
if (previous?.id !== now?.id) {
|
|
||||||
storeKey(eventKey);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maybe the event itself has changed
|
|
||||||
if (!deepEqual(TimerService.previousState?.[eventKey], state[eventKey])) {
|
|
||||||
storeKey(eventKey);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function storeKey(eventKey: keyof RuntimeStore) {
|
|
||||||
eventStore.set(eventKey, state[eventKey]);
|
|
||||||
TimerService.previousState[eventKey] = { ...state[eventKey] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to save the restore state
|
|
||||||
function saveRestoreState(state: RuntimeState) {
|
|
||||||
restoreService.save({
|
|
||||||
playback: state.timer.playback,
|
|
||||||
selectedEventId: state.eventNow?.id ?? null,
|
|
||||||
startedAt: state.timer.startedAt,
|
|
||||||
addedTime: state.timer.addedTime,
|
|
||||||
pausedAt: state._timer.pausedAt,
|
|
||||||
firstStart: state.runtime.actualStart,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
return descriptor;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { LogOrigin } from 'ontime-types';
|
import { LogOrigin } from 'ontime-types';
|
||||||
|
|
||||||
import IIntegration, { TimerLifeCycleKey } from './IIntegration.js';
|
import IIntegration, { TimerLifeCycleKey } from './IIntegration.js';
|
||||||
import { getState } from '../../stores/runtimeState.js';
|
import { eventStore } from '../../stores/EventStore.js';
|
||||||
import { logger } from '../../classes/Logger.js';
|
import { logger } from '../../classes/Logger.js';
|
||||||
|
|
||||||
class IntegrationService {
|
class IntegrationService {
|
||||||
@@ -20,14 +20,7 @@ class IntegrationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch(action: TimerLifeCycleKey) {
|
dispatch(action: TimerLifeCycleKey) {
|
||||||
/**
|
const state = eventStore.poll();
|
||||||
* We currently get the state from the runtimeState store
|
|
||||||
* This solves an issue where the state is not updated until after the integrations have ran
|
|
||||||
* The workaround solves the issue with the tradeoff of
|
|
||||||
* - we do not have access to data outside runtimeState (eg: messages or auxtimers)
|
|
||||||
* - we couple the integrationService to runtimeState
|
|
||||||
*/
|
|
||||||
const state = getState();
|
|
||||||
this.integrations.forEach((integration) => {
|
this.integrations.forEach((integration) => {
|
||||||
integration.dispatch(action, state);
|
integration.dispatch(action, state);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { EndAction, LogOrigin, OntimeEvent, Playback, TimerLifeCycle } from 'ontime-types';
|
import { EndAction, LogOrigin, OntimeEvent, Playback, RuntimeStore, TimerLifeCycle } from 'ontime-types';
|
||||||
import { millisToString, validatePlayback } from 'ontime-utils';
|
import { millisToString, validatePlayback } from 'ontime-utils';
|
||||||
|
|
||||||
import { TimerService } from '../TimerService.js';
|
import { deepEqual } from 'fast-equals';
|
||||||
|
|
||||||
import { logger } from '../../classes/Logger.js';
|
import { logger } from '../../classes/Logger.js';
|
||||||
import { RestorePoint } from '../RestoreService.js';
|
|
||||||
|
|
||||||
import * as runtimeState from '../../stores/runtimeState.js';
|
import * as runtimeState from '../../stores/runtimeState.js';
|
||||||
|
import type { RuntimeState } from '../../stores/runtimeState.js';
|
||||||
|
import { timerConfig } from '../../config/config.js';
|
||||||
|
import { eventStore } from '../../stores/EventStore.js';
|
||||||
|
|
||||||
|
import { TimerService } from '../TimerService.js';
|
||||||
|
import { RestorePoint, restoreService } from '../RestoreService.js';
|
||||||
import {
|
import {
|
||||||
findNext,
|
findNext,
|
||||||
findPrevious,
|
findPrevious,
|
||||||
@@ -16,7 +20,6 @@ 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 { timerConfig } from '../../config/config.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service manages runtime status of app
|
* Service manages runtime status of app
|
||||||
@@ -26,11 +29,24 @@ class RuntimeService {
|
|||||||
private eventTimer: TimerService | null = null;
|
private eventTimer: TimerService | null = null;
|
||||||
private lastOnUpdate = -1;
|
private lastOnUpdate = -1;
|
||||||
|
|
||||||
|
/** last time we updated the socket */
|
||||||
|
static previousUpdate: number;
|
||||||
|
/** last known state */
|
||||||
|
static previousState: RuntimeState;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
RuntimeService.previousUpdate = -1;
|
||||||
|
RuntimeService.previousState = {} as RuntimeState;
|
||||||
|
}
|
||||||
|
|
||||||
/** Checks result of an update and notifies integrations as needed */
|
/** Checks result of an update and notifies integrations as needed */
|
||||||
|
@broadcastResult
|
||||||
checkTimerUpdate({ shouldCallRoll, hasTimerFinished }: runtimeState.UpdateResult) {
|
checkTimerUpdate({ shouldCallRoll, hasTimerFinished }: runtimeState.UpdateResult) {
|
||||||
const newState = runtimeState.getState();
|
const newState = runtimeState.getState();
|
||||||
if (hasTimerFinished) {
|
if (hasTimerFinished) {
|
||||||
integrationService.dispatch(TimerLifeCycle.onFinish);
|
process.nextTick(() => {
|
||||||
|
integrationService.dispatch(TimerLifeCycle.onFinish);
|
||||||
|
});
|
||||||
|
|
||||||
// handle end action if there was a timer playing
|
// handle end action if there was a timer playing
|
||||||
// actions are added to the queue stack to ensure that the order of operations is maintained
|
// actions are added to the queue stack to ensure that the order of operations is maintained
|
||||||
@@ -49,10 +65,14 @@ class RuntimeService {
|
|||||||
if (newState.clock - this.lastOnUpdate >= timerConfig.notificationRate) {
|
if (newState.clock - this.lastOnUpdate >= timerConfig.notificationRate) {
|
||||||
const hasRunningTimer = Boolean(newState.eventNow) && newState.timer.playback === Playback.Play;
|
const hasRunningTimer = Boolean(newState.eventNow) && newState.timer.playback === Playback.Play;
|
||||||
if (hasRunningTimer) {
|
if (hasRunningTimer) {
|
||||||
integrationService.dispatch(TimerLifeCycle.onUpdate);
|
process.nextTick(() => {
|
||||||
|
integrationService.dispatch(TimerLifeCycle.onUpdate);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
process.nextTick(() => {
|
||||||
|
integrationService.dispatch(TimerLifeCycle.onClock);
|
||||||
|
});
|
||||||
|
|
||||||
integrationService.dispatch(TimerLifeCycle.onClock);
|
|
||||||
this.lastOnUpdate = newState.clock;
|
this.lastOnUpdate = newState.clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +206,7 @@ class RuntimeService {
|
|||||||
* @param {OntimeEvent} event
|
* @param {OntimeEvent} event
|
||||||
* @return {boolean} success - whether an event was loaded
|
* @return {boolean} success - whether an event was loaded
|
||||||
*/
|
*/
|
||||||
|
@broadcastResult
|
||||||
loadEvent(event: OntimeEvent): boolean {
|
loadEvent(event: OntimeEvent): boolean {
|
||||||
if (event.skip) {
|
if (event.skip) {
|
||||||
logger.warning(LogOrigin.Playback, `Refused skipped event with ID ${event.id}`);
|
logger.warning(LogOrigin.Playback, `Refused skipped event with ID ${event.id}`);
|
||||||
@@ -196,10 +217,10 @@ class RuntimeService {
|
|||||||
const success = runtimeState.load(event, timedEvents);
|
const success = runtimeState.load(event, timedEvents);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
// TODO: dispatch should happen after store update
|
|
||||||
// currently store update is handled in TimerService only
|
|
||||||
integrationService.dispatch(TimerLifeCycle.onLoad);
|
|
||||||
logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`);
|
logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`);
|
||||||
|
process.nextTick(() => {
|
||||||
|
integrationService.dispatch(TimerLifeCycle.onLoad);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
@@ -325,6 +346,7 @@ class RuntimeService {
|
|||||||
/**
|
/**
|
||||||
* Starts playback on selected event
|
* Starts playback on selected event
|
||||||
*/
|
*/
|
||||||
|
@broadcastResult
|
||||||
start(): boolean {
|
start(): boolean {
|
||||||
const state = runtimeState.getState();
|
const state = runtimeState.getState();
|
||||||
const canStart = validatePlayback(state.timer.playback).start;
|
const canStart = validatePlayback(state.timer.playback).start;
|
||||||
@@ -335,7 +357,9 @@ class RuntimeService {
|
|||||||
const didStart = this.eventTimer?.start() ?? false;
|
const didStart = this.eventTimer?.start() ?? false;
|
||||||
logger.info(LogOrigin.Playback, `Play Mode ${state.timer.playback.toUpperCase()}`);
|
logger.info(LogOrigin.Playback, `Play Mode ${state.timer.playback.toUpperCase()}`);
|
||||||
if (didStart) {
|
if (didStart) {
|
||||||
integrationService.dispatch(TimerLifeCycle.onStart);
|
process.nextTick(() => {
|
||||||
|
integrationService.dispatch(TimerLifeCycle.onStart);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return didStart;
|
return didStart;
|
||||||
}
|
}
|
||||||
@@ -367,6 +391,7 @@ class RuntimeService {
|
|||||||
/**
|
/**
|
||||||
* Pauses playback on selected event
|
* Pauses playback on selected event
|
||||||
*/
|
*/
|
||||||
|
@broadcastResult
|
||||||
pause() {
|
pause() {
|
||||||
const state = runtimeState.getState();
|
const state = runtimeState.getState();
|
||||||
const canPause = validatePlayback(state.timer.playback).pause;
|
const canPause = validatePlayback(state.timer.playback).pause;
|
||||||
@@ -376,12 +401,15 @@ class RuntimeService {
|
|||||||
this.eventTimer?.pause();
|
this.eventTimer?.pause();
|
||||||
const newState = state.timer.playback;
|
const newState = state.timer.playback;
|
||||||
logger.info(LogOrigin.Playback, `Play Mode ${newState.toUpperCase()}`);
|
logger.info(LogOrigin.Playback, `Play Mode ${newState.toUpperCase()}`);
|
||||||
integrationService.dispatch(TimerLifeCycle.onPause);
|
process.nextTick(() => {
|
||||||
|
integrationService.dispatch(TimerLifeCycle.onPause);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops timer and unloads any events
|
* Stops timer and unloads any events
|
||||||
*/
|
*/
|
||||||
|
@broadcastResult
|
||||||
stop(): boolean {
|
stop(): boolean {
|
||||||
const state = runtimeState.getState();
|
const state = runtimeState.getState();
|
||||||
const canStop = validatePlayback(state.timer.playback).stop;
|
const canStop = validatePlayback(state.timer.playback).stop;
|
||||||
@@ -392,7 +420,10 @@ class RuntimeService {
|
|||||||
if (didStop) {
|
if (didStop) {
|
||||||
const newState = state.timer.playback;
|
const newState = state.timer.playback;
|
||||||
logger.info(LogOrigin.Playback, `Play Mode ${newState.toUpperCase()}`);
|
logger.info(LogOrigin.Playback, `Play Mode ${newState.toUpperCase()}`);
|
||||||
integrationService.dispatch(TimerLifeCycle.onStop);
|
process.nextTick(() => {
|
||||||
|
integrationService.dispatch(TimerLifeCycle.onStop);
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -401,6 +432,7 @@ class RuntimeService {
|
|||||||
/**
|
/**
|
||||||
* Reloads current event
|
* Reloads current event
|
||||||
*/
|
*/
|
||||||
|
@broadcastResult
|
||||||
reload() {
|
reload() {
|
||||||
const state = runtimeState.getState();
|
const state = runtimeState.getState();
|
||||||
if (state.eventNow) {
|
if (state.eventNow) {
|
||||||
@@ -411,6 +443,7 @@ class RuntimeService {
|
|||||||
/**
|
/**
|
||||||
* Sets playback to roll
|
* Sets playback to roll
|
||||||
*/
|
*/
|
||||||
|
@broadcastResult
|
||||||
roll() {
|
roll() {
|
||||||
const beforeState = runtimeState.getState();
|
const beforeState = runtimeState.getState();
|
||||||
const canRoll = validatePlayback(beforeState.timer.playback).roll;
|
const canRoll = validatePlayback(beforeState.timer.playback).roll;
|
||||||
@@ -435,6 +468,7 @@ class RuntimeService {
|
|||||||
* @description resume playback state given a restore point
|
* @description resume playback state given a restore point
|
||||||
* @param restorePoint
|
* @param restorePoint
|
||||||
*/
|
*/
|
||||||
|
@broadcastResult
|
||||||
resume(restorePoint: RestorePoint) {
|
resume(restorePoint: RestorePoint) {
|
||||||
const { selectedEventId, playback } = restorePoint;
|
const { selectedEventId, playback } = restorePoint;
|
||||||
if (playback === Playback.Roll) {
|
if (playback === Playback.Roll) {
|
||||||
@@ -470,3 +504,99 @@ class RuntimeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const runtimeService = new RuntimeService();
|
export const runtimeService = new RuntimeService();
|
||||||
|
|
||||||
|
function broadcastResult(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||||
|
const originalMethod = descriptor.value;
|
||||||
|
|
||||||
|
descriptor.value = function (...args: any[]) {
|
||||||
|
// call the original method and get the state
|
||||||
|
const result = originalMethod.apply(this, args);
|
||||||
|
const state = runtimeState.getState();
|
||||||
|
|
||||||
|
// we do the comparison by explicitly for each property
|
||||||
|
// to apply custom logic for different datasets
|
||||||
|
|
||||||
|
// some of the data, we only update at intervals
|
||||||
|
const isTimeToUpdate =
|
||||||
|
state.clock < RuntimeService.previousUpdate ||
|
||||||
|
state.clock - RuntimeService.previousUpdate >= timerConfig.notificationRate;
|
||||||
|
|
||||||
|
// some changes need an immediate update
|
||||||
|
const hasNewLoaded = state.eventNow?.id !== RuntimeService.previousState?.eventNow?.id;
|
||||||
|
|
||||||
|
const hasSkippedBack = state.clock < RuntimeService.previousUpdate;
|
||||||
|
const justStarted = !RuntimeService.previousState?.timer;
|
||||||
|
const hasChangedPlayback = RuntimeService.previousState.timer?.playback !== state.timer.playback;
|
||||||
|
const hasImmediateChanges = hasNewLoaded || hasSkippedBack || justStarted || hasChangedPlayback;
|
||||||
|
|
||||||
|
if (hasChangedPlayback) {
|
||||||
|
eventStore.set('onAir', state.timer.playback !== Playback.Stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasImmediateChanges || (isTimeToUpdate && !deepEqual(RuntimeService.previousState?.timer, state.timer))) {
|
||||||
|
eventStore.set('timer', state.timer);
|
||||||
|
RuntimeService.previousState.timer = { ...state.timer };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChangedPlayback || (isTimeToUpdate && !deepEqual(RuntimeService.previousState?.runtime, state.runtime))) {
|
||||||
|
eventStore.set('runtime', state.runtime);
|
||||||
|
RuntimeService.previousState.runtime = { ...state.runtime };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the events if they have changed
|
||||||
|
updateEventIfChanged('eventNow', state);
|
||||||
|
updateEventIfChanged('publicEventNow', state);
|
||||||
|
updateEventIfChanged('eventNext', state);
|
||||||
|
updateEventIfChanged('publicEventNext', state);
|
||||||
|
|
||||||
|
if (isTimeToUpdate) {
|
||||||
|
RuntimeService.previousUpdate = state.clock;
|
||||||
|
eventStore.set('clock', state.clock);
|
||||||
|
saveRestoreState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to update an event if it has changed
|
||||||
|
function updateEventIfChanged(eventKey: keyof RuntimeStore, state: runtimeState.RuntimeState) {
|
||||||
|
const previous = RuntimeService.previousState?.[eventKey];
|
||||||
|
const now = state[eventKey];
|
||||||
|
|
||||||
|
// if there was nothing, and there is nothing, noop
|
||||||
|
if (!previous?.id && !now?.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if load status changed, save new
|
||||||
|
if (previous?.id !== now?.id) {
|
||||||
|
storeKey(eventKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybe the event itself has changed
|
||||||
|
if (!deepEqual(RuntimeService.previousState?.[eventKey], state[eventKey])) {
|
||||||
|
storeKey(eventKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function storeKey(eventKey: keyof RuntimeStore) {
|
||||||
|
eventStore.set(eventKey, state[eventKey]);
|
||||||
|
RuntimeService.previousState[eventKey] = { ...state[eventKey] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to save the restore state
|
||||||
|
function saveRestoreState(state: runtimeState.RuntimeState) {
|
||||||
|
restoreService.save({
|
||||||
|
playback: state.timer.playback,
|
||||||
|
selectedEventId: state.eventNow?.id ?? null,
|
||||||
|
startedAt: state.timer.startedAt,
|
||||||
|
addedTime: state.timer.addedTime,
|
||||||
|
pausedAt: state._timer.pausedAt,
|
||||||
|
firstStart: state.runtime.actualStart,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user