From 2cc58b4691053541a5acb162b4447cc5abe44ce0 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Wed, 27 Jul 2016 17:36:56 -0400 Subject: [PATCH 1/9] Unify node/browser dispatcher code Instead of having a mock Dispatcher for node, pull out the relevant code into a small wrapper, web_worker.js, which contains the node/browser code split to a smaller scope. --- js/util/browser/dispatcher.js | 73 ------------------------ js/util/browser/web_worker.js | 6 ++ js/util/dispatcher.js | 102 ++++++++++++++++++++-------------- js/util/web_worker.js | 42 ++++++++++++++ package.json | 2 +- 5 files changed, 108 insertions(+), 117 deletions(-) delete mode 100644 js/util/browser/dispatcher.js create mode 100644 js/util/browser/web_worker.js create mode 100644 js/util/web_worker.js diff --git a/js/util/browser/dispatcher.js b/js/util/browser/dispatcher.js deleted file mode 100644 index 605d5b3bbdb..00000000000 --- a/js/util/browser/dispatcher.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -var util = require('../util'); -var Actor = require('../actor'); -var WebWorkify = require('webworkify'); - -module.exports = Dispatcher; - -/** - * Responsible for sending messages from a {@link Source} to an associated - * {@link WorkerSource}. - * - * @interface Dispatcher - * @private - */ -function Dispatcher(length, parent) { - this.actors = []; - this.currentActor = 0; - for (var i = 0; i < length; i++) { - var worker = new WebWorkify(require('../../source/worker')); - var actor = new Actor(worker, parent); - actor.name = "Worker " + i; - this.actors.push(actor); - } -} - -Dispatcher.prototype = { - /** - * Broadcast a message to all Workers. - * @method - * @name broadcast - * @param {string} type - * @param {object} data - * @param {Function} callback - * @memberof Dispatcher - * @instance - */ - broadcast: function(type, data, cb) { - cb = cb || function () {}; - util.asyncAll(this.actors, function (actor, done) { - actor.send(type, data, done); - }, cb); - }, - - /** - * Send a message to a Worker. - * @method - * @name send - * @param {string} type - * @param {object} data - * @param {Function} callback - * @param {number|undefined} [targetID] The ID of the Worker to which to send this message. Omit to allow the dispatcher to choose. - * @returns {number} The ID of the worker to which the message was sent. - * @memberof Dispatcher - * @instance - */ - send: function(type, data, callback, targetID, buffers) { - if (typeof targetID !== 'number' || isNaN(targetID)) { - // Use round robin to send requests to web workers. - targetID = this.currentActor = (this.currentActor + 1) % this.actors.length; - } - - this.actors[targetID].send(type, data, callback, buffers); - return targetID; - }, - - remove: function() { - for (var i = 0; i < this.actors.length; i++) { - this.actors[i].target.terminate(); - } - this.actors = []; - } -}; diff --git a/js/util/browser/web_worker.js b/js/util/browser/web_worker.js new file mode 100644 index 00000000000..7717220b6cf --- /dev/null +++ b/js/util/browser/web_worker.js @@ -0,0 +1,6 @@ +'use strict'; +var WebWorkify = require('webworkify'); + +module.exports = function () { + return new WebWorkify(require('../../source/worker')); +}; diff --git a/js/util/dispatcher.js b/js/util/dispatcher.js index 3406eaef8f8..7ca9bd7c367 100644 --- a/js/util/dispatcher.js +++ b/js/util/dispatcher.js @@ -1,57 +1,73 @@ 'use strict'; -var Worker = require('../source/worker'); -var Actor = require('../util/actor'); +var util = require('./util'); +var Actor = require('./actor'); +var WebWorker = require('./web_worker'); module.exports = Dispatcher; -function MessageBus(addListeners, postListeners) { - return { - addEventListener: function(event, callback) { - if (event === 'message') { - addListeners.push(callback); - } - }, - postMessage: function(data) { - setImmediate(function() { - for (var i = 0; i < postListeners.length; i++) { - postListeners[i]({data: data, target: this.target}); - } - }.bind(this)); - } - }; -} - +/** + * Responsible for sending messages from a {@link Source} to an associated + * {@link WorkerSource}. + * + * @interface Dispatcher + * @private + */ function Dispatcher(length, parent) { - - this.actors = new Array(length); - - var parentListeners = [], - workerListeners = [], - parentBus = new MessageBus(workerListeners, parentListeners), - workerBus = new MessageBus(parentListeners, workerListeners); - - parentBus.target = workerBus; - workerBus.target = parentBus; - // workerBus substitutes the WebWorker global `self`, and Worker uses - // self.importScripts for the 'load worker source' target. - workerBus.importScripts = function () {}; - - this.worker = new Worker(workerBus); - this.actor = new Actor(parentBus, parent); - - this.remove = function() { - parentListeners.splice(0, parentListeners.length); - workerListeners.splice(0, workerListeners.length); - }; + this.actors = []; + this.currentActor = 0; + for (var i = 0; i < length; i++) { + var worker = new WebWorker(); + var actor = new Actor(worker, parent); + actor.name = "Worker " + i; + this.actors.push(actor); + } } Dispatcher.prototype = { - broadcast: function(type, data, callback) { - this.actor.send(type, data, callback); + /** + * Broadcast a message to all Workers. + * @method + * @name broadcast + * @param {string} type + * @param {object} data + * @param {Function} callback + * @memberof Dispatcher + * @instance + */ + broadcast: function(type, data, cb) { + cb = cb || function () {}; + util.asyncAll(this.actors, function (actor, done) { + actor.send(type, data, done); + }, cb); }, + /** + * Send a message to a Worker. + * @method + * @name send + * @param {string} type + * @param {object} data + * @param {Function} callback + * @param {number|undefined} [targetID] The ID of the Worker to which to send this message. Omit to allow the dispatcher to choose. + * @returns {number} The ID of the worker to which the message was sent. + * @memberof Dispatcher + * @instance + */ send: function(type, data, callback, targetID, buffers) { - this.actor.send(type, data, callback, buffers); + if (typeof targetID !== 'number' || isNaN(targetID)) { + // Use round robin to send requests to web workers. + targetID = this.currentActor = (this.currentActor + 1) % this.actors.length; + } + + this.actors[targetID].send(type, data, callback, buffers); + return targetID; + }, + + remove: function() { + for (var i = 0; i < this.actors.length; i++) { + this.actors[i].target.terminate(); + } + this.actors = []; } }; diff --git a/js/util/web_worker.js b/js/util/web_worker.js new file mode 100644 index 00000000000..3a3d9196975 --- /dev/null +++ b/js/util/web_worker.js @@ -0,0 +1,42 @@ +'use strict'; + +var Worker = require('../source/worker'); + +module.exports = function () { + var parentListeners = [], + workerListeners = [], + parentBus = new MessageBus(workerListeners, parentListeners), + workerBus = new MessageBus(parentListeners, workerListeners); + + parentBus.target = workerBus; + workerBus.target = parentBus; + // workerBus substitutes the WebWorker global `self`, and Worker uses + // self.importScripts for the 'load worker source' target. + workerBus.importScripts = function () {}; + + new Worker(workerBus); // eslint-disable-line no-new + + return parentBus; +}; + +function MessageBus(addListeners, postListeners) { + return { + addEventListener: function(event, callback) { + if (event === 'message') { + addListeners.push(callback); + } + }, + postMessage: function(data) { + setImmediate(function() { + for (var i = 0; i < postListeners.length; i++) { + postListeners[i]({data: data, target: this.target}); + } + }.bind(this)); + }, + terminate: function() { + addListeners.splice(0, addListeners.length); + postListeners.splice(0, postListeners.length); + } + }; +} + diff --git a/package.json b/package.json index 2ccbeb7fc88..df8999f5a2c 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "./js/util/browser.js": "./js/util/browser/browser.js", "./js/util/canvas.js": "./js/util/browser/canvas.js", "./js/util/dom.js": "./js/util/browser/dom.js", - "./js/util/dispatcher.js": "./js/util/browser/dispatcher.js" + "./js/util/web_worker.js": "./js/util/browser/web_worker.js" }, "scripts": { "build-dev": "browserify js/mapbox-gl.js --debug --ignore-transform unassertify --standalone mapboxgl > dist/mapbox-gl-dev.js && tap --no-coverage test/build/dev.test.js", From 02614e7fae22bcae737f37fe761c60f078088c08 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Wed, 27 Jul 2016 18:39:23 -0400 Subject: [PATCH 2/9] Add a mapInstanceId param to inter-thread methods This prepares for workers being shared across map instances. --- js/source/geojson_worker_source.js | 2 +- js/source/vector_tile_worker_source.js | 8 ++++---- js/source/worker.js | 26 +++++++++++++------------- js/style/style.js | 6 +++--- js/util/actor.js | 11 +++++++---- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/js/source/geojson_worker_source.js b/js/source/geojson_worker_source.js index 205199c52a4..bd1ba677956 100644 --- a/js/source/geojson_worker_source.js +++ b/js/source/geojson_worker_source.js @@ -69,7 +69,7 @@ GeoJSONWorkerSource.prototype = util.inherit(VectorTileWorkerSource, /** @lends * @param {string} params.source The id of the source. * @param {Function} callback */ - loadData: function (params, callback) { + loadData: function (map, params, callback) { var handleData = function(err, data) { if (err) return callback(err); if (typeof data != 'object') { diff --git a/js/source/vector_tile_worker_source.js b/js/source/vector_tile_worker_source.js index b3e23ca2492..50ebcb02b7c 100644 --- a/js/source/vector_tile_worker_source.js +++ b/js/source/vector_tile_worker_source.js @@ -41,7 +41,7 @@ VectorTileWorkerSource.prototype = { * @param {number} params.pitch * @param {boolean} params.showCollisionBoxes */ - loadTile: function(params, callback) { + loadTile: function(map, params, callback) { var source = params.source, uid = params.uid; @@ -72,7 +72,7 @@ VectorTileWorkerSource.prototype = { * @param {string} params.source The id of the source for which we're loading this tile. * @param {string} params.uid The UID for this tile. */ - reloadTile: function(params, callback) { + reloadTile: function(map, params, callback) { var loaded = this.loaded[params.source], uid = params.uid; if (loaded && loaded[uid]) { @@ -88,7 +88,7 @@ VectorTileWorkerSource.prototype = { * @param {string} params.source The id of the source for which we're loading this tile. * @param {string} params.uid The UID for this tile. */ - abortTile: function(params) { + abortTile: function(map, params) { var loading = this.loading[params.source], uid = params.uid; if (loading && loading[uid] && loading[uid].abort) { @@ -104,7 +104,7 @@ VectorTileWorkerSource.prototype = { * @param {string} params.source The id of the source for which we're loading this tile. * @param {string} params.uid The UID for this tile. */ - removeTile: function(params) { + removeTile: function(map, params) { var loaded = this.loaded[params.source], uid = params.uid; if (loaded && loaded[uid]) { diff --git a/js/source/worker.js b/js/source/worker.js index 5aeebfd3464..1827fe64290 100644 --- a/js/source/worker.js +++ b/js/source/worker.js @@ -35,7 +35,7 @@ function Worker(self) { } util.extend(Worker.prototype, { - 'set layers': function(layers) { + 'set layers': function(map, layers) { this.layers = {}; var that = this; @@ -69,7 +69,7 @@ util.extend(Worker.prototype, { this.layerFamilies = createLayerFamilies(this.layers); }, - 'update layers': function(layers) { + 'update layers': function(map, layers) { var that = this; var id; var layer; @@ -99,29 +99,29 @@ util.extend(Worker.prototype, { this.layerFamilies = createLayerFamilies(this.layers); }, - 'load tile': function(params, callback) { + 'load tile': function(map, params, callback) { var type = params.type || 'vector'; - this.workerSources[type].loadTile(params, callback); + this.workerSources[type].loadTile(map, params, callback); }, - 'reload tile': function(params, callback) { + 'reload tile': function(map, params, callback) { var type = params.type || 'vector'; - this.workerSources[type].reloadTile(params, callback); + this.workerSources[type].reloadTile(map, params, callback); }, - 'abort tile': function(params) { + 'abort tile': function(map, params) { var type = params.type || 'vector'; - this.workerSources[type].abortTile(params); + this.workerSources[type].abortTile(map, params); }, - 'remove tile': function(params) { + 'remove tile': function(map, params) { var type = params.type || 'vector'; - this.workerSources[type].removeTile(params); + this.workerSources[type].removeTile(map, params); }, - 'redo placement': function(params, callback) { + 'redo placement': function(map, params, callback) { var type = params.type || 'vector'; - this.workerSources[type].redoPlacement(params, callback); + this.workerSources[type].redoPlacement(map, params, callback); }, /** @@ -130,7 +130,7 @@ util.extend(Worker.prototype, { * function taking `(name, workerSourceObject)`. * @private */ - 'load worker source': function(params, callback) { + 'load worker source': function(map, params, callback) { try { this.self.importScripts(params.url); callback(); diff --git a/js/style/style.js b/js/style/style.js index d6a30f90013..3a1a0b17cea 100644 --- a/js/style/style.js +++ b/js/style/style.js @@ -730,7 +730,7 @@ Style.prototype = util.inherit(Evented, { // Callbacks from web workers - 'get sprite json': function(params, callback) { + 'get sprite json': function(_, params, callback) { var sprite = this.sprite; if (sprite.loaded()) { callback(null, { sprite: sprite.data, retina: sprite.retina }); @@ -741,7 +741,7 @@ Style.prototype = util.inherit(Evented, { } }, - 'get icons': function(params, callback) { + 'get icons': function(_, params, callback) { var sprite = this.sprite; var spriteAtlas = this.spriteAtlas; if (sprite.loaded()) { @@ -755,7 +755,7 @@ Style.prototype = util.inherit(Evented, { } }, - 'get glyphs': function(params, callback) { + 'get glyphs': function(_, params, callback) { var stacks = params.stacks, remaining = Object.keys(stacks).length, allGlyphs = {}; diff --git a/js/util/actor.js b/js/util/actor.js index dc09086cfeb..a8e9f25f89c 100644 --- a/js/util/actor.js +++ b/js/util/actor.js @@ -10,11 +10,13 @@ module.exports = Actor; * * @param {WebWorker} target * @param {WebWorker} parent + * @param {string|number} mapInstanceId A unique identifier for the Map instance using this Actor. * @private */ -function Actor(target, parent) { +function Actor(target, parent, mapInstanceId) { this.target = target; this.parent = parent; + this.mapInstanceId = mapInstanceId; this.callbacks = {}; this.callbackID = 0; this.receive = this.receive.bind(this); @@ -32,17 +34,18 @@ Actor.prototype.receive = function(message) { if (callback) callback(data.error || null, data.data); } else if (typeof data.id !== 'undefined' && this.parent[data.type]) { // data.type == 'load tile', 'remove tile', etc. - this.parent[data.type](data.data, done.bind(this)); + this.parent[data.type](data.mapInstanceId, data.data, done.bind(this)); } else if (typeof data.id !== 'undefined' && this.parent.workerSources) { // data.type == sourcetype.method var keys = data.type.split('.'); - this.parent.workerSources[keys[0]][keys[1]](data.data, done.bind(this)); + this.parent.workerSources[keys[0]][keys[1]](data.mapInstanceId, data.data, done.bind(this)); } else { this.parent[data.type](data.data); } function done(err, data, buffers) { this.postMessage({ + mapInstanceId: this.mapInstanceId, type: '', id: String(id), error: err ? String(err) : null, @@ -54,7 +57,7 @@ Actor.prototype.receive = function(message) { Actor.prototype.send = function(type, data, callback, buffers) { var id = null; if (callback) this.callbacks[id = this.callbackID++] = callback; - this.postMessage({ type: type, id: String(id), data: data }, buffers); + this.postMessage({ mapInstanceId: this.mapInstanceId, type: type, id: String(id), data: data }, buffers); }; /** From 50b4c088b0c61f52213326e51f2cd68c2fb57bf9 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Thu, 28 Jul 2016 10:32:46 -0400 Subject: [PATCH 3/9] WIP - share workers across Map instances --- debug/multiple.html | 58 ++++++++------- js/source/geojson_source.js | 14 ++-- js/source/source_cache.js | 2 +- js/source/tile.js | 4 +- js/source/vector_tile_source.js | 10 +-- js/source/vector_tile_worker_source.js | 43 +++++++---- js/source/worker.js | 98 ++++++++++++++++++++------ js/source/worker_tile.js | 2 +- js/style/style.js | 7 +- js/util/actor.js | 13 ++-- js/util/dispatcher.js | 32 +++------ js/util/util.js | 15 ++++ js/util/worker_pool.js | 85 ++++++++++++++++++++++ 13 files changed, 275 insertions(+), 108 deletions(-) create mode 100644 js/util/worker_pool.js diff --git a/debug/multiple.html b/debug/multiple.html index 9cd0488cfe8..068340dd898 100644 --- a/debug/multiple.html +++ b/debug/multiple.html @@ -8,13 +8,10 @@ -
-
- @@ -27,6 +24,10 @@ "url": "mapbox://mapbox.satellite", "tileSize": 256 }, + "street": { + "type": "vector", + "url": "mapbox://mapbox.mapbox-streets-v7", + } }, "layers": [{ "id": "background", @@ -38,28 +39,38 @@ "id": "satellite", "type": "raster", "source": "satellite" + }, { + "id": "streets", + "type": "line", + "source": "street", + "source-layer": "road", + "paint": { + "line-color": "#aaff00", + "line-width": 5 + } }] }; -var map1 = new mapboxgl.Map({ - container: 'map1', - minZoom: 14, - zoom: 17, - center: [-122.514426, 37.562984], - bearing: -96, - style: style, - hash: false -}); +document.addEventListener('DOMContentLoaded', function () { + var count = window.location.hash.substring(1) + for (var i = 0; i < (isNaN(count) ? 2 : count); i++) { + var div = document.createElement('div') + var id = div.id = 'map' + i + div.className = 'map' + document.body.appendChild(div) + var map = new mapboxgl.Map({ + container: id, + minZoom: 14, + zoom: 14, + center: [-122.514426, 37.562984], + bearing: -96, + style: style, + hash: false + }) -var map2 = new mapboxgl.Map({ - container: 'map2', - minZoom: 14, - zoom: 17, - center: [-122.514426, 37.562984], - bearing: -96, - style: style, - hash: false -}); + map.on('click', popup.bind(map)); + } +}) function popup(e) { if (e.originalEvent.shiftKey) return; @@ -69,9 +80,6 @@ .addTo(this); } -map1.on('click', popup.bind(map1)); -map2.on('click', popup.bind(map2)); - diff --git a/js/source/geojson_source.js b/js/source/geojson_source.js index 3bb5844a7e3..45df0032e89 100644 --- a/js/source/geojson_source.js +++ b/js/source/geojson_source.js @@ -138,14 +138,16 @@ GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototy options.data = JSON.stringify(data); } + var workerKey = options.url || options.data; + // target {this.type}.loadData rather than literally geojson.loadData, // so that other geojson-like source types can easily reuse this // implementation - this.workerID = this.dispatcher.send(this.type + '.loadData', options, function(err) { + this.dispatcher.send(this.type + '.loadData', options, function(err) { this._loaded = true; + this.workerKey = workerKey; callback(err); - - }.bind(this)); + }.bind(this), workerKey); }, loadTile: function (tile, callback) { @@ -164,7 +166,7 @@ GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototy showCollisionBoxes: this.map.showCollisionBoxes }; - tile.workerID = this.dispatcher.send('load tile', params, function(err, data) { + this.dispatcher.send('load tile', params, function(err, data) { tile.unloadVectorData(this.map.painter); @@ -184,7 +186,7 @@ GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototy return callback(null); - }.bind(this), this.workerID); + }.bind(this), this.workerKey); }, abortTile: function(tile) { @@ -193,7 +195,7 @@ GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototy unloadTile: function(tile) { tile.unloadVectorData(this.map.painter); - this.dispatcher.send('remove tile', { uid: tile.uid, source: this.id }, function() {}, tile.workerID); + this.dispatcher.send('remove tile', { uid: tile.uid, source: this.id }, function() {}, this.workerKey); }, serialize: function() { diff --git a/js/source/source_cache.js b/js/source/source_cache.js index c72a10dd4ce..2f81fef1f40 100644 --- a/js/source/source_cache.js +++ b/js/source/source_cache.js @@ -387,7 +387,7 @@ SourceCache.prototype = util.inherit(Evented, { if (!tile) { var zoom = coord.z; var overscaling = zoom > this.maxzoom ? Math.pow(2, zoom - this.maxzoom) : 1; - tile = new Tile(wrapped, this.tileSize * overscaling, this.maxzoom); + tile = new Tile(this.id + coord.id, wrapped, this.tileSize * overscaling, this.maxzoom); this.loadTile(tile, this._tileLoaded.bind(this, tile)); } diff --git a/js/source/tile.js b/js/source/tile.js index 1c223546a01..fe720c412e4 100644 --- a/js/source/tile.js +++ b/js/source/tile.js @@ -22,9 +22,9 @@ module.exports = Tile; * @param {number} size * @private */ -function Tile(coord, size, sourceMaxZoom) { +function Tile(id, coord, size, sourceMaxZoom) { this.coord = coord; - this.uid = util.uniqueId(); + this.uid = id; this.uses = 0; this.tileSize = size; this.sourceMaxZoom = sourceMaxZoom; diff --git a/js/source/vector_tile_source.js b/js/source/vector_tile_source.js index 05bd1206f5e..fedfc2f225d 100644 --- a/js/source/vector_tile_source.js +++ b/js/source/vector_tile_source.js @@ -58,11 +58,11 @@ VectorTileSource.prototype = util.inherit(Evented, { showCollisionBoxes: this.map.showCollisionBoxes }; - if (tile.workerID) { + if (tile.state === 'loaded') { params.rawTileData = tile.rawTileData; - this.dispatcher.send('reload tile', params, done.bind(this), tile.workerID); + this.dispatcher.send('reload tile', params, done.bind(this), tile.uid); } else { - tile.workerID = this.dispatcher.send('load tile', params, done.bind(this)); + this.dispatcher.send('load tile', params, done.bind(this), tile.uid); } function done(err, data) { @@ -85,11 +85,11 @@ VectorTileSource.prototype = util.inherit(Evented, { }, abortTile: function(tile) { - this.dispatcher.send('abort tile', { uid: tile.uid, source: this.id }, null, tile.workerID); + this.dispatcher.send('abort tile', { uid: tile.uid, source: this.id }, null, tile.uid); }, unloadTile: function(tile) { tile.unloadVectorData(this.map.painter); - this.dispatcher.send('remove tile', { uid: tile.uid, source: this.id }, null, tile.workerID); + this.dispatcher.send('remove tile', { uid: tile.uid, source: this.id }, null, tile.uid); } }); diff --git a/js/source/vector_tile_worker_source.js b/js/source/vector_tile_worker_source.js index 50ebcb02b7c..9d9777c3f96 100644 --- a/js/source/vector_tile_worker_source.js +++ b/js/source/vector_tile_worker_source.js @@ -11,15 +11,15 @@ module.exports = VectorTileWorkerSource; * This class is designed to be easily reused to support custom source types * for data formats that can be parsed/converted into an in-memory VectorTile * representation. To do so, create it with - * `new VectorTileWorkerSource(actor, styleLayers, customLoadVectorDataFunction)`. + * `new VectorTileWorkerSource(actor, styles, customLoadVectorDataFunction)`. * * @class VectorTileWorkerSource * @private * @param {Function} [loadVectorData] Optional method for custom loading of a VectorTile object based on parameters passed from the main-thread Source. See {@link VectorTileWorkerSource#loadTile}. The default implementation simply loads the pbf at `params.url`. */ -function VectorTileWorkerSource (actor, styleLayers, loadVectorData) { +function VectorTileWorkerSource (actor, styles, loadVectorData) { this.actor = actor; - this.styleLayers = styleLayers; + this.styles = styles; if (loadVectorData) { this.loadVectorData = loadVectorData; } @@ -42,26 +42,39 @@ VectorTileWorkerSource.prototype = { * @param {boolean} params.showCollisionBoxes */ loadTile: function(map, params, callback) { - var source = params.source, + var style = this.styles.getKey(map), + source = params.source, uid = params.uid; - if (!this.loading[source]) - this.loading[source] = {}; + var loading = this.loading[style + source]; + + if (!loading) + loading = this.loading[style + source] = {}; + + if (loading[uid]) { + loading[uid].callbacks.push(callback); + return; + } + + var tile = loading[uid] = new WorkerTile(params); + tile.callbacks = [callback]; + callback = function (err, data) { + tile.callbacks.forEach(function(cb) { cb(err, data); }); + }; - var tile = this.loading[source][uid] = new WorkerTile(params); tile.abort = this.loadVectorData(params, done.bind(this)); function done(err, data) { - delete this.loading[source][uid]; + delete loading[uid]; if (err) return callback(err); if (!data) return callback(null, null); tile.data = data.tile; - tile.parse(tile.data, this.styleLayers.getLayerFamilies(), this.actor, data.rawTileData, callback); + tile.parse(tile.data, this.styles.getLayerFamilies(map), this.actor, data.rawTileData, callback); - this.loaded[source] = this.loaded[source] || {}; - this.loaded[source][uid] = tile; + this.loaded[style + source] = this.loaded[style + source] || {}; + this.loaded[style + source][uid] = tile; } }, @@ -73,11 +86,11 @@ VectorTileWorkerSource.prototype = { * @param {string} params.uid The UID for this tile. */ reloadTile: function(map, params, callback) { - var loaded = this.loaded[params.source], + var loaded = this.loaded[this.styles.getKey(map) + params.source], uid = params.uid; if (loaded && loaded[uid]) { var tile = loaded[uid]; - tile.parse(tile.data, this.styleLayers.getLayerFamilies(), this.actor, params.rawTileData, callback); + tile.parse(tile.data, this.styles.getLayerFamilies(map), this.actor, params.rawTileData, callback); } }, @@ -89,7 +102,7 @@ VectorTileWorkerSource.prototype = { * @param {string} params.uid The UID for this tile. */ abortTile: function(map, params) { - var loading = this.loading[params.source], + var loading = this.loading[this.styles.getKey(map) + params.source], uid = params.uid; if (loading && loading[uid] && loading[uid].abort) { loading[uid].abort(); @@ -105,7 +118,7 @@ VectorTileWorkerSource.prototype = { * @param {string} params.uid The UID for this tile. */ removeTile: function(map, params) { - var loaded = this.loaded[params.source], + var loaded = this.loaded[this.styles.getKey(map) + params.source], uid = params.uid; if (loaded && loaded[uid]) { delete loaded[uid]; diff --git a/js/source/worker.js b/js/source/worker.js index 1827fe64290..d8cceee1307 100644 --- a/js/source/worker.js +++ b/js/source/worker.js @@ -1,5 +1,6 @@ 'use strict'; +var assert = require('assert'); var Actor = require('../util/actor'); var StyleLayer = require('../style/style_layer'); var util = require('../util/util'); @@ -15,10 +16,21 @@ function Worker(self) { this.self = self; this.actor = new Actor(self, this); + this.styles = {}; + this.layers = {}; + this.layerFamilies = {}; + // simple accessor object for passing to WorkerSources var styleLayers = { - getLayers: function () { return this.layers; }.bind(this), - getLayerFamilies: function () { return this.layerFamilies; }.bind(this) + getKey: function (map) { + return this.styles[map]; + }.bind(this), + getLayers: function (map) { + return this.layers[this.styles[map]]; + }.bind(this), + getLayerFamilies: function (map) { + return this.layerFamilies[this.styles[map]]; + }.bind(this) }; this.workerSources = { @@ -35,14 +47,31 @@ function Worker(self) { } util.extend(Worker.prototype, { - 'set layers': function(map, layers) { - this.layers = {}; - var that = this; + 'set style': function(map, style) { + // this.styles is an object mapping map id to a content-based key for + // the map instance's style. ideally, the key should be the same for + // two map instances whose style is identical. + var key = createKeyForStyle(style); + + var prevKey = this.styles[map]; + if (prevKey === key) return; + this.styles[map] = key; + + var shouldDeletePreviousLayers = util.values(this.styles) + .some(function (usedKey) { return usedKey === prevKey; }); + if (shouldDeletePreviousLayers) { + delete this.layers[prevKey]; + delete this.layerFamilies[prevKey]; + } + + if (this.layers[key]) return; + + var styleLayers = this.layers[key] = {}; // Filter layers and create an id -> layer map var childLayerIndicies = []; - for (var i = 0; i < layers.length; i++) { - var layer = layers[i]; + for (var i = 0; i < style.layers.length; i++) { + var layer = style.layers[i]; if (layer.type === 'fill' || layer.type === 'line' || layer.type === 'circle' || layer.type === 'symbol') { if (layer.ref) { childLayerIndicies.push(i); @@ -54,49 +83,72 @@ util.extend(Worker.prototype, { // Create an instance of StyleLayer per layer for (var j = 0; j < childLayerIndicies.length; j++) { - setLayer(layers[childLayerIndicies[j]]); + setLayer(style.layers[childLayerIndicies[j]]); } + this.layerFamilies[key] = createLayerFamilies(styleLayers); + function setLayer(serializedLayer) { var styleLayer = StyleLayer.create( serializedLayer, - serializedLayer.ref && that.layers[serializedLayer.ref] + serializedLayer.ref && styleLayers[serializedLayer.ref] ); styleLayer.updatePaintTransitions({}, {transition: false}); - that.layers[styleLayer.id] = styleLayer; + styleLayers[styleLayer.id] = styleLayer; } - - this.layerFamilies = createLayerFamilies(this.layers); }, - 'update layers': function(map, layers) { + 'update style': function(map, style) { var that = this; var id; var layer; + var key = createKeyForStyle(style); + + var prevKey = this.styles[map]; + if (prevKey === key) return; + assert(this.layers[prevKey]); + + this.styles[map] = key; + + // if the current style is being used by another map instance, then + // delegate to 'set style'. + var existingStyleIsUsed = util.values(this.styles) + .some(function (usedKey) { return usedKey === prevKey; }); + if (existingStyleIsUsed) + return this['set style'](map, style); + + var prevLayers = this.layers[prevKey]; + delete this.layers[prevKey]; + delete this.layerFamilies[prevKey]; + + if (this.layers[key]) return; + + this.layers[key] = prevLayers; + // Update ref parents - for (id in layers) { - layer = layers[id]; + for (id in style.layers) { + layer = style.layers[id]; if (layer.ref) updateLayer(layer); } // Update ref children - for (id in layers) { - layer = layers[id]; + for (id in style.layers) { + layer = style.layers[id]; if (!layer.ref) updateLayer(layer); } + this.layerFamilies[key] = createLayerFamilies(this.layers); + function updateLayer(layer) { - var refLayer = that.layers[layer.ref]; - if (that.layers[layer.id]) { + var refLayer = that.layers[prevKey][layer.ref]; + if (that.layers[prevKey][layer.id]) { that.layers[layer.id].set(layer, refLayer); } else { that.layers[layer.id] = StyleLayer.create(layer, refLayer); } that.layers[layer.id].updatePaintTransitions({}, {transition: false}); } - - this.layerFamilies = createLayerFamilies(this.layers); }, 'load tile': function(map, params, callback) { @@ -161,3 +213,7 @@ function createLayerFamilies(layers) { return families; } +function createKeyForStyle(style) { + return JSON.stringify(style); +} + diff --git a/js/source/worker_tile.js b/js/source/worker_tile.js index 68aaa4bd920..6d20e589641 100644 --- a/js/source/worker_tile.js +++ b/js/source/worker_tile.js @@ -24,7 +24,7 @@ function WorkerTile(params) { } WorkerTile.prototype.parse = function(data, layerFamilies, actor, rawTileData, callback) { - + console.log('PARSING TILE', this.source, this.coord.z + '/' + this.coord.x + '/' + this.coord.y, this.uid) this.status = 'parsing'; this.data = data; diff --git a/js/style/style.js b/js/style/style.js index 3a1a0b17cea..93a78b48e94 100644 --- a/js/style/style.js +++ b/js/style/style.js @@ -178,7 +178,12 @@ Style.prototype = util.inherit(Evented, { }, _updateWorkerLayers: function(ids) { - this.dispatcher.broadcast(ids ? 'update layers' : 'set layers', this._serializeLayers(ids)); + this.dispatcher.broadcast(ids ? 'update style' : 'set style', { + layers: this._serializeLayers(ids), + sources: util.mapObject(this.sources, function(source) { + return source.serialize(); + }) + }); }, _serializeLayers: function(ids) { diff --git a/js/util/actor.js b/js/util/actor.js index a8e9f25f89c..0f1e60a336d 100644 --- a/js/util/actor.js +++ b/js/util/actor.js @@ -10,13 +10,13 @@ module.exports = Actor; * * @param {WebWorker} target * @param {WebWorker} parent - * @param {string|number} mapInstanceId A unique identifier for the Map instance using this Actor. + * @param {string|undefined} parentId * @private */ -function Actor(target, parent, mapInstanceId) { +function Actor(target, parent, parentId) { this.target = target; this.parent = parent; - this.mapInstanceId = mapInstanceId; + this.parentId = parentId; this.callbacks = {}; this.callbackID = 0; this.receive = this.receive.bind(this); @@ -34,18 +34,17 @@ Actor.prototype.receive = function(message) { if (callback) callback(data.error || null, data.data); } else if (typeof data.id !== 'undefined' && this.parent[data.type]) { // data.type == 'load tile', 'remove tile', etc. - this.parent[data.type](data.mapInstanceId, data.data, done.bind(this)); + this.parent[data.type](data.parentId, data.data, done.bind(this)); } else if (typeof data.id !== 'undefined' && this.parent.workerSources) { // data.type == sourcetype.method var keys = data.type.split('.'); - this.parent.workerSources[keys[0]][keys[1]](data.mapInstanceId, data.data, done.bind(this)); + this.parent.workerSources[keys[0]][keys[1]](data.parentId, data.data, done.bind(this)); } else { this.parent[data.type](data.data); } function done(err, data, buffers) { this.postMessage({ - mapInstanceId: this.mapInstanceId, type: '', id: String(id), error: err ? String(err) : null, @@ -57,7 +56,7 @@ Actor.prototype.receive = function(message) { Actor.prototype.send = function(type, data, callback, buffers) { var id = null; if (callback) this.callbacks[id = this.callbackID++] = callback; - this.postMessage({ mapInstanceId: this.mapInstanceId, type: type, id: String(id), data: data }, buffers); + this.postMessage({ parentId: this.parentId, type: type, id: String(id), data: data }, buffers); }; /** diff --git a/js/util/dispatcher.js b/js/util/dispatcher.js index 7ca9bd7c367..c00c3520c00 100644 --- a/js/util/dispatcher.js +++ b/js/util/dispatcher.js @@ -1,8 +1,7 @@ 'use strict'; var util = require('./util'); -var Actor = require('./actor'); -var WebWorker = require('./web_worker'); +var workerPool = require('./worker_pool'); module.exports = Dispatcher; @@ -14,14 +13,9 @@ module.exports = Dispatcher; * @private */ function Dispatcher(length, parent) { - this.actors = []; + this.id = util.uniqueId(); + this.actors = workerPool.requestActors(this.id, length, parent); this.currentActor = 0; - for (var i = 0; i < length; i++) { - var worker = new WebWorker(); - var actor = new Actor(worker, parent); - actor.name = "Worker " + i; - this.actors.push(actor); - } } Dispatcher.prototype = { @@ -37,7 +31,7 @@ Dispatcher.prototype = { */ broadcast: function(type, data, cb) { cb = cb || function () {}; - util.asyncAll(this.actors, function (actor, done) { + util.asyncAll(this.actors.getAll(), function (actor, done) { actor.send(type, data, done); }, cb); }, @@ -49,25 +43,15 @@ Dispatcher.prototype = { * @param {string} type * @param {object} data * @param {Function} callback - * @param {number|undefined} [targetID] The ID of the Worker to which to send this message. Omit to allow the dispatcher to choose. - * @returns {number} The ID of the worker to which the message was sent. + * @param {string|undefined} [actorKey] When defined, messages send with a given value of actorKey will always be sent to the same actor. * @memberof Dispatcher * @instance */ - send: function(type, data, callback, targetID, buffers) { - if (typeof targetID !== 'number' || isNaN(targetID)) { - // Use round robin to send requests to web workers. - targetID = this.currentActor = (this.currentActor + 1) % this.actors.length; - } - - this.actors[targetID].send(type, data, callback, buffers); - return targetID; + send: function(type, data, callback, actorKey, buffers) { + this.actors.get(actorKey).send(type, data, callback, buffers); }, remove: function() { - for (var i = 0; i < this.actors.length; i++) { - this.actors[i].target.terminate(); - } - this.actors = []; + this.actors.release(); } }; diff --git a/js/util/util.js b/js/util/util.js index ac627194b79..fb09519bab9 100644 --- a/js/util/util.js +++ b/js/util/util.js @@ -389,6 +389,21 @@ exports.filterObject = function(input, iterator, context) { return output; }; +/** + * Create an array of the given object's property values. + * + * @param {Object} input + * @returns {Array} + * @private + */ +exports.values = function(input) { + var output = []; + for (var key in input) { + output.push(input[key]); + } + return output; +}; + /** * Deeply compares two object literals. * @param {Object} obj1 diff --git a/js/util/worker_pool.js b/js/util/worker_pool.js new file mode 100644 index 00000000000..0f9bdeb6fde --- /dev/null +++ b/js/util/worker_pool.js @@ -0,0 +1,85 @@ +'use strict'; + +var assert = require('assert'); +var Actor = require('./actor'); +var WebWorker = require('./web_worker'); + +var WorkerPool = { + reset: function () { + this.workers = []; + this.actors = {}; + this.parents = {}; + this.currentWorker = 0; + this.uidToActor = {}; + }, + + requestActors: function (dispatcherId, actorCount, parent) { + assert(!this.actors[dispatcherId]); + this.actors[dispatcherId] = []; + this.parents[dispatcherId] = parent; + resize(this.workers, actorCount); + + return { + release: this.releaseActors.bind(this, dispatcherId), + get: this.getActor.bind(this, dispatcherId), + getAll: this.getAllActors.bind(this, dispatcherId) + }; + }, + + releaseActors: function (dispatcherId) { + assert(this.actors[dispatcherId]); + var removed = this.actors[dispatcherId]; + delete this.actors[dispatcherId]; + + if (Object.keys(this.actors).length === 0) { + for (var i = 0; i < removed.length; i++) { + this.actors[i].target.terminate(); + } + this.reset(); + } + }, + + getActor: function (dispatcherId, uid) { + assert(this.actors[dispatcherId]); + var actorId; + if (typeof uid !== 'undefined' && typeof this.uidToActor[uid] !== 'undefined') { + actorId = this.uidToActor[uid]; + } else { + // Use round robin to send requests to web workers. + actorId = this.currentWorker = (this.currentWorker + 1) % this.workers.length; + if (typeof uid !== 'undefined') { + this.uidToActor[uid] = actorId; + } + } + + this.ensureActor(dispatcherId, actorId); + return this.actors[dispatcherId][actorId]; + }, + + getAllActors: function (dispatcherId) { + assert(this.actors[dispatcherId]); + for (var i = 0; i < this.workers.length; i++) { + this.ensureActor(dispatcherId, i); + } + return this.actors[dispatcherId].slice(); + }, + + ensureActor: function (dispatcherId, actorId) { + assert(actorId < this.workers.length); + + if (!this.actors[dispatcherId][actorId]) { + var actor = new Actor(this.workers[actorId], this.parents[dispatcherId], dispatcherId); + actor.name = "Actor " + actorId; + this.actors[dispatcherId][actorId] = actor; + } + } +}; + +WorkerPool.reset(); +module.exports = WorkerPool; + +function resize(workerList, length) { + while (workerList.length < length) { + workerList.push(new WebWorker()); + } +} From fb0b68d7475ab92ce230c8058389d8742e78212a Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Thu, 28 Jul 2016 13:44:13 -0400 Subject: [PATCH 4/9] Add comment re: geojson worker key --- js/source/geojson_source.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/source/geojson_source.js b/js/source/geojson_source.js index 45df0032e89..ac58cb1508d 100644 --- a/js/source/geojson_source.js +++ b/js/source/geojson_source.js @@ -138,6 +138,9 @@ GeoJSONSource.prototype = util.inherit(Evented, /** @lends GeoJSONSource.prototy options.data = JSON.stringify(data); } + // We use a 'worker key' that's tied to the current geojson data for + // this source, so that when we ask the worker for tiles, the request + // goes to the same worker that parsed/prepared the geojson. var workerKey = options.url || options.data; // target {this.type}.loadData rather than literally geojson.loadData, From 48a173f6a7aacbd40cb5a5a533fda6d587f4b0e6 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Thu, 28 Jul 2016 13:51:07 -0400 Subject: [PATCH 5/9] s/map/mapId/ --- js/source/geojson_worker_source.js | 3 +- js/source/vector_tile_worker_source.js | 24 +++++++------ js/source/worker.js | 48 +++++++++++++------------- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/js/source/geojson_worker_source.js b/js/source/geojson_worker_source.js index bd1ba677956..2da21a01d21 100644 --- a/js/source/geojson_worker_source.js +++ b/js/source/geojson_worker_source.js @@ -65,11 +65,12 @@ GeoJSONWorkerSource.prototype = util.inherit(VectorTileWorkerSource, /** @lends * Defers to {@link GeoJSONWorkerSource#loadGeoJSON} for the fetching/parsing, * expecting `callback(error, data)` to be called with either an error or a * parsed GeoJSON object. + * @param {string} mapId A unique identifier for the map instance making this request * @param {object} params * @param {string} params.source The id of the source. * @param {Function} callback */ - loadData: function (map, params, callback) { + loadData: function (mapId, params, callback) { var handleData = function(err, data) { if (err) return callback(err); if (typeof data != 'object') { diff --git a/js/source/vector_tile_worker_source.js b/js/source/vector_tile_worker_source.js index 9d9777c3f96..5c091067427 100644 --- a/js/source/vector_tile_worker_source.js +++ b/js/source/vector_tile_worker_source.js @@ -31,6 +31,7 @@ VectorTileWorkerSource.prototype = { /** * Implements {@link WorkerSource#loadTile}. Delegates to {@link VectorTileWorkerSource#loadVectorData} (which by default expects a `params.url` property) for fetching and producing a VectorTile object. * + * @param {string} mapId A unique identifier for the map instance making this request * @param {object} params * @param {string} params.source The id of the source for which we're loading this tile. * @param {string} params.uid The UID for this tile. @@ -41,8 +42,8 @@ VectorTileWorkerSource.prototype = { * @param {number} params.pitch * @param {boolean} params.showCollisionBoxes */ - loadTile: function(map, params, callback) { - var style = this.styles.getKey(map), + loadTile: function(mapId, params, callback) { + var style = this.styles.getKey(mapId), source = params.source, uid = params.uid; @@ -71,7 +72,7 @@ VectorTileWorkerSource.prototype = { if (!data) return callback(null, null); tile.data = data.tile; - tile.parse(tile.data, this.styles.getLayerFamilies(map), this.actor, data.rawTileData, callback); + tile.parse(tile.data, this.styles.getLayerFamilies(mapId), this.actor, data.rawTileData, callback); this.loaded[style + source] = this.loaded[style + source] || {}; this.loaded[style + source][uid] = tile; @@ -81,28 +82,30 @@ VectorTileWorkerSource.prototype = { /** * Implements {@link WorkerSource#reloadTile}. * + * @param {string} mapId A unique identifier for the map instance making this request * @param {object} params * @param {string} params.source The id of the source for which we're loading this tile. * @param {string} params.uid The UID for this tile. */ - reloadTile: function(map, params, callback) { - var loaded = this.loaded[this.styles.getKey(map) + params.source], + reloadTile: function(mapId, params, callback) { + var loaded = this.loaded[this.styles.getKey(mapId) + params.source], uid = params.uid; if (loaded && loaded[uid]) { var tile = loaded[uid]; - tile.parse(tile.data, this.styles.getLayerFamilies(map), this.actor, params.rawTileData, callback); + tile.parse(tile.data, this.styles.getLayerFamilies(mapId), this.actor, params.rawTileData, callback); } }, /** * Implements {@link WorkerSource#abortTile}. * + * @param {string} mapId A unique identifier for the map instance making this request * @param {object} params * @param {string} params.source The id of the source for which we're loading this tile. * @param {string} params.uid The UID for this tile. */ - abortTile: function(map, params) { - var loading = this.loading[this.styles.getKey(map) + params.source], + abortTile: function(mapId, params) { + var loading = this.loading[this.styles.getKey(mapId) + params.source], uid = params.uid; if (loading && loading[uid] && loading[uid].abort) { loading[uid].abort(); @@ -113,12 +116,13 @@ VectorTileWorkerSource.prototype = { /** * Implements {@link WorkerSource#removeTile}. * + * @param {string} mapId A unique identifier for the map instance making this request * @param {object} params * @param {string} params.source The id of the source for which we're loading this tile. * @param {string} params.uid The UID for this tile. */ - removeTile: function(map, params) { - var loaded = this.loaded[this.styles.getKey(map) + params.source], + removeTile: function(mapId, params) { + var loaded = this.loaded[this.styles.getKey(mapId) + params.source], uid = params.uid; if (loaded && loaded[uid]) { delete loaded[uid]; diff --git a/js/source/worker.js b/js/source/worker.js index d8cceee1307..abed1c32865 100644 --- a/js/source/worker.js +++ b/js/source/worker.js @@ -22,14 +22,14 @@ function Worker(self) { // simple accessor object for passing to WorkerSources var styleLayers = { - getKey: function (map) { - return this.styles[map]; + getKey: function (mapId) { + return this.styles[mapId]; }.bind(this), - getLayers: function (map) { - return this.layers[this.styles[map]]; + getLayers: function (mapId) { + return this.layers[this.styles[mapId]]; }.bind(this), - getLayerFamilies: function (map) { - return this.layerFamilies[this.styles[map]]; + getLayerFamilies: function (mapId) { + return this.layerFamilies[this.styles[mapId]]; }.bind(this) }; @@ -47,15 +47,15 @@ function Worker(self) { } util.extend(Worker.prototype, { - 'set style': function(map, style) { + 'set style': function(mapId, style) { // this.styles is an object mapping map id to a content-based key for // the map instance's style. ideally, the key should be the same for // two map instances whose style is identical. var key = createKeyForStyle(style); - var prevKey = this.styles[map]; + var prevKey = this.styles[mapId]; if (prevKey === key) return; - this.styles[map] = key; + this.styles[mapId] = key; var shouldDeletePreviousLayers = util.values(this.styles) .some(function (usedKey) { return usedKey === prevKey; }); @@ -98,25 +98,25 @@ util.extend(Worker.prototype, { } }, - 'update style': function(map, style) { + 'update style': function(mapId, style) { var that = this; var id; var layer; var key = createKeyForStyle(style); - var prevKey = this.styles[map]; + var prevKey = this.styles[mapId]; if (prevKey === key) return; assert(this.layers[prevKey]); - this.styles[map] = key; + this.styles[mapId] = key; // if the current style is being used by another map instance, then // delegate to 'set style'. var existingStyleIsUsed = util.values(this.styles) .some(function (usedKey) { return usedKey === prevKey; }); if (existingStyleIsUsed) - return this['set style'](map, style); + return this['set style'](mapId, style); var prevLayers = this.layers[prevKey]; delete this.layers[prevKey]; @@ -151,29 +151,29 @@ util.extend(Worker.prototype, { } }, - 'load tile': function(map, params, callback) { + 'load tile': function(mapId, params, callback) { var type = params.type || 'vector'; - this.workerSources[type].loadTile(map, params, callback); + this.workerSources[type].loadTile(mapId, params, callback); }, - 'reload tile': function(map, params, callback) { + 'reload tile': function(mapId, params, callback) { var type = params.type || 'vector'; - this.workerSources[type].reloadTile(map, params, callback); + this.workerSources[type].reloadTile(mapId, params, callback); }, - 'abort tile': function(map, params) { + 'abort tile': function(mapId, params) { var type = params.type || 'vector'; - this.workerSources[type].abortTile(map, params); + this.workerSources[type].abortTile(mapId, params); }, - 'remove tile': function(map, params) { + 'remove tile': function(mapId, params) { var type = params.type || 'vector'; - this.workerSources[type].removeTile(map, params); + this.workerSources[type].removeTile(mapId, params); }, - 'redo placement': function(map, params, callback) { + 'redo placement': function(mapId, params, callback) { var type = params.type || 'vector'; - this.workerSources[type].redoPlacement(map, params, callback); + this.workerSources[type].redoPlacement(mapId, params, callback); }, /** @@ -182,7 +182,7 @@ util.extend(Worker.prototype, { * function taking `(name, workerSourceObject)`. * @private */ - 'load worker source': function(map, params, callback) { + 'load worker source': function(mapId, params, callback) { try { this.self.importScripts(params.url); callback(); From 3ce8cebcdc431495efc00ffa1e579cc2524ceaf0 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Thu, 28 Jul 2016 13:55:03 -0400 Subject: [PATCH 6/9] Add comment re: tile uid change --- js/source/source_cache.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/source/source_cache.js b/js/source/source_cache.js index 2f81fef1f40..edc3a83752b 100644 --- a/js/source/source_cache.js +++ b/js/source/source_cache.js @@ -387,7 +387,11 @@ SourceCache.prototype = util.inherit(Evented, { if (!tile) { var zoom = coord.z; var overscaling = zoom > this.maxzoom ? Math.pow(2, zoom - this.maxzoom) : 1; - tile = new Tile(this.id + coord.id, wrapped, this.tileSize * overscaling, this.maxzoom); + // Using [source id][coord id] for the tile's unique id allows it to + // be unique within this map instance while avoiding duplicated + // worker-side parsing across different map instances + var tileUID = this.id + coord.id; + tile = new Tile(tileUID, wrapped, this.tileSize * overscaling, this.maxzoom); this.loadTile(tile, this._tileLoaded.bind(this, tile)); } From 01ca9f77ffd17f51a8b696926b764d6a173aea4c Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Thu, 28 Jul 2016 14:09:48 -0400 Subject: [PATCH 7/9] Add some documentation to worker pool --- js/util/worker_pool.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/js/util/worker_pool.js b/js/util/worker_pool.js index 0f9bdeb6fde..be1461fd8e0 100644 --- a/js/util/worker_pool.js +++ b/js/util/worker_pool.js @@ -4,6 +4,13 @@ var assert = require('assert'); var Actor = require('./actor'); var WebWorker = require('./web_worker'); +/** + * A pool for sharing Worker instances across Map instances on the same page. + * Maintains the relationship Dispatcher:Actor:Worker :: 1:many:1, with Actors + * serving essentially as individual dispatcher-worker connections. + * + * @private + */ var WorkerPool = { reset: function () { this.workers = []; @@ -26,6 +33,10 @@ var WorkerPool = { }; }, + /** + * Release the given dispatcherId's claim on the workers, and clean up + * worker instances if this was the last claim. + */ releaseActors: function (dispatcherId) { assert(this.actors[dispatcherId]); var removed = this.actors[dispatcherId]; @@ -39,6 +50,11 @@ var WorkerPool = { } }, + /** + * Get an actor for the given dispatcher. If `uid` is provided, then + * subsequent calls with the same value of uid will return the same Actor + * (with the same target Worker). + */ getActor: function (dispatcherId, uid) { assert(this.actors[dispatcherId]); var actorId; From 069871b9217621cdf3d7cf6faf0c3f9f48d50acf Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Thu, 28 Jul 2016 14:18:24 -0400 Subject: [PATCH 8/9] Add comments about worker "styles" interface --- js/source/worker.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/js/source/worker.js b/js/source/worker.js index abed1c32865..126d9aa7187 100644 --- a/js/source/worker.js +++ b/js/source/worker.js @@ -22,12 +22,26 @@ function Worker(self) { // simple accessor object for passing to WorkerSources var styleLayers = { + /** + * Get the 'style key' for the given map instance. Two maps with + * identical styles may have the same style key. Thus, this may be + * used to support caching of common work across map instances with + * the same style. + */ getKey: function (mapId) { return this.styles[mapId]; }.bind(this), + + /** + * Get the style layers for the given map instance. + */ getLayers: function (mapId) { return this.layers[this.styles[mapId]]; }.bind(this), + + /** + * Get the style 'layer families' for the given map instance. + */ getLayerFamilies: function (mapId) { return this.layerFamilies[this.styles[mapId]]; }.bind(this) @@ -48,18 +62,15 @@ function Worker(self) { util.extend(Worker.prototype, { 'set style': function(mapId, style) { - // this.styles is an object mapping map id to a content-based key for - // the map instance's style. ideally, the key should be the same for - // two map instances whose style is identical. var key = createKeyForStyle(style); var prevKey = this.styles[mapId]; if (prevKey === key) return; this.styles[mapId] = key; - var shouldDeletePreviousLayers = util.values(this.styles) + var shouldDeletePreviousStyle = util.values(this.styles) .some(function (usedKey) { return usedKey === prevKey; }); - if (shouldDeletePreviousLayers) { + if (shouldDeletePreviousStyle) { delete this.layers[prevKey]; delete this.layerFamilies[prevKey]; } From e07e77fb4e9e4a8190153fb3f222ad7d2cdaae31 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Wed, 3 Aug 2016 17:03:44 -0400 Subject: [PATCH 9/9] Fix old workerID reference --- js/source/tile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/source/tile.js b/js/source/tile.js index fe720c412e4..c3453e8d0ce 100644 --- a/js/source/tile.js +++ b/js/source/tile.js @@ -131,7 +131,7 @@ Tile.prototype = { angle: source.map.transform.angle, pitch: source.map.transform.pitch, showCollisionBoxes: source.map.showCollisionBoxes - }, done.bind(this), this.workerID); + }, done.bind(this), this.uid); function done(_, data) { this.reloadSymbolData(data, source.map.painter, source.map.style);