UNPKG

reign

Version:

A persistent, typed-objects implementation.

632 lines (573 loc) 20.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseArray = exports.MIN_TYPE_ID = undefined; exports.make = make; var _backing = require("backing"); var _backing2 = _interopRequireDefault(_backing); var _ = require("../"); var _util = require("../../util"); var _2 = require("../../"); var _symbols = require("../../symbols"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const MIN_TYPE_ID = exports.MIN_TYPE_ID = Math.pow(2, 20) * 4; class BaseArray extends _.TypedObject { /** * Return the length of the array. */ get length() { // Issue 252 return this[_symbols.$Backing].getUint32(this[_symbols.$Address] + 8); } /** * Visit every item in the typed array. */ forEach(visitor) { // Issue 252 const ElementType = this[_symbols.$ElementType]; // Issue 252 const backing = this[_symbols.$Backing]; // Issue 252 const address = this[_symbols.$Address]; const length = backing.getUint32(address + 8); let current = backing.getFloat64(address); for (let i = 0; i < length; i++) { visitor(ElementType.load(backing, current), i, this); current += this.BYTES_PER_ELEMENT; } return this; } /** * Map over every item in the typed array and return a new `Array` containing the result. */ map(visitor) { // Issue 252 const ElementType = this[_symbols.$ElementType]; // Issue 252 const backing = this[_symbols.$Backing]; // Issue 252 const address = this[_symbols.$Address]; const length = backing.getUint32(address + 8); const array = new Array(length); let current = backing.getFloat64(address); for (let i = 0; i < length; i++) { array[i] = visitor(ElementType.load(backing, current), i, this); current += this.BYTES_PER_ELEMENT; } return array; } /** * Filter the items in the array and return a new array containing the results. */ filter(filterer) { // Issue 252 const ElementType = this[_symbols.$ElementType]; // Issue 252 const backing = this[_symbols.$Backing]; // Issue 252 const address = this[_symbols.$Address]; const length = backing.getUint32(address + 8); const array = []; let current = backing.getFloat64(address); for (let i = 0; i < length; i++) { const item = ElementType.load(backing, current); if (filterer(item, i, this)) { array.push(item); } current += this.BYTES_PER_ELEMENT; } return array; } /** * Applies a function against an accumulator and each value of the array * (from left-to-right) to reduce it to a single value. */ reduce(reducer, initialValue) { // Issue 252 const ElementType = this[_symbols.$ElementType]; // Issue 252 const backing = this[_symbols.$Backing]; // Issue 252 const address = this[_symbols.$Address]; const length = backing.getUint32(address + 8); if (length === 0) { return initialValue; } let result = initialValue; let current = backing.getFloat64(address); let index = 0; if (initialValue === undefined) { initialValue = ElementType.load(backing, current); current += this.BYTES_PER_ELEMENT; index = 1; } for (; index < length; index++) { result = reducer(initialValue, ElementType.load(backing, current), index, this); current += this.BYTES_PER_ELEMENT; } return result; } /** * Return a representation of the array which can be encoded as JSON. */ toJSON() { // Issue 252 const ElementType = this[_symbols.$ElementType]; // Issue 252 const backing = this[_symbols.$Backing]; // Issue 252 const address = this[_symbols.$Address]; const length = backing.getUint32(address + 8); const array = new Array(length); let current = backing.getFloat64(address); for (let i = 0; i < length; i++) { array[i] = ElementType.load(backing, current); current += this.BYTES_PER_ELEMENT; } return array; } /** * Typed array iterator. * Issue 252 */ *[Symbol.iterator]() { const ElementType = this[_symbols.$ElementType]; const backing = this[_symbols.$Backing]; const address = this[_symbols.$Address]; const pointer = backing.getFloat64(address); const length = backing.getUint32(address + 8); for (let i = 0; i < length; i++) { yield ElementType.load(backing, pointer + i * this.BYTES_PER_ELEMENT); } } } exports.BaseArray = BaseArray; /** * The number of slots which have so far been defined. */ let definedSlotCount = 0; /** * Ensure that the typed array prototype has at least the given number of slots. */ function ensureSlots(min) { if (definedSlotCount >= min) { return; } const max = Math.max(min, definedSlotCount * 1.5) + 100; for (let index = definedSlotCount; index < max; index++) { Object.defineProperty(BaseArray.prototype, index, { get: function get() { return this[_symbols.$GetElement](index); }, set: function set(value) { return this[_symbols.$SetElement](index, value); } }); definedSlotCount++; } } /** * Makes a TypedArray type class for a given realm. */ function make(realm) { const TypeClass = realm.TypeClass; const ReferenceType = realm.ReferenceType; const backing = realm.backing; let typeCounter = 0; return new TypeClass('ArrayType', function (ElementType) { let config = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; return Partial => { // Issue 252 const mustClearElements = ElementType[_symbols.$CanBeEmbedded] && ElementType[_symbols.$CanContainReferences]; // Issue 252 Partial[_symbols.$CanBeEmbedded] = false; // Issue 252 Partial[_symbols.$CanBeReferenced] = true; // Issue 252 Partial[_symbols.$CanContainReferences] = ElementType[_symbols.$CanContainReferences]; let MultidimensionalArray; const name = typeof config.name === 'string' ? config.name : typeof ElementType.name === 'string' && ElementType.name.length ? `Array<${ ElementType.name }>` : `%Array<0x${ typeCounter.toString(16) }>`; if (realm.T[name]) { return realm.T[name]; } typeCounter++; const id = typeof config.id === 'number' && config.id > 0 ? config.id : MIN_TYPE_ID + typeCounter; // Issue 285 Object.defineProperties(Partial, { name: { value: name }, Array: { get: function get() { if (MultidimensionalArray === undefined) { MultidimensionalArray = new realm.ArrayType(Partial); } return MultidimensionalArray; } } }); Partial.ref = new ReferenceType(Partial); const prototype = Object.create(BaseArray.prototype); prototype[_symbols.$ElementType] = ElementType; const BYTES_PER_ELEMENT = (0, _util.alignTo)(ElementType.byteLength, ElementType.byteAlignment); prototype.BYTES_PER_ELEMENT = BYTES_PER_ELEMENT; Partial.BYTES_PER_ELEMENT = BYTES_PER_ELEMENT; /** * The constructor for array type instances. */ function constructor(backingOrInput, address) { if (backingOrInput instanceof _backing2.default) { this[_symbols.$Backing] = backingOrInput; this[_symbols.$Address] = address; ensureSlots(this.length); } else { this[_symbols.$Backing] = backing; this[_symbols.$Address] = createArray(backing, backingOrInput); } } /** * Get an element at the given index. */ prototype[_symbols.$GetElement] = function GetElement(index) { const normalizedIndex = index >>> 0; const backing = this[_symbols.$Backing]; const address = this[_symbols.$Address]; const length = backing.getUint32(address + 8); if (length === 0 || normalizedIndex >= length) { throw new RangeError(`Cannot get an element at index ${ normalizedIndex } from an array of length ${ length }.`); } const pointer = backing.getFloat64(address); return ElementType.load(backing, pointer + normalizedIndex * BYTES_PER_ELEMENT); }; /** * Set an element at the given index. */ prototype[_symbols.$SetElement] = function SetElement(index, value) { const normalizedIndex = index >>> 0; const backing = this[_symbols.$Backing]; const address = this[_symbols.$Address]; const length = backing.getUint32(address + 8); if (length === 0 || normalizedIndex >= length) { throw new RangeError(`Cannot set an element at index ${ normalizedIndex } in an array of length ${ length }.`); } const pointer = backing.getFloat64(address); return ElementType.store(backing, pointer + normalizedIndex * BYTES_PER_ELEMENT, value); }; /** * Allocate space for the given array and write the input if any. */ function createArray(backing, input) { if (input == null) { const address = backing.gc.alloc(16); backing.setFloat64(address, 0); backing.setUint32(address + 8, 0); return address; } else if (typeof input === 'number') { if (input >>> 0 !== input) { throw new TypeError(`Cannot create a typed array with an invalid length.`); } else if (input === 0) { const address = backing.gc.alloc(16); backing.setFloat64(address, 0); backing.setUint32(address + 8, 0); return address; } else { ensureSlots(input); const byteLength = input * BYTES_PER_ELEMENT; const address = backing.gc.alloc(byteLength + 16); backing.setFloat64(address, address + 16); backing.setUint32(address + 8, input); writeDefaultValues(backing, address + 16, input); return address; } } else if (typeof input === 'object') { let array; if (Array.isArray(input)) { array = input; } else if (input[Symbol.iterator]) { array = Array.from(input); } else { throw new TypeError(`Cannot create a typed array from a non-iterable input.`); } if (array.length === 0) { const address = backing.gc.alloc(16); backing.setFloat64(address, 0); backing.setUint32(address + 8, 0); return address; } else { ensureSlots(array.length); const byteLength = array.length * BYTES_PER_ELEMENT; const address = backing.gc.alloc(byteLength + 16); backing.setFloat64(address, address + 16); backing.setUint32(address + 8, array.length); writeValues(backing, address + 16, array); return address; } } else { throw new TypeError(`Cannot create a typed array from invalid input.`); } } /** * Write empty values to the given address. */ function writeDefaultValues(backing, address, length) { let current = address; for (let i = 0; i < length; i++) { ElementType.initialize(backing, current); current += BYTES_PER_ELEMENT; } return address; } /** * Write values to the given address. */ function writeValues(backing, address, input) { const length = input.length; let current = address; for (let i = 0; i < length; i++) { ElementType.initialize(backing, current, input[i]); current += BYTES_PER_ELEMENT; } return address; } /** * Initialize the given array at the given address. */ function initializeArray(backing, address, input) { if (input == null) { backing.setFloat64(address, 0); backing.setUint32(address + 8, 0); } else if (typeof input === 'object') { let array; if (Array.isArray(input)) { array = input; } else if (input[Symbol.iterator]) { array = Array.from(input); } else { throw new TypeError(`Cannot create a typed array from a non-iterable input.`); } if (array.length === 0) { backing.setFloat64(address, 0); backing.setUint32(address + 8, 0); } else { const byteLength = array.length * BYTES_PER_ELEMENT; const dataAddress = backing.alloc(byteLength); backing.setFloat64(address, dataAddress); backing.setUint32(address + 8, array.length); writeValues(backing, dataAddress, array); } } else { throw new TypeError(`Cannot create a typed array from invalid input.`); } } /** * Store the given array at the given address. */ function storeArray(backing, address, input) { const existing = backing.getFloat64(address); if (existing > 0) { if (mustClearElements) { const length = backing.getUint32(address + 8); let current = existing; for (let i = 0; i < length; i++) { ElementType.clear(backing, current); current += BYTES_PER_ELEMENT; } } backing.free(existing); } if (input == null) { backing.setFloat64(address, 0); backing.setUint32(address + 8, 0); } else if (typeof input === 'object') { let array; if (Array.isArray(input)) { array = input; } else if (input[Symbol.iterator]) { array = Array.from(input); } else { throw new TypeError(`Cannot create a typed array from a non-iterable input.`); } if (array.length === 0) { backing.setFloat64(address, 0); backing.setUint32(address + 8, 0); } else { const byteLength = array.length * BYTES_PER_ELEMENT; const dataAddress = backing.alloc(byteLength); backing.setFloat64(address, dataAddress); backing.setUint32(address + 8, array.length); writeValues(backing, dataAddress, array); } } else { throw new TypeError(`Cannot create a typed array from invalid input.`); } } /** * Load the array at the given address. */ function loadArray(backing, address) { return new Partial(backing, address); } /** * Hash the given array. */ function hashArray(array) { const backing = array[_symbols.$Backing]; const address = array[_symbols.$Address]; let current = backing.getFloat64(address); const length = backing.getUint32(address + 8); let hash = 0x811c9dc5; for (let i = 0; i < length; i++) { hash ^= ElementType.hashValue(ElementType.load(backing, current)); hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24); current += BYTES_PER_ELEMENT; } return hash >>> 0; } /** * Return a random array. */ function randomArray() { const length = Math.floor(Math.random() * Math.pow(2, 7)); const array = new Array(length); for (let i = 0; i < length; i++) { array[i] = ElementType.randomValue(); } return new Partial(array); } return { id: id, name: name, byteAlignment: 8, byteLength: 16, gc: true, constructor: constructor, prototype: prototype, accepts: function accepts(input) { return input == null || input instanceof Partial || Array.isArray(input) || input[Symbol.iterator]; }, cast: function cast(input) { if (input instanceof Partial) { return input; } else { return new Partial(input); } }, initialize: initializeArray, store: storeArray, load: loadArray, clear: function clear(backing, address) { const pointer = backing.getFloat64(address); const length = backing.getUint32(address + 8); let current = pointer; for (let i = 0; i < length; i++) { ElementType.clear(backing, current); current += BYTES_PER_ELEMENT; } }, destructor: function destructor(backing, address) { const pointer = backing.getFloat64(address); if (mustClearElements) { const length = backing.getUint32(address + 8); let current = pointer; for (let i = 0; i < length; i++) { ElementType.clear(backing, current); current += BYTES_PER_ELEMENT; } } if (pointer !== address + 16) { // this was allocated using `BaseArray.store()` // so we need to reclaim the data segment separately backing.free(pointer); } backing.setFloat64(address, 0); backing.setUint32(address + 8, 0); }, equal: function equal(arrayA, arrayB) { if (arrayA[_symbols.$Backing] === arrayB[_symbols.$Backing] && arrayA[_symbols.$Address] === arrayB[_symbols.$Address]) { return true; } else if (arrayA.length !== arrayB.length) { return false; } const length = arrayA.length; for (let i = 0; i < length; i++) { if (!ElementType.equal(arrayA[i], arrayB[i])) { return false; } } return true; }, compareValues: function compareValues(valueA, valueB) { if (valueA === valueB) { return 0; } else if (valueA.length > valueB.length) { return 1; } else if (valueA.length < valueB.length) { return -1; } const length = valueA.length; for (let i = 0; i < length; i++) { const result = ElementType.compareValues(valueA[i], valueB[i]); if (result !== 0) { return result; } } return 0; }, compareAddresses: function compareAddresses(backing, addressA, addressB) { if (addressA === addressB) { return 0; } else if (addressA === 0) { return -1; } else if (addressB === 0) { return 1; } const lengthA = backing.getUint32(addressA + 8); const lengthB = backing.getUint32(addressB + 8); if (lengthA > lengthB) { return 1; } else if (lengthB < lengthA) { return -1; } let locA = backing.getFloat64(addressA); let locB = backing.getFloat64(addressB); for (let i = 0; i < lengthA; i++) { const result = ElementType.compareAddresses(backing, locA, locB); if (result !== 0) { return result; } locA += BYTES_PER_ELEMENT; locB += BYTES_PER_ELEMENT; } return 0; }, compareAddressValue: function compareAddressValue(backing, address, value) { const length = backing.getUint32(address + 8); if (length === 0 && value.length === 0) { return 0; } else if (length > value.length) { return 1; } else if (length < value.length) { return -1; } let loc = backing.getFloat64(address); for (let i = 0; i < length; i++) { const result = ElementType.compareAddressValue(backing, loc, value[i]); if (result !== 0) { return result; } loc += BYTES_PER_ELEMENT; } return 0; }, emptyValue: function emptyValue() { return []; }, hashValue: hashArray, randomValue: randomArray, flowType: function flowType() { return `Array<${ ElementType.flowType() }>`; } }; }; }); };