superfly-timeline
Version:
Resolver for defining objects with temporal boolean logic relationships on a timeline
371 lines • 16.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.InstanceHandler = void 0;
const cap_1 = require("./lib/cap");
const event_1 = require("./lib/event");
const instance_1 = require("./lib/instance");
const lib_1 = require("./lib/lib");
const reference_1 = require("./lib/reference");
class InstanceHandler {
constructor(resolvedTimeline) {
this.resolvedTimeline = resolvedTimeline;
}
invertInstances(instances) {
if (instances.length) {
instances = this.cleanInstances(instances, true, true);
const invertedInstances = [];
if (instances[0].start !== 0) {
invertedInstances.push({
id: this.resolvedTimeline.getInstanceId(),
isFirst: true,
start: 0,
end: null,
references: (0, reference_1.joinReferences)(instances[0].references, `@${instances[0].id}`),
});
}
for (let i = 0; i < instances.length; i++) {
const instance = instances[i];
const lastInstance = (0, lib_1.last)(invertedInstances);
if (lastInstance) {
lastInstance.end = instance.start;
}
if (instance.end !== null) {
invertedInstances.push({
id: this.resolvedTimeline.getInstanceId(),
start: instance.end,
end: null,
references: (0, reference_1.joinReferences)(instance.references, `@${instance.id}`),
caps: instance.caps,
});
}
}
return invertedInstances;
}
else {
return [
{
id: this.resolvedTimeline.getInstanceId(),
isFirst: true,
start: 0,
end: null,
references: [],
},
];
}
}
/**
* Converts a list of events into a list of instances.
* @param events The list of start- and end- events
* @param allowMerge If true, will merge instances that overlap into one.
* @param allowZeroGaps If true, allows zero-length gaps between instances. If false, will combine the two into one instance.
* @param omitOriginalStartEnd Of true, will not keep .originalStart and .originalEnd of the instances
*/
convertEventsToInstances(events, allowMerge, allowZeroGaps = false, omitOriginalStartEnd = false) {
(0, event_1.sortEvents)(events);
const activeInstances = {};
let activeInstanceId = null;
let previousActive = false;
const negativeInstances = {};
let previousNegative = false;
let negativeInstanceId = null;
const returnInstances = [];
for (let i = 0; i < events.length; i++) {
const event = events[i];
const eventId = event.data.id ?? event.data.instance.id;
const lastInstance = returnInstances[returnInstances.length - 1];
if (event.value) {
// Start-event
activeInstances[eventId] = event;
delete negativeInstances[eventId];
}
else {
// End-event
delete activeInstances[eventId];
negativeInstances[eventId] = event;
}
if (Object.keys(activeInstances).length) {
// There is an active instance
if (!allowMerge && !allowZeroGaps && lastInstance && previousNegative) {
// There is previously an inActive (negative) instance
lastInstance.start = event.time;
}
else {
const o = this.handleActiveInstances(event, lastInstance, activeInstanceId, eventId, activeInstances, allowMerge, allowZeroGaps);
activeInstanceId = o.activeInstanceId;
if (o.returnInstance) {
let newInstance = o.returnInstance;
if (omitOriginalStartEnd) {
newInstance = { ...newInstance };
newInstance.originalStart = undefined;
newInstance.originalEnd = undefined;
}
returnInstances.push(newInstance);
}
}
previousActive = true;
previousNegative = false;
}
else {
// No instances are active
if (lastInstance && previousActive) {
lastInstance.end = event.time;
}
else if (Object.keys(negativeInstances).length && !event.data.notANegativeInstance) {
// There is a negative instance running
const o = this.handleActiveInstances(event, lastInstance, negativeInstanceId, eventId, negativeInstances, allowMerge, allowZeroGaps);
negativeInstanceId = o.activeInstanceId;
if (o.returnInstance) {
const newInstance = {
...o.returnInstance,
start: o.returnInstance.end ?? 0,
end: o.returnInstance.start,
};
if (omitOriginalStartEnd) {
newInstance.originalStart = undefined;
newInstance.originalEnd = undefined;
}
returnInstances.push(newInstance);
}
previousNegative = true;
}
previousActive = false;
}
}
for (const instance of returnInstances) {
if (instance.end !== null && instance.end < instance.start) {
// Don't allow negative durations, set it to zero instead:
instance.end = instance.start;
}
}
return returnInstances;
}
handleActiveInstances(event, lastInstance, activeInstanceId, eventId, activeInstances, allowMerge, allowZeroGaps = false) {
let returnInstance = null;
if (!allowMerge &&
event.value &&
lastInstance &&
lastInstance.end === null &&
activeInstanceId !== null &&
activeInstanceId !== eventId) {
// Start a new instance:
lastInstance.end = event.time;
returnInstance = {
id: this.resolvedTimeline.getInstanceId(),
start: event.time,
end: null,
references: event.references,
originalEnd: event.data.instance.originalEnd,
originalStart: event.data.instance.originalStart,
};
activeInstanceId = eventId;
}
else if (!allowMerge && !event.value && lastInstance && activeInstanceId === eventId) {
// The active instance stopped playing, but another is still playing
const latestInstance = (0, lib_1.reduceObj)(activeInstances, (memo, instanceEvent, id) => {
if (memo === null || memo.event.time < instanceEvent.time) {
return {
event: instanceEvent,
id: id,
};
}
return memo;
}, null);
if (latestInstance) {
// Restart that instance now:
lastInstance.end = event.time;
returnInstance = {
id: (0, instance_1.isInstanceId)(eventId)
? `${eventId}_${this.resolvedTimeline.getInstanceId()}`
: `@${eventId}_${this.resolvedTimeline.getInstanceId()}`,
start: event.time,
end: null,
references: latestInstance.event.references,
originalEnd: event.data.instance.originalEnd,
originalStart: event.data.instance.originalStart,
};
activeInstanceId = latestInstance.id;
}
}
else if (allowMerge && !allowZeroGaps && lastInstance && lastInstance.end === event.time) {
// The previously running ended just now
// resume previous instance:
lastInstance.end = null;
lastInstance.references = (0, reference_1.joinReferences)(lastInstance.references, event.references);
(0, cap_1.addCapsToResuming)(lastInstance, event.data.instance.caps);
}
else if (!lastInstance || lastInstance.end !== null) {
// There is no previously running instance
// Start a new instance:
returnInstance = {
id: (0, instance_1.isInstanceId)(eventId) ? eventId : `@${eventId}`,
start: event.time,
end: null,
references: event.references,
caps: event.data.instance.caps,
originalEnd: event.data.instance.originalEnd,
originalStart: event.data.instance.originalStart,
};
activeInstanceId = eventId;
}
else {
// There is already a running instance
lastInstance.references = (0, reference_1.joinReferences)(lastInstance.references, event.references);
(0, cap_1.addCapsToResuming)(lastInstance, event.data.instance.caps);
}
if (lastInstance?.caps && !lastInstance.caps.length)
delete lastInstance.caps;
if (returnInstance &&
lastInstance &&
lastInstance.start === lastInstance.end &&
lastInstance.end === returnInstance.start) {
// replace the previous zero-length with this one instead
lastInstance.id = returnInstance.id;
lastInstance.start = returnInstance.start;
lastInstance.end = returnInstance.end;
lastInstance.references = returnInstance.references;
lastInstance.caps = returnInstance.caps;
lastInstance.originalStart = returnInstance.originalStart;
lastInstance.originalEnd = returnInstance.originalEnd;
returnInstance = null;
}
return {
activeInstanceId,
returnInstance,
};
}
/**
* Clean up instances, join overlapping etc..
* @param instances
*/
cleanInstances(instances, allowMerge, allowZeroGaps = false) {
// First, optimize for certain common situations:
if (instances.length === 0)
return [];
if (instances.length === 1)
return instances;
const events = [];
for (const instance of instances) {
events.push({
time: instance.start,
value: true,
data: { instance: instance },
references: instance.references,
});
if (instance.end !== null) {
events.push({
time: instance.end,
value: false,
data: { instance: instance },
references: instance.references,
});
}
}
return this.convertEventsToInstances(events, allowMerge, allowZeroGaps);
}
/**
* Cap instances so that they are within their parentInstances
* @param instances
* @param cappingInstances
*/
capInstances(instances, cappingInstances, allowZeroGaps = true) {
if ((0, reference_1.isReference)(cappingInstances) || cappingInstances === null)
return instances;
let returnInstances = [];
for (let i = 0; i < instances.length; i++) {
const instanceOrg = instances[i];
const addedInstanceTimes = new Set();
for (let j = 0; j < cappingInstances.length; j++) {
const capInstance = cappingInstances[j];
// First, check if the instance crosses the parent at all:
if (instanceOrg.start <= (capInstance.end ?? Infinity) &&
(instanceOrg.end ?? Infinity) >= capInstance.start) {
const instance = this.capInstance(instanceOrg, capInstance);
if (instance.start >= capInstance.start &&
(instance.end ?? Infinity) <= (capInstance.end ?? Infinity)) {
// The instance is within the parent
if (instance.start === instance.end && addedInstanceTimes.has(instance.start)) {
// Don't add zero-length instances if there are already is instances covering that time
}
else {
instance.references = (0, reference_1.joinReferences)(instance.references, capInstance.references);
returnInstances.push(instance);
addedInstanceTimes.add(instance.start);
if (instance.end)
addedInstanceTimes.add(instance.end);
}
}
}
}
}
returnInstances.sort((a, b) => a.start - b.start);
// Ensure unique ids:
const ids = {};
for (const instance of returnInstances) {
// tslint:disable-next-line
if (ids[instance.id] !== undefined) {
instance.id = `${instance.id}${++ids[instance.id]}`;
}
else {
ids[instance.id] = 0;
}
}
// Clean up the instances, to remove duplicates
returnInstances = this.cleanInstances(returnInstances, true, allowZeroGaps);
return returnInstances;
}
capInstance(instanceOrg, capInstance) {
const instance = { ...instanceOrg };
// Cap start
if (instance.start < capInstance.start) {
this.setInstanceStartTime(instance, capInstance.start);
}
// Cap end
if ((instance.end ?? Infinity) > (capInstance.end ?? Infinity)) {
this.setInstanceEndTime(instance, capInstance.end);
}
return instance;
}
setInstanceEndTime(instance, endTime) {
instance.originalEnd = instance.originalEnd ?? instance.end;
instance.end = endTime;
}
setInstanceStartTime(instance, startTime) {
instance.originalStart = instance.originalStart ?? instance.start;
instance.start = startTime;
}
applyRepeatingInstances(instances, repeatTime0) {
if (repeatTime0 === null || !repeatTime0.value)
return instances;
const options = this.resolvedTimeline.options;
const repeatTime = repeatTime0.value;
const repeatedInstances = [];
for (const instance of instances) {
let startTime = Math.max(options.time - ((options.time - instance.start) % repeatTime), instance.start);
let endTime = instance.end === null ? null : instance.end + (startTime - instance.start);
const cap = (instance.caps
? instance.caps.find((cap) => instance.references.indexOf(`@${cap.id}`) !== -1)
: null) ?? null;
const limit = options.limitCount ?? 2;
for (let i = 0; i < limit; i++) {
if (options.limitTime && startTime >= options.limitTime)
break;
const cappedStartTime = cap ? Math.max(cap.start, startTime) : startTime;
const cappedEndTime = cap && cap.end !== null && endTime !== null ? Math.min(cap.end, endTime) : endTime;
if ((cappedEndTime ?? Infinity) > cappedStartTime) {
repeatedInstances.push({
id: this.resolvedTimeline.getInstanceId(),
start: cappedStartTime,
end: cappedEndTime,
references: (0, reference_1.joinReferences)(instance.references, repeatTime0.references, `@${instance.id}`),
});
}
startTime += repeatTime;
if (endTime !== null)
endTime += repeatTime;
}
}
return this.cleanInstances(repeatedInstances, false);
}
}
exports.InstanceHandler = InstanceHandler;
//# sourceMappingURL=InstanceHandler.js.map