Skip to content

Commit

Permalink
chore: refactor CacheHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
nytamin committed Sep 3, 2024
1 parent afad2eb commit 677ee3d
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 97 deletions.
192 changes: 96 additions & 96 deletions src/resolver/CacheHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand All @@ -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 */
Expand Down Expand Up @@ -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.
Expand All @@ -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<ResolvedTimelineObject>(validObjects)) {
for (const obj of invalidator.getValidObjects()) {
if (!this.cache.objects[obj.id])
/* istanbul ignore next */
throw new Error(
Expand All @@ -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<Reference>()

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<Reference> {
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<ResolvedTimelineObject>(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}`
}
2 changes: 1 addition & 1 deletion src/resolver/lib/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 677ee3d

Please sign in to comment.