refactor: use runtime metadate in runtimeState (#1653)

* change load to use metadata

* update roll to use metadata

* remove filterTimedEvents

* add comment

* atempth to refactor rollUtils

* refactor util functions
This commit is contained in:
Alex Christoffer Rasmussen
2025-06-22 10:32:48 +02:00
committed by GitHub
parent e090bde8bb
commit 14a99ce27b
7 changed files with 698 additions and 395 deletions
@@ -23,6 +23,7 @@ import {
import { event as eventDef, block as blockDef, delay as delayDef } from '../../models/eventsDefinition.js'; import { event as eventDef, block as blockDef, delay as delayDef } from '../../models/eventsDefinition.js';
import { makeString } from '../../utils/parserUtils.js'; import { makeString } from '../../utils/parserUtils.js';
import { RundownMetadata } from './rundown.types.js';
type CompleteEntry<T> = type CompleteEntry<T> =
T extends Partial<OntimeEvent> T extends Partial<OntimeEvent>
@@ -356,3 +357,23 @@ export function getInsertAfterId(rundown: Rundown, afterId?: EntryId, beforeId?:
return null; return null;
} }
/**
* converts an index from the timedEventOrder to an index in the playableEventOrder
* or returns null if it can not be found
*/
export function getPlayableIndexFromTimedIndex(metadata: RundownMetadata, index: number): number | null {
const timedId = metadata.timedEventOrder[index];
const playableIndex = metadata.playableEventOrder.findIndex((id) => id === timedId);
return playableIndex < 0 ? null : playableIndex;
}
/**
* converts an index from the playableEventOrder to an index in the timedEventOrder
* all indexes in playableEventOrder must also exist in timedEventOrder, otherwise the app is broken
*/
export function getTimedIndexFromPlayableIndex(metadata: RundownMetadata, index: number): number {
const playableId = metadata.playableEventOrder[index];
const timedIndex = metadata.timedEventOrder.findIndex((id) => id === playableId);
return timedIndex;
}
File diff suppressed because it is too large Load Diff
+14 -15
View File
@@ -1,22 +1,25 @@
import { dayInMs, getFirstEvent } from 'ontime-utils'; import { dayInMs } from 'ontime-utils';
import { OntimeEvent, MaybeNumber, PlayableEvent, isPlayableEvent } from 'ontime-types'; import { MaybeNumber, PlayableEvent, Rundown } from 'ontime-types';
import { normaliseEndTime } from './timerUtils.js'; import { normaliseEndTime } from './timerUtils.js';
import { RundownMetadata } from '../api-data/rundown/rundown.types.js';
import { getTimedIndexFromPlayableIndex } from '../api-data/rundown/rundown.utils.js';
/** /**
* Finds current event in a rolling rundown * Finds current event in a rolling rundown
*/ */
export function loadRoll( export function loadRoll(
timedEvents: OntimeEvent[], rundown: Rundown,
metadata: RundownMetadata,
timeNow: number, timeNow: number,
): { ): {
event: PlayableEvent | null; event: PlayableEvent | null;
index: MaybeNumber; index: MaybeNumber;
isPending?: boolean; isPending?: boolean;
} { } {
const { firstEvent } = getFirstEvent(timedEvents); const firstEventId = metadata.playableEventOrder[0];
if (!firstEvent) { if (!firstEventId) {
return { event: null, index: null }; return { event: null, index: null };
} }
@@ -24,13 +27,8 @@ export function loadRoll(
// account for number of times we went over midnight // account for number of times we went over midnight
let daySpan = 0; let daySpan = 0;
for (let i = 0; i < timedEvents.length; i++) { for (let i = 0; i < metadata.playableEventOrder.length; i++) {
const event = timedEvents[i]; const event = rundown.entries[metadata.playableEventOrder[i]] as PlayableEvent;
if (!isPlayableEvent(event)) {
continue;
}
if (event.duration === 0) { if (event.duration === 0) {
continue; continue;
} }
@@ -62,16 +60,17 @@ export function loadRoll(
const isFromDayBefore = normalEnd > dayInMs && timeNow < event.timeEnd; const isFromDayBefore = normalEnd > dayInMs && timeNow < event.timeEnd;
const hasStarted = isFromDayBefore || timeNow >= event.timeStart; const hasStarted = isFromDayBefore || timeNow >= event.timeStart;
if (hasStarted) { if (hasStarted) {
return { event, index: i }; return { event, index: getTimedIndexFromPlayableIndex(metadata, i) };
} }
// 3. event will run in the future // 3. event will run in the future
// we set the isPending flag to indicate that the event is currently playing // we set the isPending flag to indicate that the event is currently playing
return { event, index: i, isPending: true }; return { event, index: getTimedIndexFromPlayableIndex(metadata, i), isPending: true };
} }
// in case we were unable to find anything, we load the first event // in case we were unable to find anything, we load the first event
return { event: firstEvent, index: 0, isPending: true }; console.log('returning first event');
return { event: rundown.entries[firstEventId] as PlayableEvent, index: 0, isPending: true };
} }
/** /**
@@ -30,7 +30,6 @@ import { RestorePoint, restoreService } from '../RestoreService.js';
import { skippedOutOfEvent } from '../timerUtils.js'; import { skippedOutOfEvent } from '../timerUtils.js';
import { import {
filterTimedEvents,
findNextPlayableId, findNextPlayableId,
findNextPlayableWithCue, findNextPlayableWithCue,
findPreviousPlayableId, findPreviousPlayableId,
@@ -190,17 +189,15 @@ class RuntimeService {
} }
private isNewNext() { private isNewNext() {
const rundown = getCurrentRundown();
const { timedEventOrder } = getRundownMetadata(); const { timedEventOrder } = getRundownMetadata();
const timedEvents = filterTimedEvents(rundown, timedEventOrder);
const state = runtimeState.getState(); const state = runtimeState.getState();
const now = state.eventNow?.id; const now = state.eventNow?.id;
const next = state.eventNext?.id; const next = state.eventNext?.id;
// check whether the index of now and next are consecutive // check whether the index of now and next are consecutive
const indexNow = timedEvents.findIndex((event) => event.id === now); const indexNow = timedEventOrder.findIndex((id) => id === now);
const indexNext = timedEvents.findIndex((event) => event.id === next); const indexNext = timedEventOrder.findIndex((id) => id === next);
return indexNext - indexNow !== 1; return indexNext - indexNow !== 1;
} }
@@ -241,8 +238,8 @@ class RuntimeService {
runtimeState.updateLoaded(eventNow); runtimeState.updateLoaded(eventNow);
} else { } else {
const rundown = getCurrentRundown(); const rundown = getCurrentRundown();
const { timedEventOrder } = getRundownMetadata(); const metadata = getRundownMetadata();
runtimeState.updateAll(rundown, timedEventOrder); runtimeState.updateAll(rundown, metadata);
} }
return; return;
} }
@@ -252,9 +249,8 @@ class RuntimeService {
isNext = this.isNewNext(); isNext = this.isNewNext();
if (isNext) { if (isNext) {
const rundown = getCurrentRundown(); const rundown = getCurrentRundown();
const { timedEventOrder } = getRundownMetadata(); const metadata = getRundownMetadata();
const timedEvents = filterTimedEvents(rundown, timedEventOrder); runtimeState.loadNext(rundown, metadata);
runtimeState.loadNext(timedEvents);
} }
} }
@@ -273,8 +269,8 @@ class RuntimeService {
// we can ignore events which are not playable // we can ignore events which are not playable
const rundown = getCurrentRundown(); const rundown = getCurrentRundown();
const rundownMetadata = getRundownMetadata(); const metadata = getRundownMetadata();
const success = runtimeState.load(event, rundown, rundownMetadata.playableEventOrder, initialData); const success = runtimeState.load(event, rundown, metadata, initialData);
if (success) { if (success) {
logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`); logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`);
@@ -603,10 +599,10 @@ class RuntimeService {
*/ */
private rollLoaded(offset?: number) { private rollLoaded(offset?: number) {
const rundown = getCurrentRundown(); const rundown = getCurrentRundown();
const { timedEventOrder } = getRundownMetadata(); const metadata = getRundownMetadata();
try { try {
runtimeState.roll(rundown, timedEventOrder, offset); runtimeState.roll(rundown, metadata, offset);
} catch (error) { } catch (error) {
logger.error(LogOrigin.Server, `Roll: ${error}`); logger.error(LogOrigin.Server, `Roll: ${error}`);
} }
@@ -627,8 +623,8 @@ class RuntimeService {
try { try {
const rundown = getCurrentRundown(); const rundown = getCurrentRundown();
const rundownMetadata = getRundownMetadata(); const metadata = getRundownMetadata();
const result = runtimeState.roll(rundown, rundownMetadata.playableEventOrder); const result = runtimeState.roll(rundown, metadata);
const newState = runtimeState.getState(); const newState = runtimeState.getState();
if (result.eventId !== previousState.eventNow?.id) { if (result.eventId !== previousState.eventNow?.id) {
@@ -681,8 +677,8 @@ class RuntimeService {
} }
const rundown = getCurrentRundown(); const rundown = getCurrentRundown();
const rundownMetadata = getRundownMetadata(); const metadata = getRundownMetadata();
runtimeState.resume(restorePoint, event, rundown, rundownMetadata.playableEventOrder); runtimeState.resume(restorePoint, event, rundown, metadata);
logger.info(LogOrigin.Playback, 'Resuming playback'); logger.info(LogOrigin.Playback, 'Resuming playback');
} }
@@ -120,10 +120,3 @@ export function getEventAtIndex(
return rundown.entries[eventId] as OntimeEvent | undefined; return rundown.entries[eventId] as OntimeEvent | undefined;
} }
/**
* TODO(v4): we dont need this function
*/
export function filterTimedEvents(rundown: Rundown, timedEventOrder: EntryId[]): OntimeEvent[] {
return timedEventOrder.map((id) => rundown.entries[id] as OntimeEvent);
}
@@ -15,6 +15,7 @@ import {
start, start,
stop, stop,
} from '../runtimeState.js'; } from '../runtimeState.js';
import { rundownCache } from '../../api-data/rundown/rundown.dao.js';
const mockEvent = { const mockEvent = {
type: 'event', type: 'event',
@@ -93,7 +94,8 @@ describe('mutation on runtimeState', () => {
vi.runAllTimers(); vi.runAllTimers();
vi.useRealTimers(); vi.useRealTimers();
load(mockEvent, mockRundown, mockRundown.order); const { metadata, rundown } = rundownCache.get();
load(mockEvent, rundown, metadata);
let newState = getState(); let newState = getState();
expect(newState.eventNow?.id).toBe(mockEvent.id); expect(newState.eventNow?.id).toBe(mockEvent.id);
expect(newState.timer.playback).toBe(Playback.Armed); expect(newState.timer.playback).toBe(Playback.Armed);
@@ -162,16 +164,18 @@ describe('mutation on runtimeState', () => {
event1: { ...mockEvent, id: 'event1', timeStart: 0, timeEnd: 1000, duration: 1000, parent: null }, event1: { ...mockEvent, id: 'event1', timeStart: 0, timeEnd: 1000, duration: 1000, parent: null },
event2: { ...mockEvent, id: 'event2', timeStart: 1000, timeEnd: 1500, duration: 500, parent: null }, event2: { ...mockEvent, id: 'event2', timeStart: 1000, timeEnd: 1500, duration: 500, parent: null },
}; };
const rundown = makeRundown({ entries, order: ['event1', 'event2'] }); const mockRundown = makeRundown({ entries, order: ['event1', 'event2'] });
// force update // force update
vi.useFakeTimers(); vi.useFakeTimers();
await initRundown(rundown, {}); await initRundown(mockRundown, {});
vi.runAllTimers(); vi.runAllTimers();
vi.useRealTimers(); vi.useRealTimers();
const { metadata, rundown } = rundownCache.get();
// 1. Load event // 1. Load event
load(entries.event1, rundown, rundown.order); load(entries.event1, rundown, metadata);
let newState = getState(); let newState = getState();
expect(newState.runtime.actualStart).toBeNull(); expect(newState.runtime.actualStart).toBeNull();
expect(newState.runtime.plannedStart).toBe(0); expect(newState.runtime.plannedStart).toBe(0);
@@ -192,7 +196,7 @@ describe('mutation on runtimeState', () => {
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd - newState.runtime.offset); expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd - newState.runtime.offset);
// 3. Next event // 3. Next event
load(entries.event2, rundown, rundown.order); load(entries.event2, rundown, metadata);
start(); start();
newState = getState(); newState = getState();
@@ -237,22 +241,29 @@ describe('roll mode', () => {
vi.setSystemTime('jan 1 00:00'); vi.setSystemTime('jan 1 00:00');
clearState(); clearState();
}); });
afterEach(() => { afterEach(() => {
vi.useRealTimers(); vi.useRealTimers();
}); });
describe('normal roll', () => { describe('normal roll', async () => {
const rundown = makeRundown({ beforeEach(async () => {
entries: { vi.useFakeTimers();
1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 }, const mockRundown = makeRundown({
2: { ...mockEvent, id: '2', timeStart: 2000, duration: 1000, timeEnd: 3000 }, entries: {
3: { ...mockEvent, id: '3', timeStart: 3000, duration: 1000, timeEnd: 4000 }, 1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
}, 2: { ...mockEvent, id: '2', timeStart: 2000, duration: 1000, timeEnd: 3000 },
order: ['1', '2', '3'], 3: { ...mockEvent, id: '3', timeStart: 3000, duration: 1000, timeEnd: 4000 },
},
order: ['1', '2', '3'],
});
await initRundown(mockRundown, {});
vi.runAllTimers();
}); });
test('pending event', () => { test('pending event', () => {
const { eventId, didStart } = roll(rundown, rundown.order); const { rundown, metadata } = rundownCache.get();
const { eventId, didStart } = roll(rundown, metadata);
const state = getState(); const state = getState();
expect(eventId).toBe('1'); expect(eventId).toBe('1');
@@ -263,55 +274,22 @@ describe('roll mode', () => {
test('roll events', () => { test('roll events', () => {
vi.setSystemTime('jan 1 00:00:01'); vi.setSystemTime('jan 1 00:00:01');
let result = roll(rundown, rundown.order); const { rundown, metadata } = rundownCache.get();
let result = roll(rundown, metadata);
expect(result).toStrictEqual({ eventId: '1', didStart: true }); expect(result).toStrictEqual({ eventId: '1', didStart: true });
vi.setSystemTime('jan 1 00:00:02'); vi.setSystemTime('jan 1 00:00:02');
result = roll(rundown, rundown.order); result = roll(rundown, metadata);
expect(result).toStrictEqual({ eventId: '2', didStart: true }); expect(result).toStrictEqual({ eventId: '2', didStart: true });
vi.setSystemTime('jan 1 00:00:03:500'); vi.setSystemTime('jan 1 00:00:03:500');
result = roll(rundown, rundown.order); result = roll(rundown, metadata);
expect(result).toStrictEqual({ eventId: '3', didStart: true }); expect(result).toStrictEqual({ eventId: '3', didStart: true });
}); });
}); });
describe('roll takeover', async () => { describe('roll takeover', () => {
const rundown = makeRundown({ beforeEach(async () => {
entries: {
1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
2: { ...mockEvent, id: '2', timeStart: 2000, duration: 1000, timeEnd: 3000 },
3: { ...mockEvent, id: '3', timeStart: 3000, duration: 1000, timeEnd: 4000 },
},
order: ['1', '2', '3'],
});
// force update
vi.useFakeTimers();
await initRundown(rundown, {});
vi.runAllTimers();
vi.useRealTimers();
test('from load', () => {
load(rundown.entries[3] as PlayableEvent, rundown, rundown.order);
const result = roll(rundown, rundown.order);
expect(result).toStrictEqual({ eventId: '3', didStart: false });
const state = getState();
expect(state.timer.phase).toBe(TimerPhase.Pending);
expect(state.timer.secondaryTimer).toBe(3000);
});
test('from play', () => {
load(rundown.entries[1] as PlayableEvent, rundown, rundown.order);
start();
const result = roll(rundown, rundown.order);
expect(result).toStrictEqual({ eventId: '1', didStart: false });
expect(getState().runtime.offset).toBe(1000);
});
});
describe('roll continue with offset', () => {
test('no gaps', async () => {
const rundown = makeRundown({ const rundown = makeRundown({
entries: { entries: {
1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 }, 1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
@@ -325,23 +303,61 @@ describe('roll mode', () => {
vi.useFakeTimers(); vi.useFakeTimers();
await initRundown(rundown, {}); await initRundown(rundown, {});
vi.runAllTimers(); vi.runAllTimers();
});
load(rundown.entries[1] as PlayableEvent, rundown, rundown.order); test('from load', () => {
const { rundown, metadata } = rundownCache.get();
load(rundown.entries[3] as PlayableEvent, rundown, metadata);
const result = roll(rundown, metadata);
expect(result).toStrictEqual({ eventId: '3', didStart: false });
const state = getState();
expect(state.timer.phase).toBe(TimerPhase.Pending);
expect(state.timer.secondaryTimer).toBe(3000);
});
test('from play', () => {
const { rundown, metadata } = rundownCache.get();
load(rundown.entries[1] as PlayableEvent, rundown, metadata);
start();
const result = roll(rundown, metadata);
expect(result).toStrictEqual({ eventId: '1', didStart: false });
expect(getState().runtime.offset).toBe(1000);
});
});
describe('roll continue with offset', () => {
test('no gaps', async () => {
const mockRundown = makeRundown({
entries: {
1: { ...mockEvent, id: '1', timeStart: 1000, duration: 1000, timeEnd: 2000 },
2: { ...mockEvent, id: '2', timeStart: 2000, duration: 1000, timeEnd: 3000 },
3: { ...mockEvent, id: '3', timeStart: 3000, duration: 1000, timeEnd: 4000 },
},
order: ['1', '2', '3'],
});
// force update
vi.useFakeTimers();
await initRundown(mockRundown, {});
vi.runAllTimers();
const { rundown, metadata } = rundownCache.get();
load(rundown.entries[1] as PlayableEvent, rundown, metadata);
start(); start();
// the current offset after manual play // the current offset after manual play
const currentOffset = getState().runtime.offset; const currentOffset = getState().runtime.offset;
let result = roll(rundown, rundown.order, getState().runtime.offset); let result = roll(rundown, metadata, getState().runtime.offset);
expect(result).toStrictEqual({ eventId: '1', didStart: false }); expect(result).toStrictEqual({ eventId: '1', didStart: false });
// the current offset should be maintain by roll mode whn taking over from play // the current offset should be maintain by roll mode whn taking over from play
expect(getState().runtime.offset).toBe(currentOffset); expect(getState().runtime.offset).toBe(currentOffset);
vi.setSystemTime('jan 1 00:00:01'); vi.setSystemTime('jan 1 00:00:01');
result = roll(rundown, rundown.order, getState().runtime.offset); result = roll(rundown, metadata, getState().runtime.offset);
expect(result).toStrictEqual({ eventId: '2', didStart: true }); expect(result).toStrictEqual({ eventId: '2', didStart: true });
expect(getState().runtime.offset).toBe(1000); expect(getState().runtime.offset).toBe(1000);
vi.setSystemTime('jan 1 00:00:02'); vi.setSystemTime('jan 1 00:00:02');
result = roll(rundown, rundown.order, getState().runtime.offset); result = roll(rundown, metadata, getState().runtime.offset);
expect(result).toStrictEqual({ eventId: '3', didStart: true }); expect(result).toStrictEqual({ eventId: '3', didStart: true });
expect(getState().runtime.offset).toBe(1000); expect(getState().runtime.offset).toBe(1000);
+36 -40
View File
@@ -1,12 +1,9 @@
import { import {
CurrentBlockState, CurrentBlockState,
EntryId,
isPlayableEvent,
MaybeNumber, MaybeNumber,
MaybeString, MaybeString,
OffsetMode, OffsetMode,
OntimeBlock, OntimeBlock,
OntimeEvent,
PlayableEvent, PlayableEvent,
Playback, Playback,
Rundown, Rundown,
@@ -28,7 +25,8 @@ import {
} from '../services/timerUtils.js'; } from '../services/timerUtils.js';
import { loadRoll, normaliseRollStart } from '../services/rollUtils.js'; import { loadRoll, normaliseRollStart } from '../services/rollUtils.js';
import { timerConfig } from '../setup/config.js'; import { timerConfig } from '../setup/config.js';
import { filterTimedEvents } from '../services/runtime-service/rundownService.utils.js'; import { RundownMetadata } from '../api-data/rundown/rundown.types.js';
import { getPlayableIndexFromTimedIndex } from '../api-data/rundown/rundown.utils.js';
export type RuntimeState = { export type RuntimeState = {
clock: number; // realtime clock clock: number; // realtime clock
@@ -171,12 +169,13 @@ export function updateRundownData(rundownData: RundownData) {
export function load( export function load(
event: PlayableEvent, event: PlayableEvent,
rundown: Rundown, rundown: Rundown,
timedEventOrder: EntryId[], metadata: RundownMetadata,
initialData?: Partial<TimerState & RestorePoint>, initialData?: Partial<TimerState & RestorePoint>,
): boolean { ): boolean {
clearEventData(); clearEventData();
if (timedEventOrder.length === 0 || !isPlayableEvent(event)) { const { timedEventOrder } = metadata;
if (timedEventOrder.length === 0) {
return false; return false;
} }
@@ -186,18 +185,16 @@ export function load(
return false; return false;
} }
// TODO(remove public): it is wasteful to recreate the object
const timedEvents = filterTimedEvents(rundown, timedEventOrder);
// load events in memory along with their data // load events in memory along with their data
loadNow(timedEvents, eventIndex); loadNow(rundown, metadata, eventIndex);
loadNext(timedEvents, eventIndex); loadNext(rundown, metadata, eventIndex);
loadBlock(rundown); loadBlock(rundown);
// update state // update state
runtimeState.timer.playback = Playback.Armed; runtimeState.timer.playback = Playback.Armed;
runtimeState.timer.duration = calculateDuration(event.timeStart, event.timeEnd); runtimeState.timer.duration = calculateDuration(event.timeStart, event.timeEnd);
runtimeState.timer.current = getCurrent(runtimeState); runtimeState.timer.current = getCurrent(runtimeState);
runtimeState.runtime.numEvents = timedEvents.length; runtimeState.runtime.numEvents = metadata.timedEventOrder.length;
// patch with potential provided data // patch with potential provided data
if (initialData) { if (initialData) {
@@ -220,7 +217,11 @@ export function load(
/** /**
* Loads current event and its public counterpart * Loads current event and its public counterpart
*/ */
export function loadNow(timedEvents: OntimeEvent[], eventIndex: MaybeNumber = runtimeState.runtime.selectedEventIndex) { export function loadNow(
rundown: Rundown,
metadata: RundownMetadata,
eventIndex: MaybeNumber = runtimeState.runtime.selectedEventIndex,
) {
if (eventIndex === null) { if (eventIndex === null) {
// reset the state to indicate there is no selection // reset the state to indicate there is no selection
runtimeState.runtime.selectedEventIndex = null; runtimeState.runtime.selectedEventIndex = null;
@@ -228,7 +229,7 @@ export function loadNow(timedEvents: OntimeEvent[], eventIndex: MaybeNumber = ru
return; return;
} }
const event = timedEvents[eventIndex] as PlayableEvent; const event = rundown.entries[metadata.timedEventOrder[eventIndex]] as PlayableEvent;
runtimeState.runtime.selectedEventIndex = eventIndex; runtimeState.runtime.selectedEventIndex = eventIndex;
runtimeState.eventNow = event; runtimeState.eventNow = event;
} }
@@ -237,7 +238,8 @@ export function loadNow(timedEvents: OntimeEvent[], eventIndex: MaybeNumber = ru
* Loads the next event and its public counterpart * Loads the next event and its public counterpart
*/ */
export function loadNext( export function loadNext(
timedEvents: OntimeEvent[], rundown: Rundown,
metadata: RundownMetadata,
eventIndex: MaybeNumber = runtimeState.runtime.selectedEventIndex, eventIndex: MaybeNumber = runtimeState.runtime.selectedEventIndex,
) { ) {
if (eventIndex === null) { if (eventIndex === null) {
@@ -245,27 +247,22 @@ export function loadNext(
runtimeState.eventNext = null; runtimeState.eventNext = null;
return; return;
} }
const nowPlayableIndex = getPlayableIndexFromTimedIndex(metadata, eventIndex);
// temporarily reset this value to simplify loop logic if (!nowPlayableIndex || nowPlayableIndex > metadata.playableEventOrder.length - 2) {
runtimeState.eventNext = null; // we cound not find the event now or the event now is the last playable event
runtimeState.eventNext = null;
//TODO: do we already have a it as a list of not skipped events
for (let i = eventIndex + 1; i < timedEvents.length; i++) {
const event = timedEvents[i];
// we dont deal with events that are not playable
if (!isPlayableEvent(event)) {
continue;
}
runtimeState.eventNext = event;
return; return;
} }
const nextId = metadata.playableEventOrder[nowPlayableIndex + 1];
runtimeState.eventNext = rundown.entries[nextId] as PlayableEvent;
} }
/** /**
* Resume from restore point * Resume from restore point
*/ */
export function resume(restorePoint: RestorePoint, event: PlayableEvent, rundown: Rundown, timedEventOrder: EntryId[]) { export function resume(restorePoint: RestorePoint, event: PlayableEvent, rundown: Rundown, metadata: RundownMetadata) {
load(event, rundown, timedEventOrder, restorePoint); load(event, rundown, metadata, restorePoint);
} }
/** /**
@@ -325,12 +322,12 @@ export function updateLoaded(event?: PlayableEvent): string | undefined {
/** /**
* Used in situations when we want to hot-reload all events without interrupting timer * Used in situations when we want to hot-reload all events without interrupting timer
*/ */
export function updateAll(rundown: Rundown, timedEventsOrder: EntryId[]) { export function updateAll(rundown: Rundown, metadata: RundownMetadata) {
const timedEvents = filterTimedEvents(rundown, timedEventsOrder); // event now might have moved so we find the event now id and recalculate the the index again
// TODO(remove public): we dont need to make the timedEvents object, we pass primitives and let the functions handle it const eventNowIndex = metadata.timedEventOrder.findIndex((id) => id === runtimeState.eventNow?.id);
const eventNowIndex = timedEventsOrder.findIndex((id) => id === runtimeState.eventNow?.id);
loadNow(timedEvents, eventNowIndex >= 0 ? eventNowIndex : undefined); loadNow(rundown, metadata, eventNowIndex >= 0 ? eventNowIndex : undefined);
loadNext(timedEvents, eventNowIndex >= 0 ? eventNowIndex : undefined); loadNext(rundown, metadata, eventNowIndex >= 0 ? eventNowIndex : undefined);
updateLoaded(runtimeState.eventNow ?? undefined); updateLoaded(runtimeState.eventNow ?? undefined);
loadBlock(rundown); loadBlock(rundown);
} }
@@ -542,7 +539,7 @@ export function update(): UpdateResult {
export function roll( export function roll(
rundown: Rundown, rundown: Rundown,
timedEventOrder: EntryId[], metadata: RundownMetadata,
offset = 0, offset = 0,
): { eventId: MaybeString; didStart: boolean } { ): { eventId: MaybeString; didStart: boolean } {
// 1. if an event is running, we simply take over the playback // 1. if an event is running, we simply take over the playback
@@ -601,8 +598,7 @@ export function roll(
} }
// 3. if there is no event running, we need to find the next event // 3. if there is no event running, we need to find the next event
const timedEvents = filterTimedEvents(rundown, timedEventOrder); if (metadata.playableEventOrder.length === 0) {
if (timedEvents.length === 0) {
throw new Error('No playable events found'); throw new Error('No playable events found');
} }
@@ -613,16 +609,16 @@ export function roll(
runtimeState.runtime.offset = offset; runtimeState.runtime.offset = offset;
const offsetClock = runtimeState.clock + runtimeState.runtime.offset; const offsetClock = runtimeState.clock + runtimeState.runtime.offset;
const { index, isPending } = loadRoll(timedEvents, offsetClock); const { index, isPending } = loadRoll(rundown, metadata, offsetClock);
// load events in memory along with their data // load events in memory along with their data
loadNow(timedEvents, index); loadNow(rundown, metadata, index);
loadNext(timedEvents, index); loadNext(rundown, metadata, index);
loadBlock(rundown); loadBlock(rundown);
// update roll state // update roll state
runtimeState.timer.playback = Playback.Roll; runtimeState.timer.playback = Playback.Roll;
runtimeState.runtime.numEvents = timedEvents.length; runtimeState.runtime.numEvents = metadata.timedEventOrder.length;
// in roll mode spec, there should always be something to load // in roll mode spec, there should always be something to load
// as long as playableEvents is not empty // as long as playableEvents is not empty