mapbox-gl
Version:
A WebGL interactive maps library
347 lines (290 loc) • 10.1 kB
JavaScript
'use strict';
// Note: all "sizes" are measured in bytes
var assert = require('assert');
module.exports = StructArrayType;
var viewTypes = {
'Int8': Int8Array,
'Uint8': Uint8Array,
'Uint8Clamped': Uint8ClampedArray,
'Int16': Int16Array,
'Uint16': Uint16Array,
'Int32': Int32Array,
'Uint32': Uint32Array,
'Float32': Float32Array,
'Float64': Float64Array
};
/**
* @typedef StructMember
* @private
* @property {string} name
* @property {string} type
* @property {number} components
*/
var structArrayTypeCache = {};
/**
* `StructArrayType` is used to create new `StructArray` types.
*
* `StructArray` provides an abstraction over `ArrayBuffer` and `TypedArray` making it behave like
* an array of typed structs. A StructArray is comprised of elements. Each element has a set of
* members that are defined when the `StructArrayType` is created.
*
* StructArrays useful for creating large arrays that:
* - can be transferred from workers as a Transferable object
* - can be copied cheaply
* - use less memory for lower-precision members
* - can be used as buffers in WebGL.
*
* @class StructArrayType
* @param {Array.<StructMember>}
* @param options
* @param {number} options.alignment Use `4` to align members to 4 byte boundaries. Default is 1.
*
* @example
*
* var PointArrayType = new StructArrayType({
* members: [
* { type: 'Int16', name: 'x' },
* { type: 'Int16', name: 'y' }
* ]});
*
* var pointArray = new PointArrayType();
* pointArray.emplaceBack(10, 15);
* pointArray.emplaceBack(20, 35);
*
* point = pointArray.get(0);
* assert(point.x === 10);
* assert(point.y === 15);
*
* @private
*/
function StructArrayType(options) {
var key = JSON.stringify(options);
if (structArrayTypeCache[key]) {
return structArrayTypeCache[key];
}
if (options.alignment === undefined) options.alignment = 1;
function StructType() {
Struct.apply(this, arguments);
}
StructType.prototype = Object.create(Struct.prototype);
var offset = 0;
var maxSize = 0;
var usedTypes = ['Uint8'];
StructType.prototype.members = options.members.map(function(member) {
member = {
name: member.name,
type: member.type,
components: member.components || 1
};
assert(member.name.length);
assert(member.type in viewTypes);
if (usedTypes.indexOf(member.type) < 0) usedTypes.push(member.type);
var typeSize = sizeOf(member.type);
maxSize = Math.max(maxSize, typeSize);
member.offset = offset = align(offset, Math.max(options.alignment, typeSize));
for (var c = 0; c < member.components; c++) {
Object.defineProperty(StructType.prototype, member.name + (member.components === 1 ? '' : c), {
get: createGetter(member, c),
set: createSetter(member, c)
});
}
offset += typeSize * member.components;
return member;
});
StructType.prototype.alignment = options.alignment;
StructType.prototype.size = align(offset, Math.max(maxSize, options.alignment));
function StructArrayType() {
StructArray.apply(this, arguments);
this.members = StructType.prototype.members;
}
StructArrayType.serialize = serializeStructArrayType;
StructArrayType.prototype = Object.create(StructArray.prototype);
StructArrayType.prototype.StructType = StructType;
StructArrayType.prototype.bytesPerElement = StructType.prototype.size;
StructArrayType.prototype.emplaceBack = createEmplaceBack(StructType.prototype.members, StructType.prototype.size);
StructArrayType.prototype._usedTypes = usedTypes;
structArrayTypeCache[key] = StructArrayType;
return StructArrayType;
}
/**
* Serialize the StructArray type. This serializes the *type* not an instance of the type.
* @private
*/
function serializeStructArrayType() {
return {
members: this.prototype.StructType.prototype.members,
alignment: this.prototype.StructType.prototype.alignment,
bytesPerElement: this.prototype.bytesPerElement
};
}
function align(offset, size) {
return Math.ceil(offset / size) * size;
}
function sizeOf(type) {
return viewTypes[type].BYTES_PER_ELEMENT;
}
function getArrayViewName(type) {
return type.toLowerCase();
}
/*
* > I saw major perf gains by shortening the source of these generated methods (i.e. renaming
* > elementIndex to i) (likely due to v8 inlining heuristics).
* - lucaswoj
*/
function createEmplaceBack(members, bytesPerElement) {
var usedTypeSizes = [];
var argNames = [];
var body = '' +
'var i = this.length;\n' +
'this.resize(this.length + 1);\n';
for (var m = 0; m < members.length; m++) {
var member = members[m];
var size = sizeOf(member.type);
// array offsets to the end of current data for each type size
// var o{SIZE} = i * ROUNDED(bytesPerElement / size);
if (usedTypeSizes.indexOf(size) < 0) {
usedTypeSizes.push(size);
body += 'var o' + size.toFixed(0) + ' = i * ' + (bytesPerElement / size).toFixed(0) + ';\n';
}
for (var c = 0; c < member.components; c++) {
// arguments v0, v1, v2, ... are, in order, the components of
// member 0, then the components of member 1, etc.
var argName = 'v' + argNames.length;
// The index for `member` component `c` into the appropriate type array is:
// this.{TYPE}[o{SIZE} + MEMBER_OFFSET + {c}] = v{X}
// where MEMBER_OFFSET = ROUND(member.offset / size) is the per-element
// offset of this member into the array
var index = 'o' + size.toFixed(0) + ' + ' + (member.offset / size + c).toFixed(0);
body += 'this.' + getArrayViewName(member.type) + '[' + index + '] = ' + argName + ';\n';
argNames.push(argName);
}
}
body += 'return i;';
return new Function(argNames, body);
}
function createMemberComponentString(member, component) {
var elementOffset = 'this._pos' + sizeOf(member.type).toFixed(0);
var componentOffset = (member.offset / sizeOf(member.type) + component).toFixed(0);
var index = elementOffset + ' + ' + componentOffset;
return 'this._structArray.' + getArrayViewName(member.type) + '[' + index + ']';
}
function createGetter(member, c) {
return new Function([], 'return ' + createMemberComponentString(member, c) + ';');
}
function createSetter(member, c) {
return new Function(['x'], createMemberComponentString(member, c) + ' = x;');
}
/**
* @class Struct
* @param {StructArray} structArray The StructArray the struct is stored in
* @param {number} index The index of the struct in the StructArray.
* @private
*/
function Struct(structArray, index) {
this._structArray = structArray;
this._pos1 = index * this.size;
this._pos2 = this._pos1 / 2;
this._pos4 = this._pos1 / 4;
this._pos8 = this._pos1 / 8;
}
/**
* @class StructArray
* The StructArray class is inherited by the custom StructArrayType classes created with
* `new StructArrayType(members, options)`.
* @private
*/
function StructArray(serialized) {
if (serialized !== undefined) {
// Create from an serialized StructArray
this.arrayBuffer = serialized.arrayBuffer;
this.length = serialized.length;
this.capacity = this.arrayBuffer.byteLength / this.bytesPerElement;
this._refreshViews();
// Create a new StructArray
} else {
this.capacity = -1;
this.resize(0);
}
}
/**
* @property {number}
* @private
* @readonly
*/
StructArray.prototype.DEFAULT_CAPACITY = 128;
/**
* @property {number}
* @private
* @readonly
*/
StructArray.prototype.RESIZE_MULTIPLIER = 5;
/**
* Serialize this StructArray instance
* @private
*/
StructArray.prototype.serialize = function() {
this.trim();
return {
length: this.length,
arrayBuffer: this.arrayBuffer
};
};
/**
* Return the Struct at the given location in the array.
* @private
* @param {number} index The index of the element.
*/
StructArray.prototype.get = function(index) {
return new this.StructType(this, index);
};
/**
* Resize the array to discard unused capacity.
* @private
*/
StructArray.prototype.trim = function() {
if (this.length !== this.capacity) {
this.capacity = this.length;
this.arrayBuffer = this.arrayBuffer.slice(0, this.length * this.bytesPerElement);
this._refreshViews();
}
};
/**
* Resize the array.
* If `n` is greater than the current length then additional elements with undefined values are added.
* If `n` is less than the current length then the array will be reduced to the first `n` elements.
* @param {number} n The new size of the array.
*/
StructArray.prototype.resize = function(n) {
this.length = n;
if (n > this.capacity) {
this.capacity = Math.max(n, Math.floor(this.capacity * this.RESIZE_MULTIPLIER), this.DEFAULT_CAPACITY);
this.arrayBuffer = new ArrayBuffer(this.capacity * this.bytesPerElement);
var oldUint8Array = this.uint8;
this._refreshViews();
if (oldUint8Array) this.uint8.set(oldUint8Array);
}
};
/**
* Create TypedArray views for the current ArrayBuffer.
* @private
*/
StructArray.prototype._refreshViews = function() {
for (var t = 0; t < this._usedTypes.length; t++) {
var type = this._usedTypes[t];
this[getArrayViewName(type)] = new viewTypes[type](this.arrayBuffer);
}
};
/**
* Output the `StructArray` between indices `startIndex` and `endIndex` as an array of `StructTypes` to enable sorting
* @param {number} startIndex
* @param {number} endIndex
* @private
*/
StructArray.prototype.toArray = function(startIndex, endIndex) {
var array = [];
for (var i = startIndex; i < endIndex; i++) {
var struct = this.get(i);
array.push(struct);
}
return array;
};