UNPKG

reign

Version:

A persistent, typed-objects implementation.

365 lines (316 loc) 11.7 kB
"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(); } }; }; }); };