Skip to content

Commit da2bae0

Browse files
authored
Merge pull request #1386 from bbc/upstream/blueprint-playout-store
feat(Blueprint API): expose persistent playout store to more methods
2 parents 2addb10 + 7956f7b commit da2bae0

File tree

11 files changed

+139
-32
lines changed

11 files changed

+139
-32
lines changed

packages/blueprints-integration/src/api/showStyle.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import type { ExpectedPackage } from '../package'
4848
import type { ABResolverConfiguration } from '../abPlayback'
4949
import type { SofieIngestSegment } from '../ingest-types'
5050
import { PackageStatusMessage } from '@sofie-automation/shared-lib/dist/packageStatusMessages'
51+
import { BlueprintPlayoutPersistentStore } from '../context/playoutStore'
5152

5253
export { PackageStatusMessage }
5354

@@ -111,7 +112,6 @@ export interface ShowStyleBlueprintManifest<TRawConfig = IBlueprintConfig, TProc
111112
context: ISyncIngestUpdateToPartInstanceContext,
112113
existingPartInstance: BlueprintSyncIngestPartInstance,
113114
newData: BlueprintSyncIngestNewData,
114-
115115
playoutStatus: 'previous' | 'current' | 'next'
116116
) => void
117117

@@ -130,12 +130,13 @@ export interface ShowStyleBlueprintManifest<TRawConfig = IBlueprintConfig, TProc
130130
/** Execute an action defined by an IBlueprintActionManifest */
131131
executeAction?: (
132132
context: IActionExecutionContext,
133+
playoutPersistentState: BlueprintPlayoutPersistentStore<TimelinePersistentState>,
133134
actionId: string,
134135
userData: ActionUserData,
135136
triggerMode: string | undefined,
136-
privateData?: unknown,
137-
publicData?: unknown,
138-
actionOptions?: { [key: string]: any }
137+
privateData: unknown | undefined,
138+
publicData: unknown | undefined,
139+
actionOptions: { [key: string]: any } | undefined
139140
) => Promise<{ validationErrors: any } | void>
140141

141142
/** Generate adlib piece from ingest data */
@@ -204,21 +205,27 @@ export interface ShowStyleBlueprintManifest<TRawConfig = IBlueprintConfig, TProc
204205
* Called during a Take action.
205206
* Allows for part modification or aborting the take.
206207
*/
207-
onTake?: (context: IOnTakeContext) => Promise<void>
208+
onTake?: (
209+
context: IOnTakeContext,
210+
playoutPersistentState: BlueprintPlayoutPersistentStore<TimelinePersistentState>
211+
) => Promise<void>
208212
/** Called after a Take action */
209213
onPostTake?: (context: IPartEventContext) => Promise<void>
210214

211215
/**
212216
* Called when a part is set as Next, including right after a Take.
213217
* Allows for part modification.
214218
*/
215-
onSetAsNext?: (context: IOnSetAsNextContext) => Promise<void>
219+
onSetAsNext?: (
220+
context: IOnSetAsNextContext,
221+
playoutPersistentState: BlueprintPlayoutPersistentStore<TimelinePersistentState>
222+
) => Promise<void>
216223

217224
/** Called after the timeline has been generated, used to manipulate the timeline */
218225
onTimelineGenerate?: (
219226
context: ITimelineEventContext,
220227
timeline: OnGenerateTimelineObj<TSR.TSRTimelineContent>[],
221-
previousPersistentState: TimelinePersistentState | undefined,
228+
playoutPersistentState: BlueprintPlayoutPersistentStore<TimelinePersistentState>,
222229
previousPartEndState: PartEndState | undefined,
223230
resolvedPieces: IBlueprintResolvedPieceInstance[]
224231
) => Promise<BlueprintResultTimeline>
@@ -229,7 +236,7 @@ export interface ShowStyleBlueprintManifest<TRawConfig = IBlueprintConfig, TProc
229236
/** Called just before taking the next part. This generates some persisted data used by onTimelineGenerate to modify the timeline based on the previous part (eg, persist audio levels) */
230237
getEndStateForPart?: (
231238
context: IRundownContext,
232-
previousPersistentState: TimelinePersistentState | undefined,
239+
playoutPersistentState: BlueprintPlayoutPersistentStore<TimelinePersistentState>,
233240
partInstance: IBlueprintPartInstance,
234241
resolvedPieces: IBlueprintResolvedPieceInstance[],
235242
time: number
@@ -249,7 +256,6 @@ export interface ShowStyleBlueprintManifest<TRawConfig = IBlueprintConfig, TProc
249256

250257
export interface BlueprintResultTimeline {
251258
timeline: OnGenerateTimelineObj<TSR.TSRTimelineContent>[]
252-
persistentState: TimelinePersistentState
253259
}
254260
export interface BlueprintResultBaseline {
255261
timelineObjects: TimelineObjectCoreExt<TSR.TSRTimelineContent>[]

packages/blueprints-integration/src/context/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './fixUpConfigContext'
55
export * from './onSetAsNextContext'
66
export * from './onTakeContext'
77
export * from './packageInfoContext'
8+
export * from './playoutStore'
89
export * from './processIngestDataContext'
910
export * from './rundownContext'
1011
export * from './showStyleContext'
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* A store for persisting playout state between bluerpint method calls
3+
* This belongs to the Playlist and will be discarded when the Playlist is reset
4+
*/
5+
export interface BlueprintPlayoutPersistentStore<T = unknown> {
6+
/**
7+
* Get all the data in the store
8+
*/
9+
getAll(): Partial<T>
10+
/**
11+
* Retrieve a key of data from the store
12+
* @param k The key to retrieve
13+
*/
14+
getKey<K extends keyof T>(k: K): T[K] | undefined
15+
/**
16+
* Update a key of data in the store
17+
* @param k The key to update
18+
* @param v The value to set
19+
*/
20+
setKey<K extends keyof T>(k: K, v: T[K]): void
21+
/**
22+
* Replace all the data in the store
23+
* @param obj The new data
24+
*/
25+
setAll(obj: T): void
26+
}

packages/corelib/src/dataModel/RundownPlaylist.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ export interface DBRundownPlaylist {
169169
/** If the order of rundowns in this playlist has ben set manually by a user/blueprints in Sofie */
170170
rundownIdsInOrder: RundownId[]
171171

172-
/** Previous state persisted from ShowStyleBlueprint.onTimelineGenerate */
172+
/**
173+
* Persistent state belong to blueprint playout methods
174+
* This can be accessed and modified by the blueprints in various methods
175+
*/
173176
previousPersistentState?: TimelinePersistentState
174177
/** AB playback sessions calculated in the last timeline genertaion */
175178
trackedAbSessions?: ABSessionInfo[]
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { TimelinePersistentState } from '@sofie-automation/blueprints-integration'
2+
import type { BlueprintPlayoutPersistentStore } from '@sofie-automation/blueprints-integration/dist/context/playoutStore'
3+
import { clone } from '@sofie-automation/corelib/dist/lib'
4+
5+
export class PersistentPlayoutStateStore implements BlueprintPlayoutPersistentStore {
6+
#state: TimelinePersistentState | undefined
7+
#hasChanges = false
8+
9+
get hasChanges(): boolean {
10+
return this.#hasChanges
11+
}
12+
13+
constructor(state: TimelinePersistentState | undefined) {
14+
this.#state = clone(state)
15+
}
16+
17+
getAll(): Partial<unknown> {
18+
return this.#state || {}
19+
}
20+
getKey<K extends never>(k: K): unknown {
21+
return this.#state?.[k]
22+
}
23+
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
27+
}
28+
setAll(obj: unknown): void {
29+
this.#state = obj
30+
this.#hasChanges = true
31+
}
32+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { convertNoteToNotification } from '../notifications/util'
3434
import type { INoteBase } from '@sofie-automation/corelib/dist/dataModel/Notes'
3535
import { NotificationsModelHelper } from '../notifications/NotificationsModelHelper'
3636
import type { INotificationsModel } from '../notifications/NotificationsModel'
37+
import { PersistentPlayoutStateStore } from '../blueprints/context/services/PersistantStateStore'
3738

3839
/**
3940
* Execute an AdLib Action
@@ -230,15 +231,22 @@ export async function executeActionInner(
230231
)
231232

232233
try {
234+
const blueprintPersistentState = new PersistentPlayoutStateStore(playoutModel.playlist.previousPersistentState)
235+
233236
await blueprint.blueprint.executeAction(
234237
actionContext,
238+
blueprintPersistentState,
235239
actionParameters.actionId,
236240
actionParameters.userData,
237241
actionParameters.triggerMode,
238242
actionParameters.privateData,
239243
actionParameters.publicData,
240244
actionParameters.actionOptions ?? {}
241245
)
246+
247+
if (blueprintPersistentState.hasChanges) {
248+
playoutModel.setBlueprintPersistentState(blueprintPersistentState.getAll())
249+
}
242250
} catch (err) {
243251
logger.error(`Error in showStyleBlueprint.executeAction: ${stringifyError(err)}`)
244252
throw UserError.fromUnknown(err)

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -317,17 +317,21 @@ export interface PlayoutModel extends PlayoutModelReadonly, StudioPlayoutModelBa
317317
setHoldState(newState: RundownHoldState): void
318318

319319
/**
320-
* Store the persistent results of the AB playback resolving and onTimelineGenerate
321-
* @param persistentState Blueprint owned state from onTimelineGenerate
320+
* Store the persistent results of the AB playback resolving
322321
* @param assignedAbSessions The applied AB sessions
323322
* @param trackedAbSessions The known AB sessions
324323
*/
325-
setOnTimelineGenerateResult(
326-
persistentState: unknown | undefined,
324+
setAbResolvingState(
327325
assignedAbSessions: Record<string, ABSessionAssignments>,
328326
trackedAbSessions: ABSessionInfo[]
329327
): void
330328

329+
/**
330+
* Store the blueprint persistent state
331+
* @param persistentState Blueprint owned state
332+
*/
333+
setBlueprintPersistentState(persistentState: unknown | undefined): void
334+
331335
/**
332336
* Set a PartInstance as the nexted PartInstance
333337
* @param partInstance PartInstance to be set as next, or none

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -727,18 +727,22 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou
727727
this.#playlistHasChanged = true
728728
}
729729

730-
setOnTimelineGenerateResult(
731-
persistentState: unknown | undefined,
730+
setAbResolvingState(
732731
assignedAbSessions: Record<string, ABSessionAssignments>,
733732
trackedAbSessions: ABSessionInfo[]
734733
): void {
735-
this.playlistImpl.previousPersistentState = persistentState
736734
this.playlistImpl.assignedAbSessions = assignedAbSessions
737735
this.playlistImpl.trackedAbSessions = trackedAbSessions
738736

739737
this.#playlistHasChanged = true
740738
}
741739

740+
setBlueprintPersistentState(persistentState: unknown | undefined): void {
741+
this.playlistImpl.previousPersistentState = persistentState
742+
743+
this.#playlistHasChanged = true
744+
}
745+
742746
setPartInstanceAsNext(
743747
partInstance: PlayoutPartInstanceModel | null,
744748
setManually: boolean,

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
} from '../blueprints/context/services/PartAndPieceInstanceActionService'
3232
import { NoteSeverity } from '@sofie-automation/blueprints-integration'
3333
import { convertNoteToNotification } from '../notifications/util'
34+
import { PersistentPlayoutStateStore } from '../blueprints/context/services/PersistantStateStore'
3435

3536
/**
3637
* Set or clear the nexted part, from a given PartInstance, or SelectNextPartResult
@@ -225,9 +226,15 @@ async function executeOnSetAsNextCallback(
225226
playoutModel.clearAllNotifications(NOTIFICATION_CATEGORY)
226227

227228
try {
228-
await blueprint.blueprint.onSetAsNext(onSetAsNextContext)
229+
const blueprintPersistentState = new PersistentPlayoutStateStore(playoutModel.playlist.previousPersistentState)
230+
231+
await blueprint.blueprint.onSetAsNext(onSetAsNextContext, blueprintPersistentState)
229232
await applyOnSetAsNextSideEffects(context, playoutModel, onSetAsNextContext)
230233

234+
if (blueprintPersistentState.hasChanges) {
235+
playoutModel.setBlueprintPersistentState(blueprintPersistentState.getAll())
236+
}
237+
231238
for (const note of onSetAsNextContext.notes) {
232239
// Update the notifications. Even though these are related to a partInstance, they will be cleared on the next take
233240
playoutModel.setNotification(NOTIFICATION_CATEGORY, {

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
} from '../blueprints/context/services/PartAndPieceInstanceActionService'
3535
import { PlayoutRundownModel } from './model/PlayoutRundownModel'
3636
import { convertNoteToNotification } from '../notifications/util'
37+
import { PersistentPlayoutStateStore } from '../blueprints/context/services/PersistantStateStore'
3738

3839
/**
3940
* Take the currently Next:ed Part (start playing it)
@@ -316,10 +317,18 @@ async function executeOnTakeCallback(
316317
new PartAndPieceInstanceActionService(context, playoutModel, showStyle, currentRundown)
317318
)
318319
try {
319-
await blueprint.blueprint.onTake(onSetAsNextContext)
320+
const blueprintPersistentState = new PersistentPlayoutStateStore(
321+
playoutModel.playlist.previousPersistentState
322+
)
323+
324+
await blueprint.blueprint.onTake(onSetAsNextContext, blueprintPersistentState)
320325
await applyOnTakeSideEffects(context, playoutModel, onSetAsNextContext)
321326
isTakeAborted = onSetAsNextContext.isTakeAborted
322327

328+
if (blueprintPersistentState.hasChanges) {
329+
playoutModel.setBlueprintPersistentState(blueprintPersistentState.getAll())
330+
}
331+
323332
for (const note of onSetAsNextContext.notes) {
324333
// Update the notifications. Even though these are related to a partInstance, they will be cleared on the next take
325334
playoutModel.setNotification(NOTIFICATION_CATEGORY, {
@@ -511,13 +520,19 @@ export function updatePartInstanceOnTake(
511520
context.getShowStyleBlueprintConfig(showStyle),
512521
takeRundown
513522
)
523+
const blueprintPersistentState = new PersistentPlayoutStateStore(
524+
playoutModel.playlist.previousPersistentState
525+
)
514526
previousPartEndState = blueprint.blueprint.getEndStateForPart(
515527
context2,
516-
playoutModel.playlist.previousPersistentState,
528+
blueprintPersistentState,
517529
convertPartInstanceToBlueprints(currentPartInstance.partInstance),
518530
resolvedPieces.map(convertResolvedPieceInstanceToBlueprints),
519531
time
520532
)
533+
if (blueprintPersistentState.hasChanges) {
534+
playoutModel.setBlueprintPersistentState(blueprintPersistentState.getAll())
535+
}
521536
if (span) span.end()
522537
logger.info(`Calculated end state in ${getCurrentTime() - time}ms`)
523538
} catch (err) {

0 commit comments

Comments
 (0)