From 260006b6a85270237e2a907bf8cca2f06eead194 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 29 Aug 2024 16:31:35 +0100 Subject: [PATCH 01/22] wip: this is the theoretical mongo update flow implemented --- meteor/server/api/ingest/packageInfo.ts | 3 + .../corelib/src/dataModel/ExpectedPackages.ts | 17 +++++ .../PartAndPieceInstanceActionService.test.ts | 4 +- .../model/PlayoutPieceInstanceModel.ts | 12 ++++ .../model/implementation/PlayoutModelImpl.ts | 4 +- .../PlayoutPieceInstanceModelImpl.ts | 66 ++++++++++++++++++- .../model/implementation/SavePlayoutModel.ts | 31 ++++++++- .../__tests__/SavePlayoutModel.spec.ts | 14 ++-- packages/job-worker/src/playout/snapshot.ts | 3 + 9 files changed, 138 insertions(+), 16 deletions(-) diff --git a/meteor/server/api/ingest/packageInfo.ts b/meteor/server/api/ingest/packageInfo.ts index 44452774e0..d9fc1b3068 100644 --- a/meteor/server/api/ingest/packageInfo.ts +++ b/meteor/server/api/ingest/packageInfo.ts @@ -44,6 +44,9 @@ export async function onUpdatedPackageInfo(packageId: ExpectedPackageId, _doc: P case ExpectedPackageDBType.STUDIO_BASELINE_OBJECTS: onUpdatedPackageInfoForStudioBaselineDebounce(pkg) break + case ExpectedPackageDBType.PIECE_INSTANCE: + // No-op, we can't handle these updates + break default: assertNever(pkg) break diff --git a/packages/corelib/src/dataModel/ExpectedPackages.ts b/packages/corelib/src/dataModel/ExpectedPackages.ts index 404fd2bfdd..780b22b68a 100644 --- a/packages/corelib/src/dataModel/ExpectedPackages.ts +++ b/packages/corelib/src/dataModel/ExpectedPackages.ts @@ -8,6 +8,7 @@ import { BucketId, ExpectedPackageId, PartId, + PartInstanceId, PieceId, PieceInstanceId, RundownBaselineAdLibActionId, @@ -40,6 +41,7 @@ export type ExpectedPackageDB = | ExpectedPackageDBFromBucket | ExpectedPackageFromRundownBaseline | ExpectedPackageDBFromStudioBaselineObjects + | ExpectedPackageDBFromPieceInstance export enum ExpectedPackageDBType { PIECE = 'piece', @@ -51,6 +53,7 @@ export enum ExpectedPackageDBType { BUCKET_ADLIB_ACTION = 'bucket_adlib_action', RUNDOWN_BASELINE_OBJECTS = 'rundown_baseline_objects', STUDIO_BASELINE_OBJECTS = 'studio_baseline_objects', + PIECE_INSTANCE = 'piece_instance', } export interface ExpectedPackageDBBase extends Omit { _id: ExpectedPackageId @@ -134,6 +137,20 @@ export interface ExpectedPackageDBFromBucketAdLibAction extends ExpectedPackageD /** The `externalId` of the Bucket adlib-action this package belongs to */ pieceExternalId: string } +export interface ExpectedPackageDBFromPieceInstance extends ExpectedPackageDBBase { + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE + /** The PieceInstance this package belongs to */ + pieceInstanceId: PieceInstanceId + /** The PartInstance this package belongs to */ + partInstanceId: PartInstanceId + /** The Segment this package belongs to */ + segmentId: SegmentId + /** The rundown of the Piece this package belongs to */ + rundownId: RundownId + + // For type compatibility: + pieceId: null +} export function getContentVersionHash(expectedPackage: ReadonlyDeep>): string { return hashObj({ diff --git a/packages/job-worker/src/blueprints/context/services/__tests__/PartAndPieceInstanceActionService.test.ts b/packages/job-worker/src/blueprints/context/services/__tests__/PartAndPieceInstanceActionService.test.ts index 35f7922fff..5430588b60 100644 --- a/packages/job-worker/src/blueprints/context/services/__tests__/PartAndPieceInstanceActionService.test.ts +++ b/packages/job-worker/src/blueprints/context/services/__tests__/PartAndPieceInstanceActionService.test.ts @@ -32,7 +32,7 @@ import { PlayoutPartInstanceModel } from '../../../../playout/model/PlayoutPartI import { convertPartInstanceToBlueprints, convertPieceInstanceToBlueprints } from '../../lib' import { TimelineObjRundown, TimelineObjType } from '@sofie-automation/corelib/dist/dataModel/Timeline' import { PlayoutPartInstanceModelImpl } from '../../../../playout/model/implementation/PlayoutPartInstanceModelImpl' -import { writePartInstancesAndPieceInstances } from '../../../../playout/model/implementation/SavePlayoutModel' +import { writePartInstancesAndPieceInstancesAndExpectedPackages } from '../../../../playout/model/implementation/SavePlayoutModel' import { PlayoutPieceInstanceModel } from '../../../../playout/model/PlayoutPieceInstanceModel' import { DatabasePersistedModel } from '../../../../modelBase' @@ -217,7 +217,7 @@ describe('Test blueprint api context', () => { ) { // We need to push changes back to 'mongo' for these tests await Promise.all( - writePartInstancesAndPieceInstances( + writePartInstancesAndPieceInstancesAndExpectedPackages( context, normalizeArrayToMapFunc(allPartInstances as PlayoutPartInstanceModelImpl[], (p) => p.partInstance._id) ) diff --git a/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts b/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts index 0f233afe45..b340368165 100644 --- a/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts @@ -2,6 +2,7 @@ import { PieceInstanceInfiniteId } from '@sofie-automation/corelib/dist/dataMode import { ReadonlyDeep } from 'type-fest' import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { Time } from '@sofie-automation/blueprints-integration' +import { ExpectedPackageDBFromPieceInstance } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' export interface PlayoutPieceInstanceModel { /** @@ -9,6 +10,11 @@ export interface PlayoutPieceInstanceModel { */ readonly pieceInstance: ReadonlyDeep + /** + * The ExpectedPackages for the PieceInstance + */ + readonly expectedPackages: ReadonlyDeep + /** * Prepare this PieceInstance to be continued during HOLD * This sets the PieceInstance up as an infinite, to allow the Timeline to be generated correctly @@ -57,4 +63,10 @@ export interface PlayoutPieceInstanceModel { * @param props New properties for the Piece being wrapped */ updatePieceProps(props: Partial): void + + /** + * Update the expected packages for the PieceInstance + * @param expectedPackages The new packages + */ + setExpectedPackages(expectedPackages: ExpectedPackageDBFromPieceInstance[]): void } diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts index f6d23ce52d..9c11ddb5dc 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts @@ -48,7 +48,7 @@ import { protectString } from '@sofie-automation/shared-lib/dist/lib/protectedSt import { queuePartInstanceTimingEvent } from '../../timings/events' import { IS_PRODUCTION } from '../../../environment' import { DeferredAfterSaveFunction, DeferredFunction, PlayoutModel, PlayoutModelReadonly } from '../PlayoutModel' -import { writePartInstancesAndPieceInstances, writeAdlibTestingSegments } from './SavePlayoutModel' +import { writePartInstancesAndPieceInstancesAndExpectedPackages, writeAdlibTestingSegments } from './SavePlayoutModel' import { PlayoutPieceInstanceModel } from '../PlayoutPieceInstanceModel' import { DatabasePersistedModel } from '../../../modelBase' import { ExpectedPackageDBFromStudioBaselineObjects } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' @@ -559,7 +559,7 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou this.#playlistHasChanged ? this.context.directCollections.RundownPlaylists.replace(this.playlistImpl) : undefined, - ...writePartInstancesAndPieceInstances(this.context, this.allPartInstances), + ...writePartInstancesAndPieceInstancesAndExpectedPackages(this.context, this.allPartInstances), writeAdlibTestingSegments(this.context, this.rundownsImpl), this.#baselineHelper.saveAllToDatabase(), ]) diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts index c9390119ff..5240978eea 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts @@ -1,10 +1,16 @@ -import { PieceInstanceInfiniteId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { ExpectedPackageId, PieceInstanceInfiniteId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { clone, getRandomId } from '@sofie-automation/corelib/dist/lib' import { Time } from '@sofie-automation/blueprints-integration' import { PlayoutPieceInstanceModel } from '../PlayoutPieceInstanceModel' import _ = require('underscore') +import { ExpectedPackageDBFromPieceInstance } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { + DocumentChanges, + getDocumentChanges, + diffAndReturnLatestObjects, +} from '../../../ingest/model/implementation/utils' export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel { /** @@ -13,6 +19,9 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel */ PieceInstanceImpl: PieceInstance + #expectedPackages: ExpectedPackageDBFromPieceInstance[] + #expectedPackagesWithChanges = new Set() + /** * Set/delete a value for this PieceInstance, and track that there are changes * @param key Property key @@ -57,7 +66,8 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel * Whether this PieceInstance has unsaved changes */ get HasChanges(): boolean { - return this.#hasChanges + // nocommit - should this be two properties? + return this.#hasChanges || this.#expectedPackagesWithChanges.size > 0 } /** @@ -65,15 +75,31 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel */ clearChangedFlag(): void { this.#hasChanges = false + this.#expectedPackagesWithChanges.clear() } get pieceInstance(): ReadonlyDeep { return this.PieceInstanceImpl } - constructor(pieceInstances: PieceInstance, hasChanges: boolean) { + get expectedPackages(): ReadonlyDeep { + return [...this.#expectedPackages] + } + + get expectedPackagesChanges(): DocumentChanges { + return getDocumentChanges(this.#expectedPackagesWithChanges, this.#expectedPackages) + } + + constructor( + pieceInstances: PieceInstance, + expectedPackages: ExpectedPackageDBFromPieceInstance[], + hasChanges: boolean, + expectedPackagesWithChanges: ExpectedPackageId[] | null + ) { this.PieceInstanceImpl = pieceInstances + this.#expectedPackages = expectedPackages this.#hasChanges = hasChanges + this.#expectedPackagesWithChanges = new Set(expectedPackagesWithChanges) } /** @@ -136,4 +162,38 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel true ) } + + /** + * Update the expected packages for the PieceInstance + * @param expectedPackages The new packages + */ + setExpectedPackages(expectedPackages: ExpectedPackageDBFromPieceInstance[]): void { + // nocommit - refactor this into a simpler type than `ExpectedPackagesStore` or just reuse that? + + const newExpectedPackages: ExpectedPackageDBFromPieceInstance[] = expectedPackages.map((pkg) => ({ + ...pkg, + partInstanceId: this.PieceInstanceImpl.partInstanceId, + pieceInstanceId: this.PieceInstanceImpl._id, + rundownId: this.PieceInstanceImpl.rundownId, + })) + + this.#expectedPackages = diffAndReturnLatestObjects( + this.#expectedPackagesWithChanges, + this.#expectedPackages, + newExpectedPackages, + mutateExpectedPackage + ) + } +} + +// nocommit - this is copied from elsewhere +function mutateExpectedPackage( + oldObj: ExpectedPackageDBFromPieceInstance, + newObj: ExpectedPackageDBFromPieceInstance +): ExpectedPackageDBFromPieceInstance { + return { + ...newObj, + // Retain the created property + created: oldObj.created, + } } diff --git a/packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts b/packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts index 7fcf051046..414b5f4c76 100644 --- a/packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts +++ b/packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts @@ -7,6 +7,8 @@ import { AnyBulkWriteOperation } from 'mongodb' import { JobContext } from '../../../jobs' import { PlayoutPartInstanceModelImpl } from './PlayoutPartInstanceModelImpl' import { PlayoutRundownModelImpl } from './PlayoutRundownModelImpl' +import { DocumentChangeTracker } from '../../../ingest/model/implementation/DocumentChangeTracker' +import { ExpectedPackageDB, ExpectedPackageDBType } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' /** * Save any changed AdlibTesting Segments @@ -58,12 +60,13 @@ export async function writeAdlibTestingSegments( * @param context Context from the job queue * @param partInstances Map of PartInstances to check for changes or deletion */ -export function writePartInstancesAndPieceInstances( +export function writePartInstancesAndPieceInstancesAndExpectedPackages( context: JobContext, partInstances: Map -): [Promise, Promise] { +): [Promise, Promise, Promise] { const partInstanceOps: AnyBulkWriteOperation[] = [] const pieceInstanceOps: AnyBulkWriteOperation[] = [] + const expectedPackagesChanges = new DocumentChangeTracker() const deletedPartInstanceIds: PartInstanceId[] = [] const deletedPieceInstanceIds: PieceInstanceId[] = [] @@ -93,6 +96,8 @@ export function writePartInstancesAndPieceInstances( upsert: true, }, }) + + expectedPackagesChanges.addChanges(pieceInstance.expectedPackagesChanges, false) } } @@ -100,6 +105,8 @@ export function writePartInstancesAndPieceInstances( } } + const expectedPackagesOps = expectedPackagesChanges.generateWriteOps() + // Delete any removed PartInstances if (deletedPartInstanceIds.length) { partInstanceOps.push({ @@ -116,6 +123,15 @@ export function writePartInstancesAndPieceInstances( }, }, }) + + expectedPackagesOps.push({ + deleteMany: { + filter: { + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, + partInstanceId: { $in: deletedPartInstanceIds }, + }, + }, + }) } // Delete any removed PieceInstances @@ -127,6 +143,14 @@ export function writePartInstancesAndPieceInstances( }, }, }) + expectedPackagesOps.push({ + deleteMany: { + filter: { + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, + pieceInstanceId: { $in: deletedPieceInstanceIds }, + }, + }, + }) } return [ @@ -134,5 +158,8 @@ export function writePartInstancesAndPieceInstances( pieceInstanceOps.length ? context.directCollections.PieceInstances.bulkWrite(pieceInstanceOps) : Promise.resolve(), + expectedPackagesOps.length + ? context.directCollections.ExpectedPackages.bulkWrite(expectedPackagesOps) + : Promise.resolve(), ] } diff --git a/packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts b/packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts index 5d756c5600..b5b957a47b 100644 --- a/packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts +++ b/packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts @@ -4,7 +4,7 @@ import { protectString } from '@sofie-automation/corelib/dist/protectedString' import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' import { PlayoutRundownModelImpl } from '../PlayoutRundownModelImpl' import { setupDefaultJobEnvironment } from '../../../../__mocks__/context' -import { writePartInstancesAndPieceInstances, writeAdlibTestingSegments } from '../SavePlayoutModel' +import { writePartInstancesAndPieceInstancesAndExpectedPackages, writeAdlibTestingSegments } from '../SavePlayoutModel' import { PlayoutPartInstanceModelImpl } from '../PlayoutPartInstanceModelImpl' import { PartInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' @@ -122,7 +122,7 @@ describe('SavePlayoutModel', () => { it('no PartInstances', async () => { const context = setupDefaultJobEnvironment() - await Promise.all(writePartInstancesAndPieceInstances(context, new Map())) + await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, new Map())) expect(context.mockCollections.PartInstances.operations).toHaveLength(0) expect(context.mockCollections.PieceInstances.operations).toHaveLength(0) @@ -135,7 +135,7 @@ describe('SavePlayoutModel', () => { partInstances.set(protectString('id0'), null) partInstances.set(protectString('id1'), null) - await Promise.all(writePartInstancesAndPieceInstances(context, partInstances)) + await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, partInstances)) expect(context.mockCollections.PartInstances.operations).toHaveLength(2) expect(context.mockCollections.PartInstances.operations).toMatchInlineSnapshot(` @@ -197,7 +197,7 @@ describe('SavePlayoutModel', () => { const partInstances = new Map() partInstances.set(protectString('id0'), partInstanceModel) - await Promise.all(writePartInstancesAndPieceInstances(context, partInstances)) + await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, partInstances)) expect(context.mockCollections.PartInstances.operations).toHaveLength(0) @@ -237,7 +237,7 @@ describe('SavePlayoutModel', () => { const partInstances = new Map() partInstances.set(protectString('id0'), partInstanceModel) - await Promise.all(writePartInstancesAndPieceInstances(context, partInstances)) + await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, partInstances)) expect(context.mockCollections.PartInstances.operations).toHaveLength(2) expect(context.mockCollections.PartInstances.operations).toMatchInlineSnapshot(` @@ -273,7 +273,7 @@ describe('SavePlayoutModel', () => { const partInstances = new Map() partInstances.set(protectString('id0'), partInstanceModel) - await Promise.all(writePartInstancesAndPieceInstances(context, partInstances)) + await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, partInstances)) expect(context.mockCollections.PartInstances.operations).toHaveLength(0) @@ -319,7 +319,7 @@ describe('SavePlayoutModel', () => { partInstances.set(protectString('id0'), partInstanceModel) partInstances.set(protectString('id1'), null) - await Promise.all(writePartInstancesAndPieceInstances(context, partInstances)) + await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, partInstances)) expect(context.mockCollections.PartInstances.operations).toHaveLength(3) expect(context.mockCollections.PartInstances.operations).toMatchInlineSnapshot(` diff --git a/packages/job-worker/src/playout/snapshot.ts b/packages/job-worker/src/playout/snapshot.ts index 9b479f0968..c30c430d32 100644 --- a/packages/job-worker/src/playout/snapshot.ts +++ b/packages/job-worker/src/playout/snapshot.ts @@ -342,6 +342,9 @@ export async function handleRestorePlaylistSnapshot( logger.warn(`Unexpected ExpectedPackage in snapshot: ${JSON.stringify(expectedPackage)}`) break } + case ExpectedPackageDBType.PIECE_INSTANCE: + // nocommit implement me! + break default: assertNever(expectedPackage) From 67e46533beec6aa3f7627f47cae826ea7da5d395 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 29 Aug 2024 16:47:17 +0100 Subject: [PATCH 02/22] wip: start propogating through the model --- .../SyncIngestUpdateToPartInstanceContext.ts | 4 +- .../playout/model/PlayoutPartInstanceModel.ts | 20 +++- .../model/implementation/PlayoutModelImpl.ts | 2 +- .../PlayoutPartInstanceModelImpl.ts | 104 +++++++++++++++--- .../PlayoutPieceInstanceModelImpl.ts | 12 +- 5 files changed, 118 insertions(+), 24 deletions(-) diff --git a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts index 7d28375728..463b26944c 100644 --- a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts +++ b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts @@ -94,11 +94,13 @@ export class SyncIngestUpdateToPartInstanceContext )[0] : proposedPieceInstance.piece + const newExpectedPackages = modifiedPiece ? piece.expectedPackages : proposedPieceInstance.expectedPackages // nocommit - these need solving + const newPieceInstance: ReadonlyDeep = { ...proposedPieceInstance, piece: piece, } - this.partInstance.mergeOrInsertPieceInstance(newPieceInstance) + this.partInstance.mergeOrInsertPieceInstance(newPieceInstance, newExpectedPackages) return convertPieceInstanceToBlueprints(newPieceInstance) } diff --git a/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts b/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts index b8d8409180..87afd1f23c 100644 --- a/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts @@ -6,6 +6,10 @@ import { PartNote } from '@sofie-automation/corelib/dist/dataModel/Notes' import { IBlueprintMutatablePart, PieceLifespan, Time } from '@sofie-automation/blueprints-integration' import { PartCalculatedTimings } from '@sofie-automation/corelib/dist/playout/timings' import { PlayoutPieceInstanceModel } from './PlayoutPieceInstanceModel' +import { + ExpectedPackageDBFromPiece, + ExpectedPackageDBFromPieceInstance, +} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' /** * Token returned when making a backup copy of a PlayoutPartInstanceModel @@ -64,11 +68,13 @@ export interface PlayoutPartInstanceModel { * Insert a Piece into this PartInstance as an adlibbed PieceInstance * @param piece Piece to insert * @param fromAdlibId Id of the source Adlib, if any + * @param pieceExpectedPackages ExpectedPackages for the Piece * @returns The inserted PlayoutPieceInstanceModel */ insertAdlibbedPiece( piece: Omit, - fromAdlibId: PieceId | undefined + fromAdlibId: PieceId | undefined, + pieceExpectedPackages: ExpectedPackageDBFromPiece[] ): PlayoutPieceInstanceModel /** @@ -83,9 +89,13 @@ export interface PlayoutPartInstanceModel { * Insert a Piece as if it were originally planned at the time of ingest * This is a weird operation to have for playout, but it is a needed part of the SyncIngestChanges flow * @param piece Piece to insert into this PartInstance + * @param pieceExpectedPackages ExpectedPackages for the Piece * @returns The inserted PlayoutPieceInstanceModel */ - insertPlannedPiece(piece: Omit): PlayoutPieceInstanceModel + insertPlannedPiece( + piece: Omit, + pieceExpectedPackages: ExpectedPackageDBFromPiece[] + ): PlayoutPieceInstanceModel /** * Insert a virtual adlib Piece into this PartInstance @@ -137,9 +147,13 @@ export interface PlayoutPartInstanceModel { * If there is an existing PieceInstance with the same id, it will be merged onto that * Note: this can replace any playout owned properties too * @param pieceInstance Replacement PieceInstance to use + * @param expectedPackages Replacement ExpectedPackages for the PieceInstance * @returns The inserted PlayoutPieceInstanceModel */ - mergeOrInsertPieceInstance(pieceInstance: ReadonlyDeep): PlayoutPieceInstanceModel + mergeOrInsertPieceInstance( + pieceInstance: ReadonlyDeep, + expectedPackages: ExpectedPackageDBFromPieceInstance[] + ): PlayoutPieceInstanceModel /** * Mark this PartInstance as being orphaned diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts index 9c11ddb5dc..a610535894 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts @@ -406,7 +406,7 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou }, } - const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, [], true) + const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, [], [], true) partInstance.recalculateExpectedDurationWithTransition() this.allPartInstances.set(newPartInstance._id, partInstance) diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts index a152084f08..57c9036527 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts @@ -1,4 +1,9 @@ -import { PieceId, PieceInstanceId, RundownPlaylistActivationId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { + ExpectedPackageId, + PieceId, + PieceInstanceId, + RundownPlaylistActivationId, +} from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' import { @@ -7,7 +12,7 @@ import { PieceInstance, PieceInstancePiece, } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' -import { clone, getRandomId } from '@sofie-automation/corelib/dist/lib' +import { clone, getRandomId, omit } from '@sofie-automation/corelib/dist/lib' import { getCurrentTime } from '../../../lib' import { setupPieceInstanceInfiniteProperties } from '../../pieces' import { @@ -29,10 +34,17 @@ import { EmptyPieceTimelineObjectsBlob } from '@sofie-automation/corelib/dist/da import _ = require('underscore') import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { IBlueprintMutatablePartSampleKeys } from '../../../blueprints/context/lib' +import { + ExpectedPackageDBFromPiece, + ExpectedPackageDBFromPieceInstance, + ExpectedPackageDBType, +} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' interface PlayoutPieceInstanceModelSnapshotImpl { PieceInstance: PieceInstance + ExpectedPackages: ExpectedPackageDBFromPieceInstance[] HasChanges: boolean + ExpectedPackageChanges: ExpectedPackageId[] } class PlayoutPartInstanceModelSnapshotImpl implements PlayoutPartInstanceModelSnapshot { readonly __isPlayoutPartInstanceModelBackup = true @@ -52,7 +64,9 @@ class PlayoutPartInstanceModelSnapshotImpl implements PlayoutPartInstanceModelSn if (pieceInstance) { pieceInstances.set(pieceInstanceId, { PieceInstance: clone(pieceInstance.PieceInstanceImpl), + ExpectedPackages: clone(pieceInstance.expectedPackages), HasChanges: pieceInstance.HasChanges, + ExpectedPackageChanges: Array.from(pieceInstance.ExpectedPackagesWithChanges), }) } else { pieceInstances.set(pieceInstanceId, null) @@ -154,13 +168,29 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { return result } - constructor(partInstance: DBPartInstance, pieceInstances: PieceInstance[], hasChanges: boolean) { + constructor( + partInstance: DBPartInstance, + pieceInstances: PieceInstance[], + expectedPackages: ExpectedPackageDBFromPieceInstance[], + hasChanges: boolean + ) { this.partInstanceImpl = partInstance this.#partInstanceHasChanges = hasChanges this.pieceInstancesImpl = new Map() for (const pieceInstance of pieceInstances) { - this.pieceInstancesImpl.set(pieceInstance._id, new PlayoutPieceInstanceModelImpl(pieceInstance, hasChanges)) + // nocommit - avoid multiple iterations? + const pieceInstancePackages = expectedPackages.filter((p) => p.pieceInstanceId === pieceInstance._id) + + this.pieceInstancesImpl.set( + pieceInstance._id, + new PlayoutPieceInstanceModelImpl( + pieceInstance, + pieceInstancePackages, + hasChanges, + hasChanges ? pieceInstancePackages.map((p) => p._id) : null + ) + ) } } @@ -185,7 +215,12 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { if (pieceInstance) { this.pieceInstancesImpl.set( pieceInstanceId, - new PlayoutPieceInstanceModelImpl(pieceInstance.PieceInstance, pieceInstance.HasChanges) + new PlayoutPieceInstanceModelImpl( + pieceInstance.PieceInstance, + pieceInstance.ExpectedPackages, + pieceInstance.HasChanges, + pieceInstance.ExpectedPackageChanges + ) ) } else { this.pieceInstancesImpl.set(pieceInstanceId, null) @@ -207,7 +242,8 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { insertAdlibbedPiece( piece: Omit, - fromAdlibId: PieceId | undefined + fromAdlibId: PieceId | undefined, + pieceExpectedPackages: ExpectedPackageDBFromPiece[] ): PlayoutPieceInstanceModel { const pieceInstance: PieceInstance = { _id: protectString(`${this.partInstance._id}_${piece._id}`), @@ -231,7 +267,14 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { setupPieceInstanceInfiniteProperties(pieceInstance) - const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(pieceInstance, true) + const expectedPackages = this.#convertExpectedPackagesForPieceInstance(pieceInstance, pieceExpectedPackages) + + const pieceInstanceModel = new PlayoutPieceInstanceModelImpl( + pieceInstance, + expectedPackages, + true, + expectedPackages.map((p) => p._id) + ) this.pieceInstancesImpl.set(pieceInstance._id, pieceInstanceModel) return pieceInstanceModel @@ -271,13 +314,32 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { plannedStoppedPlayback: extendPieceInstance.pieceInstance.plannedStoppedPlayback, } - const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(newInstance, true) + // Don't preserve any ExpectedPackages, the existing ones will suffice + const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(newInstance, [], true, null) this.pieceInstancesImpl.set(newInstance._id, pieceInstanceModel) return pieceInstanceModel } - insertPlannedPiece(piece: Omit): PlayoutPieceInstanceModel { + #convertExpectedPackagesForPieceInstance( + pieceInstance: ReadonlyDeep, + expectedPackages: ExpectedPackageDBFromPiece[] + ): ExpectedPackageDBFromPieceInstance[] { + return expectedPackages.map((p) => ({ + ...omit(p, 'pieceId', 'partId'), + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, + partInstanceId: this.partInstance._id, + pieceInstanceId: pieceInstance._id, + segmentId: this.partInstance.segmentId, + rundownId: this.partInstance.rundownId, + pieceId: null, + })) + } + + insertPlannedPiece( + piece: Omit, + pieceExpectedPackages: ExpectedPackageDBFromPiece[] + ): PlayoutPieceInstanceModel { const pieceInstanceId = getPieceInstanceIdForPiece(this.partInstance._id, piece._id) if (this.pieceInstancesImpl.has(pieceInstanceId)) throw new Error(`PieceInstance "${pieceInstanceId}" already exists`) @@ -296,7 +358,14 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { // Ensure the infinite-ness is setup correctly setupPieceInstanceInfiniteProperties(newPieceInstance) - const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(newPieceInstance, true) + const expectedPackages = this.#convertExpectedPackagesForPieceInstance(newPieceInstance, pieceExpectedPackages) + + const pieceInstanceModel = new PlayoutPieceInstanceModelImpl( + newPieceInstance, + expectedPackages, + true, + expectedPackages.map((p) => p._id) + ) this.pieceInstancesImpl.set(pieceInstanceId, pieceInstanceModel) return pieceInstanceModel @@ -334,7 +403,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { } setupPieceInstanceInfiniteProperties(newPieceInstance) - const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(newPieceInstance, true) + const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(newPieceInstance, [], true, null) this.pieceInstancesImpl.set(newPieceInstance._id, pieceInstanceModel) return pieceInstanceModel @@ -398,16 +467,25 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { } } - mergeOrInsertPieceInstance(doc: ReadonlyDeep): PlayoutPieceInstanceModel { + mergeOrInsertPieceInstance( + doc: ReadonlyDeep, + expectedPackages: ExpectedPackageDBFromPieceInstance[] + ): PlayoutPieceInstanceModel { // Future: this should do some validation of the new PieceInstance const existingPieceInstance = this.pieceInstancesImpl.get(doc._id) if (existingPieceInstance) { existingPieceInstance.mergeProperties(doc) + existingPieceInstance.setExpectedPackages(expectedPackages) return existingPieceInstance } else { - const newPieceInstance = new PlayoutPieceInstanceModelImpl(clone(doc), true) + const newPieceInstance = new PlayoutPieceInstanceModelImpl( + clone(doc), + expectedPackages, + true, + expectedPackages.map((p) => p._id) + ) this.pieceInstancesImpl.set(newPieceInstance.pieceInstance._id, newPieceInstance) return newPieceInstance } diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts index 5240978eea..161e53006f 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts @@ -20,7 +20,7 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel PieceInstanceImpl: PieceInstance #expectedPackages: ExpectedPackageDBFromPieceInstance[] - #expectedPackagesWithChanges = new Set() + ExpectedPackagesWithChanges = new Set() /** * Set/delete a value for this PieceInstance, and track that there are changes @@ -67,7 +67,7 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel */ get HasChanges(): boolean { // nocommit - should this be two properties? - return this.#hasChanges || this.#expectedPackagesWithChanges.size > 0 + return this.#hasChanges || this.ExpectedPackagesWithChanges.size > 0 } /** @@ -75,7 +75,7 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel */ clearChangedFlag(): void { this.#hasChanges = false - this.#expectedPackagesWithChanges.clear() + this.ExpectedPackagesWithChanges.clear() } get pieceInstance(): ReadonlyDeep { @@ -87,7 +87,7 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel } get expectedPackagesChanges(): DocumentChanges { - return getDocumentChanges(this.#expectedPackagesWithChanges, this.#expectedPackages) + return getDocumentChanges(this.ExpectedPackagesWithChanges, this.#expectedPackages) } constructor( @@ -99,7 +99,7 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel this.PieceInstanceImpl = pieceInstances this.#expectedPackages = expectedPackages this.#hasChanges = hasChanges - this.#expectedPackagesWithChanges = new Set(expectedPackagesWithChanges) + this.ExpectedPackagesWithChanges = new Set(expectedPackagesWithChanges) } /** @@ -178,7 +178,7 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel })) this.#expectedPackages = diffAndReturnLatestObjects( - this.#expectedPackagesWithChanges, + this.ExpectedPackagesWithChanges, this.#expectedPackages, newExpectedPackages, mutateExpectedPackage From a3e6106c9bff539fb2619221566a8c70d03749fa Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 2 Sep 2024 13:52:56 +0100 Subject: [PATCH 03/22] wip: some more chasing --- .../corelib/src/dataModel/ExpectedPackages.ts | 31 ++++++- .../corelib/src/dataModel/PieceInstance.ts | 6 ++ packages/corelib/src/playout/infinites.ts | 8 +- .../PartAndPieceInstanceActionService.ts | 2 +- .../src/ingest/syncChangesToPartInstance.ts | 6 +- packages/job-worker/src/playout/adlibJobs.ts | 6 +- packages/job-worker/src/playout/adlibUtils.ts | 4 +- packages/job-worker/src/playout/infinites.ts | 84 +++++++++++++++++-- .../src/playout/model/PlayoutModel.ts | 14 +++- .../playout/model/PlayoutPartInstanceModel.ts | 8 +- .../model/implementation/LoadPlayoutModel.ts | 26 ++++-- .../model/implementation/PlayoutModelImpl.ts | 20 +++-- .../PlayoutPartInstanceModelImpl.ts | 47 ++++++----- packages/job-worker/src/playout/setNext.ts | 2 +- 14 files changed, 203 insertions(+), 61 deletions(-) diff --git a/packages/corelib/src/dataModel/ExpectedPackages.ts b/packages/corelib/src/dataModel/ExpectedPackages.ts index 780b22b68a..9abb21d391 100644 --- a/packages/corelib/src/dataModel/ExpectedPackages.ts +++ b/packages/corelib/src/dataModel/ExpectedPackages.ts @@ -1,6 +1,6 @@ import { ExpectedPackage, Time } from '@sofie-automation/blueprints-integration' import { protectString } from '../protectedString' -import { getHash, hashObj } from '../lib' +import { getHash, hashObj, omit } from '../lib' import { AdLibActionId, BucketAdLibActionId, @@ -17,6 +17,7 @@ import { StudioId, } from './Ids' import { ReadonlyDeep } from 'type-fest' +import { DBPartInstance } from './PartInstance' /* Expected Packages are created from Pieces in the rundown. @@ -176,3 +177,31 @@ export function getExpectedPackageId( ): ExpectedPackageId { return protectString(`${ownerId}_${getHash(localExpectedPackageId)}`) } + +export function convertPieceExpectedPackageToPieceInstance( + expectedPackage: ReadonlyDeep, + pieceInstanceId: PieceInstanceId, + partInstance: ReadonlyDeep +): ExpectedPackageDBFromPieceInstance { + if (expectedPackage.fromPieceType === ExpectedPackageDBType.PIECE_INSTANCE) { + return { + ...expectedPackage, + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, + partInstanceId: partInstance._id, + pieceInstanceId: pieceInstanceId, + segmentId: partInstance.segmentId, + rundownId: partInstance.rundownId, + pieceId: null, + } + } else { + return { + ...omit(expectedPackage, 'pieceId', 'partId'), + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, + partInstanceId: partInstance._id, + pieceInstanceId: pieceInstanceId, + segmentId: partInstance.segmentId, + rundownId: partInstance.rundownId, + pieceId: null, + } + } +} diff --git a/packages/corelib/src/dataModel/PieceInstance.ts b/packages/corelib/src/dataModel/PieceInstance.ts index fc8f3f9db8..8adb65c82e 100644 --- a/packages/corelib/src/dataModel/PieceInstance.ts +++ b/packages/corelib/src/dataModel/PieceInstance.ts @@ -11,6 +11,7 @@ import { import { Piece } from './Piece' import { omit } from '../lib' import { ReadonlyDeep } from 'type-fest' +import { ExpectedPackageDBFromPiece, ExpectedPackageDBFromPieceInstance } from './ExpectedPackages' export type PieceInstancePiece = Omit @@ -93,6 +94,11 @@ export interface ResolvedPieceInstance { timelinePriority: number } +export interface PieceInstanceWithExpectedPackages { + pieceInstance: PieceInstance + expectedPackages: ExpectedPackageDBFromPieceInstance[] | ExpectedPackageDBFromPiece[] +} + export function omitPiecePropertiesForInstance(piece: Piece | PieceInstancePiece): PieceInstancePiece { return omit(piece as Piece, 'startRundownId', 'startSegmentId') } diff --git a/packages/corelib/src/playout/infinites.ts b/packages/corelib/src/playout/infinites.ts index eac8b92f5a..1136fde7fc 100644 --- a/packages/corelib/src/playout/infinites.ts +++ b/packages/corelib/src/playout/infinites.ts @@ -258,11 +258,9 @@ export function getPlayheadTrackingInfinitesForPart( return undefined } - return flatten( - Array.from(piecesOnSourceLayers.values()).map((ps) => { - return _.compact(Object.values(ps as any).map(rewrapInstance)) - }) - ) + return Array.from(piecesOnSourceLayers.values()).flatMap((ps) => { + return _.compact(Object.values(ps as any).map(rewrapInstance)) + }) } function markPieceInstanceAsContinuation(previousInstance: ReadonlyDeep, instance: PieceInstance) { diff --git a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts index 6d35a6bf01..c721dc9c4d 100644 --- a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts +++ b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts @@ -262,7 +262,7 @@ export class PartAndPieceInstanceActionService { piece._id = getRandomId() // Make id random, as postProcessPieces is too predictable (for ingest) // Do the work - const newPieceInstance = partInstance.insertAdlibbedPiece(piece, undefined) + const newPieceInstance = partInstance.insertAdlibbedPiece(piece, undefined, piece.expectedPackages ?? []) if (part === 'current') { this.currentPartState = Math.max(this.currentPartState, ActionPartChange.SAFE_CHANGE) diff --git a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts index 4c3981c4ca..df913ee379 100644 --- a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts +++ b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts @@ -132,7 +132,7 @@ export async function syncChangesToPartInstances( pieceInstances: pieceInstancesInPart.map((p) => convertPieceInstanceToBlueprints(p.pieceInstance)), } - const proposedPieceInstances = getPieceInstancesForPart( + const proposedPieceInstances = await getPieceInstancesForPart( context, playoutModel, previousPartInstance, @@ -154,7 +154,9 @@ export async function syncChangesToPartInstances( const newResultData: BlueprintSyncIngestNewData = { part: newPart ? convertPartToBlueprints(newPart) : undefined, - pieceInstances: proposedPieceInstances.map(convertPieceInstanceToBlueprints), + pieceInstances: proposedPieceInstances.map((p) => + convertPieceInstanceToBlueprints(p.pieceInstance, p.expectedPackages) + ), adLibPieces: newPart && ingestPart ? ingestPart.adLibPieces.map(convertAdLibPieceToBlueprints) : [], actions: newPart && ingestPart ? ingestPart.adLibActions.map(convertAdLibActionToBlueprints) : [], referencedAdlibs: referencedAdlibs, diff --git a/packages/job-worker/src/playout/adlibJobs.ts b/packages/job-worker/src/playout/adlibJobs.ts index 31eeb8382c..d2506ef434 100644 --- a/packages/job-worker/src/playout/adlibJobs.ts +++ b/packages/job-worker/src/playout/adlibJobs.ts @@ -142,7 +142,11 @@ async function pieceTakeNowAsAdlib( | undefined ): Promise { const genericAdlibPiece = convertAdLibToGenericPiece(pieceToCopy, false) - /*const newPieceInstance = */ currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, pieceToCopy._id) + /*const newPieceInstance = */ currentPartInstance.insertAdlibbedPiece( + genericAdlibPiece, + pieceToCopy._id, + genericAdlibPiece.expectedPackages ?? [] + ) // Disable the original piece if from the same Part if ( diff --git a/packages/job-worker/src/playout/adlibUtils.ts b/packages/job-worker/src/playout/adlibUtils.ts index 1aea41cc52..ac00477dce 100644 --- a/packages/job-worker/src/playout/adlibUtils.ts +++ b/packages/job-worker/src/playout/adlibUtils.ts @@ -63,7 +63,7 @@ export async function innerStartOrQueueAdLibPiece( // syncPlayheadInfinitesForNextPartInstance is handled by setNextPart } else { const genericAdlibPiece = convertAdLibToGenericPiece(adLibPiece, false) - currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, adLibPiece._id) + currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, adLibPiece._id, adLibPiece.expectedPackages) await syncPlayheadInfinitesForNextPartInstance( context, @@ -225,7 +225,7 @@ export async function insertQueuedPartWithPieces( // Find any rundown defined infinites that we should inherit const possiblePieces = await fetchPiecesThatMayBeActiveForPart(context, playoutModel, undefined, newPartFull) - const infinitePieceInstances = getPieceInstancesForPart( + const infinitePieceInstances = await getPieceInstancesForPart( context, playoutModel, currentPartInstance, diff --git a/packages/job-worker/src/playout/infinites.ts b/packages/job-worker/src/playout/infinites.ts index 5924cb4a5c..ecd429baac 100644 --- a/packages/job-worker/src/playout/infinites.ts +++ b/packages/job-worker/src/playout/infinites.ts @@ -1,8 +1,11 @@ -import { PartInstanceId, RundownId, ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { PartInstanceId, PieceId, RundownId, ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' -import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' +import { + PieceInstance, + PieceInstanceWithExpectedPackages, +} from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { getPieceInstancesForPart as libgetPieceInstancesForPart, getPlayheadTrackingInfinitesForPart as libgetPlayheadTrackingInfinitesForPart, @@ -16,13 +19,19 @@ import { PlayoutModel } from './model/PlayoutModel' import { PlayoutPartInstanceModel } from './model/PlayoutPartInstanceModel' import { PlayoutSegmentModel } from './model/PlayoutSegmentModel' import { getCurrentTime } from '../lib' -import { flatten } from '@sofie-automation/corelib/dist/lib' +import { clone, flatten, groupByToMap, normalizeArrayToMapFunc } from '@sofie-automation/corelib/dist/lib' import _ = require('underscore') import { IngestModelReadonly } from '../ingest/model/IngestModel' import { SegmentOrphanedReason } from '@sofie-automation/corelib/dist/dataModel/Segment' import { sortRundownIDsInPlaylist } from '@sofie-automation/corelib/dist/playout/playlist' import { mongoWhere } from '@sofie-automation/corelib/dist/mongo' import { PlayoutRundownModel } from './model/PlayoutRundownModel' +import { + ExpectedPackageDBFromPiece, + ExpectedPackageDBFromPieceInstance, + ExpectedPackageDBType, +} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { PlayoutPieceInstanceModel } from './model/PlayoutPieceInstanceModel' /** When we crop a piece, set the piece as "it has definitely ended" this far into the future. */ export const DEFINITELY_ENDED_FUTURE_DURATION = 1 * 1000 @@ -280,7 +289,20 @@ export async function syncPlayheadInfinitesForNextPartInstance( false ) - toPartInstance.replaceInfinitesFromPreviousPlayhead(infinites) + const playingPieceInstancesMap = normalizeArrayToMapFunc( + fromPartInstance.pieceInstances, + (o) => o.pieceInstance.piece._id + ) + const wrappedInfinites = infinites.map((pieceInstance): PieceInstanceWithExpectedPackages => { + const expectedPackages = playingPieceInstancesMap.get(pieceInstance.piece._id)?.expectedPackages + + return { + pieceInstance: pieceInstance, + expectedPackages: clone(expectedPackages ?? []), + } + }) + + toPartInstance.replaceInfinitesFromPreviousPlayhead(wrappedInfinites) } if (span) span.end() } @@ -296,7 +318,7 @@ export async function syncPlayheadInfinitesForNextPartInstance( * @param newInstanceId Id of the PartInstance * @returns Array of PieceInstances for the specified PartInstance */ -export function getPieceInstancesForPart( +export async function getPieceInstancesForPart( context: JobContext, playoutModel: PlayoutModel, playingPartInstance: PlayoutPartInstanceModel | null, @@ -304,7 +326,7 @@ export function getPieceInstancesForPart( part: ReadonlyDeep, possiblePieces: ReadonlyDeep[], newInstanceId: PartInstanceId -): PieceInstance[] { +): Promise { const span = context.startSpan('getPieceInstancesForPart') const { partsToReceiveOnSegmentEndFrom, segmentsToReceiveOnRundownEndFrom, rundownsToReceiveOnShowStyleEndFrom } = getIdsBeforeThisPart(context, playoutModel, part) @@ -354,6 +376,54 @@ export function getPieceInstancesForPart( nextPartIsAfterCurrentPart, false ) + + // Pair the pieceInstances with their expectedPackages + const resWithExpectedPackages = wrapPieceInstancesWithExpectedPackages(context, playingPieceInstances, res) + if (span) span.end() - return res + return resWithExpectedPackages +} + +async function wrapPieceInstancesWithExpectedPackages( + context: JobContext, + playingPieceInstances: PlayoutPieceInstanceModel[], + res: PieceInstance[] +) { + const playingPieceInstanceMap = normalizeArrayToMapFunc(playingPieceInstances, (o) => o.pieceInstance._id) + const pieceIdsToLoad = new Map() + + // Pair the PieceInstances with their expectedPackages + // The side effects of this is a little dirty, but avoids a lot of extra loops + const resWithExpectedPackages = res.map((pieceInstance) => { + const expectedPackages = playingPieceInstanceMap.get(pieceInstance._id)?.expectedPackages + const pieceInstanceWithExpectedPackages: PieceInstanceWithExpectedPackages = { + pieceInstance: pieceInstance, + expectedPackages: clone(expectedPackages ?? []), + } + + if (!expectedPackages) { + // Mark this piece as needing instances to be loaded + pieceIdsToLoad.set(pieceInstance.piece._id, pieceInstanceWithExpectedPackages) + } + + return pieceInstanceWithExpectedPackages + }) + + // Any Pieces which were auto-wrapped don't have their expectedPackages loaded yet + if (pieceIdsToLoad.size > 0) { + const expectedPackages = (await context.directCollections.ExpectedPackages.findFetch({ + fromPieceType: ExpectedPackageDBType.PIECE, + pieceId: { $in: Array.from(pieceIdsToLoad.keys()) }, + })) as ExpectedPackageDBFromPiece[] + const expectedPackagesByPieceId = groupByToMap(expectedPackages, 'pieceId') + + for (const [pieceId, expectedPackages] of expectedPackagesByPieceId.entries()) { + const pieceInstance = pieceIdsToLoad.get(pieceId) + if (pieceInstance) { + pieceInstance.expectedPackages = expectedPackages + } + } + } + + return resWithExpectedPackages } diff --git a/packages/job-worker/src/playout/model/PlayoutModel.ts b/packages/job-worker/src/playout/model/PlayoutModel.ts index 93390558ee..38273807ff 100644 --- a/packages/job-worker/src/playout/model/PlayoutModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutModel.ts @@ -18,7 +18,10 @@ import { import { ReadonlyDeep } from 'type-fest' import { StudioPlayoutModelBase, StudioPlayoutModelBaseReadonly } from '../../studio/model/StudioPlayoutModel' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' -import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' +import { + PieceInstancePiece, + PieceInstanceWithExpectedPackages, +} from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { PlaylistLock } from '../../jobs/lock' import { PlayoutRundownModel } from './PlayoutRundownModel' import { PlayoutSegmentModel } from './PlayoutSegmentModel' @@ -203,9 +206,9 @@ export interface PlayoutModel extends PlayoutModelReadonly, StudioPlayoutModelBa */ createAdlibbedPartInstance( part: Omit, - pieces: Omit[], + pieces: Omit[], // nocommit - expectedPackages? fromAdlibId: PieceId | undefined, - infinitePieceInstances: PieceInstance[] + infinitePieceInstances: PieceInstanceWithExpectedPackages[] ): PlayoutPartInstanceModel /** @@ -215,7 +218,10 @@ export interface PlayoutModel extends PlayoutModelReadonly, StudioPlayoutModelBa * @param pieceInstances All the PieceInstances to insert * @returns The inserted PlayoutPartInstanceModel */ - createInstanceForPart(nextPart: ReadonlyDeep, pieceInstances: PieceInstance[]): PlayoutPartInstanceModel + createInstanceForPart( + nextPart: ReadonlyDeep, + pieceInstances: PieceInstanceWithExpectedPackages[] + ): PlayoutPartInstanceModel /** * Insert an adlibbed PartInstance into the AdlibTesting Segment of a Rundown in this RundownPlaylist diff --git a/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts b/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts index 87afd1f23c..3114f64c1b 100644 --- a/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts @@ -1,7 +1,11 @@ import { PieceId, PieceInstanceId, RundownPlaylistActivationId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' -import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' +import { + PieceInstance, + PieceInstancePiece, + PieceInstanceWithExpectedPackages, +} from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { PartNote } from '@sofie-automation/corelib/dist/dataModel/Notes' import { IBlueprintMutatablePart, PieceLifespan, Time } from '@sofie-automation/blueprints-integration' import { PartCalculatedTimings } from '@sofie-automation/corelib/dist/playout/timings' @@ -140,7 +144,7 @@ export interface PlayoutPartInstanceModel { * This allows them to be replaced without embedding the infinite logic inside the model * @param pieceInstances New infinite pieces from previous playhead */ - replaceInfinitesFromPreviousPlayhead(pieceInstances: PieceInstance[]): void + replaceInfinitesFromPreviousPlayhead(pieceInstances: PieceInstanceWithExpectedPackages[]): void /** * Merge a PieceInstance with a new version, or insert as a new PieceInstance. diff --git a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts index 769c911a7e..3eaaf5e46a 100644 --- a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts +++ b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts @@ -22,6 +22,10 @@ import { PeripheralDevice } from '@sofie-automation/corelib/dist/dataModel/Perip import { PlayoutModel, PlayoutModelPreInit } from '../PlayoutModel' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { RundownBaselineObj } from '@sofie-automation/corelib/dist/dataModel/RundownBaselineObj' +import { + ExpectedPackageDBFromPieceInstance, + ExpectedPackageDBType, +} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' /** * Load a PlayoutModelPreInit for the given RundownPlaylist @@ -295,20 +299,30 @@ async function loadPartInstances( // Filter the PieceInstances to the activationId, if possible pieceInstancesSelector.playlistActivationId = playlist.activationId || { $exists: false } - const [partInstances, pieceInstances] = await Promise.all([ + const [partInstances, pieceInstances, expectedPackages] = await Promise.all([ partInstancesCollection, context.directCollections.PieceInstances.findFetch(pieceInstancesSelector), + context.directCollections.ExpectedPackages.findFetch({ + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, + // Future: this could be optimised to only limit the loading to only those which match `activationId` + partInstanceId: { $in: selectedPartInstanceIds }, + rundownId: { $in: rundownIds }, + }) as Promise, ]) const groupedPieceInstances = groupByToMap(pieceInstances, 'partInstanceId') + const groupedExpectedPackages = groupByToMap(expectedPackages, 'pieceInstanceId') const allPartInstances: PlayoutPartInstanceModelImpl[] = [] for (const partInstance of partInstances) { - const wrappedPartInstance = new PlayoutPartInstanceModelImpl( - partInstance, - groupedPieceInstances.get(partInstance._id) ?? [], - false - ) + const rawPieceInstances = groupedPieceInstances.get(partInstance._id) ?? [] + + const pieceInstancesAndPackages = rawPieceInstances.map((pieceInstance) => ({ + pieceInstance, + expectedPackages: groupedExpectedPackages.get(pieceInstance._id) ?? [], + })) + + const wrappedPartInstance = new PlayoutPartInstanceModelImpl(partInstance, pieceInstancesAndPackages, false) allPartInstances.push(wrappedPartInstance) } diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts index a610535894..7c293aef2b 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts @@ -23,8 +23,8 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' import { getPieceInstanceIdForPiece, - PieceInstance, PieceInstancePiece, + PieceInstanceWithExpectedPackages, } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { serializeTimelineBlob, @@ -291,8 +291,11 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou this.#playlistHasChanged = true } - #fixupPieceInstancesForPartInstance(partInstance: DBPartInstance, pieceInstances: PieceInstance[]): void { - for (const pieceInstance of pieceInstances) { + #fixupPieceInstancesForPartInstance( + partInstance: DBPartInstance, + pieceInstances: PieceInstanceWithExpectedPackages[] + ): void { + for (const { pieceInstance } of pieceInstances) { // Future: should these be PieceInstance already, or should that be handled here? pieceInstance._id = getPieceInstanceIdForPiece(partInstance._id, pieceInstance.piece._id) pieceInstance.partInstanceId = partInstance._id @@ -303,7 +306,7 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou part: Omit, pieces: Omit[], fromAdlibId: PieceId | undefined, - infinitePieceInstances: PieceInstance[] + infinitePieceInstances: PieceInstanceWithExpectedPackages[] ): PlayoutPartInstanceModel { const currentPartInstance = this.currentPartInstance if (!currentPartInstance) throw new Error('No currentPartInstance') @@ -329,7 +332,7 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, infinitePieceInstances, true) for (const piece of pieces) { - partInstance.insertAdlibbedPiece(piece, fromAdlibId) + partInstance.insertAdlibbedPiece(piece, fromAdlibId, piece.expectedPackages ?? []) } partInstance.recalculateExpectedDurationWithTransition() @@ -339,7 +342,10 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou return partInstance } - createInstanceForPart(nextPart: ReadonlyDeep, pieceInstances: PieceInstance[]): PlayoutPartInstanceModel { + createInstanceForPart( + nextPart: ReadonlyDeep, + pieceInstances: PieceInstanceWithExpectedPackages[] + ): PlayoutPartInstanceModel { const playlistActivationId = this.playlist.activationId if (!playlistActivationId) throw new Error(`Playlist is not active`) @@ -406,7 +412,7 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou }, } - const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, [], [], true) + const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, [], true) partInstance.recalculateExpectedDurationWithTransition() this.allPartInstances.set(newPartInstance._id, partInstance) diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts index 57c9036527..86e44d28d3 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts @@ -11,8 +11,9 @@ import { omitPiecePropertiesForInstance, PieceInstance, PieceInstancePiece, + PieceInstanceWithExpectedPackages, } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' -import { clone, getRandomId, omit } from '@sofie-automation/corelib/dist/lib' +import { clone, getRandomId } from '@sofie-automation/corelib/dist/lib' import { getCurrentTime } from '../../../lib' import { setupPieceInstanceInfiniteProperties } from '../../pieces' import { @@ -35,9 +36,9 @@ import _ = require('underscore') import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { IBlueprintMutatablePartSampleKeys } from '../../../blueprints/context/lib' import { + convertPieceExpectedPackageToPieceInstance, ExpectedPackageDBFromPiece, ExpectedPackageDBFromPieceInstance, - ExpectedPackageDBType, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' interface PlayoutPieceInstanceModelSnapshotImpl { @@ -170,25 +171,25 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { constructor( partInstance: DBPartInstance, - pieceInstances: PieceInstance[], - expectedPackages: ExpectedPackageDBFromPieceInstance[], + pieceInstances: PieceInstanceWithExpectedPackages[], hasChanges: boolean ) { this.partInstanceImpl = partInstance this.#partInstanceHasChanges = hasChanges this.pieceInstancesImpl = new Map() - for (const pieceInstance of pieceInstances) { - // nocommit - avoid multiple iterations? - const pieceInstancePackages = expectedPackages.filter((p) => p.pieceInstanceId === pieceInstance._id) + for (const { pieceInstance, expectedPackages } of pieceInstances) { + const expectedPackagesConverted = expectedPackages.map((p) => + convertPieceExpectedPackageToPieceInstance(p, pieceInstance._id, partInstance) + ) this.pieceInstancesImpl.set( pieceInstance._id, new PlayoutPieceInstanceModelImpl( pieceInstance, - pieceInstancePackages, + expectedPackagesConverted, hasChanges, - hasChanges ? pieceInstancePackages.map((p) => p._id) : null + hasChanges ? expectedPackagesConverted.map((p) => p._id) : null ) ) } @@ -323,17 +324,11 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { #convertExpectedPackagesForPieceInstance( pieceInstance: ReadonlyDeep, - expectedPackages: ExpectedPackageDBFromPiece[] + expectedPackages: ReadonlyDeep> ): ExpectedPackageDBFromPieceInstance[] { - return expectedPackages.map((p) => ({ - ...omit(p, 'pieceId', 'partId'), - fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, - partInstanceId: this.partInstance._id, - pieceInstanceId: pieceInstance._id, - segmentId: this.partInstance.segmentId, - rundownId: this.partInstance.rundownId, - pieceId: null, - })) + return expectedPackages.map((p) => + convertPieceExpectedPackageToPieceInstance(p, pieceInstance._id, this.partInstanceImpl) + ) } insertPlannedPiece( @@ -440,7 +435,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { return false } - replaceInfinitesFromPreviousPlayhead(pieceInstances: PieceInstance[]): void { + replaceInfinitesFromPreviousPlayhead(pieceInstances: PieceInstanceWithExpectedPackages[]): void { // Future: this should do some of the wrapping from a Piece into a PieceInstance // Remove old infinite pieces @@ -452,7 +447,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { } } - for (const pieceInstance of pieceInstances) { + for (const { pieceInstance, expectedPackages } of pieceInstances) { if (this.pieceInstancesImpl.get(pieceInstance._id)) throw new Error( `Cannot replace infinite PieceInstance "${pieceInstance._id}" as it replaces a non-infinite` @@ -463,7 +458,15 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { // Future: should this do any deeper validation of the PieceInstances? - this.pieceInstancesImpl.set(pieceInstance._id, new PlayoutPieceInstanceModelImpl(pieceInstance, true)) + this.pieceInstancesImpl.set( + pieceInstance._id, + new PlayoutPieceInstanceModelImpl( + pieceInstance, + this.#convertExpectedPackagesForPieceInstance(pieceInstance, expectedPackages), + true, + expectedPackages.map((p) => p._id) + ) + ) } } diff --git a/packages/job-worker/src/playout/setNext.ts b/packages/job-worker/src/playout/setNext.ts index ab6fcf7514..63a6c3d4f5 100644 --- a/packages/job-worker/src/playout/setNext.ts +++ b/packages/job-worker/src/playout/setNext.ts @@ -204,7 +204,7 @@ async function preparePartInstanceForPartBeingNexted( if (!rundown) throw new Error(`Could not find rundown ${nextPart.rundownId}`) const possiblePieces = await fetchPiecesThatMayBeActiveForPart(context, playoutModel, undefined, nextPart) - const newPieceInstances = getPieceInstancesForPart( + const newPieceInstances = await getPieceInstancesForPart( context, playoutModel, currentPartInstance, From 29f5dff563a779057189a09b3e36fd4e0466d3f6 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 3 Sep 2024 11:01:21 +0100 Subject: [PATCH 04/22] wip --- packages/corelib/src/dataModel/Piece.ts | 11 +- .../PartAndPieceInstanceActionService.ts | 4 +- .../job-worker/src/blueprints/postProcess.ts | 239 +++++++++++++++--- .../job-worker/src/ingest/expectedPackages.ts | 18 +- 4 files changed, 220 insertions(+), 52 deletions(-) diff --git a/packages/corelib/src/dataModel/Piece.ts b/packages/corelib/src/dataModel/Piece.ts index a340c45fff..fe5a42586d 100644 --- a/packages/corelib/src/dataModel/Piece.ts +++ b/packages/corelib/src/dataModel/Piece.ts @@ -43,9 +43,6 @@ export interface PieceGeneric extends Omit { content: SomeContent - /** A flag to signal that a given Piece has no content, and exists only as a marker on the timeline */ - virtual?: boolean - /** Stringified timelineObjects */ timelineObjectsString: PieceTimelineObjectsBlob } @@ -74,6 +71,14 @@ export interface Piece extends PieceGeneric, Omit export function deserializePieceTimelineObjectsBlob( diff --git a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts index c721dc9c4d..24550ad977 100644 --- a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts +++ b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts @@ -259,10 +259,10 @@ export class PartAndPieceInstanceActionService { partInstance.partInstance.part._id, part === 'current' )[0] - piece._id = getRandomId() // Make id random, as postProcessPieces is too predictable (for ingest) + piece.doc._id = getRandomId() // Make id random, as postProcessPieces is too predictable (for ingest) // Do the work - const newPieceInstance = partInstance.insertAdlibbedPiece(piece, undefined, piece.expectedPackages ?? []) + const newPieceInstance = partInstance.insertAdlibbedPiece(piece.doc, undefined, piece.expectedPackages ?? []) if (part === 'current') { this.currentPartState = Math.max(this.currentPartState, ActionPartChange.SAFE_CHANGE) diff --git a/packages/job-worker/src/blueprints/postProcess.ts b/packages/job-worker/src/blueprints/postProcess.ts index 6e9d2f8fca..ca922d9f8c 100644 --- a/packages/job-worker/src/blueprints/postProcess.ts +++ b/packages/job-worker/src/blueprints/postProcess.ts @@ -13,6 +13,7 @@ import { PieceLifespan, IBlueprintPieceType, ITranslatableMessage, + ExpectedPackage, } from '@sofie-automation/blueprints-integration' import { AdLibActionId, @@ -27,12 +28,13 @@ import { JobContext, ProcessedShowStyleCompound } from '../jobs' import { EmptyPieceTimelineObjectsBlob, Piece, + PieceExpectedPackage, serializePieceTimelineObjectsBlob, } from '@sofie-automation/corelib/dist/dataModel/Piece' import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { AdLibAction } from '@sofie-automation/corelib/dist/dataModel/AdlibAction' import { RundownBaselineAdLibAction } from '@sofie-automation/corelib/dist/dataModel/RundownBaselineAdLibAction' -import { ArrayElement, getHash, literal, omit } from '@sofie-automation/corelib/dist/lib' +import { ArrayElement, Complete, getHash, literal, omit } from '@sofie-automation/corelib/dist/lib' import { BucketAdLibAction } from '@sofie-automation/corelib/dist/dataModel/BucketAdLibAction' import { RundownImportVersions } from '@sofie-automation/corelib/dist/dataModel/Rundown' import { BucketAdLib, BucketAdLibIngestInfo } from '@sofie-automation/corelib/dist/dataModel/BucketAdLibPiece' @@ -58,6 +60,11 @@ function getIdHash(docType: string, usedIds: Map, uniqueId: stri } } +export interface PostProcessDoc { + doc: T + expectedPackages: ExpectedPackage.Any[] +} + /** * Process and validate some IBlueprintPiece into Piece * @param context Context from the job queue @@ -78,13 +85,13 @@ export function postProcessPieces( partId: PartId, allowNowForPiece: boolean, setInvalid?: boolean -): Piece[] { +): PostProcessDoc[] { const span = context.startSpan('blueprints.postProcess.postProcessPieces') const uniqueIds = new Map() const timelineUniqueIds = new Set() - const processedPieces = pieces.map((orgPiece: IBlueprintPiece) => { + const processedPieces = pieces.map((orgPiece: IBlueprintPiece): PostProcessDoc => { if (!orgPiece.externalId) throw new Error( `Error in blueprint "${blueprintId}" externalId not set for adlib piece in ${partId}! ("${orgPiece.name}")` @@ -96,10 +103,12 @@ export function postProcessPieces( `${rundownId}_${blueprintId}_${partId}_piece_${orgPiece.sourceLayerId}_${orgPiece.externalId}` ) - const piece: Piece = { + // Fill in ids of unnamed expectedPackages + const processedExpectedPackages = setDefaultIdOnExpectedPackages(orgPiece.expectedPackages) + + const piece: Complete = { pieceType: IBlueprintPieceType.Normal, - ...orgPiece, content: omit(orgPiece.content, 'timelineObjects'), _id: protectString(docId), @@ -108,6 +117,29 @@ export function postProcessPieces( startPartId: partId, invalid: setInvalid ?? false, timelineObjectsString: EmptyPieceTimelineObjectsBlob, + + virtual: orgPiece.virtual, + externalId: orgPiece.externalId, + sourceLayerId: orgPiece.sourceLayerId, + outputLayerId: orgPiece.outputLayerId, + enable: orgPiece.enable, + lifespan: orgPiece.lifespan, + extendOnHold: orgPiece.extendOnHold, + name: orgPiece.name, + privateData: orgPiece.privateData, + publicData: orgPiece.publicData, + + prerollDuration: orgPiece.prerollDuration, + postrollDuration: orgPiece.postrollDuration, + + toBeQueued: orgPiece.toBeQueued, + allowDirectPlay: orgPiece.allowDirectPlay, + expectedPlayoutItems: orgPiece.expectedPlayoutItems, + tags: orgPiece.tags, + hasSideEffects: orgPiece.hasSideEffects, + abSessions: orgPiece.abSessions, + notInVision: orgPiece.notInVision, + expectedPackages: processedExpectedPackages, // convertExpectedPackages(processedExpectedPackages), } if (piece.pieceType !== IBlueprintPieceType.Normal) { @@ -132,10 +164,10 @@ export function postProcessPieces( ) piece.timelineObjectsString = serializePieceTimelineObjectsBlob(timelineObjects) - // Fill in ids of unnamed expectedPackages - setDefaultIdOnExpectedPackages(piece.expectedPackages) - - return piece + return { + doc: piece, + expectedPackages: processedExpectedPackages, + } }) span?.end() @@ -222,7 +254,7 @@ export function postProcessAdLibPieces( rundownId: RundownId, partId: PartId | undefined, adLibPieces: Array -): AdLibPiece[] { +): PostProcessDoc[] { const span = context.startSpan('blueprints.postProcess.postProcessAdLibPieces') const uniqueIds = new Map() @@ -240,13 +272,46 @@ export function postProcessAdLibPieces( `${rundownId}_${blueprintId}_${partId}_adlib_piece_${orgAdlib.sourceLayerId}_${orgAdlib.externalId}` ) - const piece: AdLibPiece = { - ...orgAdlib, + // Fill in ids of unnamed expectedPackages + const processedExpectedPackages = setDefaultIdOnExpectedPackages(orgAdlib.expectedPackages) + + const piece: Complete = { content: omit(orgAdlib.content, 'timelineObjects'), _id: protectString(docId), rundownId: rundownId, partId: partId, timelineObjectsString: EmptyPieceTimelineObjectsBlob, + externalId: orgAdlib.externalId, + _rank: orgAdlib._rank, + + expectedPackages: processedExpectedPackages, // convertExpectedPackages(processedExpectedPackages), + expectedPlayoutItems: orgAdlib.expectedPlayoutItems, + + privateData: orgAdlib.privateData, + publicData: orgAdlib.publicData, + invalid: orgAdlib.invalid, + floated: orgAdlib.floated, + name: orgAdlib.name, + expectedDuration: orgAdlib.expectedDuration, + + currentPieceTags: orgAdlib.currentPieceTags, + nextPieceTags: orgAdlib.nextPieceTags, + uniquenessId: orgAdlib.uniquenessId, + invertOnAirState: orgAdlib.invertOnAirState, + + lifespan: orgAdlib.lifespan, + sourceLayerId: orgAdlib.sourceLayerId, + outputLayerId: orgAdlib.outputLayerId, + + prerollDuration: orgAdlib.prerollDuration, + postrollDuration: orgAdlib.postrollDuration, + toBeQueued: orgAdlib.toBeQueued, + + allowDirectPlay: orgAdlib.allowDirectPlay, + tags: orgAdlib.tags, + + hasSideEffects: orgAdlib.hasSideEffects, + abSessions: orgAdlib.abSessions, } if (!piece.externalId) @@ -262,10 +327,10 @@ export function postProcessAdLibPieces( ) piece.timelineObjectsString = serializePieceTimelineObjectsBlob(timelineObjects) - // Fill in ids of unnamed expectedPackages - setDefaultIdOnExpectedPackages(piece.expectedPackages) - - return piece + return { + doc: piece, + expectedPackages: processedExpectedPackages, + } }) span?.end() @@ -282,7 +347,7 @@ export function postProcessGlobalAdLibActions( blueprintId: BlueprintId, rundownId: RundownId, adlibActions: IBlueprintActionManifest[] -): RundownBaselineAdLibAction[] { +): PostProcessDoc[] { const uniqueIds = new Map() return adlibActions.map((action) => { @@ -300,16 +365,34 @@ export function postProcessGlobalAdLibActions( ) // Fill in ids of unnamed expectedPackages - setDefaultIdOnExpectedPackages(action.expectedPackages) + const processedExpectedPackages = setDefaultIdOnExpectedPackages(action.expectedPackages) - return literal({ - ...action, - actionId: action.actionId, + const processedAction = literal>({ + externalId: action.externalId, _id: protectString(docId), rundownId: rundownId, partId: undefined, ...processAdLibActionITranslatableMessages(action, blueprintId), + + expectedPackages: processedExpectedPackages, // convertExpectedPackages(processedExpectedPackages), + expectedPlayoutItems: action.expectedPlayoutItems, + + privateData: action.privateData, + publicData: action.publicData, + + actionId: action.actionId, + userData: action.userData, + userDataManifest: action.userDataManifest, + allVariants: action.allVariants, + + // Not used? + uniquenessId: undefined, }) + + return { + doc: processedAction, + expectedPackages: processedExpectedPackages, + } }) } @@ -325,7 +408,7 @@ export function postProcessAdLibActions( rundownId: RundownId, partId: PartId, adlibActions: IBlueprintActionManifest[] -): AdLibAction[] { +): PostProcessDoc[] { const uniqueIds = new Map() return adlibActions.map((action) => { @@ -341,16 +424,34 @@ export function postProcessAdLibActions( ) // Fill in ids of unnamed expectedPackages - setDefaultIdOnExpectedPackages(action.expectedPackages) + const processedExpectedPackages = setDefaultIdOnExpectedPackages(action.expectedPackages) - return literal({ - ...action, - actionId: action.actionId, + const processedAction = literal>({ + externalId: action.externalId, _id: protectString(docId), rundownId: rundownId, partId: partId, ...processAdLibActionITranslatableMessages(action, blueprintId), + + expectedPackages: processedExpectedPackages, // convertExpectedPackages(processedExpectedPackages), + expectedPlayoutItems: action.expectedPlayoutItems, + + privateData: action.privateData, + publicData: action.publicData, + + actionId: action.actionId, + userData: action.userData, + userDataManifest: action.userDataManifest, + allVariants: action.allVariants, + + // Not used? + uniquenessId: undefined, }) + + return { + doc: processedAction, + expectedPackages: processedExpectedPackages, + } }) } @@ -398,14 +499,17 @@ export function postProcessBucketAdLib( bucketId: BucketId, rank: number | undefined, importVersions: RundownImportVersions -): BucketAdLib { +): PostProcessDoc { const id: PieceId = protectString( getHash( `${showStyleCompound.showStyleVariantId}_${context.studioId}_${bucketId}_bucket_adlib_${ingestInfo.payload.externalId}` ) ) - const piece: BucketAdLib = { - ...itemOrig, + + // Fill in ids of unnamed expectedPackages + const processedExpectedPackages = setDefaultIdOnExpectedPackages(itemOrig.expectedPackages) + + const piece: Complete = { content: omit(itemOrig.content, 'timelineObjects'), _id: id, externalId: ingestInfo.payload.externalId, @@ -417,14 +521,44 @@ export function postProcessBucketAdLib( ingestInfo, _rank: rank || itemOrig._rank, timelineObjectsString: EmptyPieceTimelineObjectsBlob, + + expectedPackages: processedExpectedPackages, // convertExpectedPackages(processedExpectedPackages), + expectedPlayoutItems: itemOrig.expectedPlayoutItems, + + privateData: itemOrig.privateData, + publicData: itemOrig.publicData, + invalid: itemOrig.invalid, + floated: itemOrig.floated, + name: itemOrig.name, + expectedDuration: itemOrig.expectedDuration, + + currentPieceTags: itemOrig.currentPieceTags, + nextPieceTags: itemOrig.nextPieceTags, + uniquenessId: itemOrig.uniquenessId, + invertOnAirState: itemOrig.invertOnAirState, + + lifespan: itemOrig.lifespan, + sourceLayerId: itemOrig.sourceLayerId, + outputLayerId: itemOrig.outputLayerId, + + prerollDuration: itemOrig.prerollDuration, + postrollDuration: itemOrig.postrollDuration, + toBeQueued: itemOrig.toBeQueued, + + allowDirectPlay: itemOrig.allowDirectPlay, + tags: itemOrig.tags, + + hasSideEffects: itemOrig.hasSideEffects, + abSessions: itemOrig.abSessions, } - // Fill in ids of unnamed expectedPackages - setDefaultIdOnExpectedPackages(piece.expectedPackages) const timelineObjects = postProcessTimelineObjects(piece._id, blueprintId, itemOrig.content.timelineObjects) piece.timelineObjectsString = serializePieceTimelineObjectsBlob(timelineObjects) - return piece + return { + doc: piece, + expectedPackages: processedExpectedPackages, + } } /** @@ -447,14 +581,17 @@ export function postProcessBucketAction( bucketId: BucketId, rank: number | undefined, importVersions: RundownImportVersions -): BucketAdLibAction { +): PostProcessDoc { const id: AdLibActionId = protectString( getHash( `${showStyleCompound.showStyleVariantId}_${context.studioId}_${bucketId}_bucket_adlib_${ingestInfo.payload.externalId}` ) ) - const action: BucketAdLibAction = { - ...omit(itemOrig, 'partId'), + + // Fill in ids of unnamed expectedPackages + const processedExpectedPackages = setDefaultIdOnExpectedPackages(itemOrig.expectedPackages) + + const action: Complete = { _id: id, externalId: ingestInfo.payload.externalId, studioId: context.studioId, @@ -464,12 +601,26 @@ export function postProcessBucketAction( importVersions, ingestInfo, ...processAdLibActionITranslatableMessages(itemOrig, blueprintId, rank), - } + expectedPackages: processedExpectedPackages, // convertExpectedPackages(processedExpectedPackages), + expectedPlayoutItems: itemOrig.expectedPlayoutItems, - // Fill in ids of unnamed expectedPackages - setDefaultIdOnExpectedPackages(action.expectedPackages) + privateData: itemOrig.privateData, + publicData: itemOrig.publicData, + + actionId: itemOrig.actionId, + userData: itemOrig.userData, + userDataManifest: itemOrig.userDataManifest, + allVariants: itemOrig.allVariants, - return action + // Not used? + partId: undefined, + uniquenessId: undefined, + } + + return { + doc: action, + expectedPackages: processedExpectedPackages, + } } /** @@ -498,7 +649,7 @@ function processAdLibActionITranslatableMessages< })[] }, T extends IBlueprintActionManifest ->(itemOrig: T, blueprintId: BlueprintId, rank?: number): Pick { +>(itemOrig: T, blueprintId: BlueprintId, rank?: number): Complete> { return { display: { ...itemOrig.display, @@ -527,3 +678,11 @@ function processAdLibActionITranslatableMessages< ), } } + +function convertExpectedPackages(expectedPackages: ExpectedPackage.Any[]): Complete[] { + if (!expectedPackages) return [] + + return expectedPackages.map((expectedPackage) => ({ + _id: expectedPackage._id, + })) +} diff --git a/packages/job-worker/src/ingest/expectedPackages.ts b/packages/job-worker/src/ingest/expectedPackages.ts index c1d6099a9e..92cde81f8b 100644 --- a/packages/job-worker/src/ingest/expectedPackages.ts +++ b/packages/job-worker/src/ingest/expectedPackages.ts @@ -381,14 +381,18 @@ export function updateBaselineExpectedPackagesOnStudio( ) } -export function setDefaultIdOnExpectedPackages(expectedPackages: ExpectedPackage.Any[] | undefined): void { +export function setDefaultIdOnExpectedPackages( + expectedPackages: ExpectedPackage.Any[] | undefined +): ExpectedPackage.Any[] { // Fill in ids of unnamed expectedPackage - if (expectedPackages) { - for (let i = 0; i < expectedPackages.length; i++) { - const expectedPackage = expectedPackages[i] - if (!expectedPackage._id) { - expectedPackage._id = `__index${i}` - } + if (!expectedPackages) return [] + + for (let i = 0; i < expectedPackages.length; i++) { + const expectedPackage = expectedPackages[i] + if (!expectedPackage._id) { + expectedPackage._id = `__index${i}` } } + + return expectedPackages } From a4dac47b1f745e3d7a9db52cc875edb2c2452537 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 3 Sep 2024 12:02:51 +0100 Subject: [PATCH 05/22] wip --- .../SyncIngestUpdateToPartInstanceContext.ts | 34 +++++++++++-------- .../job-worker/src/blueprints/context/lib.ts | 13 ++++--- .../PartAndPieceInstanceActionService.ts | 4 +-- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts index 463b26944c..45785a1a45 100644 --- a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts +++ b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts @@ -1,6 +1,9 @@ import { PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids' -import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' -import { normalizeArrayToMap, omit } from '@sofie-automation/corelib/dist/lib' +import { + PieceInstance, + PieceInstanceWithExpectedPackages, +} from '@sofie-automation/corelib/dist/dataModel/PieceInstance' +import { normalizeArrayToMap, normalizeArrayToMapFunc, omit } from '@sofie-automation/corelib/dist/lib' import { protectString, protectStringArray, unprotectStringArray } from '@sofie-automation/corelib/dist/protectedString' import { PlayoutPartInstanceModel } from '../../playout/model/PlayoutPartInstanceModel' import { ReadonlyDeep } from 'type-fest' @@ -36,7 +39,7 @@ export class SyncIngestUpdateToPartInstanceContext extends RundownUserContext implements ISyncIngestUpdateToPartInstanceContext { - private readonly _proposedPieceInstances: Map> + private readonly _proposedPieceInstances: Map> private partInstance: PlayoutPartInstanceModel | null @@ -47,7 +50,7 @@ export class SyncIngestUpdateToPartInstanceContext showStyleCompound: ReadonlyDeep, rundown: ReadonlyDeep, partInstance: PlayoutPartInstanceModel, - proposedPieceInstances: ReadonlyDeep, + proposedPieceInstances: ReadonlyDeep, private playStatus: 'previous' | 'current' | 'next' ) { super( @@ -61,7 +64,7 @@ export class SyncIngestUpdateToPartInstanceContext this.partInstance = partInstance - this._proposedPieceInstances = normalizeArrayToMap(proposedPieceInstances, '_id') + this._proposedPieceInstances = normalizeArrayToMapFunc(proposedPieceInstances, (p) => p.pieceInstance._id) } syncPieceInstance( @@ -76,14 +79,14 @@ export class SyncIngestUpdateToPartInstanceContext if (!this.partInstance) throw new Error(`PartInstance has been removed`) // filter the submission to the allowed ones - const piece = modifiedPiece + const postProcessedPiece = modifiedPiece ? postProcessPieces( this._context, [ { ...modifiedPiece, // Some properties arent allowed to be changed - lifespan: proposedPieceInstance.piece.lifespan, + lifespan: proposedPieceInstance.pieceInstance.piece.lifespan, }, ], this.showStyleCompound.blueprintId, @@ -92,17 +95,17 @@ export class SyncIngestUpdateToPartInstanceContext this.partInstance.partInstance.part._id, this.playStatus === 'current' )[0] - : proposedPieceInstance.piece + : null - const newExpectedPackages = modifiedPiece ? piece.expectedPackages : proposedPieceInstance.expectedPackages // nocommit - these need solving + const newExpectedPackages = postProcessedPiece?.expectedPackages ?? proposedPieceInstance.expectedPackages const newPieceInstance: ReadonlyDeep = { ...proposedPieceInstance, - piece: piece, + piece: postProcessedPiece?.doc ?? proposedPieceInstance.pieceInstance.piece, } this.partInstance.mergeOrInsertPieceInstance(newPieceInstance, newExpectedPackages) - return convertPieceInstanceToBlueprints(newPieceInstance) + return convertPieceInstanceToBlueprints(newPieceInstance, newExpectedPackages) } insertPieceInstance(piece0: IBlueprintPiece): IBlueprintPieceInstance { @@ -120,9 +123,9 @@ export class SyncIngestUpdateToPartInstanceContext this.playStatus === 'current' )[0] - const newPieceInstance = this.partInstance.insertPlannedPiece(piece) + const newPieceInstance = this.partInstance.insertPlannedPiece(piece.doc, piece.expectedPackages) - return convertPieceInstanceToBlueprints(newPieceInstance.pieceInstance) + return convertPieceInstanceToBlueprints(newPieceInstance.pieceInstance, newPieceInstance.expectedPackages) } updatePieceInstance(pieceInstanceId: string, updatedPiece: Partial): IBlueprintPieceInstance { // filter the submission to the allowed ones @@ -160,8 +163,11 @@ export class SyncIngestUpdateToPartInstanceContext timelineObjectsString, }) } + if (trimmedPiece.expectedPackages) { + pieceInstance.setExpectedPackages(trimmedPiece.expectedPackages) + } - return convertPieceInstanceToBlueprints(pieceInstance.pieceInstance) + return convertPieceInstanceToBlueprints(pieceInstance.pieceInstance, pieceInstance.expectedPackages) } updatePartInstance(updatePart: Partial): IBlueprintPartInstance { if (!this.partInstance) throw new Error(`PartInstance has been removed`) diff --git a/packages/job-worker/src/blueprints/context/lib.ts b/packages/job-worker/src/blueprints/context/lib.ts index 19811519d1..cda24d5a4c 100644 --- a/packages/job-worker/src/blueprints/context/lib.ts +++ b/packages/job-worker/src/blueprints/context/lib.ts @@ -44,6 +44,7 @@ import { } from '@sofie-automation/blueprints-integration' import { JobContext, ProcessedShowStyleBase, ProcessedShowStyleVariant } from '../../jobs' import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { ExpectedPackageDBFromPieceInstance } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' /** * Convert an object to have all the values of all keys (including optionals) be 'true' @@ -111,7 +112,8 @@ export const IBlueprintMutatablePartSampleKeys = allKeysOfObject + pieceInstance: ReadonlyDeep, + expectedPackages: ReadonlyDeep ): Complete { const obj: Complete = { _id: unprotectString(pieceInstance._id), @@ -139,8 +141,11 @@ function convertPieceInstanceToBlueprintsInner( * @param pieceInstance the PieceInstance to convert * @returns a cloned complete and clean IBlueprintPieceInstance */ -export function convertPieceInstanceToBlueprints(pieceInstance: ReadonlyDeep): IBlueprintPieceInstance { - return convertPieceInstanceToBlueprintsInner(pieceInstance) +export function convertPieceInstanceToBlueprints( + pieceInstance: ReadonlyDeep, + expectedPackages: ReadonlyDeep +): IBlueprintPieceInstance { + return convertPieceInstanceToBlueprintsInner(pieceInstance, expectedPackages) } /** @@ -152,7 +157,7 @@ export function convertResolvedPieceInstanceToBlueprints( pieceInstance: ResolvedPieceInstance ): IBlueprintResolvedPieceInstance { const obj: Complete = { - ...convertPieceInstanceToBlueprintsInner(pieceInstance.instance), + ...convertPieceInstanceToBlueprintsInner(pieceInstance.instance, undefined), resolvedStart: pieceInstance.resolvedStart, resolvedDuration: pieceInstance.resolvedDuration, } diff --git a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts index 24550ad977..ecce11146a 100644 --- a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts +++ b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts @@ -270,7 +270,7 @@ export class PartAndPieceInstanceActionService { this.nextPartState = Math.max(this.nextPartState, ActionPartChange.SAFE_CHANGE) } - return convertPieceInstanceToBlueprints(newPieceInstance.pieceInstance) + return convertPieceInstanceToBlueprints(newPieceInstance.pieceInstance, newPieceInstance.expectedPackages) } async updatePieceInstance( @@ -327,7 +327,7 @@ export class PartAndPieceInstanceActionService { this.nextPartState = Math.max(this.nextPartState, updatesNextPart) this.currentPartState = Math.max(this.currentPartState, updatesCurrentPart) - return convertPieceInstanceToBlueprints(pieceInstance.pieceInstance) + return convertPieceInstanceToBlueprints(pieceInstance.pieceInstance, pieceInstance.expectedPackages) } async updatePartInstance( From 15b801acf183ab38cb26e9c6d4720b721b909e35 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 3 Sep 2024 12:29:07 +0100 Subject: [PATCH 06/22] wip: restructure expected packages collection, to make conversion easier --- .../corelib/src/dataModel/ExpectedPackages.ts | 32 +++-- packages/corelib/src/playout/infinites.ts | 2 +- .../SyncIngestUpdateToPartInstanceContext.ts | 8 +- .../PartAndPieceInstanceActionService.ts | 6 +- .../src/blueprints/context/watchedPackages.ts | 4 +- .../job-worker/src/blueprints/postProcess.ts | 4 + .../job-worker/src/ingest/expectedPackages.ts | 136 ++---------------- .../src/ingest/generationRundown.ts | 89 +++++++++++- .../src/ingest/generationSegment.ts | 2 +- .../src/ingest/model/IngestModel.ts | 13 +- .../model/implementation/IngestModelImpl.ts | 14 +- packages/job-worker/src/ingest/packageInfo.ts | 5 +- 12 files changed, 151 insertions(+), 164 deletions(-) diff --git a/packages/corelib/src/dataModel/ExpectedPackages.ts b/packages/corelib/src/dataModel/ExpectedPackages.ts index 9abb21d391..9f8b184720 100644 --- a/packages/corelib/src/dataModel/ExpectedPackages.ts +++ b/packages/corelib/src/dataModel/ExpectedPackages.ts @@ -1,6 +1,6 @@ import { ExpectedPackage, Time } from '@sofie-automation/blueprints-integration' import { protectString } from '../protectedString' -import { getHash, hashObj, omit } from '../lib' +import { getHash, hashObj } from '../lib' import { AdLibActionId, BucketAdLibActionId, @@ -56,10 +56,13 @@ export enum ExpectedPackageDBType { STUDIO_BASELINE_OBJECTS = 'studio_baseline_objects', PIECE_INSTANCE = 'piece_instance', } -export interface ExpectedPackageDBBase extends Omit { +export interface ExpectedPackageDBBaseSimple { _id: ExpectedPackageId + + package: ReadonlyDeep + /** The local package id - as given by the blueprints */ - blueprintPackageId: string + blueprintPackageId: string // TODO - remove this? /** The studio of the Rundown of the Piece this package belongs to */ studioId: StudioId @@ -67,11 +70,12 @@ export interface ExpectedPackageDBBase extends Omit /** Hash that changes whenever the content or version changes. See getContentVersionHash() */ contentVersionHash: string - // pieceId: ProtectedString | null - fromPieceType: ExpectedPackageDBType - created: Time } + +export interface ExpectedPackageDBBase extends ExpectedPackageDBBaseSimple { + fromPieceType: ExpectedPackageDBType +} export interface ExpectedPackageDBFromPiece extends ExpectedPackageDBBase { fromPieceType: ExpectedPackageDBType.PIECE | ExpectedPackageDBType.ADLIB_PIECE /** The Piece this package belongs to */ @@ -183,9 +187,20 @@ export function convertPieceExpectedPackageToPieceInstance( pieceInstanceId: PieceInstanceId, partInstance: ReadonlyDeep ): ExpectedPackageDBFromPieceInstance { + const baseProps: ExpectedPackageDBBaseSimple = { + blueprintPackageId: expectedPackage.blueprintPackageId, + contentVersionHash: expectedPackage.contentVersionHash, + created: expectedPackage.created, + studioId: expectedPackage.studioId, + + package: expectedPackage.package, + _id: getExpectedPackageId(pieceInstanceId, expectedPackage.package._id), + } + if (expectedPackage.fromPieceType === ExpectedPackageDBType.PIECE_INSTANCE) { return { - ...expectedPackage, + ...baseProps, + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, partInstanceId: partInstance._id, pieceInstanceId: pieceInstanceId, @@ -195,7 +210,8 @@ export function convertPieceExpectedPackageToPieceInstance( } } else { return { - ...omit(expectedPackage, 'pieceId', 'partId'), + ...baseProps, + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, partInstanceId: partInstance._id, pieceInstanceId: pieceInstanceId, diff --git a/packages/corelib/src/playout/infinites.ts b/packages/corelib/src/playout/infinites.ts index 1136fde7fc..9fafdc9211 100644 --- a/packages/corelib/src/playout/infinites.ts +++ b/packages/corelib/src/playout/infinites.ts @@ -13,7 +13,7 @@ import { PieceInstance, PieceInstancePiece, rewrapPieceToInstance } from '../dat import { DBPartInstance } from '../dataModel/PartInstance' import { DBRundown } from '../dataModel/Rundown' import { ReadonlyDeep } from 'type-fest' -import { assertNever, clone, flatten, getRandomId, groupByToMapFunc, max, normalizeArrayToMapFunc } from '../lib' +import { assertNever, clone, getRandomId, groupByToMapFunc, max, normalizeArrayToMapFunc } from '../lib' import { protectString } from '../protectedString' import _ = require('underscore') import { MongoQuery } from '../mongo' diff --git a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts index 45785a1a45..c13cc25b8a 100644 --- a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts +++ b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts @@ -3,7 +3,7 @@ import { PieceInstance, PieceInstanceWithExpectedPackages, } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' -import { normalizeArrayToMap, normalizeArrayToMapFunc, omit } from '@sofie-automation/corelib/dist/lib' +import { normalizeArrayToMapFunc, omit } from '@sofie-automation/corelib/dist/lib' import { protectString, protectStringArray, unprotectStringArray } from '@sofie-automation/corelib/dist/protectedString' import { PlayoutPartInstanceModel } from '../../playout/model/PlayoutPartInstanceModel' import { ReadonlyDeep } from 'type-fest' @@ -97,10 +97,12 @@ export class SyncIngestUpdateToPartInstanceContext )[0] : null - const newExpectedPackages = postProcessedPiece?.expectedPackages ?? proposedPieceInstance.expectedPackages + const newExpectedPackages = postProcessedPiece + ? postProcessedPiece.expectedPackages + : proposedPieceInstance.expectedPackages const newPieceInstance: ReadonlyDeep = { - ...proposedPieceInstance, + ...proposedPieceInstance.pieceInstance, piece: postProcessedPiece?.doc ?? proposedPieceInstance.pieceInstance.piece, } this.partInstance.mergeOrInsertPieceInstance(newPieceInstance, newExpectedPackages) diff --git a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts index ecce11146a..37f1a1322c 100644 --- a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts +++ b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts @@ -109,7 +109,11 @@ export class PartAndPieceInstanceActionService { } async getPieceInstances(part: 'current' | 'next'): Promise { const partInstance = this._getPartInstance(part) - return partInstance?.pieceInstances?.map((p) => convertPieceInstanceToBlueprints(p.pieceInstance)) ?? [] + return ( + partInstance?.pieceInstances?.map((p) => + convertPieceInstanceToBlueprints(p.pieceInstance, p.expectedPackages) + ) ?? [] + ) } async getResolvedPieceInstances(part: 'current' | 'next'): Promise { const partInstance = this._getPartInstance(part) diff --git a/packages/job-worker/src/blueprints/context/watchedPackages.ts b/packages/job-worker/src/blueprints/context/watchedPackages.ts index 3ae20ec9fe..d84e08dd2e 100644 --- a/packages/job-worker/src/blueprints/context/watchedPackages.ts +++ b/packages/job-worker/src/blueprints/context/watchedPackages.ts @@ -77,7 +77,7 @@ export class WatchedPackagesHelper { return this.#createFromPackages( context, - packages.filter((pkg) => !!pkg.listenToPackageInfoUpdates) + packages.filter((pkg) => !!pkg.package.listenToPackageInfoUpdates) ) } @@ -105,7 +105,7 @@ export class WatchedPackagesHelper { return this.#createFromPackages( context, - packages.filter((pkg) => !!pkg.listenToPackageInfoUpdates) + packages.filter((pkg) => !!pkg.package.listenToPackageInfoUpdates) ) } diff --git a/packages/job-worker/src/blueprints/postProcess.ts b/packages/job-worker/src/blueprints/postProcess.ts index ca922d9f8c..d040800672 100644 --- a/packages/job-worker/src/blueprints/postProcess.ts +++ b/packages/job-worker/src/blueprints/postProcess.ts @@ -65,6 +65,10 @@ export interface PostProcessDoc { expectedPackages: ExpectedPackage.Any[] } +export function unwrapPostProccessDocs(docs: PostProcessDoc[]): T[] { + return docs.map((doc) => doc.doc) +} + /** * Process and validate some IBlueprintPiece into Piece * @param context Context from the job queue diff --git a/packages/job-worker/src/ingest/expectedPackages.ts b/packages/job-worker/src/ingest/expectedPackages.ts index 92cde81f8b..f250b0623c 100644 --- a/packages/job-worker/src/ingest/expectedPackages.ts +++ b/packages/job-worker/src/ingest/expectedPackages.ts @@ -5,17 +5,14 @@ import { BucketAdLib } from '@sofie-automation/corelib/dist/dataModel/BucketAdLi import { ExpectedPackageDBType, ExpectedPackageDBFromPiece, - ExpectedPackageDBFromBaselineAdLibPiece, ExpectedPackageDBFromAdLibAction, - ExpectedPackageDBFromBaselineAdLibAction, ExpectedPackageDBFromBucketAdLib, ExpectedPackageDBFromBucketAdLibAction, - ExpectedPackageDBBase, - ExpectedPackageDBFromRundownBaselineObjects, ExpectedPackageDBFromStudioBaselineObjects, getContentVersionHash, getExpectedPackageId, ExpectedPackageFromRundown, + ExpectedPackageDBBaseSimple, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { SegmentId, @@ -28,8 +25,6 @@ import { StudioId, } from '@sofie-automation/corelib/dist/dataModel/Ids' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' -import { RundownBaselineAdLibAction } from '@sofie-automation/corelib/dist/dataModel/RundownBaselineAdLibAction' -import { RundownBaselineAdLibItem } from '@sofie-automation/corelib/dist/dataModel/RundownBaselineAdLibPiece' import { saveIntoDb } from '../db/changes' import { PlayoutModel } from '../playout/model/PlayoutModel' import { StudioPlayoutModel } from '../studio/model/StudioPlayoutModel' @@ -42,7 +37,7 @@ import { updateExpectedPlayoutItemsForRundownBaseline, } from './expectedPlayoutItems' import { JobContext } from '../jobs' -import { ExpectedPackageForIngestModelBaseline, IngestModel } from './model/IngestModel' +import { IngestModel } from './model/IngestModel' import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { IngestPartModel } from './model/IngestPartModel' import { clone } from '@sofie-automation/corelib/dist/lib' @@ -77,86 +72,13 @@ export function updateExpectedPackagesForPartModel(context: JobContext, part: In part.setExpectedPackages(expectedPackages) } -export async function updateExpectedPackagesForRundownBaseline( +export async function updateExpectedMediaAndPlayoutItemsForRundownBaseline( context: JobContext, ingestModel: IngestModel, - baseline: BlueprintResultBaseline | undefined, - forceBaseline = false + baseline: BlueprintResultBaseline | undefined ): Promise { await updateExpectedMediaItemsForRundownBaseline(context, ingestModel) await updateExpectedPlayoutItemsForRundownBaseline(context, ingestModel, baseline) - - const expectedPackages: ExpectedPackageForIngestModelBaseline[] = [] - - const preserveTypesDuringSave = new Set() - - // Only regenerate the baseline types if they are already loaded into memory - // If the data isn't already loaded, then we haven't made any changes to the baseline adlibs - // This means we can skip regenerating them as it is guaranteed there will be no changes - const baselineAdlibPieceCache = forceBaseline - ? await ingestModel.rundownBaselineAdLibPieces.get() - : ingestModel.rundownBaselineAdLibPieces.getIfLoaded() - if (baselineAdlibPieceCache) { - expectedPackages.push( - ...generateExpectedPackagesForBaselineAdlibPiece( - context.studio, - ingestModel.rundownId, - baselineAdlibPieceCache - ) - ) - } else { - // We haven't regenerated anything, so preserve the values in the save - preserveTypesDuringSave.add(ExpectedPackageDBType.BASELINE_ADLIB_PIECE) - } - const baselineAdlibActionCache = forceBaseline - ? await ingestModel.rundownBaselineAdLibActions.get() - : ingestModel.rundownBaselineAdLibActions.getIfLoaded() - if (baselineAdlibActionCache) { - expectedPackages.push( - ...generateExpectedPackagesForBaselineAdlibAction( - context.studio, - ingestModel.rundownId, - baselineAdlibActionCache - ) - ) - } else { - // We haven't regenerated anything, so preserve the values in the save - preserveTypesDuringSave.add(ExpectedPackageDBType.BASELINE_ADLIB_ACTION) - } - - if (baseline) { - // Fill in ids of unnamed expectedPackages - setDefaultIdOnExpectedPackages(baseline.expectedPackages) - - const bases = generateExpectedPackageBases( - context.studio, - ingestModel.rundownId, - baseline.expectedPackages ?? [] - ) - - expectedPackages.push( - ...bases.map((item): ExpectedPackageDBFromRundownBaselineObjects => { - return { - ...item, - fromPieceType: ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS, - rundownId: ingestModel.rundownId, - pieceId: null, - } - }) - ) - } else { - // We haven't regenerated anything, so preserve the values in the save - preserveTypesDuringSave.add(ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS) - } - - // Preserve anything existing - for (const expectedPackage of ingestModel.expectedPackagesForRundownBaseline) { - if (preserveTypesDuringSave.has(expectedPackage.fromPieceType)) { - expectedPackages.push(clone(expectedPackage)) - } - } - - ingestModel.setExpectedPackagesForRundownBaseline(expectedPackages) } function generateExpectedPackagesForPiece( @@ -185,27 +107,6 @@ function generateExpectedPackagesForPiece( } return packages } -function generateExpectedPackagesForBaselineAdlibPiece( - studio: ReadonlyDeep, - rundownId: RundownId, - pieces: ReadonlyDeep -) { - const packages: ExpectedPackageDBFromBaselineAdLibPiece[] = [] - for (const piece of pieces) { - if (piece.expectedPackages) { - const bases = generateExpectedPackageBases(studio, piece._id, piece.expectedPackages) - for (const base of bases) { - packages.push({ - ...base, - rundownId, - pieceId: piece._id, - fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_PIECE, - }) - } - } - } - return packages -} function generateExpectedPackagesForAdlibAction( studio: ReadonlyDeep, rundownId: RundownId, @@ -230,27 +131,6 @@ function generateExpectedPackagesForAdlibAction( } return packages } -function generateExpectedPackagesForBaselineAdlibAction( - studio: ReadonlyDeep, - rundownId: RundownId, - actions: ReadonlyDeep -) { - const packages: ExpectedPackageDBFromBaselineAdLibAction[] = [] - for (const action of actions) { - if (action.expectedPackages) { - const bases = generateExpectedPackageBases(studio, action._id, action.expectedPackages) - for (const base of bases) { - packages.push({ - ...base, - rundownId, - pieceId: action._id, - fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_ACTION, - }) - } - } - } - return packages -} function generateExpectedPackagesForBucketAdlib(studio: ReadonlyDeep, adlibs: BucketAdLib[]) { const packages: ExpectedPackageDBFromBucketAdLib[] = [] for (const adlib of adlibs) { @@ -290,7 +170,7 @@ function generateExpectedPackagesForBucketAdlibAction( } return packages } -function generateExpectedPackageBases( +export function generateExpectedPackageBases( studio: ReadonlyDeep, ownerId: | PieceId @@ -301,15 +181,15 @@ function generateExpectedPackageBases( | RundownId | StudioId, expectedPackages: ReadonlyDeep -) { - const bases: Omit[] = [] +): ExpectedPackageDBBaseSimple[] { + const bases: ExpectedPackageDBBaseSimple[] = [] for (let i = 0; i < expectedPackages.length; i++) { const expectedPackage = expectedPackages[i] const id = expectedPackage._id || '__unnamed' + i bases.push({ - ...clone(expectedPackage), + package: clone(expectedPackage), _id: getExpectedPackageId(ownerId, id), blueprintPackageId: id, contentVersionHash: getContentVersionHash(expectedPackage), diff --git a/packages/job-worker/src/ingest/generationRundown.ts b/packages/job-worker/src/ingest/generationRundown.ts index f41ed7db49..db25b3ba63 100644 --- a/packages/job-worker/src/ingest/generationRundown.ts +++ b/packages/job-worker/src/ingest/generationRundown.ts @@ -1,5 +1,10 @@ -import { ExpectedPackageDBType } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' -import { BlueprintId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { + ExpectedPackageDBFromBaselineAdLibAction, + ExpectedPackageDBFromBaselineAdLibPiece, + ExpectedPackageDBFromRundownBaselineObjects, + ExpectedPackageDBType, +} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { BlueprintId, RundownId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { RundownNote } from '@sofie-automation/corelib/dist/dataModel/Notes' import { serializePieceTimelineObjectsBlob } from '@sofie-automation/corelib/dist/dataModel/Piece' import { DBRundown, RundownSource } from '@sofie-automation/corelib/dist/dataModel/Rundown' @@ -10,23 +15,31 @@ import { StudioUserContext, GetRundownContext } from '../blueprints/context' import { WatchedPackagesHelper } from '../blueprints/context/watchedPackages' import { postProcessAdLibPieces, + PostProcessDoc, postProcessGlobalAdLibActions, postProcessRundownBaselineItems, + unwrapPostProccessDocs, } from '../blueprints/postProcess' import { logger } from '../logging' import _ = require('underscore') -import { IngestModel } from './model/IngestModel' +import { ExpectedPackageForIngestModelBaseline, IngestModel } from './model/IngestModel' import { LocalIngestRundown } from './ingestCache' import { extendIngestRundownCore, canRundownBeUpdated } from './lib' import { JobContext } from '../jobs' import { CommitIngestData } from './lock' import { SelectedShowStyleVariant, selectShowStyleVariant } from './selectShowStyleVariant' -import { updateExpectedPackagesForRundownBaseline } from './expectedPackages' +import { generateExpectedPackageBases, updateExpectedMediaAndPlayoutItemsForRundownBaseline } from './expectedPackages' import { ReadonlyDeep } from 'type-fest' -import { BlueprintResultRundown, ExtendedIngestRundown } from '@sofie-automation/blueprints-integration' +import { + BlueprintResultRundown, + ExpectedPackage, + ExtendedIngestRundown, +} from '@sofie-automation/blueprints-integration' import { wrapTranslatableMessageFromBlueprints } from '@sofie-automation/corelib/dist/TranslatableMessage' import { convertRundownToBlueprintSegmentRundown } from '../blueprints/context/lib' import { calculateSegmentsAndRemovalsFromIngestData } from './generationSegment' +import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' +import { RundownBaselineAdLibAction } from '@sofie-automation/corelib/dist/dataModel/RundownBaselineAdLibAction' /** * Regenerate and save a whole Rundown @@ -342,9 +355,71 @@ export async function regenerateRundownAndBaselineFromIngestData( rundownRes.globalActions || [] ) - await ingestModel.setRundownBaseline(timelineObjectsBlob, adlibPieces, adlibActions) + const expectedPackages: ExpectedPackageForIngestModelBaseline[] = [ + ...generateRundownBaselineExpectedPackages(context, dbRundown._id, rundownRes.baseline.expectedPackages ?? []), + ...generateGlobalAdLibPieceExpectedPackages(context, dbRundown._id, adlibPieces), + ...generateGlobalAdLibActionExpectedPackages(context, dbRundown._id, adlibActions), + ] + + await ingestModel.setRundownBaseline( + timelineObjectsBlob, + unwrapPostProccessDocs(adlibPieces), + unwrapPostProccessDocs(adlibActions), + expectedPackages + ) - await updateExpectedPackagesForRundownBaseline(context, ingestModel, rundownRes.baseline) + await updateExpectedMediaAndPlayoutItemsForRundownBaseline(context, ingestModel, rundownRes.baseline) return dbRundown } + +function generateGlobalAdLibPieceExpectedPackages( + context: JobContext, + rundownId: RundownId, + adlibPieces: PostProcessDoc[] +): ExpectedPackageDBFromBaselineAdLibPiece[] { + return adlibPieces.flatMap(({ doc, expectedPackages }) => { + const bases = generateExpectedPackageBases(context.studio, doc._id, expectedPackages) + + return bases.map((base) => ({ + ...base, + rundownId, + pieceId: doc._id, + fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_PIECE, + })) + }) +} + +function generateGlobalAdLibActionExpectedPackages( + context: JobContext, + rundownId: RundownId, + adlibActions: PostProcessDoc[] +): ExpectedPackageDBFromBaselineAdLibAction[] { + return adlibActions.flatMap(({ doc, expectedPackages }) => { + const bases = generateExpectedPackageBases(context.studio, doc._id, expectedPackages) + + return bases.map((base) => ({ + ...base, + rundownId, + pieceId: doc._id, + fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_ACTION, + })) + }) +} + +function generateRundownBaselineExpectedPackages( + context: JobContext, + rundownId: RundownId, + expectedPackages: ExpectedPackage.Any[] +): ExpectedPackageDBFromRundownBaselineObjects[] { + const bases = generateExpectedPackageBases(context.studio, rundownId, expectedPackages) + + return bases.map((item) => { + return { + ...item, + fromPieceType: ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS, + rundownId: rundownId, + pieceId: null, + } + }) +} diff --git a/packages/job-worker/src/ingest/generationSegment.ts b/packages/job-worker/src/ingest/generationSegment.ts index e90d17dcdf..3b4b50a6e5 100644 --- a/packages/job-worker/src/ingest/generationSegment.ts +++ b/packages/job-worker/src/ingest/generationSegment.ts @@ -190,7 +190,7 @@ async function checkIfSegmentReferencesUnloadedPackageInfos( // check if there are any updates right away? for (const part of segmentModel.parts) { for (const expectedPackage of part.expectedPackages) { - if (expectedPackage.listenToPackageInfoUpdates) { + if (expectedPackage.package.listenToPackageInfoUpdates) { const loadedPackage = segmentWatchedPackages.getPackage(expectedPackage._id) if (!loadedPackage) { // The package didn't exist prior to the blueprint running diff --git a/packages/job-worker/src/ingest/model/IngestModel.ts b/packages/job-worker/src/ingest/model/IngestModel.ts index 305a4197cd..666f396a1a 100644 --- a/packages/job-worker/src/ingest/model/IngestModel.ts +++ b/packages/job-worker/src/ingest/model/IngestModel.ts @@ -212,11 +212,11 @@ export interface IngestModel extends IngestModelReadonly, BaseModel { */ setExpectedMediaItemsForRundownBaseline(expectedMediaItems: ExpectedMediaItemRundown[]): void - /** - * Set the ExpectedPackages for the baseline of this Rundown - * @param expectedPackages The new ExpectedPackages - */ - setExpectedPackagesForRundownBaseline(expectedPackages: ExpectedPackageForIngestModelBaseline[]): void + // /** + // * Set the ExpectedPackages for the baseline of this Rundown + // * @param expectedPackages The new ExpectedPackages + // */ + // setExpectedPackagesForRundownBaseline(expectedPackages: ExpectedPackageForIngestModelBaseline[]): void /** * Set the data for this Rundown. @@ -246,7 +246,8 @@ export interface IngestModel extends IngestModelReadonly, BaseModel { setRundownBaseline( timelineObjectsBlob: PieceTimelineObjectsBlob, adlibPieces: RundownBaselineAdLibItem[], - adlibActions: RundownBaselineAdLibAction[] + adlibActions: RundownBaselineAdLibAction[], + expectedPackages: ExpectedPackageForIngestModelBaseline[] ): Promise /** diff --git a/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts b/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts index f441921f1a..adaff4192c 100644 --- a/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts +++ b/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts @@ -410,10 +410,10 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { setExpectedMediaItemsForRundownBaseline(expectedMediaItems: ExpectedMediaItemRundown[]): void { this.#rundownBaselineExpectedPackagesStore.setExpectedMediaItems(expectedMediaItems) } - setExpectedPackagesForRundownBaseline(expectedPackages: ExpectedPackageForIngestModelBaseline[]): void { - // Future: should these be here, or held as part of each adlib? - this.#rundownBaselineExpectedPackagesStore.setExpectedPackages(expectedPackages) - } + // setExpectedPackagesForRundownBaseline(expectedPackages: ExpectedPackageForIngestModelBaseline[]): void { + // // Future: should these be here, or held as part of each adlib? + // this.#rundownBaselineExpectedPackagesStore.setExpectedPackages(expectedPackages) + // } setRundownData( rundownData: IBlueprintRundown, @@ -469,7 +469,8 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { async setRundownBaseline( timelineObjectsBlob: PieceTimelineObjectsBlob, adlibPieces: RundownBaselineAdLibItem[], - adlibActions: RundownBaselineAdLibAction[] + adlibActions: RundownBaselineAdLibAction[], + expectedPackages: ExpectedPackageForIngestModelBaseline[] ): Promise { const [loadedRundownBaselineObjs, loadedRundownBaselineAdLibPieces, loadedRundownBaselineAdLibActions] = await Promise.all([ @@ -517,6 +518,9 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { newAdlibActions ) ) + + // Future: should these be here, or held as part of each adlib? + this.#rundownBaselineExpectedPackagesStore.setExpectedPackages(expectedPackages) } setRundownOrphaned(orphaned: RundownOrphanedReason | undefined): void { diff --git a/packages/job-worker/src/ingest/packageInfo.ts b/packages/job-worker/src/ingest/packageInfo.ts index a669f65d19..6910e74d2c 100644 --- a/packages/job-worker/src/ingest/packageInfo.ts +++ b/packages/job-worker/src/ingest/packageInfo.ts @@ -8,7 +8,7 @@ import { logger } from '../logging' import { JobContext } from '../jobs' import { regenerateSegmentsFromIngestData } from './generationSegment' import { UpdateIngestRundownAction, runIngestJob, runWithRundownLock } from './lock' -import { updateExpectedPackagesForPartModel, updateExpectedPackagesForRundownBaseline } from './expectedPackages' +import { updateExpectedPackagesForPartModel } from './expectedPackages' import { loadIngestModelFromRundown } from './model/implementation/LoadIngestModel' /** @@ -27,7 +27,8 @@ export async function handleExpectedPackagesRegenerate( updateExpectedPackagesForPartModel(context, part) } - await updateExpectedPackagesForRundownBaseline(context, ingestModel, undefined, true) + // nocommit - reimplement? + // await updateExpectedPackagesForRundownBaseline(context, ingestModel, undefined, true) await ingestModel.saveAllToDatabase() }) From 930e340de8f325d04ee1bbf2922d8a6923143015 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 3 Sep 2024 12:45:32 +0100 Subject: [PATCH 07/22] wip: more --- .../corelib/src/dataModel/ExpectedPackages.ts | 7 ++++ .../SyncIngestUpdateToPartInstanceContext.ts | 11 ++++-- .../job-worker/src/blueprints/context/lib.ts | 4 +- .../job-worker/src/ingest/expectedPackages.ts | 39 +++++++++++++++---- .../src/ingest/generationRundown.ts | 6 +-- .../src/ingest/syncChangesToPartInstance.ts | 7 +++- packages/job-worker/src/playout/adlibUtils.ts | 2 +- .../playout/model/PlayoutPartInstanceModel.ts | 12 ++---- .../model/PlayoutPieceInstanceModel.ts | 4 +- .../PlayoutPartInstanceModelImpl.ts | 26 ++++++++----- .../PlayoutPieceInstanceModelImpl.ts | 19 ++++++--- 11 files changed, 93 insertions(+), 44 deletions(-) diff --git a/packages/corelib/src/dataModel/ExpectedPackages.ts b/packages/corelib/src/dataModel/ExpectedPackages.ts index 9f8b184720..aab3de44f9 100644 --- a/packages/corelib/src/dataModel/ExpectedPackages.ts +++ b/packages/corelib/src/dataModel/ExpectedPackages.ts @@ -221,3 +221,10 @@ export function convertPieceExpectedPackageToPieceInstance( } } } + +export function unwrapExpectedPackages( + expectedPackages: ReadonlyDeep | undefined +): ReadonlyDeep { + if (!expectedPackages) return [] + return expectedPackages.map((p) => p.package) +} diff --git a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts index c13cc25b8a..45858bbf12 100644 --- a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts +++ b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts @@ -19,6 +19,7 @@ import { IBlueprintPartInstance, SomeContent, WithTimeline, + ExpectedPackage, } from '@sofie-automation/blueprints-integration' import { postProcessPieces, postProcessTimelineObjects } from '../postProcess' import { @@ -34,6 +35,7 @@ import { serializePieceTimelineObjectsBlob, } from '@sofie-automation/corelib/dist/dataModel/Piece' import { EXPECTED_INGEST_TO_PLAYOUT_TIME } from '@sofie-automation/shared-lib/dist/core/constants' +import { unwrapExpectedPackages } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' export class SyncIngestUpdateToPartInstanceContext extends RundownUserContext @@ -97,9 +99,9 @@ export class SyncIngestUpdateToPartInstanceContext )[0] : null - const newExpectedPackages = postProcessedPiece + const newExpectedPackages: ReadonlyDeep = postProcessedPiece ? postProcessedPiece.expectedPackages - : proposedPieceInstance.expectedPackages + : unwrapExpectedPackages(proposedPieceInstance.expectedPackages) const newPieceInstance: ReadonlyDeep = { ...proposedPieceInstance.pieceInstance, @@ -127,7 +129,10 @@ export class SyncIngestUpdateToPartInstanceContext const newPieceInstance = this.partInstance.insertPlannedPiece(piece.doc, piece.expectedPackages) - return convertPieceInstanceToBlueprints(newPieceInstance.pieceInstance, newPieceInstance.expectedPackages) + return convertPieceInstanceToBlueprints( + newPieceInstance.pieceInstance, + unwrapExpectedPackages(newPieceInstance.expectedPackages) + ) } updatePieceInstance(pieceInstanceId: string, updatedPiece: Partial): IBlueprintPieceInstance { // filter the submission to the allowed ones diff --git a/packages/job-worker/src/blueprints/context/lib.ts b/packages/job-worker/src/blueprints/context/lib.ts index cda24d5a4c..46a4b34057 100644 --- a/packages/job-worker/src/blueprints/context/lib.ts +++ b/packages/job-worker/src/blueprints/context/lib.ts @@ -113,7 +113,7 @@ export const IBlueprintMutatablePartSampleKeys = allKeysOfObject, - expectedPackages: ReadonlyDeep + expectedPackages: ReadonlyDeep ): Complete { const obj: Complete = { _id: unprotectString(pieceInstance._id), @@ -143,7 +143,7 @@ function convertPieceInstanceToBlueprintsInner( */ export function convertPieceInstanceToBlueprints( pieceInstance: ReadonlyDeep, - expectedPackages: ReadonlyDeep + expectedPackages: ReadonlyDeep ): IBlueprintPieceInstance { return convertPieceInstanceToBlueprintsInner(pieceInstance, expectedPackages) } diff --git a/packages/job-worker/src/ingest/expectedPackages.ts b/packages/job-worker/src/ingest/expectedPackages.ts index f250b0623c..80c254f76d 100644 --- a/packages/job-worker/src/ingest/expectedPackages.ts +++ b/packages/job-worker/src/ingest/expectedPackages.ts @@ -13,6 +13,7 @@ import { getExpectedPackageId, ExpectedPackageFromRundown, ExpectedPackageDBBaseSimple, + ExpectedPackageDBFromPieceInstance, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { SegmentId, @@ -23,6 +24,7 @@ import { BucketAdLibActionId, BucketAdLibId, StudioId, + PieceInstanceId, } from '@sofie-automation/corelib/dist/dataModel/Ids' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' import { saveIntoDb } from '../db/changes' @@ -41,6 +43,7 @@ import { IngestModel } from './model/IngestModel' import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { IngestPartModel } from './model/IngestPartModel' import { clone } from '@sofie-automation/corelib/dist/lib' +import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' export function updateExpectedPackagesForPartModel(context: JobContext, part: IngestPartModel): void { updateExpectedMediaItemsForPartModel(context, part) @@ -92,7 +95,7 @@ function generateExpectedPackagesForPiece( for (const piece of pieces) { const partId = 'startPartId' in piece ? piece.startPartId : piece.partId if (piece.expectedPackages && partId) { - const bases = generateExpectedPackageBases(studio, piece._id, piece.expectedPackages) + const bases = generateExpectedPackageBases(studio._id, piece._id, piece.expectedPackages) for (const base of bases) { packages.push({ ...base, @@ -116,7 +119,7 @@ function generateExpectedPackagesForAdlibAction( const packages: ExpectedPackageDBFromAdLibAction[] = [] for (const action of actions) { if (action.expectedPackages) { - const bases = generateExpectedPackageBases(studio, action._id, action.expectedPackages) + const bases = generateExpectedPackageBases(studio._id, action._id, action.expectedPackages) for (const base of bases) { packages.push({ ...base, @@ -135,7 +138,7 @@ function generateExpectedPackagesForBucketAdlib(studio: ReadonlyDeep, const packages: ExpectedPackageDBFromBucketAdLib[] = [] for (const adlib of adlibs) { if (adlib.expectedPackages) { - const bases = generateExpectedPackageBases(studio, adlib._id, adlib.expectedPackages) + const bases = generateExpectedPackageBases(studio._id, adlib._id, adlib.expectedPackages) for (const base of bases) { packages.push({ ...base, @@ -156,7 +159,7 @@ function generateExpectedPackagesForBucketAdlibAction( const packages: ExpectedPackageDBFromBucketAdLibAction[] = [] for (const action of adlibActions) { if (action.expectedPackages) { - const bases = generateExpectedPackageBases(studio, action._id, action.expectedPackages) + const bases = generateExpectedPackageBases(studio._id, action._id, action.expectedPackages) for (const base of bases) { packages.push({ ...base, @@ -171,7 +174,7 @@ function generateExpectedPackagesForBucketAdlibAction( return packages } export function generateExpectedPackageBases( - studio: ReadonlyDeep, + studioId: StudioId, ownerId: | PieceId | AdLibActionId @@ -179,7 +182,8 @@ export function generateExpectedPackageBases( | BucketAdLibId | BucketAdLibActionId | RundownId - | StudioId, + | StudioId + | PieceInstanceId, expectedPackages: ReadonlyDeep ): ExpectedPackageDBBaseSimple[] { const bases: ExpectedPackageDBBaseSimple[] = [] @@ -193,7 +197,7 @@ export function generateExpectedPackageBases( _id: getExpectedPackageId(ownerId, id), blueprintPackageId: id, contentVersionHash: getContentVersionHash(expectedPackage), - studioId: studio._id, + studioId: studioId, created: Date.now(), }) } @@ -249,7 +253,7 @@ export function updateBaselineExpectedPackagesOnStudio( // Fill in ids of unnamed expectedPackages setDefaultIdOnExpectedPackages(baseline.expectedPackages) - const bases = generateExpectedPackageBases(context.studio, context.studio._id, baseline.expectedPackages ?? []) + const bases = generateExpectedPackageBases(context.studio._id, context.studio._id, baseline.expectedPackages ?? []) playoutModel.setExpectedPackagesForStudioBaseline( bases.map((item): ExpectedPackageDBFromStudioBaselineObjects => { return { @@ -276,3 +280,22 @@ export function setDefaultIdOnExpectedPackages( return expectedPackages } + +export function wrapPackagesForPieceInstance( + studio: ReadonlyDeep, + partInstance: ReadonlyDeep, + pieceInstanceId: PieceInstanceId, + expectedPackages: ReadonlyDeep +): ExpectedPackageDBFromPieceInstance[] { + const bases = generateExpectedPackageBases(studio._id, pieceInstanceId, expectedPackages) + return bases.map((base) => ({ + ...base, + + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, + partInstanceId: partInstance._id, + pieceInstanceId: pieceInstanceId, + segmentId: partInstance.segmentId, + rundownId: partInstance.rundownId, + pieceId: null, + })) +} diff --git a/packages/job-worker/src/ingest/generationRundown.ts b/packages/job-worker/src/ingest/generationRundown.ts index db25b3ba63..366fe13543 100644 --- a/packages/job-worker/src/ingest/generationRundown.ts +++ b/packages/job-worker/src/ingest/generationRundown.ts @@ -379,7 +379,7 @@ function generateGlobalAdLibPieceExpectedPackages( adlibPieces: PostProcessDoc[] ): ExpectedPackageDBFromBaselineAdLibPiece[] { return adlibPieces.flatMap(({ doc, expectedPackages }) => { - const bases = generateExpectedPackageBases(context.studio, doc._id, expectedPackages) + const bases = generateExpectedPackageBases(context.studio._id, doc._id, expectedPackages) return bases.map((base) => ({ ...base, @@ -396,7 +396,7 @@ function generateGlobalAdLibActionExpectedPackages( adlibActions: PostProcessDoc[] ): ExpectedPackageDBFromBaselineAdLibAction[] { return adlibActions.flatMap(({ doc, expectedPackages }) => { - const bases = generateExpectedPackageBases(context.studio, doc._id, expectedPackages) + const bases = generateExpectedPackageBases(context.studio._id, doc._id, expectedPackages) return bases.map((base) => ({ ...base, @@ -412,7 +412,7 @@ function generateRundownBaselineExpectedPackages( rundownId: RundownId, expectedPackages: ExpectedPackage.Any[] ): ExpectedPackageDBFromRundownBaselineObjects[] { - const bases = generateExpectedPackageBases(context.studio, rundownId, expectedPackages) + const bases = generateExpectedPackageBases(context.studio._id, rundownId, expectedPackages) return bases.map((item) => { return { diff --git a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts index df913ee379..5a1c3442a7 100644 --- a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts +++ b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts @@ -32,6 +32,7 @@ import { import { validateAdlibTestingPartInstanceProperties } from '../playout/adlibTesting' import { ReadonlyDeep } from 'type-fest' import { convertIngestModelToPlayoutRundownWithSegments } from './commit' +import { unwrapExpectedPackages } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' type PlayStatus = 'previous' | 'current' | 'next' type SyncedInstance = { @@ -129,7 +130,9 @@ export async function syncChangesToPartInstances( const partId = existingPartInstance.partInstance.part._id const existingResultPartInstance: BlueprintSyncIngestPartInstance = { partInstance: convertPartInstanceToBlueprints(existingPartInstance.partInstance), - pieceInstances: pieceInstancesInPart.map((p) => convertPieceInstanceToBlueprints(p.pieceInstance)), + pieceInstances: pieceInstancesInPart.map((p) => + convertPieceInstanceToBlueprints(p.pieceInstance, unwrapExpectedPackages(p.expectedPackages)) + ), } const proposedPieceInstances = await getPieceInstancesForPart( @@ -155,7 +158,7 @@ export async function syncChangesToPartInstances( const newResultData: BlueprintSyncIngestNewData = { part: newPart ? convertPartToBlueprints(newPart) : undefined, pieceInstances: proposedPieceInstances.map((p) => - convertPieceInstanceToBlueprints(p.pieceInstance, p.expectedPackages) + convertPieceInstanceToBlueprints(p.pieceInstance, unwrapExpectedPackages(p.expectedPackages)) ), adLibPieces: newPart && ingestPart ? ingestPart.adLibPieces.map(convertAdLibPieceToBlueprints) : [], actions: newPart && ingestPart ? ingestPart.adLibActions.map(convertAdLibActionToBlueprints) : [], diff --git a/packages/job-worker/src/playout/adlibUtils.ts b/packages/job-worker/src/playout/adlibUtils.ts index ac00477dce..297fba69a1 100644 --- a/packages/job-worker/src/playout/adlibUtils.ts +++ b/packages/job-worker/src/playout/adlibUtils.ts @@ -63,7 +63,7 @@ export async function innerStartOrQueueAdLibPiece( // syncPlayheadInfinitesForNextPartInstance is handled by setNextPart } else { const genericAdlibPiece = convertAdLibToGenericPiece(adLibPiece, false) - currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, adLibPiece._id, adLibPiece.expectedPackages) + currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, adLibPiece._id, adLibPiece.expectedPackages ?? []) await syncPlayheadInfinitesForNextPartInstance( context, diff --git a/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts b/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts index 3114f64c1b..6f57ee2620 100644 --- a/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts @@ -7,13 +7,9 @@ import { PieceInstanceWithExpectedPackages, } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { PartNote } from '@sofie-automation/corelib/dist/dataModel/Notes' -import { IBlueprintMutatablePart, PieceLifespan, Time } from '@sofie-automation/blueprints-integration' +import { ExpectedPackage, IBlueprintMutatablePart, PieceLifespan, Time } from '@sofie-automation/blueprints-integration' import { PartCalculatedTimings } from '@sofie-automation/corelib/dist/playout/timings' import { PlayoutPieceInstanceModel } from './PlayoutPieceInstanceModel' -import { - ExpectedPackageDBFromPiece, - ExpectedPackageDBFromPieceInstance, -} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' /** * Token returned when making a backup copy of a PlayoutPartInstanceModel @@ -78,7 +74,7 @@ export interface PlayoutPartInstanceModel { insertAdlibbedPiece( piece: Omit, fromAdlibId: PieceId | undefined, - pieceExpectedPackages: ExpectedPackageDBFromPiece[] + pieceExpectedPackages: ReadonlyDeep ): PlayoutPieceInstanceModel /** @@ -98,7 +94,7 @@ export interface PlayoutPartInstanceModel { */ insertPlannedPiece( piece: Omit, - pieceExpectedPackages: ExpectedPackageDBFromPiece[] + pieceExpectedPackages: ReadonlyDeep ): PlayoutPieceInstanceModel /** @@ -156,7 +152,7 @@ export interface PlayoutPartInstanceModel { */ mergeOrInsertPieceInstance( pieceInstance: ReadonlyDeep, - expectedPackages: ExpectedPackageDBFromPieceInstance[] + expectedPackages: ReadonlyDeep ): PlayoutPieceInstanceModel /** diff --git a/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts b/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts index b340368165..1641f9145e 100644 --- a/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts @@ -1,7 +1,7 @@ import { PieceInstanceInfiniteId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' -import { Time } from '@sofie-automation/blueprints-integration' +import { ExpectedPackage, Time } from '@sofie-automation/blueprints-integration' import { ExpectedPackageDBFromPieceInstance } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' export interface PlayoutPieceInstanceModel { @@ -68,5 +68,5 @@ export interface PlayoutPieceInstanceModel { * Update the expected packages for the PieceInstance * @param expectedPackages The new packages */ - setExpectedPackages(expectedPackages: ExpectedPackageDBFromPieceInstance[]): void + setExpectedPackages(expectedPackages: ReadonlyDeep[]): void } diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts index 86e44d28d3..d05a97650c 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts @@ -22,6 +22,7 @@ import { } from '@sofie-automation/corelib/dist/playout/timings' import { PartNote } from '@sofie-automation/corelib/dist/dataModel/Notes' import { + ExpectedPackage, IBlueprintMutatablePart, IBlueprintPieceType, PieceLifespan, @@ -37,9 +38,9 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { IBlueprintMutatablePartSampleKeys } from '../../../blueprints/context/lib' import { convertPieceExpectedPackageToPieceInstance, - ExpectedPackageDBFromPiece, ExpectedPackageDBFromPieceInstance, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { wrapPackagesForPieceInstance } from '../../../ingest/expectedPackages' interface PlayoutPieceInstanceModelSnapshotImpl { PieceInstance: PieceInstance @@ -244,7 +245,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { insertAdlibbedPiece( piece: Omit, fromAdlibId: PieceId | undefined, - pieceExpectedPackages: ExpectedPackageDBFromPiece[] + pieceExpectedPackages: ReadonlyDeep ): PlayoutPieceInstanceModel { const pieceInstance: PieceInstance = { _id: protectString(`${this.partInstance._id}_${piece._id}`), @@ -324,16 +325,14 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { #convertExpectedPackagesForPieceInstance( pieceInstance: ReadonlyDeep, - expectedPackages: ReadonlyDeep> + expectedPackages: ReadonlyDeep ): ExpectedPackageDBFromPieceInstance[] { - return expectedPackages.map((p) => - convertPieceExpectedPackageToPieceInstance(p, pieceInstance._id, this.partInstanceImpl) - ) + return wrapPackagesForPieceInstance(this.studioId, this.partInstanceImpl, pieceInstance._id, expectedPackages) } insertPlannedPiece( piece: Omit, - pieceExpectedPackages: ExpectedPackageDBFromPiece[] + pieceExpectedPackages: ReadonlyDeep ): PlayoutPieceInstanceModel { const pieceInstanceId = getPieceInstanceIdForPiece(this.partInstance._id, piece._id) if (this.pieceInstancesImpl.has(pieceInstanceId)) @@ -472,7 +471,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { mergeOrInsertPieceInstance( doc: ReadonlyDeep, - expectedPackages: ExpectedPackageDBFromPieceInstance[] + expectedPackages: ReadonlyDeep[] ): PlayoutPieceInstanceModel { // Future: this should do some validation of the new PieceInstance @@ -483,11 +482,18 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { return existingPieceInstance } else { + const newExpectedPackages = wrapPackagesForPieceInstance( + studio._id, + this.partInstanceImpl, + doc._id, + expectedPackages + ) + const newPieceInstance = new PlayoutPieceInstanceModelImpl( clone(doc), - expectedPackages, + newExpectedPackages, true, - expectedPackages.map((p) => p._id) + newExpectedPackages.map((p) => p._id) ) this.pieceInstancesImpl.set(newPieceInstance.pieceInstance._id, newPieceInstance) return newPieceInstance diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts index 161e53006f..fea93bf27a 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts @@ -2,15 +2,19 @@ import { ExpectedPackageId, PieceInstanceInfiniteId } from '@sofie-automation/co import { ReadonlyDeep } from 'type-fest' import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { clone, getRandomId } from '@sofie-automation/corelib/dist/lib' -import { Time } from '@sofie-automation/blueprints-integration' +import { ExpectedPackage, Time } from '@sofie-automation/blueprints-integration' import { PlayoutPieceInstanceModel } from '../PlayoutPieceInstanceModel' import _ = require('underscore') -import { ExpectedPackageDBFromPieceInstance } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { + ExpectedPackageDBFromPieceInstance, + ExpectedPackageDBType, +} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { DocumentChanges, getDocumentChanges, diffAndReturnLatestObjects, } from '../../../ingest/model/implementation/utils' +import { generateExpectedPackageBases } from '../../../ingest/expectedPackages' export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel { /** @@ -167,14 +171,19 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel * Update the expected packages for the PieceInstance * @param expectedPackages The new packages */ - setExpectedPackages(expectedPackages: ExpectedPackageDBFromPieceInstance[]): void { + setExpectedPackages(expectedPackages: ReadonlyDeep[]): void { // nocommit - refactor this into a simpler type than `ExpectedPackagesStore` or just reuse that? - const newExpectedPackages: ExpectedPackageDBFromPieceInstance[] = expectedPackages.map((pkg) => ({ - ...pkg, + const bases = generateExpectedPackageBases(studio._id, this.PieceInstanceImpl._id, expectedPackages) + const newExpectedPackages: ExpectedPackageDBFromPieceInstance[] = bases.map((base) => ({ + ...base, + + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, partInstanceId: this.PieceInstanceImpl.partInstanceId, pieceInstanceId: this.PieceInstanceImpl._id, + segmentId: this.PieceInstanceImpl.segmentId, // TODO rundownId: this.PieceInstanceImpl.rundownId, + pieceId: null, })) this.#expectedPackages = diffAndReturnLatestObjects( From d55574411e86e8e445d5e117da5d61fc1c7de1d8 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 3 Sep 2024 13:00:39 +0100 Subject: [PATCH 08/22] wip --- .../corelib/src/dataModel/ExpectedPackages.ts | 41 ------------------- .../corelib/src/dataModel/PieceInstance.ts | 11 +++-- .../context/RundownTimingEventContext.ts | 2 +- .../SyncIngestUpdateToPartInstanceContext.ts | 7 +++- .../job-worker/src/blueprints/context/lib.ts | 1 - .../PartAndPieceInstanceActionService.ts | 13 ++++-- .../src/ingest/syncChangesToPartInstance.ts | 2 +- packages/job-worker/src/playout/infinites.ts | 10 ++--- .../model/implementation/PlayoutModelImpl.ts | 25 ++++++++++- .../PlayoutPartInstanceModelImpl.ts | 27 ++++++------ 10 files changed, 68 insertions(+), 71 deletions(-) diff --git a/packages/corelib/src/dataModel/ExpectedPackages.ts b/packages/corelib/src/dataModel/ExpectedPackages.ts index aab3de44f9..060724850a 100644 --- a/packages/corelib/src/dataModel/ExpectedPackages.ts +++ b/packages/corelib/src/dataModel/ExpectedPackages.ts @@ -17,7 +17,6 @@ import { StudioId, } from './Ids' import { ReadonlyDeep } from 'type-fest' -import { DBPartInstance } from './PartInstance' /* Expected Packages are created from Pieces in the rundown. @@ -182,46 +181,6 @@ export function getExpectedPackageId( return protectString(`${ownerId}_${getHash(localExpectedPackageId)}`) } -export function convertPieceExpectedPackageToPieceInstance( - expectedPackage: ReadonlyDeep, - pieceInstanceId: PieceInstanceId, - partInstance: ReadonlyDeep -): ExpectedPackageDBFromPieceInstance { - const baseProps: ExpectedPackageDBBaseSimple = { - blueprintPackageId: expectedPackage.blueprintPackageId, - contentVersionHash: expectedPackage.contentVersionHash, - created: expectedPackage.created, - studioId: expectedPackage.studioId, - - package: expectedPackage.package, - _id: getExpectedPackageId(pieceInstanceId, expectedPackage.package._id), - } - - if (expectedPackage.fromPieceType === ExpectedPackageDBType.PIECE_INSTANCE) { - return { - ...baseProps, - - fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, - partInstanceId: partInstance._id, - pieceInstanceId: pieceInstanceId, - segmentId: partInstance.segmentId, - rundownId: partInstance.rundownId, - pieceId: null, - } - } else { - return { - ...baseProps, - - fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, - partInstanceId: partInstance._id, - pieceInstanceId: pieceInstanceId, - segmentId: partInstance.segmentId, - rundownId: partInstance.rundownId, - pieceId: null, - } - } -} - export function unwrapExpectedPackages( expectedPackages: ReadonlyDeep | undefined ): ReadonlyDeep { diff --git a/packages/corelib/src/dataModel/PieceInstance.ts b/packages/corelib/src/dataModel/PieceInstance.ts index 8adb65c82e..abc2d88d41 100644 --- a/packages/corelib/src/dataModel/PieceInstance.ts +++ b/packages/corelib/src/dataModel/PieceInstance.ts @@ -1,4 +1,4 @@ -import { Time } from '@sofie-automation/blueprints-integration' +import { ExpectedPackage, Time } from '@sofie-automation/blueprints-integration' import { protectString } from '../protectedString' import { PieceInstanceInfiniteId, @@ -11,7 +11,7 @@ import { import { Piece } from './Piece' import { omit } from '../lib' import { ReadonlyDeep } from 'type-fest' -import { ExpectedPackageDBFromPiece, ExpectedPackageDBFromPieceInstance } from './ExpectedPackages' +import { ExpectedPackageDBFromPieceInstance } from './ExpectedPackages' export type PieceInstancePiece = Omit @@ -96,7 +96,12 @@ export interface ResolvedPieceInstance { export interface PieceInstanceWithExpectedPackages { pieceInstance: PieceInstance - expectedPackages: ExpectedPackageDBFromPieceInstance[] | ExpectedPackageDBFromPiece[] + expectedPackages: ReadonlyDeep +} + +export interface PieceInstanceWithExpectedPackagesFull { + pieceInstance: PieceInstance + expectedPackages: ExpectedPackageDBFromPieceInstance[] } export function omitPiecePropertiesForInstance(piece: Piece | PieceInstancePiece): PieceInstancePiece { diff --git a/packages/job-worker/src/blueprints/context/RundownTimingEventContext.ts b/packages/job-worker/src/blueprints/context/RundownTimingEventContext.ts index 8b25072596..f4cbc23697 100644 --- a/packages/job-worker/src/blueprints/context/RundownTimingEventContext.ts +++ b/packages/job-worker/src/blueprints/context/RundownTimingEventContext.ts @@ -115,7 +115,7 @@ export class RundownTimingEventContext extends RundownDataChangedEventContext im partInstanceId: { $in: protectStringArray(partInstanceIds) }, }) - return pieceInstances.map(convertPieceInstanceToBlueprints) + return pieceInstances.map((p) => convertPieceInstanceToBlueprints(p, [])) } async getSegment(segmentId: string): Promise> | undefined> { diff --git a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts index 45858bbf12..56bde0c2b1 100644 --- a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts +++ b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts @@ -101,7 +101,7 @@ export class SyncIngestUpdateToPartInstanceContext const newExpectedPackages: ReadonlyDeep = postProcessedPiece ? postProcessedPiece.expectedPackages - : unwrapExpectedPackages(proposedPieceInstance.expectedPackages) + : proposedPieceInstance.expectedPackages const newPieceInstance: ReadonlyDeep = { ...proposedPieceInstance.pieceInstance, @@ -174,7 +174,10 @@ export class SyncIngestUpdateToPartInstanceContext pieceInstance.setExpectedPackages(trimmedPiece.expectedPackages) } - return convertPieceInstanceToBlueprints(pieceInstance.pieceInstance, pieceInstance.expectedPackages) + return convertPieceInstanceToBlueprints( + pieceInstance.pieceInstance, + unwrapExpectedPackages(pieceInstance.expectedPackages) + ) } updatePartInstance(updatePart: Partial): IBlueprintPartInstance { if (!this.partInstance) throw new Error(`PartInstance has been removed`) diff --git a/packages/job-worker/src/blueprints/context/lib.ts b/packages/job-worker/src/blueprints/context/lib.ts index 46a4b34057..d252727527 100644 --- a/packages/job-worker/src/blueprints/context/lib.ts +++ b/packages/job-worker/src/blueprints/context/lib.ts @@ -44,7 +44,6 @@ import { } from '@sofie-automation/blueprints-integration' import { JobContext, ProcessedShowStyleBase, ProcessedShowStyleVariant } from '../../jobs' import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' -import { ExpectedPackageDBFromPieceInstance } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' /** * Convert an object to have all the values of all keys (including optionals) be 'true' diff --git a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts index 37f1a1322c..e7ef19d252 100644 --- a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts +++ b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts @@ -55,6 +55,7 @@ import { validateAdlibTestingPartInstanceProperties } from '../../../playout/adl import { isTooCloseToAutonext } from '../../../playout/lib' import { DBPart, isPartPlayable } from '@sofie-automation/corelib/dist/dataModel/Part' import { PlayoutRundownModel } from '../../../playout/model/PlayoutRundownModel' +import { unwrapExpectedPackages } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' export enum ActionPartChange { NONE = 0, @@ -111,7 +112,7 @@ export class PartAndPieceInstanceActionService { const partInstance = this._getPartInstance(part) return ( partInstance?.pieceInstances?.map((p) => - convertPieceInstanceToBlueprints(p.pieceInstance, p.expectedPackages) + convertPieceInstanceToBlueprints(p.pieceInstance, unwrapExpectedPackages(p.expectedPackages)) ) ?? [] ) } @@ -274,7 +275,10 @@ export class PartAndPieceInstanceActionService { this.nextPartState = Math.max(this.nextPartState, ActionPartChange.SAFE_CHANGE) } - return convertPieceInstanceToBlueprints(newPieceInstance.pieceInstance, newPieceInstance.expectedPackages) + return convertPieceInstanceToBlueprints( + newPieceInstance.pieceInstance, + unwrapExpectedPackages(newPieceInstance.expectedPackages) + ) } async updatePieceInstance( @@ -331,7 +335,10 @@ export class PartAndPieceInstanceActionService { this.nextPartState = Math.max(this.nextPartState, updatesNextPart) this.currentPartState = Math.max(this.currentPartState, updatesCurrentPart) - return convertPieceInstanceToBlueprints(pieceInstance.pieceInstance, pieceInstance.expectedPackages) + return convertPieceInstanceToBlueprints( + pieceInstance.pieceInstance, + unwrapExpectedPackages(pieceInstance.expectedPackages) + ) } async updatePartInstance( diff --git a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts index 5a1c3442a7..85d5712ee3 100644 --- a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts +++ b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts @@ -158,7 +158,7 @@ export async function syncChangesToPartInstances( const newResultData: BlueprintSyncIngestNewData = { part: newPart ? convertPartToBlueprints(newPart) : undefined, pieceInstances: proposedPieceInstances.map((p) => - convertPieceInstanceToBlueprints(p.pieceInstance, unwrapExpectedPackages(p.expectedPackages)) + convertPieceInstanceToBlueprints(p.pieceInstance, p.expectedPackages) ), adLibPieces: newPart && ingestPart ? ingestPart.adLibPieces.map(convertAdLibPieceToBlueprints) : [], actions: newPart && ingestPart ? ingestPart.adLibActions.map(convertAdLibActionToBlueprints) : [], diff --git a/packages/job-worker/src/playout/infinites.ts b/packages/job-worker/src/playout/infinites.ts index ecd429baac..00ce6b66f1 100644 --- a/packages/job-worker/src/playout/infinites.ts +++ b/packages/job-worker/src/playout/infinites.ts @@ -19,7 +19,7 @@ import { PlayoutModel } from './model/PlayoutModel' import { PlayoutPartInstanceModel } from './model/PlayoutPartInstanceModel' import { PlayoutSegmentModel } from './model/PlayoutSegmentModel' import { getCurrentTime } from '../lib' -import { clone, flatten, groupByToMap, normalizeArrayToMapFunc } from '@sofie-automation/corelib/dist/lib' +import { flatten, groupByToMap, normalizeArrayToMapFunc } from '@sofie-automation/corelib/dist/lib' import _ = require('underscore') import { IngestModelReadonly } from '../ingest/model/IngestModel' import { SegmentOrphanedReason } from '@sofie-automation/corelib/dist/dataModel/Segment' @@ -28,8 +28,8 @@ import { mongoWhere } from '@sofie-automation/corelib/dist/mongo' import { PlayoutRundownModel } from './model/PlayoutRundownModel' import { ExpectedPackageDBFromPiece, - ExpectedPackageDBFromPieceInstance, ExpectedPackageDBType, + unwrapExpectedPackages, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { PlayoutPieceInstanceModel } from './model/PlayoutPieceInstanceModel' @@ -298,7 +298,7 @@ export async function syncPlayheadInfinitesForNextPartInstance( return { pieceInstance: pieceInstance, - expectedPackages: clone(expectedPackages ?? []), + expectedPackages: unwrapExpectedPackages(expectedPackages ?? []), } }) @@ -398,7 +398,7 @@ async function wrapPieceInstancesWithExpectedPackages( const expectedPackages = playingPieceInstanceMap.get(pieceInstance._id)?.expectedPackages const pieceInstanceWithExpectedPackages: PieceInstanceWithExpectedPackages = { pieceInstance: pieceInstance, - expectedPackages: clone(expectedPackages ?? []), + expectedPackages: unwrapExpectedPackages(expectedPackages ?? []), } if (!expectedPackages) { @@ -420,7 +420,7 @@ async function wrapPieceInstancesWithExpectedPackages( for (const [pieceId, expectedPackages] of expectedPackagesByPieceId.entries()) { const pieceInstance = pieceIdsToLoad.get(pieceId) if (pieceInstance) { - pieceInstance.expectedPackages = expectedPackages + pieceInstance.expectedPackages = unwrapExpectedPackages(expectedPackages) } } } diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts index 7c293aef2b..f5c39f6803 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts @@ -56,6 +56,7 @@ import { ExpectedPlayoutItemStudio } from '@sofie-automation/corelib/dist/dataMo import { StudioBaselineHelper } from '../../../studio/model/StudioBaselineHelper' import { EventsJobs } from '@sofie-automation/corelib/dist/worker/events' import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError' +import { wrapPackagesForPieceInstance } from '../../../ingest/expectedPackages' export class PlayoutModelReadonlyImpl implements PlayoutModelReadonly { public readonly playlistId: RundownPlaylistId @@ -329,7 +330,17 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou this.#fixupPieceInstancesForPartInstance(newPartInstance, infinitePieceInstances) - const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, infinitePieceInstances, true) + const infinitePieceInstances2 = infinitePieceInstances.map((p) => ({ + pieceInstance: p.pieceInstance, + expectedPackages: wrapPackagesForPieceInstance( + this.studio._id, + newPartInstance, + p.pieceInstance._id, + p.expectedPackages + ), + })) + + const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, infinitePieceInstances2, true) for (const piece of pieces) { partInstance.insertAdlibbedPiece(piece, fromAdlibId, piece.expectedPackages ?? []) @@ -373,7 +384,17 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou this.#fixupPieceInstancesForPartInstance(newPartInstance, pieceInstances) - const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, pieceInstances, true) + const pieceInstances2 = pieceInstances.map((p) => ({ + pieceInstance: p.pieceInstance, + expectedPackages: wrapPackagesForPieceInstance( + this.studio._id, + newPartInstance, + p.pieceInstance._id, + p.expectedPackages + ), + })) + + const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, pieceInstances2, true) partInstance.recalculateExpectedDurationWithTransition() this.allPartInstances.set(newPartInstance._id, partInstance) diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts index d05a97650c..f3ee587b50 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts @@ -12,6 +12,7 @@ import { PieceInstance, PieceInstancePiece, PieceInstanceWithExpectedPackages, + PieceInstanceWithExpectedPackagesFull, } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { clone, getRandomId } from '@sofie-automation/corelib/dist/lib' import { getCurrentTime } from '../../../lib' @@ -36,10 +37,7 @@ import { EmptyPieceTimelineObjectsBlob } from '@sofie-automation/corelib/dist/da import _ = require('underscore') import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { IBlueprintMutatablePartSampleKeys } from '../../../blueprints/context/lib' -import { - convertPieceExpectedPackageToPieceInstance, - ExpectedPackageDBFromPieceInstance, -} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { ExpectedPackageDBFromPieceInstance } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { wrapPackagesForPieceInstance } from '../../../ingest/expectedPackages' interface PlayoutPieceInstanceModelSnapshotImpl { @@ -172,7 +170,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { constructor( partInstance: DBPartInstance, - pieceInstances: PieceInstanceWithExpectedPackages[], + pieceInstances: PieceInstanceWithExpectedPackagesFull[], hasChanges: boolean ) { this.partInstanceImpl = partInstance @@ -180,17 +178,20 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { this.pieceInstancesImpl = new Map() for (const { pieceInstance, expectedPackages } of pieceInstances) { - const expectedPackagesConverted = expectedPackages.map((p) => - convertPieceExpectedPackageToPieceInstance(p, pieceInstance._id, partInstance) - ) + // const expectedPackagesConverted = wrapPackagesForPieceInstance( + // studio._id, + // partInstance, + // pieceInstance._id, + // expectedPackages + // ) this.pieceInstancesImpl.set( pieceInstance._id, new PlayoutPieceInstanceModelImpl( pieceInstance, - expectedPackagesConverted, + expectedPackages, hasChanges, - hasChanges ? expectedPackagesConverted.map((p) => p._id) : null + hasChanges ? expectedPackages.map((p) => p._id) : null ) ) } @@ -457,13 +458,15 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { // Future: should this do any deeper validation of the PieceInstances? + const newExpectedPackages = this.#convertExpectedPackagesForPieceInstance(pieceInstance, expectedPackages) + this.pieceInstancesImpl.set( pieceInstance._id, new PlayoutPieceInstanceModelImpl( pieceInstance, - this.#convertExpectedPackagesForPieceInstance(pieceInstance, expectedPackages), + newExpectedPackages, true, - expectedPackages.map((p) => p._id) + newExpectedPackages.map((p) => p._id) ) ) } From ba1625104c5d8b7bd7d874d321107c704888a12c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 3 Sep 2024 13:06:34 +0100 Subject: [PATCH 09/22] wip: propogate context --- .../job-worker/src/ingest/expectedPackages.ts | 4 +-- .../model/implementation/LoadPlayoutModel.ts | 7 ++++- .../model/implementation/PlayoutModelImpl.ts | 15 ++++++--- .../PlayoutPartInstanceModelImpl.ts | 31 ++++++++++++------- .../PlayoutPieceInstanceModelImpl.ts | 7 ++++- 5 files changed, 44 insertions(+), 20 deletions(-) diff --git a/packages/job-worker/src/ingest/expectedPackages.ts b/packages/job-worker/src/ingest/expectedPackages.ts index 80c254f76d..6996ab34a1 100644 --- a/packages/job-worker/src/ingest/expectedPackages.ts +++ b/packages/job-worker/src/ingest/expectedPackages.ts @@ -282,12 +282,12 @@ export function setDefaultIdOnExpectedPackages( } export function wrapPackagesForPieceInstance( - studio: ReadonlyDeep, + studioId: StudioId, partInstance: ReadonlyDeep, pieceInstanceId: PieceInstanceId, expectedPackages: ReadonlyDeep ): ExpectedPackageDBFromPieceInstance[] { - const bases = generateExpectedPackageBases(studio._id, pieceInstanceId, expectedPackages) + const bases = generateExpectedPackageBases(studioId, pieceInstanceId, expectedPackages) return bases.map((base) => ({ ...base, diff --git a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts index 3eaaf5e46a..46d0c8533c 100644 --- a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts +++ b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts @@ -322,7 +322,12 @@ async function loadPartInstances( expectedPackages: groupedExpectedPackages.get(pieceInstance._id) ?? [], })) - const wrappedPartInstance = new PlayoutPartInstanceModelImpl(partInstance, pieceInstancesAndPackages, false) + const wrappedPartInstance = new PlayoutPartInstanceModelImpl( + context, + partInstance, + pieceInstancesAndPackages, + false + ) allPartInstances.push(wrappedPartInstance) } diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts index f5c39f6803..bd99981fad 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts @@ -333,14 +333,19 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou const infinitePieceInstances2 = infinitePieceInstances.map((p) => ({ pieceInstance: p.pieceInstance, expectedPackages: wrapPackagesForPieceInstance( - this.studio._id, + this.context.studioId, newPartInstance, p.pieceInstance._id, p.expectedPackages ), })) - const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, infinitePieceInstances2, true) + const partInstance = new PlayoutPartInstanceModelImpl( + this.context, + newPartInstance, + infinitePieceInstances2, + true + ) for (const piece of pieces) { partInstance.insertAdlibbedPiece(piece, fromAdlibId, piece.expectedPackages ?? []) @@ -387,14 +392,14 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou const pieceInstances2 = pieceInstances.map((p) => ({ pieceInstance: p.pieceInstance, expectedPackages: wrapPackagesForPieceInstance( - this.studio._id, + this.context.studioId, newPartInstance, p.pieceInstance._id, p.expectedPackages ), })) - const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, pieceInstances2, true) + const partInstance = new PlayoutPartInstanceModelImpl(this.context, newPartInstance, pieceInstances2, true) partInstance.recalculateExpectedDurationWithTransition() this.allPartInstances.set(newPartInstance._id, partInstance) @@ -433,7 +438,7 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou }, } - const partInstance = new PlayoutPartInstanceModelImpl(newPartInstance, [], true) + const partInstance = new PlayoutPartInstanceModelImpl(this.context, newPartInstance, [], true) partInstance.recalculateExpectedDurationWithTransition() this.allPartInstances.set(newPartInstance._id, partInstance) diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts index f3ee587b50..0d9aa14d90 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts @@ -39,6 +39,7 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { IBlueprintMutatablePartSampleKeys } from '../../../blueprints/context/lib' import { ExpectedPackageDBFromPieceInstance } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { wrapPackagesForPieceInstance } from '../../../ingest/expectedPackages' +import { JobContext } from '../../../jobs' interface PlayoutPieceInstanceModelSnapshotImpl { PieceInstance: PieceInstance @@ -76,6 +77,8 @@ class PlayoutPartInstanceModelSnapshotImpl implements PlayoutPartInstanceModelSn } } export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { + #context: JobContext + partInstanceImpl: DBPartInstance pieceInstancesImpl: Map @@ -169,25 +172,21 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { } constructor( + context: JobContext, partInstance: DBPartInstance, pieceInstances: PieceInstanceWithExpectedPackagesFull[], hasChanges: boolean ) { + this.#context = context this.partInstanceImpl = partInstance this.#partInstanceHasChanges = hasChanges this.pieceInstancesImpl = new Map() for (const { pieceInstance, expectedPackages } of pieceInstances) { - // const expectedPackagesConverted = wrapPackagesForPieceInstance( - // studio._id, - // partInstance, - // pieceInstance._id, - // expectedPackages - // ) - this.pieceInstancesImpl.set( pieceInstance._id, new PlayoutPieceInstanceModelImpl( + this.#context, pieceInstance, expectedPackages, hasChanges, @@ -219,6 +218,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { this.pieceInstancesImpl.set( pieceInstanceId, new PlayoutPieceInstanceModelImpl( + this.#context, pieceInstance.PieceInstance, pieceInstance.ExpectedPackages, pieceInstance.HasChanges, @@ -273,6 +273,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { const expectedPackages = this.#convertExpectedPackagesForPieceInstance(pieceInstance, pieceExpectedPackages) const pieceInstanceModel = new PlayoutPieceInstanceModelImpl( + this.#context, pieceInstance, expectedPackages, true, @@ -318,7 +319,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { } // Don't preserve any ExpectedPackages, the existing ones will suffice - const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(newInstance, [], true, null) + const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(this.#context, newInstance, [], true, null) this.pieceInstancesImpl.set(newInstance._id, pieceInstanceModel) return pieceInstanceModel @@ -328,7 +329,12 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { pieceInstance: ReadonlyDeep, expectedPackages: ReadonlyDeep ): ExpectedPackageDBFromPieceInstance[] { - return wrapPackagesForPieceInstance(this.studioId, this.partInstanceImpl, pieceInstance._id, expectedPackages) + return wrapPackagesForPieceInstance( + this.#context.studioId, + this.partInstanceImpl, + pieceInstance._id, + expectedPackages + ) } insertPlannedPiece( @@ -356,6 +362,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { const expectedPackages = this.#convertExpectedPackagesForPieceInstance(newPieceInstance, pieceExpectedPackages) const pieceInstanceModel = new PlayoutPieceInstanceModelImpl( + this.#context, newPieceInstance, expectedPackages, true, @@ -398,7 +405,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { } setupPieceInstanceInfiniteProperties(newPieceInstance) - const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(newPieceInstance, [], true, null) + const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(this.#context, newPieceInstance, [], true, null) this.pieceInstancesImpl.set(newPieceInstance._id, pieceInstanceModel) return pieceInstanceModel @@ -463,6 +470,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { this.pieceInstancesImpl.set( pieceInstance._id, new PlayoutPieceInstanceModelImpl( + this.#context, pieceInstance, newExpectedPackages, true, @@ -486,13 +494,14 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { return existingPieceInstance } else { const newExpectedPackages = wrapPackagesForPieceInstance( - studio._id, + this.#context.studioId, this.partInstanceImpl, doc._id, expectedPackages ) const newPieceInstance = new PlayoutPieceInstanceModelImpl( + this.#context, clone(doc), newExpectedPackages, true, diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts index fea93bf27a..04b119f802 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts @@ -15,8 +15,11 @@ import { diffAndReturnLatestObjects, } from '../../../ingest/model/implementation/utils' import { generateExpectedPackageBases } from '../../../ingest/expectedPackages' +import { JobContext } from '../../../jobs' export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel { + #context: JobContext + /** * The raw mutable PieceInstance * Danger: This should not be modified externally, this is exposed for cloning and saving purposes @@ -95,11 +98,13 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel } constructor( + context: JobContext, pieceInstances: PieceInstance, expectedPackages: ExpectedPackageDBFromPieceInstance[], hasChanges: boolean, expectedPackagesWithChanges: ExpectedPackageId[] | null ) { + this.#context = context this.PieceInstanceImpl = pieceInstances this.#expectedPackages = expectedPackages this.#hasChanges = hasChanges @@ -174,7 +179,7 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel setExpectedPackages(expectedPackages: ReadonlyDeep[]): void { // nocommit - refactor this into a simpler type than `ExpectedPackagesStore` or just reuse that? - const bases = generateExpectedPackageBases(studio._id, this.PieceInstanceImpl._id, expectedPackages) + const bases = generateExpectedPackageBases(this.#context.studioId, this.PieceInstanceImpl._id, expectedPackages) const newExpectedPackages: ExpectedPackageDBFromPieceInstance[] = bases.map((base) => ({ ...base, From 2a879ad1e1bc69d83f10808ed78c9cb9e99c229b Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 3 Sep 2024 13:17:30 +0100 Subject: [PATCH 10/22] wip: --- .../ingest/__tests__/expectedPackages.test.ts | 4 +- .../job-worker/src/ingest/expectedPackages.ts | 84 +------------- .../src/ingest/generationSegment.ts | 4 +- .../src/ingest/model/IngestPartModel.ts | 10 +- .../src/ingest/model/IngestSegmentModel.ts | 9 +- .../model/implementation/IngestModelImpl.ts | 11 +- .../implementation/IngestPartModelImpl.ts | 8 +- .../implementation/IngestSegmentModelImpl.ts | 106 ++++++++++++++++-- packages/job-worker/src/ingest/packageInfo.ts | 10 +- .../PlayoutPartInstanceModelImpl.ts | 2 +- .../PlayoutPieceInstanceModelImpl.ts | 2 +- 11 files changed, 131 insertions(+), 119 deletions(-) diff --git a/packages/job-worker/src/ingest/__tests__/expectedPackages.test.ts b/packages/job-worker/src/ingest/__tests__/expectedPackages.test.ts index ec920872bd..5f066561be 100644 --- a/packages/job-worker/src/ingest/__tests__/expectedPackages.test.ts +++ b/packages/job-worker/src/ingest/__tests__/expectedPackages.test.ts @@ -6,7 +6,7 @@ import { protectString } from '@sofie-automation/corelib/dist/protectedString' import { defaultPart, defaultPiece, defaultAdLibPiece } from '../../__mocks__/defaultCollectionObjects' import { LAYER_IDS } from '../../__mocks__/presetCollections' import { ExpectedPackage, PieceLifespan, VTContent } from '@sofie-automation/blueprints-integration' -import { updateExpectedPackagesForPartModel } from '../expectedPackages' +import { updateExpectedMediaAndPlayoutItemsForPartModel } from '../expectedPackages' import { MockJobContext, setupDefaultJobEnvironment } from '../../__mocks__/context' import { ReadonlyDeep } from 'type-fest' import { IngestPartModel } from '../model/IngestPartModel' @@ -141,7 +141,7 @@ describe('Expected Media Items', () => { }, } - updateExpectedPackagesForPartModel(context, partModel) + updateExpectedMediaAndPlayoutItemsForPartModel(context, partModel) expect(setExpectedPackages).toHaveBeenCalledTimes(1) expect(setExpectedPackages.mock.calls[0][0]).toHaveLength(4) diff --git a/packages/job-worker/src/ingest/expectedPackages.ts b/packages/job-worker/src/ingest/expectedPackages.ts index 6996ab34a1..5fe97f3e7d 100644 --- a/packages/job-worker/src/ingest/expectedPackages.ts +++ b/packages/job-worker/src/ingest/expectedPackages.ts @@ -1,22 +1,16 @@ -import { AdLibAction } from '@sofie-automation/corelib/dist/dataModel/AdlibAction' -import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { BucketAdLibAction } from '@sofie-automation/corelib/dist/dataModel/BucketAdLibAction' import { BucketAdLib } from '@sofie-automation/corelib/dist/dataModel/BucketAdLibPiece' import { ExpectedPackageDBType, - ExpectedPackageDBFromPiece, - ExpectedPackageDBFromAdLibAction, ExpectedPackageDBFromBucketAdLib, ExpectedPackageDBFromBucketAdLibAction, ExpectedPackageDBFromStudioBaselineObjects, getContentVersionHash, getExpectedPackageId, - ExpectedPackageFromRundown, ExpectedPackageDBBaseSimple, ExpectedPackageDBFromPieceInstance, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { - SegmentId, RundownId, AdLibActionId, PieceId, @@ -26,7 +20,6 @@ import { StudioId, PieceInstanceId, } from '@sofie-automation/corelib/dist/dataModel/Ids' -import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' import { saveIntoDb } from '../db/changes' import { PlayoutModel } from '../playout/model/PlayoutModel' import { StudioPlayoutModel } from '../studio/model/StudioPlayoutModel' @@ -45,34 +38,9 @@ import { IngestPartModel } from './model/IngestPartModel' import { clone } from '@sofie-automation/corelib/dist/lib' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' -export function updateExpectedPackagesForPartModel(context: JobContext, part: IngestPartModel): void { +export function updateExpectedMediaAndPlayoutItemsForPartModel(context: JobContext, part: IngestPartModel): void { updateExpectedMediaItemsForPartModel(context, part) updateExpectedPlayoutItemsForPartModel(context, part) - - const expectedPackages: ExpectedPackageFromRundown[] = [ - ...generateExpectedPackagesForPiece( - context.studio, - part.part.rundownId, - part.part.segmentId, - part.pieces, - ExpectedPackageDBType.PIECE - ), - ...generateExpectedPackagesForPiece( - context.studio, - part.part.rundownId, - part.part.segmentId, - part.adLibPieces, - ExpectedPackageDBType.ADLIB_PIECE - ), - ...generateExpectedPackagesForAdlibAction( - context.studio, - part.part.rundownId, - part.part.segmentId, - part.adLibActions - ), - ] - - part.setExpectedPackages(expectedPackages) } export async function updateExpectedMediaAndPlayoutItemsForRundownBaseline( @@ -84,56 +52,6 @@ export async function updateExpectedMediaAndPlayoutItemsForRundownBaseline( await updateExpectedPlayoutItemsForRundownBaseline(context, ingestModel, baseline) } -function generateExpectedPackagesForPiece( - studio: ReadonlyDeep, - rundownId: RundownId, - segmentId: SegmentId, - pieces: ReadonlyDeep[], - type: ExpectedPackageDBType.PIECE | ExpectedPackageDBType.ADLIB_PIECE -) { - const packages: ExpectedPackageDBFromPiece[] = [] - for (const piece of pieces) { - const partId = 'startPartId' in piece ? piece.startPartId : piece.partId - if (piece.expectedPackages && partId) { - const bases = generateExpectedPackageBases(studio._id, piece._id, piece.expectedPackages) - for (const base of bases) { - packages.push({ - ...base, - rundownId, - segmentId, - partId, - pieceId: piece._id, - fromPieceType: type, - }) - } - } - } - return packages -} -function generateExpectedPackagesForAdlibAction( - studio: ReadonlyDeep, - rundownId: RundownId, - segmentId: SegmentId, - actions: ReadonlyDeep -) { - const packages: ExpectedPackageDBFromAdLibAction[] = [] - for (const action of actions) { - if (action.expectedPackages) { - const bases = generateExpectedPackageBases(studio._id, action._id, action.expectedPackages) - for (const base of bases) { - packages.push({ - ...base, - rundownId, - segmentId, - partId: action.partId, - pieceId: action._id, - fromPieceType: ExpectedPackageDBType.ADLIB_ACTION, - }) - } - } - } - return packages -} function generateExpectedPackagesForBucketAdlib(studio: ReadonlyDeep, adlibs: BucketAdLib[]) { const packages: ExpectedPackageDBFromBucketAdLib[] = [] for (const adlib of adlibs) { diff --git a/packages/job-worker/src/ingest/generationSegment.ts b/packages/job-worker/src/ingest/generationSegment.ts index 3b4b50a6e5..585d0b0775 100644 --- a/packages/job-worker/src/ingest/generationSegment.ts +++ b/packages/job-worker/src/ingest/generationSegment.ts @@ -19,7 +19,7 @@ import { NoteSeverity, } from '@sofie-automation/blueprints-integration' import { wrapTranslatableMessageFromBlueprints } from '@sofie-automation/corelib/dist/TranslatableMessage' -import { updateExpectedPackagesForPartModel } from './expectedPackages' +import { updateExpectedMediaAndPlayoutItemsForPartModel } from './expectedPackages' import { IngestReplacePartType, IngestSegmentModel } from './model/IngestSegmentModel' import { ReadonlyDeep } from 'type-fest' import { Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' @@ -402,7 +402,7 @@ function updateModelWithGeneratedPart( ) const partModel = segmentModel.replacePart(part, processedPieces, adlibPieces, adlibActions) - updateExpectedPackagesForPartModel(context, partModel) + updateExpectedMediaAndPlayoutItemsForPartModel(context, partModel) } /** diff --git a/packages/job-worker/src/ingest/model/IngestPartModel.ts b/packages/job-worker/src/ingest/model/IngestPartModel.ts index 610be862c6..a6ab5fd4b1 100644 --- a/packages/job-worker/src/ingest/model/IngestPartModel.ts +++ b/packages/job-worker/src/ingest/model/IngestPartModel.ts @@ -61,9 +61,9 @@ export interface IngestPartModel extends IngestPartModelReadonly { */ setExpectedMediaItems(expectedMediaItems: ExpectedMediaItemRundown[]): void - /** - * Set the ExpectedPackages for the contents of this Part - * @param expectedPackages The new ExpectedPackages - */ - setExpectedPackages(expectedPackages: ExpectedPackageFromRundown[]): void + // /** + // * Set the ExpectedPackages for the contents of this Part + // * @param expectedPackages The new ExpectedPackages + // */ + // setExpectedPackages(expectedPackages: ExpectedPackageFromRundown[]): void } diff --git a/packages/job-worker/src/ingest/model/IngestSegmentModel.ts b/packages/job-worker/src/ingest/model/IngestSegmentModel.ts index d708cb5228..2a232dfabb 100644 --- a/packages/job-worker/src/ingest/model/IngestSegmentModel.ts +++ b/packages/job-worker/src/ingest/model/IngestSegmentModel.ts @@ -6,6 +6,7 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { AdLibAction } from '@sofie-automation/corelib/dist/dataModel/AdlibAction' +import { PostProcessDoc } from '../../blueprints/postProcess' export interface IngestSegmentModelReadonly { /** @@ -81,14 +82,14 @@ export interface IngestSegmentModel extends IngestSegmentModelReadonly { * Replace or insert a Part into this Segment * @param part New part data * @param pieces Pieces to add to the Part - * @param adLibPiece AdLib Pieces to add to the Part + * @param adLibPieces AdLib Pieces to add to the Part * @param adLibActions AdLib Actions to add to the Part */ replacePart( part: IngestReplacePartType, - pieces: Piece[], - adLibPiece: AdLibPiece[], - adLibActions: AdLibAction[] + pieces: PostProcessDoc[], + adLibPieces: PostProcessDoc[], + adLibActions: PostProcessDoc[] ): IngestPartModel } diff --git a/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts b/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts index adaff4192c..96a4d1d00c 100644 --- a/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts +++ b/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts @@ -215,7 +215,7 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { ) ) this.segmentsImpl.set(segment._id, { - segmentModel: new IngestSegmentModelImpl(false, segment, parts), + segmentModel: new IngestSegmentModelImpl(this.context, false, segment, parts), deleted: false, }) } @@ -375,7 +375,7 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { } const oldSegment = this.segmentsImpl.get(segment._id) - const newSegment = new IngestSegmentModelImpl(true, segment, [], oldSegment?.segmentModel) + const newSegment = new IngestSegmentModelImpl(this.context, true, segment, [], oldSegment?.segmentModel) this.segmentsImpl.set(segment._id, { segmentModel: newSegment, deleted: false, @@ -394,7 +394,12 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { this.segmentsImpl.set(oldId, { // Make a minimal clone of the old segment, the reference is needed to issue a mongo delete - segmentModel: new IngestSegmentModelImpl(false, clone(existingSegment.segmentModel.segment), []), + segmentModel: new IngestSegmentModelImpl( + this.context, + false, + clone(existingSegment.segmentModel.segment), + [] + ), deleted: true, }) diff --git a/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts b/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts index a830b34cb4..064f8d5d5f 100644 --- a/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts +++ b/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts @@ -234,8 +234,8 @@ export class IngestPartModelImpl implements IngestPartModel { setExpectedMediaItems(expectedMediaItems: ExpectedMediaItemRundown[]): void { this.expectedPackagesStore.setExpectedMediaItems(expectedMediaItems) } - setExpectedPackages(expectedPackages: ExpectedPackageFromRundown[]): void { - // Future: should these be here, or held as part of each adlib/piece? - this.expectedPackagesStore.setExpectedPackages(expectedPackages) - } + // setExpectedPackages(expectedPackages: ExpectedPackageFromRundown[]): void { + // // Future: should these be here, or held as part of each adlib/piece? + // this.expectedPackagesStore.setExpectedPackages(expectedPackages) + // } } diff --git a/packages/job-worker/src/ingest/model/implementation/IngestSegmentModelImpl.ts b/packages/job-worker/src/ingest/model/implementation/IngestSegmentModelImpl.ts index aae742db65..49f7235b27 100644 --- a/packages/job-worker/src/ingest/model/implementation/IngestSegmentModelImpl.ts +++ b/packages/job-worker/src/ingest/model/implementation/IngestSegmentModelImpl.ts @@ -1,4 +1,4 @@ -import { PartId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { PartId, RundownId, SegmentId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { DBSegment, SegmentOrphanedReason } from '@sofie-automation/corelib/dist/dataModel/Segment' import { IngestReplacePartType, IngestSegmentModel } from '../IngestSegmentModel' @@ -12,6 +12,15 @@ import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' import { calculatePartExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings' import { clone } from '@sofie-automation/corelib/dist/lib' import { getPartId } from '../../lib' +import { PostProcessDoc, unwrapPostProccessDocs } from '../../../blueprints/postProcess' +import { + ExpectedPackageDBFromAdLibAction, + ExpectedPackageDBFromPiece, + ExpectedPackageDBType, + ExpectedPackageFromRundown, +} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { generateExpectedPackageBases } from '../../expectedPackages' +import { JobContext } from '../../../jobs' /** * A light wrapper around the IngestPartModel, so that we can track the deletions while still accessing the contents @@ -22,6 +31,7 @@ interface PartWrapper { } export class IngestSegmentModelImpl implements IngestSegmentModel { + readonly #context: JobContext readonly segmentImpl: DBSegment readonly partsImpl: Map @@ -105,11 +115,14 @@ export class IngestSegmentModelImpl implements IngestSegmentModel { } constructor( + context: JobContext, isBeingCreated: boolean, segment: DBSegment, currentParts: IngestPartModelImpl[], previousSegment?: IngestSegmentModelImpl ) { + this.#context = context + currentParts.sort((a, b) => a.part._rank - b.part._rank) this.#segmentHasChanges = isBeingCreated @@ -206,16 +219,19 @@ export class IngestSegmentModelImpl implements IngestSegmentModel { replacePart( rawPart: IngestReplacePartType, - pieces: Piece[], - adLibPiece: AdLibPiece[], - adLibActions: AdLibAction[] + pieces: PostProcessDoc[], + adLibPieces: PostProcessDoc[], + adLibActions: PostProcessDoc[] ): IngestPartModel { const part: DBPart = { ...rawPart, _id: this.getPartIdFromExternalId(rawPart.externalId), rundownId: this.segment.rundownId, segmentId: this.segment._id, - expectedDurationWithTransition: calculatePartExpectedDurationWithTransition(rawPart, pieces), + expectedDurationWithTransition: calculatePartExpectedDurationWithTransition( + rawPart, + unwrapPostProccessDocs(pieces) + ), } // We don't need to worry about this being present on other Segments. The caller must make sure it gets removed if needed, @@ -224,15 +240,38 @@ export class IngestSegmentModelImpl implements IngestSegmentModel { const oldPart = this.partsImpl.get(part._id) + const expectedPackages: ExpectedPackageFromRundown[] = [ + ...generateExpectedPackagesForPiece( + this.#context.studioId, + part.rundownId, + part.segmentId, + pieces, + ExpectedPackageDBType.PIECE + ), + ...generateExpectedPackagesForPiece( + this.#context.studioId, + part.rundownId, + part.segmentId, + adLibPieces, + ExpectedPackageDBType.ADLIB_PIECE + ), + ...generateExpectedPackagesForAdlibAction( + this.#context.studioId, + part.rundownId, + part.segmentId, + adLibActions + ), + ] + const partModel = new IngestPartModelImpl( !oldPart, clone(part), - clone(pieces), - clone(adLibPiece), - clone(adLibActions), + clone(unwrapPostProccessDocs(pieces)), + clone(unwrapPostProccessDocs(adLibPieces)), + clone(unwrapPostProccessDocs(adLibActions)), [], [], - [] + expectedPackages ) partModel.setOwnerIds(this.segment.rundownId, this.segment._id) @@ -243,3 +282,52 @@ export class IngestSegmentModelImpl implements IngestSegmentModel { return partModel } } + +function generateExpectedPackagesForPiece( + studioId: StudioId, + rundownId: RundownId, + segmentId: SegmentId, + pieces: ReadonlyDeep>[], + type: ExpectedPackageDBType.PIECE | ExpectedPackageDBType.ADLIB_PIECE +): ExpectedPackageDBFromPiece[] { + const packages: ExpectedPackageDBFromPiece[] = [] + for (const { doc, expectedPackages } of pieces) { + const partId = 'startPartId' in doc ? doc.startPartId : doc.partId + if (partId) { + const bases = generateExpectedPackageBases(studioId, doc._id, expectedPackages) + for (const base of bases) { + packages.push({ + ...base, + rundownId, + segmentId, + partId, + pieceId: doc._id, + fromPieceType: type, + }) + } + } + } + return packages +} +function generateExpectedPackagesForAdlibAction( + studioId: StudioId, + rundownId: RundownId, + segmentId: SegmentId, + actions: ReadonlyDeep[]> +): ExpectedPackageDBFromAdLibAction[] { + const packages: ExpectedPackageDBFromAdLibAction[] = [] + for (const { doc, expectedPackages } of actions) { + const bases = generateExpectedPackageBases(studioId, doc._id, expectedPackages) + for (const base of bases) { + packages.push({ + ...base, + rundownId, + segmentId, + partId: doc.partId, + pieceId: doc._id, + fromPieceType: ExpectedPackageDBType.ADLIB_ACTION, + }) + } + } + return packages +} diff --git a/packages/job-worker/src/ingest/packageInfo.ts b/packages/job-worker/src/ingest/packageInfo.ts index 6910e74d2c..f961aa6cd7 100644 --- a/packages/job-worker/src/ingest/packageInfo.ts +++ b/packages/job-worker/src/ingest/packageInfo.ts @@ -8,7 +8,6 @@ import { logger } from '../logging' import { JobContext } from '../jobs' import { regenerateSegmentsFromIngestData } from './generationSegment' import { UpdateIngestRundownAction, runIngestJob, runWithRundownLock } from './lock' -import { updateExpectedPackagesForPartModel } from './expectedPackages' import { loadIngestModelFromRundown } from './model/implementation/LoadIngestModel' /** @@ -23,11 +22,12 @@ export async function handleExpectedPackagesRegenerate( const ingestModel = await loadIngestModelFromRundown(context, rundownLock, rundown) - for (const part of ingestModel.getAllOrderedParts()) { - updateExpectedPackagesForPartModel(context, part) - } - // nocommit - reimplement? + + // for (const part of ingestModel.getAllOrderedParts()) { + // updateExpectedMediaAndPlayoutItemsForPartModel(context, part) + // } + // await updateExpectedPackagesForRundownBaseline(context, ingestModel, undefined, true) await ingestModel.saveAllToDatabase() diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts index 0d9aa14d90..9665393ddd 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts @@ -77,7 +77,7 @@ class PlayoutPartInstanceModelSnapshotImpl implements PlayoutPartInstanceModelSn } } export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { - #context: JobContext + readonly #context: JobContext partInstanceImpl: DBPartInstance pieceInstancesImpl: Map diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts index 04b119f802..ebaf14600f 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts @@ -18,7 +18,7 @@ import { generateExpectedPackageBases } from '../../../ingest/expectedPackages' import { JobContext } from '../../../jobs' export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel { - #context: JobContext + readonly #context: JobContext /** * The raw mutable PieceInstance From 6367bdd0494058a3fdefbd95aefae8c5743f9236 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 3 Sep 2024 13:26:55 +0100 Subject: [PATCH 11/22] wip: fix meteor types --- meteor/lib/collections/ExpectedPackages.ts | 11 ++++++----- meteor/server/api/__tests__/cleanup.test.ts | 7 +------ meteor/server/api/ingest/packageInfo.ts | 2 +- .../packageManager/expectedPackages/generate.ts | 13 ++++++++----- packages/shared-lib/src/package-manager/helpers.ts | 9 ++++++--- .../shared-lib/src/package-manager/publications.ts | 3 ++- 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/meteor/lib/collections/ExpectedPackages.ts b/meteor/lib/collections/ExpectedPackages.ts index cfffad7842..280bcaffd6 100644 --- a/meteor/lib/collections/ExpectedPackages.ts +++ b/meteor/lib/collections/ExpectedPackages.ts @@ -6,9 +6,10 @@ import { htmlTemplateGetFileNamesFromSteps, } from '@sofie-automation/shared-lib/dist/package-manager/helpers' import deepExtend from 'deep-extend' +import { ReadonlyDeep } from 'type-fest' export function getPreviewPackageSettings( - expectedPackage: ExpectedPackage.Any + expectedPackage: ReadonlyDeep ): ExpectedPackage.SideEffectPreviewSettings | undefined { if (expectedPackage.type === ExpectedPackage.PackageType.MEDIA_FILE) { const packagePath = expectedPackage.content.filePath @@ -29,7 +30,7 @@ export function getPreviewPackageSettings( } } export function getThumbnailPackageSettings( - expectedPackage: ExpectedPackage.Any + expectedPackage: ReadonlyDeep ): ExpectedPackage.SideEffectThumbnailSettings | undefined { if (expectedPackage.type === ExpectedPackage.PackageType.MEDIA_FILE) { const packagePath = expectedPackage.content.filePath @@ -51,7 +52,7 @@ export function getThumbnailPackageSettings( } } export function getSideEffect( - expectedPackage: ExpectedPackage.Base, + expectedPackage: ReadonlyDeep, studio: Pick ): ExpectedPackage.Base['sideEffect'] { return deepExtend( @@ -59,8 +60,8 @@ export function getSideEffect( literal({ previewContainerId: studio.previewContainerIds[0], // just pick the first. Todo: something else? thumbnailContainerId: studio.thumbnailContainerIds[0], // just pick the first. Todo: something else? - previewPackageSettings: getPreviewPackageSettings(expectedPackage as ExpectedPackage.Any), - thumbnailPackageSettings: getThumbnailPackageSettings(expectedPackage as ExpectedPackage.Any), + previewPackageSettings: getPreviewPackageSettings(expectedPackage as ReadonlyDeep), + thumbnailPackageSettings: getThumbnailPackageSettings(expectedPackage as ReadonlyDeep), }), expectedPackage.sideEffect ) diff --git a/meteor/server/api/__tests__/cleanup.test.ts b/meteor/server/api/__tests__/cleanup.test.ts index b9f2f34ec4..1f5e9882bf 100644 --- a/meteor/server/api/__tests__/cleanup.test.ts +++ b/meteor/server/api/__tests__/cleanup.test.ts @@ -256,19 +256,14 @@ async function setDefaultDatatoDB(env: DefaultEnvironment, now: number) { _id: getRandomId(), blueprintPackageId: '', bucketId, - content: {} as any, + package: {} as any, contentVersionHash: '', created: 0, fromPieceType: '' as any, - layers: [], pieceId, rundownId, segmentId, - sideEffect: {} as any, studioId, - sources: {} as any, - type: '' as any, - version: {} as any, }) await ExpectedPackageWorkStatuses.insertAsync({ _id: getRandomId(), diff --git a/meteor/server/api/ingest/packageInfo.ts b/meteor/server/api/ingest/packageInfo.ts index d9fc1b3068..5223d6ab85 100644 --- a/meteor/server/api/ingest/packageInfo.ts +++ b/meteor/server/api/ingest/packageInfo.ts @@ -27,7 +27,7 @@ export async function onUpdatedPackageInfo(packageId: ExpectedPackageId, _doc: P return } - if (pkg.listenToPackageInfoUpdates) { + if (pkg.package.listenToPackageInfoUpdates) { switch (pkg.fromPieceType) { case ExpectedPackageDBType.PIECE: case ExpectedPackageDBType.ADLIB_PIECE: diff --git a/meteor/server/publications/packageManager/expectedPackages/generate.ts b/meteor/server/publications/packageManager/expectedPackages/generate.ts index 7d2194eca5..324d324a27 100644 --- a/meteor/server/publications/packageManager/expectedPackages/generate.ts +++ b/meteor/server/publications/packageManager/expectedPackages/generate.ts @@ -47,7 +47,7 @@ export async function updateCollectionForExpectedPackageIds( // Map the expectedPackages onto their specified layer: const allDeviceIds = new Set() - for (const layerName of packageDoc.layers) { + for (const layerName of packageDoc.package.layers) { const layerDeviceIds = layerNameToDeviceIds.get(layerName) for (const deviceId of layerDeviceIds || []) { allDeviceIds.add(deviceId) @@ -61,8 +61,9 @@ export async function updateCollectionForExpectedPackageIds( const routedPackage = generateExpectedPackageForDevice( studio, { - ...packageDoc, + ...packageDoc.package, _id: unprotectString(packageDoc._id), + rundownId: 'rundownId' in packageDoc ? packageDoc.rundownId : undefined, }, deviceId, null, @@ -207,11 +208,13 @@ function generateExpectedPackageForDevice( if (!combinedTargets.length) { logger.warn(`Pub.expectedPackagesForDevice: No targets found for "${expectedPackage._id}"`) } - expectedPackage.sideEffect = getSideEffect(expectedPackage, studio) return { _id: protectString(`${expectedPackage._id}_${deviceId}_${pieceInstanceId}`), - expectedPackage: expectedPackage, + expectedPackage: { + ...expectedPackage, + sideEffect: getSideEffect(expectedPackage, studio), + }, sources: combinedSources, targets: combinedTargets, priority: priority, @@ -239,7 +242,7 @@ function calculateCombinedSource( for (const accessorId of accessorIds) { const sourceAccessor: Accessor.Any | undefined = lookedUpSource.container.accessors[accessorId] - const packageAccessor: AccessorOnPackage.Any | undefined = packageSource.accessors?.[accessorId] + const packageAccessor: ReadonlyDeep | undefined = packageSource.accessors?.[accessorId] if (packageAccessor && sourceAccessor && packageAccessor.type === sourceAccessor.type) { combinedSource.accessors[accessorId] = deepExtend({}, sourceAccessor, packageAccessor) diff --git a/packages/shared-lib/src/package-manager/helpers.ts b/packages/shared-lib/src/package-manager/helpers.ts index c6eb6d8bb1..d9591b1df5 100644 --- a/packages/shared-lib/src/package-manager/helpers.ts +++ b/packages/shared-lib/src/package-manager/helpers.ts @@ -1,11 +1,14 @@ +import { ReadonlyDeep } from 'type-fest' import { ExpectedPackage } from './package' // Note: These functions are copied from Package Manager type Steps = Required['steps'] -export function htmlTemplateGetSteps(version: ExpectedPackage.ExpectedPackageHtmlTemplate['version']): Steps { - let steps: Steps +export function htmlTemplateGetSteps( + version: ReadonlyDeep +): ReadonlyDeep { + let steps: ReadonlyDeep if (version.casparCG) { // Generate a set of steps for standard CasparCG templates const casparData = version.casparCG.data @@ -29,7 +32,7 @@ export function htmlTemplateGetSteps(version: ExpectedPackage.ExpectedPackageHtm } return steps } -export function htmlTemplateGetFileNamesFromSteps(steps: Steps): { +export function htmlTemplateGetFileNamesFromSteps(steps: ReadonlyDeep): { /** List of all file names that will be output from in the steps */ fileNames: string[] /** The "main file", ie the file that will carry the main metadata */ diff --git a/packages/shared-lib/src/package-manager/publications.ts b/packages/shared-lib/src/package-manager/publications.ts index 48ffbe0953..09d1c845ed 100644 --- a/packages/shared-lib/src/package-manager/publications.ts +++ b/packages/shared-lib/src/package-manager/publications.ts @@ -1,6 +1,7 @@ import { ExpectedPackage, PackageContainer, PackageContainerOnPackage } from './package' import { PeripheralDeviceId, PieceInstanceId, RundownId, RundownPlaylistId } from '../core/model/Ids' import { ProtectedString } from '../lib/protectedString' +import { ReadonlyDeep } from 'type-fest' export interface PackageManagerPlayoutContext { _id: PeripheralDeviceId @@ -27,7 +28,7 @@ export interface PackageManagerPackageContainers { export type PackageManagerExpectedPackageId = ProtectedString<'PackageManagerExpectedPackage'> -export type PackageManagerExpectedPackageBase = ExpectedPackage.Base & { rundownId?: RundownId } +export type PackageManagerExpectedPackageBase = ReadonlyDeep & { rundownId?: RundownId } export interface PackageManagerExpectedPackage { /** Unique id of the expectedPackage */ From 0538cee275ecd691fab9fdaf74885646e09d6c92 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 3 Sep 2024 13:48:13 +0100 Subject: [PATCH 12/22] wip --- packages/corelib/src/dataModel/AdLibPiece.ts | 2 +- packages/corelib/src/dataModel/Piece.ts | 6 +- .../job-worker/src/blueprints/context/lib.ts | 25 ++++++--- .../PartAndPieceInstanceActionService.ts | 17 ++++-- .../job-worker/src/blueprints/postProcess.ts | 4 +- packages/job-worker/src/playout/adlibJobs.ts | 51 +++++++++++++++-- packages/job-worker/src/playout/adlibUtils.ts | 56 ++++++++++++++----- .../job-worker/src/playout/bucketAdlibJobs.ts | 3 +- .../src/playout/model/PlayoutModel.ts | 8 ++- .../model/implementation/PlayoutModelImpl.ts | 13 +++-- .../PlayoutPartInstanceModelImpl.ts | 1 + 11 files changed, 146 insertions(+), 40 deletions(-) diff --git a/packages/corelib/src/dataModel/AdLibPiece.ts b/packages/corelib/src/dataModel/AdLibPiece.ts index 1c21e5a61a..6194a7f53e 100644 --- a/packages/corelib/src/dataModel/AdLibPiece.ts +++ b/packages/corelib/src/dataModel/AdLibPiece.ts @@ -2,7 +2,7 @@ import { IBlueprintAdLibPiece, SomeContent } from '@sofie-automation/blueprints- import { RundownId, PartId } from './Ids' import { PieceGeneric } from './Piece' -export interface AdLibPiece extends PieceGeneric, Omit { +export interface AdLibPiece extends PieceGeneric, Omit { /** Rundown this AdLib belongs to */ rundownId: RundownId diff --git a/packages/corelib/src/dataModel/Piece.ts b/packages/corelib/src/dataModel/Piece.ts index fe5a42586d..4d80a46437 100644 --- a/packages/corelib/src/dataModel/Piece.ts +++ b/packages/corelib/src/dataModel/Piece.ts @@ -38,16 +38,18 @@ export enum PieceStatusCode { } /** A Single item in a Part: script, VT, cameras */ -export interface PieceGeneric extends Omit { +export interface PieceGeneric extends Omit { _id: PieceId // TODO - this should be moved to the implementation types content: SomeContent + expectedPackages: PieceExpectedPackage[] + /** Stringified timelineObjects */ timelineObjectsString: PieceTimelineObjectsBlob } -export interface Piece extends PieceGeneric, Omit { +export interface Piece extends PieceGeneric, Omit { /** * This is the id of the rundown this piece starts playing in. * Currently this is the only rundown the piece could be playing in diff --git a/packages/job-worker/src/blueprints/context/lib.ts b/packages/job-worker/src/blueprints/context/lib.ts index d252727527..a2c2d9e2a6 100644 --- a/packages/job-worker/src/blueprints/context/lib.ts +++ b/packages/job-worker/src/blueprints/context/lib.ts @@ -129,7 +129,7 @@ function convertPieceInstanceToBlueprintsInner( fromPreviousPlayhead: pieceInstance.infinite.fromPreviousPlayhead, }) : undefined, - piece: convertPieceToBlueprints(pieceInstance.piece), + piece: convertPieceToBlueprints(pieceInstance.piece, expectedPackages), } return obj @@ -156,7 +156,7 @@ export function convertResolvedPieceInstanceToBlueprints( pieceInstance: ResolvedPieceInstance ): IBlueprintResolvedPieceInstance { const obj: Complete = { - ...convertPieceInstanceToBlueprintsInner(pieceInstance.instance, undefined), + ...convertPieceInstanceToBlueprintsInner(pieceInstance.instance, []), // nocommit - is this ok? resolvedStart: pieceInstance.resolvedStart, resolvedDuration: pieceInstance.resolvedDuration, } @@ -184,7 +184,10 @@ export function convertPartInstanceToBlueprints(partInstance: ReadonlyDeep): Complete { +function convertPieceGenericToBlueprintsInner( + piece: ReadonlyDeep, + expectedPackages: ReadonlyDeep +): Complete { const obj: Complete = { externalId: piece.externalId, name: piece.name, @@ -199,7 +202,7 @@ function convertPieceGenericToBlueprintsInner(piece: ReadonlyDeep) expectedPlayoutItems: clone(piece.expectedPlayoutItems), tags: clone(piece.tags), allowDirectPlay: clone(piece.allowDirectPlay), - expectedPackages: clone(piece.expectedPackages), + expectedPackages: clone(expectedPackages), hasSideEffects: piece.hasSideEffects, content: { ...clone(piece.content), @@ -216,9 +219,12 @@ function convertPieceGenericToBlueprintsInner(piece: ReadonlyDeep) * @param piece the Piece to convert * @returns a cloned complete and clean IBlueprintPieceDB */ -export function convertPieceToBlueprints(piece: ReadonlyDeep): IBlueprintPieceDB { +export function convertPieceToBlueprints( + piece: ReadonlyDeep, + expectedPackages: ReadonlyDeep +): IBlueprintPieceDB { const obj: Complete = { - ...convertPieceGenericToBlueprintsInner(piece), + ...convertPieceGenericToBlueprintsInner(piece, expectedPackages), _id: unprotectString(piece._id), enable: clone(piece.enable), virtual: piece.virtual, @@ -276,9 +282,12 @@ export function convertPartToBlueprints(part: ReadonlyDeep): IBlueprintP * @param adLib the AdLibPiece to convert * @returns a cloned complete and clean IBlueprintAdLibPieceDB */ -export function convertAdLibPieceToBlueprints(adLib: ReadonlyDeep): IBlueprintAdLibPieceDB { +export function convertAdLibPieceToBlueprints( + adLib: ReadonlyDeep, + expectedPackages: ReadonlyDeep +): IBlueprintAdLibPieceDB { const obj: Complete = { - ...convertPieceGenericToBlueprintsInner(adLib), + ...convertPieceGenericToBlueprintsInner(adLib, expectedPackages), _id: unprotectString(adLib._id), _rank: adLib._rank, invalid: adLib.invalid, diff --git a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts index e7ef19d252..2c33cd89a9 100644 --- a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts +++ b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts @@ -163,7 +163,13 @@ export class PartAndPieceInstanceActionService { query ) - return lastPieceInstance && convertPieceInstanceToBlueprints(lastPieceInstance) + return ( + lastPieceInstance && + convertPieceInstanceToBlueprints( + lastPieceInstance.pieceInstance, + unwrapExpectedPackages(lastPieceInstance.expectedPackages) + ) + ) } async findLastScriptedPieceOnLayer( @@ -190,14 +196,17 @@ export class PartAndPieceInstanceActionService { const sourceLayerId = Array.isArray(sourceLayerId0) ? sourceLayerId0 : [sourceLayerId0] - const lastPiece = await innerFindLastScriptedPieceOnLayer( + const lastPieceAndPackages = await innerFindLastScriptedPieceOnLayer( this._context, this._playoutModel, sourceLayerId, query ) - return lastPiece && convertPieceToBlueprints(lastPiece) + return ( + lastPieceAndPackages && + convertPieceToBlueprints(lastPieceAndPackages.doc, lastPieceAndPackages.expectedPackages) + ) } async getPartInstanceForPreviousPiece(piece: IBlueprintPieceInstance): Promise { @@ -419,7 +428,7 @@ export class PartAndPieceInstanceActionService { this._rundown, currentPartInstance, newPart, - pieces, + pieces.map((p) => ({ piece: p.doc, expectedPackages: p.expectedPackages })), undefined ) diff --git a/packages/job-worker/src/blueprints/postProcess.ts b/packages/job-worker/src/blueprints/postProcess.ts index d040800672..a7d79f84d3 100644 --- a/packages/job-worker/src/blueprints/postProcess.ts +++ b/packages/job-worker/src/blueprints/postProcess.ts @@ -62,7 +62,7 @@ function getIdHash(docType: string, usedIds: Map, uniqueId: stri export interface PostProcessDoc { doc: T - expectedPackages: ExpectedPackage.Any[] + expectedPackages: ReadonlyDeep } export function unwrapPostProccessDocs(docs: PostProcessDoc[]): T[] { @@ -288,7 +288,7 @@ export function postProcessAdLibPieces( externalId: orgAdlib.externalId, _rank: orgAdlib._rank, - expectedPackages: processedExpectedPackages, // convertExpectedPackages(processedExpectedPackages), + expectedPackages: convertExpectedPackages(processedExpectedPackages), expectedPlayoutItems: orgAdlib.expectedPlayoutItems, privateData: orgAdlib.privateData, diff --git a/packages/job-worker/src/playout/adlibJobs.ts b/packages/job-worker/src/playout/adlibJobs.ts index d2506ef434..aed498ac3b 100644 --- a/packages/job-worker/src/playout/adlibJobs.ts +++ b/packages/job-worker/src/playout/adlibJobs.ts @@ -23,13 +23,21 @@ import { syncPlayheadInfinitesForNextPartInstance } from './infinites' import { UserError, UserErrorMessage } from '@sofie-automation/corelib/dist/error' import { PieceId, PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' -import { IBlueprintDirectPlayType, IBlueprintPieceType } from '@sofie-automation/blueprints-integration' +import { + ExpectedPackage, + IBlueprintDirectPlayType, + IBlueprintPieceType, +} from '@sofie-automation/blueprints-integration' import { ReadonlyDeep } from 'type-fest' import { WatchedPackagesHelper } from '../blueprints/context/watchedPackages' import { innerFindLastPieceOnLayer, innerStartOrQueueAdLibPiece, innerStopPieces } from './adlibUtils' import _ = require('underscore') import { executeActionInner } from './adlibAction' import { PlayoutPieceInstanceModel } from './model/PlayoutPieceInstanceModel' +import { + ExpectedPackageDBType, + unwrapExpectedPackages, +} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' /** * Play an existing Piece in the Rundown as an AdLib @@ -226,16 +234,29 @@ export async function handleAdLibPieceStart(context: JobContext, data: AdlibPiec .map((r) => r.rundown._id) let adLibPiece: AdLibPiece | BucketAdLib | undefined + let expectedPackages: ReadonlyDeep if (data.pieceType === 'baseline') { adLibPiece = await context.directCollections.RundownBaselineAdLibPieces.findOne({ _id: data.adLibPieceId, rundownId: { $in: safeRundownIds }, }) + const rawExpectedPackages = await context.directCollections.ExpectedPackages.findFetch({ + fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_PIECE, + fromPieceId: data.adLibPieceId, + rundownId: { $in: safeRundownIds }, + }) + expectedPackages = unwrapExpectedPackages(rawExpectedPackages) } else if (data.pieceType === 'normal') { adLibPiece = await context.directCollections.AdLibPieces.findOne({ _id: data.adLibPieceId, rundownId: { $in: safeRundownIds }, }) + const rawExpectedPackages = await context.directCollections.ExpectedPackages.findFetch({ + fromPieceType: ExpectedPackageDBType.ADLIB_PIECE, + fromPieceId: data.adLibPieceId, + rundownId: { $in: safeRundownIds }, + }) + expectedPackages = unwrapExpectedPackages(rawExpectedPackages) } else if (data.pieceType === 'bucket') { const bucketAdlib = await context.directCollections.BucketAdLibPieces.findOne({ _id: data.adLibPieceId, @@ -252,6 +273,12 @@ export async function handleAdLibPieceStart(context: JobContext, data: AdlibPiec } adLibPiece = bucketAdlib + expectedPackages = bucketAdlib?.expectedPackages ?? [] + } else { + throw UserError.from( + new Error(`AdLib type "${data.pieceType}" not supported!`), + UserErrorMessage.AdlibNotFound + ) } if (!adLibPiece) @@ -270,7 +297,15 @@ export async function handleAdLibPieceStart(context: JobContext, data: AdlibPiec UserErrorMessage.AdlibUnplayable ) - await innerStartOrQueueAdLibPiece(context, playoutModel, rundown, !!data.queue, partInstance, adLibPiece) + await innerStartOrQueueAdLibPiece( + context, + playoutModel, + rundown, + !!data.queue, + partInstance, + adLibPiece, + expectedPackages + ) } ) } @@ -320,8 +355,16 @@ export async function handleStartStickyPieceOnSourceLayer( throw UserError.create(UserErrorMessage.SourceLayerStickyNothingFound) } - const lastPiece = convertPieceToAdLibPiece(context, lastPieceInstance.piece) - await innerStartOrQueueAdLibPiece(context, playoutModel, rundown, false, currentPartInstance, lastPiece) + const lastPiece = convertPieceToAdLibPiece(context, lastPieceInstance.pieceInstance.piece) + await innerStartOrQueueAdLibPiece( + context, + playoutModel, + rundown, + false, + currentPartInstance, + lastPiece, + unwrapExpectedPackages(lastPieceInstance.expectedPackages) + ) } ) } diff --git a/packages/job-worker/src/playout/adlibUtils.ts b/packages/job-worker/src/playout/adlibUtils.ts index 297fba69a1..fb8996ac9b 100644 --- a/packages/job-worker/src/playout/adlibUtils.ts +++ b/packages/job-worker/src/playout/adlibUtils.ts @@ -2,12 +2,15 @@ import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { BucketAdLib } from '@sofie-automation/corelib/dist/dataModel/BucketAdLibPiece' import { PartInstanceId, PieceId, PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' -import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' +import { + PieceInstance, + PieceInstanceWithExpectedPackagesFull, +} from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { assertNever, getRandomId, getRank } from '@sofie-automation/corelib/dist/lib' import { MongoQuery } from '@sofie-automation/corelib/dist/mongo' import { getCurrentTime } from '../lib' import { JobContext } from '../jobs' -import { PlayoutModel } from './model/PlayoutModel' +import { AdlibPieceWithPackages, PlayoutModel } from './model/PlayoutModel' import { PlayoutPartInstanceModel } from './model/PlayoutPartInstanceModel' import { fetchPiecesThatMayBeActiveForPart, @@ -17,7 +20,7 @@ import { import { convertAdLibToGenericPiece } from './pieces' import { getResolvedPiecesForCurrentPartInstance } from './resolvedPieces' import { updateTimeline } from './timeline/generate' -import { PieceLifespan } from '@sofie-automation/blueprints-integration' +import { ExpectedPackage, PieceLifespan } from '@sofie-automation/blueprints-integration' import { SourceLayers } from '@sofie-automation/corelib/dist/dataModel/ShowStyleBase' import { updatePartInstanceRanksAfterAdlib } from '../updatePartInstanceRanksAndOrphanedState' import { setNextPart } from './setNext' @@ -27,6 +30,12 @@ import { ReadonlyDeep } from 'type-fest' import { PlayoutRundownModel } from './model/PlayoutRundownModel' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { protectString } from '@sofie-automation/corelib/dist/protectedString' +import { + ExpectedPackageDBFromPieceInstance, + ExpectedPackageDBType, + unwrapExpectedPackages, +} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { PostProcessDoc } from '../blueprints/postProcess' export async function innerStartOrQueueAdLibPiece( context: JobContext, @@ -34,7 +43,8 @@ export async function innerStartOrQueueAdLibPiece( rundown: PlayoutRundownModel, queue: boolean, currentPartInstance: PlayoutPartInstanceModel, - adLibPiece: AdLibPiece | BucketAdLib + adLibPiece: AdLibPiece | BucketAdLib, + expectedPackages: ReadonlyDeep ): Promise { const span = context.startSpan('innerStartOrQueueAdLibPiece') let queuedPartInstanceId: PartInstanceId | undefined @@ -55,7 +65,7 @@ export async function innerStartOrQueueAdLibPiece( rundown, currentPartInstance, adlibbedPart, - [genericAdlibPiece], + [{ piece: genericAdlibPiece, expectedPackages }], adLibPiece._id ) queuedPartInstanceId = newPartInstance.partInstance._id @@ -63,7 +73,7 @@ export async function innerStartOrQueueAdLibPiece( // syncPlayheadInfinitesForNextPartInstance is handled by setNextPart } else { const genericAdlibPiece = convertAdLibToGenericPiece(adLibPiece, false) - currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, adLibPiece._id, adLibPiece.expectedPackages ?? []) + currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, adLibPiece._id, expectedPackages) await syncPlayheadInfinitesForNextPartInstance( context, @@ -85,7 +95,7 @@ export async function innerFindLastPieceOnLayer( sourceLayerId: string[], originalOnly: boolean, customQuery?: MongoQuery -): Promise { +): Promise { const span = context.startSpan('innerFindLastPieceOnLayer') const rundownIds = playoutModel.getRundownIds() @@ -110,11 +120,22 @@ export async function innerFindLastPieceOnLayer( // Note: This does not want to use the in-memory model, as we want to search as far back as we can // TODO - will this cause problems? - return context.directCollections.PieceInstances.findOne(query, { + const pieceInstance = await context.directCollections.PieceInstances.findOne(query, { sort: { plannedStartedPlayback: -1, }, }) + + if (!pieceInstance) return undefined + + const expectedPackages = (await context.directCollections.ExpectedPackages.findFetch({ + fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, + pieceInstanceId: pieceInstance._id, + partInstanceId: pieceInstance.partInstanceId, + rundownId: { $in: rundownIds }, + })) as ExpectedPackageDBFromPieceInstance[] + + return { pieceInstance, expectedPackages } } export async function innerFindLastScriptedPieceOnLayer( @@ -122,7 +143,7 @@ export async function innerFindLastScriptedPieceOnLayer( playoutModel: PlayoutModel, sourceLayerId: string[], customQuery?: MongoQuery -): Promise { +): Promise | undefined> { const span = context.startSpan('innerFindLastScriptedPieceOnLayer') const playlist = playoutModel.playlist @@ -179,11 +200,20 @@ export async function innerFindLastScriptedPieceOnLayer( return } - const fullPiece = await context.directCollections.Pieces.findOne(piece._id) - if (!fullPiece) return + const [fullPiece, expectedPackages] = await Promise.all([ + context.directCollections.Pieces.findOne(piece._id), + context.directCollections.ExpectedPackages.findFetch({ + fromPieceType: ExpectedPackageDBType.PIECE, + pieceId: piece._id, + rundownId: { $in: rundownIds }, + }), + ]) if (span) span.end() - return fullPiece + + if (!fullPiece) return + + return { doc: fullPiece, expectedPackages: unwrapExpectedPackages(expectedPackages) } } function updateRankForAdlibbedPartInstance( @@ -212,7 +242,7 @@ export async function insertQueuedPartWithPieces( rundown: PlayoutRundownModel, currentPartInstance: PlayoutPartInstanceModel, newPart: Omit, - initialPieces: Omit[], + initialPieces: AdlibPieceWithPackages[], fromAdlibId: PieceId | undefined ): Promise { const span = context.startSpan('insertQueuedPartWithPieces') diff --git a/packages/job-worker/src/playout/bucketAdlibJobs.ts b/packages/job-worker/src/playout/bucketAdlibJobs.ts index a6cbff8248..784e700442 100644 --- a/packages/job-worker/src/playout/bucketAdlibJobs.ts +++ b/packages/job-worker/src/playout/bucketAdlibJobs.ts @@ -53,7 +53,8 @@ export async function handleExecuteBucketAdLibOrAction( fullRundown, !!bucketAdLib.toBeQueued, partInstance, - bucketAdLib + bucketAdLib, + bucketAdLib.expectedPackages ?? [] ) await playoutModel.saveAllToDatabase() return {} diff --git a/packages/job-worker/src/playout/model/PlayoutModel.ts b/packages/job-worker/src/playout/model/PlayoutModel.ts index 38273807ff..ed1833014c 100644 --- a/packages/job-worker/src/playout/model/PlayoutModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutModel.ts @@ -29,10 +29,16 @@ import { PlayoutPartInstanceModel } from './PlayoutPartInstanceModel' import { PeripheralDevice } from '@sofie-automation/corelib/dist/dataModel/PeripheralDevice' import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' import { PlayoutPieceInstanceModel } from './PlayoutPieceInstanceModel' +import { ExpectedPackage } from '@sofie-automation/blueprints-integration' export type DeferredFunction = (playoutModel: PlayoutModel) => void | Promise export type DeferredAfterSaveFunction = (playoutModel: PlayoutModelReadonly) => void | Promise +export interface AdlibPieceWithPackages { + piece: Omit + expectedPackages: ReadonlyDeep +} + /** * A lightweight version of the `PlayoutModel`, used to perform some pre-checks before loading the full model * @@ -206,7 +212,7 @@ export interface PlayoutModel extends PlayoutModelReadonly, StudioPlayoutModelBa */ createAdlibbedPartInstance( part: Omit, - pieces: Omit[], // nocommit - expectedPackages? + pieces: AdlibPieceWithPackages[], fromAdlibId: PieceId | undefined, infinitePieceInstances: PieceInstanceWithExpectedPackages[] ): PlayoutPartInstanceModel diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts index bd99981fad..ee6adac74d 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts @@ -23,7 +23,6 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' import { getPieceInstanceIdForPiece, - PieceInstancePiece, PieceInstanceWithExpectedPackages, } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { @@ -47,7 +46,13 @@ import { getCurrentTime } from '../../../lib' import { protectString } from '@sofie-automation/shared-lib/dist/lib/protectedString' import { queuePartInstanceTimingEvent } from '../../timings/events' import { IS_PRODUCTION } from '../../../environment' -import { DeferredAfterSaveFunction, DeferredFunction, PlayoutModel, PlayoutModelReadonly } from '../PlayoutModel' +import { + AdlibPieceWithPackages, + DeferredAfterSaveFunction, + DeferredFunction, + PlayoutModel, + PlayoutModelReadonly, +} from '../PlayoutModel' import { writePartInstancesAndPieceInstancesAndExpectedPackages, writeAdlibTestingSegments } from './SavePlayoutModel' import { PlayoutPieceInstanceModel } from '../PlayoutPieceInstanceModel' import { DatabasePersistedModel } from '../../../modelBase' @@ -305,7 +310,7 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou createAdlibbedPartInstance( part: Omit, - pieces: Omit[], + pieces: AdlibPieceWithPackages[], fromAdlibId: PieceId | undefined, infinitePieceInstances: PieceInstanceWithExpectedPackages[] ): PlayoutPartInstanceModel { @@ -348,7 +353,7 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou ) for (const piece of pieces) { - partInstance.insertAdlibbedPiece(piece, fromAdlibId, piece.expectedPackages ?? []) + partInstance.insertAdlibbedPiece(piece.piece, fromAdlibId, piece.expectedPackages) } partInstance.recalculateExpectedDurationWithTransition() diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts index 9665393ddd..9a76457599 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts @@ -399,6 +399,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { virtual: true, content: {}, timelineObjectsString: EmptyPieceTimelineObjectsBlob, + expectedPackages: [], }, dynamicallyInserted: getCurrentTime(), From 1464a4ce2daae87f374a0c6d2053cb0d57d3b045 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 3 Sep 2024 13:59:34 +0100 Subject: [PATCH 13/22] wip: a new concept to reduce the number of mongo documents? --- packages/corelib/src/dataModel/AdlibAction.ts | 6 ++- .../src/dataModel/BucketAdLibAction.ts | 4 ++ .../corelib/src/dataModel/ExpectedPackages.ts | 46 +++++++++++++++++++ .../src/dataModel/ExternalMessageQueue.ts | 3 +- .../job-worker/src/blueprints/context/lib.ts | 7 ++- .../job-worker/src/blueprints/postProcess.ts | 32 +++++++------ .../src/ingest/expectedMediaItems.ts | 2 +- .../src/ingest/model/IngestModel.ts | 5 +- .../model/implementation/IngestModelImpl.ts | 16 ++++++- .../src/ingest/syncChangesToPartInstance.ts | 10 +++- packages/job-worker/src/playout/adlibJobs.ts | 15 +++++- packages/job-worker/src/playout/snapshot.ts | 2 +- 12 files changed, 118 insertions(+), 30 deletions(-) diff --git a/packages/corelib/src/dataModel/AdlibAction.ts b/packages/corelib/src/dataModel/AdlibAction.ts index 86a05c9734..ab93b3d2aa 100644 --- a/packages/corelib/src/dataModel/AdlibAction.ts +++ b/packages/corelib/src/dataModel/AdlibAction.ts @@ -1,13 +1,13 @@ import { IBlueprintActionManifest } from '@sofie-automation/blueprints-integration' import { ArrayElement } from '../lib' import { ITranslatableMessage } from '../TranslatableMessage' -import { ProtectedStringProperties } from '../protectedString' import { RundownId, AdLibActionId, PartId } from './Ids' +import { PieceExpectedPackage } from './Piece' /** The following extended interface allows assigning namespace information to the actions as they are stored in the * database after being emitted from the blueprints */ -export interface AdLibActionCommon extends ProtectedStringProperties { +export interface AdLibActionCommon extends Omit { rundownId: RundownId display: IBlueprintActionManifest['display'] & { // this property can be a string if the name is modified by the User @@ -22,6 +22,8 @@ export interface AdLibActionCommon extends ProtectedStringProperties { _id: BucketAdLibActionId bucketId: BucketId + // nocommit - temporary copy to avoid type errors + expectedPackages: IBlueprintActionManifest['expectedPackages'] + externalId: string /** diff --git a/packages/corelib/src/dataModel/ExpectedPackages.ts b/packages/corelib/src/dataModel/ExpectedPackages.ts index 060724850a..f5015e2c4b 100644 --- a/packages/corelib/src/dataModel/ExpectedPackages.ts +++ b/packages/corelib/src/dataModel/ExpectedPackages.ts @@ -72,6 +72,48 @@ export interface ExpectedPackageDBBaseSimple { created: Time } +/* + * What about this new concept. The aim here is to avoid the constant inserting and deleting of expectedPackages during playout, and avoiding duplicate packages with the same content. + * The idea is to have a single expectedPackage for each 'content'. + * Ingest will 'deduplicate' the packages produced by the blueprints, with playout able to reference them with pieceInstanceIds. + * + * During the ingest save phase, it will need to reload the `playoutSources` property, in case it has changed. And if there are uses remaining, it will need to keep the package after clearing the `ingestSources`. + * During playout operations, pieceInstanceIds will be added and removed as needed. If there remains no sources (of either type), then the document can be removed. If an in-progress ingest tried to reclaim it, it will get reinserted. + * + * Playout can then load just the ones referenced by piece instances, and just before it needs to use them (for bluerpint types or something), can ensure that everything needed has been loaded. + * During a take, any packages referenced by the previous(?) partinstance must be removed. + * When doing a reset of the rundown, all playout references must be removed. + * When inserting/removing pieceinstances, the expectedPackages must be updated. + */ +export interface ExpectedPackageDBNew extends ExpectedPackageDBBase { + /** The rundown of the Piece this package belongs to */ + rundownId: RundownId + + _id: ExpectedPackageId // derived from rundownId and contentVersionHash + + ingestSources: Array< + | { + fromPieceType: ExpectedPackageDBType.PIECE | ExpectedPackageDBType.ADLIB_PIECE + /** The Piece this package belongs to */ + pieceId: PieceId + /** The Part this package belongs to */ + partId: PartId + /** The Segment this package belongs to */ + segmentId: SegmentId + } + | { + fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_PIECE + /** The Piece this package belongs to */ + pieceId: PieceId + } + > + + playoutSources: { + /** Any playout PieceInstance. This is limited to the current and next partInstances */ // nocommit - verify this + pieceInstanceIds: PieceInstanceId[] + } +} + export interface ExpectedPackageDBBase extends ExpectedPackageDBBaseSimple { fromPieceType: ExpectedPackageDBType } @@ -85,6 +127,9 @@ export interface ExpectedPackageDBFromPiece extends ExpectedPackageDBBase { segmentId: SegmentId /** The rundown of the Piece this package belongs to */ rundownId: RundownId + + /** Any PieceInstance */ + pieceInstanceIds: PieceInstanceId[] } export interface ExpectedPackageDBFromBaselineAdLibPiece extends ExpectedPackageDBBase { @@ -143,6 +188,7 @@ export interface ExpectedPackageDBFromBucketAdLibAction extends ExpectedPackageD } export interface ExpectedPackageDBFromPieceInstance extends ExpectedPackageDBBase { fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE + /** The PieceInstance this package belongs to */ pieceInstanceId: PieceInstanceId /** The PartInstance this package belongs to */ diff --git a/packages/corelib/src/dataModel/ExternalMessageQueue.ts b/packages/corelib/src/dataModel/ExternalMessageQueue.ts index d84fbd08f1..46b6101aee 100644 --- a/packages/corelib/src/dataModel/ExternalMessageQueue.ts +++ b/packages/corelib/src/dataModel/ExternalMessageQueue.ts @@ -3,10 +3,9 @@ import { Time, IBlueprintExternalMessageQueueType, } from '@sofie-automation/blueprints-integration' -import { ProtectedStringProperties } from '../protectedString' import { ExternalMessageQueueObjId, StudioId, RundownId } from './Ids' -export interface ExternalMessageQueueObj extends ProtectedStringProperties { +export interface ExternalMessageQueueObj extends Omit { _id: ExternalMessageQueueObjId /** Id of the studio this message originates from */ studioId: StudioId diff --git a/packages/job-worker/src/blueprints/context/lib.ts b/packages/job-worker/src/blueprints/context/lib.ts index a2c2d9e2a6..3f898e0499 100644 --- a/packages/job-worker/src/blueprints/context/lib.ts +++ b/packages/job-worker/src/blueprints/context/lib.ts @@ -307,7 +307,10 @@ export function convertAdLibPieceToBlueprints( * @param action the AdLibAction to convert * @returns a cloned complete and clean IBlueprintActionManifest */ -export function convertAdLibActionToBlueprints(action: ReadonlyDeep): IBlueprintActionManifest { +export function convertAdLibActionToBlueprints( + action: ReadonlyDeep, + expectedPackages: ReadonlyDeep +): IBlueprintActionManifest { const obj: Complete = { externalId: action.externalId, actionId: action.actionId, @@ -320,7 +323,7 @@ export function convertAdLibActionToBlueprints(action: ReadonlyDeep display: clone(action.display), // TODO - type mismatch triggerModes: clone(action.triggerModes), // TODO - type mismatch expectedPlayoutItems: clone(action.expectedPlayoutItems), - expectedPackages: clone(action.expectedPackages), + expectedPackages: clone(expectedPackages), } return obj diff --git a/packages/job-worker/src/blueprints/postProcess.ts b/packages/job-worker/src/blueprints/postProcess.ts index a7d79f84d3..f1895703fb 100644 --- a/packages/job-worker/src/blueprints/postProcess.ts +++ b/packages/job-worker/src/blueprints/postProcess.ts @@ -143,7 +143,7 @@ export function postProcessPieces( hasSideEffects: orgPiece.hasSideEffects, abSessions: orgPiece.abSessions, notInVision: orgPiece.notInVision, - expectedPackages: processedExpectedPackages, // convertExpectedPackages(processedExpectedPackages), + expectedPackages: convertExpectedPackages(processedExpectedPackages), } if (piece.pieceType !== IBlueprintPieceType.Normal) { @@ -375,10 +375,9 @@ export function postProcessGlobalAdLibActions( externalId: action.externalId, _id: protectString(docId), rundownId: rundownId, - partId: undefined, ...processAdLibActionITranslatableMessages(action, blueprintId), - expectedPackages: processedExpectedPackages, // convertExpectedPackages(processedExpectedPackages), + expectedPackages: convertExpectedPackages(processedExpectedPackages), expectedPlayoutItems: action.expectedPlayoutItems, privateData: action.privateData, @@ -437,7 +436,7 @@ export function postProcessAdLibActions( partId: partId, ...processAdLibActionITranslatableMessages(action, blueprintId), - expectedPackages: processedExpectedPackages, // convertExpectedPackages(processedExpectedPackages), + expectedPackages: convertExpectedPackages(processedExpectedPackages), expectedPlayoutItems: action.expectedPlayoutItems, privateData: action.privateData, @@ -503,7 +502,7 @@ export function postProcessBucketAdLib( bucketId: BucketId, rank: number | undefined, importVersions: RundownImportVersions -): PostProcessDoc { +): BucketAdLib { const id: PieceId = protectString( getHash( `${showStyleCompound.showStyleVariantId}_${context.studioId}_${bucketId}_bucket_adlib_${ingestInfo.payload.externalId}` @@ -559,10 +558,12 @@ export function postProcessBucketAdLib( const timelineObjects = postProcessTimelineObjects(piece._id, blueprintId, itemOrig.content.timelineObjects) piece.timelineObjectsString = serializePieceTimelineObjectsBlob(timelineObjects) - return { - doc: piece, - expectedPackages: processedExpectedPackages, - } + // nocommit: TODO + // return { + // doc: piece, + // expectedPackages: processedExpectedPackages, + // } + return piece } /** @@ -585,7 +586,7 @@ export function postProcessBucketAction( bucketId: BucketId, rank: number | undefined, importVersions: RundownImportVersions -): PostProcessDoc { +): BucketAdLibAction { const id: AdLibActionId = protectString( getHash( `${showStyleCompound.showStyleVariantId}_${context.studioId}_${bucketId}_bucket_adlib_${ingestInfo.payload.externalId}` @@ -617,14 +618,15 @@ export function postProcessBucketAction( allVariants: itemOrig.allVariants, // Not used? - partId: undefined, uniquenessId: undefined, } - return { - doc: action, - expectedPackages: processedExpectedPackages, - } + // nocommit: TODO + // return { + // doc: action, + // expectedPackages: processedExpectedPackages, + // } + return action } /** diff --git a/packages/job-worker/src/ingest/expectedMediaItems.ts b/packages/job-worker/src/ingest/expectedMediaItems.ts index f694ab3bc7..73f3e67dac 100644 --- a/packages/job-worker/src/ingest/expectedMediaItems.ts +++ b/packages/job-worker/src/ingest/expectedMediaItems.ts @@ -119,7 +119,7 @@ function generateExpectedMediaItemsFull( ...generateExpectedMediaItems( doc._id, { - partId: doc.partId, + partId: 'partId' in doc ? doc.partId : undefined, rundownId: rundownId, }, studioId, diff --git a/packages/job-worker/src/ingest/model/IngestModel.ts b/packages/job-worker/src/ingest/model/IngestModel.ts index 666f396a1a..fca213b79b 100644 --- a/packages/job-worker/src/ingest/model/IngestModel.ts +++ b/packages/job-worker/src/ingest/model/IngestModel.ts @@ -2,6 +2,7 @@ import { ExpectedMediaItemRundown } from '@sofie-automation/corelib/dist/dataMod import { ExpectedPackageDBFromBaselineAdLibAction, ExpectedPackageDBFromBaselineAdLibPiece, + ExpectedPackageDBFromPiece, ExpectedPackageDBFromRundownBaselineObjects, ExpectedPackageFromRundown, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' @@ -138,7 +139,9 @@ export interface IngestModelReadonly { * Search for an AdLibPiece in all Parts of the Rundown * @param id Id of the AdLib Piece */ - findAdlibPiece(adLibPieceId: PieceId): ReadonlyDeep | undefined + findAdlibPieceAndPackages( + adLibPieceId: PieceId + ): { adlib: ReadonlyDeep; expectedPackages: ReadonlyDeep[] } | undefined /** * Search for an ExpectedPackage through the whole Rundown diff --git a/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts b/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts index 96a4d1d00c..9f846206fc 100644 --- a/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts +++ b/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts @@ -3,6 +3,7 @@ import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { ExpectedMediaItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedMediaItem' import { ExpectedPackageDB, + ExpectedPackageDBFromPiece, ExpectedPackageDBType, ExpectedPackageFromRundown, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' @@ -337,10 +338,21 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { return undefined } - findAdlibPiece(adLibPieceId: PieceId): ReadonlyDeep | undefined { + findAdlibPieceAndPackages( + adLibPieceId: PieceId + ): { adlib: ReadonlyDeep; expectedPackages: ReadonlyDeep[] } | undefined { for (const part of this.getAllOrderedParts()) { for (const adlib of part.adLibPieces) { - if (adlib._id === adLibPieceId) return adlib + if (adlib._id === adLibPieceId) { + const expectedPackages = part.expectedPackages.filter( + (p): p is ReadonlyDeep => + p.fromPieceType === ExpectedPackageDBType.ADLIB_PIECE && p.pieceId === adLibPieceId + ) + return { + adlib, + expectedPackages, + } + } } } return undefined diff --git a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts index 85d5712ee3..ddce363268 100644 --- a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts +++ b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts @@ -151,8 +151,14 @@ export async function syncChangesToPartInstances( const referencedAdlibs: IBlueprintAdLibPieceDB[] = [] for (const adLibPieceId of _.compact(pieceInstancesInPart.map((p) => p.pieceInstance.adLibSourceId))) { - const adLibPiece = ingestModel.findAdlibPiece(adLibPieceId) - if (adLibPiece) referencedAdlibs.push(convertAdLibPieceToBlueprints(adLibPiece)) + const adLibPiece = ingestModel.findAdlibPieceAndPackages(adLibPieceId) + if (adLibPiece) + referencedAdlibs.push( + convertAdLibPieceToBlueprints( + adLibPiece.adlib, + unwrapExpectedPackages(adLibPiece.expectedPackages) + ) + ) } const newResultData: BlueprintSyncIngestNewData = { diff --git a/packages/job-worker/src/playout/adlibJobs.ts b/packages/job-worker/src/playout/adlibJobs.ts index aed498ac3b..8c3b90e52d 100644 --- a/packages/job-worker/src/playout/adlibJobs.ts +++ b/packages/job-worker/src/playout/adlibJobs.ts @@ -96,16 +96,26 @@ export async function handleTakePieceAsAdlibNow(context: JobContext, data: TakeP } switch (pieceToCopy.allowDirectPlay.type) { - case IBlueprintDirectPlayType.AdLibPiece: + case IBlueprintDirectPlayType.AdLibPiece: { + const expectedPackages = pieceInstanceToCopy + ? pieceInstanceToCopy.pieceInstance.expectedPackages + : await context.directCollections.ExpectedPackages.findFetch({ + fromPieceType: ExpectedPackageDBType.PIECE, + fromPieceId: pieceToCopy._id, + rundownId: { $in: rundownIds }, + }) + await pieceTakeNowAsAdlib( context, playoutModel, showStyleCompound, currentPartInstance, pieceToCopy, + unwrapExpectedPackages(expectedPackages), pieceInstanceToCopy ) break + } case IBlueprintDirectPlayType.AdLibAction: { const executeProps = pieceToCopy.allowDirectPlay @@ -145,6 +155,7 @@ async function pieceTakeNowAsAdlib( showStyleBase: ReadonlyDeep, currentPartInstance: PlayoutPartInstanceModel, pieceToCopy: PieceInstancePiece, + expectedPackages: ReadonlyDeep, pieceInstanceToCopy: | { partInstance: PlayoutPartInstanceModel; pieceInstance: PlayoutPieceInstanceModel } | undefined @@ -153,7 +164,7 @@ async function pieceTakeNowAsAdlib( /*const newPieceInstance = */ currentPartInstance.insertAdlibbedPiece( genericAdlibPiece, pieceToCopy._id, - genericAdlibPiece.expectedPackages ?? [] + expectedPackages ) // Disable the original piece if from the same Part diff --git a/packages/job-worker/src/playout/snapshot.ts b/packages/job-worker/src/playout/snapshot.ts index c30c430d32..c4f50343bf 100644 --- a/packages/job-worker/src/playout/snapshot.ts +++ b/packages/job-worker/src/playout/snapshot.ts @@ -279,7 +279,7 @@ export async function handleRestorePlaylistSnapshot( ...snapshot.baselineAdLibActions, ]) { const oldId = adlib._id - if (adlib.partId) adlib.partId = partIdMap.get(adlib.partId) + if ('partId' in adlib && adlib.partId) adlib.partId = partIdMap.get(adlib.partId) adlib._id = getRandomId() pieceIdMap.set(oldId, adlib._id) } From 7078e23c31afe40efe5c933d3df7bd2979a804aa Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 4 Sep 2024 09:37:47 +0100 Subject: [PATCH 14/22] wip --- packages/corelib/src/dataModel/BucketAdLibAction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/corelib/src/dataModel/BucketAdLibAction.ts b/packages/corelib/src/dataModel/BucketAdLibAction.ts index 3bda4812cb..45740aa7d7 100644 --- a/packages/corelib/src/dataModel/BucketAdLibAction.ts +++ b/packages/corelib/src/dataModel/BucketAdLibAction.ts @@ -4,7 +4,7 @@ import { AdLibActionCommon } from './AdlibAction' import { BucketAdLibIngestInfo } from './BucketAdLibPiece' import { IBlueprintActionManifest } from '@sofie-automation/blueprints-integration' -export interface BucketAdLibAction extends Omit { +export interface BucketAdLibAction extends Omit { _id: BucketAdLibActionId bucketId: BucketId From d0dd8ca88412cafaad660d39c9493d4318174cd2 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 4 Sep 2024 15:29:45 +0100 Subject: [PATCH 15/22] wip: start prototyping new flow --- .../corelib/src/dataModel/ExpectedPackages.ts | 98 +++++++--- packages/corelib/src/dataModel/Piece.ts | 9 +- .../corelib/src/dataModel/PieceInstance.ts | 6 +- .../SyncIngestUpdateToPartInstanceContext.ts | 35 ++-- .../PartAndPieceInstanceActionService.test.ts | 4 +- .../job-worker/src/blueprints/postProcess.ts | 98 +++++----- .../job-worker/src/ingest/expectedPackages.ts | 23 +-- .../src/ingest/generationRundown.ts | 149 +++++++++------ .../src/ingest/model/IngestModel.ts | 3 +- .../src/ingest/model/IngestSegmentModel.ts | 8 +- .../implementation/ExpectedPackagesStore.ts | 50 +++--- .../implementation/IngestPartModelImpl.ts | 7 +- .../implementation/IngestSegmentModelImpl.ts | 170 ++++++++++-------- .../model/implementation/SaveIngestModel.ts | 13 +- .../src/ingest/syncChangesToPartInstance.ts | 2 +- packages/job-worker/src/playout/adlibJobs.ts | 8 +- packages/job-worker/src/playout/adlibUtils.ts | 13 +- packages/job-worker/src/playout/infinites.ts | 81 +-------- .../model/PlayoutExpectedPackagesModel.ts | 23 +++ .../src/playout/model/PlayoutModel.ts | 25 +-- .../playout/model/PlayoutPartInstanceModel.ts | 26 +-- .../model/PlayoutPieceInstanceModel.ts | 14 +- .../model/implementation/LoadPlayoutModel.ts | 41 ++--- .../PlayoutExpectedPackagesModelImpl.ts | 30 ++++ .../model/implementation/PlayoutModelImpl.ts | 108 +++++------ .../PlayoutPartInstanceModelImpl.ts | 163 ++++++----------- .../PlayoutPieceInstanceModelImpl.ts | 107 ++++------- .../model/implementation/SavePlayoutModel.ts | 31 +--- .../__tests__/SavePlayoutModel.spec.ts | 14 +- 29 files changed, 649 insertions(+), 710 deletions(-) create mode 100644 packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts create mode 100644 packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts diff --git a/packages/corelib/src/dataModel/ExpectedPackages.ts b/packages/corelib/src/dataModel/ExpectedPackages.ts index f5015e2c4b..1e190ad8a7 100644 --- a/packages/corelib/src/dataModel/ExpectedPackages.ts +++ b/packages/corelib/src/dataModel/ExpectedPackages.ts @@ -85,28 +85,26 @@ export interface ExpectedPackageDBBaseSimple { * When doing a reset of the rundown, all playout references must be removed. * When inserting/removing pieceinstances, the expectedPackages must be updated. */ -export interface ExpectedPackageDBNew extends ExpectedPackageDBBase { +export interface ExpectedPackageDBNew { + _id: ExpectedPackageId // derived from rundownId and hash of `package` + + // /** The local package id - as given by the blueprints */ + // blueprintPackageId: string // TODO - remove this? + + /** The studio of the Rundown of the Piece this package belongs to */ + studioId: StudioId + /** The rundown of the Piece this package belongs to */ rundownId: RundownId - _id: ExpectedPackageId // derived from rundownId and contentVersionHash - - ingestSources: Array< - | { - fromPieceType: ExpectedPackageDBType.PIECE | ExpectedPackageDBType.ADLIB_PIECE - /** The Piece this package belongs to */ - pieceId: PieceId - /** The Part this package belongs to */ - partId: PartId - /** The Segment this package belongs to */ - segmentId: SegmentId - } - | { - fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_PIECE - /** The Piece this package belongs to */ - pieceId: PieceId - } - > + /** Hash that changes whenever the content or version changes. See getContentVersionHash() */ + contentVersionHash: string + + created: Time + + package: ReadonlyDeep + + ingestSources: ExpectedPackageIngestSource[] playoutSources: { /** Any playout PieceInstance. This is limited to the current and next partInstances */ // nocommit - verify this @@ -114,6 +112,50 @@ export interface ExpectedPackageDBNew extends ExpectedPackageDBBase { } } +export interface ExpectedPackageIngestSourcePiece { + fromPieceType: ExpectedPackageDBType.PIECE | ExpectedPackageDBType.ADLIB_PIECE + /** The Piece this package belongs to */ + pieceId: PieceId + /** The Part this package belongs to */ + partId: PartId + /** The Segment this package belongs to */ + segmentId: SegmentId +} +export interface ExpectedPackageIngestSourceAdlibAction { + fromPieceType: ExpectedPackageDBType.ADLIB_ACTION + /** The Piece this package belongs to */ + pieceId: AdLibActionId + /** The Part this package belongs to */ + partId: PartId + /** The Segment this package belongs to */ + segmentId: SegmentId +} +export interface ExpectedPackageIngestSourceBaselineAdlibPiece { + fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_PIECE + /** The Piece this package belongs to */ + pieceId: PieceId +} +export interface ExpectedPackageIngestSourceBaselineAdlibAction { + fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_ACTION + /** The Piece this package belongs to */ + pieceId: RundownBaselineAdLibActionId +} +export interface ExpectedPackageIngestSourceBaselineObjects { + fromPieceType: ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS +} + +export type ExpectedPackageIngestSource = + | ExpectedPackageIngestSourcePiece + | ExpectedPackageIngestSourceAdlibAction + | ExpectedPackageIngestSourceBaselineAdlibPiece + | ExpectedPackageIngestSourceBaselineAdlibAction + | ExpectedPackageIngestSourceBaselineObjects + +export interface ExpectedPackageWithId { + _id: ExpectedPackageId + expectedPackage: ReadonlyDeep +} + export interface ExpectedPackageDBBase extends ExpectedPackageDBBaseSimple { fromPieceType: ExpectedPackageDBType } @@ -127,9 +169,6 @@ export interface ExpectedPackageDBFromPiece extends ExpectedPackageDBBase { segmentId: SegmentId /** The rundown of the Piece this package belongs to */ rundownId: RundownId - - /** Any PieceInstance */ - pieceInstanceIds: PieceInstanceId[] } export interface ExpectedPackageDBFromBaselineAdLibPiece extends ExpectedPackageDBBase { @@ -227,6 +266,21 @@ export function getExpectedPackageId( return protectString(`${ownerId}_${getHash(localExpectedPackageId)}`) } +export function getExpectedPackageIdNew( + /** _id of the rundown*/ + rundownId: RundownId, + /** The locally unique id of the expectedPackage */ + expectedPackage: ReadonlyDeep +): ExpectedPackageId { + // This may be too agressive, but we don't know how to merge some of the properties + const objHash = hashObj({ + ...expectedPackage, + listenToPackageInfoUpdates: false, // Not relevant for the hash + } satisfies ReadonlyDeep) + + return protectString(`${rundownId}_${getHash(objHash)}`) +} + export function unwrapExpectedPackages( expectedPackages: ReadonlyDeep | undefined ): ReadonlyDeep { diff --git a/packages/corelib/src/dataModel/Piece.ts b/packages/corelib/src/dataModel/Piece.ts index 4d80a46437..21e08620f2 100644 --- a/packages/corelib/src/dataModel/Piece.ts +++ b/packages/corelib/src/dataModel/Piece.ts @@ -6,7 +6,7 @@ import { SomeContent, } from '@sofie-automation/blueprints-integration' import { ProtectedString, protectString, unprotectString } from '../protectedString' -import { PieceId, RundownId, SegmentId, PartId } from './Ids' +import { PieceId, RundownId, SegmentId, PartId, ExpectedPackageId } from './Ids' /** A generic list of playback availability statuses for a Piece */ export enum PieceStatusCode { @@ -77,8 +77,11 @@ export interface Piece extends PieceGeneric, Omit diff --git a/packages/corelib/src/dataModel/PieceInstance.ts b/packages/corelib/src/dataModel/PieceInstance.ts index abc2d88d41..3250e286eb 100644 --- a/packages/corelib/src/dataModel/PieceInstance.ts +++ b/packages/corelib/src/dataModel/PieceInstance.ts @@ -94,11 +94,7 @@ export interface ResolvedPieceInstance { timelinePriority: number } -export interface PieceInstanceWithExpectedPackages { - pieceInstance: PieceInstance - expectedPackages: ReadonlyDeep -} - +// nocommit - remove me export interface PieceInstanceWithExpectedPackagesFull { pieceInstance: PieceInstance expectedPackages: ExpectedPackageDBFromPieceInstance[] diff --git a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts index 56bde0c2b1..2eee58b51d 100644 --- a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts +++ b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts @@ -1,8 +1,5 @@ import { PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids' -import { - PieceInstance, - PieceInstanceWithExpectedPackages, -} from '@sofie-automation/corelib/dist/dataModel/PieceInstance' +import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { normalizeArrayToMapFunc, omit } from '@sofie-automation/corelib/dist/lib' import { protectString, protectStringArray, unprotectStringArray } from '@sofie-automation/corelib/dist/protectedString' import { PlayoutPartInstanceModel } from '../../playout/model/PlayoutPartInstanceModel' @@ -19,7 +16,6 @@ import { IBlueprintPartInstance, SomeContent, WithTimeline, - ExpectedPackage, } from '@sofie-automation/blueprints-integration' import { postProcessPieces, postProcessTimelineObjects } from '../postProcess' import { @@ -41,7 +37,7 @@ export class SyncIngestUpdateToPartInstanceContext extends RundownUserContext implements ISyncIngestUpdateToPartInstanceContext { - private readonly _proposedPieceInstances: Map> + private readonly _proposedPieceInstances: Map> private partInstance: PlayoutPartInstanceModel | null @@ -52,7 +48,7 @@ export class SyncIngestUpdateToPartInstanceContext showStyleCompound: ReadonlyDeep, rundown: ReadonlyDeep, partInstance: PlayoutPartInstanceModel, - proposedPieceInstances: ReadonlyDeep, + proposedPieceInstances: ReadonlyDeep, private playStatus: 'previous' | 'current' | 'next' ) { super( @@ -66,7 +62,7 @@ export class SyncIngestUpdateToPartInstanceContext this.partInstance = partInstance - this._proposedPieceInstances = normalizeArrayToMapFunc(proposedPieceInstances, (p) => p.pieceInstance._id) + this._proposedPieceInstances = normalizeArrayToMapFunc(proposedPieceInstances, (p) => p._id) } syncPieceInstance( @@ -88,7 +84,7 @@ export class SyncIngestUpdateToPartInstanceContext { ...modifiedPiece, // Some properties arent allowed to be changed - lifespan: proposedPieceInstance.pieceInstance.piece.lifespan, + lifespan: proposedPieceInstance.piece.lifespan, }, ], this.showStyleCompound.blueprintId, @@ -99,17 +95,20 @@ export class SyncIngestUpdateToPartInstanceContext )[0] : null - const newExpectedPackages: ReadonlyDeep = postProcessedPiece - ? postProcessedPiece.expectedPackages - : proposedPieceInstance.expectedPackages + if (postProcessedPiece) { + this.expectedPackages.ensurePackages(postProcessedPiece.expectedPackages) + } const newPieceInstance: ReadonlyDeep = { - ...proposedPieceInstance.pieceInstance, - piece: postProcessedPiece?.doc ?? proposedPieceInstance.pieceInstance.piece, + ...proposedPieceInstance, + piece: postProcessedPiece?.doc ?? proposedPieceInstance.piece, } - this.partInstance.mergeOrInsertPieceInstance(newPieceInstance, newExpectedPackages) + this.partInstance.mergeOrInsertPieceInstance(newPieceInstance) - return convertPieceInstanceToBlueprints(newPieceInstance, newExpectedPackages) + return convertPieceInstanceToBlueprints( + newPieceInstance, + this.expectedPackages.getPackagesForPieceInstance(newPieceInstance) + ) } insertPieceInstance(piece0: IBlueprintPiece): IBlueprintPieceInstance { @@ -127,7 +126,9 @@ export class SyncIngestUpdateToPartInstanceContext this.playStatus === 'current' )[0] - const newPieceInstance = this.partInstance.insertPlannedPiece(piece.doc, piece.expectedPackages) + this.expectedPackages.ensurePackages(piece.expectedPackages) + + const newPieceInstance = this.partInstance.insertPlannedPiece(piece.doc) return convertPieceInstanceToBlueprints( newPieceInstance.pieceInstance, diff --git a/packages/job-worker/src/blueprints/context/services/__tests__/PartAndPieceInstanceActionService.test.ts b/packages/job-worker/src/blueprints/context/services/__tests__/PartAndPieceInstanceActionService.test.ts index 5430588b60..35f7922fff 100644 --- a/packages/job-worker/src/blueprints/context/services/__tests__/PartAndPieceInstanceActionService.test.ts +++ b/packages/job-worker/src/blueprints/context/services/__tests__/PartAndPieceInstanceActionService.test.ts @@ -32,7 +32,7 @@ import { PlayoutPartInstanceModel } from '../../../../playout/model/PlayoutPartI import { convertPartInstanceToBlueprints, convertPieceInstanceToBlueprints } from '../../lib' import { TimelineObjRundown, TimelineObjType } from '@sofie-automation/corelib/dist/dataModel/Timeline' import { PlayoutPartInstanceModelImpl } from '../../../../playout/model/implementation/PlayoutPartInstanceModelImpl' -import { writePartInstancesAndPieceInstancesAndExpectedPackages } from '../../../../playout/model/implementation/SavePlayoutModel' +import { writePartInstancesAndPieceInstances } from '../../../../playout/model/implementation/SavePlayoutModel' import { PlayoutPieceInstanceModel } from '../../../../playout/model/PlayoutPieceInstanceModel' import { DatabasePersistedModel } from '../../../../modelBase' @@ -217,7 +217,7 @@ describe('Test blueprint api context', () => { ) { // We need to push changes back to 'mongo' for these tests await Promise.all( - writePartInstancesAndPieceInstancesAndExpectedPackages( + writePartInstancesAndPieceInstances( context, normalizeArrayToMapFunc(allPartInstances as PlayoutPartInstanceModelImpl[], (p) => p.partInstance._id) ) diff --git a/packages/job-worker/src/blueprints/postProcess.ts b/packages/job-worker/src/blueprints/postProcess.ts index f1895703fb..356d834fb5 100644 --- a/packages/job-worker/src/blueprints/postProcess.ts +++ b/packages/job-worker/src/blueprints/postProcess.ts @@ -19,6 +19,7 @@ import { AdLibActionId, BlueprintId, BucketId, + ExpectedPackageId, PartId, PieceId, RundownId, @@ -46,6 +47,7 @@ import { setDefaultIdOnExpectedPackages } from '../ingest/expectedPackages' import { logger } from '../logging' import { validateTimeline } from 'superfly-timeline' import { ReadonlyDeep } from 'type-fest' +import { getExpectedPackageIdNew } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' function getIdHash(docType: string, usedIds: Map, uniqueId: string): string { const count = usedIds.get(uniqueId) @@ -60,14 +62,14 @@ function getIdHash(docType: string, usedIds: Map, uniqueId: stri } } -export interface PostProcessDoc { - doc: T - expectedPackages: ReadonlyDeep +export interface PostProcessDocs { + docs: T[] + expectedPackages: Map> } -export function unwrapPostProccessDocs(docs: PostProcessDoc[]): T[] { - return docs.map((doc) => doc.doc) -} +// export function unwrapPostProccessDocs(docs: PostProcessDoc[]): T[] { +// return docs.map((doc) => doc.doc) +// } /** * Process and validate some IBlueprintPiece into Piece @@ -89,13 +91,15 @@ export function postProcessPieces( partId: PartId, allowNowForPiece: boolean, setInvalid?: boolean -): PostProcessDoc[] { +): PostProcessDocs { const span = context.startSpan('blueprints.postProcess.postProcessPieces') + const expectedPackages = new Map>() + const uniqueIds = new Map() const timelineUniqueIds = new Set() - const processedPieces = pieces.map((orgPiece: IBlueprintPiece): PostProcessDoc => { + const processedPieces = pieces.map((orgPiece: IBlueprintPiece): Piece => { if (!orgPiece.externalId) throw new Error( `Error in blueprint "${blueprintId}" externalId not set for adlib piece in ${partId}! ("${orgPiece.name}")` @@ -143,7 +147,7 @@ export function postProcessPieces( hasSideEffects: orgPiece.hasSideEffects, abSessions: orgPiece.abSessions, notInVision: orgPiece.notInVision, - expectedPackages: convertExpectedPackages(processedExpectedPackages), + expectedPackages: convertExpectedPackages(rundownId, processedExpectedPackages, expectedPackages), } if (piece.pieceType !== IBlueprintPieceType.Normal) { @@ -168,14 +172,14 @@ export function postProcessPieces( ) piece.timelineObjectsString = serializePieceTimelineObjectsBlob(timelineObjects) - return { - doc: piece, - expectedPackages: processedExpectedPackages, - } + return piece }) span?.end() - return processedPieces + return { + docs: processedPieces, + expectedPackages, + } } function isNow(enable: TimelineObjectCoreExt['enable']): boolean { @@ -258,9 +262,11 @@ export function postProcessAdLibPieces( rundownId: RundownId, partId: PartId | undefined, adLibPieces: Array -): PostProcessDoc[] { +): PostProcessDocs { const span = context.startSpan('blueprints.postProcess.postProcessAdLibPieces') + const expectedPackages = new Map>() + const uniqueIds = new Map() const timelineUniqueIds = new Set() @@ -288,7 +294,7 @@ export function postProcessAdLibPieces( externalId: orgAdlib.externalId, _rank: orgAdlib._rank, - expectedPackages: convertExpectedPackages(processedExpectedPackages), + expectedPackages: convertExpectedPackages(rundownId, processedExpectedPackages, expectedPackages), expectedPlayoutItems: orgAdlib.expectedPlayoutItems, privateData: orgAdlib.privateData, @@ -331,14 +337,14 @@ export function postProcessAdLibPieces( ) piece.timelineObjectsString = serializePieceTimelineObjectsBlob(timelineObjects) - return { - doc: piece, - expectedPackages: processedExpectedPackages, - } + return piece }) span?.end() - return processedPieces + return { + docs: processedPieces, + expectedPackages, + } } /** @@ -351,10 +357,12 @@ export function postProcessGlobalAdLibActions( blueprintId: BlueprintId, rundownId: RundownId, adlibActions: IBlueprintActionManifest[] -): PostProcessDoc[] { +): PostProcessDocs { + const expectedPackages = new Map>() + const uniqueIds = new Map() - return adlibActions.map((action) => { + const processedActions = adlibActions.map((action) => { if (!action.externalId) throw new Error( `Error in blueprint "${blueprintId}" externalId not set for baseline adlib action! ("${ @@ -371,13 +379,13 @@ export function postProcessGlobalAdLibActions( // Fill in ids of unnamed expectedPackages const processedExpectedPackages = setDefaultIdOnExpectedPackages(action.expectedPackages) - const processedAction = literal>({ + return literal>({ externalId: action.externalId, _id: protectString(docId), rundownId: rundownId, ...processAdLibActionITranslatableMessages(action, blueprintId), - expectedPackages: convertExpectedPackages(processedExpectedPackages), + expectedPackages: convertExpectedPackages(rundownId, processedExpectedPackages, expectedPackages), expectedPlayoutItems: action.expectedPlayoutItems, privateData: action.privateData, @@ -391,12 +399,12 @@ export function postProcessGlobalAdLibActions( // Not used? uniquenessId: undefined, }) - - return { - doc: processedAction, - expectedPackages: processedExpectedPackages, - } }) + + return { + docs: processedActions, + expectedPackages, + } } /** @@ -411,10 +419,12 @@ export function postProcessAdLibActions( rundownId: RundownId, partId: PartId, adlibActions: IBlueprintActionManifest[] -): PostProcessDoc[] { +): PostProcessDocs { + const expectedPackages = new Map>() + const uniqueIds = new Map() - return adlibActions.map((action) => { + const processedActions = adlibActions.map((action) => { if (!action.externalId) throw new Error( `Error in blueprint "${blueprintId}" externalId not set for adlib action in ${partId}! ("${action.display.label}")` @@ -436,7 +446,7 @@ export function postProcessAdLibActions( partId: partId, ...processAdLibActionITranslatableMessages(action, blueprintId), - expectedPackages: convertExpectedPackages(processedExpectedPackages), + expectedPackages: convertExpectedPackages(rundownId, processedExpectedPackages, expectedPackages), expectedPlayoutItems: action.expectedPlayoutItems, privateData: action.privateData, @@ -451,11 +461,10 @@ export function postProcessAdLibActions( uniquenessId: undefined, }) - return { - doc: processedAction, - expectedPackages: processedExpectedPackages, - } + return processedAction }) + + return { docs: processedActions, expectedPackages } } /** @@ -685,10 +694,17 @@ function processAdLibActionITranslatableMessages< } } -function convertExpectedPackages(expectedPackages: ExpectedPackage.Any[]): Complete[] { +function convertExpectedPackages( + rundownId: RundownId, + expectedPackages: ExpectedPackage.Any[], + expectedPackagesMap: Map> +): Complete[] { if (!expectedPackages) return [] - return expectedPackages.map((expectedPackage) => ({ - _id: expectedPackage._id, - })) + return expectedPackages.map((expectedPackage) => { + const expectedPackageId = getExpectedPackageIdNew(rundownId, expectedPackage) + expectedPackagesMap.set(expectedPackageId, expectedPackage) + + return { blueprintPackageId: expectedPackage._id, expectedPackageId } + }) } diff --git a/packages/job-worker/src/ingest/expectedPackages.ts b/packages/job-worker/src/ingest/expectedPackages.ts index 5fe97f3e7d..3bb60243da 100644 --- a/packages/job-worker/src/ingest/expectedPackages.ts +++ b/packages/job-worker/src/ingest/expectedPackages.ts @@ -8,7 +8,6 @@ import { getContentVersionHash, getExpectedPackageId, ExpectedPackageDBBaseSimple, - ExpectedPackageDBFromPieceInstance, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { RundownId, @@ -36,7 +35,6 @@ import { IngestModel } from './model/IngestModel' import { DBStudio } from '@sofie-automation/corelib/dist/dataModel/Studio' import { IngestPartModel } from './model/IngestPartModel' import { clone } from '@sofie-automation/corelib/dist/lib' -import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' export function updateExpectedMediaAndPlayoutItemsForPartModel(context: JobContext, part: IngestPartModel): void { updateExpectedMediaItemsForPartModel(context, part) @@ -91,7 +89,7 @@ function generateExpectedPackagesForBucketAdlibAction( } return packages } -export function generateExpectedPackageBases( +function generateExpectedPackageBases( studioId: StudioId, ownerId: | PieceId @@ -198,22 +196,3 @@ export function setDefaultIdOnExpectedPackages( return expectedPackages } - -export function wrapPackagesForPieceInstance( - studioId: StudioId, - partInstance: ReadonlyDeep, - pieceInstanceId: PieceInstanceId, - expectedPackages: ReadonlyDeep -): ExpectedPackageDBFromPieceInstance[] { - const bases = generateExpectedPackageBases(studioId, pieceInstanceId, expectedPackages) - return bases.map((base) => ({ - ...base, - - fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, - partInstanceId: partInstance._id, - pieceInstanceId: pieceInstanceId, - segmentId: partInstance.segmentId, - rundownId: partInstance.rundownId, - pieceId: null, - })) -} diff --git a/packages/job-worker/src/ingest/generationRundown.ts b/packages/job-worker/src/ingest/generationRundown.ts index 366fe13543..9faee12d90 100644 --- a/packages/job-worker/src/ingest/generationRundown.ts +++ b/packages/job-worker/src/ingest/generationRundown.ts @@ -2,9 +2,18 @@ import { ExpectedPackageDBFromBaselineAdLibAction, ExpectedPackageDBFromBaselineAdLibPiece, ExpectedPackageDBFromRundownBaselineObjects, + ExpectedPackageDBNew, ExpectedPackageDBType, + getContentVersionHash, + getExpectedPackageIdNew, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' -import { BlueprintId, RundownId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { + BlueprintId, + ExpectedPackageId, + RundownId, + SegmentId, + StudioId, +} from '@sofie-automation/corelib/dist/dataModel/Ids' import { RundownNote } from '@sofie-automation/corelib/dist/dataModel/Notes' import { serializePieceTimelineObjectsBlob } from '@sofie-automation/corelib/dist/dataModel/Piece' import { DBRundown, RundownSource } from '@sofie-automation/corelib/dist/dataModel/Rundown' @@ -15,20 +24,19 @@ import { StudioUserContext, GetRundownContext } from '../blueprints/context' import { WatchedPackagesHelper } from '../blueprints/context/watchedPackages' import { postProcessAdLibPieces, - PostProcessDoc, + PostProcessDocs, postProcessGlobalAdLibActions, postProcessRundownBaselineItems, - unwrapPostProccessDocs, } from '../blueprints/postProcess' import { logger } from '../logging' import _ = require('underscore') -import { ExpectedPackageForIngestModelBaseline, IngestModel } from './model/IngestModel' +import { IngestModel } from './model/IngestModel' import { LocalIngestRundown } from './ingestCache' import { extendIngestRundownCore, canRundownBeUpdated } from './lib' import { JobContext } from '../jobs' import { CommitIngestData } from './lock' import { SelectedShowStyleVariant, selectShowStyleVariant } from './selectShowStyleVariant' -import { generateExpectedPackageBases, updateExpectedMediaAndPlayoutItemsForRundownBaseline } from './expectedPackages' +import { updateExpectedMediaAndPlayoutItemsForRundownBaseline } from './expectedPackages' import { ReadonlyDeep } from 'type-fest' import { BlueprintResultRundown, @@ -355,71 +363,98 @@ export async function regenerateRundownAndBaselineFromIngestData( rundownRes.globalActions || [] ) - const expectedPackages: ExpectedPackageForIngestModelBaseline[] = [ - ...generateRundownBaselineExpectedPackages(context, dbRundown._id, rundownRes.baseline.expectedPackages ?? []), - ...generateGlobalAdLibPieceExpectedPackages(context, dbRundown._id, adlibPieces), - ...generateGlobalAdLibActionExpectedPackages(context, dbRundown._id, adlibActions), - ] - - await ingestModel.setRundownBaseline( - timelineObjectsBlob, - unwrapPostProccessDocs(adlibPieces), - unwrapPostProccessDocs(adlibActions), - expectedPackages + const expectedPackages = generateExpectedPackagesForBaseline( + context.studioId, + dbRundown._id, + adlibPieces, + adlibActions, + rundownRes.baseline.expectedPackages ?? [] ) + await ingestModel.setRundownBaseline(timelineObjectsBlob, adlibPieces.docs, adlibActions.docs, expectedPackages) + await updateExpectedMediaAndPlayoutItemsForRundownBaseline(context, ingestModel, rundownRes.baseline) return dbRundown } -function generateGlobalAdLibPieceExpectedPackages( - context: JobContext, +function generateExpectedPackagesForBaseline( + studioId: StudioId, rundownId: RundownId, - adlibPieces: PostProcessDoc[] -): ExpectedPackageDBFromBaselineAdLibPiece[] { - return adlibPieces.flatMap(({ doc, expectedPackages }) => { - const bases = generateExpectedPackageBases(context.studio._id, doc._id, expectedPackages) + adLibPieces: PostProcessDocs, + adLibActions: PostProcessDocs, + expectedPackages: ExpectedPackage.Any[] +): ExpectedPackageDBNew[] { + const packages = new Map() - return bases.map((base) => ({ - ...base, - rundownId, - pieceId: doc._id, - fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_PIECE, - })) - }) -} + const baselineExpectedPackages = new Map>() + for (const expectedPackage of expectedPackages) { + const packageId = getExpectedPackageIdNew(rundownId, expectedPackage) + baselineExpectedPackages.set(packageId, expectedPackage) + } -function generateGlobalAdLibActionExpectedPackages( - context: JobContext, - rundownId: RundownId, - adlibActions: PostProcessDoc[] -): ExpectedPackageDBFromBaselineAdLibAction[] { - return adlibActions.flatMap(({ doc, expectedPackages }) => { - const bases = generateExpectedPackageBases(context.studio._id, doc._id, expectedPackages) + // Generate the full version of each expectedPackage + // nocommit - deduplicate this work with the other files + const allRawPackages = [ + ...adLibPieces.expectedPackages, + ...adLibActions.expectedPackages, + ...baselineExpectedPackages, + ] + for (const [packageId, expectedPackage] of allRawPackages) { + if (packages.has(packageId)) continue + + packages.set(packageId, { + _id: packageId, - return bases.map((base) => ({ - ...base, + studioId, rundownId, - pieceId: doc._id, - fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_ACTION, - })) - }) -} -function generateRundownBaselineExpectedPackages( - context: JobContext, - rundownId: RundownId, - expectedPackages: ExpectedPackage.Any[] -): ExpectedPackageDBFromRundownBaselineObjects[] { - const bases = generateExpectedPackageBases(context.studio._id, rundownId, expectedPackages) + contentVersionHash: getContentVersionHash(expectedPackage), - return bases.map((item) => { - return { - ...item, - fromPieceType: ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS, - rundownId: rundownId, - pieceId: null, + created: Date.now(), // nocommit - avoid churn on this? + + package: expectedPackage, + + ingestSources: [], + + playoutSources: { + // nocommit - avoid this here? + pieceInstanceIds: [], + }, + }) + } + + // Populate the ingestSources + for (const piece of adLibPieces.docs) { + for (const expectedPackage of piece.expectedPackages) { + const expectedPackageDoc = packages.get(expectedPackage.expectedPackageId) + if (!expectedPackageDoc) continue // nocommit - log error? this should never happen + + expectedPackageDoc.ingestSources.push({ + fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_PIECE, + pieceId: piece._id, + }) } - }) + } + for (const piece of adLibActions.docs) { + for (const expectedPackage of piece.expectedPackages) { + const expectedPackageDoc = packages.get(expectedPackage.expectedPackageId) + if (!expectedPackageDoc) continue // nocommit - log error? this should never happen + + expectedPackageDoc.ingestSources.push({ + fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_ACTION, + pieceId: piece._id, + }) + } + } + for (const packageId of baselineExpectedPackages.keys()) { + const expectedPackageDoc = packages.get(packageId) + if (!expectedPackageDoc) continue // nocommit - log error? this should never happen + + expectedPackageDoc.ingestSources.push({ + fromPieceType: ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS, + }) + } + + return Array.from(packages.values()) } diff --git a/packages/job-worker/src/ingest/model/IngestModel.ts b/packages/job-worker/src/ingest/model/IngestModel.ts index fca213b79b..7ade42d0b0 100644 --- a/packages/job-worker/src/ingest/model/IngestModel.ts +++ b/packages/job-worker/src/ingest/model/IngestModel.ts @@ -4,6 +4,7 @@ import { ExpectedPackageDBFromBaselineAdLibPiece, ExpectedPackageDBFromPiece, ExpectedPackageDBFromRundownBaselineObjects, + ExpectedPackageDBNew, ExpectedPackageFromRundown, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { ExpectedPlayoutItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedPlayoutItem' @@ -250,7 +251,7 @@ export interface IngestModel extends IngestModelReadonly, BaseModel { timelineObjectsBlob: PieceTimelineObjectsBlob, adlibPieces: RundownBaselineAdLibItem[], adlibActions: RundownBaselineAdLibAction[], - expectedPackages: ExpectedPackageForIngestModelBaseline[] + expectedPackages: ExpectedPackageDBNew[] ): Promise /** diff --git a/packages/job-worker/src/ingest/model/IngestSegmentModel.ts b/packages/job-worker/src/ingest/model/IngestSegmentModel.ts index 2a232dfabb..1291915398 100644 --- a/packages/job-worker/src/ingest/model/IngestSegmentModel.ts +++ b/packages/job-worker/src/ingest/model/IngestSegmentModel.ts @@ -6,7 +6,7 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { AdLibAction } from '@sofie-automation/corelib/dist/dataModel/AdlibAction' -import { PostProcessDoc } from '../../blueprints/postProcess' +import { PostProcessDocs } from '../../blueprints/postProcess' export interface IngestSegmentModelReadonly { /** @@ -87,9 +87,9 @@ export interface IngestSegmentModel extends IngestSegmentModelReadonly { */ replacePart( part: IngestReplacePartType, - pieces: PostProcessDoc[], - adLibPieces: PostProcessDoc[], - adLibActions: PostProcessDoc[] + pieces: PostProcessDocs, + adLibPieces: PostProcessDocs, + adLibActions: PostProcessDocs ): IngestPartModel } diff --git a/packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts b/packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts index 10226d15bf..e16b6bd3b9 100644 --- a/packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts +++ b/packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts @@ -1,5 +1,5 @@ import { ExpectedMediaItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedMediaItem' -import { ExpectedPackageDBBase } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { ExpectedPackageDBNew } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { ExpectedPlayoutItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedPlayoutItem' import { ExpectedMediaItemId, @@ -12,10 +12,7 @@ import { import { ReadonlyDeep } from 'type-fest' import { diffAndReturnLatestObjects, DocumentChanges, getDocumentChanges, setValuesAndTrackChanges } from './utils' -function mutateExpectedPackage( - oldObj: ExpectedPackageType, - newObj: ExpectedPackageType -): ExpectedPackageType { +function mutateExpectedPackage(oldObj: ExpectedPackageDBNew, newObj: ExpectedPackageDBNew): ExpectedPackageDBNew { return { ...newObj, // Retain the created property @@ -23,10 +20,10 @@ function mutateExpectedPackage { +export class ExpectedPackagesStore { #expectedMediaItems: ExpectedMediaItemRundown[] #expectedPlayoutItems: ExpectedPlayoutItemRundown[] - #expectedPackages: ExpectedPackageType[] + #expectedPackages: ExpectedPackageDBNew[] #expectedMediaItemsWithChanges = new Set() #expectedPlayoutItemsWithChanges = new Set() @@ -38,9 +35,8 @@ export class ExpectedPackagesStore { return this.#expectedPlayoutItems } - get expectedPackages(): ReadonlyDeep { - // Typescript is not happy with turning ExpectedPackageType into ReadonlyDeep because it can be a union - return this.#expectedPackages as any[] + get expectedPackages(): ReadonlyDeep { + return this.#expectedPackages } get hasChanges(): boolean { @@ -57,7 +53,7 @@ export class ExpectedPackagesStore { return getDocumentChanges(this.#expectedPlayoutItemsWithChanges, this.#expectedPlayoutItems) } - get expectedPackagesChanges(): DocumentChanges { + get expectedPackagesChanges(): DocumentChanges { return getDocumentChanges(this.#expectedPackagesWithChanges, this.#expectedPackages) } @@ -78,7 +74,7 @@ export class ExpectedPackagesStore): void { + compareToPreviousData(oldStore: ExpectedPackagesStore): void { // Diff the objects, but don't update the stored copies diffAndReturnLatestObjects( this.#expectedPlayoutItemsWithChanges, @@ -169,19 +165,19 @@ export class ExpectedPackagesStore ({ - ...pkg, - partId: this.#partId, - segmentId: this.#segmentId, - rundownId: this.#rundownId, - })) - - this.#expectedPackages = diffAndReturnLatestObjects( - this.#expectedPackagesWithChanges, - this.#expectedPackages, - newExpectedPackages, - mutateExpectedPackage - ) + setExpectedPackages(expectedPackages: ExpectedPackageDBNew[]): void { + // nocommit - the whole packages flow needs reimplementing + // const newExpectedPackages: ExpectedPackageDBNew[] = expectedPackages.map((pkg) => ({ + // ...pkg, + // partId: this.#partId, + // segmentId: this.#segmentId, + // rundownId: this.#rundownId, + // })) + // this.#expectedPackages = diffAndReturnLatestObjects( + // this.#expectedPackagesWithChanges, + // this.#expectedPackages, + // newExpectedPackages, + // mutateExpectedPackage + // ) } } diff --git a/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts b/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts index 064f8d5d5f..38c28c06ae 100644 --- a/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts +++ b/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts @@ -7,7 +7,10 @@ import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { AdLibAction } from '@sofie-automation/corelib/dist/dataModel/AdlibAction' import { ExpectedMediaItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedMediaItem' import { ExpectedPlayoutItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedPlayoutItem' -import { ExpectedPackageFromRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { + ExpectedPackageDBNew, + ExpectedPackageFromRundown, +} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' import { ExpectedPackagesStore } from './ExpectedPackagesStore' import { @@ -140,7 +143,7 @@ export class IngestPartModelImpl implements IngestPartModel { adLibActions: AdLibAction[], expectedMediaItems: ExpectedMediaItemRundown[], expectedPlayoutItems: ExpectedPlayoutItemRundown[], - expectedPackages: ExpectedPackageFromRundown[] + expectedPackages: ExpectedPackageDBNew[] ) { this.partImpl = part this.#pieces = pieces diff --git a/packages/job-worker/src/ingest/model/implementation/IngestSegmentModelImpl.ts b/packages/job-worker/src/ingest/model/implementation/IngestSegmentModelImpl.ts index 49f7235b27..c4e4389a91 100644 --- a/packages/job-worker/src/ingest/model/implementation/IngestSegmentModelImpl.ts +++ b/packages/job-worker/src/ingest/model/implementation/IngestSegmentModelImpl.ts @@ -1,4 +1,4 @@ -import { PartId, RundownId, SegmentId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { ExpectedPackageId, PartId, RundownId, SegmentId, StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { DBSegment, SegmentOrphanedReason } from '@sofie-automation/corelib/dist/dataModel/Segment' import { IngestReplacePartType, IngestSegmentModel } from '../IngestSegmentModel' @@ -12,15 +12,13 @@ import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' import { calculatePartExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings' import { clone } from '@sofie-automation/corelib/dist/lib' import { getPartId } from '../../lib' -import { PostProcessDoc, unwrapPostProccessDocs } from '../../../blueprints/postProcess' import { - ExpectedPackageDBFromAdLibAction, - ExpectedPackageDBFromPiece, + ExpectedPackageDBNew, ExpectedPackageDBType, - ExpectedPackageFromRundown, + getContentVersionHash, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' -import { generateExpectedPackageBases } from '../../expectedPackages' import { JobContext } from '../../../jobs' +import { PostProcessDocs } from '../../../blueprints/postProcess' /** * A light wrapper around the IngestPartModel, so that we can track the deletions while still accessing the contents @@ -219,19 +217,16 @@ export class IngestSegmentModelImpl implements IngestSegmentModel { replacePart( rawPart: IngestReplacePartType, - pieces: PostProcessDoc[], - adLibPieces: PostProcessDoc[], - adLibActions: PostProcessDoc[] + pieces: PostProcessDocs, + adLibPieces: PostProcessDocs, + adLibActions: PostProcessDocs ): IngestPartModel { const part: DBPart = { ...rawPart, _id: this.getPartIdFromExternalId(rawPart.externalId), rundownId: this.segment.rundownId, segmentId: this.segment._id, - expectedDurationWithTransition: calculatePartExpectedDurationWithTransition( - rawPart, - unwrapPostProccessDocs(pieces) - ), + expectedDurationWithTransition: calculatePartExpectedDurationWithTransition(rawPart, pieces.docs), } // We don't need to worry about this being present on other Segments. The caller must make sure it gets removed if needed, @@ -240,35 +235,22 @@ export class IngestSegmentModelImpl implements IngestSegmentModel { const oldPart = this.partsImpl.get(part._id) - const expectedPackages: ExpectedPackageFromRundown[] = [ - ...generateExpectedPackagesForPiece( - this.#context.studioId, - part.rundownId, - part.segmentId, - pieces, - ExpectedPackageDBType.PIECE - ), - ...generateExpectedPackagesForPiece( - this.#context.studioId, - part.rundownId, - part.segmentId, - adLibPieces, - ExpectedPackageDBType.ADLIB_PIECE - ), - ...generateExpectedPackagesForAdlibAction( - this.#context.studioId, - part.rundownId, - part.segmentId, - adLibActions - ), - ] + const expectedPackages = generateExpectedPackagesForPart( + this.#context.studioId, + part.rundownId, + part.segmentId, + part._id, + pieces, + adLibPieces, + adLibActions + ) const partModel = new IngestPartModelImpl( !oldPart, clone(part), - clone(unwrapPostProccessDocs(pieces)), - clone(unwrapPostProccessDocs(adLibPieces)), - clone(unwrapPostProccessDocs(adLibActions)), + clone(pieces.docs), + clone(adLibPieces.docs), + clone(adLibActions.docs), [], [], expectedPackages @@ -283,51 +265,87 @@ export class IngestSegmentModelImpl implements IngestSegmentModel { } } -function generateExpectedPackagesForPiece( +function generateExpectedPackagesForPart( studioId: StudioId, rundownId: RundownId, segmentId: SegmentId, - pieces: ReadonlyDeep>[], - type: ExpectedPackageDBType.PIECE | ExpectedPackageDBType.ADLIB_PIECE -): ExpectedPackageDBFromPiece[] { - const packages: ExpectedPackageDBFromPiece[] = [] - for (const { doc, expectedPackages } of pieces) { - const partId = 'startPartId' in doc ? doc.startPartId : doc.partId - if (partId) { - const bases = generateExpectedPackageBases(studioId, doc._id, expectedPackages) - for (const base of bases) { - packages.push({ - ...base, - rundownId, - segmentId, - partId, - pieceId: doc._id, - fromPieceType: type, - }) - } + partId: PartId, + pieces: PostProcessDocs, + adLibPieces: PostProcessDocs, + adLibActions: PostProcessDocs +): ExpectedPackageDBNew[] { + const packages = new Map() + + // Generate the full version of each expectedPackage + const allRawPackages = [ + ...pieces.expectedPackages, + ...adLibPieces.expectedPackages, + ...adLibActions.expectedPackages, + ] + for (const [packageId, expectedPackage] of allRawPackages) { + if (packages.has(packageId)) continue + + packages.set(packageId, { + _id: packageId, + + studioId, + rundownId, + + contentVersionHash: getContentVersionHash(expectedPackage), + + created: Date.now(), // nocommit - avoid churn on this? + + package: expectedPackage, + + ingestSources: [], + + playoutSources: { + // nocommit - avoid this here? + pieceInstanceIds: [], + }, + }) + } + + // Populate the ingestSources + for (const piece of pieces.docs) { + for (const expectedPackage of piece.expectedPackages) { + const expectedPackageDoc = packages.get(expectedPackage.expectedPackageId) + if (!expectedPackageDoc) continue // nocommit - log error? this should never happen + + expectedPackageDoc.ingestSources.push({ + fromPieceType: ExpectedPackageDBType.PIECE, + pieceId: piece._id, + partId: partId, + segmentId: segmentId, + }) } } - return packages -} -function generateExpectedPackagesForAdlibAction( - studioId: StudioId, - rundownId: RundownId, - segmentId: SegmentId, - actions: ReadonlyDeep[]> -): ExpectedPackageDBFromAdLibAction[] { - const packages: ExpectedPackageDBFromAdLibAction[] = [] - for (const { doc, expectedPackages } of actions) { - const bases = generateExpectedPackageBases(studioId, doc._id, expectedPackages) - for (const base of bases) { - packages.push({ - ...base, - rundownId, - segmentId, - partId: doc.partId, - pieceId: doc._id, + for (const piece of adLibPieces.docs) { + for (const expectedPackage of piece.expectedPackages) { + const expectedPackageDoc = packages.get(expectedPackage.expectedPackageId) + if (!expectedPackageDoc) continue // nocommit - log error? this should never happen + + expectedPackageDoc.ingestSources.push({ + fromPieceType: ExpectedPackageDBType.ADLIB_PIECE, + pieceId: piece._id, + partId: partId, + segmentId: segmentId, + }) + } + } + for (const piece of adLibActions.docs) { + for (const expectedPackage of piece.expectedPackages) { + const expectedPackageDoc = packages.get(expectedPackage.expectedPackageId) + if (!expectedPackageDoc) continue // nocommit - log error? this should never happen + + expectedPackageDoc.ingestSources.push({ fromPieceType: ExpectedPackageDBType.ADLIB_ACTION, + pieceId: piece._id, + partId: partId, + segmentId: segmentId, }) } } - return packages + + return Array.from(packages.values()) } diff --git a/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts b/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts index c2995869cf..59afb3bfe5 100644 --- a/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts +++ b/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts @@ -1,9 +1,8 @@ import { AdLibAction } from '@sofie-automation/corelib/dist/dataModel/AdlibAction' import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { ExpectedMediaItem } from '@sofie-automation/corelib/dist/dataModel/ExpectedMediaItem' -import { ExpectedPackageDB } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { ExpectedPackageDBNew } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { ExpectedPlayoutItem } from '@sofie-automation/corelib/dist/dataModel/ExpectedPlayoutItem' -import { RundownId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment' @@ -13,7 +12,7 @@ import { IngestSegmentModelImpl } from './IngestSegmentModelImpl' import { DocumentChangeTracker } from './DocumentChangeTracker' export class SaveIngestModelHelper { - #expectedPackages = new DocumentChangeTracker() + #expectedPackages = new DocumentChangeTracker() #expectedPlayoutItems = new DocumentChangeTracker() #expectedMediaItems = new DocumentChangeTracker() @@ -23,10 +22,7 @@ export class SaveIngestModelHelper { #adLibPieces = new DocumentChangeTracker() #adLibActions = new DocumentChangeTracker() - addExpectedPackagesStore( - store: ExpectedPackagesStore, - deleteAll?: boolean - ): void { + addExpectedPackagesStore(store: ExpectedPackagesStore, deleteAll?: boolean): void { this.#expectedPackages.addChanges(store.expectedPackagesChanges, deleteAll ?? false) this.#expectedPlayoutItems.addChanges(store.expectedPlayoutItemsChanges, deleteAll ?? false) this.#expectedMediaItems.addChanges(store.expectedMediaItemsChanges, deleteAll ?? false) @@ -56,7 +52,8 @@ export class SaveIngestModelHelper { commit(context: JobContext): Array> { return [ - context.directCollections.ExpectedPackages.bulkWrite(this.#expectedPackages.generateWriteOps()), + // nocommit - reimplement this save + // context.directCollections.ExpectedPackages.bulkWrite(this.#expectedPackages.generateWriteOps()), context.directCollections.ExpectedPlayoutItems.bulkWrite(this.#expectedPlayoutItems.generateWriteOps()), context.directCollections.ExpectedMediaItems.bulkWrite(this.#expectedMediaItems.generateWriteOps()), diff --git a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts index ddce363268..6ec9d6d639 100644 --- a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts +++ b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts @@ -164,7 +164,7 @@ export async function syncChangesToPartInstances( const newResultData: BlueprintSyncIngestNewData = { part: newPart ? convertPartToBlueprints(newPart) : undefined, pieceInstances: proposedPieceInstances.map((p) => - convertPieceInstanceToBlueprints(p.pieceInstance, p.expectedPackages) + convertPieceInstanceToBlueprints(p, p.expectedPackages) ), adLibPieces: newPart && ingestPart ? ingestPart.adLibPieces.map(convertAdLibPieceToBlueprints) : [], actions: newPart && ingestPart ? ingestPart.adLibActions.map(convertAdLibActionToBlueprints) : [], diff --git a/packages/job-worker/src/playout/adlibJobs.ts b/packages/job-worker/src/playout/adlibJobs.ts index 8c3b90e52d..39d78c581e 100644 --- a/packages/job-worker/src/playout/adlibJobs.ts +++ b/packages/job-worker/src/playout/adlibJobs.ts @@ -160,12 +160,10 @@ async function pieceTakeNowAsAdlib( | { partInstance: PlayoutPartInstanceModel; pieceInstance: PlayoutPieceInstanceModel } | undefined ): Promise { + playoutModel.expectedPackages.ensurePackagesExist(currentPartInstance.partInstance.rundownId, expectedPackages) // nocommit - what if the genericAdlibPiece doesn't quite match the set of packages? + const genericAdlibPiece = convertAdLibToGenericPiece(pieceToCopy, false) - /*const newPieceInstance = */ currentPartInstance.insertAdlibbedPiece( - genericAdlibPiece, - pieceToCopy._id, - expectedPackages - ) + /*const newPieceInstance = */ currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, pieceToCopy._id) // Disable the original piece if from the same Part if ( diff --git a/packages/job-worker/src/playout/adlibUtils.ts b/packages/job-worker/src/playout/adlibUtils.ts index fb8996ac9b..c4ccc4406a 100644 --- a/packages/job-worker/src/playout/adlibUtils.ts +++ b/packages/job-worker/src/playout/adlibUtils.ts @@ -4,13 +4,14 @@ import { PartInstanceId, PieceId, PieceInstanceId } from '@sofie-automation/core import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' import { PieceInstance, + PieceInstancePiece, PieceInstanceWithExpectedPackagesFull, } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { assertNever, getRandomId, getRank } from '@sofie-automation/corelib/dist/lib' import { MongoQuery } from '@sofie-automation/corelib/dist/mongo' import { getCurrentTime } from '../lib' import { JobContext } from '../jobs' -import { AdlibPieceWithPackages, PlayoutModel } from './model/PlayoutModel' +import { PlayoutModel } from './model/PlayoutModel' import { PlayoutPartInstanceModel } from './model/PlayoutPartInstanceModel' import { fetchPiecesThatMayBeActiveForPart, @@ -58,6 +59,8 @@ export async function innerStartOrQueueAdLibPiece( expectedDurationWithTransition: adLibPiece.expectedDuration, // Filled in later } + playoutModel.expectedPackages.ensurePackagesExist(currentPartInstance.partInstance.rundownId, expectedPackages) // nocommit - what if the genericAdlibPiece doesn't quite match the set of packages? + const genericAdlibPiece = convertAdLibToGenericPiece(adLibPiece, true) const newPartInstance = await insertQueuedPartWithPieces( context, @@ -65,15 +68,17 @@ export async function innerStartOrQueueAdLibPiece( rundown, currentPartInstance, adlibbedPart, - [{ piece: genericAdlibPiece, expectedPackages }], + [genericAdlibPiece], adLibPiece._id ) queuedPartInstanceId = newPartInstance.partInstance._id // syncPlayheadInfinitesForNextPartInstance is handled by setNextPart } else { + playoutModel.expectedPackages.ensurePackagesExist(currentPartInstance.partInstance.rundownId, expectedPackages) // nocommit - what if the adLibPiece doesn't quite match the set of packages? + const genericAdlibPiece = convertAdLibToGenericPiece(adLibPiece, false) - currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, adLibPiece._id, expectedPackages) + currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, adLibPiece._id) await syncPlayheadInfinitesForNextPartInstance( context, @@ -242,7 +247,7 @@ export async function insertQueuedPartWithPieces( rundown: PlayoutRundownModel, currentPartInstance: PlayoutPartInstanceModel, newPart: Omit, - initialPieces: AdlibPieceWithPackages[], + initialPieces: Omit[], fromAdlibId: PieceId | undefined ): Promise { const span = context.startSpan('insertQueuedPartWithPieces') diff --git a/packages/job-worker/src/playout/infinites.ts b/packages/job-worker/src/playout/infinites.ts index 00ce6b66f1..7dc1215dec 100644 --- a/packages/job-worker/src/playout/infinites.ts +++ b/packages/job-worker/src/playout/infinites.ts @@ -1,11 +1,8 @@ -import { PartInstanceId, PieceId, RundownId, ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { PartInstanceId, RundownId, ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' -import { - PieceInstance, - PieceInstanceWithExpectedPackages, -} from '@sofie-automation/corelib/dist/dataModel/PieceInstance' +import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { getPieceInstancesForPart as libgetPieceInstancesForPart, getPlayheadTrackingInfinitesForPart as libgetPlayheadTrackingInfinitesForPart, @@ -19,19 +16,13 @@ import { PlayoutModel } from './model/PlayoutModel' import { PlayoutPartInstanceModel } from './model/PlayoutPartInstanceModel' import { PlayoutSegmentModel } from './model/PlayoutSegmentModel' import { getCurrentTime } from '../lib' -import { flatten, groupByToMap, normalizeArrayToMapFunc } from '@sofie-automation/corelib/dist/lib' +import { flatten } from '@sofie-automation/corelib/dist/lib' import _ = require('underscore') import { IngestModelReadonly } from '../ingest/model/IngestModel' import { SegmentOrphanedReason } from '@sofie-automation/corelib/dist/dataModel/Segment' import { sortRundownIDsInPlaylist } from '@sofie-automation/corelib/dist/playout/playlist' import { mongoWhere } from '@sofie-automation/corelib/dist/mongo' import { PlayoutRundownModel } from './model/PlayoutRundownModel' -import { - ExpectedPackageDBFromPiece, - ExpectedPackageDBType, - unwrapExpectedPackages, -} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' -import { PlayoutPieceInstanceModel } from './model/PlayoutPieceInstanceModel' /** When we crop a piece, set the piece as "it has definitely ended" this far into the future. */ export const DEFINITELY_ENDED_FUTURE_DURATION = 1 * 1000 @@ -289,20 +280,7 @@ export async function syncPlayheadInfinitesForNextPartInstance( false ) - const playingPieceInstancesMap = normalizeArrayToMapFunc( - fromPartInstance.pieceInstances, - (o) => o.pieceInstance.piece._id - ) - const wrappedInfinites = infinites.map((pieceInstance): PieceInstanceWithExpectedPackages => { - const expectedPackages = playingPieceInstancesMap.get(pieceInstance.piece._id)?.expectedPackages - - return { - pieceInstance: pieceInstance, - expectedPackages: unwrapExpectedPackages(expectedPackages ?? []), - } - }) - - toPartInstance.replaceInfinitesFromPreviousPlayhead(wrappedInfinites) + toPartInstance.replaceInfinitesFromPreviousPlayhead(infinites) } if (span) span.end() } @@ -326,7 +304,7 @@ export async function getPieceInstancesForPart( part: ReadonlyDeep, possiblePieces: ReadonlyDeep[], newInstanceId: PartInstanceId -): Promise { +): Promise { const span = context.startSpan('getPieceInstancesForPart') const { partsToReceiveOnSegmentEndFrom, segmentsToReceiveOnRundownEndFrom, rundownsToReceiveOnShowStyleEndFrom } = getIdsBeforeThisPart(context, playoutModel, part) @@ -377,53 +355,6 @@ export async function getPieceInstancesForPart( false ) - // Pair the pieceInstances with their expectedPackages - const resWithExpectedPackages = wrapPieceInstancesWithExpectedPackages(context, playingPieceInstances, res) - if (span) span.end() - return resWithExpectedPackages -} - -async function wrapPieceInstancesWithExpectedPackages( - context: JobContext, - playingPieceInstances: PlayoutPieceInstanceModel[], - res: PieceInstance[] -) { - const playingPieceInstanceMap = normalizeArrayToMapFunc(playingPieceInstances, (o) => o.pieceInstance._id) - const pieceIdsToLoad = new Map() - - // Pair the PieceInstances with their expectedPackages - // The side effects of this is a little dirty, but avoids a lot of extra loops - const resWithExpectedPackages = res.map((pieceInstance) => { - const expectedPackages = playingPieceInstanceMap.get(pieceInstance._id)?.expectedPackages - const pieceInstanceWithExpectedPackages: PieceInstanceWithExpectedPackages = { - pieceInstance: pieceInstance, - expectedPackages: unwrapExpectedPackages(expectedPackages ?? []), - } - - if (!expectedPackages) { - // Mark this piece as needing instances to be loaded - pieceIdsToLoad.set(pieceInstance.piece._id, pieceInstanceWithExpectedPackages) - } - - return pieceInstanceWithExpectedPackages - }) - - // Any Pieces which were auto-wrapped don't have their expectedPackages loaded yet - if (pieceIdsToLoad.size > 0) { - const expectedPackages = (await context.directCollections.ExpectedPackages.findFetch({ - fromPieceType: ExpectedPackageDBType.PIECE, - pieceId: { $in: Array.from(pieceIdsToLoad.keys()) }, - })) as ExpectedPackageDBFromPiece[] - const expectedPackagesByPieceId = groupByToMap(expectedPackages, 'pieceId') - - for (const [pieceId, expectedPackages] of expectedPackagesByPieceId.entries()) { - const pieceInstance = pieceIdsToLoad.get(pieceId) - if (pieceInstance) { - pieceInstance.expectedPackages = unwrapExpectedPackages(expectedPackages) - } - } - } - - return resWithExpectedPackages + return res } diff --git a/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts b/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts new file mode 100644 index 0000000000..e5f5288c69 --- /dev/null +++ b/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts @@ -0,0 +1,23 @@ +import { ExpectedPackage } from '@sofie-automation/blueprints-integration' +import { + ExpectedPackageId, + PartInstanceId, + PieceInstanceId, + RundownId, +} from '@sofie-automation/corelib/dist/dataModel/Ids' +import { ReadonlyDeep } from 'type-fest' + +export interface PlayoutExpectedPackagesModelReadonly { + TODO: null +} + +export interface PlayoutExpectedPackagesModel extends PlayoutExpectedPackagesModelReadonly { + ensurePackagesExist(rundownId: RundownId, expectedPackages: ReadonlyDeep): void + + setPieceInstanceReferenceToPackages( + rundownId: RundownId, + partInstanceId: PartInstanceId, + pieceInstanceId: PieceInstanceId, + expectedPackageIds: ExpectedPackageId[] + ): void +} diff --git a/packages/job-worker/src/playout/model/PlayoutModel.ts b/packages/job-worker/src/playout/model/PlayoutModel.ts index ed1833014c..ebf31d3113 100644 --- a/packages/job-worker/src/playout/model/PlayoutModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutModel.ts @@ -18,10 +18,7 @@ import { import { ReadonlyDeep } from 'type-fest' import { StudioPlayoutModelBase, StudioPlayoutModelBaseReadonly } from '../../studio/model/StudioPlayoutModel' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' -import { - PieceInstancePiece, - PieceInstanceWithExpectedPackages, -} from '@sofie-automation/corelib/dist/dataModel/PieceInstance' +import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { PlaylistLock } from '../../jobs/lock' import { PlayoutRundownModel } from './PlayoutRundownModel' import { PlayoutSegmentModel } from './PlayoutSegmentModel' @@ -29,16 +26,11 @@ import { PlayoutPartInstanceModel } from './PlayoutPartInstanceModel' import { PeripheralDevice } from '@sofie-automation/corelib/dist/dataModel/PeripheralDevice' import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' import { PlayoutPieceInstanceModel } from './PlayoutPieceInstanceModel' -import { ExpectedPackage } from '@sofie-automation/blueprints-integration' +import { PlayoutExpectedPackagesModel, PlayoutExpectedPackagesModelReadonly } from './PlayoutExpectedPackagesModel' export type DeferredFunction = (playoutModel: PlayoutModel) => void | Promise export type DeferredAfterSaveFunction = (playoutModel: PlayoutModelReadonly) => void | Promise -export interface AdlibPieceWithPackages { - piece: Omit - expectedPackages: ReadonlyDeep -} - /** * A lightweight version of the `PlayoutModel`, used to perform some pre-checks before loading the full model * @@ -90,6 +82,8 @@ export interface PlayoutModelReadonly extends StudioPlayoutModelBaseReadonly { */ readonly playlistLock: PlaylistLock + readonly expectedPackages: PlayoutExpectedPackagesModelReadonly + /** * The RundownPlaylist this PlayoutModel operates for */ @@ -185,6 +179,8 @@ export interface PlayoutModelReadonly extends StudioPlayoutModelBaseReadonly { * A view of a `RundownPlaylist` and its content in a `Studio` */ export interface PlayoutModel extends PlayoutModelReadonly, StudioPlayoutModelBase, BaseModel { + readonly expectedPackages: PlayoutExpectedPackagesModel + /** * Temporary hack for debug logging */ @@ -212,9 +208,9 @@ export interface PlayoutModel extends PlayoutModelReadonly, StudioPlayoutModelBa */ createAdlibbedPartInstance( part: Omit, - pieces: AdlibPieceWithPackages[], + pieces: Omit[], fromAdlibId: PieceId | undefined, - infinitePieceInstances: PieceInstanceWithExpectedPackages[] + infinitePieceInstances: PieceInstance[] ): PlayoutPartInstanceModel /** @@ -224,10 +220,7 @@ export interface PlayoutModel extends PlayoutModelReadonly, StudioPlayoutModelBa * @param pieceInstances All the PieceInstances to insert * @returns The inserted PlayoutPartInstanceModel */ - createInstanceForPart( - nextPart: ReadonlyDeep, - pieceInstances: PieceInstanceWithExpectedPackages[] - ): PlayoutPartInstanceModel + createInstanceForPart(nextPart: ReadonlyDeep, pieceInstances: PieceInstance[]): PlayoutPartInstanceModel /** * Insert an adlibbed PartInstance into the AdlibTesting Segment of a Rundown in this RundownPlaylist diff --git a/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts b/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts index 6f57ee2620..b8d8409180 100644 --- a/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutPartInstanceModel.ts @@ -1,13 +1,9 @@ import { PieceId, PieceInstanceId, RundownPlaylistActivationId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' -import { - PieceInstance, - PieceInstancePiece, - PieceInstanceWithExpectedPackages, -} from '@sofie-automation/corelib/dist/dataModel/PieceInstance' +import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { PartNote } from '@sofie-automation/corelib/dist/dataModel/Notes' -import { ExpectedPackage, IBlueprintMutatablePart, PieceLifespan, Time } from '@sofie-automation/blueprints-integration' +import { IBlueprintMutatablePart, PieceLifespan, Time } from '@sofie-automation/blueprints-integration' import { PartCalculatedTimings } from '@sofie-automation/corelib/dist/playout/timings' import { PlayoutPieceInstanceModel } from './PlayoutPieceInstanceModel' @@ -68,13 +64,11 @@ export interface PlayoutPartInstanceModel { * Insert a Piece into this PartInstance as an adlibbed PieceInstance * @param piece Piece to insert * @param fromAdlibId Id of the source Adlib, if any - * @param pieceExpectedPackages ExpectedPackages for the Piece * @returns The inserted PlayoutPieceInstanceModel */ insertAdlibbedPiece( piece: Omit, - fromAdlibId: PieceId | undefined, - pieceExpectedPackages: ReadonlyDeep + fromAdlibId: PieceId | undefined ): PlayoutPieceInstanceModel /** @@ -89,13 +83,9 @@ export interface PlayoutPartInstanceModel { * Insert a Piece as if it were originally planned at the time of ingest * This is a weird operation to have for playout, but it is a needed part of the SyncIngestChanges flow * @param piece Piece to insert into this PartInstance - * @param pieceExpectedPackages ExpectedPackages for the Piece * @returns The inserted PlayoutPieceInstanceModel */ - insertPlannedPiece( - piece: Omit, - pieceExpectedPackages: ReadonlyDeep - ): PlayoutPieceInstanceModel + insertPlannedPiece(piece: Omit): PlayoutPieceInstanceModel /** * Insert a virtual adlib Piece into this PartInstance @@ -140,20 +130,16 @@ export interface PlayoutPartInstanceModel { * This allows them to be replaced without embedding the infinite logic inside the model * @param pieceInstances New infinite pieces from previous playhead */ - replaceInfinitesFromPreviousPlayhead(pieceInstances: PieceInstanceWithExpectedPackages[]): void + replaceInfinitesFromPreviousPlayhead(pieceInstances: PieceInstance[]): void /** * Merge a PieceInstance with a new version, or insert as a new PieceInstance. * If there is an existing PieceInstance with the same id, it will be merged onto that * Note: this can replace any playout owned properties too * @param pieceInstance Replacement PieceInstance to use - * @param expectedPackages Replacement ExpectedPackages for the PieceInstance * @returns The inserted PlayoutPieceInstanceModel */ - mergeOrInsertPieceInstance( - pieceInstance: ReadonlyDeep, - expectedPackages: ReadonlyDeep - ): PlayoutPieceInstanceModel + mergeOrInsertPieceInstance(pieceInstance: ReadonlyDeep): PlayoutPieceInstanceModel /** * Mark this PartInstance as being orphaned diff --git a/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts b/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts index 1641f9145e..0f233afe45 100644 --- a/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutPieceInstanceModel.ts @@ -1,8 +1,7 @@ import { PieceInstanceInfiniteId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' -import { ExpectedPackage, Time } from '@sofie-automation/blueprints-integration' -import { ExpectedPackageDBFromPieceInstance } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { Time } from '@sofie-automation/blueprints-integration' export interface PlayoutPieceInstanceModel { /** @@ -10,11 +9,6 @@ export interface PlayoutPieceInstanceModel { */ readonly pieceInstance: ReadonlyDeep - /** - * The ExpectedPackages for the PieceInstance - */ - readonly expectedPackages: ReadonlyDeep - /** * Prepare this PieceInstance to be continued during HOLD * This sets the PieceInstance up as an infinite, to allow the Timeline to be generated correctly @@ -63,10 +57,4 @@ export interface PlayoutPieceInstanceModel { * @param props New properties for the Piece being wrapped */ updatePieceProps(props: Partial): void - - /** - * Update the expected packages for the PieceInstance - * @param expectedPackages The new packages - */ - setExpectedPackages(expectedPackages: ReadonlyDeep[]): void } diff --git a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts index 46d0c8533c..683ed0d8b9 100644 --- a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts +++ b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts @@ -22,10 +22,7 @@ import { PeripheralDevice } from '@sofie-automation/corelib/dist/dataModel/Perip import { PlayoutModel, PlayoutModelPreInit } from '../PlayoutModel' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { RundownBaselineObj } from '@sofie-automation/corelib/dist/dataModel/RundownBaselineObj' -import { - ExpectedPackageDBFromPieceInstance, - ExpectedPackageDBType, -} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { PlayoutExpectedPackagesModelImpl } from './PlayoutExpectedPackagesModelImpl' /** * Load a PlayoutModelPreInit for the given RundownPlaylist @@ -154,8 +151,10 @@ export async function createPlayoutModelfromInitModel( const rundownIds = initModel.rundowns.map((r) => r._id) + const expectedPackages = new PlayoutExpectedPackagesModelImpl() + const [partInstances, rundownsWithContent, timeline] = await Promise.all([ - loadPartInstances(context, initModel.playlist, rundownIds), + loadPartInstances(context, expectedPackages, initModel.playlist, rundownIds), loadRundowns(context, null, initModel.rundowns), loadTimeline(context), ]) @@ -168,7 +167,8 @@ export async function createPlayoutModelfromInitModel( clone(initModel.playlist), partInstances, rundownsWithContent, - timeline + timeline, + expectedPackages ) if (span) span.end() @@ -248,6 +248,7 @@ async function loadRundowns( async function loadPartInstances( context: JobContext, + expectedPackages: PlayoutExpectedPackagesModelImpl, playlist: ReadonlyDeep, rundownIds: RundownId[] ): Promise { @@ -299,33 +300,29 @@ async function loadPartInstances( // Filter the PieceInstances to the activationId, if possible pieceInstancesSelector.playlistActivationId = playlist.activationId || { $exists: false } - const [partInstances, pieceInstances, expectedPackages] = await Promise.all([ + const [partInstances, pieceInstances /*expectedPackageDocs*/] = await Promise.all([ partInstancesCollection, context.directCollections.PieceInstances.findFetch(pieceInstancesSelector), - context.directCollections.ExpectedPackages.findFetch({ - fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, - // Future: this could be optimised to only limit the loading to only those which match `activationId` - partInstanceId: { $in: selectedPartInstanceIds }, - rundownId: { $in: rundownIds }, - }) as Promise, + // context.directCollections.ExpectedPackages.findFetch({ + // fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, + // // Future: this could be optimised to only limit the loading to only those which match `activationId` + // partInstanceId: { $in: selectedPartInstanceIds }, + // rundownId: { $in: rundownIds }, + // }) as Promise, ]) + // nocommit - populate expectedPackagesStore based on the partInstances/pieceInstances? + const groupedPieceInstances = groupByToMap(pieceInstances, 'partInstanceId') - const groupedExpectedPackages = groupByToMap(expectedPackages, 'pieceInstanceId') const allPartInstances: PlayoutPartInstanceModelImpl[] = [] for (const partInstance of partInstances) { - const rawPieceInstances = groupedPieceInstances.get(partInstance._id) ?? [] - - const pieceInstancesAndPackages = rawPieceInstances.map((pieceInstance) => ({ - pieceInstance, - expectedPackages: groupedExpectedPackages.get(pieceInstance._id) ?? [], - })) + const pieceInstances = groupedPieceInstances.get(partInstance._id) ?? [] const wrappedPartInstance = new PlayoutPartInstanceModelImpl( - context, + expectedPackages, partInstance, - pieceInstancesAndPackages, + pieceInstances, false ) allPartInstances.push(wrappedPartInstance) diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts new file mode 100644 index 0000000000..32939ff2e4 --- /dev/null +++ b/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts @@ -0,0 +1,30 @@ +import { ExpectedPackage } from '@sofie-automation/blueprints-integration' +import { + PartInstanceId, + PieceInstanceId, + ExpectedPackageId, + RundownId, +} from '@sofie-automation/corelib/dist/dataModel/Ids' +import { ReadonlyDeep } from 'type-fest' +import { PlayoutExpectedPackagesModel } from '../PlayoutExpectedPackagesModel' + +export class PlayoutExpectedPackagesModelImpl implements PlayoutExpectedPackagesModel { + ensurePackagesExist(_rundownId: RundownId, _expectedPackages: ReadonlyDeep): void { + throw new Error('Method not implemented.') + } + + setPieceInstanceReferenceToPackages( + _rundownId: RundownId, + _partInstanceId: PartInstanceId, + _pieceInstanceId: PieceInstanceId, + _expectedPackageIds: ExpectedPackageId[] + ): void { + throw new Error('Method not implemented.') + } + + readonly TODO: null = null + + async saveAllToDatabase(): Promise { + throw new Error('Method not implemented.') + } +} diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts index ee6adac74d..d35744bb15 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts @@ -23,7 +23,8 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' import { getPieceInstanceIdForPiece, - PieceInstanceWithExpectedPackages, + PieceInstance, + PieceInstancePiece, } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { serializeTimelineBlob, @@ -46,14 +47,8 @@ import { getCurrentTime } from '../../../lib' import { protectString } from '@sofie-automation/shared-lib/dist/lib/protectedString' import { queuePartInstanceTimingEvent } from '../../timings/events' import { IS_PRODUCTION } from '../../../environment' -import { - AdlibPieceWithPackages, - DeferredAfterSaveFunction, - DeferredFunction, - PlayoutModel, - PlayoutModelReadonly, -} from '../PlayoutModel' -import { writePartInstancesAndPieceInstancesAndExpectedPackages, writeAdlibTestingSegments } from './SavePlayoutModel' +import { DeferredAfterSaveFunction, DeferredFunction, PlayoutModel, PlayoutModelReadonly } from '../PlayoutModel' +import { writePartInstancesAndPieceInstances, writeAdlibTestingSegments } from './SavePlayoutModel' import { PlayoutPieceInstanceModel } from '../PlayoutPieceInstanceModel' import { DatabasePersistedModel } from '../../../modelBase' import { ExpectedPackageDBFromStudioBaselineObjects } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' @@ -61,7 +56,8 @@ import { ExpectedPlayoutItemStudio } from '@sofie-automation/corelib/dist/dataMo import { StudioBaselineHelper } from '../../../studio/model/StudioBaselineHelper' import { EventsJobs } from '@sofie-automation/corelib/dist/worker/events' import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError' -import { wrapPackagesForPieceInstance } from '../../../ingest/expectedPackages' +import { PlayoutExpectedPackagesModel, PlayoutExpectedPackagesModelReadonly } from '../PlayoutExpectedPackagesModel' +import { PlayoutExpectedPackagesModelImpl } from './PlayoutExpectedPackagesModelImpl' export class PlayoutModelReadonlyImpl implements PlayoutModelReadonly { public readonly playlistId: RundownPlaylistId @@ -85,6 +81,11 @@ export class PlayoutModelReadonlyImpl implements PlayoutModelReadonly { return this.timelineImpl } + protected readonly expectedPackagesImpl: PlayoutExpectedPackagesModelImpl + public get expectedPackages(): PlayoutExpectedPackagesModelReadonly { + return this.expectedPackagesImpl + } + protected allPartInstances: Map public constructor( @@ -95,7 +96,8 @@ export class PlayoutModelReadonlyImpl implements PlayoutModelReadonly { playlist: DBRundownPlaylist, partInstances: PlayoutPartInstanceModelImpl[], rundowns: PlayoutRundownModelImpl[], - timeline: TimelineComplete | undefined + timeline: TimelineComplete | undefined, + expectedPackages: PlayoutExpectedPackagesModelImpl ) { this.playlistId = playlistId this.playlistLock = playlistLock @@ -107,6 +109,8 @@ export class PlayoutModelReadonlyImpl implements PlayoutModelReadonly { this.timelineImpl = timeline ?? null + this.expectedPackagesImpl = expectedPackages + this.allPartInstances = normalizeArrayToMapFunc(partInstances, (p) => p.partInstance._id) } @@ -248,6 +252,10 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou #pendingPartInstanceTimingEvents = new Set() #pendingNotifyCurrentlyPlayingPartEvent = new Map() + get expectedPackages(): PlayoutExpectedPackagesModel { + return this.expectedPackagesImpl + } + get hackDeletedPartInstanceIds(): PartInstanceId[] { const result: PartInstanceId[] = [] for (const [id, doc] of this.allPartInstances) { @@ -264,9 +272,20 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou playlist: DBRundownPlaylist, partInstances: PlayoutPartInstanceModelImpl[], rundowns: PlayoutRundownModelImpl[], - timeline: TimelineComplete | undefined + timeline: TimelineComplete | undefined, + expectedPackages: PlayoutExpectedPackagesModelImpl ) { - super(context, playlistLock, playlistId, peripheralDevices, playlist, partInstances, rundowns, timeline) + super( + context, + playlistLock, + playlistId, + peripheralDevices, + playlist, + partInstances, + rundowns, + timeline, + expectedPackages + ) context.trackCache(this) this.#baselineHelper = new StudioBaselineHelper(context) @@ -297,11 +316,8 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou this.#playlistHasChanged = true } - #fixupPieceInstancesForPartInstance( - partInstance: DBPartInstance, - pieceInstances: PieceInstanceWithExpectedPackages[] - ): void { - for (const { pieceInstance } of pieceInstances) { + #fixupPieceInstancesForPartInstance(partInstance: DBPartInstance, pieceInstances: PieceInstance[]): void { + for (const pieceInstance of pieceInstances) { // Future: should these be PieceInstance already, or should that be handled here? pieceInstance._id = getPieceInstanceIdForPiece(partInstance._id, pieceInstance.piece._id) pieceInstance.partInstanceId = partInstance._id @@ -310,9 +326,9 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou createAdlibbedPartInstance( part: Omit, - pieces: AdlibPieceWithPackages[], + pieces: Omit[], fromAdlibId: PieceId | undefined, - infinitePieceInstances: PieceInstanceWithExpectedPackages[] + infinitePieceInstances: PieceInstance[] ): PlayoutPartInstanceModel { const currentPartInstance = this.currentPartInstance if (!currentPartInstance) throw new Error('No currentPartInstance') @@ -335,25 +351,15 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou this.#fixupPieceInstancesForPartInstance(newPartInstance, infinitePieceInstances) - const infinitePieceInstances2 = infinitePieceInstances.map((p) => ({ - pieceInstance: p.pieceInstance, - expectedPackages: wrapPackagesForPieceInstance( - this.context.studioId, - newPartInstance, - p.pieceInstance._id, - p.expectedPackages - ), - })) - const partInstance = new PlayoutPartInstanceModelImpl( - this.context, + this.expectedPackages, newPartInstance, - infinitePieceInstances2, + infinitePieceInstances, true ) for (const piece of pieces) { - partInstance.insertAdlibbedPiece(piece.piece, fromAdlibId, piece.expectedPackages) + partInstance.insertAdlibbedPiece(piece, fromAdlibId) } partInstance.recalculateExpectedDurationWithTransition() @@ -363,10 +369,7 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou return partInstance } - createInstanceForPart( - nextPart: ReadonlyDeep, - pieceInstances: PieceInstanceWithExpectedPackages[] - ): PlayoutPartInstanceModel { + createInstanceForPart(nextPart: ReadonlyDeep, pieceInstances: PieceInstance[]): PlayoutPartInstanceModel { const playlistActivationId = this.playlist.activationId if (!playlistActivationId) throw new Error(`Playlist is not active`) @@ -394,21 +397,25 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou this.#fixupPieceInstancesForPartInstance(newPartInstance, pieceInstances) - const pieceInstances2 = pieceInstances.map((p) => ({ - pieceInstance: p.pieceInstance, - expectedPackages: wrapPackagesForPieceInstance( - this.context.studioId, - newPartInstance, - p.pieceInstance._id, - p.expectedPackages - ), - })) - - const partInstance = new PlayoutPartInstanceModelImpl(this.context, newPartInstance, pieceInstances2, true) + const partInstance = new PlayoutPartInstanceModelImpl( + this.expectedPackages, + newPartInstance, + pieceInstances, + true + ) partInstance.recalculateExpectedDurationWithTransition() this.allPartInstances.set(newPartInstance._id, partInstance) + for (const pieceInstance of partInstance.pieceInstances) { + this.expectedPackages.setPieceInstanceReferenceToPackages( + partInstance.partInstance.rundownId, + partInstance.partInstance._id, + pieceInstance.pieceInstance._id, + pieceInstance.pieceInstance.piece.expectedPackages.map((p) => p.expectedPackageId) + ) + } + return partInstance } @@ -443,7 +450,7 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou }, } - const partInstance = new PlayoutPartInstanceModelImpl(this.context, newPartInstance, [], true) + const partInstance = new PlayoutPartInstanceModelImpl(this.expectedPackages, newPartInstance, [], true) partInstance.recalculateExpectedDurationWithTransition() this.allPartInstances.set(newPartInstance._id, partInstance) @@ -596,9 +603,10 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou this.#playlistHasChanged ? this.context.directCollections.RundownPlaylists.replace(this.playlistImpl) : undefined, - ...writePartInstancesAndPieceInstancesAndExpectedPackages(this.context, this.allPartInstances), + ...writePartInstancesAndPieceInstances(this.context, this.allPartInstances), writeAdlibTestingSegments(this.context, this.rundownsImpl), this.#baselineHelper.saveAllToDatabase(), + this.expectedPackagesImpl.saveAllToDatabase(), ]) this.#playlistHasChanged = false diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts index 9a76457599..3595b6196b 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPartInstanceModelImpl.ts @@ -1,9 +1,4 @@ -import { - ExpectedPackageId, - PieceId, - PieceInstanceId, - RundownPlaylistActivationId, -} from '@sofie-automation/corelib/dist/dataModel/Ids' +import { PieceId, PieceInstanceId, RundownPlaylistActivationId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance' import { @@ -11,8 +6,6 @@ import { omitPiecePropertiesForInstance, PieceInstance, PieceInstancePiece, - PieceInstanceWithExpectedPackages, - PieceInstanceWithExpectedPackagesFull, } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { clone, getRandomId } from '@sofie-automation/corelib/dist/lib' import { getCurrentTime } from '../../../lib' @@ -23,7 +16,6 @@ import { } from '@sofie-automation/corelib/dist/playout/timings' import { PartNote } from '@sofie-automation/corelib/dist/dataModel/Notes' import { - ExpectedPackage, IBlueprintMutatablePart, IBlueprintPieceType, PieceLifespan, @@ -37,15 +29,11 @@ import { EmptyPieceTimelineObjectsBlob } from '@sofie-automation/corelib/dist/da import _ = require('underscore') import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { IBlueprintMutatablePartSampleKeys } from '../../../blueprints/context/lib' -import { ExpectedPackageDBFromPieceInstance } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' -import { wrapPackagesForPieceInstance } from '../../../ingest/expectedPackages' -import { JobContext } from '../../../jobs' +import { PlayoutExpectedPackagesModel } from '../PlayoutExpectedPackagesModel' interface PlayoutPieceInstanceModelSnapshotImpl { PieceInstance: PieceInstance - ExpectedPackages: ExpectedPackageDBFromPieceInstance[] HasChanges: boolean - ExpectedPackageChanges: ExpectedPackageId[] } class PlayoutPartInstanceModelSnapshotImpl implements PlayoutPartInstanceModelSnapshot { readonly __isPlayoutPartInstanceModelBackup = true @@ -60,14 +48,14 @@ class PlayoutPartInstanceModelSnapshotImpl implements PlayoutPartInstanceModelSn this.partInstance = clone(copyFrom.partInstanceImpl) this.partInstanceHasChanges = copyFrom.partInstanceHasChanges + // nocommit - should this be concerned with expectedPackages? + const pieceInstances = new Map() for (const [pieceInstanceId, pieceInstance] of copyFrom.pieceInstancesImpl) { if (pieceInstance) { pieceInstances.set(pieceInstanceId, { PieceInstance: clone(pieceInstance.PieceInstanceImpl), - ExpectedPackages: clone(pieceInstance.expectedPackages), HasChanges: pieceInstance.HasChanges, - ExpectedPackageChanges: Array.from(pieceInstance.ExpectedPackagesWithChanges), }) } else { pieceInstances.set(pieceInstanceId, null) @@ -77,7 +65,7 @@ class PlayoutPartInstanceModelSnapshotImpl implements PlayoutPartInstanceModelSn } } export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { - readonly #context: JobContext + #expectedPackages: PlayoutExpectedPackagesModel partInstanceImpl: DBPartInstance pieceInstancesImpl: Map @@ -172,27 +160,18 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { } constructor( - context: JobContext, + expectedPackages: PlayoutExpectedPackagesModel, partInstance: DBPartInstance, - pieceInstances: PieceInstanceWithExpectedPackagesFull[], + pieceInstances: PieceInstance[], hasChanges: boolean ) { - this.#context = context + this.#expectedPackages = expectedPackages this.partInstanceImpl = partInstance this.#partInstanceHasChanges = hasChanges this.pieceInstancesImpl = new Map() - for (const { pieceInstance, expectedPackages } of pieceInstances) { - this.pieceInstancesImpl.set( - pieceInstance._id, - new PlayoutPieceInstanceModelImpl( - this.#context, - pieceInstance, - expectedPackages, - hasChanges, - hasChanges ? expectedPackages.map((p) => p._id) : null - ) - ) + for (const pieceInstance of pieceInstances) { + this.pieceInstancesImpl.set(pieceInstance._id, new PlayoutPieceInstanceModelImpl(pieceInstance, hasChanges)) } } @@ -217,13 +196,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { if (pieceInstance) { this.pieceInstancesImpl.set( pieceInstanceId, - new PlayoutPieceInstanceModelImpl( - this.#context, - pieceInstance.PieceInstance, - pieceInstance.ExpectedPackages, - pieceInstance.HasChanges, - pieceInstance.ExpectedPackageChanges - ) + new PlayoutPieceInstanceModelImpl(pieceInstance.PieceInstance, pieceInstance.HasChanges) ) } else { this.pieceInstancesImpl.set(pieceInstanceId, null) @@ -245,8 +218,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { insertAdlibbedPiece( piece: Omit, - fromAdlibId: PieceId | undefined, - pieceExpectedPackages: ReadonlyDeep + fromAdlibId: PieceId | undefined ): PlayoutPieceInstanceModel { const pieceInstance: PieceInstance = { _id: protectString(`${this.partInstance._id}_${piece._id}`), @@ -270,16 +242,15 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { setupPieceInstanceInfiniteProperties(pieceInstance) - const expectedPackages = this.#convertExpectedPackagesForPieceInstance(pieceInstance, pieceExpectedPackages) + const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(pieceInstance, true) + this.pieceInstancesImpl.set(pieceInstance._id, pieceInstanceModel) - const pieceInstanceModel = new PlayoutPieceInstanceModelImpl( - this.#context, - pieceInstance, - expectedPackages, - true, - expectedPackages.map((p) => p._id) + this.#expectedPackages.setPieceInstanceReferenceToPackages( + this.partInstanceImpl.rundownId, + this.partInstanceImpl._id, + pieceInstance._id, + pieceInstance.piece.expectedPackages.map((p) => p.expectedPackageId) ) - this.pieceInstancesImpl.set(pieceInstance._id, pieceInstanceModel) return pieceInstanceModel } @@ -318,29 +289,21 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { plannedStoppedPlayback: extendPieceInstance.pieceInstance.plannedStoppedPlayback, } - // Don't preserve any ExpectedPackages, the existing ones will suffice - const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(this.#context, newInstance, [], true, null) + const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(newInstance, true) this.pieceInstancesImpl.set(newInstance._id, pieceInstanceModel) - return pieceInstanceModel - } + // Don't preserve any ExpectedPackages, the existing ones will suffice // nocommit - verify this + // this.#expectedPackages.setPieceInstanceReferenceToPackages( + // this.partInstanceImpl.rundownId, + // this.partInstanceImpl._id, + // newPieceInstance._id, + // newPieceInstance.piece.expectedPackages.map((p) => p.expectedPackageId) + // ) - #convertExpectedPackagesForPieceInstance( - pieceInstance: ReadonlyDeep, - expectedPackages: ReadonlyDeep - ): ExpectedPackageDBFromPieceInstance[] { - return wrapPackagesForPieceInstance( - this.#context.studioId, - this.partInstanceImpl, - pieceInstance._id, - expectedPackages - ) + return pieceInstanceModel } - insertPlannedPiece( - piece: Omit, - pieceExpectedPackages: ReadonlyDeep - ): PlayoutPieceInstanceModel { + insertPlannedPiece(piece: Omit): PlayoutPieceInstanceModel { const pieceInstanceId = getPieceInstanceIdForPiece(this.partInstance._id, piece._id) if (this.pieceInstancesImpl.has(pieceInstanceId)) throw new Error(`PieceInstance "${pieceInstanceId}" already exists`) @@ -359,16 +322,15 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { // Ensure the infinite-ness is setup correctly setupPieceInstanceInfiniteProperties(newPieceInstance) - const expectedPackages = this.#convertExpectedPackagesForPieceInstance(newPieceInstance, pieceExpectedPackages) + const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(newPieceInstance, true) + this.pieceInstancesImpl.set(pieceInstanceId, pieceInstanceModel) - const pieceInstanceModel = new PlayoutPieceInstanceModelImpl( - this.#context, - newPieceInstance, - expectedPackages, - true, - expectedPackages.map((p) => p._id) + this.#expectedPackages.setPieceInstanceReferenceToPackages( + this.partInstanceImpl.rundownId, + this.partInstanceImpl._id, + newPieceInstance._id, + newPieceInstance.piece.expectedPackages.map((p) => p.expectedPackageId) ) - this.pieceInstancesImpl.set(pieceInstanceId, pieceInstanceModel) return pieceInstanceModel } @@ -406,7 +368,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { } setupPieceInstanceInfiniteProperties(newPieceInstance) - const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(this.#context, newPieceInstance, [], true, null) + const pieceInstanceModel = new PlayoutPieceInstanceModelImpl(newPieceInstance, true) this.pieceInstancesImpl.set(newPieceInstance._id, pieceInstanceModel) return pieceInstanceModel @@ -443,7 +405,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { return false } - replaceInfinitesFromPreviousPlayhead(pieceInstances: PieceInstanceWithExpectedPackages[]): void { + replaceInfinitesFromPreviousPlayhead(pieceInstances: PieceInstance[]): void { // Future: this should do some of the wrapping from a Piece into a PieceInstance // Remove old infinite pieces @@ -455,7 +417,7 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { } } - for (const { pieceInstance, expectedPackages } of pieceInstances) { + for (const pieceInstance of pieceInstances) { if (this.pieceInstancesImpl.get(pieceInstance._id)) throw new Error( `Cannot replace infinite PieceInstance "${pieceInstance._id}" as it replaces a non-infinite` @@ -466,49 +428,42 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel { // Future: should this do any deeper validation of the PieceInstances? - const newExpectedPackages = this.#convertExpectedPackagesForPieceInstance(pieceInstance, expectedPackages) + this.pieceInstancesImpl.set(pieceInstance._id, new PlayoutPieceInstanceModelImpl(pieceInstance, true)) - this.pieceInstancesImpl.set( + this.#expectedPackages.setPieceInstanceReferenceToPackages( + this.partInstanceImpl.rundownId, + this.partInstanceImpl._id, pieceInstance._id, - new PlayoutPieceInstanceModelImpl( - this.#context, - pieceInstance, - newExpectedPackages, - true, - newExpectedPackages.map((p) => p._id) - ) + pieceInstance.piece.expectedPackages.map((p) => p.expectedPackageId) ) } } - mergeOrInsertPieceInstance( - doc: ReadonlyDeep, - expectedPackages: ReadonlyDeep[] - ): PlayoutPieceInstanceModel { + mergeOrInsertPieceInstance(doc: ReadonlyDeep): PlayoutPieceInstanceModel { // Future: this should do some validation of the new PieceInstance const existingPieceInstance = this.pieceInstancesImpl.get(doc._id) if (existingPieceInstance) { existingPieceInstance.mergeProperties(doc) - existingPieceInstance.setExpectedPackages(expectedPackages) + + this.#expectedPackages.setPieceInstanceReferenceToPackages( + this.partInstanceImpl.rundownId, + this.partInstanceImpl._id, + existingPieceInstance.pieceInstance._id, + existingPieceInstance.pieceInstance.piece.expectedPackages.map((p) => p.expectedPackageId) + ) return existingPieceInstance } else { - const newExpectedPackages = wrapPackagesForPieceInstance( - this.#context.studioId, - this.partInstanceImpl, - doc._id, - expectedPackages - ) + const newPieceInstance = new PlayoutPieceInstanceModelImpl(clone(doc), true) + this.pieceInstancesImpl.set(newPieceInstance.pieceInstance._id, newPieceInstance) - const newPieceInstance = new PlayoutPieceInstanceModelImpl( - this.#context, - clone(doc), - newExpectedPackages, - true, - newExpectedPackages.map((p) => p._id) + this.#expectedPackages.setPieceInstanceReferenceToPackages( + this.partInstanceImpl.rundownId, + this.partInstanceImpl._id, + newPieceInstance.pieceInstance._id, + newPieceInstance.pieceInstance.piece.expectedPackages.map((p) => p.expectedPackageId) ) - this.pieceInstancesImpl.set(newPieceInstance.pieceInstance._id, newPieceInstance) return newPieceInstance } } diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts index ebaf14600f..661eaefbf1 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutPieceInstanceModelImpl.ts @@ -1,34 +1,18 @@ -import { ExpectedPackageId, PieceInstanceInfiniteId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { PieceInstanceInfiniteId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { clone, getRandomId } from '@sofie-automation/corelib/dist/lib' -import { ExpectedPackage, Time } from '@sofie-automation/blueprints-integration' +import { Time } from '@sofie-automation/blueprints-integration' import { PlayoutPieceInstanceModel } from '../PlayoutPieceInstanceModel' import _ = require('underscore') -import { - ExpectedPackageDBFromPieceInstance, - ExpectedPackageDBType, -} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' -import { - DocumentChanges, - getDocumentChanges, - diffAndReturnLatestObjects, -} from '../../../ingest/model/implementation/utils' -import { generateExpectedPackageBases } from '../../../ingest/expectedPackages' -import { JobContext } from '../../../jobs' export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel { - readonly #context: JobContext - /** * The raw mutable PieceInstance * Danger: This should not be modified externally, this is exposed for cloning and saving purposes */ PieceInstanceImpl: PieceInstance - #expectedPackages: ExpectedPackageDBFromPieceInstance[] - ExpectedPackagesWithChanges = new Set() - /** * Set/delete a value for this PieceInstance, and track that there are changes * @param key Property key @@ -73,8 +57,7 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel * Whether this PieceInstance has unsaved changes */ get HasChanges(): boolean { - // nocommit - should this be two properties? - return this.#hasChanges || this.ExpectedPackagesWithChanges.size > 0 + return this.#hasChanges } /** @@ -82,33 +65,15 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel */ clearChangedFlag(): void { this.#hasChanges = false - this.ExpectedPackagesWithChanges.clear() } get pieceInstance(): ReadonlyDeep { return this.PieceInstanceImpl } - get expectedPackages(): ReadonlyDeep { - return [...this.#expectedPackages] - } - - get expectedPackagesChanges(): DocumentChanges { - return getDocumentChanges(this.ExpectedPackagesWithChanges, this.#expectedPackages) - } - - constructor( - context: JobContext, - pieceInstances: PieceInstance, - expectedPackages: ExpectedPackageDBFromPieceInstance[], - hasChanges: boolean, - expectedPackagesWithChanges: ExpectedPackageId[] | null - ) { - this.#context = context + constructor(pieceInstances: PieceInstance, hasChanges: boolean) { this.PieceInstanceImpl = pieceInstances - this.#expectedPackages = expectedPackages this.#hasChanges = hasChanges - this.ExpectedPackagesWithChanges = new Set(expectedPackagesWithChanges) } /** @@ -172,42 +137,30 @@ export class PlayoutPieceInstanceModelImpl implements PlayoutPieceInstanceModel ) } - /** - * Update the expected packages for the PieceInstance - * @param expectedPackages The new packages - */ - setExpectedPackages(expectedPackages: ReadonlyDeep[]): void { - // nocommit - refactor this into a simpler type than `ExpectedPackagesStore` or just reuse that? - - const bases = generateExpectedPackageBases(this.#context.studioId, this.PieceInstanceImpl._id, expectedPackages) - const newExpectedPackages: ExpectedPackageDBFromPieceInstance[] = bases.map((base) => ({ - ...base, - - fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, - partInstanceId: this.PieceInstanceImpl.partInstanceId, - pieceInstanceId: this.PieceInstanceImpl._id, - segmentId: this.PieceInstanceImpl.segmentId, // TODO - rundownId: this.PieceInstanceImpl.rundownId, - pieceId: null, - })) - - this.#expectedPackages = diffAndReturnLatestObjects( - this.ExpectedPackagesWithChanges, - this.#expectedPackages, - newExpectedPackages, - mutateExpectedPackage - ) - } -} - -// nocommit - this is copied from elsewhere -function mutateExpectedPackage( - oldObj: ExpectedPackageDBFromPieceInstance, - newObj: ExpectedPackageDBFromPieceInstance -): ExpectedPackageDBFromPieceInstance { - return { - ...newObj, - // Retain the created property - created: oldObj.created, - } + // /** + // * Update the expected packages for the PieceInstance + // * @param expectedPackages The new packages + // */ + // setExpectedPackages(expectedPackages: ReadonlyDeep[]): void { + // // nocommit - refactor this into a simpler type than `ExpectedPackagesStore` or just reuse that? + + // const bases = generateExpectedPackageBases(this.#context.studioId, this.PieceInstanceImpl._id, expectedPackages) + // const newExpectedPackages: ExpectedPackageDBFromPieceInstance[] = bases.map((base) => ({ + // ...base, + + // fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, + // partInstanceId: this.PieceInstanceImpl.partInstanceId, + // pieceInstanceId: this.PieceInstanceImpl._id, + // segmentId: this.PieceInstanceImpl.segmentId, // TODO + // rundownId: this.PieceInstanceImpl.rundownId, + // pieceId: null, + // })) + + // this.#expectedPackages = diffAndReturnLatestObjects( + // this.ExpectedPackagesWithChanges, + // this.#expectedPackages, + // newExpectedPackages, + // mutateExpectedPackage + // ) + // } } diff --git a/packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts b/packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts index 414b5f4c76..7fcf051046 100644 --- a/packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts +++ b/packages/job-worker/src/playout/model/implementation/SavePlayoutModel.ts @@ -7,8 +7,6 @@ import { AnyBulkWriteOperation } from 'mongodb' import { JobContext } from '../../../jobs' import { PlayoutPartInstanceModelImpl } from './PlayoutPartInstanceModelImpl' import { PlayoutRundownModelImpl } from './PlayoutRundownModelImpl' -import { DocumentChangeTracker } from '../../../ingest/model/implementation/DocumentChangeTracker' -import { ExpectedPackageDB, ExpectedPackageDBType } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' /** * Save any changed AdlibTesting Segments @@ -60,13 +58,12 @@ export async function writeAdlibTestingSegments( * @param context Context from the job queue * @param partInstances Map of PartInstances to check for changes or deletion */ -export function writePartInstancesAndPieceInstancesAndExpectedPackages( +export function writePartInstancesAndPieceInstances( context: JobContext, partInstances: Map -): [Promise, Promise, Promise] { +): [Promise, Promise] { const partInstanceOps: AnyBulkWriteOperation[] = [] const pieceInstanceOps: AnyBulkWriteOperation[] = [] - const expectedPackagesChanges = new DocumentChangeTracker() const deletedPartInstanceIds: PartInstanceId[] = [] const deletedPieceInstanceIds: PieceInstanceId[] = [] @@ -96,8 +93,6 @@ export function writePartInstancesAndPieceInstancesAndExpectedPackages( upsert: true, }, }) - - expectedPackagesChanges.addChanges(pieceInstance.expectedPackagesChanges, false) } } @@ -105,8 +100,6 @@ export function writePartInstancesAndPieceInstancesAndExpectedPackages( } } - const expectedPackagesOps = expectedPackagesChanges.generateWriteOps() - // Delete any removed PartInstances if (deletedPartInstanceIds.length) { partInstanceOps.push({ @@ -123,15 +116,6 @@ export function writePartInstancesAndPieceInstancesAndExpectedPackages( }, }, }) - - expectedPackagesOps.push({ - deleteMany: { - filter: { - fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, - partInstanceId: { $in: deletedPartInstanceIds }, - }, - }, - }) } // Delete any removed PieceInstances @@ -143,14 +127,6 @@ export function writePartInstancesAndPieceInstancesAndExpectedPackages( }, }, }) - expectedPackagesOps.push({ - deleteMany: { - filter: { - fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, - pieceInstanceId: { $in: deletedPieceInstanceIds }, - }, - }, - }) } return [ @@ -158,8 +134,5 @@ export function writePartInstancesAndPieceInstancesAndExpectedPackages( pieceInstanceOps.length ? context.directCollections.PieceInstances.bulkWrite(pieceInstanceOps) : Promise.resolve(), - expectedPackagesOps.length - ? context.directCollections.ExpectedPackages.bulkWrite(expectedPackagesOps) - : Promise.resolve(), ] } diff --git a/packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts b/packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts index b5b957a47b..5d756c5600 100644 --- a/packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts +++ b/packages/job-worker/src/playout/model/implementation/__tests__/SavePlayoutModel.spec.ts @@ -4,7 +4,7 @@ import { protectString } from '@sofie-automation/corelib/dist/protectedString' import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' import { PlayoutRundownModelImpl } from '../PlayoutRundownModelImpl' import { setupDefaultJobEnvironment } from '../../../../__mocks__/context' -import { writePartInstancesAndPieceInstancesAndExpectedPackages, writeAdlibTestingSegments } from '../SavePlayoutModel' +import { writePartInstancesAndPieceInstances, writeAdlibTestingSegments } from '../SavePlayoutModel' import { PlayoutPartInstanceModelImpl } from '../PlayoutPartInstanceModelImpl' import { PartInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' @@ -122,7 +122,7 @@ describe('SavePlayoutModel', () => { it('no PartInstances', async () => { const context = setupDefaultJobEnvironment() - await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, new Map())) + await Promise.all(writePartInstancesAndPieceInstances(context, new Map())) expect(context.mockCollections.PartInstances.operations).toHaveLength(0) expect(context.mockCollections.PieceInstances.operations).toHaveLength(0) @@ -135,7 +135,7 @@ describe('SavePlayoutModel', () => { partInstances.set(protectString('id0'), null) partInstances.set(protectString('id1'), null) - await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, partInstances)) + await Promise.all(writePartInstancesAndPieceInstances(context, partInstances)) expect(context.mockCollections.PartInstances.operations).toHaveLength(2) expect(context.mockCollections.PartInstances.operations).toMatchInlineSnapshot(` @@ -197,7 +197,7 @@ describe('SavePlayoutModel', () => { const partInstances = new Map() partInstances.set(protectString('id0'), partInstanceModel) - await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, partInstances)) + await Promise.all(writePartInstancesAndPieceInstances(context, partInstances)) expect(context.mockCollections.PartInstances.operations).toHaveLength(0) @@ -237,7 +237,7 @@ describe('SavePlayoutModel', () => { const partInstances = new Map() partInstances.set(protectString('id0'), partInstanceModel) - await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, partInstances)) + await Promise.all(writePartInstancesAndPieceInstances(context, partInstances)) expect(context.mockCollections.PartInstances.operations).toHaveLength(2) expect(context.mockCollections.PartInstances.operations).toMatchInlineSnapshot(` @@ -273,7 +273,7 @@ describe('SavePlayoutModel', () => { const partInstances = new Map() partInstances.set(protectString('id0'), partInstanceModel) - await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, partInstances)) + await Promise.all(writePartInstancesAndPieceInstances(context, partInstances)) expect(context.mockCollections.PartInstances.operations).toHaveLength(0) @@ -319,7 +319,7 @@ describe('SavePlayoutModel', () => { partInstances.set(protectString('id0'), partInstanceModel) partInstances.set(protectString('id1'), null) - await Promise.all(writePartInstancesAndPieceInstancesAndExpectedPackages(context, partInstances)) + await Promise.all(writePartInstancesAndPieceInstances(context, partInstances)) expect(context.mockCollections.PartInstances.operations).toHaveLength(3) expect(context.mockCollections.PartInstances.operations).toMatchInlineSnapshot(` From 15569132c5671a46ca0521a1cd5bb2ad851d4c8a Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 4 Sep 2024 15:51:54 +0100 Subject: [PATCH 16/22] wip --- .../corelib/src/dataModel/ExpectedPackages.ts | 8 +- .../src/ingest/model/IngestModel.ts | 3 +- .../model/implementation/IngestModelImpl.ts | 97 +++++++++++++------ packages/job-worker/src/ingest/packageInfo.ts | 36 ++++--- 4 files changed, 96 insertions(+), 48 deletions(-) diff --git a/packages/corelib/src/dataModel/ExpectedPackages.ts b/packages/corelib/src/dataModel/ExpectedPackages.ts index 1e190ad8a7..cfcb3ac366 100644 --- a/packages/corelib/src/dataModel/ExpectedPackages.ts +++ b/packages/corelib/src/dataModel/ExpectedPackages.ts @@ -144,13 +144,15 @@ export interface ExpectedPackageIngestSourceBaselineObjects { fromPieceType: ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS } -export type ExpectedPackageIngestSource = - | ExpectedPackageIngestSourcePiece - | ExpectedPackageIngestSourceAdlibAction +export type ExpectedPackageIngestSourcePart = ExpectedPackageIngestSourcePiece | ExpectedPackageIngestSourceAdlibAction + +export type ExpectedPackageIngestSourceRundownBaseline = | ExpectedPackageIngestSourceBaselineAdlibPiece | ExpectedPackageIngestSourceBaselineAdlibAction | ExpectedPackageIngestSourceBaselineObjects +export type ExpectedPackageIngestSource = ExpectedPackageIngestSourcePart | ExpectedPackageIngestSourceRundownBaseline + export interface ExpectedPackageWithId { _id: ExpectedPackageId expectedPackage: ReadonlyDeep diff --git a/packages/job-worker/src/ingest/model/IngestModel.ts b/packages/job-worker/src/ingest/model/IngestModel.ts index 7ade42d0b0..0fd3e52e28 100644 --- a/packages/job-worker/src/ingest/model/IngestModel.ts +++ b/packages/job-worker/src/ingest/model/IngestModel.ts @@ -6,6 +6,7 @@ import { ExpectedPackageDBFromRundownBaselineObjects, ExpectedPackageDBNew, ExpectedPackageFromRundown, + ExpectedPackageIngestSource, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { ExpectedPlayoutItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedPlayoutItem' import { @@ -148,7 +149,7 @@ export interface IngestModelReadonly { * Search for an ExpectedPackage through the whole Rundown * @param id Id of the ExpectedPackage */ - findExpectedPackage(packageId: ExpectedPackageId): ReadonlyDeep | undefined + findExpectedPackageIngestSources(packageId: ExpectedPackageId): ReadonlyDeep[] } export interface IngestModel extends IngestModelReadonly, BaseModel { diff --git a/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts b/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts index 9f846206fc..5e4b1a7338 100644 --- a/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts +++ b/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts @@ -2,10 +2,12 @@ import { AdLibAction } from '@sofie-automation/corelib/dist/dataModel/AdlibActio import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { ExpectedMediaItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedMediaItem' import { - ExpectedPackageDB, ExpectedPackageDBFromPiece, + ExpectedPackageDBNew, ExpectedPackageDBType, - ExpectedPackageFromRundown, + ExpectedPackageIngestSource, + ExpectedPackageIngestSourcePart, + ExpectedPackageIngestSourceRundownBaseline, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { ExpectedPlayoutItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedPlayoutItem' import { @@ -33,23 +35,20 @@ import { IngestSegmentModel } from '../IngestSegmentModel' import { IngestSegmentModelImpl } from './IngestSegmentModelImpl' import { IngestPartModel } from '../IngestPartModel' import { + assertNever, clone, Complete, deleteAllUndefinedProperties, getRandomId, groupByToMap, + groupByToMapFunc, literal, } from '@sofie-automation/corelib/dist/lib' import { IngestPartModelImpl } from './IngestPartModelImpl' import { DatabasePersistedModel } from '../../../modelBase' import { ExpectedPackagesStore } from './ExpectedPackagesStore' import { ReadonlyDeep } from 'type-fest' -import { - ExpectedPackageForIngestModel, - ExpectedPackageForIngestModelBaseline, - IngestModel, - IngestReplaceSegmentType, -} from '../IngestModel' +import { ExpectedPackageForIngestModelBaseline, IngestModel, IngestReplaceSegmentType } from '../IngestModel' import { RundownNote } from '@sofie-automation/corelib/dist/dataModel/Notes' import { diffAndReturnLatestObjects } from './utils' import _ = require('underscore') @@ -71,7 +70,7 @@ export interface IngestModelImplExistingData { adLibActions: AdLibAction[] expectedMediaItems: ExpectedMediaItemRundown[] expectedPlayoutItems: ExpectedPlayoutItemRundown[] - expectedPackages: ExpectedPackageDB[] + expectedPackages: ExpectedPackageDBNew[] } /** @@ -114,7 +113,7 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { protected readonly segmentsImpl: Map - readonly #rundownBaselineExpectedPackagesStore: ExpectedPackagesStore + readonly #rundownBaselineExpectedPackagesStore: ExpectedPackagesStore get rundownBaselineTimelineObjects(): LazyInitialiseReadonly { // Return a simplified view of what we store, of just `timelineObjectsString` @@ -173,18 +172,8 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { const groupedExpectedMediaItems = groupByToMap(existingData.expectedMediaItems, 'partId') const groupedExpectedPlayoutItems = groupByToMap(existingData.expectedPlayoutItems, 'partId') - const rundownExpectedPackages = existingData.expectedPackages.filter( - (pkg): pkg is ExpectedPackageFromRundown => - pkg.fromPieceType === ExpectedPackageDBType.PIECE || - pkg.fromPieceType === ExpectedPackageDBType.ADLIB_PIECE || - pkg.fromPieceType === ExpectedPackageDBType.ADLIB_ACTION - ) - const groupedExpectedPackages = groupByToMap(rundownExpectedPackages, 'partId') - const baselineExpectedPackages = existingData.expectedPackages.filter( - (pkg): pkg is ExpectedPackageForIngestModelBaseline => - pkg.fromPieceType === ExpectedPackageDBType.BASELINE_ADLIB_ACTION || - pkg.fromPieceType === ExpectedPackageDBType.BASELINE_ADLIB_PIECE || - pkg.fromPieceType === ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS + const { baselineExpectedPackages, groupedExpectedPackagesByPart } = groupExpectedPackages( + existingData.expectedPackages ) this.#rundownBaselineExpectedPackagesStore = new ExpectedPackagesStore( @@ -212,7 +201,7 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { groupedAdLibActions.get(part._id) ?? [], groupedExpectedMediaItems.get(part._id) ?? [], groupedExpectedPlayoutItems.get(part._id) ?? [], - groupedExpectedPackages.get(part._id) ?? [] + groupedExpectedPackagesByPart.get(part._id) ?? [] ) ) this.segmentsImpl.set(segment._id, { @@ -358,18 +347,20 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { return undefined } - findExpectedPackage(packageId: ExpectedPackageId): ReadonlyDeep | undefined { + findExpectedPackageIngestSources(packageId: ExpectedPackageId): ReadonlyDeep[] { + const sources: ReadonlyDeep[] = [] + const baselinePackage = this.#rundownBaselineExpectedPackagesStore.expectedPackages.find( (pkg) => pkg._id === packageId ) - if (baselinePackage) return baselinePackage + if (baselinePackage) sources.push(...baselinePackage.ingestSources) for (const part of this.getAllOrderedParts()) { const partPackage = part.expectedPackages.find((pkg) => pkg._id === packageId) - if (partPackage) return partPackage + if (partPackage) sources.push(...partPackage.ingestSources) } - return undefined + return sources } removeSegment(id: SegmentId): void { @@ -487,7 +478,7 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { timelineObjectsBlob: PieceTimelineObjectsBlob, adlibPieces: RundownBaselineAdLibItem[], adlibActions: RundownBaselineAdLibAction[], - expectedPackages: ExpectedPackageForIngestModelBaseline[] + expectedPackages: ExpectedPackageDBNew[] ): Promise { const [loadedRundownBaselineObjs, loadedRundownBaselineAdLibPieces, loadedRundownBaselineAdLibActions] = await Promise.all([ @@ -701,3 +692,53 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { span?.end() } } + +function groupExpectedPackages(expectedPackages: ExpectedPackageDBNew[]) { + const baselineExpectedPackages: ExpectedPackageDBNew[] = [] + const groupedExpectedPackagesByPart = new Map() + + for (const expectedPackage of expectedPackages) { + const baselineIngestSources: ExpectedPackageIngestSourceRundownBaseline[] = [] + const rundownIngestSources: ExpectedPackageIngestSourcePart[] = [] + + for (const src of expectedPackage.ingestSources) { + switch (src.fromPieceType) { + case ExpectedPackageDBType.BASELINE_ADLIB_ACTION: + case ExpectedPackageDBType.BASELINE_ADLIB_PIECE: + case ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS: + baselineIngestSources.push(src) + break + case ExpectedPackageDBType.PIECE: + case ExpectedPackageDBType.ADLIB_PIECE: + case ExpectedPackageDBType.ADLIB_ACTION: + rundownIngestSources.push(src) + break + default: + assertNever(src) + break + } + } + + if (baselineIngestSources.length > 0) { + baselineExpectedPackages.push({ + ...expectedPackage, + ingestSources: baselineIngestSources, + }) + } + + const sourcesByPartId = groupByToMapFunc(rundownIngestSources, (src) => src.partId) + for (const [partId, sources] of sourcesByPartId.entries()) { + const partPackages = groupedExpectedPackagesByPart.get(partId) ?? [] + partPackages.push({ + ...expectedPackage, + ingestSources: sources, + }) + groupedExpectedPackagesByPart.set(partId, partPackages) + } + } + + return { + baselineExpectedPackages, + groupedExpectedPackagesByPart, + } +} diff --git a/packages/job-worker/src/ingest/packageInfo.ts b/packages/job-worker/src/ingest/packageInfo.ts index f961aa6cd7..47e4e1755f 100644 --- a/packages/job-worker/src/ingest/packageInfo.ts +++ b/packages/job-worker/src/ingest/packageInfo.ts @@ -9,6 +9,7 @@ import { JobContext } from '../jobs' import { regenerateSegmentsFromIngestData } from './generationSegment' import { UpdateIngestRundownAction, runIngestJob, runWithRundownLock } from './lock' import { loadIngestModelFromRundown } from './model/implementation/LoadIngestModel' +import { assertNever } from '@sofie-automation/corelib/dist/lib' /** * Debug: Regenerate ExpectedPackages for a Rundown @@ -65,23 +66,26 @@ export async function handleUpdatedPackageInfoForRundown( let regenerateRundownBaseline = false for (const packageId of data.packageIds) { - const pkg = ingestModel.findExpectedPackage(packageId) - if (pkg) { - if ( - pkg.fromPieceType === ExpectedPackageDBType.PIECE || - pkg.fromPieceType === ExpectedPackageDBType.ADLIB_PIECE || - pkg.fromPieceType === ExpectedPackageDBType.ADLIB_ACTION - ) { - segmentsToUpdate.add(pkg.segmentId) - } else if ( - pkg.fromPieceType === ExpectedPackageDBType.BASELINE_ADLIB_ACTION || - pkg.fromPieceType === ExpectedPackageDBType.BASELINE_ADLIB_PIECE || - pkg.fromPieceType === ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS - ) { - regenerateRundownBaseline = true + const pkgIngestSources = ingestModel.findExpectedPackageIngestSources(packageId) + for (const source of pkgIngestSources) { + switch (source.fromPieceType) { + case ExpectedPackageDBType.PIECE: + case ExpectedPackageDBType.ADLIB_PIECE: + case ExpectedPackageDBType.ADLIB_ACTION: + segmentsToUpdate.add(source.segmentId) + break + + case ExpectedPackageDBType.BASELINE_ADLIB_ACTION: + case ExpectedPackageDBType.BASELINE_ADLIB_PIECE: + case ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS: + regenerateRundownBaseline = true + break + default: + assertNever(source) } - } else { - logger.warn(`onUpdatedPackageInfoForRundown: Missing package: "${packageId}"`) + } + if (pkgIngestSources.length === 0) { + logger.warn(`onUpdatedPackageInfoForRundown: Missing ingestSources for package: "${packageId}"`) } } From 8efd628116ed32f1103a29a03682c551d8dcfad7 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 4 Sep 2024 15:56:24 +0100 Subject: [PATCH 17/22] wip --- .../PartAndPieceInstanceActionService.ts | 18 +++++++++++++----- .../job-worker/src/ingest/generationRundown.ts | 3 --- .../model/PlayoutExpectedPackagesModel.ts | 5 +++++ .../PlayoutExpectedPackagesModelImpl.ts | 7 +++++++ 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts index 2c33cd89a9..3caa365655 100644 --- a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts +++ b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts @@ -264,7 +264,7 @@ export class PartAndPieceInstanceActionService { const trimmedPiece: IBlueprintPiece = _.pick(rawPiece, IBlueprintPieceObjectsSampleKeys) - const piece = postProcessPieces( + const postProcessed = postProcessPieces( this._context, [trimmedPiece], this.showStyleCompound.blueprintId, @@ -272,11 +272,17 @@ export class PartAndPieceInstanceActionService { partInstance.partInstance.segmentId, partInstance.partInstance.part._id, part === 'current' - )[0] - piece.doc._id = getRandomId() // Make id random, as postProcessPieces is too predictable (for ingest) + ) + const piece = postProcessed.docs[0] + piece._id = getRandomId() // Make id random, as postProcessPieces is too predictable (for ingest) + + this._playoutModel.expectedPackages.ensurePackagesExistMap( + this._rundown.rundown._id, + postProcessed.expectedPackages + ) // Do the work - const newPieceInstance = partInstance.insertAdlibbedPiece(piece.doc, undefined, piece.expectedPackages ?? []) + const newPieceInstance = partInstance.insertAdlibbedPiece(piece, undefined) if (part === 'current') { this.currentPartState = Math.max(this.currentPartState, ActionPartChange.SAFE_CHANGE) @@ -421,6 +427,8 @@ export class PartAndPieceInstanceActionService { throw new Error('Cannot queue a part which is not playable') } + this._playoutModel.expectedPackages.ensurePackagesExistMap(this._rundown.rundown._id, pieces.expectedPackages) + // Do the work const newPartInstance = await insertQueuedPartWithPieces( this._context, @@ -428,7 +436,7 @@ export class PartAndPieceInstanceActionService { this._rundown, currentPartInstance, newPart, - pieces.map((p) => ({ piece: p.doc, expectedPackages: p.expectedPackages })), + pieces.docs, undefined ) diff --git a/packages/job-worker/src/ingest/generationRundown.ts b/packages/job-worker/src/ingest/generationRundown.ts index 9faee12d90..f0bd051bc7 100644 --- a/packages/job-worker/src/ingest/generationRundown.ts +++ b/packages/job-worker/src/ingest/generationRundown.ts @@ -1,7 +1,4 @@ import { - ExpectedPackageDBFromBaselineAdLibAction, - ExpectedPackageDBFromBaselineAdLibPiece, - ExpectedPackageDBFromRundownBaselineObjects, ExpectedPackageDBNew, ExpectedPackageDBType, getContentVersionHash, diff --git a/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts b/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts index e5f5288c69..957b3b9edd 100644 --- a/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts @@ -14,6 +14,11 @@ export interface PlayoutExpectedPackagesModelReadonly { export interface PlayoutExpectedPackagesModel extends PlayoutExpectedPackagesModelReadonly { ensurePackagesExist(rundownId: RundownId, expectedPackages: ReadonlyDeep): void + ensurePackagesExistMap( + rundownId: RundownId, + expectedPackages: ReadonlyMap> + ): void + setPieceInstanceReferenceToPackages( rundownId: RundownId, partInstanceId: PartInstanceId, diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts index 32939ff2e4..0902296ac6 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts @@ -13,6 +13,13 @@ export class PlayoutExpectedPackagesModelImpl implements PlayoutExpectedPackages throw new Error('Method not implemented.') } + ensurePackagesExistMap( + _rundownId: RundownId, + _expectedPackages: ReadonlyMap> + ): void { + throw new Error('Method not implemented.') + } + setPieceInstanceReferenceToPackages( _rundownId: RundownId, _partInstanceId: PartInstanceId, From b4737d64445f48576219cde340e003194a0c9aa3 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 4 Sep 2024 16:10:19 +0100 Subject: [PATCH 18/22] wip --- .../src/blueprints/context/watchedPackages.ts | 15 +++++------- .../src/ingest/model/IngestModel.ts | 8 +++---- .../src/ingest/model/IngestPartModel.ts | 4 ++-- .../implementation/ExpectedPackagesStore.ts | 24 ++++++++++--------- .../implementation/IngestExpectedPackage.ts | 21 ++++++++++++++++ .../model/implementation/IngestModelImpl.ts | 19 ++++----------- .../implementation/IngestPartModelImpl.ts | 10 ++++---- .../src/ingest/syncChangesToPartInstance.ts | 4 ++-- 8 files changed, 56 insertions(+), 49 deletions(-) create mode 100644 packages/job-worker/src/ingest/model/implementation/IngestExpectedPackage.ts diff --git a/packages/job-worker/src/blueprints/context/watchedPackages.ts b/packages/job-worker/src/blueprints/context/watchedPackages.ts index d84e08dd2e..dc761cbcf9 100644 --- a/packages/job-worker/src/blueprints/context/watchedPackages.ts +++ b/packages/job-worker/src/blueprints/context/watchedPackages.ts @@ -1,16 +1,13 @@ -import { - ExpectedPackageDB, - ExpectedPackageDBBase, - ExpectedPackageFromRundown, -} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { ExpectedPackageDB, ExpectedPackageDBBase } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { PackageInfoDB } from '@sofie-automation/corelib/dist/dataModel/PackageInfos' import { JobContext } from '../../jobs' import { ExpectedPackageId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { Filter as FilterQuery } from 'mongodb' import { PackageInfo } from '@sofie-automation/blueprints-integration' import { unprotectObjectArray } from '@sofie-automation/corelib/dist/protectedString' -import { ExpectedPackageForIngestModel, IngestModelReadonly } from '../../ingest/model/IngestModel' +import { IngestModelReadonly } from '../../ingest/model/IngestModel' import { ReadonlyDeep } from 'type-fest' +import { IngestExpectedPackage } from '../../ingest/model/implementation/IngestExpectedPackage' /** * This is a helper class to simplify exposing packageInfo to various places in the blueprints @@ -65,7 +62,7 @@ export class WatchedPackagesHelper { context: JobContext, ingestModel: IngestModelReadonly ): Promise { - const packages: ReadonlyDeep[] = [] + const packages: ReadonlyDeep[] = [] packages.push(...ingestModel.expectedPackagesForRundownBaseline) @@ -92,7 +89,7 @@ export class WatchedPackagesHelper { ingestModel: IngestModelReadonly, segmentExternalIds: string[] ): Promise { - const packages: ReadonlyDeep[] = [] + const packages: ReadonlyDeep[] = [] for (const externalId of segmentExternalIds) { const segment = ingestModel.getSegmentByExternalId(externalId) @@ -109,7 +106,7 @@ export class WatchedPackagesHelper { ) } - static async #createFromPackages(context: JobContext, packages: ReadonlyDeep[]) { + static async #createFromPackages(context: JobContext, packages: ReadonlyDeep[]) { // Load all the packages and the infos that are watched const watchedPackageInfos = packages.length > 0 diff --git a/packages/job-worker/src/ingest/model/IngestModel.ts b/packages/job-worker/src/ingest/model/IngestModel.ts index 0fd3e52e28..a7fcfadea5 100644 --- a/packages/job-worker/src/ingest/model/IngestModel.ts +++ b/packages/job-worker/src/ingest/model/IngestModel.ts @@ -2,7 +2,6 @@ import { ExpectedMediaItemRundown } from '@sofie-automation/corelib/dist/dataMod import { ExpectedPackageDBFromBaselineAdLibAction, ExpectedPackageDBFromBaselineAdLibPiece, - ExpectedPackageDBFromPiece, ExpectedPackageDBFromRundownBaselineObjects, ExpectedPackageDBNew, ExpectedPackageFromRundown, @@ -33,6 +32,7 @@ import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment' import { ProcessedShowStyleBase, ProcessedShowStyleVariant } from '../../jobs/showStyle' import { WrappedShowStyleBlueprint } from '../../blueprints/cache' import { IBlueprintRundown } from '@sofie-automation/blueprints-integration' +import { IngestExpectedPackage } from './implementation/IngestExpectedPackage' export type ExpectedPackageForIngestModelBaseline = | ExpectedPackageDBFromBaselineAdLibAction @@ -67,7 +67,7 @@ export interface IngestModelReadonly { /** * The ExpectedPackages for the baseline of this Rundown */ - readonly expectedPackagesForRundownBaseline: ReadonlyDeep[] + readonly expectedPackagesForRundownBaseline: ReadonlyDeep[] /** * The baseline Timeline objects of this Rundown @@ -141,9 +141,7 @@ export interface IngestModelReadonly { * Search for an AdLibPiece in all Parts of the Rundown * @param id Id of the AdLib Piece */ - findAdlibPieceAndPackages( - adLibPieceId: PieceId - ): { adlib: ReadonlyDeep; expectedPackages: ReadonlyDeep[] } | undefined + findAdlibPiece(adLibPieceId: PieceId): ReadonlyDeep | undefined /** * Search for an ExpectedPackage through the whole Rundown diff --git a/packages/job-worker/src/ingest/model/IngestPartModel.ts b/packages/job-worker/src/ingest/model/IngestPartModel.ts index a6ab5fd4b1..595c512c49 100644 --- a/packages/job-worker/src/ingest/model/IngestPartModel.ts +++ b/packages/job-worker/src/ingest/model/IngestPartModel.ts @@ -3,9 +3,9 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { AdLibAction } from '@sofie-automation/corelib/dist/dataModel/AdlibAction' import { ExpectedMediaItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedMediaItem' -import { ExpectedPackageFromRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { ExpectedPlayoutItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedPlayoutItem' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' +import { IngestExpectedPackage } from './implementation/IngestExpectedPackage' export interface IngestPartModelReadonly { /** @@ -37,7 +37,7 @@ export interface IngestPartModelReadonly { /** * The ExpectedPackages belonging to this Part */ - readonly expectedPackages: ReadonlyDeep[] + readonly expectedPackages: ReadonlyDeep[] } /** * Wrap a Part and its contents in a view for Ingest operations diff --git a/packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts b/packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts index e16b6bd3b9..c1f4b81bbb 100644 --- a/packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts +++ b/packages/job-worker/src/ingest/model/implementation/ExpectedPackagesStore.ts @@ -11,8 +11,9 @@ import { } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { diffAndReturnLatestObjects, DocumentChanges, getDocumentChanges, setValuesAndTrackChanges } from './utils' +import { IngestExpectedPackage } from './IngestExpectedPackage' -function mutateExpectedPackage(oldObj: ExpectedPackageDBNew, newObj: ExpectedPackageDBNew): ExpectedPackageDBNew { +function mutateExpectedPackage(oldObj: IngestExpectedPackage, newObj: IngestExpectedPackage): IngestExpectedPackage { return { ...newObj, // Retain the created property @@ -23,7 +24,7 @@ function mutateExpectedPackage(oldObj: ExpectedPackageDBNew, newObj: ExpectedPac export class ExpectedPackagesStore { #expectedMediaItems: ExpectedMediaItemRundown[] #expectedPlayoutItems: ExpectedPlayoutItemRundown[] - #expectedPackages: ExpectedPackageDBNew[] + #expectedPackages: IngestExpectedPackage[] #expectedMediaItemsWithChanges = new Set() #expectedPlayoutItemsWithChanges = new Set() @@ -35,7 +36,7 @@ export class ExpectedPackagesStore { get expectedPlayoutItems(): ReadonlyDeep { return this.#expectedPlayoutItems } - get expectedPackages(): ReadonlyDeep { + get expectedPackages(): ReadonlyDeep { return this.#expectedPackages } @@ -53,7 +54,7 @@ export class ExpectedPackagesStore { get expectedPlayoutItemsChanges(): DocumentChanges { return getDocumentChanges(this.#expectedPlayoutItemsWithChanges, this.#expectedPlayoutItems) } - get expectedPackagesChanges(): DocumentChanges { + get expectedPackagesChanges(): DocumentChanges { return getDocumentChanges(this.#expectedPackagesWithChanges, this.#expectedPackages) } @@ -111,12 +112,13 @@ export class ExpectedPackagesStore { rundownId, partId, }) - setValuesAndTrackChanges(this.#expectedPackagesWithChanges, this.#expectedPackages, { - rundownId, - // @ts-expect-error Not all ExpectedPackage types have this property - segmentId, - partId, - }) + // nocommit - reimplement + // setValuesAndTrackChanges(this.#expectedPackagesWithChanges, this.#expectedPackages, { + // rundownId, + // // @ts-expect-error Not all ExpectedPackage types have this property + // segmentId, + // partId, + // }) } compareToPreviousData(oldStore: ExpectedPackagesStore): void { @@ -165,7 +167,7 @@ export class ExpectedPackagesStore { newExpectedMediaItems ) } - setExpectedPackages(expectedPackages: ExpectedPackageDBNew[]): void { + setExpectedPackages(expectedPackages: IngestExpectedPackage[]): void { // nocommit - the whole packages flow needs reimplementing // const newExpectedPackages: ExpectedPackageDBNew[] = expectedPackages.map((pkg) => ({ // ...pkg, diff --git a/packages/job-worker/src/ingest/model/implementation/IngestExpectedPackage.ts b/packages/job-worker/src/ingest/model/implementation/IngestExpectedPackage.ts new file mode 100644 index 0000000000..5281251fd7 --- /dev/null +++ b/packages/job-worker/src/ingest/model/implementation/IngestExpectedPackage.ts @@ -0,0 +1,21 @@ +import { ExpectedPackage } from '@sofie-automation/blueprints-integration' +import { + ExpectedPackageIngestSourcePart, + ExpectedPackageIngestSourceRundownBaseline, +} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { ExpectedPackageId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { Time } from 'superfly-timeline' +import { ReadonlyDeep } from 'type-fest' + +export interface IngestExpectedPackage { + _id: ExpectedPackageId + + /** Hash that changes whenever the content or version changes. See getContentVersionHash() */ + contentVersionHash: string + + created: Time + + package: ReadonlyDeep + + ingestSources: Array +} diff --git a/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts b/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts index 5e4b1a7338..325126570a 100644 --- a/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts +++ b/packages/job-worker/src/ingest/model/implementation/IngestModelImpl.ts @@ -2,7 +2,6 @@ import { AdLibAction } from '@sofie-automation/corelib/dist/dataModel/AdlibActio import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { ExpectedMediaItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedMediaItem' import { - ExpectedPackageDBFromPiece, ExpectedPackageDBNew, ExpectedPackageDBType, ExpectedPackageIngestSource, @@ -48,7 +47,7 @@ import { IngestPartModelImpl } from './IngestPartModelImpl' import { DatabasePersistedModel } from '../../../modelBase' import { ExpectedPackagesStore } from './ExpectedPackagesStore' import { ReadonlyDeep } from 'type-fest' -import { ExpectedPackageForIngestModelBaseline, IngestModel, IngestReplaceSegmentType } from '../IngestModel' +import { IngestModel, IngestReplaceSegmentType } from '../IngestModel' import { RundownNote } from '@sofie-automation/corelib/dist/dataModel/Notes' import { diffAndReturnLatestObjects } from './utils' import _ = require('underscore') @@ -60,6 +59,7 @@ import { SaveIngestModelHelper } from './SaveIngestModel' import { generateWriteOpsForLazyDocuments } from './DocumentChangeTracker' import { IS_PRODUCTION } from '../../../environment' import { logger } from '../../../logging' +import { IngestExpectedPackage } from './IngestExpectedPackage' export interface IngestModelImplExistingData { rundown: DBRundown @@ -145,7 +145,7 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { get expectedPlayoutItemsForRundownBaseline(): ReadonlyDeep[] { return [...this.#rundownBaselineExpectedPackagesStore.expectedPlayoutItems] } - get expectedPackagesForRundownBaseline(): ReadonlyDeep[] { + get expectedPackagesForRundownBaseline(): ReadonlyDeep[] { return [...this.#rundownBaselineExpectedPackagesStore.expectedPackages] } @@ -327,20 +327,11 @@ export class IngestModelImpl implements IngestModel, DatabasePersistedModel { return undefined } - findAdlibPieceAndPackages( - adLibPieceId: PieceId - ): { adlib: ReadonlyDeep; expectedPackages: ReadonlyDeep[] } | undefined { + findAdlibPiece(adLibPieceId: PieceId): ReadonlyDeep | undefined { for (const part of this.getAllOrderedParts()) { for (const adlib of part.adLibPieces) { if (adlib._id === adLibPieceId) { - const expectedPackages = part.expectedPackages.filter( - (p): p is ReadonlyDeep => - p.fromPieceType === ExpectedPackageDBType.ADLIB_PIECE && p.pieceId === adLibPieceId - ) - return { - adlib, - expectedPackages, - } + return adlib } } } diff --git a/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts b/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts index 38c28c06ae..24ceb95800 100644 --- a/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts +++ b/packages/job-worker/src/ingest/model/implementation/IngestPartModelImpl.ts @@ -7,10 +7,7 @@ import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { AdLibAction } from '@sofie-automation/corelib/dist/dataModel/AdlibAction' import { ExpectedMediaItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedMediaItem' import { ExpectedPlayoutItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedPlayoutItem' -import { - ExpectedPackageDBNew, - ExpectedPackageFromRundown, -} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { ExpectedPackageDBNew } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' import { ExpectedPackagesStore } from './ExpectedPackagesStore' import { @@ -20,13 +17,14 @@ import { getDocumentChanges, setValuesAndTrackChanges, } from './utils' +import { IngestExpectedPackage } from './IngestExpectedPackage' export class IngestPartModelImpl implements IngestPartModel { readonly partImpl: DBPart readonly #pieces: Piece[] readonly #adLibPieces: AdLibPiece[] readonly #adLibActions: AdLibAction[] - readonly expectedPackagesStore: ExpectedPackagesStore + readonly expectedPackagesStore: ExpectedPackagesStore #setPartValue(key: T, newValue: DBPart[T]): void { if (newValue === undefined) { @@ -93,7 +91,7 @@ export class IngestPartModelImpl implements IngestPartModel { get expectedPlayoutItems(): ReadonlyDeep[] { return [...this.expectedPackagesStore.expectedPlayoutItems] } - get expectedPackages(): ReadonlyDeep[] { + get expectedPackages(): ReadonlyDeep[] { return [...this.expectedPackagesStore.expectedPackages] } diff --git a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts index 6ec9d6d639..af8e3dc542 100644 --- a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts +++ b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts @@ -151,11 +151,11 @@ export async function syncChangesToPartInstances( const referencedAdlibs: IBlueprintAdLibPieceDB[] = [] for (const adLibPieceId of _.compact(pieceInstancesInPart.map((p) => p.pieceInstance.adLibSourceId))) { - const adLibPiece = ingestModel.findAdlibPieceAndPackages(adLibPieceId) + const adLibPiece = ingestModel.findAdlibPiece(adLibPieceId) if (adLibPiece) referencedAdlibs.push( convertAdLibPieceToBlueprints( - adLibPiece.adlib, + adLibPiece, unwrapExpectedPackages(adLibPiece.expectedPackages) ) ) From 6374d53853700ed46d48b2721bedb489876959f5 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 4 Sep 2024 16:23:58 +0100 Subject: [PATCH 19/22] wip --- .../SyncIngestUpdateToPartInstanceContext.ts | 52 ++++++++++++++----- .../src/ingest/syncChangesToPartInstance.ts | 19 ++++++- .../model/PlayoutExpectedPackagesModel.ts | 27 +++++++++- .../model/implementation/LoadPlayoutModel.ts | 8 ++- .../PlayoutExpectedPackagesModelImpl.ts | 19 +++++-- 5 files changed, 103 insertions(+), 22 deletions(-) diff --git a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts index 2eee58b51d..08bfc1f276 100644 --- a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts +++ b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts @@ -31,7 +31,7 @@ import { serializePieceTimelineObjectsBlob, } from '@sofie-automation/corelib/dist/dataModel/Piece' import { EXPECTED_INGEST_TO_PLAYOUT_TIME } from '@sofie-automation/shared-lib/dist/core/constants' -import { unwrapExpectedPackages } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' +import { PlayoutExpectedPackagesModel } from '../../playout/model/PlayoutExpectedPackagesModel' export class SyncIngestUpdateToPartInstanceContext extends RundownUserContext @@ -49,6 +49,7 @@ export class SyncIngestUpdateToPartInstanceContext rundown: ReadonlyDeep, partInstance: PlayoutPartInstanceModel, proposedPieceInstances: ReadonlyDeep, + private readonly expectedPackages: PlayoutExpectedPackagesModel, private playStatus: 'previous' | 'current' | 'next' ) { super( @@ -77,7 +78,7 @@ export class SyncIngestUpdateToPartInstanceContext if (!this.partInstance) throw new Error(`PartInstance has been removed`) // filter the submission to the allowed ones - const postProcessedPiece = modifiedPiece + const postProcessed = modifiedPiece ? postProcessPieces( this._context, [ @@ -92,22 +93,25 @@ export class SyncIngestUpdateToPartInstanceContext this.partInstance.partInstance.segmentId, this.partInstance.partInstance.part._id, this.playStatus === 'current' - )[0] + ) : null - if (postProcessedPiece) { - this.expectedPackages.ensurePackages(postProcessedPiece.expectedPackages) + if (postProcessed) { + this.expectedPackages.ensurePackagesExistMap( + this.partInstance.partInstance.rundownId, + postProcessed.expectedPackages + ) } const newPieceInstance: ReadonlyDeep = { ...proposedPieceInstance, - piece: postProcessedPiece?.doc ?? proposedPieceInstance.piece, + piece: postProcessed?.docs[0] ?? proposedPieceInstance.piece, } this.partInstance.mergeOrInsertPieceInstance(newPieceInstance) return convertPieceInstanceToBlueprints( newPieceInstance, - this.expectedPackages.getPackagesForPieceInstance(newPieceInstance) + this.expectedPackages.getPackagesForPieceInstance(newPieceInstance.rundownId, newPieceInstance._id) ) } @@ -116,7 +120,7 @@ export class SyncIngestUpdateToPartInstanceContext if (!this.partInstance) throw new Error(`PartInstance has been removed`) - const piece = postProcessPieces( + const processedPieces = postProcessPieces( this._context, [trimmedPiece], this.showStyleCompound.blueprintId, @@ -124,15 +128,22 @@ export class SyncIngestUpdateToPartInstanceContext this.partInstance.partInstance.segmentId, this.partInstance.partInstance.part._id, this.playStatus === 'current' - )[0] + ) + const piece = processedPieces.docs[0] - this.expectedPackages.ensurePackages(piece.expectedPackages) + this.expectedPackages.ensurePackagesExistMap( + this.partInstance.partInstance.rundownId, + processedPieces.expectedPackages + ) - const newPieceInstance = this.partInstance.insertPlannedPiece(piece.doc) + const newPieceInstance = this.partInstance.insertPlannedPiece(piece) return convertPieceInstanceToBlueprints( newPieceInstance.pieceInstance, - unwrapExpectedPackages(newPieceInstance.expectedPackages) + this.expectedPackages.getPackagesForPieceInstance( + newPieceInstance.pieceInstance.rundownId, + newPieceInstance.pieceInstance._id + ) ) } updatePieceInstance(pieceInstanceId: string, updatedPiece: Partial): IBlueprintPieceInstance { @@ -172,12 +183,25 @@ export class SyncIngestUpdateToPartInstanceContext }) } if (trimmedPiece.expectedPackages) { - pieceInstance.setExpectedPackages(trimmedPiece.expectedPackages) + this.expectedPackages.ensurePackagesExist( + pieceInstance.pieceInstance.rundownId, + trimmedPiece.expectedPackages + ) + + this.expectedPackages.setPieceInstanceReferenceToPackages( + pieceInstance.pieceInstance.rundownId, + pieceInstance.pieceInstance.partInstanceId, + pieceInstance.pieceInstance._id, + pieceInstance.pieceInstance.piece.expectedPackages.map((p) => p.expectedPackageId) + ) } return convertPieceInstanceToBlueprints( pieceInstance.pieceInstance, - unwrapExpectedPackages(pieceInstance.expectedPackages) + this.expectedPackages.getPackagesForPieceInstance( + pieceInstance.pieceInstance.rundownId, + pieceInstance.pieceInstance._id + ) ) } updatePartInstance(updatePart: Partial): IBlueprintPartInstance { diff --git a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts index af8e3dc542..2a477edd15 100644 --- a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts +++ b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts @@ -131,7 +131,14 @@ export async function syncChangesToPartInstances( const existingResultPartInstance: BlueprintSyncIngestPartInstance = { partInstance: convertPartInstanceToBlueprints(existingPartInstance.partInstance), pieceInstances: pieceInstancesInPart.map((p) => - convertPieceInstanceToBlueprints(p.pieceInstance, unwrapExpectedPackages(p.expectedPackages)) + convertPieceInstanceToBlueprints( + p.pieceInstance, + // nocommit - make sure the expectedPackages are loaded? + playoutModel.expectedPackages.getPackagesForPieceInstance( + p.pieceInstance.rundownId, + p.pieceInstance._id + ) + ) ), } @@ -164,7 +171,11 @@ export async function syncChangesToPartInstances( const newResultData: BlueprintSyncIngestNewData = { part: newPart ? convertPartToBlueprints(newPart) : undefined, pieceInstances: proposedPieceInstances.map((p) => - convertPieceInstanceToBlueprints(p, p.expectedPackages) + convertPieceInstanceToBlueprints( + p, + // nocommit - make sure the expectedPackages are loaded? + playoutModel.expectedPackages.getPackagesForPieceInstance(p.rundownId, p._id) + ) ), adLibPieces: newPart && ingestPart ? ingestPart.adLibPieces.map(convertAdLibPieceToBlueprints) : [], actions: newPart && ingestPart ? ingestPart.adLibActions.map(convertAdLibActionToBlueprints) : [], @@ -173,6 +184,8 @@ export async function syncChangesToPartInstances( const partInstanceSnapshot = existingPartInstance.snapshotMakeCopy() + const expectedPackagesSnapshot = playoutModel.expectedPackages.snapshotMakeCopy() + const syncContext = new SyncIngestUpdateToPartInstanceContext( context, { @@ -184,6 +197,7 @@ export async function syncChangesToPartInstances( playoutRundownModel.rundown, existingPartInstance, proposedPieceInstances, + playoutModel.expectedPackages, playStatus ) // TODO - how can we limit the frequency we run this? (ie, how do we know nothing affecting this has changed) @@ -200,6 +214,7 @@ export async function syncChangesToPartInstances( // Operation failed, rollback the changes existingPartInstance.snapshotRestore(partInstanceSnapshot) + playoutModel.expectedPackages.snapshotRestore(expectedPackagesSnapshot) } if (playStatus === 'next') { diff --git a/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts b/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts index 957b3b9edd..e106456d27 100644 --- a/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts @@ -7,11 +7,36 @@ import { } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' +/** + * Token returned when making a backup copy of a PlayoutExpectedPackagesModel + * The contents of this type is opaque and will vary fully across implementations + */ +export interface PlayoutExpectedPackagesModelSnapshot { + __isPlayoutExpectedPackagesModelBackup: true +} + export interface PlayoutExpectedPackagesModelReadonly { - TODO: null + getPackagesForPieceInstance( + rundownId: RundownId, + pieceInstanceId: PieceInstanceId + ): ReadonlyDeep[] } export interface PlayoutExpectedPackagesModel extends PlayoutExpectedPackagesModelReadonly { + /** + * Take a snapshot of the current state of this PlayoutExpectedPackagesModel + * This can be restored with `snapshotRestore` to rollback to a previous state of the model + */ + snapshotMakeCopy(): PlayoutExpectedPackagesModelSnapshot + + /** + * Restore a snapshot of this PlayoutExpectedPackagesModel, to rollback to a previous state + * Note: It is only possible to restore each snapshot once. + * Note: Any references to child documents may no longer be valid after this operation + * @param snapshot Snapshot to restore + */ + snapshotRestore(snapshot: PlayoutExpectedPackagesModelSnapshot): void + ensurePackagesExist(rundownId: RundownId, expectedPackages: ReadonlyDeep): void ensurePackagesExistMap( diff --git a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts index 683ed0d8b9..8577a286b0 100644 --- a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts +++ b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts @@ -88,8 +88,11 @@ export async function createPlayoutModelFromIngestModel( const [peripheralDevices, playlist, rundowns] = await loadInitData(context, loadedPlaylist, false, newRundowns) const rundownIds = rundowns.map((r) => r._id) + const expectedPackages = new PlayoutExpectedPackagesModelImpl() + // nocommit - populate some content from the ingestModel + const [partInstances, rundownsWithContent, timeline] = await Promise.all([ - loadPartInstances(context, loadedPlaylist, rundownIds), + loadPartInstances(context, expectedPackages, loadedPlaylist, rundownIds), loadRundowns(context, ingestModel, rundowns), loadTimeline(context), ]) @@ -102,7 +105,8 @@ export async function createPlayoutModelFromIngestModel( playlist, partInstances, rundownsWithContent, - timeline + timeline, + expectedPackages ) return res diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts index 0902296ac6..72c63325b7 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts @@ -6,9 +6,24 @@ import { RundownId, } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' -import { PlayoutExpectedPackagesModel } from '../PlayoutExpectedPackagesModel' +import { PlayoutExpectedPackagesModel, PlayoutExpectedPackagesModelSnapshot } from '../PlayoutExpectedPackagesModel' export class PlayoutExpectedPackagesModelImpl implements PlayoutExpectedPackagesModel { + getPackagesForPieceInstance( + _rundownId: RundownId, + _pieceInstanceId: PieceInstanceId + ): ReadonlyDeep[] { + throw new Error('Method not implemented.') + } + + snapshotMakeCopy(): PlayoutExpectedPackagesModelSnapshot { + throw new Error('Method not implemented.') + } + + snapshotRestore(_snapshot: PlayoutExpectedPackagesModelSnapshot): void { + throw new Error('Method not implemented.') + } + ensurePackagesExist(_rundownId: RundownId, _expectedPackages: ReadonlyDeep): void { throw new Error('Method not implemented.') } @@ -29,8 +44,6 @@ export class PlayoutExpectedPackagesModelImpl implements PlayoutExpectedPackages throw new Error('Method not implemented.') } - readonly TODO: null = null - async saveAllToDatabase(): Promise { throw new Error('Method not implemented.') } From f9269ce5af1ddefa586a4722c7f890d661b7b2d9 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 4 Sep 2024 16:36:05 +0100 Subject: [PATCH 20/22] wip --- .../corelib/src/dataModel/PieceInstance.ts | 9 +--- .../PartAndPieceInstanceActionService.ts | 53 ++++++++++++++----- packages/job-worker/src/playout/adlibJobs.ts | 10 +++- packages/job-worker/src/playout/adlibUtils.ts | 42 +++------------ .../model/PlayoutExpectedPackagesModel.ts | 3 ++ .../PlayoutExpectedPackagesModelImpl.ts | 5 ++ 6 files changed, 62 insertions(+), 60 deletions(-) diff --git a/packages/corelib/src/dataModel/PieceInstance.ts b/packages/corelib/src/dataModel/PieceInstance.ts index 3250e286eb..fc8f3f9db8 100644 --- a/packages/corelib/src/dataModel/PieceInstance.ts +++ b/packages/corelib/src/dataModel/PieceInstance.ts @@ -1,4 +1,4 @@ -import { ExpectedPackage, Time } from '@sofie-automation/blueprints-integration' +import { Time } from '@sofie-automation/blueprints-integration' import { protectString } from '../protectedString' import { PieceInstanceInfiniteId, @@ -11,7 +11,6 @@ import { import { Piece } from './Piece' import { omit } from '../lib' import { ReadonlyDeep } from 'type-fest' -import { ExpectedPackageDBFromPieceInstance } from './ExpectedPackages' export type PieceInstancePiece = Omit @@ -94,12 +93,6 @@ export interface ResolvedPieceInstance { timelinePriority: number } -// nocommit - remove me -export interface PieceInstanceWithExpectedPackagesFull { - pieceInstance: PieceInstance - expectedPackages: ExpectedPackageDBFromPieceInstance[] -} - export function omitPiecePropertiesForInstance(piece: Piece | PieceInstancePiece): PieceInstancePiece { return omit(piece as Piece, 'startRundownId', 'startSegmentId') } diff --git a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts index 3caa365655..b570e49edd 100644 --- a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts +++ b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts @@ -55,7 +55,6 @@ import { validateAdlibTestingPartInstanceProperties } from '../../../playout/adl import { isTooCloseToAutonext } from '../../../playout/lib' import { DBPart, isPartPlayable } from '@sofie-automation/corelib/dist/dataModel/Part' import { PlayoutRundownModel } from '../../../playout/model/PlayoutRundownModel' -import { unwrapExpectedPackages } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' export enum ActionPartChange { NONE = 0, @@ -112,7 +111,13 @@ export class PartAndPieceInstanceActionService { const partInstance = this._getPartInstance(part) return ( partInstance?.pieceInstances?.map((p) => - convertPieceInstanceToBlueprints(p.pieceInstance, unwrapExpectedPackages(p.expectedPackages)) + convertPieceInstanceToBlueprints( + p.pieceInstance, + this._playoutModel.expectedPackages.getPackagesForPieceInstance( + p.pieceInstance.rundownId, + p.pieceInstance._id + ) + ) ) ?? [] ) } @@ -163,11 +168,16 @@ export class PartAndPieceInstanceActionService { query ) - return ( - lastPieceInstance && - convertPieceInstanceToBlueprints( - lastPieceInstance.pieceInstance, - unwrapExpectedPackages(lastPieceInstance.expectedPackages) + if (!lastPieceInstance) return undefined + + // Ensure that any referenced packages are loaded into memory + await this._playoutModel.expectedPackages.ensurePackagesAreLoaded(lastPieceInstance.piece.expectedPackages) + + return convertPieceInstanceToBlueprints( + lastPieceInstance, + this._playoutModel.expectedPackages.getPackagesForPieceInstance( + lastPieceInstance.rundownId, + lastPieceInstance._id ) ) } @@ -196,17 +206,18 @@ export class PartAndPieceInstanceActionService { const sourceLayerId = Array.isArray(sourceLayerId0) ? sourceLayerId0 : [sourceLayerId0] - const lastPieceAndPackages = await innerFindLastScriptedPieceOnLayer( + const lastPiece = await innerFindLastScriptedPieceOnLayer( this._context, this._playoutModel, sourceLayerId, query ) - return ( - lastPieceAndPackages && - convertPieceToBlueprints(lastPieceAndPackages.doc, lastPieceAndPackages.expectedPackages) - ) + if (!lastPiece) return undefined + + const packages = await this._playoutModel.expectedPackages.ensurePackagesAreLoaded(lastPiece.expectedPackages) + + return convertPieceToBlueprints(lastPiece, packages) } async getPartInstanceForPreviousPiece(piece: IBlueprintPieceInstance): Promise { @@ -292,7 +303,10 @@ export class PartAndPieceInstanceActionService { return convertPieceInstanceToBlueprints( newPieceInstance.pieceInstance, - unwrapExpectedPackages(newPieceInstance.expectedPackages) + this._playoutModel.expectedPackages.getPackagesForPieceInstance( + newPieceInstance.pieceInstance.rundownId, + newPieceInstance.pieceInstance._id + ) ) } @@ -342,6 +356,14 @@ export class PartAndPieceInstanceActionService { trimmedPiece.content = omit(trimmedPiece.content, 'timelineObjects') as WithTimeline } + if (trimmedPiece.expectedPackages) { + // nocommit - this needs to go through some postProcess + this._playoutModel.expectedPackages.ensurePackagesExist( + pieceInstance.pieceInstance.rundownId, + trimmedPiece.expectedPackages + ) + } + pieceInstance.updatePieceProps(trimmedPiece as any) // TODO: this needs to be more type safe if (timelineObjectsString !== undefined) pieceInstance.updatePieceProps({ timelineObjectsString }) @@ -352,7 +374,10 @@ export class PartAndPieceInstanceActionService { return convertPieceInstanceToBlueprints( pieceInstance.pieceInstance, - unwrapExpectedPackages(pieceInstance.expectedPackages) + this._playoutModel.expectedPackages.getPackagesForPieceInstance( + pieceInstance.pieceInstance.rundownId, + pieceInstance.pieceInstance._id + ) ) } diff --git a/packages/job-worker/src/playout/adlibJobs.ts b/packages/job-worker/src/playout/adlibJobs.ts index 39d78c581e..3cc80107d8 100644 --- a/packages/job-worker/src/playout/adlibJobs.ts +++ b/packages/job-worker/src/playout/adlibJobs.ts @@ -364,7 +364,10 @@ export async function handleStartStickyPieceOnSourceLayer( throw UserError.create(UserErrorMessage.SourceLayerStickyNothingFound) } - const lastPiece = convertPieceToAdLibPiece(context, lastPieceInstance.pieceInstance.piece) + // Ensure that any referenced packages are loaded into memory + await playoutModel.expectedPackages.ensurePackagesAreLoaded(lastPieceInstance.piece.expectedPackages) + + const lastPiece = convertPieceToAdLibPiece(context, lastPieceInstance.piece) await innerStartOrQueueAdLibPiece( context, playoutModel, @@ -372,7 +375,10 @@ export async function handleStartStickyPieceOnSourceLayer( false, currentPartInstance, lastPiece, - unwrapExpectedPackages(lastPieceInstance.expectedPackages) + playoutModel.expectedPackages.getPackagesForPieceInstance( + lastPieceInstance.rundownId, + lastPieceInstance._id + ) ) } ) diff --git a/packages/job-worker/src/playout/adlibUtils.ts b/packages/job-worker/src/playout/adlibUtils.ts index c4ccc4406a..9d0daa412f 100644 --- a/packages/job-worker/src/playout/adlibUtils.ts +++ b/packages/job-worker/src/playout/adlibUtils.ts @@ -2,11 +2,7 @@ import { AdLibPiece } from '@sofie-automation/corelib/dist/dataModel/AdLibPiece' import { BucketAdLib } from '@sofie-automation/corelib/dist/dataModel/BucketAdLibPiece' import { PartInstanceId, PieceId, PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { Piece } from '@sofie-automation/corelib/dist/dataModel/Piece' -import { - PieceInstance, - PieceInstancePiece, - PieceInstanceWithExpectedPackagesFull, -} from '@sofie-automation/corelib/dist/dataModel/PieceInstance' +import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' import { assertNever, getRandomId, getRank } from '@sofie-automation/corelib/dist/lib' import { MongoQuery } from '@sofie-automation/corelib/dist/mongo' import { getCurrentTime } from '../lib' @@ -31,12 +27,6 @@ import { ReadonlyDeep } from 'type-fest' import { PlayoutRundownModel } from './model/PlayoutRundownModel' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { protectString } from '@sofie-automation/corelib/dist/protectedString' -import { - ExpectedPackageDBFromPieceInstance, - ExpectedPackageDBType, - unwrapExpectedPackages, -} from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' -import { PostProcessDoc } from '../blueprints/postProcess' export async function innerStartOrQueueAdLibPiece( context: JobContext, @@ -100,7 +90,7 @@ export async function innerFindLastPieceOnLayer( sourceLayerId: string[], originalOnly: boolean, customQuery?: MongoQuery -): Promise { +): Promise { const span = context.startSpan('innerFindLastPieceOnLayer') const rundownIds = playoutModel.getRundownIds() @@ -125,22 +115,11 @@ export async function innerFindLastPieceOnLayer( // Note: This does not want to use the in-memory model, as we want to search as far back as we can // TODO - will this cause problems? - const pieceInstance = await context.directCollections.PieceInstances.findOne(query, { + return context.directCollections.PieceInstances.findOne(query, { sort: { plannedStartedPlayback: -1, }, }) - - if (!pieceInstance) return undefined - - const expectedPackages = (await context.directCollections.ExpectedPackages.findFetch({ - fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE, - pieceInstanceId: pieceInstance._id, - partInstanceId: pieceInstance.partInstanceId, - rundownId: { $in: rundownIds }, - })) as ExpectedPackageDBFromPieceInstance[] - - return { pieceInstance, expectedPackages } } export async function innerFindLastScriptedPieceOnLayer( @@ -148,7 +127,7 @@ export async function innerFindLastScriptedPieceOnLayer( playoutModel: PlayoutModel, sourceLayerId: string[], customQuery?: MongoQuery -): Promise | undefined> { +): Promise { const span = context.startSpan('innerFindLastScriptedPieceOnLayer') const playlist = playoutModel.playlist @@ -205,20 +184,11 @@ export async function innerFindLastScriptedPieceOnLayer( return } - const [fullPiece, expectedPackages] = await Promise.all([ - context.directCollections.Pieces.findOne(piece._id), - context.directCollections.ExpectedPackages.findFetch({ - fromPieceType: ExpectedPackageDBType.PIECE, - pieceId: piece._id, - rundownId: { $in: rundownIds }, - }), - ]) + const fullPiece = await context.directCollections.Pieces.findOne(piece._id) if (span) span.end() - if (!fullPiece) return - - return { doc: fullPiece, expectedPackages: unwrapExpectedPackages(expectedPackages) } + return fullPiece } function updateRankForAdlibbedPartInstance( diff --git a/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts b/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts index e106456d27..cda5d0b7e2 100644 --- a/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts @@ -5,6 +5,7 @@ import { PieceInstanceId, RundownId, } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { PieceExpectedPackage } from '@sofie-automation/corelib/dist/dataModel/Piece' import { ReadonlyDeep } from 'type-fest' /** @@ -37,6 +38,8 @@ export interface PlayoutExpectedPackagesModel extends PlayoutExpectedPackagesMod */ snapshotRestore(snapshot: PlayoutExpectedPackagesModelSnapshot): void + ensurePackagesAreLoaded(expectedPackages: PieceExpectedPackage[]): Promise + ensurePackagesExist(rundownId: RundownId, expectedPackages: ReadonlyDeep): void ensurePackagesExistMap( diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts index 72c63325b7..d9cdf11e7e 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts @@ -7,6 +7,7 @@ import { } from '@sofie-automation/corelib/dist/dataModel/Ids' import { ReadonlyDeep } from 'type-fest' import { PlayoutExpectedPackagesModel, PlayoutExpectedPackagesModelSnapshot } from '../PlayoutExpectedPackagesModel' +import { PieceExpectedPackage } from '@sofie-automation/corelib/dist/dataModel/Piece' export class PlayoutExpectedPackagesModelImpl implements PlayoutExpectedPackagesModel { getPackagesForPieceInstance( @@ -24,6 +25,10 @@ export class PlayoutExpectedPackagesModelImpl implements PlayoutExpectedPackages throw new Error('Method not implemented.') } + async ensurePackagesAreLoaded(_expectedPackages: PieceExpectedPackage[]): Promise { + throw new Error('Method not implemented.') + } + ensurePackagesExist(_rundownId: RundownId, _expectedPackages: ReadonlyDeep): void { throw new Error('Method not implemented.') } From cd59f9ae17b803af6e9e75d323ba81ec4c2be008 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 4 Sep 2024 16:38:00 +0100 Subject: [PATCH 21/22] wip --- .../corelib/src/dataModel/ExpectedPackages.ts | 16 ---------------- .../job-worker/src/ingest/model/IngestModel.ts | 10 ---------- packages/job-worker/src/playout/snapshot.ts | 3 --- 3 files changed, 29 deletions(-) diff --git a/packages/corelib/src/dataModel/ExpectedPackages.ts b/packages/corelib/src/dataModel/ExpectedPackages.ts index cfcb3ac366..2bd8cd28e9 100644 --- a/packages/corelib/src/dataModel/ExpectedPackages.ts +++ b/packages/corelib/src/dataModel/ExpectedPackages.ts @@ -41,7 +41,6 @@ export type ExpectedPackageDB = | ExpectedPackageDBFromBucket | ExpectedPackageFromRundownBaseline | ExpectedPackageDBFromStudioBaselineObjects - | ExpectedPackageDBFromPieceInstance export enum ExpectedPackageDBType { PIECE = 'piece', @@ -227,21 +226,6 @@ export interface ExpectedPackageDBFromBucketAdLibAction extends ExpectedPackageD /** The `externalId` of the Bucket adlib-action this package belongs to */ pieceExternalId: string } -export interface ExpectedPackageDBFromPieceInstance extends ExpectedPackageDBBase { - fromPieceType: ExpectedPackageDBType.PIECE_INSTANCE - - /** The PieceInstance this package belongs to */ - pieceInstanceId: PieceInstanceId - /** The PartInstance this package belongs to */ - partInstanceId: PartInstanceId - /** The Segment this package belongs to */ - segmentId: SegmentId - /** The rundown of the Piece this package belongs to */ - rundownId: RundownId - - // For type compatibility: - pieceId: null -} export function getContentVersionHash(expectedPackage: ReadonlyDeep>): string { return hashObj({ diff --git a/packages/job-worker/src/ingest/model/IngestModel.ts b/packages/job-worker/src/ingest/model/IngestModel.ts index a7fcfadea5..247cc764ab 100644 --- a/packages/job-worker/src/ingest/model/IngestModel.ts +++ b/packages/job-worker/src/ingest/model/IngestModel.ts @@ -1,10 +1,6 @@ import { ExpectedMediaItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedMediaItem' import { - ExpectedPackageDBFromBaselineAdLibAction, - ExpectedPackageDBFromBaselineAdLibPiece, - ExpectedPackageDBFromRundownBaselineObjects, ExpectedPackageDBNew, - ExpectedPackageFromRundown, ExpectedPackageIngestSource, } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' import { ExpectedPlayoutItemRundown } from '@sofie-automation/corelib/dist/dataModel/ExpectedPlayoutItem' @@ -34,12 +30,6 @@ import { WrappedShowStyleBlueprint } from '../../blueprints/cache' import { IBlueprintRundown } from '@sofie-automation/blueprints-integration' import { IngestExpectedPackage } from './implementation/IngestExpectedPackage' -export type ExpectedPackageForIngestModelBaseline = - | ExpectedPackageDBFromBaselineAdLibAction - | ExpectedPackageDBFromBaselineAdLibPiece - | ExpectedPackageDBFromRundownBaselineObjects -export type ExpectedPackageForIngestModel = ExpectedPackageFromRundown | ExpectedPackageForIngestModelBaseline - export interface IngestModelReadonly { /** * The Id of the Rundown this IngestModel operates for diff --git a/packages/job-worker/src/playout/snapshot.ts b/packages/job-worker/src/playout/snapshot.ts index c4f50343bf..b0ca9edcd2 100644 --- a/packages/job-worker/src/playout/snapshot.ts +++ b/packages/job-worker/src/playout/snapshot.ts @@ -342,9 +342,6 @@ export async function handleRestorePlaylistSnapshot( logger.warn(`Unexpected ExpectedPackage in snapshot: ${JSON.stringify(expectedPackage)}`) break } - case ExpectedPackageDBType.PIECE_INSTANCE: - // nocommit implement me! - break default: assertNever(expectedPackage) From bf42a57f35de59f0ff2c75afff63849a1cc51438 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 5 Sep 2024 12:30:21 +0100 Subject: [PATCH 22/22] wip --- .../corelib/src/dataModel/ExpectedPackages.ts | 137 ++++-------------- .../SyncIngestUpdateToPartInstanceContext.ts | 6 +- .../PartAndPieceInstanceActionService.ts | 9 +- packages/job-worker/src/playout/adlibJobs.ts | 2 +- packages/job-worker/src/playout/adlibUtils.ts | 10 +- .../model/PlayoutExpectedPackagesModel.ts | 4 +- .../model/implementation/LoadPlayoutModel.ts | 16 +- .../PlayoutExpectedPackagesModelImpl.ts | 6 +- 8 files changed, 64 insertions(+), 126 deletions(-) diff --git a/packages/corelib/src/dataModel/ExpectedPackages.ts b/packages/corelib/src/dataModel/ExpectedPackages.ts index 2bd8cd28e9..7ea9589124 100644 --- a/packages/corelib/src/dataModel/ExpectedPackages.ts +++ b/packages/corelib/src/dataModel/ExpectedPackages.ts @@ -5,10 +5,8 @@ import { AdLibActionId, BucketAdLibActionId, BucketAdLibId, - BucketId, ExpectedPackageId, PartId, - PartInstanceId, PieceId, PieceInstanceId, RundownBaselineAdLibActionId, @@ -27,20 +25,13 @@ import { ReadonlyDeep } from 'type-fest' The Package Manager will then copy the file to the right place. */ -export type ExpectedPackageFromRundown = ExpectedPackageDBFromPiece | ExpectedPackageDBFromAdLibAction - -export type ExpectedPackageFromRundownBaseline = - | ExpectedPackageDBFromBaselineAdLibAction - | ExpectedPackageDBFromBaselineAdLibPiece - | ExpectedPackageDBFromRundownBaselineObjects - export type ExpectedPackageDBFromBucket = ExpectedPackageDBFromBucketAdLib | ExpectedPackageDBFromBucketAdLibAction -export type ExpectedPackageDB = - | ExpectedPackageFromRundown - | ExpectedPackageDBFromBucket - | ExpectedPackageFromRundownBaseline - | ExpectedPackageDBFromStudioBaselineObjects +// export type ExpectedPackageDB = +// | ExpectedPackageFromRundown +// | ExpectedPackageDBFromBucket +// | ExpectedPackageFromRundownBaseline +// | ExpectedPackageDBFromStudioBaselineObjects export enum ExpectedPackageDBType { PIECE = 'piece', @@ -54,22 +45,6 @@ export enum ExpectedPackageDBType { STUDIO_BASELINE_OBJECTS = 'studio_baseline_objects', PIECE_INSTANCE = 'piece_instance', } -export interface ExpectedPackageDBBaseSimple { - _id: ExpectedPackageId - - package: ReadonlyDeep - - /** The local package id - as given by the blueprints */ - blueprintPackageId: string // TODO - remove this? - - /** The studio of the Rundown of the Piece this package belongs to */ - studioId: StudioId - - /** Hash that changes whenever the content or version changes. See getContentVersionHash() */ - contentVersionHash: string - - created: Time -} /* * What about this new concept. The aim here is to avoid the constant inserting and deleting of expectedPackages during playout, and avoiding duplicate packages with the same content. @@ -152,80 +127,27 @@ export type ExpectedPackageIngestSourceRundownBaseline = export type ExpectedPackageIngestSource = ExpectedPackageIngestSourcePart | ExpectedPackageIngestSourceRundownBaseline -export interface ExpectedPackageWithId { - _id: ExpectedPackageId - expectedPackage: ReadonlyDeep -} - -export interface ExpectedPackageDBBase extends ExpectedPackageDBBaseSimple { - fromPieceType: ExpectedPackageDBType -} -export interface ExpectedPackageDBFromPiece extends ExpectedPackageDBBase { - fromPieceType: ExpectedPackageDBType.PIECE | ExpectedPackageDBType.ADLIB_PIECE - /** The Piece this package belongs to */ - pieceId: PieceId - /** The Part this package belongs to */ - partId: PartId - /** The Segment this package belongs to */ - segmentId: SegmentId - /** The rundown of the Piece this package belongs to */ - rundownId: RundownId -} - -export interface ExpectedPackageDBFromBaselineAdLibPiece extends ExpectedPackageDBBase { - fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_PIECE - /** The Piece this package belongs to */ - pieceId: PieceId - /** The rundown of the Piece this package belongs to */ - rundownId: RundownId -} - -export interface ExpectedPackageDBFromAdLibAction extends ExpectedPackageDBBase { - fromPieceType: ExpectedPackageDBType.ADLIB_ACTION - /** The Adlib Action this package belongs to */ - pieceId: AdLibActionId - /** The Part this package belongs to */ - partId: PartId - /** The Segment this package belongs to */ - segmentId: SegmentId - /** The rundown of the Piece this package belongs to */ - rundownId: RundownId -} -export interface ExpectedPackageDBFromBaselineAdLibAction extends ExpectedPackageDBBase { - fromPieceType: ExpectedPackageDBType.BASELINE_ADLIB_ACTION - /** The Piece this package belongs to */ - pieceId: RundownBaselineAdLibActionId - /** The rundown of the Piece this package belongs to */ - rundownId: RundownId -} - -export interface ExpectedPackageDBFromRundownBaselineObjects extends ExpectedPackageDBBase { - fromPieceType: ExpectedPackageDBType.RUNDOWN_BASELINE_OBJECTS - /** The rundown of the Piece this package belongs to */ - rundownId: RundownId - pieceId: null -} -export interface ExpectedPackageDBFromStudioBaselineObjects extends ExpectedPackageDBBase { - fromPieceType: ExpectedPackageDBType.STUDIO_BASELINE_OBJECTS - pieceId: null -} - -export interface ExpectedPackageDBFromBucketAdLib extends ExpectedPackageDBBase { - fromPieceType: ExpectedPackageDBType.BUCKET_ADLIB - bucketId: BucketId - /** The Bucket adlib this package belongs to */ - pieceId: BucketAdLibId - /** The `externalId` of the Bucket adlib this package belongs to */ - pieceExternalId: string -} -export interface ExpectedPackageDBFromBucketAdLibAction extends ExpectedPackageDBBase { - fromPieceType: ExpectedPackageDBType.BUCKET_ADLIB_ACTION - bucketId: BucketId - /** The Bucket adlib-action this package belongs to */ - pieceId: BucketAdLibActionId - /** The `externalId` of the Bucket adlib-action this package belongs to */ - pieceExternalId: string -} +// export interface ExpectedPackageDBFromStudioBaselineObjects extends ExpectedPackageDBBase { +// fromPieceType: ExpectedPackageDBType.STUDIO_BASELINE_OBJECTS +// pieceId: null +// } + +// export interface ExpectedPackageDBFromBucketAdLib extends ExpectedPackageDBBase { +// fromPieceType: ExpectedPackageDBType.BUCKET_ADLIB +// bucketId: BucketId +// /** The Bucket adlib this package belongs to */ +// pieceId: BucketAdLibId +// /** The `externalId` of the Bucket adlib this package belongs to */ +// pieceExternalId: string +// } +// export interface ExpectedPackageDBFromBucketAdLibAction extends ExpectedPackageDBBase { +// fromPieceType: ExpectedPackageDBType.BUCKET_ADLIB_ACTION +// bucketId: BucketId +// /** The Bucket adlib-action this package belongs to */ +// pieceId: BucketAdLibActionId +// /** The `externalId` of the Bucket adlib-action this package belongs to */ +// pieceExternalId: string +// } export function getContentVersionHash(expectedPackage: ReadonlyDeep>): string { return hashObj({ @@ -266,10 +188,3 @@ export function getExpectedPackageIdNew( return protectString(`${rundownId}_${getHash(objHash)}`) } - -export function unwrapExpectedPackages( - expectedPackages: ReadonlyDeep | undefined -): ReadonlyDeep { - if (!expectedPackages) return [] - return expectedPackages.map((p) => p.package) -} diff --git a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts index 08bfc1f276..accbbb8f25 100644 --- a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts +++ b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts @@ -97,7 +97,7 @@ export class SyncIngestUpdateToPartInstanceContext : null if (postProcessed) { - this.expectedPackages.ensurePackagesExistMap( + this.expectedPackages.createPackagesIfMissingFromMap( this.partInstance.partInstance.rundownId, postProcessed.expectedPackages ) @@ -131,7 +131,7 @@ export class SyncIngestUpdateToPartInstanceContext ) const piece = processedPieces.docs[0] - this.expectedPackages.ensurePackagesExistMap( + this.expectedPackages.createPackagesIfMissingFromMap( this.partInstance.partInstance.rundownId, processedPieces.expectedPackages ) @@ -183,7 +183,7 @@ export class SyncIngestUpdateToPartInstanceContext }) } if (trimmedPiece.expectedPackages) { - this.expectedPackages.ensurePackagesExist( + this.expectedPackages.createPackagesIfMissing( pieceInstance.pieceInstance.rundownId, trimmedPiece.expectedPackages ) diff --git a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts index b570e49edd..86a39fd0d9 100644 --- a/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts +++ b/packages/job-worker/src/blueprints/context/services/PartAndPieceInstanceActionService.ts @@ -287,7 +287,7 @@ export class PartAndPieceInstanceActionService { const piece = postProcessed.docs[0] piece._id = getRandomId() // Make id random, as postProcessPieces is too predictable (for ingest) - this._playoutModel.expectedPackages.ensurePackagesExistMap( + this._playoutModel.expectedPackages.createPackagesIfMissingFromMap( this._rundown.rundown._id, postProcessed.expectedPackages ) @@ -358,7 +358,7 @@ export class PartAndPieceInstanceActionService { if (trimmedPiece.expectedPackages) { // nocommit - this needs to go through some postProcess - this._playoutModel.expectedPackages.ensurePackagesExist( + this._playoutModel.expectedPackages.createPackagesIfMissing( pieceInstance.pieceInstance.rundownId, trimmedPiece.expectedPackages ) @@ -452,7 +452,10 @@ export class PartAndPieceInstanceActionService { throw new Error('Cannot queue a part which is not playable') } - this._playoutModel.expectedPackages.ensurePackagesExistMap(this._rundown.rundown._id, pieces.expectedPackages) + this._playoutModel.expectedPackages.createPackagesIfMissingFromMap( + this._rundown.rundown._id, + pieces.expectedPackages + ) // Do the work const newPartInstance = await insertQueuedPartWithPieces( diff --git a/packages/job-worker/src/playout/adlibJobs.ts b/packages/job-worker/src/playout/adlibJobs.ts index 3cc80107d8..f4a040ec8e 100644 --- a/packages/job-worker/src/playout/adlibJobs.ts +++ b/packages/job-worker/src/playout/adlibJobs.ts @@ -160,7 +160,7 @@ async function pieceTakeNowAsAdlib( | { partInstance: PlayoutPartInstanceModel; pieceInstance: PlayoutPieceInstanceModel } | undefined ): Promise { - playoutModel.expectedPackages.ensurePackagesExist(currentPartInstance.partInstance.rundownId, expectedPackages) // nocommit - what if the genericAdlibPiece doesn't quite match the set of packages? + playoutModel.expectedPackages.createPackagesIfMissing(currentPartInstance.partInstance.rundownId, expectedPackages) // nocommit - what if the genericAdlibPiece doesn't quite match the set of packages? const genericAdlibPiece = convertAdLibToGenericPiece(pieceToCopy, false) /*const newPieceInstance = */ currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, pieceToCopy._id) diff --git a/packages/job-worker/src/playout/adlibUtils.ts b/packages/job-worker/src/playout/adlibUtils.ts index 9d0daa412f..14cf9fad72 100644 --- a/packages/job-worker/src/playout/adlibUtils.ts +++ b/packages/job-worker/src/playout/adlibUtils.ts @@ -49,7 +49,10 @@ export async function innerStartOrQueueAdLibPiece( expectedDurationWithTransition: adLibPiece.expectedDuration, // Filled in later } - playoutModel.expectedPackages.ensurePackagesExist(currentPartInstance.partInstance.rundownId, expectedPackages) // nocommit - what if the genericAdlibPiece doesn't quite match the set of packages? + playoutModel.expectedPackages.createPackagesIfMissing( + currentPartInstance.partInstance.rundownId, + expectedPackages + ) // nocommit - what if the genericAdlibPiece doesn't quite match the set of packages? const genericAdlibPiece = convertAdLibToGenericPiece(adLibPiece, true) const newPartInstance = await insertQueuedPartWithPieces( @@ -65,7 +68,10 @@ export async function innerStartOrQueueAdLibPiece( // syncPlayheadInfinitesForNextPartInstance is handled by setNextPart } else { - playoutModel.expectedPackages.ensurePackagesExist(currentPartInstance.partInstance.rundownId, expectedPackages) // nocommit - what if the adLibPiece doesn't quite match the set of packages? + playoutModel.expectedPackages.createPackagesIfMissing( + currentPartInstance.partInstance.rundownId, + expectedPackages + ) // nocommit - what if the adLibPiece doesn't quite match the set of packages? const genericAdlibPiece = convertAdLibToGenericPiece(adLibPiece, false) currentPartInstance.insertAdlibbedPiece(genericAdlibPiece, adLibPiece._id) diff --git a/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts b/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts index cda5d0b7e2..73f7b8d2a1 100644 --- a/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutExpectedPackagesModel.ts @@ -40,9 +40,9 @@ export interface PlayoutExpectedPackagesModel extends PlayoutExpectedPackagesMod ensurePackagesAreLoaded(expectedPackages: PieceExpectedPackage[]): Promise - ensurePackagesExist(rundownId: RundownId, expectedPackages: ReadonlyDeep): void + createPackagesIfMissing(rundownId: RundownId, expectedPackages: ReadonlyDeep): void - ensurePackagesExistMap( + createPackagesIfMissingFromMap( rundownId: RundownId, expectedPackages: ReadonlyMap> ): void diff --git a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts index 8577a286b0..97724462e9 100644 --- a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts +++ b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts @@ -23,6 +23,7 @@ import { PlayoutModel, PlayoutModelPreInit } from '../PlayoutModel' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { RundownBaselineObj } from '@sofie-automation/corelib/dist/dataModel/RundownBaselineObj' import { PlayoutExpectedPackagesModelImpl } from './PlayoutExpectedPackagesModelImpl' +import { ExpectedPackageDBNew } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages' /** * Load a PlayoutModelPreInit for the given RundownPlaylist @@ -91,12 +92,15 @@ export async function createPlayoutModelFromIngestModel( const expectedPackages = new PlayoutExpectedPackagesModelImpl() // nocommit - populate some content from the ingestModel - const [partInstances, rundownsWithContent, timeline] = await Promise.all([ + const [partInstances, rundownsWithContent, timeline, expectedPackagesDocs] = await Promise.all([ loadPartInstances(context, expectedPackages, loadedPlaylist, rundownIds), loadRundowns(context, ingestModel, rundowns), loadTimeline(context), + loadExpectedPackages(context), ]) + expectedPackages.populateWithPackages(expectedPackagesDocs) + const res = new PlayoutModelImpl( context, playlistLock, @@ -157,12 +161,15 @@ export async function createPlayoutModelfromInitModel( const expectedPackages = new PlayoutExpectedPackagesModelImpl() - const [partInstances, rundownsWithContent, timeline] = await Promise.all([ + const [partInstances, rundownsWithContent, timeline, expectedPackagesDocs] = await Promise.all([ loadPartInstances(context, expectedPackages, initModel.playlist, rundownIds), loadRundowns(context, null, initModel.rundowns), loadTimeline(context), + loadExpectedPackages(context), ]) + expectedPackages.populateWithPackages(expectedPackagesDocs) + const res = new PlayoutModelImpl( context, initModel.playlistLock, @@ -334,3 +341,8 @@ async function loadPartInstances( return allPartInstances } + +async function loadExpectedPackages(context: JobContext): Promise { + // nocommit: load the expectedPackages from the database + return [] +} diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts index d9cdf11e7e..e0c384471e 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutExpectedPackagesModelImpl.ts @@ -29,11 +29,11 @@ export class PlayoutExpectedPackagesModelImpl implements PlayoutExpectedPackages throw new Error('Method not implemented.') } - ensurePackagesExist(_rundownId: RundownId, _expectedPackages: ReadonlyDeep): void { + createPackagesIfMissing(_rundownId: RundownId, _expectedPackages: ReadonlyDeep): void { throw new Error('Method not implemented.') } - ensurePackagesExistMap( + createPackagesIfMissingFromMap( _rundownId: RundownId, _expectedPackages: ReadonlyMap> ): void { @@ -49,6 +49,8 @@ export class PlayoutExpectedPackagesModelImpl implements PlayoutExpectedPackages throw new Error('Method not implemented.') } + populateWithPackages(packages: ExpectedPackageDBNew[]): void {} + async saveAllToDatabase(): Promise { throw new Error('Method not implemented.') }