UNPKG

reign

Version:

A persistent, typed-objects implementation.

717 lines (603 loc) 25.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MIN_TYPE_ID = exports.HashMap = undefined; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); exports.make = make; var _backing = require("backing"); var _backing2 = _interopRequireDefault(_backing); var _ = require("../"); var _2 = require("../../"); var _util = require("../../util"); var _symbols = require("../../symbols"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var HashMap = exports.HashMap = function (_TypedObject) { _inherits(HashMap, _TypedObject); function HashMap() { _classCallCheck(this, HashMap); return _possibleConstructorReturn(this, Object.getPrototypeOf(HashMap).apply(this, arguments)); } _createClass(HashMap, [{ key: "get", /** * Get the value associated with the given key, otherwise undefined. */ value: function get(key) { return undefined; } /** * Set the value associated with the given key. */ }, { key: "set", value: function set(key) { return this; } /** * Determine whether the hash map contains the given key or not. */ }, { key: "has", value: function has(key) { return false; } /** * Deletes a key from the hash map. * Returns `true` if the given key was deleted, otherwise `false`. */ }, { key: "delete", value: function _delete(key) { return false; } }, { key: "size", /** * Return the size of the hash map. */ get: function get() { // Issue 252 return this[_symbols.$Backing].getUint32(this[_symbols.$Address] + CARDINALITY_OFFSET); } }]); return HashMap; }(_.TypedObject); var HEADER_SIZE = 16; var ARRAY_POINTER_OFFSET = 0; var ARRAY_LENGTH_OFFSET = 8; var CARDINALITY_OFFSET = 12; var INITIAL_BUCKET_COUNT = 16; var MIN_TYPE_ID = exports.MIN_TYPE_ID = Math.pow(2, 20) * 6; /** * Makes a HashMapType type class for the given realm. */ function make(realm) { var TypeClass = realm.TypeClass; var StructType = realm.StructType; var T = realm.T; var backing = realm.backing; var typeCounter = 0; return new TypeClass('HashMapType', function (KeyType, ValueType) { var config = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; return function (Partial) { var name = typeof config.name === 'string' ? config.name : "HashMap<" + KeyType.name + ", " + ValueType.name + ">"; if (realm.T[name]) { return realm.T[name]; } typeCounter++; var id = typeof config.id === 'number' ? config.id : MIN_TYPE_ID + typeCounter; Partial[_symbols.$CanBeEmbedded] = false; Partial[_symbols.$CanBeReferenced] = true; Partial[_symbols.$CanContainReferences] = true; Object.defineProperties(Partial, { id: { value: id }, name: { value: name } }); function getArrayAddress(backing, address) { return backing.getFloat64(address); } function setArrayAddress(backing, address, value) { backing.setFloat64(address, value); } function getArrayLength(backing, address) { return backing.getUint32(address + ARRAY_LENGTH_OFFSET); } function setArrayLength(backing, address, value) { backing.setUint32(address + ARRAY_LENGTH_OFFSET, value); } function getCardinality(backing, address) { return backing.getUint32(address + CARDINALITY_OFFSET); } function setCardinality(backing, address, value) { backing.setUint32(address + CARDINALITY_OFFSET, value); } var Bucket = new StructType([['hash', T.Uint32], ['key', KeyType], ['value', ValueType]]); var KEY_OFFSET = Bucket.fieldOffsets.key; var VALUE_OFFSET = Bucket.fieldOffsets.value; var BUCKET_SIZE = (0, _util.alignTo)(Bucket.byteLength, Bucket.byteAlignment); function getBucketHash(backing, bucketAddress) { return backing.getUint32(bucketAddress); } function setBucketHash(backing, bucketAddress, value) { backing.setUint32(bucketAddress, value); } function getBucketKey(backing, bucketAddress) { return KeyType.load(backing, bucketAddress + KEY_OFFSET); } function setBucketKey(backing, bucketAddress, value) { return KeyType.store(backing, bucketAddress + KEY_OFFSET, value); } function getBucketValue(backing, bucketAddress) { return ValueType.load(backing, bucketAddress + VALUE_OFFSET); } function setBucketValue(backing, bucketAddress, value) { return ValueType.store(backing, bucketAddress + VALUE_OFFSET, value); } /** * The constructor for this kind of hash map. */ function constructor(backingOrInput, address) { if (backingOrInput instanceof _backing2.default) { this[_symbols.$Backing] = backingOrInput; this[_symbols.$Address] = address; } else { this[_symbols.$Backing] = backing; this[_symbols.$Address] = createHashMapAt(backing, backing.gc.calloc(HEADER_SIZE, id), backingOrInput); } this[_symbols.$CanBeReferenced] = true; } /** * Create a new hash map from the given input and return its address. */ function createHashMapAt(backing, address, input) { if (input == null) { createEmptyHashMap(backing, address); } else if ((typeof input === "undefined" ? "undefined" : _typeof(input)) !== 'object') { throw new TypeError("Cannot create a " + name + " from invalid input."); } else if (Array.isArray(input)) { createHashMapFromArray(backing, address, input); } else if (typeof input[Symbol.iterator] === 'function') { createHashMapFromIterable(backing, address, input); } else { createHashMapFromObject(backing, address, input); } return address; } /** * Create an empty hashmap with a bucket array. * Use `initialCardinalityHint` to pre-allocate a bucket array which can * handle at least the given number of entries. Note that specifying this * argument does not actually write the cardinality value. */ function createEmptyHashMap(backing, header) { var initialCardinalityHint = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2]; var initialSize = void 0; if (initialCardinalityHint * 2 < INITIAL_BUCKET_COUNT) { initialSize = INITIAL_BUCKET_COUNT; } else { initialSize = initialCardinalityHint * 2; } var body = backing.calloc(initialSize * BUCKET_SIZE); setArrayAddress(backing, header, body); setArrayLength(backing, header, initialSize); return header; } /** * Create a hashmap from an array of key / values. */ function createHashMapFromArray(backing, header, input) { var length = input.length; createEmptyHashMap(backing, header, length); for (var i = 0; i < length; i++) { var _input$i = _slicedToArray(input[i], 2); var _key = _input$i[0]; var _value = _input$i[1]; var hash = KeyType.hashValue(_key); setBucketValue(backing, lookupOrInsert(backing, header, _key, hash), _value); } } /** * Create a hashmap from an iterable. */ function createHashMapFromIterable(backing, header, input) { createEmptyHashMap(backing, header); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = input[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var _step$value = _slicedToArray(_step.value, 2); var _key2 = _step$value[0]; var _value2 = _step$value[1]; var hash = KeyType.hashValue(_key2); setBucketValue(backing, lookupOrInsert(backing, header, _key2, hash), _value2); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } /** * Create a hashmap from an object. */ function createHashMapFromObject(backing, header, input) { var keys = Object.keys(input); var length = keys.length; createEmptyHashMap(backing, header, length); for (var i = 0; i < length; i++) { var _key3 = keys[i]; var _value3 = input[_key3]; var hash = KeyType.hashValue(_key3); setBucketValue(backing, lookupOrInsert(backing, header, _key3, hash), _value3); } } /** * Return the appropriate bucket for the given key + hash. */ function probe(backing, header, key, hash) { var bucketArrayLength = getArrayLength(backing, header); var bucketArrayAddress = getArrayAddress(backing, header); var index = hash & bucketArrayLength - 1; var address = bucketArrayAddress + index * BUCKET_SIZE; var bucketHash = getBucketHash(backing, address); while (bucketHash !== 0 && (bucketHash !== hash || !KeyType.equal(key, getBucketKey(backing, address)))) { index++; if (index >= bucketArrayLength) { index = 0; } address = bucketArrayAddress + index * BUCKET_SIZE; bucketHash = getBucketHash(backing, address); } return address; } /** * Find the address of the bucket for the given key + hash, or 0 if it does not exist. */ function lookup(backing, header, key, hash) { var address = probe(backing, header, key, hash); return getBucketHash(backing, address) === 0 ? 0 : address; } /** * Find the address of the bucket for the given key + hash, or create it if it does not exist. */ function lookupOrInsert(backing, header, key, hash) { var bucketArrayLength = getArrayLength(backing, header); var address = probe(backing, header, key, hash); if (getBucketHash(backing, address) !== 0) { return address; } setBucketKey(backing, address, key); setBucketHash(backing, address, hash); var cardinality = getCardinality(backing, header) + 1; setCardinality(backing, header, cardinality); if (cardinality + (cardinality >> 2) >= bucketArrayLength) { grow(backing, header); return probe(backing, header, key, hash); } else { return address; } } /** * Remove the given key + hash from the hash map. */ function remove(backing, header, key, hash) { var p = probe(backing, header, key, hash); if (getBucketHash(backing, p) === 0) { return false; } var bucketArrayLength = getArrayLength(backing, header); var bucketArrayAddress = getArrayAddress(backing, header); var end = bucketArrayAddress + bucketArrayLength * BUCKET_SIZE; // @fixme free the bucket value? var q = p; while (true) { // Move q to the next entry q = q + BUCKET_SIZE; if (q === end) { q = bucketArrayAddress; } var qHash = getBucketHash(backing, q); // All entries between p and q have their initial position between p and q // and the entry p can be cleared without breaking the search for these // entries. if (qHash === 0) { break; } // Find the initial position for the entry at position q. var r = bucketArrayAddress + (qHash & bucketArrayLength - 1) * BUCKET_SIZE; // If the entry at position q has its initial position outside the range // between p and q it can be moved forward to position p and will still be // found. There is now a new candidate entry for clearing. if (q > p && (r <= p || r > q) || q < p && r <= p && r > q) { backing.copy(p, q, BUCKET_SIZE); p = q; } } // Clear the entry which is allowed to en emptied. setBucketHash(backing, p, 0); var cardinality = getCardinality(backing, header); setCardinality(backing, header, cardinality - 1); return true; } /** * Grow the backing array to twice its current capacity. */ function grow(backing, header) { var bucketArrayLength = getArrayLength(backing, header); var bucketArrayAddress = getArrayAddress(backing, header); var cardinality = getCardinality(backing, header); var newBuckets = backing.calloc(bucketArrayLength * 2 * BUCKET_SIZE); setArrayAddress(backing, header, newBuckets); setArrayLength(backing, header, bucketArrayLength * 2); setCardinality(backing, header, 1); for (var index = 0; index < bucketArrayLength; index++) { var oldAddress = bucketArrayAddress + index * BUCKET_SIZE; var bucketHash = getBucketHash(backing, oldAddress); if (bucketHash !== 0) { var newAddress = lookupOrInsert(backing, header, getBucketKey(backing, oldAddress), bucketHash); setBucketValue(backing, newAddress, getBucketValue(backing, oldAddress)); } } setCardinality(backing, header, cardinality); backing.free(bucketArrayAddress); } /** * Destroy the hashmap at the given address, along with all its contents. */ function destructor(backing, header) { var bucketArrayAddress = getArrayAddress(backing, header); if (bucketArrayAddress !== 0) { var bucketArrayLength = getArrayLength(backing, header); setArrayAddress(backing, header, 0); setArrayLength(backing, header, 0); var current = bucketArrayAddress; for (var index = 0; index < bucketArrayLength; index++) { Bucket.clear(backing, current); current += BUCKET_SIZE; } backing.free(bucketArrayAddress); } } var prototype = Object.create(HashMap.prototype, _defineProperty({ /** * Get the value associated with the given key, otherwise undefined. */ get: { value: function value(key) { var hash = KeyType.hashValue(key); var backing = this[_symbols.$Backing]; var address = lookup(backing, this[_symbols.$Address], key, hash); if (address === 0) { return undefined; } else { return getBucketValue(backing, address); } } }, /** * Set the value associated with the given key. */ set: { value: function (_value4) { function value(_x3, _x4) { return _value4.apply(this, arguments); } value.toString = function () { return _value4.toString(); }; return value; }(function (key, value) { var hash = KeyType.hashValue(key); var backing = this[_symbols.$Backing]; var address = lookupOrInsert(backing, this[_symbols.$Address], key, hash); setBucketValue(backing, address, value); return this; }) }, /** * Determine whether the hash map contains the given key or not. */ has: { value: function value(key) { var hash = KeyType.hashValue(key); return lookup(this[_symbols.$Backing], this[_symbols.$Address], key, hash) !== 0; } }, /** * Deletes a key from the hash map. * Returns `true` if the given key was deleted, otherwise `false`. */ delete: { value: function value(key) { var hash = KeyType.hashValue(key); return remove(this[_symbols.$Backing], this[_symbols.$Address], key, hash); } }, /** * Return a representation of the hash map which can be encoded as JSON. */ toJSON: { value: function value() { var backing = this[_symbols.$Backing]; var address = this[_symbols.$Address]; var size = getCardinality(backing, address); var bucketArrayLength = getArrayLength(backing, address); var arr = new Array(size); var current = getArrayAddress(backing, address); var index = 0; for (var i = 0; i < bucketArrayLength; i++) { if (getBucketHash(backing, current) !== 0) { arr[index++] = [getBucketKey(backing, current), getBucketValue(backing, current)]; } current += BUCKET_SIZE; } return arr; } }, forEach: { value: function value(visitor) { var backing = this[_symbols.$Backing]; var address = this[_symbols.$Address]; var bucketArrayLength = getArrayLength(backing, address); var current = getArrayAddress(backing, address); for (var index = 0; index < bucketArrayLength; index++) { if (getBucketHash(backing, current) !== 0) { visitor(getBucketValue(backing, current), getBucketKey(backing, current), this); } current += BUCKET_SIZE; } return this; } } }, Symbol.iterator, { value: regeneratorRuntime.mark(function value() { var backing, address, bucketArrayLength, current, index; return regeneratorRuntime.wrap(function value$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: backing = this[_symbols.$Backing]; address = this[_symbols.$Address]; bucketArrayLength = getArrayLength(backing, address); current = getArrayAddress(backing, address); index = 0; case 5: if (!(index < bucketArrayLength)) { _context.next = 13; break; } if (!(getBucketHash(backing, current) !== 0)) { _context.next = 9; break; } _context.next = 9; return [getBucketKey(backing, current), getBucketValue(backing, current)]; case 9: current += BUCKET_SIZE; case 10: index++; _context.next = 5; break; case 13: case "end": return _context.stop(); } } }, value, this); }) })); return { id: id, name: name, byteLength: 8, byteAlignment: 8, constructor: constructor, prototype: prototype, accepts: function accepts(input) { return input !== null && (typeof input === "undefined" ? "undefined" : _typeof(input)) === 'object'; }, initialize: function initialize(backing, pointerAddress, initialValue) { var address = backing.gc.calloc(HEADER_SIZE, Partial.id, 1); createHashMapAt(backing, address, initialValue); backing.setFloat64(pointerAddress, address); }, store: function store(backing, pointerAddress, input) { var existing = backing.getFloat64(pointerAddress); if (existing !== 0) { backing.setFloat64(pointerAddress, 0); backing.gc.unref(existing); } var address = backing.gc.calloc(HEADER_SIZE, Partial.id, 1); createHashMapAt(backing, address, input); backing.setFloat64(pointerAddress, address); }, load: function load(backing, pointerAddress) { var address = backing.getFloat64(pointerAddress); return address === 0 ? null : new Partial(backing, address); }, clear: function clear(backing, pointerAddress) { var address = backing.getFloat64(pointerAddress); if (address !== 0) { backing.setFloat64(pointerAddress, 0); backing.gc.unref(address); } }, destructor: destructor, equal: function equal(mapA, mapB) { if (mapA[_symbols.$Backing] === mapB[_symbols.$Backing] && mapA[_symbols.$Address] === mapB[_symbols.$Address]) { return true; } else if (mapA.size !== mapB.size) { return false; } var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = mapA[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var _step2$value = _slicedToArray(_step2.value, 2); var _key4 = _step2$value[0]; var a = _step2$value[1]; var b = mapB.get(_key4); // Fixme we should check the value types here. if (a !== b && (b === undefined || !ValueType.equal(a, b))) { return false; } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } return true; }, randomValue: function randomValue() { var map = new Partial(); var size = Math.ceil(Math.random() * 32); for (var i = 0; i < size; i++) { map.set(KeyType.randomValue(), ValueType.randomValue()); } return map; }, emptyValue: function emptyValue() { return new Partial(); }, flowType: function flowType() { return "HashMap<" + KeyType.flowType() + ", " + ValueType.flowType() + ">"; }, hashValue: function hashValue(input) { return input[_symbols.$Address]; } }; }; }); }