reign
Version:
A persistent, typed-objects implementation.
465 lines (401 loc) • 15.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Struct = exports.MIN_TYPE_ID = 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"); } }; }();
exports.make = make;
var _backing = require("backing");
var _backing2 = _interopRequireDefault(_backing);
var _2 = require("../");
var _util = require("../../util");
var _methods = require("./methods");
var _3 = require("../../");
var _symbols = require("../../symbols");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: 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 MIN_TYPE_ID = exports.MIN_TYPE_ID = Math.pow(2, 20) * 2;
var Struct = exports.Struct = function (_TypedObject) {
_inherits(Struct, _TypedObject);
function Struct() {
_classCallCheck(this, Struct);
return _possibleConstructorReturn(this, Object.getPrototypeOf(Struct).apply(this, arguments));
}
return Struct;
}(_2.TypedObject);
function make(realm) {
var TypeClass = realm.TypeClass;
var ReferenceType = realm.ReferenceType;
var backing = realm.backing;
var typeCounter = 0;
return new TypeClass('StructType', function (fields, lengthOrOptions, options) {
return function (Partial) {
typeCounter++;
var capturedTypeCount = typeCounter;
Partial[_symbols.$CanBeEmbedded] = true;
Partial[_symbols.$CanBeReferenced] = true;
var StructArray = void 0;
// Issue 285
Object.defineProperties(Partial, {
name: {
configurable: true,
value: "%Struct<0x" + typeCounter.toString(16) + ">"
},
flowType: {
configurable: true,
value: function value() {
return 'Object';
}
},
Array: {
get: function get() {
if (StructArray === undefined) {
StructArray = new realm.ArrayType(Partial);
}
return StructArray;
}
}
});
var prototype = Object.create(Struct.prototype);
var isFinalized = false;
/**
* Holds information about the size and layout of the struct.
*/
var metadata = {
byteLength: 0,
byteAlignment: 0,
canContainReferences: false
};
/**
* The specialized type which references this kind of struct.
*/
var Reference = new ReferenceType(Partial);
/**
* The constructor for struct type instances.
*/
function constructor(backingOrInput, address, embedded) {
if (!isFinalized) {
throw new ReferenceError("Cannot create an instance of a struct before it is finalized.");
} else if (backingOrInput instanceof _backing2.default) {
this[_symbols.$Backing] = backingOrInput;
this[_symbols.$Address] = address;
this[_symbols.$CanBeReferenced] = !embedded;
} else {
this[_symbols.$Backing] = backing;
this[_symbols.$Address] = createStruct(backing, backingOrInput);
this[_symbols.$CanBeReferenced] = true;
}
}
/**
* Allocate space for the given struct and write the input if any.
*/
function createStruct(backing, input) {
var address = backing.gc.alloc(metadata.byteLength, Partial.id);
Partial.initialize(backing, address, input);
return address;
}
/**
* Finalize the layout of the fields within the struct.
*/
function finalizeLayout(fieldsConfig, lengthOrOptions, options) {
//fieldsConfig: StructFieldsConfig, options: StructOptions = {}): typeof Partial {
if (isFinalized) {
throw new Error("Struct layout is already finalized");
}
if (typeof lengthOrOptions === 'number') {
(function () {
var ElementType = fieldsConfig;
fieldsConfig = Array.from({ length: lengthOrOptions }, function (_, index) {
return [String(index), ElementType];
});
options = options || {};
})();
} else {
options = lengthOrOptions || {};
}
var fields = processStructConfig(fieldsConfig, options);
var fieldOffsets = {};
var fieldTypes = {};
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = fields[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var field = _step.value;
var _name = field.name;
var type = field.type;
if (type.byteAlignment > metadata.byteAlignment) {
metadata.byteAlignment = type.byteAlignment;
}
field.offset = (0, _util.alignTo)(metadata.byteLength, type.byteAlignment);
metadata.byteLength = field.offset + type.byteLength;
fieldOffsets[_name] = field.offset;
fieldTypes[_name] = type;
defineAccessors(field);
/* Issue 252 */
if (type[_symbols.$CanContainReferences]) {
metadata.canContainReferences = true;
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
metadata.byteLength = (0, _util.alignTo)(metadata.byteLength, metadata.byteAlignment);
Object.freeze(fieldOffsets);
Object.freeze(fieldTypes);
Partial[_symbols.$CanContainReferences] = metadata.canContainReferences;
Object.defineProperties(Partial, {
id: {
value: options.id || MIN_TYPE_ID + capturedTypeCount
},
name: {
value: options.name || "%StructType<0x" + capturedTypeCount.toString(16) + ">"
},
byteLength: {
value: metadata.byteLength
},
byteAlignment: {
value: metadata.byteAlignment
},
fieldOffsets: {
value: fieldOffsets
},
fieldTypes: {
value: fieldTypes
},
accepts: {
value: (0, _methods.createAccepts)(fields)
},
initialize: {
value: (0, _methods.createInitializeStruct)(Partial, fields)
},
store: {
value: (0, _methods.createStoreStruct)(Partial, fields)
},
load: {
value: function value(backing, address, embedded) {
return new Partial(backing, address, embedded);
}
},
clear: {
value: (0, _methods.createClearStruct)(fields)
},
destructor: {
value: (0, _methods.createStructDestructor)(fields)
},
equal: {
value: (0, _methods.createEqual)(fields)
},
compareValues: {
value: (0, _methods.createCompareValues)(fields)
},
compareAddresses: {
value: (0, _methods.createCompareAddresses)(fields)
},
compareAddressValue: {
value: (0, _methods.createCompareAddressValue)(fields)
},
hashValue: {
value: (0, _methods.createHashStruct)(fields)
},
randomValue: {
value: (0, _methods.createRandomValue)(fields)
},
flowType: {
value: function value() {
return "{" + fields.map(function (field) {
return field.name + ": " + field.type.flowType() + ";";
}).join('\n') + "}";
}
}
});
Object.defineProperties(prototype, {
toJSON: {
value: (0, _methods.createToJSON)(fields)
}
});
isFinalized = true;
realm.registry.add(Partial);
return Partial;
}
/**
* Define the getter and setter for a field.
*/
function defineAccessors(field) {
var name = field.name;
var type = field.type;
var offset = field.offset;
// Issue 252
var embedded = type[_symbols.$CanBeEmbedded];
Object.defineProperty(prototype, name, {
enumerable: true,
get: function get() {
return type.load(this[_symbols.$Backing], this[_symbols.$Address] + offset, embedded);
},
set: function set(value) {
type.store(this[_symbols.$Backing], this[_symbols.$Address] + offset, value);
}
});
}
/**
* Normalize the configuration for a struct and return a list of fields.
*/
function processStructConfig(fields, options) {
var normalized = [];
var defaults = options.defaults || {};
if (Array.isArray(fields)) {
var names = new Set();
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
var _loop = function _loop() {
var _step2$value = _slicedToArray(_step2.value, 2);
var name = _step2$value[0];
var type = _step2$value[1];
if (names.has(name)) {
throw new TypeError("A field with the name \"" + name + "\" already exists.");
}
/* Issue 252 */
if (!type || !type[_symbols.$isType]) {
throw new TypeError("Field \"" + name + "\" must be a finalized type.");
}
names.add(name);
normalized.push({
name: name,
offset: 0,
default: defaults.hasOwnProperty(name) ? function () {
return defaults[name];
} : function () {
return type.emptyValue(true);
},
type: type
});
};
for (var _iterator2 = fields[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
_loop();
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
return normalized;
} else {
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
var _loop2 = function _loop2() {
var name = _step3.value;
var type = fields[name];
/* Issue 252 */
if (!type || !type[_symbols.$isType]) {
throw new TypeError("Field \"" + name + "\" must be a finalized type.");
}
normalized.push({
name: name,
offset: 0,
default: defaults.hasOwnProperty(name) ? function () {
return defaults[name];
} : function () {
return type.emptyValue(true);
},
type: type
});
};
for (var _iterator3 = Object.keys(fields)[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
_loop2();
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
return optimizeFieldLayout(normalized);
}
}
/**
* Given an object mapping field names to types, return an array which
* contains the fields in an order optimized for the smallest possible struct size,
* whilst still respecting each field's alignment requirements.
*
* @fixme this is not currently very good, can do better.
*/
function optimizeFieldLayout(fields) {
return fields.sort(compareFieldsByByteAlignmentOrName);
}
/**
* Comparator used for sorting fields based on the byteAlignment of their types.
* If two fields have the same byte alignment, they will be compared by name instead.
*/
function compareFieldsByByteAlignmentOrName(a, b) {
if (a.type.byteAlignment > b.type.byteAlignment) {
return 1;
} else if (a.type.byteAlignment < b.type.byteAlignment) {
return -1;
} else {
if (a.name > b.name) {
return 1;
} else if (a.name < b.name) {
return -1;
}
return 0;
}
}
if (fields != null) {
finalizeLayout(fields, lengthOrOptions, options);
}
return {
constructor: constructor,
prototype: prototype,
gc: true,
ref: Reference,
finalize: finalizeLayout,
cast: function cast(input) {
if (input == null) {
return null;
} else if (input instanceof Partial) {
return input;
} else {
return new Partial(input);
}
},
emptyValue: function emptyValue(embedded) {
return embedded ? null : new Partial();
}
};
};
});
};