Skip to content

Commit 58b7f8b

Browse files
committed
Merge branch 'contribute/ianshade/EAV-518'
2 parents 6d51ea6 + 4d5934e commit 58b7f8b

File tree

4 files changed

+156
-33
lines changed

4 files changed

+156
-33
lines changed

src/__tests__/basic.spec.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,6 +1783,77 @@ describeVariants(
17831783
const resolved = resolveTimeline(timeline, { cache: getCache(), time: 0, dontThrowOnError: true })
17841784
expect(resolved.error).toBeTruthy()
17851785
})
1786+
test('Object that start at the same time as another', () => {
1787+
// An object that start at the same time as another should behave as expected
1788+
//
1789+
const timeline = fixTimeline([
1790+
{
1791+
id: 'A',
1792+
enable: { start: 1000 }, // 1000
1793+
layer: 'L1',
1794+
content: { value: 'replaced' },
1795+
priority: 0,
1796+
},
1797+
{
1798+
id: 'B',
1799+
enable: { start: '#A.start' }, // 1000
1800+
layer: 'L1',
1801+
content: { value: 'playing' },
1802+
priority: 1,
1803+
},
1804+
// Same as above, but with ids in different alphabetical order
1805+
{
1806+
id: 'D',
1807+
enable: { start: 1000 }, // 1000
1808+
layer: 'L2',
1809+
content: { value: 'replaced' },
1810+
priority: 0,
1811+
},
1812+
{
1813+
id: 'C',
1814+
enable: { start: '#D.start' }, // 1000
1815+
layer: 'L2',
1816+
content: { value: 'playing' },
1817+
priority: 1,
1818+
},
1819+
])
1820+
const time = 1010
1821+
const resolved = resolveTimeline(timeline, { time, cache: getCache() })
1822+
1823+
expect(resolved.objects).toMatchObject({
1824+
A: {
1825+
resolved: {
1826+
instances: [{ start: 1000, end: 1000 }],
1827+
},
1828+
},
1829+
B: {
1830+
resolved: {
1831+
instances: [{ start: 1000, end: null }],
1832+
},
1833+
},
1834+
D: {
1835+
resolved: {
1836+
instances: [{ start: 1000, end: 1000 }],
1837+
},
1838+
},
1839+
C: {
1840+
resolved: {
1841+
instances: [{ start: 1000, end: null }],
1842+
},
1843+
},
1844+
})
1845+
1846+
const state0 = getResolvedState(resolved, time)
1847+
expect(state0.time).toEqual(time)
1848+
expect(state0.layers).toMatchObject({
1849+
L1: {
1850+
content: { value: 'playing' },
1851+
},
1852+
L2: {
1853+
content: { value: 'playing' },
1854+
},
1855+
})
1856+
})
17861857
},
17871858
{
17881859
normal: true,

src/__tests__/groups.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,6 +1617,45 @@ describeVariants(
16171617
},
16181618
])
16191619
})
1620+
1621+
test('Referencing and replacing a same-layer child of a group from the outside', () => {
1622+
const timeline = fixTimeline([
1623+
{
1624+
id: 'group',
1625+
enable: { start: 1000 },
1626+
priority: 0,
1627+
layer: '',
1628+
content: {},
1629+
children: [
1630+
{
1631+
id: 'obj_inside',
1632+
enable: { start: 0 },
1633+
layer: 'L1',
1634+
content: { value: 'wrong' },
1635+
priority: 0,
1636+
},
1637+
],
1638+
isGroup: true,
1639+
},
1640+
{
1641+
id: 'obj_outside',
1642+
enable: { start: '#obj_inside.start' },
1643+
layer: 'L1',
1644+
content: { value: 'right' },
1645+
priority: 1,
1646+
},
1647+
])
1648+
const time = 1000
1649+
const resolved = resolveTimeline(timeline, { time, cache: getCache() })
1650+
1651+
const state0 = getResolvedState(resolved, time)
1652+
expect(state0.time).toEqual(time)
1653+
expect(state0.layers).toMatchObject({
1654+
L1: {
1655+
content: { value: 'right' },
1656+
},
1657+
})
1658+
})
16201659
},
16211660
{
16221661
normal: true,

src/resolver/LayerStateHandler.ts

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ export class LayerStateHandler {
2121
constructor(
2222
private resolvedTimeline: ResolvedTimelineHandler,
2323
private instance: InstanceHandler,
24-
private layer: string
24+
private layer: string,
25+
/**
26+
* Maps an array of object ids to an object id (objects that directly reference an reference).
27+
*/
28+
private directReferenceMap: Map<string, string[]>
2529
) {
2630
this.objectsOnLayer = []
2731
this.objectIdsOnLayer = this.resolvedTimeline.getLayerObjects(layer)
@@ -117,7 +121,7 @@ export class LayerStateHandler {
117121

118122
/** List of the instances to check at this point in time. */
119123
const instancesToCheck: InstanceAtPointInTime[] = this.pointsInTime[time]
120-
instancesToCheck.sort(compareInstancesToCheck)
124+
instancesToCheck.sort(this.compareInstancesToCheck)
121125

122126
for (let j = 0; j < instancesToCheck.length; j++) {
123127
const o = instancesToCheck[j]
@@ -268,6 +272,45 @@ export class LayerStateHandler {
268272
if (!this.pointsInTime[time + '']) this.pointsInTime[time + ''] = []
269273
this.pointsInTime[time + ''].push({ obj, instance, instanceEvent })
270274
}
275+
private compareInstancesToCheck = (a: InstanceAtPointInTime, b: InstanceAtPointInTime) => {
276+
// Note: we assume that there are no keyframes here. (if there where, they would be sorted first)
277+
278+
if (
279+
a.instance.id === b.instance.id &&
280+
a.instance.start === b.instance.start &&
281+
a.instance.end === b.instance.end
282+
) {
283+
// A & B are the same instance, it is a zero-length instance!
284+
// In this case, put the start before the end:
285+
if (a.instanceEvent === 'start' && b.instanceEvent === 'end') return -1
286+
if (a.instanceEvent === 'end' && b.instanceEvent === 'start') return 1
287+
}
288+
289+
// Handle ending instances first:
290+
if (a.instanceEvent === 'start' && b.instanceEvent === 'end') return 1
291+
if (a.instanceEvent === 'end' && b.instanceEvent === 'start') return -1
292+
293+
if (a.instance.start === a.instance.end || b.instance.start === b.instance.end) {
294+
// Put later-ending instances last (in the case of zero-length vs non-zero-length instance):
295+
const difference = (a.instance.end ?? Infinity) - (b.instance.end ?? Infinity)
296+
if (difference) return difference
297+
}
298+
299+
// If A references B, A should be handled after B, (B might resolve into a zero-length instance)
300+
const aRefObjIds = this.directReferenceMap.get(a.obj.id)
301+
if (aRefObjIds?.includes(b.obj.id)) return -1
302+
const bRefObjIds = this.directReferenceMap.get(b.obj.id)
303+
if (bRefObjIds?.includes(a.obj.id)) return 1
304+
305+
if (a.obj.resolved && b.obj.resolved) {
306+
// Deeper objects (children in groups) comes later, we want to check the parent groups first:
307+
const difference = a.obj.resolved.levelDeep - b.obj.resolved.levelDeep
308+
if (difference) return difference
309+
}
310+
311+
// Last resort, sort by id to make it deterministic:
312+
return compareStrings(a.obj.id, b.obj.id) || compareStrings(a.instance.id, b.instance.id)
313+
}
271314
}
272315
export interface TimeEvent {
273316
time: number
@@ -292,36 +335,6 @@ function compareObjectsOnLayer(a: ResolvedTimelineObject, b: ResolvedTimelineObj
292335
return a.resolved.levelDeep - b.resolved.levelDeep || compareStrings(a.id, b.id)
293336
}
294337

295-
function compareInstancesToCheck(a: InstanceAtPointInTime, b: InstanceAtPointInTime) {
296-
// Note: we assume that there are no keyframes here. (if there where, they would be sorted first)
297-
298-
if (a.instance.id === b.instance.id && a.instance.start === b.instance.start && a.instance.end === b.instance.end) {
299-
// A & B are the same instance, it is a zero-length instance!
300-
// In this case, put the start before the end:
301-
if (a.instanceEvent === 'start' && b.instanceEvent === 'end') return -1
302-
if (a.instanceEvent === 'end' && b.instanceEvent === 'start') return 1
303-
}
304-
305-
// Handle ending instances first:
306-
if (a.instanceEvent === 'start' && b.instanceEvent === 'end') return 1
307-
if (a.instanceEvent === 'end' && b.instanceEvent === 'start') return -1
308-
309-
if (a.instance.start === a.instance.end || b.instance.start === b.instance.end) {
310-
// Put later-ending instances last (in the case of zero-length vs non-zero-length instance):
311-
const difference = (a.instance.end ?? Infinity) - (b.instance.end ?? Infinity)
312-
if (difference) return difference
313-
}
314-
315-
if (a.obj.resolved && b.obj.resolved) {
316-
// Deeper objects (children in groups) comes later, we want to check the parent groups first:
317-
const difference = a.obj.resolved.levelDeep - b.obj.resolved.levelDeep
318-
if (difference) return difference
319-
}
320-
321-
// Last resort, sort by id to make it deterministic:
322-
return compareStrings(a.obj.id, b.obj.id) || compareStrings(a.instance.id, b.instance.id)
323-
}
324-
325338
const removeFromAspiringInstances = (aspiringInstances: AspiringInstance[], objId: string): AspiringInstance[] => {
326339
const returnInstances: AspiringInstance[] = []
327340
for (let i = 0; i < aspiringInstances.length; i++) {

src/resolver/ResolvedTimelineHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1129,7 +1129,7 @@ export class ResolvedTimelineHandler<TContent extends Content = Content> {
11291129
* @returns A list of objects on that layer
11301130
*/
11311131
private resolveConflictsForLayer(layer: string): ResolvedTimelineObject[] {
1132-
const handler = new LayerStateHandler(this, this.instance, layer)
1132+
const handler = new LayerStateHandler(this, this.instance, layer, this.directReferenceMap)
11331133

11341134
// Fast path: If an object on this layer depends on an already changed object we should skip this layer, this iteration.
11351135
// Because the objects will likely change during the next resolve-iteration anyway.

0 commit comments

Comments
 (0)