mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-13 19:33:46 +00:00
Refactor: split runtime state (#1725)
* refactor: runtime types * refactor: runtimeState functions to use the split type * refactor: RuntimeService to use the new split data * refactor: use the split data in the UI * update comments * refactor: rename runtime to offset * fix utils test
This commit is contained in:
committed by
Carlos Valente
parent
ed93cf313a
commit
048a9c96ea
@@ -12,7 +12,6 @@ export type RestorePoint = {
|
||||
addedTime: number;
|
||||
pausedAt: MaybeNumber;
|
||||
firstStart: MaybeNumber;
|
||||
groupStartAt: MaybeNumber;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -51,10 +50,6 @@ export function isRestorePoint(obj: unknown): obj is RestorePoint {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof restorePoint.groupStartAt !== 'number' && restorePoint.groupStartAt !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ describe('isRestorePoint()', () => {
|
||||
addedTime: 2,
|
||||
pausedAt: 3,
|
||||
firstStart: 1,
|
||||
groupStartAt: 10,
|
||||
};
|
||||
expect(isRestorePoint(restorePoint)).toBe(true);
|
||||
|
||||
@@ -25,7 +24,6 @@ describe('isRestorePoint()', () => {
|
||||
addedTime: 0,
|
||||
pausedAt: null,
|
||||
firstStart: 1,
|
||||
groupStartAt: null,
|
||||
};
|
||||
expect(isRestorePoint(restorePoint)).toBe(true);
|
||||
});
|
||||
@@ -76,7 +74,6 @@ describe('RestoreService()', () => {
|
||||
addedTime: 5678,
|
||||
pausedAt: 9087,
|
||||
firstStart: 1234,
|
||||
groupStartAt: 1652,
|
||||
};
|
||||
|
||||
const restoreService = new RestoreService('/path/to/restore/file');
|
||||
@@ -94,7 +91,6 @@ describe('RestoreService()', () => {
|
||||
addedTime: 0,
|
||||
pausedAt: null,
|
||||
firstStart: 1234,
|
||||
groupStartAt: null,
|
||||
};
|
||||
|
||||
const restoreService = new RestoreService('/path/to/restore/file');
|
||||
@@ -132,7 +128,6 @@ describe('RestoreService()', () => {
|
||||
addedTime: 1234,
|
||||
pausedAt: 1234,
|
||||
firstStart: 1234,
|
||||
groupStartAt: null,
|
||||
};
|
||||
|
||||
const restoreService = new RestoreService('/path/to/restore/file');
|
||||
|
||||
@@ -204,7 +204,7 @@ describe('getExpectedFinish()', () => {
|
||||
pausedAt: null,
|
||||
hasFinished: false,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
actualStart: 79200000,
|
||||
plannedEnd: 600000,
|
||||
},
|
||||
@@ -358,7 +358,7 @@ describe('getCurrent()', () => {
|
||||
duration: 100,
|
||||
startedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
plannedEnd: null,
|
||||
},
|
||||
_timer: {
|
||||
@@ -383,7 +383,7 @@ describe('getCurrent()', () => {
|
||||
duration: 100,
|
||||
startedAt: 10,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
plannedEnd: 100,
|
||||
},
|
||||
_timer: {
|
||||
@@ -408,7 +408,7 @@ describe('getCurrent()', () => {
|
||||
duration: 100,
|
||||
startedAt: 10,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
plannedEnd: 100,
|
||||
},
|
||||
_timer: {
|
||||
@@ -434,7 +434,7 @@ describe('getCurrent()', () => {
|
||||
duration: Infinity, // not relevant,
|
||||
startedAt: 79200000, // 22:00:00
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
actualStart: 79200000,
|
||||
plannedEnd: 600000,
|
||||
},
|
||||
@@ -462,7 +462,7 @@ describe('getCurrent()', () => {
|
||||
duration: Infinity, // not relevant,
|
||||
startedAt: 79200000, // 22:00:00
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
actualStart: 82000000, // 22:46:40 <--- started now
|
||||
plannedEnd: 81000000, // 22:30:00
|
||||
},
|
||||
@@ -735,14 +735,14 @@ describe('getRuntimeOffset()', () => {
|
||||
_timer: {
|
||||
pausedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
actualStart: 150,
|
||||
plannedStart: 100,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
const { offsetAbs } = getRuntimeOffset(state);
|
||||
expect(offsetAbs).toBe(50);
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
expect(absolute).toBe(50);
|
||||
});
|
||||
|
||||
it('added time subtracts time offset (positive offset)', () => {
|
||||
@@ -759,14 +759,14 @@ describe('getRuntimeOffset()', () => {
|
||||
_timer: {
|
||||
pausedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
actualStart: 150,
|
||||
plannedStart: 100,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
const { offsetAbs } = getRuntimeOffset(state);
|
||||
expect(offsetAbs).toBe(60);
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
expect(absolute).toBe(60);
|
||||
});
|
||||
|
||||
it('considers running overtime (negative offset)', () => {
|
||||
@@ -784,14 +784,14 @@ describe('getRuntimeOffset()', () => {
|
||||
_timer: {
|
||||
pausedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
actualStart: 100,
|
||||
plannedStart: 100,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
const { offsetAbs } = getRuntimeOffset(state);
|
||||
expect(offsetAbs).toBe(10);
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
expect(absolute).toBe(10);
|
||||
});
|
||||
|
||||
it('paused time is delayed time (negative offset)', () => {
|
||||
@@ -810,14 +810,14 @@ describe('getRuntimeOffset()', () => {
|
||||
_timer: {
|
||||
pausedAt: 125, // we have been paused for 25ms (see clock)
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
actualStart: 100,
|
||||
plannedStart: 100,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
const { offsetAbs } = getRuntimeOffset(state);
|
||||
expect(offsetAbs).toBe(25);
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
expect(absolute).toBe(25);
|
||||
});
|
||||
|
||||
it('offset doesnt exist if we havent started', () => {
|
||||
@@ -831,14 +831,16 @@ describe('getRuntimeOffset()', () => {
|
||||
timeStrategy: 'lock-duration',
|
||||
linkStart: false,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
selectedEventIndex: 0,
|
||||
numEvents: 2,
|
||||
offsetAbs: -77400000,
|
||||
plannedStart: 77400000,
|
||||
plannedEnd: 84600000,
|
||||
actualStart: null,
|
||||
expectedEnd: null,
|
||||
},
|
||||
offset: {
|
||||
absolute: -77400000,
|
||||
expectedRundownEnd: null,
|
||||
},
|
||||
timer: {
|
||||
addedTime: 0,
|
||||
@@ -853,8 +855,8 @@ describe('getRuntimeOffset()', () => {
|
||||
_timer: { pausedAt: null },
|
||||
} as RuntimeState;
|
||||
|
||||
const { offsetAbs } = getRuntimeOffset(state);
|
||||
expect(offsetAbs).toBe(0);
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
expect(absolute).toBe(0);
|
||||
});
|
||||
|
||||
it('with time-to-end, offsets dont exist if we are not in overtime', () => {
|
||||
@@ -882,14 +884,16 @@ describe('getRuntimeOffset()', () => {
|
||||
custom: {},
|
||||
delay: 0,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
selectedEventIndex: 0,
|
||||
numEvents: 1,
|
||||
offsetAbs: 0,
|
||||
plannedStart: 77400000, // 21:30:00
|
||||
plannedEnd: 81000000, // 22:30:00
|
||||
actualStart: 78000000, // 21:40:00
|
||||
expectedEnd: 81600000, // 22:40:00
|
||||
},
|
||||
offset: {
|
||||
absolute: 0,
|
||||
expectedRundownEnd: 81600000, // 22:40:00
|
||||
},
|
||||
timer: {
|
||||
addedTime: 0,
|
||||
@@ -904,8 +908,8 @@ describe('getRuntimeOffset()', () => {
|
||||
_timer: { pausedAt: null },
|
||||
} as RuntimeState;
|
||||
|
||||
const { offsetAbs } = getRuntimeOffset(state);
|
||||
expect(offsetAbs).toBe(0);
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
expect(absolute).toBe(0);
|
||||
});
|
||||
|
||||
it('with time-to-end, offset is the overtime', () => {
|
||||
@@ -933,14 +937,16 @@ describe('getRuntimeOffset()', () => {
|
||||
custom: {},
|
||||
delay: 0,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
selectedEventIndex: 0,
|
||||
numEvents: 1,
|
||||
offsetAbs: 0,
|
||||
plannedStart: 77400000, // 21:30:00
|
||||
plannedEnd: 81000000, // 22:30:00
|
||||
actualStart: 78000000, // 21:40:00
|
||||
expectedEnd: 81600000, // 22:40:00
|
||||
},
|
||||
offset: {
|
||||
absolute: 0,
|
||||
expectedRundownEnd: 81600000, // 22:40:00
|
||||
},
|
||||
timer: {
|
||||
addedTime: -200000,
|
||||
@@ -955,8 +961,8 @@ describe('getRuntimeOffset()', () => {
|
||||
_timer: { pausedAt: null },
|
||||
} as RuntimeState;
|
||||
|
||||
const { offsetAbs } = getRuntimeOffset(state);
|
||||
expect(offsetAbs).toBe(400000); // <--- offset is always the overtime
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
expect(absolute).toBe(400000); // <--- offset is always the overtime
|
||||
});
|
||||
|
||||
it('handles time-to-end started after the end time', () => {
|
||||
@@ -973,14 +979,16 @@ describe('getRuntimeOffset()', () => {
|
||||
timerType: TimerType.CountDown,
|
||||
countToEnd: true,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
selectedEventIndex: 0,
|
||||
numEvents: 1,
|
||||
offsetAbs: 0,
|
||||
plannedStart: 77400000, // 21:30:00
|
||||
plannedEnd: 81000000, // 22:30:00
|
||||
actualStart: 82000000, // 22:46:40 <--- started now
|
||||
expectedEnd: 82000000 + 3600000, // <--- now + duration
|
||||
},
|
||||
offset: {
|
||||
absolute: 0,
|
||||
expectedRundownEnd: 82000000 + 3600000, // <--- now + duration
|
||||
},
|
||||
timer: {
|
||||
addedTime: 0,
|
||||
@@ -997,9 +1005,9 @@ describe('getRuntimeOffset()', () => {
|
||||
|
||||
const updateCurrent = getCurrent(state);
|
||||
state.timer.current = updateCurrent;
|
||||
const { offsetAbs } = getRuntimeOffset(state);
|
||||
expect(millisToString(offsetAbs)).toBe('00:16:40');
|
||||
expect(offsetAbs).toBe(82000000 - 81000000); // <-- now - planned end
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
expect(millisToString(absolute)).toBe('00:16:40');
|
||||
expect(absolute).toBe(82000000 - 81000000); // <-- now - planned end
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1018,15 +1026,15 @@ describe('getoffsetRel()', () => {
|
||||
_timer: {
|
||||
pausedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
actualStart: 150,
|
||||
plannedStart: 150,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
const { offsetAbs, offsetRel } = getRuntimeOffset(state);
|
||||
expect(offsetAbs).toBe(0);
|
||||
expect(offsetRel).toBe(0);
|
||||
const { absolute, relative } = getRuntimeOffset(state);
|
||||
expect(absolute).toBe(0);
|
||||
expect(relative).toBe(0);
|
||||
});
|
||||
it('relative offset is 0 when starting after the planed time', () => {
|
||||
const state = {
|
||||
@@ -1042,15 +1050,15 @@ describe('getoffsetRel()', () => {
|
||||
_timer: {
|
||||
pausedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
actualStart: 150,
|
||||
plannedStart: 100,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
const { offsetAbs, offsetRel } = getRuntimeOffset(state);
|
||||
expect(offsetAbs).toBe(50);
|
||||
expect(offsetRel).toBe(0);
|
||||
const { absolute, relative } = getRuntimeOffset(state);
|
||||
expect(absolute).toBe(50);
|
||||
expect(relative).toBe(0);
|
||||
});
|
||||
it('relative offset is 0 when starting before the planed time', () => {
|
||||
const state = {
|
||||
@@ -1066,15 +1074,15 @@ describe('getoffsetRel()', () => {
|
||||
_timer: {
|
||||
pausedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
rundown: {
|
||||
actualStart: 100,
|
||||
plannedStart: 150,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
const { offsetAbs, offsetRel } = getRuntimeOffset(state);
|
||||
expect(offsetAbs).toBe(-50);
|
||||
expect(offsetRel).toBe(0);
|
||||
const { absolute, relative } = getRuntimeOffset(state);
|
||||
expect(absolute).toBe(-50);
|
||||
expect(relative).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1175,14 +1183,16 @@ describe('getTimerPhase()', () => {
|
||||
clock: 55691050,
|
||||
eventNow: null,
|
||||
eventNext: null,
|
||||
runtime: {
|
||||
rundown: {
|
||||
selectedEventIndex: null,
|
||||
numEvents: 1,
|
||||
offsetAbs: 0,
|
||||
plannedStart: 55860000,
|
||||
plannedEnd: 55880000,
|
||||
actualStart: null,
|
||||
expectedEnd: null,
|
||||
},
|
||||
offset: {
|
||||
absolute: 0,
|
||||
expectedRundownEnd: null,
|
||||
},
|
||||
timer: {
|
||||
addedTime: 0,
|
||||
@@ -1213,14 +1223,16 @@ describe('getTimerPhase()', () => {
|
||||
clock: 55691050,
|
||||
eventNow: null,
|
||||
eventNext: null,
|
||||
runtime: {
|
||||
rundown: {
|
||||
selectedEventIndex: null,
|
||||
numEvents: 1,
|
||||
offsetAbs: 0,
|
||||
plannedStart: 55860000,
|
||||
plannedEnd: 55880000,
|
||||
actualStart: null,
|
||||
expectedEnd: null,
|
||||
},
|
||||
offset: {
|
||||
absolute: 0,
|
||||
expectedRundownEnd: null,
|
||||
},
|
||||
timer: {
|
||||
addedTime: 0,
|
||||
|
||||
@@ -3,10 +3,10 @@ import {
|
||||
isOntimeEvent,
|
||||
isPlayableEvent,
|
||||
LogOrigin,
|
||||
MaybeNumber,
|
||||
OffsetMode,
|
||||
OntimeEvent,
|
||||
Playback,
|
||||
RuntimeStore,
|
||||
TimerLifeCycle,
|
||||
TimerPhase,
|
||||
TimerState,
|
||||
@@ -34,16 +34,12 @@ import {
|
||||
findPreviousPlayableId,
|
||||
getEventAtIndex,
|
||||
getShouldClockUpdate,
|
||||
getShouldFlagUpdate,
|
||||
getShouldGroupUpdate,
|
||||
getShouldRuntimeUpdate,
|
||||
getShouldOffsetUpdate,
|
||||
getShouldTimerUpdate,
|
||||
isNewSecond,
|
||||
} from './rundownService.utils.js';
|
||||
import { RundownMetadata } from '../../api-data/rundown/rundown.types.js';
|
||||
|
||||
type RuntimeStateEventKeys = keyof Pick<RuntimeState, 'eventNext' | 'eventNow'>;
|
||||
|
||||
/**
|
||||
* Service manages runtime status of app
|
||||
* Coordinating with necessary services
|
||||
@@ -53,22 +49,11 @@ class RuntimeService {
|
||||
private lastIntegrationClockUpdate = -1;
|
||||
private lastIntegrationTimerValue = -1;
|
||||
|
||||
/** last time we updated the socket */
|
||||
static previousTimerUpdate: number;
|
||||
static previousRuntimeUpdate: number;
|
||||
static previousTimerValue: MaybeNumber; // previous timer value, could be null
|
||||
static previousClockUpdate: number;
|
||||
|
||||
/** last known state */
|
||||
static previousState: RuntimeState;
|
||||
|
||||
constructor(eventTimer: EventTimer) {
|
||||
this.eventTimer = eventTimer;
|
||||
|
||||
RuntimeService.previousTimerUpdate = -1;
|
||||
RuntimeService.previousRuntimeUpdate = -1;
|
||||
RuntimeService.previousTimerValue = -1;
|
||||
RuntimeService.previousClockUpdate = -1;
|
||||
RuntimeService.previousState = {} as RuntimeState;
|
||||
}
|
||||
|
||||
@@ -101,7 +86,7 @@ class RuntimeService {
|
||||
// 2. handle edge cases related to roll
|
||||
if (newState.timer.playback === Playback.Roll) {
|
||||
// check if we need to call any side effects
|
||||
const keepOffset = newState.runtime.offsetAbs;
|
||||
const keepOffset = newState.offset.absolute;
|
||||
if (hasSecondaryTimerFinished) {
|
||||
// if the secondary timer has finished, we need to call roll
|
||||
// since event is already loaded
|
||||
@@ -292,7 +277,7 @@ class RuntimeService {
|
||||
rundown,
|
||||
playableEventOrder,
|
||||
cue,
|
||||
state.runtime.selectedEventIndex ?? undefined,
|
||||
state.rundown.selectedEventIndex ?? undefined,
|
||||
);
|
||||
|
||||
if (!event) {
|
||||
@@ -353,7 +338,7 @@ class RuntimeService {
|
||||
rundown,
|
||||
playableEventOrder,
|
||||
cue,
|
||||
state.runtime.selectedEventIndex ?? undefined,
|
||||
state.rundown.selectedEventIndex ?? undefined,
|
||||
);
|
||||
|
||||
if (!event) {
|
||||
@@ -412,7 +397,7 @@ class RuntimeService {
|
||||
}
|
||||
|
||||
if (state.timer.playback === Playback.Roll) {
|
||||
return this.loadEvent(nextEvent, { firstStart: state.runtime.actualStart });
|
||||
return this.loadEvent(nextEvent, { firstStart: state.rundown.actualStart });
|
||||
}
|
||||
return this.loadEvent(nextEvent);
|
||||
}
|
||||
@@ -667,20 +652,24 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
||||
// we do the comparison by explicitly for each property
|
||||
// to apply custom logic for different datasets
|
||||
|
||||
// if a new event was loaded most things should update
|
||||
const hasNewLoaded = state.eventNow?.id !== RuntimeService.previousState?.eventNow?.id;
|
||||
// Update the entry if they have changed
|
||||
let entryChanged = false;
|
||||
entryChanged ||= updateMaybeEntryIfChanged('eventNow');
|
||||
entryChanged ||= updateMaybeEntryIfChanged('eventNext');
|
||||
entryChanged ||= updateMaybeEntryIfChanged('eventFlag');
|
||||
entryChanged ||= updateMaybeEntryIfChanged('groupNow');
|
||||
|
||||
// for the very fist run there will be nothing in the previousState so we force an update
|
||||
const justStarted = !RuntimeService.previousState?.timer;
|
||||
|
||||
// offset mode has been changed
|
||||
const offsetModeChanged = RuntimeService.previousState?.runtime?.offsetMode !== state.runtime.offsetMode;
|
||||
const offsetModeChanged = RuntimeService.previousState?.offset?.mode !== state.offset.mode;
|
||||
|
||||
// if playback changes most things should update
|
||||
const hasChangedPlayback = RuntimeService.previousState.timer?.playback !== state.timer.playback;
|
||||
|
||||
// combine all big changes
|
||||
const hasImmediateChanges = hasNewLoaded || justStarted || hasChangedPlayback || offsetModeChanged;
|
||||
const hasImmediateChanges = entryChanged || justStarted || hasChangedPlayback || offsetModeChanged;
|
||||
|
||||
// clock has changed by a second or more
|
||||
const updateClock = getShouldClockUpdate(RuntimeService.previousState.clock, state.clock);
|
||||
@@ -693,74 +682,43 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
||||
const updateTimer = getShouldTimerUpdate(RuntimeService.previousState?.timer, state.timer);
|
||||
if (updateTimer) {
|
||||
batch.add('timer', state.timer);
|
||||
RuntimeService.previousTimerUpdate = state.clock;
|
||||
RuntimeService.previousTimerValue = state.timer.current;
|
||||
RuntimeService.previousState.timer = { ...state.timer };
|
||||
}
|
||||
|
||||
// if any values have changed, values that have the possibility to tick are modulated by `hasClockUpdate`
|
||||
const updateRuntime = getShouldRuntimeUpdate(
|
||||
RuntimeService.previousState?.runtime,
|
||||
state.runtime,
|
||||
const updateRuntime = getShouldOffsetUpdate(
|
||||
RuntimeService.previousState?.offset,
|
||||
state.offset,
|
||||
updateClock || hasImmediateChanges,
|
||||
);
|
||||
if (updateRuntime) {
|
||||
batch.add('runtime', state.runtime);
|
||||
RuntimeService.previousRuntimeUpdate = state.clock;
|
||||
RuntimeService.previousState.runtime = structuredClone(state.runtime);
|
||||
batch.add('offset', state.offset);
|
||||
RuntimeService.previousState.offset = structuredClone(state.offset);
|
||||
}
|
||||
|
||||
// if any values have changed, values that have the possibility to tick are modulated by `hasClockUpdate`
|
||||
const updateGroupNow = getShouldGroupUpdate(
|
||||
RuntimeService.previousState.groupNow,
|
||||
state.groupNow,
|
||||
updateClock || hasImmediateChanges,
|
||||
);
|
||||
if (updateGroupNow) {
|
||||
batch.add('groupNow', state.groupNow);
|
||||
RuntimeService.previousState.groupNow = structuredClone(state.groupNow);
|
||||
// if any values have changed
|
||||
const updateRundownData = !deepEqual(RuntimeService.previousState.rundown, state.rundown);
|
||||
if (updateRundownData) {
|
||||
batch.add('rundown', state.rundown);
|
||||
RuntimeService.previousState.rundown = structuredClone(state.rundown);
|
||||
}
|
||||
|
||||
// next group is just a simple string or null compare
|
||||
const updateGroupNext = RuntimeService.previousState.groupNext !== state.groupNext;
|
||||
if (updateGroupNext) {
|
||||
batch.add('groupNext', state.groupNext);
|
||||
RuntimeService.previousState.groupNext = structuredClone(state.groupNext);
|
||||
}
|
||||
function updateMaybeEntryIfChanged<
|
||||
K extends keyof Pick<RuntimeState, 'eventNow' | 'eventNext' | 'eventFlag' | 'groupNow'>,
|
||||
>(key: K) {
|
||||
const previousEntry = RuntimeService.previousState[key];
|
||||
const currentEntry = state[key];
|
||||
|
||||
// if any values have changed, values that have the possibility to tick are modulated by `hasClockUpdate`
|
||||
const updateFlag = getShouldFlagUpdate(
|
||||
RuntimeService.previousState.nextFlag,
|
||||
state.nextFlag,
|
||||
updateClock || hasImmediateChanges,
|
||||
);
|
||||
if (updateFlag) {
|
||||
batch.add('nextFlag', state.nextFlag);
|
||||
RuntimeService.previousState.nextFlag = structuredClone(state.nextFlag);
|
||||
}
|
||||
if (!previousEntry && !currentEntry) return false; // if both are null -> skip
|
||||
|
||||
// Update the events if they have changed
|
||||
updateEventIfChanged('eventNow', state);
|
||||
updateEventIfChanged('eventNext', state);
|
||||
|
||||
// Helper function to update an event if it has changed
|
||||
function updateEventIfChanged(eventKey: RuntimeStateEventKeys, 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;
|
||||
|
||||
const eventChanged =
|
||||
// if load status changed, save new
|
||||
previous?.id !== now?.id ||
|
||||
// maybe the event itself has changed
|
||||
!deepEqual(RuntimeService.previousState?.[eventKey], state[eventKey]);
|
||||
|
||||
if (!eventChanged) return;
|
||||
|
||||
batch.add(eventKey, state[eventKey]);
|
||||
RuntimeService.previousState[eventKey] = structuredClone(state[eventKey]);
|
||||
// if they have the same id the check if the contents have changed
|
||||
if (previousEntry?.id === currentEntry?.id) {
|
||||
if (deepEqual(previousEntry, currentEntry)) return false; // contents are the same -> skip
|
||||
}
|
||||
// at this point we know that either the id or the contents has changed
|
||||
batch.add(key, currentEntry as RuntimeStore[K]); // we know that there is the necessary overlap in the types to cast this
|
||||
RuntimeService.previousState[key] = structuredClone(currentEntry);
|
||||
return true;
|
||||
}
|
||||
|
||||
// save the restore state
|
||||
@@ -772,8 +730,7 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
||||
startedAt: state.timer.startedAt,
|
||||
addedTime: state.timer.addedTime,
|
||||
pausedAt: state._timer.pausedAt,
|
||||
firstStart: state.runtime.actualStart,
|
||||
groupStartAt: state.groupNow?.startedAt ?? null,
|
||||
firstStart: state.rundown.actualStart,
|
||||
})
|
||||
.catch((_e) => {
|
||||
//we don't do anything with the error here
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { millisToSeconds } from 'ontime-utils';
|
||||
import {
|
||||
EntryId,
|
||||
GroupState,
|
||||
isOntimeEvent,
|
||||
isPlayableEvent,
|
||||
MaybeNumber,
|
||||
OntimeEvent,
|
||||
Rundown,
|
||||
Runtime,
|
||||
Offset,
|
||||
TimerState,
|
||||
TimerType,
|
||||
UpcomingEntry,
|
||||
} from 'ontime-types';
|
||||
|
||||
import { deepEqual } from 'fast-equals';
|
||||
@@ -55,51 +53,15 @@ export function getShouldTimerUpdate(previousValue: TimerState | undefined, curr
|
||||
);
|
||||
}
|
||||
|
||||
export function getShouldRuntimeUpdate(
|
||||
previousValue: Runtime | undefined,
|
||||
currentValue: Runtime,
|
||||
export function getShouldOffsetUpdate(
|
||||
previousValue: Offset | undefined,
|
||||
currentValue: Offset,
|
||||
didDependencyUpdate: boolean,
|
||||
): boolean {
|
||||
if (previousValue === undefined) return true;
|
||||
if (didDependencyUpdate) return !deepEqual(previousValue, currentValue);
|
||||
|
||||
return (
|
||||
previousValue.selectedEventIndex !== currentValue.selectedEventIndex ||
|
||||
previousValue.numEvents !== currentValue.numEvents ||
|
||||
previousValue.plannedStart !== currentValue.plannedStart ||
|
||||
previousValue.plannedEnd !== currentValue.plannedEnd ||
|
||||
previousValue.actualStart !== currentValue.actualStart ||
|
||||
previousValue.offsetMode !== currentValue.offsetMode
|
||||
// offsetAbs, offsetRel and expectedEnd are ticked with `didDependencyUpdate`
|
||||
);
|
||||
}
|
||||
|
||||
export function getShouldGroupUpdate(
|
||||
previousValue: GroupState | null | undefined,
|
||||
currentValue: GroupState | null,
|
||||
didDependencyUpdate: boolean,
|
||||
): boolean {
|
||||
if (previousValue === undefined) return true;
|
||||
if (didDependencyUpdate) return !deepEqual(previousValue, currentValue);
|
||||
|
||||
return (
|
||||
previousValue?.id !== currentValue?.id || previousValue?.startedAt !== currentValue?.startedAt
|
||||
// expectedEnd are ticked with `didDependencyUpdate`
|
||||
);
|
||||
}
|
||||
|
||||
export function getShouldFlagUpdate(
|
||||
previousValue: UpcomingEntry | null | undefined,
|
||||
currentValue: UpcomingEntry | null,
|
||||
didDependencyUpdate: boolean,
|
||||
): boolean {
|
||||
if (previousValue === undefined) return true;
|
||||
if (didDependencyUpdate) return !deepEqual(previousValue, currentValue);
|
||||
|
||||
return (
|
||||
previousValue?.id !== currentValue?.id
|
||||
// expectedStart
|
||||
);
|
||||
if (previousValue.mode !== currentValue.mode) return true;
|
||||
// absolute, relative, expected*End are ticked with `didDependencyUpdate`
|
||||
return didDependencyUpdate && !deepEqual(previousValue, currentValue);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -112,23 +112,23 @@ export function skippedOutOfEvent(state: RuntimeState, previousTime: number, ski
|
||||
* Positive offset is over time / behind schedule
|
||||
* Negative offset is under time / ahead of schedule
|
||||
*/
|
||||
export function getRuntimeOffset(state: RuntimeState): { offsetAbs: number; offsetRel: number } {
|
||||
export function getRuntimeOffset(state: RuntimeState): { absolute: number; relative: number } {
|
||||
const { eventNow, clock } = state;
|
||||
const { addedTime, current, startedAt } = state.timer;
|
||||
// nothing to calculate if there are no loaded events or if we havent started
|
||||
if (eventNow === null || startedAt === null) {
|
||||
return { offsetAbs: 0, offsetRel: 0 };
|
||||
return { absolute: 0, relative: 0 };
|
||||
}
|
||||
|
||||
const { countToEnd, timeStart } = eventNow;
|
||||
const { plannedStart, actualStart } = state.runtime;
|
||||
const { plannedStart, actualStart } = state.rundown;
|
||||
|
||||
// eslint-disable-next-line no-unused-labels -- dev code path
|
||||
DEV: {
|
||||
// we know current exists as long as eventNow exists
|
||||
if (current === null) throw new Error('timerUtils.getRuntimeOffset: state.timer.current must be set');
|
||||
if (plannedStart === null) throw new Error('timerUtils.getRuntimeOffset: state.runtime.plannedStart must be set');
|
||||
if (actualStart === null) throw new Error('timerUtils.getRuntimeOffset: state.runtime.actualStart must be set');
|
||||
if (plannedStart === null) throw new Error('timerUtils.getRuntimeOffset: state.rundown.plannedStart must be set');
|
||||
if (actualStart === null) throw new Error('timerUtils.getRuntimeOffset: state.rundown.plannedStart must be set');
|
||||
}
|
||||
|
||||
// difference between planned event start and actual event start (will be positive if we stared behind )
|
||||
@@ -140,13 +140,13 @@ export function getRuntimeOffset(state: RuntimeState): { offsetAbs: number; offs
|
||||
// time the playback was paused, the different from now to when we paused is added to the offset TODO: brakes when crossing midnight
|
||||
const pausedTime = state._timer.pausedAt === null ? 0 : clock - state._timer.pausedAt;
|
||||
|
||||
const offsetAbs = eventStartOffset + overtime + pausedTime + addedTime;
|
||||
const absolute = eventStartOffset + overtime + pausedTime + addedTime;
|
||||
|
||||
// the relative offset i the same as the absolute offset but adjusted relative to the actual start time
|
||||
const offsetRel = offsetAbs + plannedStart - actualStart;
|
||||
const relative = absolute + plannedStart - actualStart;
|
||||
|
||||
// in case of count to end, the absolute offset is just the overtime
|
||||
return countToEnd ? { offsetAbs: overtime, offsetRel } : { offsetAbs, offsetRel };
|
||||
return countToEnd ? { absolute: overtime, relative } : { absolute, relative };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user