reign
Version:
A persistent, typed-objects implementation.
365 lines (316 loc) • 11.7 kB
JavaScript
"use strict";
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 }; }
const MIN_TYPE_ID = exports.MIN_TYPE_ID = Math.pow(2, 20) * 2;
class Struct extends _2.TypedObject {}
exports.Struct = Struct;
function make(realm) {
const TypeClass = realm.TypeClass;
const ReferenceType = realm.ReferenceType;
const backing = realm.backing;
let typeCounter = 0;
return new TypeClass('StructType', function (fields, lengthOrOptions, options) {
return Partial => {
typeCounter++;
const capturedTypeCount = typeCounter;
Partial[_symbols.$CanBeEmbedded] = true;
Partial[_symbols.$CanBeReferenced] = true;
let StructArray;
// 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;
}
}
});
const prototype = Object.create(Struct.prototype);
let isFinalized = false;
/**
* Holds information about the size and layout of the struct.
*/
const metadata = {
byteLength: 0,
byteAlignment: 0,
canContainReferences: false
};
/**
* The specialized type which references this kind of struct.
*/
const 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) {
const 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') {
const ElementType = fieldsConfig;
fieldsConfig = Array.from({ length: lengthOrOptions }, (_, index) => [String(index), ElementType]);
options = options || {};
} else {
options = lengthOrOptions || {};
}
const fields = processStructConfig(fieldsConfig, options);
const fieldOffsets = {};
const fieldTypes = {};
for (const field of fields) {
const name = field.name;
const 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;
}
}
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(field => `${ 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) {
const name = field.name;
const type = field.type;
const offset = field.offset;
// Issue 252
const 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) {
const normalized = [];
const defaults = options.defaults || {};
if (Array.isArray(fields)) {
const names = new Set();
for (const _ref of fields) {
var _ref2 = _slicedToArray(_ref, 2);
const name = _ref2[0];
const type = _ref2[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) ? () => defaults[name] : () => type.emptyValue(true),
type: type
});
}
return normalized;
} else {
for (const name of Object.keys(fields)) {
const 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) ? () => defaults[name] : () => type.emptyValue(true),
type: type
});
}
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();
}
};
};
});
};