reign
Version:
A persistent, typed-objects implementation.
717 lines (603 loc) • 25.7 kB
JavaScript
;
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];
}
};
};
});
}