reign
Version:
A persistent, typed-objects implementation.
632 lines (573 loc) • 20.5 kB
JavaScript
"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() }>`;
}
};
};
});
};