map data now handles refreshes itself. this is so it can ensure that it doesn't start the refresh timer until all requests are complete

this avoids the situation where map data requests are slow enough that requests get aborted before a complete refresh has been done
This commit is contained in:
Jon Atkins
2013-08-27 05:42:59 +01:00
parent 0302d76a67
commit dc41671279
5 changed files with 107 additions and 77 deletions

View File

@ -4,7 +4,7 @@
// Uses the map data cache class to reduce network requests
window.Request = function() {
window.MapDataRequest = function() {
this.cache = new DataCache();
this.render = new Render();
this.debugTiles = new RenderDebugTiles();
@ -12,18 +12,72 @@ window.Request = function() {
this.activeRequestCount = 0;
this.requestedTiles = {};
// no more than this many requests in parallel
this.MAX_REQUESTS = 4;
this.MAX_TILES_PER_REQUEST = 12;
// no more than this many tiles in one request
this.MAX_TILES_PER_REQUEST = 16;
// but don't create more requests if it would make less than this per request
this.MIN_TILES_PER_REQUEST = 4;
// number of times to retty a tile after a 'bad' error (i.e. not a timeout)
this.MAX_TILE_RETRIES = 3;
// refresh timers
this.MOVE_REFRESH = 2.5; //refresh time to use after a move
this.STARTUP_REFRESH = 1; //refresh time used on first load of IITC
this.IDLE_RESUME_REFRESH = 5; //refresh time used after resuming from idle
this.REFRESH = 60; //refresh time to use when not idle and not moving
}
window.Request.prototype.start = function() {
window.MapDataRequest.prototype.start = function() {
var savedContext = this;
// setup idle resume function
window.addResumeFunction ( function() { savedContext.refreshOnTimeout(savedContext.IDLE_RESUME_REFRESH); } );
// and map move callback
window.map.on('moveend', function() { savedContext.refreshOnTimeout(savedContext.MOVE_REFRESH); } );
// and on movestart, we clear the request queue
window.map.on('movestart', function() { savedContext.clearQueue(); } );
// then set a timeout to start the first refresh
this.refreshOnTimeout (this.MOVE_REFRESH);
}
window.MapDataRequest.prototype.refreshOnTimeout = function(seconds) {
if (this.timer) {
console.log("cancelling existing map refresh timer");
clearTimeout(this.timer);
this.timer = undefined;
}
console.log("starting map refresh in "+seconds+" seconds");
// 'this' won't be right inside the callback, so save it
var savedContext = this;
this.timer = setTimeout ( function() { savedContext.timer = undefined; savedContext.refresh(); }, seconds*1000);
}
window.MapDataRequest.prototype.clearQueue = function() {
this.tileBounds = {};
}
window.MapDataRequest.prototype.refresh = function() {
this.cache.expire();
this.debugTiles.reset();
// a 'set' to keep track of hard failures for tiles
this.tileErrorCount = {};
var bounds = clampLatLngBounds(map.getBounds());
var zoom = getPortalDataZoom();
@ -55,61 +109,48 @@ window.Request.prototype.start = function() {
this.debugTiles.create(tile_id,[[latSouth,lngWest],[latNorth,lngEast]]);
var boundsParams = generateBoundsParams(
tile_id,
latSouth,
lngWest,
latNorth,
lngEast
);
this.tileBounds[tile_id] = boundsParams;
if (this.cache.isFresh(tile_id) ) {
// data is fresh in the cache - just render it
this.debugTiles.setState(tile_id, 'cache-fresh');
this.render.processTileData (this.cache.get(tile_id));
} else {
// no fresh data - queue a request
var boundsParams = generateBoundsParams(
tile_id,
latSouth,
lngWest,
latNorth,
lngEast
);
this.tileBounds[tile_id] = boundsParams;
}
}
}
this.renderCachedTiles();
this.processRequestQueue();
this.processRequestQueue(true);
}
window.Request.prototype.renderCachedTiles = function() {
for (var tile_id in this.tileBounds) {
if (this.cache.isFresh(tile_id)) {
// fresh cache data
this.debugTiles.setState(tile_id, 'cache-fresh');
var data = this.cache.get(tile_id);
// render it...
// TODO?? make rendering sort-of asynchronous? rendering a large number of dense tiles is slow - so perhaps use a timer and render one/a few at a time??
// (could even consider splitting these larger tiles into multiple render calls, perhaps?)
this.render.processTileData(data);
// .. and delete from the pending requests
delete this.tileBounds[tile_id];
}
}
}
window.Request.prototype.processRequestQueue = function() {
console.log("Request.processRequestQueue...");
window.MapDataRequest.prototype.processRequestQueue = function(isFirstPass) {
// if nothing left in the queue, end the render. otherwise, send network requests
if (Object.keys(this.tileBounds).length == 0) {
this.render.endRenderPass();
// TODO: start timer for next refresh cycle here
console.log("Request.processRequestQueue: ended");
console.log("finished requesting data!");
if (!window.isIdle()) {
this.refreshOnTimeout(this.REFRESH);
} else {
console.log("suspending map refresh - is idle");
}
return;
}
// create a list of tiles that aren't requested over the network
var pendingTiles = {};
for (var id in this.tileBounds) {
@ -118,15 +159,17 @@ window.Request.prototype.processRequestQueue = function() {
}
}
console.log("Request.processRequestQueue: "+Object.keys(pendingTiles).length+" tiles waiting");
console.log("- request state: "+Object.keys(this.requestedTiles).length+" tiles in "+this.activeRequestCount+" active requests, "+Object.keys(pendingTiles).length+" tiles queued");
var requestTileCount = Math.min(this.MAX_TILES_PER_REQUEST,Math.max(this.MIN_TILES_PER_REQUEST, Object.keys(pendingTiles).length/this.MAX_REQUESTS));
while (this.activeRequestCount < this.MAX_REQUESTS && Object.keys(pendingTiles).length > 0) {
// let's distribute the requests evenly throughout the pending list.
var pendingTilesArray = Object.keys(pendingTiles);
var mod = Math.ceil(Object.keys(pendingTiles).length / this.MAX_TILES_PER_REQUEST);
var mod = Math.ceil(pendingTilesArray.length / requestTileCount);
var tiles = [];
for (var i in pendingTilesArray) {
@ -134,20 +177,17 @@ window.Request.prototype.processRequestQueue = function() {
id = pendingTilesArray[i];
tiles.push(id);
delete pendingTiles[id];
// if (tiles.length >= this.MAX_TILES_PER_REQUEST) {
// break;
// }
}
}
console.log("Request.processRequestQueue: asking for "+tiles.length+" tiles in one request");
console.log("-- asking for "+tiles.length+" tiles in one request");
this.sendTileRequest(tiles);
}
}
window.Request.prototype.sendTileRequest = function(tiles) {
window.MapDataRequest.prototype.sendTileRequest = function(tiles) {
var boundsParamsList = [];
@ -178,12 +218,21 @@ window.Request.prototype.sendTileRequest = function(tiles) {
));
}
window.Request.prototype.requeueTile = function(id, error) {
window.MapDataRequest.prototype.requeueTile = function(id, error) {
if (id in this.tileBounds) {
// tile is currently wanted...
// first, see if the error can be ignored due to retry counts
if (error) {
// if error is true, it was a 'bad' error - in this case we limit the number of retries (or prefer stale cached data?)
this.tileErrorCount[id] = (this.tileErrorCount[id]||0)+1;
if (tileErrorCount < this.MAX_TILE_RETRIES) {
// retry limit low enough - clear the error flag
error = false;
}
}
if (error) {
// if error is still true, retry limit hit. use stale data from cache if available
var data = this.cache.get(id);
if (data) {
// we have cached data - use it, even though it's stale
@ -205,7 +254,7 @@ window.Request.prototype.requeueTile = function(id, error) {
}
window.Request.prototype.handleResponse = function (data, tiles, success) {
window.MapDataRequest.prototype.handleResponse = function (data, tiles, success) {
this.activeRequestCount -= 1;
@ -236,7 +285,6 @@ window.Request.prototype.handleResponse = function (data, tiles, success) {
// server returned an error for this individual data tile
if (val.error == "TIMEOUT") {
console.log('map data tile '+id+' returned TIMEOUT');
// TIMEOUT errors for individual tiles are 'expected'(!) - and result in a silent unlimited retries
this.requeueTile(id, false);
} else {
@ -245,7 +293,6 @@ window.Request.prototype.handleResponse = function (data, tiles, success) {
}
} else {
// no error for this data tile - process it
console.log('map data tile '+id+' is good...');
// store the result in the cache
this.cache.store (id, val);