Skip to content

Commit 8f1a435

Browse files
committed
feat: rework ExpectedPackages generation/management to add PieceInstances as owners to existing docs
1 parent 8ebf2a2 commit 8f1a435

File tree

27 files changed

+621
-218
lines changed

27 files changed

+621
-218
lines changed

meteor/server/api/__tests__/cleanup.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,10 @@ async function setDefaultDatatoDB(env: DefaultEnvironment, now: number) {
261261
bucketId: null,
262262
created: 0,
263263
package: {} as any,
264-
ingestSources: [] as any,
264+
ingestSources: [],
265+
playoutSources: {
266+
pieceInstanceIds: [],
267+
},
265268
})
266269
await ExpectedPackageWorkStatuses.insertAsync({
267270
_id: getRandomId(),

meteor/server/migration/X_X_X.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ export const addSteps = addMigrationSteps(CURRENT_SYSTEM_VERSION, [
142142
},
143143
created: pkg.created,
144144
ingestSources: [ingestSource],
145+
playoutSources: {
146+
pieceInstanceIds: [],
147+
},
145148
} satisfies Complete<ExpectedPackageDB>)
146149
}
147150
}

meteor/server/publications/packageManager/expectedPackages/contentCache.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { literal } from '@sofie-automation/corelib/dist/lib'
33
import { MongoFieldSpecifierOnesStrict } from '@sofie-automation/corelib/dist/mongo'
44
import { ExpectedPackageDB } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages'
55
import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
6-
import { PieceInstance, PieceInstancePiece } from '@sofie-automation/corelib/dist/dataModel/PieceInstance'
6+
import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance'
77

88
export type RundownPlaylistCompact = Pick<
99
DBRundownPlaylist,
@@ -16,16 +16,16 @@ export const rundownPlaylistFieldSpecifier = literal<MongoFieldSpecifierOnesStri
1616
nextPartInfo: 1, // So that it invalidates when the next changes
1717
})
1818

19-
export type PieceInstanceCompact = Pick<PieceInstance, '_id' | 'rundownId'> & {
20-
piece: Pick<PieceInstancePiece, 'expectedPackages'>
21-
}
19+
export type PieceInstanceCompact = Pick<
20+
PieceInstance,
21+
'_id' | 'rundownId' | 'partInstanceId' | 'neededExpectedPackageIds'
22+
>
2223

2324
export const pieceInstanceFieldsSpecifier = literal<MongoFieldSpecifierOnesStrict<PieceInstanceCompact>>({
2425
_id: 1,
2526
rundownId: 1,
26-
piece: {
27-
expectedPackages: 1,
28-
},
27+
partInstanceId: 1,
28+
neededExpectedPackageIds: 1,
2929
})
3030

3131
export type ExpectedPackageDBCompact = Pick<ExpectedPackageDB, '_id' | 'package'>

meteor/server/publications/packageManager/expectedPackages/contentObserver.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
ExpectedPackagesContentCache,
66
rundownPlaylistFieldSpecifier,
77
pieceInstanceFieldsSpecifier,
8-
expectedPackageDBFieldsSpecifier,
98
} from './contentCache'
109
import { ExpectedPackages, PieceInstances, RundownPlaylists } from '../../../collections'
1110
import { ReactiveMongoObserverGroup, ReactiveMongoObserverGroupHandle } from '../../lib/observerGroup'
@@ -62,10 +61,7 @@ export class ExpectedPackagesContentObserver implements Meteor.LiveQueryHandle {
6261
{
6362
studioId: studioId,
6463
},
65-
cache.ExpectedPackages.link(),
66-
{
67-
projection: expectedPackageDBFieldsSpecifier,
68-
}
64+
cache.ExpectedPackages.link()
6965
),
7066

7167
RundownPlaylists.observeChanges(

meteor/server/publications/packageManager/expectedPackages/generate.ts

Lines changed: 22 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { PackageContainerOnPackage, Accessor, AccessorOnPackage } from '@sofie-automation/blueprints-integration'
2-
import { getExpectedPackageIdForPieceInstance } from '@sofie-automation/corelib/dist/dataModel/ExpectedPackages'
3-
import { PeripheralDeviceId, ExpectedPackageId, PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids'
1+
import {
2+
PackageContainerOnPackage,
3+
Accessor,
4+
AccessorOnPackage,
5+
ExpectedPackage,
6+
} from '@sofie-automation/blueprints-integration'
7+
import { PeripheralDeviceId, ExpectedPackageId } from '@sofie-automation/corelib/dist/dataModel/Ids'
48
import { protectString, unprotectString } from '@sofie-automation/corelib/dist/protectedString'
59
import {
610
PackageManagerExpectedPackage,
@@ -15,7 +19,7 @@ import { DBStudio, StudioLight, StudioPackageContainer } from '@sofie-automation
1519
import { clone, omit } from '../../../lib/tempLib'
1620
import { CustomPublishCollection } from '../../../lib/customPublication'
1721
import { logger } from '../../../logging'
18-
import { ExpectedPackagesContentCache } from './contentCache'
22+
import { ExpectedPackageDBCompact, ExpectedPackagesContentCache } from './contentCache'
1923
import type { StudioFields } from './publication'
2024

2125
/**
@@ -59,17 +63,7 @@ export async function updateCollectionForExpectedPackageIds(
5963
// Filter, keep only the routed mappings for this device:
6064
if (filterPlayoutDeviceIds && !filterPlayoutDeviceIds.includes(deviceId)) continue
6165

62-
const routedPackage = generateExpectedPackageForDevice(
63-
studio,
64-
{
65-
...packageDoc.package,
66-
_id: unprotectString(packageDoc._id),
67-
},
68-
deviceId,
69-
null,
70-
Priorities.OTHER, // low priority
71-
packageContainers
72-
)
66+
const routedPackage = generateExpectedPackageForDevice(studio, packageDoc, deviceId, packageContainers)
7367

7468
updatedDocIds.add(routedPackage._id)
7569
collection.replace(routedPackage)
@@ -78,95 +72,15 @@ export async function updateCollectionForExpectedPackageIds(
7872

7973
// Remove all documents for an ExpectedPackage that was regenerated, and no update was issues
8074
collection.remove((doc) => {
81-
if (doc.pieceInstanceId) return false
82-
83-
if (missingExpectedPackageIds.has(protectString(doc.expectedPackage._id))) return true
84-
85-
if (updatedDocIds.has(doc._id) && !regenerateIds.has(protectString(doc.expectedPackage._id))) return true
86-
87-
return false
88-
})
89-
}
90-
91-
/**
92-
* Regenerate the output for the provided PieceInstance `regenerateIds`, updating the data in `collection` as needed
93-
* @param contentCache Cache of the database documents used
94-
* @param studio Minimal studio document
95-
* @param layerNameToDeviceIds Lookup table of package layers, to PeripheralDeviceIds the layer could be used with
96-
* @param collection Output collection of the publication
97-
* @param filterPlayoutDeviceIds PeripheralDeviceId filter applied to this publication
98-
* @param regenerateIds Ids of PieceInstance documents to be recalculated
99-
*/
100-
export async function updateCollectionForPieceInstanceIds(
101-
contentCache: ReadonlyDeep<ExpectedPackagesContentCache>,
102-
studio: Pick<DBStudio, StudioFields>,
103-
layerNameToDeviceIds: Map<string, PeripheralDeviceId[]>,
104-
packageContainers: Record<string, StudioPackageContainer>,
105-
collection: CustomPublishCollection<PackageManagerExpectedPackage>,
106-
filterPlayoutDeviceIds: ReadonlyDeep<PeripheralDeviceId[]> | undefined,
107-
regenerateIds: Set<PieceInstanceId>
108-
): Promise<void> {
109-
const updatedDocIds = new Set<PackageManagerExpectedPackageId>()
110-
const missingPieceInstanceIds = new Set<PieceInstanceId>()
111-
112-
for (const pieceInstanceId of regenerateIds) {
113-
const pieceInstanceDoc = contentCache.PieceInstances.findOne(pieceInstanceId)
114-
if (!pieceInstanceDoc) {
115-
missingPieceInstanceIds.add(pieceInstanceId)
116-
continue
117-
}
118-
if (!pieceInstanceDoc.piece?.expectedPackages) continue
119-
120-
pieceInstanceDoc.piece.expectedPackages.forEach((expectedPackage, i) => {
121-
const sanitisedPackageId = getExpectedPackageIdForPieceInstance(
122-
pieceInstanceId,
123-
expectedPackage._id || '__unnamed' + i
124-
)
125-
126-
// Map the expectedPackages onto their specified layer:
127-
const allDeviceIds = new Set<PeripheralDeviceId>()
128-
for (const layerName of expectedPackage.layers) {
129-
const layerDeviceIds = layerNameToDeviceIds.get(layerName)
130-
for (const deviceId of layerDeviceIds || []) {
131-
allDeviceIds.add(deviceId)
132-
}
133-
}
134-
135-
for (const deviceId of allDeviceIds) {
136-
// Filter, keep only the routed mappings for this device:
137-
if (filterPlayoutDeviceIds && !filterPlayoutDeviceIds.includes(deviceId)) continue
138-
139-
const routedPackage = generateExpectedPackageForDevice(
140-
studio,
141-
{
142-
...expectedPackage,
143-
_id: unprotectString(sanitisedPackageId),
144-
},
145-
deviceId,
146-
pieceInstanceId,
147-
Priorities.OTHER, // low priority
148-
packageContainers
149-
)
150-
151-
updatedDocIds.add(routedPackage._id)
152-
collection.replace(routedPackage)
153-
}
154-
})
155-
}
156-
157-
// Remove all documents for an ExpectedPackage that was regenerated, and no update was issues
158-
collection.remove((doc) => {
159-
if (!doc.pieceInstanceId) return false
160-
161-
if (missingPieceInstanceIds.has(doc.pieceInstanceId)) return true
75+
if (missingExpectedPackageIds.has(doc.expectedPackage._id)) return true
16276

163-
if (updatedDocIds.has(doc._id) && !regenerateIds.has(doc.pieceInstanceId)) return true
77+
if (updatedDocIds.has(doc._id) && !regenerateIds.has(doc.expectedPackage._id)) return true
16478

16579
return false
16680
})
16781
}
16882

169-
enum Priorities {
83+
export enum ExpectedPackagePriorities {
17084
// Lower priorities are done first
17185

17286
/** Highest priority */
@@ -181,16 +95,14 @@ function generateExpectedPackageForDevice(
18195
StudioLight,
18296
'_id' | 'packageContainersWithOverrides' | 'previewContainerIds' | 'thumbnailContainerIds'
18397
>,
184-
expectedPackage: PackageManagerExpectedPackageBase,
98+
expectedPackage: ExpectedPackageDBCompact,
18599
deviceId: PeripheralDeviceId,
186-
pieceInstanceId: PieceInstanceId | null,
187-
priority: Priorities,
188100
packageContainers: Record<string, StudioPackageContainer>
189101
): PackageManagerExpectedPackage {
190102
// Lookup Package sources:
191103
const combinedSources: PackageContainerOnPackage[] = []
192104

193-
for (const packageSource of expectedPackage.sources) {
105+
for (const packageSource of expectedPackage.package.sources) {
194106
const lookedUpSource = packageContainers[packageSource.containerId]
195107
if (lookedUpSource) {
196108
combinedSources.push(calculateCombinedSource(packageSource, lookedUpSource))
@@ -208,27 +120,27 @@ function generateExpectedPackageForDevice(
208120
}
209121

210122
// Lookup Package targets:
211-
const combinedTargets = calculateCombinedTargets(expectedPackage, deviceId, packageContainers)
123+
const combinedTargets = calculateCombinedTargets(expectedPackage.package, deviceId, packageContainers)
212124

213-
if (!combinedSources.length && expectedPackage.sources.length !== 0) {
125+
if (!combinedSources.length && expectedPackage.package.sources.length !== 0) {
214126
logger.warn(`Pub.expectedPackagesForDevice: No sources found for "${expectedPackage._id}"`)
215127
}
216128
if (!combinedTargets.length) {
217129
logger.warn(`Pub.expectedPackagesForDevice: No targets found for "${expectedPackage._id}"`)
218130
}
219-
const packageSideEffect = getSideEffect(expectedPackage, studio)
131+
const packageSideEffect = getSideEffect(expectedPackage.package, studio)
220132

221133
return {
222-
_id: protectString(`${expectedPackage._id}_${deviceId}_${pieceInstanceId}`),
134+
_id: protectString(`${expectedPackage._id}_${deviceId}`),
223135
expectedPackage: {
224-
...expectedPackage,
136+
...expectedPackage.package,
137+
_id: expectedPackage._id,
225138
sideEffect: packageSideEffect,
226139
},
227140
sources: combinedSources,
228141
targets: combinedTargets,
229-
priority: priority,
142+
priority: ExpectedPackagePriorities.OTHER, // This gets overriden later if needed
230143
playoutDeviceId: deviceId,
231-
pieceInstanceId,
232144
}
233145
}
234146

@@ -265,7 +177,7 @@ function calculateCombinedSource(
265177
return combinedSource
266178
}
267179
function calculateCombinedTargets(
268-
expectedPackage: PackageManagerExpectedPackageBase,
180+
expectedPackage: ReadonlyDeep<ExpectedPackage.Base>,
269181
deviceId: PeripheralDeviceId,
270182
packageContainers: Record<string, StudioPackageContainer>
271183
): PackageContainerOnPackage[] {

meteor/server/publications/packageManager/expectedPackages/publication.ts

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { PackageManagerExpectedPackage } from '@sofie-automation/shared-lib/dist
2323
import { ExpectedPackagesContentObserver } from './contentObserver'
2424
import { createReactiveContentCache, ExpectedPackagesContentCache } from './contentCache'
2525
import { buildMappingsToDeviceIdMap } from './util'
26-
import { updateCollectionForExpectedPackageIds, updateCollectionForPieceInstanceIds } from './generate'
26+
import { ExpectedPackagePriorities, updateCollectionForExpectedPackageIds } from './generate'
2727
import {
2828
PeripheralDevicePubSub,
2929
PeripheralDevicePubSubCollectionsNames,
@@ -161,16 +161,13 @@ async function manipulateExpectedPackagesPublicationData(
161161
}
162162

163163
let regenerateExpectedPackageIds: Set<ExpectedPackageId>
164-
let regeneratePieceInstanceIds: Set<PieceInstanceId>
165164
if (invalidateAllItems) {
166-
// force every piece to be regenerated
165+
// force every package to be regenerated
167166
collection.remove(null)
168167
regenerateExpectedPackageIds = new Set(state.contentCache.ExpectedPackages.find({}).map((p) => p._id))
169-
regeneratePieceInstanceIds = new Set(state.contentCache.PieceInstances.find({}).map((p) => p._id))
170168
} else {
171169
// only regenerate the reported changes
172170
regenerateExpectedPackageIds = new Set(updateProps.invalidateExpectedPackageIds)
173-
regeneratePieceInstanceIds = new Set(updateProps.invalidatePieceInstanceIds)
174171
}
175172

176173
await updateCollectionForExpectedPackageIds(
@@ -182,15 +179,49 @@ async function manipulateExpectedPackagesPublicationData(
182179
args.filterPlayoutDeviceIds,
183180
regenerateExpectedPackageIds
184181
)
185-
await updateCollectionForPieceInstanceIds(
186-
state.contentCache,
187-
state.studio,
188-
state.layerNameToDeviceIds,
189-
state.packageContainers,
190-
collection,
191-
args.filterPlayoutDeviceIds,
192-
regeneratePieceInstanceIds
193-
)
182+
183+
// Ensure the priorities are correct for the packages
184+
// We can do this as a post-step, as it means we can generate the packages solely based on the content
185+
// If one gets regenerated, its priority will be reset to OTHER. But as it has already changed, this fixup is 'free'
186+
// For those not regenerated, we can set the priority to the correct value if it has changed, without any deeper checks
187+
updatePackagePriorities(state.contentCache, collection)
188+
}
189+
190+
function updatePackagePriorities(
191+
contentCache: ReadonlyDeep<ExpectedPackagesContentCache>,
192+
collection: CustomPublishCollection<PackageManagerExpectedPackage>
193+
) {
194+
const highPriorityPackages = new Map<ExpectedPackageId, ExpectedPackagePriorities>()
195+
196+
// Compile the map of the expected priority of each package
197+
const knownPieceInstances = contentCache.PieceInstances.find({})
198+
const playlist = contentCache.RundownPlaylists.findOne({})
199+
const currentPartInstanceId = playlist?.currentPartInfo?.partInstanceId
200+
for (const pieceInstance of knownPieceInstances) {
201+
const packageIds = pieceInstance.neededExpectedPackageIds
202+
if (!packageIds) continue
203+
204+
const packagePriority =
205+
pieceInstance.partInstanceId === currentPartInstanceId
206+
? ExpectedPackagePriorities.PLAYOUT_CURRENT
207+
: ExpectedPackagePriorities.PLAYOUT_NEXT
208+
209+
for (const packageId of packageIds) {
210+
const existingPriority = highPriorityPackages.get(packageId) ?? ExpectedPackagePriorities.OTHER
211+
highPriorityPackages.set(packageId, Math.min(existingPriority, packagePriority))
212+
}
213+
}
214+
215+
// Iterate through and update each package
216+
collection.updateAll((pkg) => {
217+
const expectedPriority = highPriorityPackages.get(pkg.expectedPackage._id) ?? ExpectedPackagePriorities.OTHER
218+
if (pkg.priority === expectedPriority) return false
219+
220+
return {
221+
...pkg,
222+
priority: expectedPriority,
223+
}
224+
})
194225
}
195226

196227
meteorCustomPublish(

0 commit comments

Comments
 (0)