Skip to content

Commit

Permalink
Refactored Bucket class
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas Wojciechowski committed Oct 24, 2015
1 parent b38be16 commit ff303f6
Show file tree
Hide file tree
Showing 21 changed files with 704 additions and 531 deletions.
228 changes: 228 additions & 0 deletions js/data/bucket.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
'use strict';

var featureFilter = require('feature-filter');

var StyleDeclarationSet = require('../style/style_declaration_set');
var LayoutProperties = require('../style/layout_properties');
var ElementGroups = require('./element_groups');
var Buffer = require('./buffer');
var assert = require('assert');

module.exports = Bucket;

/**
* Instantiate the appropriate subclass of `Bucket` for `options`.
* @private
* @param options See `Bucket` constructor options
* @returns {Bucket}
*/
Bucket.create = function(options) {
var Classes = {
fill: require('./fill_bucket'),
line: require('./line_bucket'),
circle: require('./circle_bucket'),
symbol: require('./symbol_bucket')
};
return new Classes[options.layer.type](options);
};

Bucket.AttributeType = Buffer.AttributeType;

/**
* The `Bucket` class builds a set of `Buffer`s for a set of vector tile
* features.
*
* `Bucket` is an abstract class. A subclass exists for each Mapbox GL
* style spec layer type. Because `Bucket` is an abstract class,
* instances should be created via the `Bucket.create` method.
*
* For performance reasons, `Bucket` creates its "add"s methods at
* runtime using `new Function(...)`.
*
* @class Bucket
* @private
* @param options
* @param {number} options.zoom Zoom level of the buffers being built. May be
* a fractional zoom level.
* @param options.layer A Mapbox GL style layer object
* @param {Object.<string, Buffer>} options.buffers The set of `Buffer`s being
* built for this tile. This object facilitates sharing of `Buffer`s be
between `Bucket`s.
*/
function Bucket(options) {
this.layer = options.layer;
this.zoom = options.zoom;

this.layers = [this.layer.id];
this.type = this.layer.type;
this.features = [];
this.id = this.layer.id;
this['source-layer'] = this.layer['source-layer'];
this.interactive = this.layer.interactive;
this.minZoom = this.layer.minzoom;
this.maxZoom = this.layer.maxzoom;
this.filter = featureFilter(this.layer.filter);

this.layoutProperties = createLayoutProperties(this.layer, this.zoom);

this.resetBuffers(options.buffers);

this.add = {};
for (var shaderName in this.shaders) {
var shader = this.shaders[shaderName];

this.add[shaderName] = {
vertex: createVertexAddMethod(shaderName, shader).bind(this),
element: createElementAddMethod(shaderName, shader, false).bind(this),
secondElement: createElementAddMethod(shaderName, shader, true).bind(this)
};
}
}

/**
* Build the buffers! Features are set directly to the `features` property.
* @private
*/
Bucket.prototype.addFeatures = function() {
for (var i = 0; i < this.features.length; i++) {
this.addFeature(this.features[i]);
}
};

/**
* Check if there is enough space available in the current element group for
* `vertexLength` vertices. If not, append a new elementGroup. Should be called
* by `addFeatures` and its callees.
* @private
* @param {string} shaderName the name of the shader associated with the buffer that will receive the vertices
* @param {number} vertexLength The number of vertices that will be inserted to the buffer.
*/
Bucket.prototype.makeRoomFor = function(shaderName, vertexLength) {
this.elementGroups[shaderName].makeRoomFor(vertexLength);
};

/**
* Start using a new shared `buffers` object and recreate instances of `Buffer`
* as necessary.
* @private
* @param {Object.<string, Buffer>} buffers
*/
Bucket.prototype.resetBuffers = function(buffers) {
this.buffers = buffers;

for (var shaderName in this.shaders) {
var shader = this.shaders[shaderName];

var vertexBufferName = shader.vertexBuffer;
if (vertexBufferName && !buffers[vertexBufferName]) {
buffers[vertexBufferName] = new Buffer({
type: Buffer.BufferType.VERTEX,
attributes: shader.attributes
});
}

var elementBufferName = shader.elementBuffer;
if (elementBufferName && !buffers[elementBufferName]) {
buffers[elementBufferName] = createElementBuffer(shader.elementBufferComponents);
}

var secondElementBufferName = shader.secondElementBuffer;
if (secondElementBufferName && !buffers[secondElementBufferName]) {
buffers[secondElementBufferName] = createElementBuffer(shader.secondElementBufferComponents);
}
}

this.elementGroups = createElementGroups(this.shaders, this.buffers);
};

function createLayoutProperties(layer, zoom) {
var values = new StyleDeclarationSet('layout', layer.type, layer.layout, {}).values();
var fakeZoomHistory = { lastIntegerZoom: Infinity, lastIntegerZoomTime: 0, lastZoom: 0 };

var layout = {};
for (var k in values) {
layout[k] = values[k].calculate(zoom, fakeZoomHistory);
}

if (layer.type === 'symbol') {
// To reduce the number of labels that jump around when zooming we need
// to use a text-size value that is the same for all zoom levels.
// This calculates text-size at a high zoom level so that all tiles can
// use the same value when calculating anchor positions.
if (values['text-size']) {
layout['text-max-size'] = values['text-size'].calculate(18, fakeZoomHistory);
layout['text-size'] = values['text-size'].calculate(zoom + 1, fakeZoomHistory);
}
if (values['icon-size']) {
layout['icon-max-size'] = values['icon-size'].calculate(18, fakeZoomHistory);
layout['icon-size'] = values['icon-size'].calculate(zoom + 1, fakeZoomHistory);
}
}

return new LayoutProperties[layer.type](layout);
}

function createVertexAddMethod(shaderName, shader) {
if (!shader.vertexBuffer) return null;

// Find max arg length of all attribute value functions
var argCount = 0;
for (var i = 0; i < shader.attributes.length; i++) {
var attribute = shader.attributes[i];
argCount = Math.max(attribute.value.length, argCount);
}

var argIds = [];
for (var j = 0; j < argCount; j++) {
argIds.push('a' + j);
}

var body = '';
body += 'var attributes = this.shaders.' + shaderName + '.attributes;\n';
body += 'var elementGroups = this.elementGroups.' + shaderName + ';\n';
body += 'elementGroups.current.vertexLength++;\n';
body += 'return this.buffers.' + shader.vertexBuffer + '.push(\n';

for (var k = 0; k < shader.attributes.length; k++) {
body += ' attributes[' + k + '].value(' + argIds.join(', ') + ')';
body += (k !== shader.attributes.length - 1) ? ',\n' : '';
}
body += '\n) - elementGroups.current.vertexStartIndex;';

return new Function(argIds, body);
}

function createElementAddMethod(shaderName, shader, isSecond) {
var bufferName = isSecond ? shader.secondElementBuffer : shader.elementBuffer;
if (!bufferName) return function() { assert(false); };
var lengthName = isSecond ? 'secondElementLength' : 'elementLength';

return function() {
this.elementGroups[shaderName].current[lengthName]++;
return this.buffers[bufferName].push(arguments);
};
}

function createElementGroups(shaders, buffers) {
var elementGroups = {};
for (var shaderName in shaders) {
var shader = shaders[shaderName];
elementGroups[shaderName] = new ElementGroups(
buffers[shader.vertexBuffer],
buffers[shader.elementBuffer],
buffers[shader.secondElementBuffer]
);
}
return elementGroups;
}

function createElementBuffer(components) {
return new Buffer({
type: Buffer.BufferType.ELEMENT,
attributes: [{
name: 'vertices',
components: components || 3,
type: Buffer.ELEMENT_ATTRIBUTE_TYPE
}]
});
}
48 changes: 34 additions & 14 deletions js/data/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

// Note: all "sizes" are measured in bytes

var util = require('../util/util');
var assert = require('assert');

/**
Expand Down Expand Up @@ -46,23 +45,26 @@ function Buffer(options) {
// element buffer attributes do not need to be aligned.
var attributeAlignment = this.type === Buffer.BufferType.VERTEX ? Buffer.VERTEX_ATTRIBUTE_ALIGNMENT : 1;

for (var i = 0; i < options.attributes.length; i++) {
var attribute = util.extend({}, options.attributes[i]);

attribute.components = attribute.components || 1;
attribute.type = attribute.type || Buffer.AttributeType.UNSIGNED_BYTE;
this.attributes = options.attributes.map(function(attributeOptions) {
var attribute = {};

attribute.name = attributeOptions.name;
attribute.components = attributeOptions.components || 1;
attribute.type = attributeOptions.type || Buffer.AttributeType.UNSIGNED_BYTE;
attribute.size = attribute.type.size * attribute.components;
attribute.offset = this.itemSize;

this.attributes.push(attribute);
this.itemSize = align(attribute.offset + attribute.size, attributeAlignment);

assert(!isNaN(this.itemSize));
assert(!isNaN(attribute.size));
assert(attribute.type.name in Buffer.AttributeType);
}

return attribute;
}, this);

// These are expensive calls. Because we only push things to buffers in
// the worker thread, we can skip in the "clone an existing buffer" case.
this._createPushMethod();
this._refreshViews();
}
Expand Down Expand Up @@ -117,12 +119,14 @@ Buffer.prototype.setAttribPointers = function(gl, shader, offset) {
};

/**
* Get an item from the `ArrayBuffer`.
* Get an item from the `ArrayBuffer`. Only used for debugging.
* @private
* @param {number} index The index of the item to get
* @returns {Object.<string, Array.<number>>}
*/
Buffer.prototype.get = function(index) {
this._refreshViews();

var item = {};
var offset = index * this.itemSize;

Expand All @@ -138,6 +142,23 @@ Buffer.prototype.get = function(index) {
return item;
};

/**
* Check that a buffer item is well formed and throw an error if not. Only
* used for debugging.
* @private
* @param {number} args The "arguments" object from Buffer::push
*/
Buffer.prototype.validate = function(args) {
assert(args.length === this.attributes.length);
for (var i = 0; i < args.length; i++) {
var attribute = this.attributes[i];
assert(args[i].length === attribute.components);
for (var j = 0; j < attribute.components; j++) {
assert(!isNaN(args[i][j]));
}
}
};

Buffer.prototype._resize = function(capacity) {
var old = this.views.UNSIGNED_BYTE;
this.capacity = align(capacity, Buffer.CAPACITY_ALIGNMENT);
Expand Down Expand Up @@ -166,16 +187,15 @@ Buffer.prototype._createPushMethod = function() {
for (var i = 0; i < this.attributes.length; i++) {
var attribute = this.attributes[i];
var offsetId = 'offset' + i;
var argId = 'value' + i;
argNames.push(argId);

body += '\nvar ' + offsetId + ' = (offset + ' + attribute.offset + ') / ' + attribute.type.size + ';\n';

for (var j = 0; j < attribute.components; j++) {
var valueId = 'value' + i + '_' + j;
var rvalue = argId + '[' + j + ']';
var lvalue = 'this.views.' + attribute.type.name + '[' + offsetId + ' + ' + j + ']';

body += lvalue + ' = ' + valueId + ';\n';

argNames.push(valueId);
body += lvalue + ' = ' + rvalue + ';\n';
}
}

Expand Down
60 changes: 0 additions & 60 deletions js/data/buffer_set.js

This file was deleted.

Loading

0 comments on commit ff303f6

Please sign in to comment.