From c890251dadebfbef386ebc75e89072e9dbbd40f5 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:30:52 +0100 Subject: [PATCH] refactor: delays account for gaps (#784) --- .../__tests__/rundownCache.test.ts | 46 +++++++++++++++++-- .../services/rundown-service/rundownCache.ts | 34 +++++++++----- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts b/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts index 9cfd4808a..067ce8562 100644 --- a/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts +++ b/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts @@ -31,15 +31,53 @@ describe('init() function', () => { it('calculates delays versions of a given rundown', () => { const testRundown: OntimeRundown = [ { type: SupportedEvent.Delay, id: '1', duration: 100 } as OntimeDelay, - { type: SupportedEvent.Event, id: '2', timeStart: 1 } as OntimeEvent, - { type: SupportedEvent.Block, id: '3' } as OntimeBlock, - { type: SupportedEvent.Event, id: '4', timeStart: 2 } as OntimeEvent, + { type: SupportedEvent.Event, id: '2', timeStart: 1, timeEnd: 100 } as OntimeEvent, ]; const initResult = generate(testRundown); - expect(initResult.order.length).toBe(4); + expect(initResult.order.length).toBe(2); expect((initResult.rundown['2'] as OntimeEvent).delay).toBe(100); + expect(initResult.totalDelay).toBe(100); + }); + + it('accounts for gaps in rundown when calculating delays', () => { + const testRundown: OntimeRundown = [ + { type: SupportedEvent.Event, id: '1', timeStart: 100, timeEnd: 200 } as OntimeEvent, + { type: SupportedEvent.Delay, id: 'delay', duration: 200 } as OntimeDelay, + { type: SupportedEvent.Event, id: '2', timeStart: 200, timeEnd: 300 } as OntimeEvent, + { type: SupportedEvent.Block, id: 'block', title: 'break' } as OntimeBlock, + { type: SupportedEvent.Event, id: '3', timeStart: 400, timeEnd: 500 } as OntimeEvent, + { type: SupportedEvent.Block, id: 'another-block', title: 'another-break' } as OntimeBlock, + { type: SupportedEvent.Event, id: '4', timeStart: 600, timeEnd: 700 } as OntimeEvent, + ]; + + const initResult = generate(testRundown); + expect(initResult.order.length).toBe(7); + expect((initResult.rundown['1'] as OntimeEvent).delay).toBe(0); + expect((initResult.rundown['2'] as OntimeEvent).delay).toBe(200); + expect((initResult.rundown['3'] as OntimeEvent).delay).toBe(100); expect((initResult.rundown['4'] as OntimeEvent).delay).toBe(0); + expect(initResult.totalDelay).toBe(0); + }); + + it('handles negative delays', () => { + const testRundown: OntimeRundown = [ + { type: SupportedEvent.Event, id: '1', timeStart: 100, timeEnd: 200 } as OntimeEvent, + { type: SupportedEvent.Delay, id: 'delay', duration: -200 } as OntimeDelay, + { type: SupportedEvent.Event, id: '2', timeStart: 200, timeEnd: 300 } as OntimeEvent, + { type: SupportedEvent.Block, id: 'block', title: 'break' } as OntimeBlock, + { type: SupportedEvent.Event, id: '3', timeStart: 400, timeEnd: 500 } as OntimeEvent, + { type: SupportedEvent.Block, id: 'another-block', title: 'another-break' } as OntimeBlock, + { type: SupportedEvent.Event, id: '4', timeStart: 600, timeEnd: 700 } as OntimeEvent, + ]; + + const initResult = generate(testRundown); + expect(initResult.order.length).toBe(7); + expect((initResult.rundown['1'] as OntimeEvent).delay).toBe(0); + expect((initResult.rundown['2'] as OntimeEvent).delay).toBe(-200); + expect((initResult.rundown['3'] as OntimeEvent).delay).toBe(-200); + expect((initResult.rundown['4'] as OntimeEvent).delay).toBe(-200); + expect(initResult.totalDelay).toBe(-200); }); it('links times across events', () => { diff --git a/apps/server/src/services/rundown-service/rundownCache.ts b/apps/server/src/services/rundown-service/rundownCache.ts index 535712659..6b679080a 100644 --- a/apps/server/src/services/rundown-service/rundownCache.ts +++ b/apps/server/src/services/rundown-service/rundownCache.ts @@ -1,11 +1,4 @@ -import { - isOntimeBlock, - isOntimeDelay, - isOntimeEvent, - OntimeEvent, - OntimeRundown, - OntimeRundownEntry, -} from 'ontime-types'; +import { isOntimeDelay, isOntimeEvent, OntimeEvent, OntimeRundown, OntimeRundownEntry } from 'ontime-types'; import { generateId, deleteAtIndex, @@ -31,6 +24,7 @@ let rundown: NormalisedRundown = {}; let order: EventID[] = []; let revision = 0; let isStale = true; +let totalDelay = 0; let links: Record = {}; @@ -64,6 +58,8 @@ export function generate(initialRundown: OntimeRundown = persistedRundown) { links = {}; let accumulatedDelay = 0; + let previousEnd: number; + for (let i = 0; i < initialRundown.length; i++) { const currentEvent = initialRundown[i]; let updatedEvent = { ...currentEvent }; @@ -89,10 +85,16 @@ export function generate(initialRundown: OntimeRundown = persistedRundown) { // calculate delays if (isOntimeDelay(updatedEvent)) { accumulatedDelay += updatedEvent.duration; - } else if (isOntimeBlock(updatedEvent)) { - accumulatedDelay = 0; } else if (isOntimeEvent(updatedEvent)) { + const eventStart = updatedEvent.timeStart; + + // we only affect positive delays (time forwards) + if (accumulatedDelay > 0 && previousEnd) { + const gap = Math.max(eventStart - previousEnd, 0); + accumulatedDelay = Math.max(accumulatedDelay - gap, 0); + } updatedEvent.delay = accumulatedDelay; + previousEnd = updatedEvent.timeEnd; } order.push(updatedEvent.id); @@ -100,7 +102,8 @@ export function generate(initialRundown: OntimeRundown = persistedRundown) { } isStale = false; - return { rundown, order, links }; + totalDelay = accumulatedDelay; + return { rundown, order, links, totalDelay }; } /** Returns an ID guaranteed to be unique */ @@ -153,6 +156,7 @@ type MutatingReturn = { newEvent?: OntimeRundownEntry; }; type MutatingFn = (params: MutationParams) => MutatingReturn; + /** * Decorators injects data into mutation * @param mutation @@ -182,10 +186,12 @@ export function mutateCache(mutation: MutatingFn) { // TODO: could we return a patch object? return { newEvent }; } + return scopedMutation; } type AddArgs = MutationParams<{ atIndex: number; event: OntimeRundownEntry }>; + export function add({ persistedRundown, atIndex, event }: AddArgs): Required { const newEvent: OntimeRundownEntry = { ...event }; const newRundown = insertAtIndex(atIndex, newEvent, persistedRundown); @@ -194,6 +200,7 @@ export function add({ persistedRundown, atIndex, event }: AddArgs): Required; + export function remove({ persistedRundown, eventId }: RemoveArgs): MutatingReturn { const atIndex = persistedRundown.findIndex((event) => event.id === eventId); const newRundown = deleteAtIndex(atIndex, persistedRundown); @@ -222,6 +229,7 @@ function makeEvent(eventFromRundown: OntimeRundownEntry, patch: Partial }>; + export function edit({ persistedRundown, eventId, patch }: EditArgs): Required { const indexAt = persistedRundown.findIndex((event) => event.id === eventId); @@ -246,6 +254,7 @@ export function edit({ persistedRundown, eventId, patch }: EditArgs): Required }>; + export function batchEdit({ persistedRundown, eventIds, patch }: BatchEditArgs): MutatingReturn { const ids = new Set(eventIds); @@ -265,6 +274,7 @@ export function batchEdit({ persistedRundown, eventIds, patch }: BatchEditArgs): } type ReorderArgs = MutationParams<{ eventId: string; from: number; to: number }>; + export function reorder({ persistedRundown, eventId, from, to }: ReorderArgs): Required { const event = persistedRundown[from]; if (!event || eventId !== event.id) { @@ -282,12 +292,14 @@ export function reorder({ persistedRundown, eventId, from, to }: ReorderArgs): R } type ApplyDelayArgs = MutationParams<{ eventId: string }>; + export function applyDelay({ persistedRundown, eventId }: ApplyDelayArgs): MutatingReturn { const newRundown = apply(eventId, persistedRundown); return { newRundown }; } type SwapArgs = MutationParams<{ fromId: string; toId: string }>; + export function swap({ persistedRundown, fromId, toId }: SwapArgs): MutatingReturn { const indexA = persistedRundown.findIndex((event) => event.id === fromId); const eventA = persistedRundown.at(indexA);