Skip to content

Commit fb5dd9f

Browse files
authored
feat: expose persistent playout state on LSG (Sofie-Automation#1644)
1 parent 340898e commit fb5dd9f

17 files changed

Lines changed: 170 additions & 50 deletions

File tree

meteor/server/migration/X_X_X.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { addMigrationSteps } from './databaseMigration'
22
import { CURRENT_SYSTEM_VERSION } from './currentSystemVersion'
33
import { MongoInternals } from 'meteor/mongo'
4-
import { Studios } from '../collections'
4+
import { RundownPlaylists, Studios } from '../collections'
55
import { ExpectedPackages } from '../collections'
66
import * as PackagesPreR53 from '@sofie-automation/corelib/dist/dataModel/Old/ExpectedPackagesR52'
77
import {
@@ -198,6 +198,50 @@ export const addSteps = addMigrationSteps(CURRENT_SYSTEM_VERSION, [
198198
}
199199
},
200200
},
201+
{
202+
id: `Rename previousPersistentState to privatePlayoutPersistentState`,
203+
canBeRunAutomatically: true,
204+
validate: async () => {
205+
const playlists = await RundownPlaylists.countDocuments({
206+
previousPersistentState: { $exists: true },
207+
privatePlayoutPersistentState: { $exists: false },
208+
})
209+
if (playlists > 0) {
210+
return 'One or more Playlists has previousPersistentState field that needs to be renamed to privatePlayoutPersistentState'
211+
}
212+
213+
return false
214+
},
215+
migrate: async () => {
216+
const playlists = await RundownPlaylists.findFetchAsync(
217+
{
218+
previousPersistentState: { $exists: true },
219+
privatePlayoutPersistentState: { $exists: false },
220+
},
221+
{
222+
projection: {
223+
_id: 1,
224+
// @ts-expect-error - This field is being renamed, so it won't exist on the type anymore
225+
previousPersistentState: 1,
226+
},
227+
}
228+
)
229+
230+
for (const playlist of playlists) {
231+
// @ts-expect-error - This field is being renamed, so it won't exist on the type anymore
232+
const previousPersistentState = playlist.previousPersistentState
233+
234+
await RundownPlaylists.mutableCollection.updateAsync(playlist._id, {
235+
$set: {
236+
privatePlayoutPersistentState: previousPersistentState,
237+
},
238+
$unset: {
239+
previousPersistentState: 1,
240+
},
241+
})
242+
}
243+
},
244+
},
201245
// Add your migration here
202246

203247
new ContainerIdsToObjectWithOverridesMigrationStep(),
Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,48 @@
11
/**
22
* A store for persisting playout state between bluerpint method calls
33
* This belongs to the Playlist and will be discarded when the Playlist is reset
4+
* This wraps both the 'privateData' and 'publicData' variants, the private variant only accessible to Blueprints, and the public variant available in APIs such as the LSG
45
*/
56
export interface BlueprintPlayoutPersistentStore<T = unknown> {
67
/**
7-
* Get all the data in the store
8+
* Get all the private data in the store
89
*/
910
getAll(): Partial<T>
1011
/**
11-
* Retrieve a key of data from the store
12+
* Retrieve a key of private data from the store
1213
* @param k The key to retrieve
1314
*/
1415
getKey<K extends keyof T>(k: K): T[K] | undefined
1516
/**
16-
* Update a key of data in the store
17+
* Update a key of private data in the store
1718
* @param k The key to update
1819
* @param v The value to set
1920
*/
2021
setKey<K extends keyof T>(k: K, v: T[K]): void
2122
/**
22-
* Replace all the data in the store
23+
* Replace all the private data in the store
2324
* @param obj The new data
2425
*/
2526
setAll(obj: T): void
27+
28+
/**
29+
* Get all the public data in the store
30+
*/
31+
getAllPublic(): Partial<T>
32+
/**
33+
* Retrieve a key of public data from the store
34+
* @param k The key to retrieve
35+
*/
36+
getKeyPublic<K extends keyof T>(k: K): T[K] | undefined
37+
/**
38+
* Update a key of public data in the store
39+
* @param k The key to update
40+
* @param v The value to set
41+
*/
42+
setKeyPublic<K extends keyof T>(k: K, v: T[K]): void
43+
/**
44+
* Replace all the public data in the store
45+
* @param obj The new data
46+
*/
47+
setAllPublic(obj: T): void
2648
}

packages/corelib/src/dataModel/RundownPlaylist.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,13 @@ export interface DBRundownPlaylist {
171171
* Persistent state belong to blueprint playout methods
172172
* This can be accessed and modified by the blueprints in various methods
173173
*/
174-
previousPersistentState?: TimelinePersistentState
174+
privatePlayoutPersistentState?: TimelinePersistentState
175+
/**
176+
* Persistent state belong to blueprint playout methods, but exposed to APIs such as the LSG
177+
* This can be accessed and modified by the blueprints in various methods, but is also exposed to APIs such as the LSG
178+
*/
179+
publicPlayoutPersistentState?: TimelinePersistentState
180+
175181
/** AB playback sessions calculated in the last timeline genertaion */
176182
trackedAbSessions?: ABSessionInfo[]
177183
/** AB playback sessions assigned in the last timeline generation */
Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,53 @@
11
import type { TimelinePersistentState } from '@sofie-automation/blueprints-integration'
22
import type { BlueprintPlayoutPersistentStore } from '@sofie-automation/blueprints-integration/dist/context/playoutStore'
33
import { clone } from '@sofie-automation/corelib/dist/lib'
4+
import type { PlayoutModel } from '../../../playout/model/PlayoutModel.js'
45

56
export class PersistentPlayoutStateStore implements BlueprintPlayoutPersistentStore {
6-
#state: TimelinePersistentState | undefined
7-
#hasChanges = false
7+
#privateState: TimelinePersistentState | undefined
8+
#hasPrivateChanges = false
9+
#publicState: TimelinePersistentState | undefined
10+
#hasPublicChanges = false
811

9-
get hasChanges(): boolean {
10-
return this.#hasChanges
12+
constructor(privateState: TimelinePersistentState | undefined, publicState: TimelinePersistentState | undefined) {
13+
this.#privateState = clone(privateState)
14+
this.#publicState = clone(publicState)
1115
}
1216

13-
constructor(state: TimelinePersistentState | undefined) {
14-
this.#state = clone(state)
17+
saveToModel(model: PlayoutModel): void {
18+
if (this.#hasPrivateChanges) model.setBlueprintPrivatePersistentState(this.#privateState)
19+
if (this.#hasPublicChanges) model.setBlueprintPublicPersistentState(this.#publicState)
1520
}
1621

1722
getAll(): Partial<unknown> {
18-
return this.#state || {}
23+
return this.#privateState || {}
1924
}
2025
getKey<K extends never>(k: K): unknown {
21-
return this.#state?.[k]
26+
return this.#privateState?.[k]
2227
}
2328
setKey<K extends never>(k: K, v: unknown): void {
24-
if (!this.#state) this.#state = {}
25-
;(this.#state as any)[k] = v
26-
this.#hasChanges = true
29+
if (!this.#privateState) this.#privateState = {}
30+
;(this.#privateState as any)[k] = v
31+
this.#hasPrivateChanges = true
2732
}
2833
setAll(obj: unknown): void {
29-
this.#state = obj
30-
this.#hasChanges = true
34+
this.#privateState = obj
35+
this.#hasPrivateChanges = true
36+
}
37+
38+
getAllPublic(): Partial<unknown> {
39+
return this.#publicState || {}
40+
}
41+
getKeyPublic<K extends never>(k: K): unknown {
42+
return this.#publicState?.[k]
43+
}
44+
setKeyPublic<K extends never>(k: K, v: unknown): void {
45+
if (!this.#publicState) this.#publicState = {}
46+
;(this.#publicState as any)[k] = v
47+
this.#hasPublicChanges = true
48+
}
49+
setAllPublic(obj: unknown): void {
50+
this.#publicState = obj
51+
this.#hasPublicChanges = true
3152
}
3253
}

packages/job-worker/src/playout/adlibAction.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,10 @@ export async function executeActionInner(
247247
)
248248

249249
try {
250-
const blueprintPersistentState = new PersistentPlayoutStateStore(playoutModel.playlist.previousPersistentState)
250+
const blueprintPersistentState = new PersistentPlayoutStateStore(
251+
playoutModel.playlist.privatePlayoutPersistentState,
252+
playoutModel.playlist.publicPlayoutPersistentState
253+
)
251254

252255
await blueprint.blueprint.executeAction(
253256
actionContext,
@@ -260,9 +263,7 @@ export async function executeActionInner(
260263
actionParameters.actionOptions ?? {}
261264
)
262265

263-
if (blueprintPersistentState.hasChanges) {
264-
playoutModel.setBlueprintPersistentState(blueprintPersistentState.getAll())
265-
}
266+
blueprintPersistentState.saveToModel(playoutModel)
266267
} catch (err) {
267268
logger.error(`Error in showStyleBlueprint.executeAction: ${stringifyError(err)}`)
268269
throw UserError.fromUnknown(err)

packages/job-worker/src/playout/model/PlayoutModel.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,16 @@ export interface PlayoutModel extends PlayoutModelReadonly, StudioPlayoutModelBa
328328
): void
329329

330330
/**
331-
* Store the blueprint persistent state
331+
* Store the blueprint private persistent state
332332
* @param persistentState Blueprint owned state
333333
*/
334-
setBlueprintPersistentState(persistentState: unknown | undefined): void
334+
setBlueprintPrivatePersistentState(persistentState: unknown | undefined): void
335+
336+
/**
337+
* Store the blueprint public persistent state
338+
* @param persistentState Blueprint owned state
339+
*/
340+
setBlueprintPublicPersistentState(persistentState: unknown | undefined): void
335341

336342
/**
337343
* Set a PartInstance as the nexted PartInstance

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,8 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou
671671
delete this.playlistImpl.startedPlayback
672672
delete this.playlistImpl.rundownsStartedPlayback
673673
delete this.playlistImpl.segmentsStartedPlayback
674-
delete this.playlistImpl.previousPersistentState
674+
delete this.playlistImpl.publicPlayoutPersistentState
675+
delete this.playlistImpl.privatePlayoutPersistentState
675676
delete this.playlistImpl.trackedAbSessions
676677
delete this.playlistImpl.queuedSegmentId
677678

@@ -783,8 +784,13 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou
783784
this.#playlistHasChanged = true
784785
}
785786

786-
setBlueprintPersistentState(persistentState: unknown | undefined): void {
787-
this.playlistImpl.previousPersistentState = persistentState
787+
setBlueprintPrivatePersistentState(persistentState: unknown | undefined): void {
788+
this.playlistImpl.privatePlayoutPersistentState = persistentState
789+
790+
this.#playlistHasChanged = true
791+
}
792+
setBlueprintPublicPersistentState(persistentState: unknown | undefined): void {
793+
this.playlistImpl.publicPlayoutPersistentState = persistentState
788794

789795
this.#playlistHasChanged = true
790796
}

packages/job-worker/src/playout/setNext.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,15 @@ async function executeOnSetAsNextCallback(
229229
playoutModel.clearAllNotifications(NOTIFICATION_CATEGORY)
230230

231231
try {
232-
const blueprintPersistentState = new PersistentPlayoutStateStore(playoutModel.playlist.previousPersistentState)
232+
const blueprintPersistentState = new PersistentPlayoutStateStore(
233+
playoutModel.playlist.privatePlayoutPersistentState,
234+
playoutModel.playlist.publicPlayoutPersistentState
235+
)
233236

234237
await blueprint.blueprint.onSetAsNext(onSetAsNextContext, blueprintPersistentState)
235238
await applyOnSetAsNextSideEffects(context, playoutModel, onSetAsNextContext)
236239

237-
if (blueprintPersistentState.hasChanges) {
238-
playoutModel.setBlueprintPersistentState(blueprintPersistentState.getAll())
239-
}
240+
blueprintPersistentState.saveToModel(playoutModel)
240241

241242
for (const note of onSetAsNextContext.notes) {
242243
// Update the notifications. Even though these are related to a partInstance, they will be cleared on the next take

packages/job-worker/src/playout/take.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,8 @@ async function executeOnTakeCallback(
343343
)
344344
try {
345345
const blueprintPersistentState = new PersistentPlayoutStateStore(
346-
playoutModel.playlist.previousPersistentState
346+
playoutModel.playlist.privatePlayoutPersistentState,
347+
playoutModel.playlist.publicPlayoutPersistentState
347348
)
348349

349350
await blueprint.blueprint.onTake(onSetAsNextContext, blueprintPersistentState)
@@ -353,9 +354,7 @@ async function executeOnTakeCallback(
353354
partToQueueAfterTake = onSetAsNextContext.partToQueueAfterTake
354355
}
355356

356-
if (blueprintPersistentState.hasChanges) {
357-
playoutModel.setBlueprintPersistentState(blueprintPersistentState.getAll())
358-
}
357+
blueprintPersistentState.saveToModel(playoutModel)
359358

360359
for (const note of onSetAsNextContext.notes) {
361360
// Update the notifications. Even though these are related to a partInstance, they will be cleared on the next take
@@ -549,7 +548,8 @@ export function updatePartInstanceOnTake(
549548
takeRundown
550549
)
551550
const blueprintPersistentState = new PersistentPlayoutStateStore(
552-
playoutModel.playlist.previousPersistentState
551+
playoutModel.playlist.privatePlayoutPersistentState,
552+
playoutModel.playlist.publicPlayoutPersistentState
553553
)
554554
previousPartEndState = blueprint.blueprint.getEndStateForPart(
555555
context2,
@@ -558,9 +558,8 @@ export function updatePartInstanceOnTake(
558558
resolvedPieces.map(convertResolvedPieceInstanceToBlueprints),
559559
time
560560
)
561-
if (blueprintPersistentState.hasChanges) {
562-
playoutModel.setBlueprintPersistentState(blueprintPersistentState.getAll())
563-
}
561+
blueprintPersistentState.saveToModel(playoutModel)
562+
564563
if (span) span.end()
565564
logger.info(`Calculated end state in ${getCurrentTime() - time}ms`)
566565
} catch (err) {

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,8 @@ export async function getTimelineRundown(
415415

416416
if (blueprint.blueprint.onTimelineGenerate) {
417417
const blueprintPersistentState = new PersistentPlayoutStateStore(
418-
playoutModel.playlist.previousPersistentState
418+
playoutModel.playlist.privatePlayoutPersistentState,
419+
playoutModel.playlist.publicPlayoutPersistentState
419420
)
420421

421422
const span = context.startSpan('blueprint.onTimelineGenerate')
@@ -437,9 +438,7 @@ export async function getTimelineRundown(
437438
})
438439
})
439440

440-
if (blueprintPersistentState.hasChanges) {
441-
playoutModel.setBlueprintPersistentState(blueprintPersistentState.getAll())
442-
}
441+
blueprintPersistentState.saveToModel(playoutModel)
443442
}
444443

445444
playoutModel.setAbResolvingState(

0 commit comments

Comments
 (0)