From 5c8d7844d582c2455aafdd0d3e7eac25781655dc Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Tue, 3 Sep 2024 11:34:59 +0200 Subject: [PATCH 1/5] chore: Add unit test --- src/__tests__/index.spec.ts | 64 +++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts index 88b02de5..25082d05 100644 --- a/src/__tests__/index.spec.ts +++ b/src/__tests__/index.spec.ts @@ -9,6 +9,7 @@ import { applyKeyframeContent, } from '../index' import { baseInstances } from '../resolver/lib/instance' +import { clone } from '../resolver/lib/lib' describe('index', () => { test('resolve timeline', () => { @@ -783,4 +784,67 @@ describe('index', () => { }, }) }) + + test('Cache', () => { + const timeline0: TimelineObject[] = [ + { + id: 'bg', + enable: { + while: 1, + }, + layer: 'layerA', + content: {}, + }, + { + id: 'group0', + enable: { + start: 10000, + }, + layer: '', + content: {}, + children: [ + { + id: 'child0', + enable: { + start: 5000, // 15000 + end: null, + }, + layer: 'layerA', + content: {}, + }, + ], + isGroup: true, + }, + ] + + const timeline1 = clone(timeline0) + + // Nudge "group0" a little bit, this should cause "child0" to be resolved differently, which in turn affects "bg" via collision + timeline1[1].enable = { + start: 10001, + } + + const cache = {} + + resolveTimeline(timeline0, { cache: cache, time: 0 }) + + const rtl1NoCache = resolveTimeline(timeline1, { time: 0 }) + const rtl1 = resolveTimeline(timeline1, { cache: cache, time: 0 }) + + const state1NoCache = getResolvedState(rtl1NoCache, 10) + const state1 = getResolvedState(rtl1, 10) + + // cache and no-cache should render the same result + expect(state1NoCache.layers['layerA']).toBeTruthy() + + expect(state1.layers['layerA']).toMatchObject({ + id: 'bg', + instance: { + start: 0, + end: 15001, + }, + }) + + expect(state1.layers['layerA']).toEqual(state1NoCache.layers['layerA']) + }) }) From 1a43707acd9e3ed93138e9fd9a0bac429df4e900 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Tue, 3 Sep 2024 13:11:03 +0200 Subject: [PATCH 2/5] chore: improve unit test Add checks for a chain of directs and indirect (due to collision) references --- src/__tests__/index.spec.ts | 40 +++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts index 25082d05..096725fb 100644 --- a/src/__tests__/index.spec.ts +++ b/src/__tests__/index.spec.ts @@ -815,6 +815,24 @@ describe('index', () => { ], isGroup: true, }, + { + id: 'bg2', + enable: { + while: '#bg', + }, + layer: 'layerB', + content: {}, + priority: 1, + }, + { + id: 'bg3', + enable: { + while: 1, + }, + layer: 'layerB', + content: {}, + priority: 0, + }, ] const timeline1 = clone(timeline0) @@ -837,13 +855,27 @@ describe('index', () => { // cache and no-cache should render the same result expect(state1NoCache.layers['layerA']).toBeTruthy() - expect(state1.layers['layerA']).toMatchObject({ - id: 'bg', - instance: { + // "bg" should have changed, since that is affected by a collision with "child0" + expect(rtl1.objects['bg'].resolved.instances).toMatchObject([ + { start: 0, end: 15001, }, - }) + ]) + // "bg2" should have changed, since that is affected by the change to "bg" + expect(rtl1.objects['bg2'].resolved.instances).toMatchObject([ + { + start: 0, + end: 15001, + }, + ]) + // "bg3" should have changed, since that is affected by a collision with "bg2" + expect(rtl1.objects['bg3'].resolved.instances).toMatchObject([ + { + start: 15001, + end: null, + }, + ]) expect(state1.layers['layerA']).toEqual(state1NoCache.layers['layerA']) }) From 26f4e821c29f6de73d80cb97007dc5cc2fd87ece Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Tue, 3 Sep 2024 13:11:50 +0200 Subject: [PATCH 3/5] fix: when using the cache: invalidate objects on layers affected by changes --- src/resolver/CacheHandler.ts | 61 +++++++++++++++++++++---- src/resolver/ResolvedTimelineHandler.ts | 3 +- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/resolver/CacheHandler.ts b/src/resolver/CacheHandler.ts index 3493b3a0..56bc8a6c 100644 --- a/src/resolver/CacheHandler.ts +++ b/src/resolver/CacheHandler.ts @@ -2,7 +2,7 @@ import { Reference, ResolvedTimelineObject, ResolvedTimelineObjects, ResolverCac import { ResolvedTimelineHandler } from './ResolvedTimelineHandler' import { mapToObject } from './lib/lib' import { tic } from './lib/performance' -import { getRefObjectId, isObjectReference, joinReferences } from './lib/reference' +import { getRefLayer, getRefObjectId, isLayerReference, isObjectReference, joinReferences } from './lib/reference' import { objHasLayer } from './lib/timeline' export class CacheHandler { @@ -43,6 +43,9 @@ export class CacheHandler { for (const ref of references) { changedReferences[ref] = true } + if (objHasLayer(obj)) { + changedReferences[`$${obj.layer}`] = true + } } for (const obj of this.resolvedTimeline.objectsMap.values()) { @@ -94,15 +97,21 @@ export class CacheHandler { addChangedObject(obj) } } - // Invalidate objects, by gradually removing the invalidated ones from validObjects + // At this point, all directly changed objects have been marked as changed. + + // Next step is to invalidate any indirectly affected objects, by gradually removing the invalidated ones from validObjects + // Prepare validObjects: const validObjects: ResolvedTimelineObjects = {} for (const obj of this.resolvedTimeline.objectsMap.values()) { validObjects[obj.id] = obj } - /** All references that depend on another reference (ie objects, classs or layers): */ + /** All references that depend on another reference (ie objects, class or layers): */ const affectReferenceMap: { [ref: Reference]: Reference[] } = {} + /** Map of which objects can be affected by any other object, per layer */ + const objectLayerMap: { [layer: string]: string[] } = {} + for (const obj of this.resolvedTimeline.objectsMap.values()) { // Add everything that this object affects: const cachedObj = this.cache.objects[obj.id] @@ -124,9 +133,12 @@ export class CacheHandler { // Add everything that this object is affected by: if (changedReferences[`#${obj.id}`]) { - // The object is directly said to be invalid, no need to add it to referencingObjects, - // since it'll be easily invalidated anyway later + // The object is directly said to have changed. + // (No need to add it to affectReferenceMap, since it'll be easily invalidated anyway later) } else { + // The object is not directly said to have changed. + // But if might have been affected by other objects that have changed. + // Note: we only have to check for the OLD object, since if the old and the new object differs, // that would mean it'll be directly invalidated anyway. if (cachedObj) { @@ -134,6 +146,13 @@ export class CacheHandler { // Note: This can be done, since _if_ the object was changed in any way since last resolve // it'll be invalidated anyway const dependOnReferences = cachedObj.resolved.directReferences + + // Build up objectLayerMap: + if (objHasLayer(cachedObj)) { + if (!objectLayerMap[cachedObj.layer]) objectLayerMap[cachedObj.layer] = [] + objectLayerMap[cachedObj.layer].push(obj.id) + } + for (let i = 0; i < dependOnReferences.length; i++) { const ref = dependOnReferences[i] if (!affectReferenceMap[ref]) affectReferenceMap[ref] = [] @@ -142,13 +161,20 @@ export class CacheHandler { } } } + // Invalidate all changed objects, and recursively invalidate all objects that reference those objects: const handledReferences: { [ref: Reference]: true } = {} for (const reference of Object.keys(changedReferences) as Reference[]) { - this.invalidateObjectsWithReference(handledReferences, reference, affectReferenceMap, validObjects) + this.invalidateObjectsWithReference( + handledReferences, + reference, + affectReferenceMap, + validObjects, + objectLayerMap + ) } - // The objects that are left in validObjects at this point are still valid. + // At this point, the objects that are left in validObjects are still valid (ie has not changed or is affected by any others). // We can reuse the old resolving for those: for (const obj of Object.values(validObjects)) { if (!this.cache.objects[obj.id]) @@ -201,7 +227,9 @@ export class CacheHandler { handledReferences: { [ref: Reference]: true }, reference: Reference, affectReferenceMap: { [ref: Reference]: Reference[] }, - validObjects: ResolvedTimelineObjects + validObjects: ResolvedTimelineObjects, + /** Map of which objects can be affected by any other object, per layer */ + objectLayerMap: { [layer: string]: string[] } ) { if (handledReferences[reference]) return // to avoid infinite loops handledReferences[reference] = true @@ -212,6 +240,20 @@ export class CacheHandler { delete validObjects[objId] } } + if (isLayerReference(reference)) { + const layer = getRefLayer(reference) + if (objectLayerMap[layer]) { + for (const affectedObjId of objectLayerMap[layer]) { + this.invalidateObjectsWithReference( + handledReferences, + `#${affectedObjId}`, + affectReferenceMap, + validObjects, + objectLayerMap + ) + } + } + } // Invalidate all objects that depend on any of the references that this reference affects: const affectedReferences = affectReferenceMap[reference] @@ -222,7 +264,8 @@ export class CacheHandler { handledReferences, referencingReference, affectReferenceMap, - validObjects + validObjects, + objectLayerMap ) } } diff --git a/src/resolver/ResolvedTimelineHandler.ts b/src/resolver/ResolvedTimelineHandler.ts index 0ed2bda9..f1fdaa56 100644 --- a/src/resolver/ResolvedTimelineHandler.ts +++ b/src/resolver/ResolvedTimelineHandler.ts @@ -233,8 +233,7 @@ export class ResolvedTimelineHandler { if (obj.disabled) { resultingInstances = [] } else { - // Loop up references to the parent: - + // Look up references to the parent: let parentInstances: TimelineObjectInstance[] | null = null let hasParent = false let parentRef: ObjectReference | undefined = undefined From afad2eb6ebf99e83113d41502d83626f349718ec Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Tue, 3 Sep 2024 13:12:07 +0200 Subject: [PATCH 4/5] chore: update unit tests --- src/__tests__/invalidate.spec.ts | 6 +++--- src/__tests__/performance.spec.ts | 4 ++-- src/__tests__/performance.ts | 6 ++++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/__tests__/invalidate.spec.ts b/src/__tests__/invalidate.spec.ts index efc7598c..a1e37856 100644 --- a/src/__tests__/invalidate.spec.ts +++ b/src/__tests__/invalidate.spec.ts @@ -55,7 +55,7 @@ describeVariants( graphic1.enable.start = '#graphic0.end + 15' // 35 const resolved2 = resolveTimeline(timeline, { time: 0, cache }) - expect(resolved2.statistics.resolvingObjectCount).toEqual(1) + expect(resolved2.statistics.resolvingObjectCount).toEqual(2) expect(resolved2.objects['video'].resolved).toMatchObject({ instances: [{ start: 0, end: 100 }] }) expect(resolved2.objects['graphic0'].resolved).toMatchObject({ instances: [{ start: 10, end: 20 }] }) expect(resolved2.objects['graphic1'].resolved).toMatchObject({ instances: [{ start: 35, end: 50 }] }) @@ -121,7 +121,7 @@ describeVariants( const resolved2 = resolveTimeline(timeline, { time: 0, cache }) - expect(resolved2.statistics.resolvingObjectCount).toEqual(2) + expect(resolved2.statistics.resolvingObjectCount).toEqual(3) expect(resolved2.objects['video0'].resolved.instances).toMatchObject([{ start: 20, end: 30 }]) expect(resolved2.objects['graphic0'].resolved.instances).toMatchObject([{ start: 30, end: 40 }]) expect(resolved2.objects['graphic1'].resolved.instances).toHaveLength(0) @@ -261,7 +261,7 @@ describeVariants( timeline.splice(index, 1) const resolved3 = resolveTimeline(timeline, { time: 0, cache }) - expect(resolved3.statistics.resolvingObjectCount).toEqual(1) + expect(resolved3.statistics.resolvingObjectCount).toEqual(2) expect(resolved3.objects['video0'].resolved).toMatchObject({ instances: [{ start: 0, end: 100 }] }) expect(resolved3.objects['graphic0'].resolved).toMatchObject({ instances: [{ start: 10, end: 20 }] }) expect(resolved3.objects['graphic1'].resolved).toMatchObject({ instances: [{ start: 20, end: 25 }] }) diff --git a/src/__tests__/performance.spec.ts b/src/__tests__/performance.spec.ts index 68d04dff..1f0925ac 100644 --- a/src/__tests__/performance.spec.ts +++ b/src/__tests__/performance.spec.ts @@ -11,7 +11,7 @@ describe('performance', () => { test( 'performance test, no cache', () => { - const { sortedTimes, executionTimeAvg } = doPerformanceTest(TEST_COUNT, false) + const { sortedTimes, executionTimeAvg } = doPerformanceTest(TEST_COUNT, TIMEOUT_TIME, false) console.log( `No Cache: Average time of execution: ${round(executionTimeAvg)} ms\n` + 'Worst 5:\n' + @@ -29,7 +29,7 @@ describe('performance', () => { test( 'performance test, with cache', () => { - const { sortedTimes, executionTimeAvg } = doPerformanceTest(TEST_COUNT, true) + const { sortedTimes, executionTimeAvg } = doPerformanceTest(TEST_COUNT, TIMEOUT_TIME, true) console.log( `With cache: Average time of execution: ${round(executionTimeAvg)} ms\n` + 'Worst 5:\n' + diff --git a/src/__tests__/performance.ts b/src/__tests__/performance.ts index 3cc5ec01..748479d2 100644 --- a/src/__tests__/performance.ts +++ b/src/__tests__/performance.ts @@ -27,6 +27,7 @@ export const round = (num: number): number => { export const doPerformanceTest = ( testCount: number, + timeoutTime: number, useCache: boolean ): { errorCount: number @@ -48,8 +49,13 @@ export const doPerformanceTest = ( const testCountMax = testCount * 2 + const startTime = Date.now() for (let i = 0; i < testCountMax; i++) { if (executionTimeCount >= testCount) break + const totalDuration = Date.now() - startTime + if (totalDuration >= timeoutTime) { + throw new Error(`Tests took too long (${totalDuration}ms)`) + } seed++ From 677ee3d9ce002d6aa9ec023ea8cc5d7d0024b026 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Tue, 3 Sep 2024 15:07:24 +0200 Subject: [PATCH 5/5] chore: refactor CacheHandler --- src/resolver/CacheHandler.ts | 192 +++++++++++++++++------------------ src/resolver/lib/timeline.ts | 2 +- 2 files changed, 97 insertions(+), 97 deletions(-) diff --git a/src/resolver/CacheHandler.ts b/src/resolver/CacheHandler.ts index 56bc8a6c..0dfd6292 100644 --- a/src/resolver/CacheHandler.ts +++ b/src/resolver/CacheHandler.ts @@ -6,7 +6,7 @@ import { getRefLayer, getRefObjectId, isLayerReference, isObjectReference, joinR import { objHasLayer } from './lib/timeline' export class CacheHandler { - /** A Persistant store. This object contains data that is persisted between resolves. */ + /** A Persistent store. This object contains data that is persisted between resolves. */ private cache: ResolverCache private canUseIncomingCache: boolean @@ -37,16 +37,8 @@ export class CacheHandler { const toc = tic(' cache.determineChangedObjects') // Go through all new objects, and determine whether they have changed: const allNewObjects: { [objId: string]: true } = {} - const changedReferences: { [reference: Reference]: true } = {} - const addChangedObject = (obj: ResolvedTimelineObject) => { - const references = this.getAllReferencesThisObjectAffects(obj) - for (const ref of references) { - changedReferences[ref] = true - } - if (objHasLayer(obj)) { - changedReferences[`$${obj.layer}`] = true - } - } + + const changedTracker = new ChangedTracker() for (const obj of this.resolvedTimeline.objectsMap.values()) { const oldHash = this.cache.objHashes[obj.id] @@ -62,10 +54,10 @@ export class CacheHandler { oldHash !== newHash ) { this.cache.objHashes[obj.id] = newHash - addChangedObject(obj) + changedTracker.addChangedObject(obj) const oldObj = this.cache.objects[obj.id] - if (oldObj) addChangedObject(oldObj) + if (oldObj) changedTracker.addChangedObject(oldObj) } else { // No timing-affecting changes detected /* istanbul ignore if */ @@ -94,47 +86,40 @@ export class CacheHandler { if (!allNewObjects[objId]) { const obj = this.cache.objects[objId] delete this.cache.objHashes[objId] - addChangedObject(obj) + changedTracker.addChangedObject(obj) } } // At this point, all directly changed objects have been marked as changed. // Next step is to invalidate any indirectly affected objects, by gradually removing the invalidated ones from validObjects - // Prepare validObjects: - const validObjects: ResolvedTimelineObjects = {} + // Prepare the invalidator, ie populate it with the objects that are still valid: + const invalidator = new Invalidator() for (const obj of this.resolvedTimeline.objectsMap.values()) { - validObjects[obj.id] = obj + invalidator.addValidObject(obj) } - /** All references that depend on another reference (ie objects, class or layers): */ - const affectReferenceMap: { [ref: Reference]: Reference[] } = {} - - /** Map of which objects can be affected by any other object, per layer */ - const objectLayerMap: { [layer: string]: string[] } = {} for (const obj of this.resolvedTimeline.objectsMap.values()) { // Add everything that this object affects: const cachedObj = this.cache.objects[obj.id] - let affectedReferences = this.getAllReferencesThisObjectAffects(obj) + let affectedReferences = getAllReferencesThisObjectAffects(obj) if (cachedObj) { affectedReferences = joinReferences( affectedReferences, - this.getAllReferencesThisObjectAffects(cachedObj) + getAllReferencesThisObjectAffects(cachedObj) ) } for (let i = 0; i < affectedReferences.length; i++) { const ref = affectedReferences[i] const objRef: Reference = `#${obj.id}` if (ref !== objRef) { - if (!affectReferenceMap[objRef]) affectReferenceMap[objRef] = [] - affectReferenceMap[objRef].push(ref) + invalidator.addAffectedReference(objRef, ref) } } // Add everything that this object is affected by: - if (changedReferences[`#${obj.id}`]) { + if (changedTracker.isChanged(`#${obj.id}`)) { // The object is directly said to have changed. - // (No need to add it to affectReferenceMap, since it'll be easily invalidated anyway later) } else { // The object is not directly said to have changed. // But if might have been affected by other objects that have changed. @@ -149,34 +134,25 @@ export class CacheHandler { // Build up objectLayerMap: if (objHasLayer(cachedObj)) { - if (!objectLayerMap[cachedObj.layer]) objectLayerMap[cachedObj.layer] = [] - objectLayerMap[cachedObj.layer].push(obj.id) + invalidator.addObjectOnLayer(`${cachedObj.layer}`, obj) } for (let i = 0; i < dependOnReferences.length; i++) { const ref = dependOnReferences[i] - if (!affectReferenceMap[ref]) affectReferenceMap[ref] = [] - affectReferenceMap[ref].push(`#${obj.id}`) + invalidator.addAffectedReference(ref, `#${obj.id}`) } } } } // Invalidate all changed objects, and recursively invalidate all objects that reference those objects: - const handledReferences: { [ref: Reference]: true } = {} - for (const reference of Object.keys(changedReferences) as Reference[]) { - this.invalidateObjectsWithReference( - handledReferences, - reference, - affectReferenceMap, - validObjects, - objectLayerMap - ) + for (const reference of changedTracker.listChanged()) { + invalidator.invalidateObjectsWithReference(reference) } // At this point, the objects that are left in validObjects are still valid (ie has not changed or is affected by any others). // We can reuse the old resolving for those: - for (const obj of Object.values(validObjects)) { + for (const obj of invalidator.getValidObjects()) { if (!this.cache.objects[obj.id]) /* istanbul ignore next */ throw new Error( @@ -203,84 +179,108 @@ export class CacheHandler { toc() } +} +/** Return a "hash-string" which changes whenever anything that affects timing of a timeline-object has changed. */ +export function hashTimelineObject(obj: ResolvedTimelineObject): string { + /* + Note: The following properties are ignored, as they don't affect timing or resolving: + * id + * children + * keyframes + * isGroup + * content + */ + return `${JSON.stringify(obj.enable)},${+!!obj.disabled},${obj.priority}',${obj.resolved.parentId},${+obj.resolved + .isKeyframe},${obj.classes ? obj.classes.join('.') : ''},${obj.layer},${+!!obj.seamless}` +} +function getAllReferencesThisObjectAffects(newObj: ResolvedTimelineObject): Reference[] { + const references: Reference[] = [`#${newObj.id}`] - private getAllReferencesThisObjectAffects(newObj: ResolvedTimelineObject): Reference[] { - const references: Reference[] = [`#${newObj.id}`] + if (newObj.classes) { + for (const className of newObj.classes) { + references.push(`.${className}`) + } + } + if (objHasLayer(newObj)) references.push(`$${newObj.layer}`) - if (newObj.classes) { - for (const className of newObj.classes) { - references.push(`.${className}`) - } + if (newObj.children) { + for (const child of newObj.children) { + references.push(`#${child.id}`) } - if (objHasLayer(newObj)) references.push(`$${newObj.layer}`) + } + return references +} +class ChangedTracker { + private changedReferences = new Set() - if (newObj.children) { - for (const child of newObj.children) { - references.push(`#${child.id}`) - } + public addChangedObject(obj: ResolvedTimelineObject) { + const references = getAllReferencesThisObjectAffects(obj) + for (const ref of references) { + this.changedReferences.add(ref) + } + if (objHasLayer(obj)) { + this.changedReferences.add(`$${obj.layer}`) } - return references + } + public isChanged(ref: Reference): boolean { + return this.changedReferences.has(ref) + } + public listChanged(): IterableIterator { + return this.changedReferences.keys() + } +} + +/** The Invalidator */ +class Invalidator { + private handledReferences: { [ref: Reference]: true } = {} + /** All references that depend on another reference (ie objects, class or layers): */ + private affectReferenceMap: { [ref: Reference]: Reference[] } = {} + private validObjects: ResolvedTimelineObjects = {} + /** Map of which objects can be affected by any other object, per layer */ + private objectLayerMap: { [layer: string]: string[] } = {} + + public addValidObject(obj: ResolvedTimelineObject) { + this.validObjects[obj.id] = obj + } + public getValidObjects(): ResolvedTimelineObject[] { + return Object.values(this.validObjects) + } + public addObjectOnLayer(layer: string, obj: ResolvedTimelineObject) { + if (!this.objectLayerMap[layer]) this.objectLayerMap[layer] = [] + this.objectLayerMap[layer].push(obj.id) + } + public addAffectedReference(objRef: Reference, ref: Reference) { + if (!this.affectReferenceMap[objRef]) this.affectReferenceMap[objRef] = [] + this.affectReferenceMap[objRef].push(ref) } /** Invalidate all changed objects, and recursively invalidate all objects that reference those objects */ - private invalidateObjectsWithReference( - handledReferences: { [ref: Reference]: true }, - reference: Reference, - affectReferenceMap: { [ref: Reference]: Reference[] }, - validObjects: ResolvedTimelineObjects, - /** Map of which objects can be affected by any other object, per layer */ - objectLayerMap: { [layer: string]: string[] } - ) { - if (handledReferences[reference]) return // to avoid infinite loops - handledReferences[reference] = true + public invalidateObjectsWithReference(reference: Reference) { + if (this.handledReferences[reference]) return // to avoid infinite loops + this.handledReferences[reference] = true if (isObjectReference(reference)) { const objId = getRefObjectId(reference) - if (validObjects[objId]) { - delete validObjects[objId] + if (this.validObjects[objId]) { + delete this.validObjects[objId] } } if (isLayerReference(reference)) { const layer = getRefLayer(reference) - if (objectLayerMap[layer]) { - for (const affectedObjId of objectLayerMap[layer]) { - this.invalidateObjectsWithReference( - handledReferences, - `#${affectedObjId}`, - affectReferenceMap, - validObjects, - objectLayerMap - ) + if (this.objectLayerMap[layer]) { + for (const affectedObjId of this.objectLayerMap[layer]) { + this.invalidateObjectsWithReference(`#${affectedObjId}`) } } } // Invalidate all objects that depend on any of the references that this reference affects: - const affectedReferences = affectReferenceMap[reference] + const affectedReferences = this.affectReferenceMap[reference] if (affectedReferences) { for (let i = 0; i < affectedReferences.length; i++) { const referencingReference = affectedReferences[i] - this.invalidateObjectsWithReference( - handledReferences, - referencingReference, - affectReferenceMap, - validObjects, - objectLayerMap - ) + this.invalidateObjectsWithReference(referencingReference) } } } } -/** Return a "hash-string" which changes whenever anything that affects timing of a timeline-object has changed. */ -export function hashTimelineObject(obj: ResolvedTimelineObject): string { - /* - Note: The following properties are ignored, as they don't affect timing or resolving: - * id - * children - * keyframes - * isGroup - * content - */ - return `${JSON.stringify(obj.enable)},${+!!obj.disabled},${obj.priority}',${obj.resolved.parentId},${+obj.resolved - .isKeyframe},${obj.classes ? obj.classes.join('.') : ''},${obj.layer},${+!!obj.seamless}` -} diff --git a/src/resolver/lib/timeline.ts b/src/resolver/lib/timeline.ts index e4f7d878..b47cdc61 100644 --- a/src/resolver/lib/timeline.ts +++ b/src/resolver/lib/timeline.ts @@ -5,6 +5,6 @@ import { TimelineObject } from '../../api' * Note: Objects without a layer are called "transparent objects", * and won't be present in the resolved state. */ -export function objHasLayer(obj: TimelineObject): boolean { +export function objHasLayer(obj: TimelineObject): obj is TimelineObject & { layer: TimelineObject['layer'] } { return obj.layer !== undefined && obj.layer !== '' && obj.layer !== null }