UNPKG

superfly-timeline

Version:

Resolver for defining objects with temporal boolean logic relationships on a timeline

371 lines 16.7 kB
"use strict"; 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