diff --git a/debug/custom_source.html b/debug/custom_source.html new file mode 100644 index 00000000000..406bea66abb --- /dev/null +++ b/debug/custom_source.html @@ -0,0 +1,101 @@ + + + + Mapbox GL JS debug page + + + + + + + + +
+
+
+
+
+ +
+ + + + + + + + + + + diff --git a/js/mapbox-gl.js b/js/mapbox-gl.js index 9f223c0a001..2f3bfe95d34 100644 --- a/js/mapbox-gl.js +++ b/js/mapbox-gl.js @@ -20,6 +20,8 @@ mapboxgl.Marker = require('./ui/marker'); mapboxgl.Style = require('./style/style'); +mapboxgl.addSourceType = require('./source/source').addType; + mapboxgl.LngLat = require('./geo/lng_lat'); mapboxgl.LngLatBounds = require('./geo/lng_lat_bounds'); mapboxgl.Point = require('point-geometry'); diff --git a/js/source/source.js b/js/source/source.js index 7d98b4d7fad..3f20518446e 100644 --- a/js/source/source.js +++ b/js/source/source.js @@ -1,6 +1,8 @@ 'use strict'; var util = require('../util/util'); +var Dispatcher = require('../util/dispatcher'); +var getWorkerPool = require('../global_worker_pool'); var sourceTypes = { 'vector': require('../source/vector_tile_source'), @@ -10,6 +12,8 @@ var sourceTypes = { 'image': require('../source/image_source') }; +var Source = module.exports = {}; + /* * Creates a tiled data source instance given an options object. * @@ -19,7 +23,7 @@ var sourceTypes = { * @param {Dispatcher} dispatcher * @returns {Source} */ -exports.create = function(id, source, dispatcher) { +Source.create = function(id, source, dispatcher) { source = new sourceTypes[source.type](id, source, dispatcher); if (source.id !== id) { @@ -30,14 +34,60 @@ exports.create = function(id, source, dispatcher) { return source; }; -exports.getType = function (name) { +Source.getType = function (name) { return sourceTypes[name]; }; -exports.setType = function (name, type) { +Source.setType = function (name, type) { sourceTypes[name] = type; }; +/** + * Adds a [custom source type](#Custom Sources), making it available for use with + * {@link Map#addSource}. + * + * @param {string} name The name of the source type; source definition objects use this name in the `{type: ...}` field. + * @param {Function} SourceType A {@link Source} constructor. + * @param {Function} callback called after SourceType has been added and, if relevant, its worker code has been sent to the workers. + * @private + */ +Source.addType = function (name, SourceType, callback) { + if (Source.getType(name)) { + throw new Error('A source type named ' + name + ' already exists.'); + } + + Source.setType(name, SourceType); + + if (SourceType.workerSourceURL) { + getDispatcher().broadcast('load worker source', { + name: name, + url: SourceType.workerSourceURL + }, function (err) { + callback(err); + }); + } else { + callback(); + } +}; + +/* + * A Dispatcher instance for use in registering custom WorkerSources. + * + * Note that it is created on demand, and once created, this dispatcher + * instance prevents the Workers in the global pool from being destroyed even + * when the the last map instance is destroyed. This is intended behavior, as + * it ensures that any custom WorkerSources will be registered on the workers + * used by future map instances. + */ +var dispatcher; +function getDispatcher () { + if (!dispatcher) { + dispatcher = new Dispatcher(getWorkerPool(), {}); + } + return dispatcher; +} + + /** * The `Source` interface must be implemented by each source type, including "core" types (`vector`, `raster`, `video`, etc.) and all custom, third-party types. * @@ -46,7 +96,7 @@ exports.setType = function (name, type) { * * @param {string} id The id for the source. Must not be used by any existing source. * @param {Object} options Source options, specific to the source type (except for `options.type`, which is always required). - * @param {string} options.type The source type, matching the value of `name` used in {@link Style#addSourceType}. + * @param {string} options.type The source type, matching the value of `name` used in {@link Source.addType}. * @param {Dispatcher} dispatcher A {@link Dispatcher} instance, which can be used to send messages to the workers. * * @fires load to indicate source data has been loaded, so that it's okay to call `loadTile` @@ -116,7 +166,7 @@ exports.setType = function (name, type) { * implementation may also be targeted by the {@link Source} via * `dispatcher.send('source-type.methodname', params, callback)`. * - * @see {@link Map#addSourceType} + * @see {@link Source.addType} * @private * * @class WorkerSource diff --git a/js/source/worker.js b/js/source/worker.js index 9f98eb7fdf5..175967fa7ee 100644 --- a/js/source/worker.js +++ b/js/source/worker.js @@ -29,7 +29,7 @@ function Worker(self) { this.self.registerWorkerSource = function (name, WorkerSource) { if (this.workerSourceTypes[name]) { - throw new Error('Worker source with name "' + name + '" already registered.'); + util.warnOnce('Worker source named "' + name + '" already registered.'); } this.workerSourceTypes[name] = WorkerSource; }.bind(this); diff --git a/js/style/style.js b/js/style/style.js index d38666169dc..455a660a6ab 100644 --- a/js/style/style.js +++ b/js/style/style.js @@ -13,7 +13,6 @@ var browser = require('../util/browser'); var Dispatcher = require('../util/dispatcher'); var AnimationLoop = require('./animation_loop'); var validateStyle = require('./validate_style'); -var Source = require('../source/source'); var QueryFeatures = require('../source/query_features'); var SourceCache = require('../source/source_cache'); var styleSpec = require('./style_spec'); @@ -660,23 +659,6 @@ Style.prototype = util.inherit(Evented, { return source ? QueryFeatures.source(source, params) : []; }, - addSourceType: function (name, SourceType, callback) { - if (Source.getType(name)) { - return callback(new Error('A source type called "' + name + '" already exists.')); - } - - Source.setType(name, SourceType); - - if (!SourceType.workerSourceURL) { - return callback(null, null); - } - - this.dispatcher.broadcast('load worker source', { - name: name, - url: SourceType.workerSourceURL - }, callback); - }, - _validate: function(validate, key, value, props, options) { if (options && options.validate === false) { return false; diff --git a/js/ui/map.js b/js/ui/map.js index 1c3c316f784..555f74f7fd0 100755 --- a/js/ui/map.js +++ b/js/ui/map.js @@ -656,7 +656,7 @@ util.extend(Map.prototype, /** @lends Map.prototype */{ * @param {string} id The ID of the source to add. Must not conflict with existing sources. * @param {Object} source The source object, conforming to the * Mapbox Style Specification's [source definition](https://www.mapbox.com/mapbox-gl-style-spec/#sources). - * @param {string} source.type The source type, which must be either one of the core Mapbox GL source types defined in the style specification or a custom type that has been added to the map with {@link Map#addSourceType}. + * @param {string} source.type The source type, which must be one of the core Mapbox GL source types defined in the style specification. * @fires source.add * @returns {Map} `this` */ @@ -666,18 +666,6 @@ util.extend(Map.prototype, /** @lends Map.prototype */{ return this; }, - /** - * Adds a [custom source type](#Custom Sources), making it available for use with - * {@link Map#addSource}. - * @private - * @param {string} name The name of the source type; source definition objects use this name in the `{type: ...}` field. - * @param {Function} SourceType A {@link Source} constructor. - * @param {Function} callback Called when the source type is ready or with an error argument if there is an error. - */ - addSourceType: function (name, SourceType, callback) { - return this.style.addSourceType(name, SourceType, callback); - }, - /** * Removes a source from the map's style. * diff --git a/test/js/source/source.test.js b/test/js/source/source.test.js new file mode 100644 index 00000000000..5beeeee5ef2 --- /dev/null +++ b/test/js/source/source.test.js @@ -0,0 +1,59 @@ +'use strict'; + +var test = require('tap').test; +var Source = require('../../../js/source/source'); +var proxyquire = require('proxyquire'); + +test('Source#addType', function (t) { + t.test('adds source type', function (t) { + // expect no call to load worker source + var Source = proxyquire('../../../js/source/source', { + '../util/dispatcher': function () { + t.fail(); + } + }); + + var SourceType = function () {}; + + Source.addType('foo', SourceType, function (err) { + t.error(err); + t.equal(Source.getType('foo'), SourceType); + t.end(); + }); + }); + + t.test('triggers workers to load worker source code', function (t) { + var SourceType = function () {}; + SourceType.workerSourceURL = 'worker-source.js'; + + var Source = proxyquire('../../../js/source/source', { + '../util/dispatcher': function () { + this.broadcast = function (type, params) { + if (type === 'load worker source') { + t.equal(Source.getType('bar'), SourceType); + t.equal(params.name, 'bar'); + t.equal(params.url, 'worker-source.js'); + t.end(); + } + }; + } + }); + + Source.addType('bar', SourceType, function (err) { t.error(err); }); + }); + + t.test('throws for duplicate source type', function (t) { + Source.addType('source.test.type-3', function () {}, function (err) { + t.error(err); + t.throws(function () { + Source.addType('source.test.type-3', function () {}, function (err) { + t.error(err); + t.fail(); + }); + }); + }); + t.end(); + }); + + t.end(); +}); diff --git a/test/js/style/style.test.js b/test/js/style/style.test.js index 8fd77a4e2c9..e1cf2b24655 100644 --- a/test/js/style/style.test.js +++ b/test/js/style/style.test.js @@ -1238,56 +1238,3 @@ test('Style#query*Features', function(t) { t.end(); }); -test('Style#addSourceType', function (t) { - var _types = { 'existing': function () {} }; - var Style = proxyquire('../../../js/style/style', { - '../source/source': { - getType: function (name) { return _types[name]; }, - setType: function (name, create) { _types[name] = create; } - } - }); - - t.test('adds factory function', function (t) { - var style = new Style(createStyleJSON()); - var SourceType = function () {}; - - // expect no call to load worker source - style.dispatcher.broadcast = function (type) { - if (type === 'load worker source') { - t.fail(); - } - }; - - style.addSourceType('foo', SourceType, function () { - t.equal(_types['foo'], SourceType); - t.end(); - }); - }); - - t.test('triggers workers to load worker source code', function (t) { - var style = new Style(createStyleJSON()); - var SourceType = function () {}; - SourceType.workerSourceURL = 'worker-source.js'; - - style.dispatcher.broadcast = function (type, params) { - if (type === 'load worker source') { - t.equal(_types['bar'], SourceType); - t.equal(params.name, 'bar'); - t.equal(params.url, 'worker-source.js'); - t.end(); - } - }; - - style.addSourceType('bar', SourceType, function (err) { t.error(err); }); - }); - - t.test('refuses to add new type over existing name', function (t) { - var style = new Style(createStyleJSON()); - style.addSourceType('existing', function () {}, function (err) { - t.ok(err); - t.end(); - }); - }); - - t.end(); -});