Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coalesce high-frequency 'setData' calls #5902

Merged
merged 1 commit into from
Jan 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/source/geojson_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ class GeoJSONSource extends Evented implements Source {
// implementation
this.workerID = this.dispatcher.send(`${this.type}.loadData`, options, (err) => {
this._loaded = true;
// Any `loadData` calls that piled up while we were processing
// this one will get coalesced into a single call when this
// 'coalesce' message is processed.
// We would self-send from the worker if we had access to its
// message queue. Waiting instated for the 'coalesce' to round-trip
// through the foreground just means we're throttling the worker
// to run at a little less than full-throttle.
this.dispatcher.send(`${this.type}.coalesce`, null, null, this.workerID);
callback(err);
}, this.workerID);
}
Expand Down
62 changes: 62 additions & 0 deletions src/source/geojson_worker_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ function loadGeoJSONTile(params: WorkerTileParameters, callback: LoadVectorDataC
});
}

export type SourceState =
| 'Idle' // Source empty or data loaded
| 'Coalescing' // Data finished loading, but discard 'loadData' messages until receiving 'coalesced'
| 'NeedsLoadData'; // 'loadData' received while coalescing, trigger one more 'loadData' on receiving 'coalesced'

/**
* The {@link WorkerSource} implementation that supports {@link GeoJSONSource}.
* This class is designed to be easily reused to support custom source types
Expand All @@ -79,6 +84,9 @@ function loadGeoJSONTile(params: WorkerTileParameters, callback: LoadVectorDataC
class GeoJSONWorkerSource extends VectorTileWorkerSource {
_geoJSONIndexes: { [string]: GeoJSONIndex };
loadGeoJSON: LoadGeoJSON;
state: SourceState;
pendingCallback: Callback<void>;
pendingLoadDataParams: LoadGeoJSONParameters;

/**
* @param [loadGeoJSON] Optional method for custom loading/parsing of
Expand All @@ -92,6 +100,7 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
}
// object mapping source ids to geojson-vt-like tile indexes
this._geoJSONIndexes = {};
this.state = 'Idle';
}

/**
Expand All @@ -102,11 +111,35 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
* 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.
*
* When `loadData` requests come in faster than they can be processed,
* they are coalesced into a single request using the latest data.
* See {@link GeoJSONWorkerSource#coalesce}
*
* @param params
* @param params.source The id of the source.
* @param callback
*/
loadData(params: LoadGeoJSONParameters, callback: Callback<void>) {
this.pendingCallback = callback;
this.pendingLoadDataParams = params;
if (this.state !== 'Idle') {
this.state = 'NeedsLoadData';
} else {
this.state = 'Coalescing';
this._loadData();
}
}

/**
* Internal implementation: called directly by `loadData`
* or by `coalesce` using stored parameters.
*/
_loadData() {
const callback = this.pendingCallback;
const params = this.pendingLoadDataParams;
delete this.pendingCallback;
delete this.pendingLoadDataParams;
this.loadGeoJSON(params, (err, data) => {
if (err || !data) {
return callback(err);
Expand All @@ -129,6 +162,35 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
});
}

/**
* While processing `loadData`, we coalesce all further
* `loadData` messages into a single call to _loadData
* that will happen once we've finished processing the
* first message. {@link GeoJSONSource#_updateWorkerData}
* is responsible for sending us the `coalesce` message
* at the time it receives a response from `loadData`
*
* State: Idle
* ↑ |
* 'coalesce' 'loadData'
* | (triggers load)
* | ↓
* State: Coalescing
* ↑ |
* (triggers load) |
* 'coalesce' 'loadData'
* | ↓
* State: NeedsLoadData
*/
coalesce() {
if (this.state === 'Coalescing') {
this.state = 'Idle';
} else if (this.state === 'NeedsLoadData') {
this.state = 'Coalescing';
this._loadData();
}
}

/**
* Implements {@link WorkerSource#reloadTile}.
*
Expand Down
16 changes: 12 additions & 4 deletions test/unit/source/geojson_source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ test('GeoJSONSource#setData', (t) => {
function createSource() {
return new GeoJSONSource('id', {data: {}}, {
send: function (type, data, callback) {
return setTimeout(callback, 0);
if (callback) {
return setTimeout(callback, 0);
}
}
});
}
Expand Down Expand Up @@ -153,7 +155,9 @@ test('GeoJSONSource#update', (t) => {
t.test('fires event when metadata loads', (t) => {
const mockDispatcher = {
send: function(message, args, callback) {
setTimeout(callback, 0);
if (callback) {
setTimeout(callback, 0);
}
}
};

Expand All @@ -169,7 +173,9 @@ test('GeoJSONSource#update', (t) => {
t.test('fires "error"', (t) => {
const mockDispatcher = {
send: function(message, args, callback) {
setTimeout(callback.bind(null, 'error'), 0);
if (callback) {
setTimeout(callback.bind(null, 'error'), 0);
}
}
};

Expand All @@ -190,7 +196,9 @@ test('GeoJSONSource#update', (t) => {
if (message === 'geojson.loadData' && --expectedLoadDataCalls <= 0) {
t.end();
}
setTimeout(callback, 0);
if (callback) {
setTimeout(callback, 0);
}
}
};

Expand Down
1 change: 1 addition & 0 deletions test/unit/source/geojson_worker_source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ test('reloadTile', (t) => {

function addData(callback) {
source.loadData({ source: 'sourceId', data: JSON.stringify(geoJson) }, (err) => {
source.coalesce();
t.equal(err, null);
callback();
});
Expand Down