mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-30 03:39:36 +02:00
Merge pull request #4259 from openstreetmap/performance
Performance: use requestIdleCallback for lazy execution
This commit is contained in:
@@ -13,6 +13,7 @@ import { services } from '../services/index';
|
||||
import { uiInit } from '../ui/init';
|
||||
import { utilDetect } from '../util/detect';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
import { utilCallWhenIdle } from '../util/index';
|
||||
|
||||
|
||||
export var areaKeys = {};
|
||||
@@ -83,9 +84,9 @@ export function coreContext() {
|
||||
|
||||
|
||||
/* Connection */
|
||||
function entitiesLoaded(err, result) {
|
||||
var entitiesLoaded = utilCallWhenIdle(function entitiesLoaded(err, result) {
|
||||
if (!err) history.merge(result.data, result.extent);
|
||||
}
|
||||
});
|
||||
|
||||
context.preauth = function(options) {
|
||||
if (connection) {
|
||||
@@ -94,7 +95,7 @@ export function coreContext() {
|
||||
return context;
|
||||
};
|
||||
|
||||
context.loadTiles = function(projection, dimensions, callback) {
|
||||
context.loadTiles = utilCallWhenIdle(function(projection, dimensions, callback) {
|
||||
function done(err, result) {
|
||||
entitiesLoaded(err, result);
|
||||
if (callback) callback(err, result);
|
||||
@@ -102,7 +103,7 @@ export function coreContext() {
|
||||
if (connection) {
|
||||
connection.loadTiles(projection, dimensions, done);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
context.loadEntity = function(id, callback) {
|
||||
function done(err, result) {
|
||||
|
||||
+26
-9
@@ -64,8 +64,27 @@ export function rendererMap(context) {
|
||||
.on('zoom', zoomPan);
|
||||
|
||||
var _selection = d3.select(null);
|
||||
var isRedrawScheduled = false;
|
||||
var pendingRedrawCall;
|
||||
|
||||
function scheduleRedraw() {
|
||||
// Only schedule the redraw if one has not already been set.
|
||||
if (isRedrawScheduled) return;
|
||||
isRedrawScheduled = true;
|
||||
var that = this;
|
||||
var args = arguments;
|
||||
pendingRedrawCall = requestIdleCallback(function () {
|
||||
// Reset the boolean so future redraws can be set.
|
||||
isRedrawScheduled = false;
|
||||
redraw.apply(that, args);
|
||||
}, { timeout: 1400 });
|
||||
}
|
||||
|
||||
function cancelPendingRedraw() {
|
||||
isRedrawScheduled = false;
|
||||
window.cancelIdleCallback(pendingRedrawCall);
|
||||
}
|
||||
|
||||
function map(selection) {
|
||||
|
||||
_selection = selection;
|
||||
@@ -323,7 +342,7 @@ export function rendererMap(context) {
|
||||
surface.interrupt();
|
||||
uiFlash().text(t('cannot_zoom'));
|
||||
setZoom(context.minEditableZoom(), true);
|
||||
queueRedraw();
|
||||
scheduleRedraw();
|
||||
dispatch.call('move', this, map);
|
||||
return;
|
||||
}
|
||||
@@ -346,7 +365,7 @@ export function rendererMap(context) {
|
||||
transformed = true;
|
||||
transformLast = eventTransform;
|
||||
utilSetTransform(supersurface, tX, tY, scale);
|
||||
queueRedraw();
|
||||
scheduleRedraw();
|
||||
|
||||
dispatch.call('move', this, map);
|
||||
}
|
||||
@@ -403,11 +422,9 @@ export function rendererMap(context) {
|
||||
}
|
||||
|
||||
|
||||
var queueRedraw = _.throttle(redraw, 750);
|
||||
|
||||
|
||||
var immediateRedraw = function(difference, extent) {
|
||||
if (!difference && !extent) queueRedraw.cancel();
|
||||
if (!difference && !extent) cancelPendingRedraw();
|
||||
redraw(difference, extent);
|
||||
};
|
||||
|
||||
@@ -576,7 +593,7 @@ export function rendererMap(context) {
|
||||
mouse = utilFastMouse(supersurface.node());
|
||||
setCenter(center);
|
||||
|
||||
queueRedraw();
|
||||
scheduleRedraw();
|
||||
return map;
|
||||
};
|
||||
|
||||
@@ -605,7 +622,7 @@ export function rendererMap(context) {
|
||||
dispatch.call('move', this, map);
|
||||
}
|
||||
|
||||
queueRedraw();
|
||||
scheduleRedraw();
|
||||
return map;
|
||||
};
|
||||
|
||||
@@ -625,7 +642,7 @@ export function rendererMap(context) {
|
||||
dispatch.call('move', this, map);
|
||||
}
|
||||
|
||||
queueRedraw();
|
||||
scheduleRedraw();
|
||||
return map;
|
||||
};
|
||||
|
||||
@@ -648,7 +665,7 @@ export function rendererMap(context) {
|
||||
dispatch.call('move', this, map);
|
||||
}
|
||||
|
||||
queueRedraw();
|
||||
scheduleRedraw();
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
+44
-23
@@ -11,7 +11,7 @@ import {
|
||||
osmWay
|
||||
} from '../osm';
|
||||
|
||||
import { utilRebind } from '../util';
|
||||
import { utilRebind, utilIdleWorker } from '../util';
|
||||
|
||||
|
||||
var dispatch = d3.dispatch('authLoading', 'authDone', 'change', 'loading', 'loaded'),
|
||||
@@ -19,6 +19,7 @@ var dispatch = d3.dispatch('authLoading', 'authDone', 'change', 'loading', 'load
|
||||
blacklists = ['.*\.google(apis)?\..*/(vt|kh)[\?/].*([xyz]=.*){3}.*'],
|
||||
inflight = {},
|
||||
loadedTiles = {},
|
||||
entityCache = {},
|
||||
tileZoom = 16,
|
||||
oauth = osmAuth({
|
||||
url: urlroot,
|
||||
@@ -100,10 +101,10 @@ function getVisible(attrs) {
|
||||
|
||||
|
||||
var parsers = {
|
||||
node: function nodeData(obj) {
|
||||
node: function nodeData(obj, uid) {
|
||||
var attrs = obj.attributes;
|
||||
return new osmNode({
|
||||
id: osmEntity.id.fromOSM('node', attrs.id.value),
|
||||
id:uid,
|
||||
visible: getVisible(attrs),
|
||||
version: attrs.version.value,
|
||||
changeset: attrs.changeset && attrs.changeset.value,
|
||||
@@ -115,10 +116,10 @@ var parsers = {
|
||||
});
|
||||
},
|
||||
|
||||
way: function wayData(obj) {
|
||||
way: function wayData(obj, uid) {
|
||||
var attrs = obj.attributes;
|
||||
return new osmWay({
|
||||
id: osmEntity.id.fromOSM('way', attrs.id.value),
|
||||
id: uid,
|
||||
visible: getVisible(attrs),
|
||||
version: attrs.version.value,
|
||||
changeset: attrs.changeset && attrs.changeset.value,
|
||||
@@ -130,10 +131,10 @@ var parsers = {
|
||||
});
|
||||
},
|
||||
|
||||
relation: function relationData(obj) {
|
||||
relation: function relationData(obj, uid) {
|
||||
var attrs = obj.attributes;
|
||||
return new osmRelation({
|
||||
id: osmEntity.id.fromOSM('relation', attrs.id.value),
|
||||
id: uid,
|
||||
visible: getVisible(attrs),
|
||||
version: attrs.version.value,
|
||||
changeset: attrs.changeset && attrs.changeset.value,
|
||||
@@ -147,22 +148,25 @@ var parsers = {
|
||||
};
|
||||
|
||||
|
||||
function parse(xml) {
|
||||
function parse(xml, callback, options) {
|
||||
options = _.extend({ cache: true }, options);
|
||||
if (!xml || !xml.childNodes) return;
|
||||
|
||||
var root = xml.childNodes[0],
|
||||
children = root.childNodes,
|
||||
entities = [];
|
||||
children = root.childNodes;
|
||||
|
||||
for (var i = 0, l = children.length; i < l; i++) {
|
||||
var child = children[i],
|
||||
parser = parsers[child.nodeName];
|
||||
function parseChild(child) {
|
||||
var parser = parsers[child.nodeName];
|
||||
if (parser) {
|
||||
entities.push(parser(child));
|
||||
var uid = osmEntity.id.fromOSM(child.nodeName, child.attributes.id.value);
|
||||
if (options.cache && entityCache[uid]) {
|
||||
return null;
|
||||
}
|
||||
return parser(child, uid);
|
||||
}
|
||||
}
|
||||
|
||||
return entities;
|
||||
utilIdleWorker(children, parseChild, callback);
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +182,7 @@ export default {
|
||||
userDetails = undefined;
|
||||
rateLimitError = undefined;
|
||||
_.forEach(inflight, abortRequest);
|
||||
entityCache = {};
|
||||
loadedTiles = {};
|
||||
inflight = {};
|
||||
return this;
|
||||
@@ -213,7 +218,8 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
loadFromAPI: function(path, callback) {
|
||||
loadFromAPI: function(path, callback, options) {
|
||||
options = _.extend({ cache: true }, options);
|
||||
var that = this;
|
||||
|
||||
function done(err, xml) {
|
||||
@@ -237,7 +243,15 @@ export default {
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(err, parse(xml));
|
||||
if (err) return callback(err, null);
|
||||
parse(xml, function (entities) {
|
||||
if (options.cache) {
|
||||
for (var i in entities) {
|
||||
entityCache[entities[i].id] = true;
|
||||
}
|
||||
}
|
||||
callback(null, entities);
|
||||
}, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,42 +267,49 @@ export default {
|
||||
|
||||
loadEntity: function(id, callback) {
|
||||
var type = osmEntity.id.type(id),
|
||||
osmID = osmEntity.id.toOSM(id);
|
||||
osmID = osmEntity.id.toOSM(id),
|
||||
options = { cache: false };
|
||||
|
||||
this.loadFromAPI(
|
||||
'/api/0.6/' + type + '/' + osmID + (type !== 'node' ? '/full' : ''),
|
||||
function(err, entities) {
|
||||
if (callback) callback(err, { data: entities });
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
loadEntityVersion: function(id, version, callback) {
|
||||
var type = osmEntity.id.type(id),
|
||||
osmID = osmEntity.id.toOSM(id);
|
||||
osmID = osmEntity.id.toOSM(id),
|
||||
options = { cache: false };
|
||||
|
||||
this.loadFromAPI(
|
||||
'/api/0.6/' + type + '/' + osmID + '/' + version,
|
||||
function(err, entities) {
|
||||
if (callback) callback(err, { data: entities });
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
loadMultiple: function(ids, callback) {
|
||||
var that = this;
|
||||
|
||||
_.each(_.groupBy(_.uniq(ids), osmEntity.id.type), function(v, k) {
|
||||
var type = k + 's',
|
||||
osmIDs = _.map(v, osmEntity.id.toOSM);
|
||||
osmIDs = _.map(v, osmEntity.id.toOSM),
|
||||
options = { cache: false };
|
||||
|
||||
_.each(_.chunk(osmIDs, 150), function(arr) {
|
||||
that.loadFromAPI(
|
||||
'/api/0.6/' + type + '?' + type + '=' + arr.join(),
|
||||
function(err, entities) {
|
||||
if (callback) callback(err, { data: entities });
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,8 @@ import { utilDetect } from '../util/detect';
|
||||
import {
|
||||
utilDisplayName,
|
||||
utilDisplayNameForPath,
|
||||
utilEntitySelector
|
||||
utilEntitySelector,
|
||||
utilCallWhenIdle
|
||||
} from '../util/index';
|
||||
|
||||
|
||||
@@ -652,7 +653,7 @@ export function svgLabels(projection, context) {
|
||||
}
|
||||
|
||||
|
||||
var throttleFilterLabels = _.throttle(filterLabels, 100);
|
||||
var throttleFilterLabels = _.throttle(utilCallWhenIdle(filterLabels), 100);
|
||||
|
||||
|
||||
drawLabels.observe = function(selection) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { d3keybinding } from '../lib/d3.keybinding.js';
|
||||
import { t, textDirection } from '../util/locale';
|
||||
import { geoMetersToOffset, geoOffsetToMeters } from '../geo/index';
|
||||
import { utilDetect } from '../util/detect';
|
||||
import { utilSetTransform } from '../util/index';
|
||||
import { utilSetTransform, utilCallWhenIdle } from '../util/index';
|
||||
import { svgIcon } from '../svg/index';
|
||||
import { uiMapInMap } from './map_in_map';
|
||||
import { uiCmd } from './cmd';
|
||||
@@ -540,7 +540,7 @@ export function uiBackground(context) {
|
||||
);
|
||||
|
||||
context.map()
|
||||
.on('move.background-update', _.debounce(update, 1000));
|
||||
.on('move.background-update', _.debounce(utilCallWhenIdle(update), 1000));
|
||||
|
||||
context.background()
|
||||
.on('change.background-update', update);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// note the function should be of low priority
|
||||
// and should not be returning a value.
|
||||
export function utilCallWhenIdle(func, timeout) {
|
||||
return function() {
|
||||
var args = arguments;
|
||||
var that = this;
|
||||
window.requestIdleCallback(function() {
|
||||
func.apply(that, args);
|
||||
}, {timeout: timeout});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
export function utilIdleWorker(tasks, processor, callback) {
|
||||
var processed = [];
|
||||
var currentPos = 0;
|
||||
var totalTasks = tasks.length;
|
||||
|
||||
function worker(deadline) {
|
||||
while (deadline.timeRemaining() > 0 && currentPos < totalTasks) {
|
||||
var result = processor(tasks[currentPos]);
|
||||
|
||||
// if falsy dont add to the processed list
|
||||
if (result) processed.push(result);
|
||||
currentPos++;
|
||||
}
|
||||
|
||||
// more tasks are left, we might need more idleCallbacks
|
||||
if (currentPos < totalTasks) {
|
||||
return window.requestIdleCallback(function(deadline) {worker(deadline);});
|
||||
}
|
||||
|
||||
// tasks are completed
|
||||
return callback(processed);
|
||||
}
|
||||
|
||||
window.requestIdleCallback(function(deadline) {worker(deadline);});
|
||||
}
|
||||
|
||||
// shim
|
||||
window.requestIdleCallback =
|
||||
window.requestIdleCallback ||
|
||||
function(cb) {
|
||||
var start = Date.now();
|
||||
return setTimeout(function() {
|
||||
cb({
|
||||
didTimeout: false,
|
||||
timeRemaining: function() {
|
||||
return Math.max(0, 50 - (Date.now() - start));
|
||||
}
|
||||
});
|
||||
}, 1);
|
||||
};
|
||||
|
||||
window.cancelIdleCallback =
|
||||
window.cancelIdleCallback ||
|
||||
function(id) {
|
||||
clearTimeout(id);
|
||||
};
|
||||
@@ -22,3 +22,5 @@ export { utilSuggestNames } from './suggest_names';
|
||||
export { utilTagText } from './util';
|
||||
export { utilTriggerEvent } from './trigger_event';
|
||||
export { utilWrap } from './util';
|
||||
export { utilIdleWorker} from './idle_worker';
|
||||
export { utilCallWhenIdle } from './call_when_idle';
|
||||
|
||||
@@ -320,6 +320,24 @@ describe('iD.serviceOsm', function () {
|
||||
[200, { 'Content-Type': 'text/xml' }, wayXML]);
|
||||
server.respond();
|
||||
});
|
||||
|
||||
it('does not ignore repeat requests', function(done) {
|
||||
var id = 'n1';
|
||||
connection.loadEntity(id, function(err1, result1) {
|
||||
var entity1 = _.find(result1.data, function(e1) { return e1.id === id; });
|
||||
expect(entity1).to.be.an.instanceOf(iD.Node);
|
||||
connection.loadEntity(id, function(err2, result2) {
|
||||
var entity2 = _.find(result2.data, function(e2) { return e2.id === id; });
|
||||
expect(entity2).to.be.an.instanceOf(iD.Node);
|
||||
done();
|
||||
});
|
||||
server.respond();
|
||||
});
|
||||
|
||||
server.respondWith('GET', 'http://www.openstreetmap.org/api/0.6/node/1',
|
||||
[200, { 'Content-Type': 'text/xml' }, nodeXML]);
|
||||
server.respond();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#loadEntityVersion', function () {
|
||||
@@ -363,6 +381,24 @@ describe('iD.serviceOsm', function () {
|
||||
[200, { 'Content-Type': 'text/xml' }, wayXML]);
|
||||
server.respond();
|
||||
});
|
||||
|
||||
it('does not ignore repeat requests', function(done) {
|
||||
var id = 'n1';
|
||||
connection.loadEntityVersion(id, 1, function(err1, result1) {
|
||||
var entity1 = _.find(result1.data, function(e1) { return e1.id === id; });
|
||||
expect(entity1).to.be.an.instanceOf(iD.Node);
|
||||
connection.loadEntityVersion(id, 1, function(err2, result2) {
|
||||
var entity2 = _.find(result2.data, function(e2) { return e2.id === id; });
|
||||
expect(entity2).to.be.an.instanceOf(iD.Node);
|
||||
done();
|
||||
});
|
||||
server.respond();
|
||||
});
|
||||
|
||||
server.respondWith('GET', 'http://www.openstreetmap.org/api/0.6/node/1/1',
|
||||
[200, { 'Content-Type': 'text/xml' }, nodeXML]);
|
||||
server.respond();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#loadMultiple', function () {
|
||||
@@ -376,6 +412,7 @@ describe('iD.serviceOsm', function () {
|
||||
|
||||
it('loads nodes');
|
||||
it('loads ways');
|
||||
it('does not ignore repeat requests');
|
||||
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user