reign
Version:
A persistent, typed-objects implementation.
628 lines (525 loc) • 21.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.MIN_TYPE_ID = exports.HashSet = undefined;
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 _util = require("../../util");
var _2 = require("../../");
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 HashSet = exports.HashSet = function (_TypedObject) {
_inherits(HashSet, _TypedObject);
function HashSet() {
_classCallCheck(this, HashSet);
return _possibleConstructorReturn(this, Object.getPrototypeOf(HashSet).apply(this, arguments));
}
_createClass(HashSet, [{
key: "add",
/**
* Add the given entry to the set if it does not already exist.
*/
value: function add(entry) {
return this;
}
/**
* Determine whether the hash set contains the given entry or not.
*/
}, {
key: "has",
value: function has(entry) {
return false;
}
/**
* Deletes an entry from the hash set.
* Returns `true` if the given entry was deleted, otherwise `false`.
*/
}, {
key: "delete",
value: function _delete(entry) {
return false;
}
}, {
key: "size",
/**
* Return the size of the hash set.
*/
get: function get() {
// Issue 252
return this[_symbols.$Backing].getUint32(this[_symbols.$Address] + CARDINALITY_OFFSET);
}
}]);
return HashSet;
}(_.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) * 7;
/**
* Makes a HashSetType type class for the given realm.
*/
function make(realm) {
var TypeClass = realm.TypeClass;
var StructType = realm.StructType;
var ReferenceType = realm.ReferenceType;
var T = realm.T;
var backing = realm.backing;
var typeCounter = 0;
return new TypeClass('HashSetType', function (EntryType) {
var config = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
return function (Partial) {
var name = typeof config.name === 'string' ? config.name : "HashSet<" + EntryType.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
}
});
Partial.ref = new ReferenceType(Partial);
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], ['entry', EntryType]]);
var ENTRY_OFFSET = Bucket.fieldOffsets.entry;
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 getBucketEntry(backing, bucketAddress) {
return EntryType.load(backing, bucketAddress + ENTRY_OFFSET);
}
function setBucketEntry(backing, bucketAddress, value) {
return EntryType.store(backing, bucketAddress + ENTRY_OFFSET, value);
}
/**
* The constructor for this kind of hash set.
*/
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] = createHashSetAt(backing, backing.gc.calloc(HEADER_SIZE, id), backingOrInput);
}
this[_symbols.$CanBeReferenced] = true;
}
/**
* Create a new hash set from the given input and return its address.
*/
function createHashSetAt(backing, address, input) {
if (input == null) {
createEmptyHashSet(backing, address);
} else if (Array.isArray(input)) {
createHashSetFromArray(backing, address, input);
}
// Issue 252
else if (typeof input[Symbol.iterator] === 'function') {
createHashSetFromIterable(backing, address, input);
} else {
throw new TypeError("Cannot create a " + name + " from invalid input.");
}
return address;
}
/**
* Create an empty hashset 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 createEmptyHashSet(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 hashset from an array of entries.
*/
function createHashSetFromArray(backing, header, input) {
var length = input.length;
createEmptyHashSet(backing, header, length);
for (var i = 0; i < length; i++) {
var entry = input[i];
var hash = EntryType.hashValue(entry);
lookupOrInsert(backing, header, entry, hash);
}
}
/**
* Create a hashset from an iterable.
*/
function createHashSetFromIterable(backing, header, input) {
createEmptyHashSet(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 entry = _step.value;
var hash = EntryType.hashValue(entry);
lookupOrInsert(backing, header, entry, hash);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
/**
* Return the appropriate bucket for the given entry + hash.
*/
function probe(backing, header, entry, 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 || !EntryType.equal(entry, getBucketEntry(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 entry + hash, or 0 if it does not exist.
*/
function lookup(backing, header, entry, hash) {
var address = probe(backing, header, entry, hash);
return getBucketHash(backing, address) === 0 ? 0 : address;
}
/**
* Find the address of the bucket for the given entry + hash, or create it if it does not exist.
*/
function lookupOrInsert(backing, header, entry, hash) {
var bucketArrayLength = getArrayLength(backing, header);
var address = probe(backing, header, entry, hash);
if (getBucketHash(backing, address) !== 0) {
return address;
}
setBucketEntry(backing, address, entry);
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, entry, hash);
} else {
return address;
}
}
/**
* Remove the given entry + hash from the hash set.
*/
function remove(backing, header, entry, hash) {
var p = probe(backing, header, entry, 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;
}
}
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) {
lookupOrInsert(backing, header, getBucketEntry(backing, oldAddress), bucketHash);
}
}
setCardinality(backing, header, cardinality);
backing.free(bucketArrayAddress);
}
/**
* Destroy the hashset 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);
var current = bucketArrayAddress;
for (var _index2 = 0; _index2 < bucketArrayLength; _index2++) {
Bucket.destructor(backing, current);
current += BUCKET_SIZE;
}
setArrayAddress(backing, header, 0);
setArrayLength(backing, header, 0);
backing.free(bucketArrayAddress);
}
}
var prototype = Object.create(HashSet.prototype, _defineProperty({
/**
* Add the given entry to the hash set.
*/
add: {
value: function value(entry) {
var hash = EntryType.hashValue(entry);
lookupOrInsert(this[_symbols.$Backing], this[_symbols.$Address], entry, hash);
return this;
}
},
/**
* Determine whether the hash set contains the given entry or not.
*/
has: {
value: function value(entry) {
var hash = EntryType.hashValue(entry);
return lookup(this[_symbols.$Backing], this[_symbols.$Address], entry, hash) !== 0;
}
},
/**
* Deletes an entry from the hash set.
* Returns `true` if the given entry was deleted, otherwise `false`.
*/
delete: {
value: function value(entry) {
var hash = EntryType.hashValue(entry);
return remove(this[_symbols.$Backing], this[_symbols.$Address], entry, hash);
}
},
/**
* Return a representation of the hash set 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++] = getBucketEntry(backing, current);
}
current += BUCKET_SIZE;
}
return arr;
}
}
}, Symbol.iterator, {
value: regeneratorRuntime.mark(function value() {
var backing, address, bucketArrayLength, current, _index3;
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);
_index3 = 0;
case 5:
if (!(_index3 < bucketArrayLength)) {
_context.next = 13;
break;
}
if (!(getBucketHash(backing, current) !== 0)) {
_context.next = 9;
break;
}
_context.next = 9;
return getBucketEntry(backing, current);
case 9:
current += BUCKET_SIZE;
case 10:
_index3++;
_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.alloc(HEADER_SIZE, Partial.id, 1);
createHashSetAt(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.alloc(HEADER_SIZE, Partial.id, 1);
createHashSetAt(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(setA, setB) {
if (setA[_symbols.$Backing] === setB[_symbols.$Backing] && setA[_symbols.$Address] === setB[_symbols.$Address]) {
return true;
} else if (setA.size !== setB.size) {
return false;
}
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = setA[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var key = _step2.value;
if (!setB.has(key)) {
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 set = new Partial();
var size = Math.ceil(Math.random() * 32);
for (var i = 0; i < size; i++) {
set.add(EntryType.randomValue());
}
return set;
},
emptyValue: function emptyValue() {
return new Partial();
},
flowType: function flowType() {
return "HashSet<" + EntryType.flowType() + ">";
},
hashValue: function hashValue(input) {
return input[_symbols.$Address];
}
};
};
});
}