UNPKG

webglimpse

Version:

Webglimpse is a data visualization library for the web.

1,405 lines (1,396 loc) 634 kB
import moment from 'moment'; /* * Copyright (c) 2014, Metron, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // Alias for more readable access to static constants let GL = WebGLRenderingContext; function hasval(value) { // Double-equals is weird: ( undefined == null ) is true return (value != null); } function isNumber(value) { return typeof (value) === 'number'; } function isString(value) { return typeof (value) === 'string'; } function isEmpty(array) { return (array.length === 0); } function notEmpty(array) { return (array.length > 0); } function alwaysTrue() { return true; } function alwaysFalse() { return false; } function constantFn(value) { return function () { return value; }; } function log10(x) { return Math.log(x) * (1.0 / Math.LN10); } function order(x) { return Math.floor(log10(x) + 1e-12); } function clamp$1(xMin, xMax, x) { return Math.max(xMin, Math.min(xMax, x)); } function copyArray(values) { return values.slice(0); } function ensureCapacityFloat32(buffer, minNewCapacity) { // if minNewCapacity is NaN, null, or undefined, don't try to resize the buffer if (!minNewCapacity || buffer.length >= minNewCapacity) { return buffer; } else { const newCapacity = Math.max(minNewCapacity, 2 * buffer.length); return new Float32Array(newCapacity); } } function ensureCapacityUint32(buffer, minNewCapacity) { // if minNewCapacity is NaN, null, or undefined, don't try to resize the buffer if (!minNewCapacity || buffer.length >= minNewCapacity) { return buffer; } else { const newCapacity = Math.max(minNewCapacity, 2 * buffer.length); return new Uint32Array(newCapacity); } } function ensureCapacityUint16(buffer, minNewCapacity) { // if minNewCapacity is NaN, null, or undefined, don't try to resize the buffer if (!minNewCapacity || buffer.length >= minNewCapacity) { return buffer; } else { const newCapacity = Math.max(minNewCapacity, 2 * buffer.length); return new Uint16Array(newCapacity); } } let getObjectId = (function () { const keyName = 'webglimpse_ObjectId'; let nextValue = 0; return function (obj) { let value = obj[keyName]; if (!hasval(value)) { value = (nextValue++).toString(); obj[keyName] = value; } return value; }; })(); function concatLines(...lines) { return lines.join('\n'); } function parseTime_PMILLIS(time_ISO8601) { // Moment's lenient parsing is way too lenient -- but its strict parsing is a little too // strict, so we have to try several possible formats. // // We could pass in multiple formats to try all at once, but Moment's docs warn that that // can be slow. Plus, we expect some formats to be more common than others, so we can make // the common formats fast at the expense of the less common ones. // let m = moment(time_ISO8601, 'YYYY-MM-DDTHH:mm:ssZZ', true); if (m.isValid()) { return m.valueOf(); } m = moment(time_ISO8601, 'YYYY-MM-DDTHH:mm:ss.SSSZZ', true); if (m.isValid()) { return m.valueOf(); } m = moment(time_ISO8601, 'YYYY-MM-DDTHH:mm:ss.SSZZ', true); if (m.isValid()) { return m.valueOf(); } m = moment(time_ISO8601, 'YYYY-MM-DDTHH:mm:ss.SZZ', true); if (m.isValid()) { return m.valueOf(); } throw new Error('Failed to parse time-string: \'' + time_ISO8601 + '\''); } /** * Formats a timestamp from posix-millis into the format 'YYYY-MM-DDThh:mm:ss.SSSZZ' (for * example '2014-01-01T00:00:00.000Z'). * * The input value is effectively truncated (not rounded!) to milliseconds. So for example, * formatTime_ISO8601(12345.999) will return '1970-01-01T00:00:12.345Z' -- exactly the same * as for an input of 12345. * */ function formatTime_ISO8601(time_PMILLIS) { return moment(time_PMILLIS).toISOString(); } /* * Copyright (c) 2014, Metron, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ class CacheEntry { constructor(value) { this.touched = false; this.value = value; } } class Cache { constructor(helper) { this.helper = helper; this.map = {}; } value(key) { if (!this.map.hasOwnProperty(key)) { this.map[key] = new CacheEntry(this.helper.create(key)); } const en = this.map[key]; en.touched = true; return en.value; } clear() { for (const k in this.map) { if (this.map.hasOwnProperty(k)) { this.helper.dispose(this.map[k].value, k); } } this.map = {}; } remove(key) { if (this.map.hasOwnProperty(key)) { this.helper.dispose(this.map[key].value, key); delete this.map[key]; } } removeAll(keys) { for (let i = 0; i < keys.length; i++) { this.remove(keys[i]); } } retain(key) { const newMap = {}; if (this.map.hasOwnProperty(key)) { newMap[key] = this.map[key]; delete this.map[key]; } for (const k in this.map) { if (this.map.hasOwnProperty(k)) { this.helper.dispose(this.map[k].value, k); } } this.map = newMap; } retainAll(keys) { const newMap = {}; for (let i = 0; i < keys.length; i++) { const k = keys[i]; if (this.map.hasOwnProperty(k)) { newMap[k] = this.map[k]; delete this.map[k]; } } for (const k2 in this.map) { if (this.map.hasOwnProperty(k2)) { this.helper.dispose(this.map[k2].value, k2); } } this.map = newMap; } resetTouches() { for (const k in this.map) { if (this.map.hasOwnProperty(k)) { this.map[k].touched = false; } } } retainTouched() { const newMap = {}; for (const k in this.map) { if (this.map.hasOwnProperty(k)) { const en = this.map[k]; if (en.touched) { newMap[k] = this.map[k]; } else { this.helper.dispose(en.value, k); } } } this.map = newMap; } } /* * Copyright (c) 2014, Metron, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ class MultiKeyCacheEntry { constructor(keyParts, value) { this.touched = false; this.keyParts = keyParts; this.value = value; } } class MultiKeyCache { constructor(helper) { this.helper = helper; this.map = {}; } combineKeyParts(keyParts) { const esc = '\\'; const sep = ';'; const escapedEsc = esc + esc; const escapedSep = esc + sep; const escapedParts = []; for (let n = 0; n < keyParts.length; n++) { escapedParts[n] = keyParts[n].replace(esc, escapedEsc).replace(sep, escapedSep); } return escapedParts.join(sep); } value(...keyParts) { const key = this.combineKeyParts(keyParts); if (!this.map.hasOwnProperty(key)) { this.map[key] = new MultiKeyCacheEntry(keyParts, this.helper.create(keyParts)); } const en = this.map[key]; en.touched = true; return en.value; } remove(...keyParts) { const key = this.combineKeyParts(keyParts); if (this.map.hasOwnProperty(key)) { const en = this.map[key]; this.helper.dispose(en.value, en.keyParts); delete this.map[key]; } } retain(...keyParts) { const newMap = {}; const retainKey = this.combineKeyParts(keyParts); if (this.map.hasOwnProperty(retainKey)) { newMap[retainKey] = this.map[retainKey]; delete this.map[retainKey]; } for (const key in this.map) { if (this.map.hasOwnProperty(key)) { const en = this.map[key]; this.helper.dispose(en.value, en.keyParts); } } this.map = newMap; } resetTouches() { for (const key in this.map) { if (this.map.hasOwnProperty(key)) { this.map[key].touched = false; } } } retainTouched() { const newMap = {}; for (const key in this.map) { if (this.map.hasOwnProperty(key)) { const en = this.map[key]; if (en.touched) { newMap[key] = this.map[key]; } else { this.helper.dispose(en.value, en.keyParts); } } } this.map = newMap; } clear() { for (const key in this.map) { if (this.map.hasOwnProperty(key)) { const en = this.map[key]; this.helper.dispose(en.value, en.keyParts); } } this.map = {}; } } class TwoKeyCache { constructor(helper) { this.cache = new MultiKeyCache({ create: function (keyParts) { return helper.create(keyParts[0], keyParts[1]); }, dispose: function (value, keyParts) { helper.dispose(value, keyParts[0], keyParts[1]); } }); } value(keyPart1, keyPart2) { return this.cache.value(keyPart1, keyPart2); } remove(keyPart1, keyPart2) { this.cache.remove(keyPart1, keyPart2); } retain(keyPart1, keyPart2) { this.cache.retain(keyPart1, keyPart2); } resetTouches() { this.cache.resetTouches(); } retainTouched() { this.cache.retainTouched(); } clear() { this.cache.clear(); } } class ThreeKeyCache { constructor(helper) { this.cache = new MultiKeyCache({ create: function (keyParts) { return helper.create(keyParts[0], keyParts[1], keyParts[2]); }, dispose: function (value, keyParts) { helper.dispose(value, keyParts[0], keyParts[1], keyParts[2]); } }); } value(keyPart1, keyPart2, keyPart3) { return this.cache.value(keyPart1, keyPart2, keyPart3); } remove(keyPart1, keyPart2, keyPart3) { this.cache.remove(keyPart1, keyPart2, keyPart3); } retain(keyPart1, keyPart2, keyPart3) { this.cache.retain(keyPart1, keyPart2, keyPart3); } resetTouches() { this.cache.resetTouches(); } retainTouched() { this.cache.retainTouched(); } clear() { this.cache.clear(); } } class SixKeyCache { constructor(helper) { this.cache = new MultiKeyCache({ create: function (keyParts) { return helper.create(keyParts[0], keyParts[1], keyParts[2], keyParts[3], keyParts[4], keyParts[5]); }, dispose: function (value, keyParts) { helper.dispose(value, keyParts[0], keyParts[1], keyParts[2], keyParts[3], keyParts[4], keyParts[5]); } }); } value(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6) { return this.cache.value(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6); } remove(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6) { this.cache.remove(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6); } retain(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6) { this.cache.retain(keyPart1, keyPart2, keyPart3, keyPart4, keyPart5, keyPart6); } resetTouches() { this.cache.resetTouches(); } retainTouched() { this.cache.retainTouched(); } clear() { this.cache.clear(); } } class BoundsUnmodifiable { constructor(bounds) { this.bounds = bounds; } get iStart() { return this.bounds.iStart; } get jStart() { return this.bounds.jStart; } get iEnd() { return this.bounds.iEnd; } get jEnd() { return this.bounds.jEnd; } get i() { return this.bounds.i; } get j() { return this.bounds.j; } get w() { return this.bounds.w; } get h() { return this.bounds.h; } xFrac(i) { return this.bounds.xFrac(i); } yFrac(j) { return this.bounds.yFrac(j); } contains(i, j) { return this.bounds.contains(i, j); } } class Bounds { constructor() { this._iStart = 0; this._jStart = 0; this._iEnd = 0; this._jEnd = 0; this._unmod = new BoundsUnmodifiable(this); } get iStart() { return this._iStart; } get jStart() { return this._jStart; } get iEnd() { return this._iEnd; } get jEnd() { return this._jEnd; } get i() { return this._iStart; } get j() { return this._jStart; } get w() { return this._iEnd - this._iStart; } get h() { return this._jEnd - this._jStart; } get unmod() { return this._unmod; } xFrac(i) { return (i - this._iStart) / (this._iEnd - this._iStart); } yFrac(j) { return (j - this._jStart) / (this._jEnd - this._jStart); } contains(i, j) { return (this._iStart <= i && i < this._iEnd && this._jStart <= j && j < this._jEnd); } setEdges(iStart, iEnd, jStart, jEnd) { this._iStart = iStart; this._jStart = jStart; this._iEnd = iEnd; this._jEnd = jEnd; } setRect(i, j, w, h) { this.setEdges(i, i + w, j, j + h); } setBounds(bounds) { this.setEdges(bounds.iStart, bounds.iEnd, bounds.jStart, bounds.jEnd); } cropToEdges(iCropStart, iCropEnd, jCropStart, jCropEnd) { this._iStart = clamp$1(iCropStart, iCropEnd, this._iStart); this._jStart = clamp$1(jCropStart, jCropEnd, this._jStart); this._iEnd = clamp$1(iCropStart, iCropEnd, this._iEnd); this._jEnd = clamp$1(jCropStart, jCropEnd, this._jEnd); } cropToRect(iCrop, jCrop, wCrop, hCrop) { this.cropToEdges(iCrop, iCrop + wCrop, jCrop, jCrop + hCrop); } cropToBounds(cropBounds) { this.cropToEdges(cropBounds.iStart, cropBounds.iEnd, cropBounds.jStart, cropBounds.jEnd); } } function newBoundsFromRect(i, j, w, h) { const b = new Bounds(); b.setRect(i, j, w, h); return b; } function newBoundsFromEdges(iStart, iEnd, jStart, jEnd) { const b = new Bounds(); b.setEdges(iStart, iEnd, jStart, jEnd); return b; } /* * Copyright (c) 2014, Metron, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ class Color { constructor(r, g, b, a = 1) { this._r = r; this._g = g; this._b = b; this._a = a; } get r() { return this._r; } get g() { return this._g; } get b() { return this._b; } get a() { return this._a; } get cssString() { return 'rgba(' + Math.round(255 * this._r) + ',' + Math.round(255 * this._g) + ',' + Math.round(255 * this._b) + ',' + this._a + ')'; } get rgbaString() { return '' + Math.round(255 * this._r) + ',' + Math.round(255 * this._g) + ',' + Math.round(255 * this._b) + ',' + Math.round(255 * this._a); } withAlphaTimes(aFactor) { return new Color(this._r, this._g, this._b, aFactor * this._a); } } function darker(color, factor) { return new Color(color.r * factor, color.g * factor, color.b * factor, color.a); } function rgba(r, g, b, a) { return new Color(r, g, b, a); } function rgb(r, g, b) { return new Color(r, g, b, 1); } function sameColor(c1, c2) { if (c1 === c2) { return true; } if (!hasval(c1) || !hasval(c2)) { return false; } return (c1.r === c2.r && c1.g === c2.g && c1.b === c2.b && c1.a === c2.a); } function parseRgba(rgbaString) { const tokens = rgbaString.split(',', 4); return new Color(parseInt(tokens[0], 10) / 255, parseInt(tokens[1], 10) / 255, parseInt(tokens[2], 10) / 255, parseInt(tokens[3], 10) / 255); } /** * Creates a Color object based on a CSS color string. Supports the following notations: * - hex * - rgb/rgba * - hsl/hsla * - named colors * Behavior is undefined for strings that are not in one of the listed notations. * * Optional alphaOverride provided for forcing value of rbga alpha. * * Note that different browsers may use different color values for the named colors. * */ let parseCssColor = (function () { const canvas = document.createElement('canvas'); canvas.width = 1; canvas.height = 1; const g = canvas.getContext('2d', { willReadFrequently: true }); return function (cssColorString, alphaOverride) { g.clearRect(0, 0, 1, 1); g.fillStyle = cssColorString; g.fillRect(0, 0, 1, 1); const rgbaData = g.getImageData(0, 0, 1, 1).data; const R = rgbaData[0] / 255; const G = rgbaData[1] / 255; const B = rgbaData[2] / 255; const A = hasval(alphaOverride) ? alphaOverride : rgbaData[3] / 255; return rgba(R, G, B, A); }; })(); function gray(brightness) { return new Color(brightness, brightness, brightness, 1); } // XXX: Make final let black = rgb(0, 0, 0); let white = rgb(1, 1, 1); let red = rgb(1, 0, 0); let green = rgb(0, 1, 0); let blue = rgb(0, 0, 1); let cyan = rgb(0, 1, 1); let magenta = rgb(1, 0, 1); let yellow = rgb(1, 1, 0); let periwinkle = rgb(0.561, 0.561, 0.961); // see: http://www.cs.rit.edu/usr/local/pub/wrc/graphics/doc/opengl/books/blue/glOrtho.html // see: http://www.songho.ca/opengl/gl_projectionmatrix.html#ortho function glOrtho(left, right, bottom, top, near, far) { const tx = (right + left) / (right - left); const ty = (top + bottom) / (top - bottom); const tz = (far + near) / (far - near); // GL ES (and therefore WebGL) requires matrices to be column-major return new Float32Array([ 2 / (right - left), 0, 0, 0, 0, 2 / (top - bottom), 0, 0, 0, 0, -2 / (far - near), 0, -tx, -ty, -tz, 1 ]); } function glOrthoViewport(viewport) { return glOrtho(-0.5, viewport.w - 0.5, -0.5, viewport.h - 0.5, -1, 1); } function glOrthoAxis(axis) { return glOrtho(axis.xAxis.vMin, axis.xAxis.vMax, axis.yAxis.vMin, axis.yAxis.vMax, -1, 1); } /* * Copyright (c) 2014, Metron, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ class Notification { constructor() { this._listeners = new OrderedSet([], getObjectId, false); this._deferring = false; this._deferred = []; } on(listener) { if (this._deferring) { const self = this; this._deferred.push(function () { self._listeners.add(listener); }); } else { this._listeners.add(listener); } } off(listener) { if (this._deferring) { const self = this; this._deferred.push(function () { self._listeners.removeValue(listener); }); } else { this._listeners.removeValue(listener); } } dispose() { this._listeners.removeAll(); } fire() { this._deferring = true; try { for (let n = 0; n < this._listeners.length; n++) { const consumed = this._listeners.valueAt(n)(); if (consumed) { return consumed; } } return false; } finally { if (this._deferred.length > 0) { for (let n = 0; n < this._deferred.length; n++) { this._deferred[n](); } this._deferred = []; } this._deferring = false; } } } class Notification1 { constructor() { this._listeners = new OrderedSet([], getObjectId, false); this._deferring = false; this._deferred = []; } on(listener) { if (this._deferring) { const self = this; this._deferred.push(function () { self._listeners.add(listener); }); } else { this._listeners.add(listener); } } off(listener) { if (this._deferring) { const self = this; this._deferred.push(function () { self._listeners.removeValue(listener); }); } else { this._listeners.removeValue(listener); } } dispose() { this._listeners.removeAll(); } fire(a) { this._deferring = true; try { for (let n = 0; n < this._listeners.length; n++) { const consumed = this._listeners.valueAt(n)(a); if (consumed) { return consumed; } } return false; } finally { if (this._deferred.length > 0) { for (let n = 0; n < this._deferred.length; n++) { this._deferred[n](); } this._deferred = []; } this._deferring = false; } } } class Notification2 { constructor() { this._listeners = new OrderedSet([], getObjectId, false); this._deferring = false; this._deferred = []; } on(listener) { if (this._deferring) { const self = this; this._deferred.push(function () { self._listeners.add(listener); }); } else { this._listeners.add(listener); } } off(listener) { if (this._deferring) { const self = this; this._deferred.push(function () { self._listeners.removeValue(listener); }); } else { this._listeners.removeValue(listener); } } dispose() { this._listeners.removeAll(); } fire(a, b) { this._deferring = true; try { for (let n = 0; n < this._listeners.length; n++) { const consumed = this._listeners.valueAt(n)(a, b); if (consumed) { return consumed; } } return false; } finally { if (this._deferred.length > 0) { for (let n = 0; n < this._deferred.length; n++) { this._deferred[n](); } this._deferred = []; } this._deferring = false; } } } class Notification3 { constructor() { this._listeners = new OrderedSet([], getObjectId, false); this._deferring = false; this._deferred = []; } on(listener) { if (this._deferring) { const self = this; this._deferred.push(function () { self._listeners.add(listener); }); } else { this._listeners.add(listener); } } off(listener) { if (this._deferring) { const self = this; this._deferred.push(function () { self._listeners.removeValue(listener); }); } else { this._listeners.removeValue(listener); } } dispose() { this._listeners.removeAll(); } fire(a, b, c) { this._deferring = true; try { for (let n = 0; n < this._listeners.length; n++) { const consumed = this._listeners.valueAt(n)(a, b, c); if (consumed) { return consumed; } } return false; } finally { if (this._deferred.length > 0) { for (let n = 0; n < this._deferred.length; n++) { this._deferred[n](); } this._deferred = []; } this._deferring = false; } } } /* * Copyright (c) 2014, Metron, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ function requireString(s) { if (isString(s)) { return s; } else { throw new Error('Expected a string, but value is ' + s); } } class OrderedSet { constructor(values = [], idFn = getObjectId, useNotifications = true) { this._idOf = idFn; this._valuesArray = copyArray(values); this._ids = []; this._indexes = {}; this._valuesMap = {}; for (let n = 0; n < this._valuesArray.length; n++) { const value = this._valuesArray[n]; const id = requireString(this._idOf(value)); this._ids[n] = id; this._indexes[id] = n; this._valuesMap[id] = value; } if (useNotifications) { this._valueAdded = new Notification2(); this._valueMoved = new Notification3(); this._valueRemoved = new Notification2(); } } get valueAdded() { return this._valueAdded; } get valueMoved() { return this._valueMoved; } get valueRemoved() { return this._valueRemoved; } get length() { return this._valuesArray.length; } get isEmpty() { return (this._valuesArray.length === 0); } toArray() { return copyArray(this._valuesArray); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedSet is undefined. */ every(callbackFn, thisArg) { return this._valuesArray.every(callbackFn, thisArg); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedSet is undefined. */ some(callbackFn, thisArg) { return this._valuesArray.some(callbackFn, thisArg); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedSet is undefined. */ forEach(callbackFn, thisArg) { this._valuesArray.forEach(callbackFn, thisArg); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedSet is undefined. */ map(callbackFn, thisArg) { return this._valuesArray.map(callbackFn, thisArg); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedSet is undefined. */ filter(callbackFn, thisArg) { return this._valuesArray.filter(callbackFn, thisArg); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedSet is undefined. */ reduce(callbackFn, initialValue) { return this._valuesArray.reduce(callbackFn, initialValue); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedSet is undefined. */ reduceRight(callbackFn, initialValue) { return this._valuesArray.reduceRight(callbackFn, initialValue); } idAt(index) { return this._ids[index]; } valueAt(index) { return this._valuesArray[index]; } indexFor(id) { return (isString(id) ? this._indexes[id] : undefined); } valueFor(id) { return (isString(id) ? this._valuesMap[id] : undefined); } idOf(value) { return requireString(this._idOf(value)); } indexOf(value) { return this._indexes[requireString(this._idOf(value))]; } hasValue(value) { return this.hasId(requireString(this._idOf(value))); } hasValues(values) { for (let n = 0; n < values.length; n++) { if (!this.hasValue(values[n])) { return false; } } return true; } hasId(id) { return (isString(id) && hasval(this._valuesMap[id])); } hasIds(ids) { for (let n = 0; n < ids.length; n++) { if (!this.hasId(ids[n])) { return false; } } return true; } add(value, index, moveIfExists) { index = (hasval(index) ? index : this._valuesArray.length); if (!hasval(moveIfExists)) { moveIfExists = false; } this._add(value, index, moveIfExists); } addAll(values, index, moveIfExists) { index = (hasval(index) ? index : this._valuesArray.length); if (!hasval(moveIfExists)) { moveIfExists = false; } for (let n = 0; n < values.length; n++) { const actualIndex = this._add(values[n], index, moveIfExists); index = actualIndex + 1; } } _add(value, newIndex, moveIfExists) { const id = requireString(this._idOf(value)); const oldIndex = this._indexes[id]; if (!hasval(oldIndex)) { this._ids.splice(newIndex, 0, id); this._valuesArray.splice(newIndex, 0, value); this._valuesMap[id] = value; for (let n = newIndex; n < this._ids.length; n++) { this._indexes[this._ids[n]] = n; } if (this._valueAdded) { this._valueAdded.fire(value, newIndex); } } else if (newIndex !== oldIndex && moveIfExists) { this._ids.splice(oldIndex, 1); this._valuesArray.splice(oldIndex, 1); if (newIndex > oldIndex) { this._ids.splice(newIndex, 0, id); this._valuesArray.splice(newIndex, 0, value); for (let n = oldIndex; n <= newIndex; n++) { this._indexes[this._ids[n]] = n; } } else { this._ids.splice(newIndex, 0, id); this._valuesArray.splice(newIndex, 0, value); for (let n = newIndex; n <= oldIndex; n++) { this._indexes[this._ids[n]] = n; } } if (this._valueMoved) { this._valueMoved.fire(value, oldIndex, newIndex); } } else { newIndex = oldIndex; } // Return the actual insertion index -- may differ from the originally // requested index if an existing value had to be moved return newIndex; } removeValue(value) { this.removeId(requireString(this._idOf(value))); } removeId(id) { if (isString(id)) { const index = this._indexes[id]; if (hasval(index)) { this._remove(id, index); } } } removeIndex(index) { const id = this._ids[index]; if (isString(id)) { this._remove(id, index); } } removeAll() { // Remove from last to first, to minimize index shifting for (let n = this._valuesArray.length - 1; n >= 0; n--) { const id = this._ids[n]; this._remove(id, n); } } retainValues(values) { const idsToRetain = {}; for (let n = 0; n < values.length; n++) { const id = this._idOf(values[n]); if (isString(id)) { idsToRetain[id] = true; } } this._retain(idsToRetain); } retainIds(ids) { const idsToRetain = {}; for (let n = 0; n < ids.length; n++) { const id = ids[n]; if (isString(id)) { idsToRetain[id] = true; } } this._retain(idsToRetain); } retainIndices(indices) { const idsToRetain = {}; for (let n = 0; n < indices.length; n++) { const id = this._ids[indices[n]]; idsToRetain[id] = true; } this._retain(idsToRetain); } _retain(ids) { // Remove from last to first, to minimize index shifting for (let n = this._valuesArray.length - 1; n >= 0; n--) { const id = this._ids[n]; if (!ids.hasOwnProperty(id)) { this._remove(id, n); } } } _remove(id, index) { const value = this._valuesArray[index]; this._ids.splice(index, 1); this._valuesArray.splice(index, 1); delete this._indexes[id]; delete this._valuesMap[id]; for (let n = index; n < this._ids.length; n++) { this._indexes[this._ids[n]] = n; } if (this._valueRemoved) { this._valueRemoved.fire(value, index); } } } class OrderedStringSet { constructor(values = [], useNotifications = true) { this._valuesArray = []; this._indexes = {}; for (let n = 0; n < values.length; n++) { const value = requireString(values[n]); this._valuesArray[n] = value; this._indexes[value] = n; } if (useNotifications) { this._valueAdded = new Notification2(); this._valueMoved = new Notification3(); this._valueRemoved = new Notification2(); } } get valueAdded() { return this._valueAdded; } get valueMoved() { return this._valueMoved; } get valueRemoved() { return this._valueRemoved; } get length() { return this._valuesArray.length; } get isEmpty() { return (this._valuesArray.length === 0); } toArray() { return copyArray(this._valuesArray); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedStringSet is undefined. */ every(callbackFn, thisArg) { return this._valuesArray.every(callbackFn, thisArg); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedStringSet is undefined. */ some(callbackFn, thisArg) { return this._valuesArray.some(callbackFn, thisArg); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedStringSet is undefined. */ forEach(callbackFn, thisArg) { this._valuesArray.forEach(callbackFn, thisArg); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedStringSet is undefined. */ map(callbackFn, thisArg) { return this._valuesArray.map(callbackFn, thisArg); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedStringSet is undefined. */ filter(callbackFn, thisArg) { return this._valuesArray.filter(callbackFn, thisArg); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedStringSet is undefined. */ reduce(callbackFn, initialValue) { return this._valuesArray.reduce(callbackFn, initialValue); } /** * The callback should not modify its array arg; if it does, the subsequent behavior * of this OrderedStringSet is undefined. */ reduceRight(callbackFn, initialValue) { return this._valuesArray.reduceRight(callbackFn, initialValue); } valueAt(index) { return this._valuesArray[index]; } indexOf(value) { return (isString(value) ? this._indexes[value] : undefined); } hasValue(value) { return (isString(value) && hasval(this._indexes[value])); } hasValues(values) { for (let n = 0; n < values.length; n++) { if (!this.hasValue(values[n])) { return false; } } return true; } add(value, index, moveIfExists) { index = (hasval(index) ? index : this._valuesArray.length); if (!hasval(moveIfExists)) { moveIfExists = false; } this._add(value, index, moveIfExists); } addAll(values, index, moveIfExists) { index = (hasval(index) ? index : this._valuesArray.length); if (!hasval(moveIfExists)) { moveIfExists = false; } for (let n = 0; n < values.length; n++) { const actualIndex = this._add(values[n], index, moveIfExists); index = actualIndex + 1; } } _add(value, newIndex, moveIfExists) { requireString(value); const oldIndex = this._indexes[value]; if (!hasval(oldIndex)) { this._valuesArray.splice(newIndex, 0, value); for (let n = newIndex; n < this._valuesArray.length; n++) { this._indexes[this._valuesArray[n]] = n; } if (this._valueAdded) { this._valueAdded.fire(value, newIndex); } } else if (newIndex !== oldIndex && moveIfExists) { this._valuesArray.splice(oldIndex, 1); if (newIndex > oldIndex) { this._valuesArray.splice(newIndex, 0, value); for (let n = oldIndex; n <= newIndex; n++) { this._indexes[this._valuesArray[n]] = n; } } else { this._valuesArray.splice(newIndex, 0, value); for (let n = newIndex; n <= oldIndex; n++) { this._indexes[this._valuesArray[n]] = n; } } if (this._valueMoved) { this._valueMoved.fire(value, oldIndex, newIndex); } } else { newIndex = oldIndex; } // Return the actual insertion index -- may differ from the originally // requested index if an existing value had to be moved return newIndex; } removeValue(value) { if (isString(value)) { const index = this._indexes[value]; if (hasval(index)) { this._remove(value, index); } } } removeIndex(index) { const value = this._valuesArray[index]; if (isString(value)) { this._remove(value, index); } } removeAll() { // Remove from last to first, to minimize index shifting for (let n = this._valuesArray.length - 1; n >= 0; n--) { const value = this._valuesArray[n]; this._remove(value, n); } } retainValues(values) { const valuesToRetain = {}; for (let n = 0; n < values.length; n++) { const value = values[n]; if (isString(value)) { valuesToRetain[value] = true; } } this._retain(valuesToRetain); } retainIndices(indices) { const valuesToRetain = {}; for (let n = 0; n < indices.length; n++) { const value = this._valuesArray[indices[n]]; valuesToRetain[value] = true; } this._retain(valuesToRetain); } _retain(values) { // Remove from last to first, to minimize index shifting for (let n = this._valuesArray.length - 1; n >= 0; n--) { const value = this._valuesArray[n]; if (!values.hasOwnProperty(value)) { this._remove(value, n); } } } _remove(value, index) { this._valuesArray.splice(index, 1); delete this._indexes[value]; for (let n = index; n < this._valuesArray.length; n++) { this._indexes[this._valuesArray[n]] = n; } if (this._valueRemoved) { this._valueRemoved.fire(value, index); } } } /* * Copyright (c) 2014, Metron, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code mus