UNPKG

superfly-timeline

Version:

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

449 lines 19 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReferenceHandler = void 0; const lib_1 = require("./lib/lib"); const cap_1 = require("./lib/cap"); const event_1 = require("./lib/event"); const reference_1 = require("./lib/reference"); const expression_1 = require("./lib/expression"); const operator_1 = require("./lib/operator"); class ReferenceHandler { constructor(resolvedTimeline, instance) { this.resolvedTimeline = resolvedTimeline; this.instance = instance; this.operateApplyParentInstance = (a, b) => { if (a === null || b === null) return null; return { value: a.value + b.value, references: (0, reference_1.joinReferences)(a.references, b.references), }; }; } /** * Look up a reference on the timeline * Return values: * TimelineObjectInstance[]: Instances on the timeline where the reference expression is true * ValueWithReference: A singular value which can be combined arithmetically with Instances * null: Means "something is invalid", an null-value will always return null when combined with other values * * @param obj * @param expr * @param context */ lookupExpression(obj, expr, context) { if (expr === null) return { result: null, allReferences: [] }; if (typeof expr === 'string' && (0, expression_1.isNumericExpr)(expr)) { return { result: { value: parseFloat(expr), references: [], }, allReferences: [], }; } else if (typeof expr === 'number') { return { result: { value: expr, references: [], }, allReferences: [], }; } else if (typeof expr === 'string') { expr = expr.trim(); const exprLower = expr.toLowerCase(); if (exprLower === 'true') { return { result: { value: 0, references: [], }, allReferences: [], }; } else if (exprLower === 'false') { return { result: null, allReferences: [], }; } // Look up string let referencedObjs = []; let ref = context; let rest = ''; let objIdsToReference = []; const allReferences = []; let referenceIsOk = false; // Match id, example: "#objectId.start" const m = /^[^\w#.$]*#([^.]+)(.*)/.exec(expr); if (m) { let id = m[1]; rest = m[2]; // Special case: if the id is '#parent', use the parent id if (id === '#parent') id = obj.resolved.parentId || id; referenceIsOk = true; objIdsToReference = [id]; allReferences.push(`#${id}`); } else { // Match class, example: ".className.start" const m = /^\W*\.([^.]+)(.*)/.exec(expr); if (m) { const className = m[1]; rest = m[2]; referenceIsOk = true; objIdsToReference = this.resolvedTimeline.getClassObjects(className) ?? []; allReferences.push(`.${className}`); } else { // Match layer, example: "$layer" const m = /^\W*\$([^.]+)(.*)/.exec(expr); if (m) { const layer = m[1]; rest = m[2]; referenceIsOk = true; objIdsToReference = this.resolvedTimeline.getLayerObjects(layer) ?? []; allReferences.push(`$${layer}`); } } } for (let i = 0; i < objIdsToReference.length; i++) { const refObjId = objIdsToReference[i]; if (refObjId === obj.id) { // Looks like the object is referencing itself! if (obj.resolved.resolving) { obj.resolved.isSelfReferencing = true; } } else { const refObj = this.resolvedTimeline.getObject(refObjId); if (refObj) referencedObjs.push(refObj); } } if (!referenceIsOk) { return { result: null, allReferences: [] }; } if (obj.resolved.isSelfReferencing) { // Exclude any self-referencing objects: referencedObjs = referencedObjs.filter((refObj) => { return !refObj.resolved.isSelfReferencing; }); } if (referencedObjs.length) { if (/start/.exec(rest)) ref = 'start'; else if (/end/.exec(rest)) ref = 'end'; else if (/duration/.exec(rest)) ref = 'duration'; if (ref === 'duration') { // Duration refers to the first object on the resolved timeline return this.lookupReferencedObjsDuration(obj, referencedObjs, allReferences); } else if (ref === 'start') { return this.lookupReferencedObjs(obj, referencedObjs, allReferences, false, false); } else if (ref === 'end') { return this.lookupReferencedObjs(obj, referencedObjs, allReferences, true, true); } else { /* istanbul ignore next */ (0, lib_1.assertNever)(ref); } } return { result: [], allReferences: allReferences }; } else if (!expr) { return { result: null, allReferences: [] }; } else { // expr is an expressionObj return this.lookupExpressionObj(obj, context, expr); } } applyParentInstances(parentInstances, value) { return this.operateOnArrays(parentInstances, value, this.operateApplyParentInstance); } /** * Perform an action on 2 arrays. Behaves somewhat like the ".*"-operator in Matlab * @param array0 * @param array1 * @param operate */ operateOnArrays(array0, array1, operate) { if (array0 === null || array1 === null) return null; if ((0, reference_1.isReference)(array0) && (0, reference_1.isReference)(array1)) { return operate(array0, array1); } const result = []; const minLength = Math.min((0, lib_1.isArray)(array0) ? array0.length : Infinity, (0, lib_1.isArray)(array1) ? array1.length : Infinity); for (let i = 0; i < minLength; i++) { const a = (0, lib_1.isArray)(array0) ? array0[i] : { id: '@', start: array0.value, end: array0.value, references: array0.references }; const b = (0, lib_1.isArray)(array1) ? array1[i] : { id: '@', start: array1.value, end: array1.value, references: array1.references }; const start = a.isFirst ? { value: a.start, references: a.references } : b.isFirst ? { value: b.start, references: b.references } : operate({ value: a.start, references: (0, reference_1.joinReferences)(a.references, a.id === '@' ? [] : `@${a.id}`) }, { value: b.start, references: (0, reference_1.joinReferences)(b.references, b.id === '@' ? [] : `@${b.id}`) }); const end = a.isFirst ? a.end !== null ? { value: a.end, references: a.references } : null : b.isFirst ? b.end !== null ? { value: b.end, references: b.references } : null : operate(a.end !== null ? { value: a.end, references: (0, reference_1.joinReferences)(a.references, a.id === '@' ? [] : `@${a.id}`), } : null, b.end !== null ? { value: b.end, references: (0, reference_1.joinReferences)(b.references, b.id === '@' ? [] : `@${b.id}`), } : null); if (start !== null) { result.push({ id: this.resolvedTimeline.getInstanceId(), start: start.value, end: end === null ? null : end.value, references: (0, reference_1.joinReferences)(start.references, end !== null ? end.references : []), caps: (0, cap_1.joinCaps)(a.caps, b.caps), }); } } return this.instance.cleanInstances(result, false); } /** * Look up the referenced objects (in the context of a duration-reference) */ lookupReferencedObjsDuration(obj, referencedObjs, allReferences) { const instanceDurations = []; for (let i = 0; i < referencedObjs.length; i++) { const referencedObj = referencedObjs[i]; // Ensure that the referenced object is resolved. // Note: This is where referenced object(s) are recursively resolved this.resolvedTimeline.resolveTimelineObj(referencedObj); if (referencedObj.resolved.resolvedReferences) { if (obj.resolved.isSelfReferencing && referencedObj.resolved.isSelfReferencing) { // If the querying object is self-referencing, exclude any other self-referencing objects, // ignore the object } else { const firstInstance = referencedObj.resolved.instances[0]; if (firstInstance) { const duration = firstInstance.end !== null ? firstInstance.end - firstInstance.start : null; if (duration !== null) { instanceDurations.push({ value: duration, references: (0, reference_1.joinReferences)([`#${referencedObj.id}`], firstInstance.references), }); } } } } } let firstDuration = null; for (let i = 0; i < instanceDurations.length; i++) { const d = instanceDurations[i]; if (firstDuration === null || d.value < firstDuration.value) firstDuration = d; } return { result: firstDuration, allReferences: allReferences }; } /** * Look up the referenced objects */ lookupReferencedObjs(obj, referencedObjs, allReferences, invert, ignoreFirstIfZero) { let referencedInstances = []; for (let i = 0; i < referencedObjs.length; i++) { const referencedObj = referencedObjs[i]; // Ensure that the referenced object is resolved. // Note: This is where referenced object(s) are recursively resolved this.resolvedTimeline.resolveTimelineObj(referencedObj); if (referencedObj.resolved.resolvedReferences) { if (obj.resolved.isSelfReferencing && referencedObj.resolved.isSelfReferencing) { // If the querying object is self-referencing, exclude any other self-referencing objects, // ignore the object } else { referencedInstances = referencedInstances.concat(referencedObj.resolved.instances); } } } if (referencedInstances.length) { if (invert) { referencedInstances = this.instance.invertInstances(referencedInstances); } else { referencedInstances = this.instance.cleanInstances(referencedInstances, true, true); } if (ignoreFirstIfZero) { const first = referencedInstances[0]; if (first && first.start === 0) { referencedInstances.splice(0, 1); } } return { result: referencedInstances, allReferences: allReferences }; } else { return { result: [], allReferences: allReferences }; } } /** * Look up an ExpressionObj */ lookupExpressionObj(obj, context, expr) { const l = this.lookupExpression(obj, expr.l, context); const r = this.lookupExpression(obj, expr.r, context); const lookupExpr = { l: l.result, o: expr.o, r: r.result, }; const allReferences = l.allReferences.concat(r.allReferences); if (lookupExpr.o === '!') { // Invert, ie discard l, invert and return r: if (lookupExpr.r && (0, lib_1.isArray)(lookupExpr.r)) { return { result: this.instance.invertInstances(lookupExpr.r), allReferences: allReferences, }; } else { // We can't invert a value return { result: lookupExpr.r, allReferences: allReferences, }; } } else if (lookupExpr.l === null || lookupExpr.r === null) { return { result: null, allReferences: allReferences }; } else if (lookupExpr.o === '&' || lookupExpr.o === '|') { const combiner = new ReferenceAndOrCombiner(this.resolvedTimeline, lookupExpr.l, lookupExpr.r, lookupExpr.o); const instances = combiner.calculateResult(); return { result: instances, allReferences: allReferences }; } else { const operate = operator_1.Operator.get(lookupExpr.o); const result = this.operateOnArrays(lookupExpr.l, lookupExpr.r, operate); return { result: result, allReferences: allReferences }; } } } exports.ReferenceHandler = ReferenceHandler; /** Helper class that deals with an And ('&') or an Or ('|') expression */ class ReferenceAndOrCombiner { constructor(resolvedTimeline, leftOperand, rightOperand, operator) { this.resolvedTimeline = resolvedTimeline; this.leftOperand = leftOperand; this.rightOperand = rightOperand; this.events = []; this.instances = []; if (operator === '&') { this.calcResult = (left, right) => !!(left && right); } else if (operator === '|') { this.calcResult = (left, right) => !!(left || right); } else { /* istanbul ignore next */ (0, lib_1.assertNever)(operator); /* istanbul ignore next */ this.calcResult = () => false; } if ((0, lib_1.isArray)(leftOperand)) this._addInstanceEvents(leftOperand, true); if ((0, lib_1.isArray)(rightOperand)) this._addInstanceEvents(rightOperand, false); this.events = (0, event_1.sortEvents)(this.events); } _addInstanceEvents(instances, left) { for (let i = 0; i < instances.length; i++) { const instance = instances[i]; if (instance.start !== instance.end) { // event doesn't actually exist... this.events.push({ left: left, time: instance.start, value: true, references: [], data: true, instance: instance, }); if (instance.end !== null) { this.events.push({ left: left, time: instance.end, value: false, references: [], data: false, instance: instance, }); } } } } calculateResult() { let leftValue = (0, reference_1.isReference)(this.leftOperand) ? !!this.leftOperand.value : false; let rightValue = (0, reference_1.isReference)(this.rightOperand) ? !!this.rightOperand.value : false; let leftInstance = null; let rightInstance = null; let resultValue = this.calcResult(leftValue, rightValue); this.updateInstance(0, resultValue, (0, reference_1.joinReferences)((0, reference_1.isReference)(this.leftOperand) ? this.leftOperand.references : [], (0, reference_1.isReference)(this.rightOperand) ? this.rightOperand.references : []), []); for (let i = 0; i < this.events.length; i++) { const e = this.events[i]; const next = this.events[i + 1]; if (e.left) { leftValue = e.value; leftInstance = e.instance; } else { rightValue = e.value; rightInstance = e.instance; } if (!next || next.time !== e.time) { const newResultValue = this.calcResult(leftValue, rightValue); const resultCaps = (leftInstance ? leftInstance.caps ?? [] : []).concat(rightInstance ? rightInstance.caps ?? [] : []); if (newResultValue !== resultValue) { this.updateInstance(e.time, newResultValue, (0, reference_1.joinReferences)(leftInstance ? leftInstance.references : [], rightInstance ? rightInstance.references : []), resultCaps); resultValue = newResultValue; } } } return this.instances; } updateInstance(time, value, references, caps) { if (value) { this.instances.push({ id: this.resolvedTimeline.getInstanceId(), start: time, end: null, references: references, caps: caps, }); } else { const lastInstance = (0, lib_1.last)(this.instances); if (lastInstance) { lastInstance.end = time; // don't update reference on end } } } } //# sourceMappingURL=ReferenceHandler.js.map