mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 06:29:07 +00:00
fix: backdate rundown start when rolling
This commit is contained in:
committed by
Carlos Valente
parent
5ff7861968
commit
6f1f10ccff
@@ -16,6 +16,7 @@ import {
|
|||||||
Trigger,
|
Trigger,
|
||||||
URLPreset,
|
URLPreset,
|
||||||
ViewSettings,
|
ViewSettings,
|
||||||
|
Day,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
import {
|
import {
|
||||||
customFieldLabelToKey,
|
customFieldLabelToKey,
|
||||||
@@ -393,7 +394,7 @@ export function migrateRundown(
|
|||||||
// !==== RUNTIME METADATA ====! //
|
// !==== RUNTIME METADATA ====! //
|
||||||
revision: -1,
|
revision: -1,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
dayOffset: 0,
|
dayOffset: 0 as Day,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
});
|
});
|
||||||
} else if (entry.type === 'block') {
|
} else if (entry.type === 'block') {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
isOntimeMilestone,
|
isOntimeMilestone,
|
||||||
OntimeMilestone,
|
OntimeMilestone,
|
||||||
OntimeGroup,
|
OntimeGroup,
|
||||||
|
Day,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
import {
|
import {
|
||||||
isObjectEmpty,
|
isObjectEmpty,
|
||||||
@@ -281,7 +282,7 @@ function processEntry<T extends OntimeEntry>(
|
|||||||
sanitiseCustomFields(customFields, currentEntry);
|
sanitiseCustomFields(customFields, currentEntry);
|
||||||
|
|
||||||
processedData.totalDays += calculateDayOffset(currentEntry, processedData.previousEvent);
|
processedData.totalDays += calculateDayOffset(currentEntry, processedData.previousEvent);
|
||||||
currentEntry.dayOffset = processedData.totalDays;
|
currentEntry.dayOffset = processedData.totalDays as Day;
|
||||||
currentEntry.delay = 0; // this means we dont calculate delays or gaps for skipped events
|
currentEntry.delay = 0; // this means we dont calculate delays or gaps for skipped events
|
||||||
currentEntry.gap = 0; // this means we dont calculate delays or gaps for skipped events
|
currentEntry.gap = 0; // this means we dont calculate delays or gaps for skipped events
|
||||||
currentEntry.parent = childOfGroup;
|
currentEntry.parent = childOfGroup;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { dayInMs, millisToString } from 'ontime-utils';
|
import { dayInMs, MILLIS_PER_HOUR, MILLIS_PER_MINUTE, millisToString } from 'ontime-utils';
|
||||||
import { Duration, Instant } from 'ontime-types';
|
import { Duration, Instant, TimeOfDay } from 'ontime-types';
|
||||||
|
|
||||||
import { timeNow } from '../../../utils/time.js';
|
import { timeNow } from '../../../utils/time.js';
|
||||||
import * as timeCore from '../timeCore.js';
|
import * as timeCore from '../timeCore.js';
|
||||||
@@ -183,3 +183,81 @@ describe('addDuration() moves a point in time by a duration', () => {
|
|||||||
expect(timeCore.addDuration(instant, [])).toBe(1000);
|
expect(timeCore.addDuration(instant, [])).toBe(1000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('elapsedTime() calculates duration between two times of day', () => {
|
||||||
|
it('calculates elapsed time on the same day', () => {
|
||||||
|
const start = (10 * MILLIS_PER_HOUR) as TimeOfDay; // 10:00
|
||||||
|
const clock = (10 * MILLIS_PER_HOUR + 30 * MILLIS_PER_MINUTE) as TimeOfDay; // 10:30
|
||||||
|
expect(timeCore.elapsedTime(clock, start)).toBe(30 * MILLIS_PER_MINUTE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calculates elapsed time when crossing midnight (overnight)', () => {
|
||||||
|
const start = (23 * MILLIS_PER_HOUR + 50 * MILLIS_PER_MINUTE) as TimeOfDay; // 23:50
|
||||||
|
const clock = (21 * MILLIS_PER_MINUTE) as TimeOfDay; // 00:21
|
||||||
|
// From 23:50 to 00:21 = 31 minutes
|
||||||
|
expect(timeCore.elapsedTime(clock, start)).toBe(31 * MILLIS_PER_MINUTE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 0 when start and clock are the same', () => {
|
||||||
|
const time = (15 * MILLIS_PER_HOUR) as TimeOfDay; // 15:00
|
||||||
|
expect(timeCore.elapsedTime(time, time)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calculates correctly for just after midnight', () => {
|
||||||
|
const start = (23 * MILLIS_PER_HOUR + 59 * MILLIS_PER_MINUTE) as TimeOfDay; // 23:59
|
||||||
|
const clock = (1 * MILLIS_PER_MINUTE) as TimeOfDay; // 00:01
|
||||||
|
// From 23:59 to 00:01 = 2 minutes
|
||||||
|
expect(timeCore.elapsedTime(clock, start)).toBe(2 * MILLIS_PER_MINUTE);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('daysSinceStart() calculates full days elapsed since a start epoch', () => {
|
||||||
|
it('returns 0 when current epoch equals start epoch', () => {
|
||||||
|
vi.setSystemTime('2025-01-15T10:00:00Z');
|
||||||
|
const epoch = timeCore.now();
|
||||||
|
expect(timeCore.daysSinceStart(epoch, epoch)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 0 when less than one day has elapsed', () => {
|
||||||
|
vi.setSystemTime('2025-01-15T10:00:00Z');
|
||||||
|
const startEpoch = timeCore.now();
|
||||||
|
|
||||||
|
vi.setSystemTime('2025-01-15T18:00:00Z'); // 8 hours later
|
||||||
|
const currentEpoch = timeCore.now();
|
||||||
|
|
||||||
|
expect(timeCore.daysSinceStart(startEpoch, currentEpoch)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 1 when crossing midnight once', () => {
|
||||||
|
// Copenhagen is UTC+1 in winter, so 22:50 UTC = 23:50 local
|
||||||
|
vi.setSystemTime('2025-01-15T22:50:00Z'); // 23:50 local
|
||||||
|
const startEpoch = timeCore.now();
|
||||||
|
|
||||||
|
vi.setSystemTime('2025-01-15T23:21:00Z'); // 00:21 local next day
|
||||||
|
const currentEpoch = timeCore.now();
|
||||||
|
|
||||||
|
expect(timeCore.daysSinceStart(startEpoch, currentEpoch)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 2 when crossing midnight twice', () => {
|
||||||
|
vi.setSystemTime('2025-01-15T10:00:00Z');
|
||||||
|
const startEpoch = timeCore.now();
|
||||||
|
|
||||||
|
vi.setSystemTime('2025-01-17T15:00:00Z'); // 2 days + 5 hours later
|
||||||
|
const currentEpoch = timeCore.now();
|
||||||
|
|
||||||
|
expect(timeCore.daysSinceStart(startEpoch, currentEpoch)).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles overnight start correctly', () => {
|
||||||
|
// Copenhagen is UTC+1 in winter
|
||||||
|
// Start at 23:50 local (22:50 UTC), check at 00:10 local next day (23:10 UTC)
|
||||||
|
vi.setSystemTime('2025-01-15T22:50:00Z'); // 23:50 local
|
||||||
|
const startEpoch = timeCore.now();
|
||||||
|
|
||||||
|
vi.setSystemTime('2025-01-15T23:10:00Z'); // 00:10 local next day
|
||||||
|
const currentEpoch = timeCore.now();
|
||||||
|
|
||||||
|
expect(timeCore.daysSinceStart(startEpoch, currentEpoch)).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Instant, TimeOfDay, Duration } from 'ontime-types';
|
import { Day, Duration, Instant, TimeOfDay } from 'ontime-types';
|
||||||
import { dayInMs, MILLIS_PER_MINUTE } from 'ontime-utils';
|
import { dayInMs, MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||||
|
|
||||||
/** Returns the current instant */
|
/** Returns the current instant */
|
||||||
@@ -58,3 +58,21 @@ export function addDuration(instant: Instant, duration: Duration | Duration[]):
|
|||||||
|
|
||||||
return (instant + totalDuration) as Instant;
|
return (instant + totalDuration) as Instant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates elapsed time on the clock from a starting time to the current time
|
||||||
|
* Handles overnight crossing (when current < start, assumes we've crossed midnight)
|
||||||
|
*/
|
||||||
|
export function elapsedTime(current: TimeOfDay, start: TimeOfDay): Duration {
|
||||||
|
return (current < start ? current + dayInMs - start : current - start) as Duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the number of full days elapsed since a start epoch
|
||||||
|
* Uses the start time-of-day to determine day boundaries
|
||||||
|
*/
|
||||||
|
export function daysSinceStart(startEpoch: Instant, currentEpoch: Instant): Day {
|
||||||
|
const startClock = toTimeOfDay(startEpoch);
|
||||||
|
const elapsedMs = currentEpoch - startEpoch;
|
||||||
|
return Math.floor((elapsedMs + startClock) / dayInMs) as Day;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DatabaseModel, EndAction, OntimeView, SupportedEntry, TimeStrategy, TimerType } from 'ontime-types';
|
import { DatabaseModel, Day, EndAction, OntimeView, SupportedEntry, TimeStrategy, TimerType } from 'ontime-types';
|
||||||
|
|
||||||
export const demoDb: DatabaseModel = {
|
export const demoDb: DatabaseModel = {
|
||||||
rundowns: {
|
rundowns: {
|
||||||
@@ -66,7 +66,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'Music plays, holding slide on screens',
|
note: 'Music plays, holding slide on screens',
|
||||||
colour: '#77C785',
|
colour: '#77C785',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
dayOffset: 0,
|
dayOffset: 0 as Day,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
cue: '1',
|
cue: '1',
|
||||||
parent: '7eaf99',
|
parent: '7eaf99',
|
||||||
@@ -109,7 +109,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'Emma Thompson',
|
note: 'Emma Thompson',
|
||||||
colour: '#FFCC78',
|
colour: '#FFCC78',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
dayOffset: 0,
|
dayOffset: 0 as Day,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
cue: '1.1',
|
cue: '1.1',
|
||||||
parent: '7eaf99',
|
parent: '7eaf99',
|
||||||
@@ -141,7 +141,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'Liam Carter, Sophia Patel + PowerPoint',
|
note: 'Liam Carter, Sophia Patel + PowerPoint',
|
||||||
colour: '#77C785',
|
colour: '#77C785',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
dayOffset: 0,
|
dayOffset: 0 as Day,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
cue: '1.2',
|
cue: '1.2',
|
||||||
parent: '7eaf99',
|
parent: '7eaf99',
|
||||||
@@ -199,7 +199,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'Buffet in lobby',
|
note: 'Buffet in lobby',
|
||||||
colour: '#779BE7',
|
colour: '#779BE7',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
dayOffset: 0,
|
dayOffset: 0 as Day,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
cue: '2.1',
|
cue: '2.1',
|
||||||
parent: 'f60403',
|
parent: 'f60403',
|
||||||
@@ -257,7 +257,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'Ethan Brooks + PowerPoint + Video playback',
|
note: 'Ethan Brooks + PowerPoint + Video playback',
|
||||||
colour: '#77C785',
|
colour: '#77C785',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
dayOffset: 0,
|
dayOffset: 0 as Day,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
cue: '3.1',
|
cue: '3.1',
|
||||||
parent: '6b0edb',
|
parent: '6b0edb',
|
||||||
@@ -289,7 +289,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'Lucas Bennett',
|
note: 'Lucas Bennett',
|
||||||
colour: '#FFCC78',
|
colour: '#FFCC78',
|
||||||
delay: 0,
|
delay: 0,
|
||||||
dayOffset: 0,
|
dayOffset: 0 as Day,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
cue: '3.2',
|
cue: '3.2',
|
||||||
parent: '6b0edb',
|
parent: '6b0edb',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MaybeNumber, TimerPhase } from 'ontime-types';
|
import { Day, MaybeNumber, TimerPhase } from 'ontime-types';
|
||||||
import { checkIsNow, dayInMs, isPlaybackActive, MILLIS_PER_HOUR } from 'ontime-utils';
|
import { checkIsNow, dayInMs, isPlaybackActive, MILLIS_PER_HOUR } from 'ontime-utils';
|
||||||
|
|
||||||
import type { RuntimeState } from '../stores/runtimeState.js';
|
import type { RuntimeState } from '../stores/runtimeState.js';
|
||||||
@@ -188,9 +188,9 @@ export function getTimerPhase(state: RuntimeState): TimerPhase {
|
|||||||
* Finds the day offset relative to an event start
|
* Finds the day offset relative to an event start
|
||||||
* used byt the runtimeState on first start to get correct offsets
|
* used byt the runtimeState on first start to get correct offsets
|
||||||
*/
|
*/
|
||||||
export function findDayOffset(plannedStart: number, clock: number): number {
|
export function findDayOffset(plannedStart: number, clock: number): Day {
|
||||||
const distance = clock - plannedStart;
|
const distance = clock - plannedStart;
|
||||||
if (distance >= 12 * MILLIS_PER_HOUR) return -1;
|
if (distance >= 12 * MILLIS_PER_HOUR) return -1 as Day;
|
||||||
if (distance < -12 * MILLIS_PER_HOUR) return 1;
|
if (distance < -12 * MILLIS_PER_HOUR) return 1 as Day;
|
||||||
return 0;
|
return 0 as Day;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Instant, PlayableEvent, Playback, TimerPhase } from 'ontime-types';
|
import { Instant, PlayableEvent, Playback, SupportedEntry, TimerPhase } from 'ontime-types';
|
||||||
import { MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from 'ontime-utils';
|
import { MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||||
|
|
||||||
import { makeOntimeGroup, makeOntimeEvent, makeRundown } from '../../api-data/rundown/__mocks__/rundown.mocks.js';
|
import { makeOntimeGroup, makeOntimeEvent, makeRundown } from '../../api-data/rundown/__mocks__/rundown.mocks.js';
|
||||||
@@ -575,6 +575,95 @@ describe('roll mode', () => {
|
|||||||
expect(stateAfterMidnight.timer.secondaryTimer).toBe(50 * MILLIS_PER_MINUTE);
|
expect(stateAfterMidnight.timer.secondaryTimer).toBe(50 * MILLIS_PER_MINUTE);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('rolling into overnight event after midnight has correct offset and expected times', async () => {
|
||||||
|
// Simulates a rundown with:
|
||||||
|
// - A group starting at 13:00, containing an overnight event
|
||||||
|
// - Overnight event: 23:50 to 01:50 (2 hours)
|
||||||
|
// Roll into the event at 00:21 (31 minutes into the event)
|
||||||
|
const groupId = 'group-1';
|
||||||
|
const eventId = 'event-1';
|
||||||
|
const eventStart = 23 * MILLIS_PER_HOUR + 50 * MILLIS_PER_MINUTE; // 23:50
|
||||||
|
const eventEnd = 1 * MILLIS_PER_HOUR + 50 * MILLIS_PER_MINUTE; // 01:50
|
||||||
|
const eventDuration = 2 * MILLIS_PER_HOUR; // 2 hours
|
||||||
|
const groupStart = 13 * MILLIS_PER_HOUR; // 13:00
|
||||||
|
const groupDuration = 12 * MILLIS_PER_HOUR + 50 * MILLIS_PER_MINUTE; // 12h 50m (ends at 01:50)
|
||||||
|
|
||||||
|
const mockRundown = makeRundown({
|
||||||
|
entries: {
|
||||||
|
[groupId]: {
|
||||||
|
id: groupId,
|
||||||
|
type: SupportedEntry.Group,
|
||||||
|
title: 'Test Group',
|
||||||
|
timeStart: groupStart,
|
||||||
|
timeEnd: eventEnd,
|
||||||
|
duration: groupDuration,
|
||||||
|
entries: [eventId],
|
||||||
|
colour: '',
|
||||||
|
note: '',
|
||||||
|
custom: {},
|
||||||
|
revision: 0,
|
||||||
|
isFirstLinked: false,
|
||||||
|
targetDuration: null,
|
||||||
|
},
|
||||||
|
[eventId]: {
|
||||||
|
...mockEvent,
|
||||||
|
id: eventId,
|
||||||
|
timeStart: eventStart,
|
||||||
|
timeEnd: eventEnd,
|
||||||
|
duration: eventDuration,
|
||||||
|
dayOffset: 0,
|
||||||
|
parent: groupId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: [groupId],
|
||||||
|
});
|
||||||
|
|
||||||
|
await initRundown(mockRundown, {});
|
||||||
|
vi.runAllTimers();
|
||||||
|
|
||||||
|
const { rundown, metadata } = rundownCache.get();
|
||||||
|
|
||||||
|
// Roll into the event AFTER midnight at 00:21 (31 minutes into the 2-hour event)
|
||||||
|
const rollTime = 21 * MILLIS_PER_MINUTE; // 00:21
|
||||||
|
vi.setSystemTime('jan 2 00:21');
|
||||||
|
const result = roll(rundown, metadata);
|
||||||
|
|
||||||
|
expect(result.eventId).toBe(eventId);
|
||||||
|
expect(result.didStart).toBe(true);
|
||||||
|
|
||||||
|
// Call update to recalculate all state (like runtime service does)
|
||||||
|
update();
|
||||||
|
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
// currentDay should be 1 (we're on the next day after midnight)
|
||||||
|
expect(state.rundown.currentDay).toBe(1);
|
||||||
|
|
||||||
|
// offset.absolute should be 0 (event started on time, backdated to planned start)
|
||||||
|
expect(state.offset.absolute).toBe(0);
|
||||||
|
|
||||||
|
// Timer should show correct remaining time
|
||||||
|
// Event started at 23:50, duration is 2 hours, clock is 00:21
|
||||||
|
// Time elapsed = 31 minutes, time remaining = 2h - 31m = 1h 29m = 89 minutes
|
||||||
|
const expectedRemaining = eventDuration - (rollTime + (24 * MILLIS_PER_HOUR - eventStart));
|
||||||
|
expect(state.timer.current).toBe(expectedRemaining);
|
||||||
|
|
||||||
|
// expectedFinish should be 01:50 (event end time)
|
||||||
|
expect(state.timer.expectedFinish).toBe(eventEnd);
|
||||||
|
|
||||||
|
// expectedRundownEnd should be 01:50 (same as event end, only one event)
|
||||||
|
expect(state.offset.expectedRundownEnd).toBe(eventEnd);
|
||||||
|
|
||||||
|
// expectedGroupEnd should be 01:50 (group ends when the event ends)
|
||||||
|
expect(state.offset.expectedGroupEnd).toBe(eventEnd);
|
||||||
|
|
||||||
|
// Verify timer started at correct backdated time
|
||||||
|
expect(state.timer.startedAt).toBe(eventStart);
|
||||||
|
|
||||||
|
// Verify actualStart is backdated to planned start
|
||||||
|
expect(state.rundown.actualStart).toBe(eventStart);
|
||||||
|
});
|
||||||
|
|
||||||
test('rundown loops back after finishing and resets currentDay when first event starts again', async () => {
|
test('rundown loops back after finishing and resets currentDay when first event starts again', async () => {
|
||||||
// Rundown: 09:00-23:00, then overnight event 23:00-01:00
|
// Rundown: 09:00-23:00, then overnight event 23:00-01:00
|
||||||
const mockRundown = makeRundown({
|
const mockRundown = makeRundown({
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
|
Day,
|
||||||
|
Duration,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
Instant,
|
Instant,
|
||||||
MaybeNumber,
|
MaybeNumber,
|
||||||
@@ -69,7 +71,7 @@ export type RuntimeState = {
|
|||||||
_flag: ExpectedMetadata;
|
_flag: ExpectedMetadata;
|
||||||
_end: ExpectedMetadata;
|
_end: ExpectedMetadata;
|
||||||
_startEpoch: Maybe<Instant>;
|
_startEpoch: Maybe<Instant>;
|
||||||
_startDayOffset: MaybeNumber;
|
_startDayOffset: Maybe<Day>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const runtimeState: RuntimeState = {
|
const runtimeState: RuntimeState = {
|
||||||
@@ -435,7 +437,7 @@ export function start(state: RuntimeState = runtimeState): boolean {
|
|||||||
state.timer.elapsed = 0;
|
state.timer.elapsed = 0;
|
||||||
|
|
||||||
if (state.rundown.actualStart === null) {
|
if (state.rundown.actualStart === null) {
|
||||||
state._startDayOffset = findDayOffset(state.eventNow.timeStart, state.clock) + state.eventNow.dayOffset;
|
state._startDayOffset = (findDayOffset(state.eventNow.timeStart, state.clock) + state.eventNow.dayOffset) as Day;
|
||||||
state.rundown.currentDay = state._startDayOffset;
|
state.rundown.currentDay = state._startDayOffset;
|
||||||
state._startEpoch = epoch;
|
state._startEpoch = epoch;
|
||||||
state.rundown.actualStart = state.clock;
|
state.rundown.actualStart = state.clock;
|
||||||
@@ -688,12 +690,21 @@ export function roll(
|
|||||||
runtimeState.rundown.actualGroupStart = plannedStart;
|
runtimeState.rundown.actualGroupStart = plannedStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* we need to backdate the actual start and start metadata
|
||||||
|
* to prevent adding unintended offset
|
||||||
|
*/
|
||||||
if (runtimeState.rundown.actualStart === null) {
|
if (runtimeState.rundown.actualStart === null) {
|
||||||
runtimeState.rundown.actualStart = plannedStart;
|
runtimeState.rundown.actualStart = plannedStart;
|
||||||
runtimeState._startDayOffset =
|
// use plannedStart (not clock) because actualStart is backdated to plannedStart
|
||||||
findDayOffset(runtimeState.eventNow.timeStart, runtimeState.clock) + runtimeState.eventNow.dayOffset;
|
runtimeState._startDayOffset = (findDayOffset(runtimeState.eventNow.timeStart, plannedStart) +
|
||||||
runtimeState.rundown.currentDay = runtimeState._startDayOffset;
|
runtimeState.eventNow.dayOffset) as Day;
|
||||||
runtimeState._startEpoch = epoch;
|
// backdate _startEpoch to when the event conceptually started
|
||||||
|
const timeElapsed = timeCore.elapsedTime(runtimeState.clock, plannedStart as TimeOfDay);
|
||||||
|
runtimeState._startEpoch = timeCore.addDuration(epoch, -timeElapsed as Duration);
|
||||||
|
// calculate currentDay from the backdated epoch
|
||||||
|
runtimeState.rundown.currentDay =
|
||||||
|
runtimeState._startDayOffset + timeCore.daysSinceStart(runtimeState._startEpoch, epoch);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
runtimeState._timer.secondaryTarget = normaliseRollStart(
|
runtimeState._timer.secondaryTarget = normaliseRollStart(
|
||||||
@@ -789,10 +800,15 @@ export function roll(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update metadata
|
// update metadata
|
||||||
runtimeState._startDayOffset =
|
// use plannedStart (not clock) because actualStart is backdated to plannedStart
|
||||||
findDayOffset(runtimeState.eventNow.timeStart, runtimeState.clock) + runtimeState.eventNow.dayOffset;
|
runtimeState._startDayOffset = (findDayOffset(runtimeState.eventNow.timeStart, plannedStart) +
|
||||||
runtimeState.rundown.currentDay = runtimeState._startDayOffset;
|
runtimeState.eventNow.dayOffset) as Day;
|
||||||
runtimeState._startEpoch = epoch;
|
// backdate _startEpoch to when the event conceptually started
|
||||||
|
const timeElapsed = timeCore.elapsedTime(runtimeState.clock, plannedStart as TimeOfDay);
|
||||||
|
runtimeState._startEpoch = timeCore.addDuration(epoch, -timeElapsed as Duration);
|
||||||
|
// calculate currentDay from the backdated epoch
|
||||||
|
runtimeState.rundown.currentDay = (runtimeState._startDayOffset +
|
||||||
|
timeCore.daysSinceStart(runtimeState._startEpoch, epoch)) as Day;
|
||||||
|
|
||||||
return { eventId: runtimeState.eventNow.id, didStart: true };
|
return { eventId: runtimeState.eventNow.id, didStart: true };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { TimerType } from '../TimerType.type.js';
|
|||||||
import type { TimeStrategy } from '../TimeStrategy.type.js';
|
import type { TimeStrategy } from '../TimeStrategy.type.js';
|
||||||
import type { Trigger } from './Automation.type.js';
|
import type { Trigger } from './Automation.type.js';
|
||||||
import type { EntryCustomFields } from './CustomFields.type.js';
|
import type { EntryCustomFields } from './CustomFields.type.js';
|
||||||
|
import type { Day } from './Temporal.js';
|
||||||
|
|
||||||
export type EntryId = string;
|
export type EntryId = string;
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ export type OntimeEvent = OntimeBaseEvent & {
|
|||||||
// !==== RUNTIME METADATA ====! //
|
// !==== RUNTIME METADATA ====! //
|
||||||
revision: number;
|
revision: number;
|
||||||
delay: number; // calculated at runtime
|
delay: number; // calculated at runtime
|
||||||
dayOffset: number; // calculated at runtime
|
dayOffset: Day; // calculated at runtime
|
||||||
gap: number; // calculated at runtime
|
gap: number; // calculated at runtime
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { OntimeDelay, OntimeEvent, OntimeGroup, OntimeMilestone } from 'ontime-types';
|
import type { Day, OntimeDelay, OntimeEvent, OntimeGroup, OntimeMilestone } from 'ontime-types';
|
||||||
import { EndAction, SupportedEntry, TimerType, TimeStrategy } from 'ontime-types';
|
import { EndAction, SupportedEntry, TimerType, TimeStrategy } from 'ontime-types';
|
||||||
|
|
||||||
export const event: Omit<OntimeEvent, 'id' | 'cue'> = {
|
export const event: Omit<OntimeEvent, 'id' | 'cue'> = {
|
||||||
@@ -24,7 +24,7 @@ export const event: Omit<OntimeEvent, 'id' | 'cue'> = {
|
|||||||
parent: null,
|
parent: null,
|
||||||
revision: 0, // calculated at runtime
|
revision: 0, // calculated at runtime
|
||||||
delay: 0, // calculated at runtime
|
delay: 0, // calculated at runtime
|
||||||
dayOffset: 0, // calculated at runtime
|
dayOffset: 0 as Day, // calculated at runtime
|
||||||
gap: 0, // calculated at runtime
|
gap: 0, // calculated at runtime
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user