Skip to content

Commit a79cd69

Browse files
committed
fix(EAV-737): prevent latency caused by onRundownDeActivate
1 parent 77dba30 commit a79cd69

5 files changed

Lines changed: 93 additions & 32 deletions

File tree

packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import {
7777
flattenAndProcessTimelineObjects,
7878
preserveOrReplaceNowTimesInObjects,
7979
logAnyRemainingNowTimes,
80+
getStudioTimeline,
8081
} from '../../timeline/generate.js'
8182
import { deNowifyMultiGatewayTimeline } from '../../timeline/multi-gateway.js'
8283

@@ -953,15 +954,17 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou
953954
versions,
954955
objs: timelineObjs,
955956
timingContext: timingInfo,
956-
regenerateTimelineToken
957-
} = await getTimelineRundown(this.context, this as PlayoutModel)
957+
regenerateTimelineToken,
958+
} = this.playlist.activationId
959+
? await getTimelineRundown(this.context, this)
960+
: await getStudioTimeline(this.context, this)
958961

959962
flattenAndProcessTimelineObjects(this.context, timelineObjs)
960963

961964
preserveOrReplaceNowTimesInObjects(this, timelineObjs)
962965

963966
if (this.isMultiGatewayMode) {
964-
deNowifyMultiGatewayTimeline(this as PlayoutModel, timelineObjs, timingInfo)
967+
deNowifyMultiGatewayTimeline(this, timelineObjs, timingInfo)
965968

966969
logAnyRemainingNowTimes(this.context, timelineObjs)
967970
}

packages/job-worker/src/playout/timeline/generate.ts

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,19 @@ function generateTimelineVersions(
6666
}
6767
}
6868

69-
export async function updateStudioTimeline(
69+
/**
70+
* Generate timeline objects for a studio (when no playlist is active)
71+
*/
72+
export async function getStudioTimeline(
7073
context: JobContext,
7174
playoutModel: StudioPlayoutModel | PlayoutModel
72-
): Promise<void> {
73-
const span = context.startSpan('updateStudioTimeline')
74-
logger.debug('updateStudioTimeline running...')
75+
): Promise<{
76+
objs: Array<TimelineObjRundown>
77+
versions: TimelineCompleteGenerationVersions
78+
timingContext: undefined
79+
regenerateTimelineToken: undefined
80+
}> {
7581
const studio = context.studio
76-
// Ensure there isn't a playlist active, as that should be using a different function call
77-
if (isModelForStudio(playoutModel)) {
78-
const activePlaylists = playoutModel.getActiveRundownPlaylists()
79-
if (activePlaylists.length > 0) {
80-
throw new Error(`Studio has an active playlist`)
81-
}
82-
} else {
83-
if (playoutModel.playlist.activationId) {
84-
throw new Error(`Studio has an active playlist`)
85-
}
86-
}
87-
8882
let baselineObjects: TimelineObjRundown[] = []
8983
let studioBaseline: BlueprintResultBaseline | undefined
9084

@@ -118,22 +112,38 @@ export async function updateStudioTimeline(
118112
studioBlueprint?.blueprint?.blueprintVersion ?? '-'
119113
)
120114

121-
flattenAndProcessTimelineObjects(context, baselineObjects)
122-
123-
// Future: We should handle any 'now' objects that are at the root of this timeline
124-
preserveOrReplaceNowTimesInObjects(playoutModel, baselineObjects)
125-
126-
if (playoutModel.isMultiGatewayMode) {
127-
logAnyRemainingNowTimes(context, baselineObjects)
115+
if (studioBaseline) {
116+
updateBaselineExpectedPackagesOnStudio(context, playoutModel, studioBaseline)
128117
}
129118

130-
const timelineHash = playoutModel.setTimeline(baselineObjects, versions, undefined).timelineHash
119+
return {
120+
objs: baselineObjects,
121+
versions,
122+
timingContext: undefined,
123+
regenerateTimelineToken: undefined,
124+
}
125+
}
131126

132-
if (studioBaseline) {
133-
updateBaselineExpectedPackagesOnStudio(context, playoutModel, studioBaseline)
127+
export async function updateStudioTimeline(
128+
context: JobContext,
129+
playoutModel: StudioPlayoutModel | PlayoutModel
130+
): Promise<void> {
131+
const span = context.startSpan('updateStudioTimeline')
132+
logger.debug('updateStudioTimeline: marking studio as needing timeline update')
133+
// Ensure there isn't a playlist active, as that should be using a different function call
134+
if (isModelForStudio(playoutModel)) {
135+
const activePlaylists = playoutModel.getActiveRundownPlaylists()
136+
if (activePlaylists.length > 0) {
137+
throw new Error(`Studio has an active playlist`)
138+
}
139+
} else {
140+
if (playoutModel.playlist.activationId) {
141+
throw new Error(`Studio has an active playlist`)
142+
}
134143
}
135144

136-
logger.verbose(`updateStudioTimeline done, hash: "${timelineHash}"`)
145+
playoutModel.markTimelineNeedsUpdate()
146+
137147
if (span) span.end()
138148
}
139149

packages/job-worker/src/playout/timings/timelineTriggerTime.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ function timelineTriggerTimeInner(
203203
}
204204
if (tlChanged) {
205205
const timelineHash = studioPlayoutModel.setTimeline(
206-
timelineObjs,
207-
timeline.generationVersions,
206+
timelineObjs,
207+
timeline.generationVersions,
208208
timeline.regenerateTimelineToken
209209
).timelineHash
210210

packages/job-worker/src/studio/model/StudioPlayoutModel.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,10 @@ export interface StudioPlayoutModel extends StudioPlayoutModelBase, BaseModel {
7979
* @returns Whether the change may affect timeline generation
8080
*/
8181
switchRouteSet(routeSetId: string, isActive: boolean | 'toggle'): boolean
82+
83+
/**
84+
* Mark the studio as needing a timeline update.
85+
* The timeline will be generated and published when model is ready to be saved.
86+
*/
87+
markTimelineNeedsUpdate(): void
8288
}

packages/job-worker/src/studio/model/StudioPlayoutModelImpl.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ import { DatabasePersistedModel } from '../../modelBase.js'
1818
import { ExpectedPlayoutItemStudio } from '@sofie-automation/corelib/dist/dataModel/ExpectedPlayoutItem'
1919
import { StudioBaselineHelper } from './StudioBaselineHelper.js'
2020
import { ExpectedPackage } from '@sofie-automation/blueprints-integration'
21+
import {
22+
getStudioTimeline,
23+
flattenAndProcessTimelineObjects,
24+
preserveOrReplaceNowTimesInObjects,
25+
logAnyRemainingNowTimes,
26+
} from '../../playout/timeline/generate.js'
27+
import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError'
2128

2229
/**
2330
* This is a model used for studio operations.
@@ -33,6 +40,7 @@ export class StudioPlayoutModelImpl implements StudioPlayoutModel {
3340
public readonly rundownPlaylists: ReadonlyDeep<DBRundownPlaylist[]>
3441

3542
#timelineHasChanged = false
43+
#timelineNeedsRegeneration = false
3644
#timeline: TimelineComplete | null
3745

3846
public get timeline(): TimelineComplete | null {
@@ -111,20 +119,54 @@ export class StudioPlayoutModelImpl implements StudioPlayoutModel {
111119
return this.context.setRouteSetActive(routeSetId, isActive)
112120
}
113121

122+
markTimelineNeedsUpdate(): void {
123+
this.#timelineNeedsRegeneration = true
124+
}
125+
114126
/**
115127
* Discards all documents in this model, and marks it as unusable
116128
*/
117129
dispose(): void {
118130
this.#disposed = true
119131
}
120132

133+
async #regenerateStudioTimeline(): Promise<void> {
134+
const span = this.context.startSpan('StudioPlayoutModelImpl.regenerateStudioTimeline')
135+
logger.debug('Regenerating studio timeline...')
136+
137+
try {
138+
const { versions, objs: timelineObjs } = await getStudioTimeline(this.context, this)
139+
140+
flattenAndProcessTimelineObjects(this.context, timelineObjs)
141+
preserveOrReplaceNowTimesInObjects(this, timelineObjs)
142+
143+
if (this.isMultiGatewayMode) {
144+
logAnyRemainingNowTimes(this.context, timelineObjs)
145+
}
146+
147+
const timelineHash = this.setTimeline(timelineObjs, versions, undefined).timelineHash
148+
logger.verbose(`Studio timeline regeneration done, hash: "${timelineHash}"`)
149+
} catch (err) {
150+
logger.error(`Error regenerating studio timeline: ${stringifyError(err)}`)
151+
throw err
152+
} finally {
153+
if (span) span.end()
154+
}
155+
}
156+
121157
async saveAllToDatabase(): Promise<void> {
122158
if (this.#disposed) {
123159
throw new Error('Cannot save disposed PlayoutModel')
124160
}
125161

126162
const span = this.context.startSpan('StudioPlayoutModelImpl.saveAllToDatabase')
127163

164+
// Generate timeline if needed
165+
if (this.#timelineNeedsRegeneration) {
166+
await this.#regenerateStudioTimeline()
167+
this.#timelineNeedsRegeneration = false
168+
}
169+
128170
// Prioritise the timeline for publication reasons
129171
if (this.#timelineHasChanged && this.#timeline) {
130172
// Do a fast-track for the timeline to be published faster:

0 commit comments

Comments
 (0)