-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Request Scheduler Prioritization #3397
Changes from 3 commits
825689e
40082fe
546f19d
71f6c50
712881e
e15dfae
7fef79b
a7286e6
7805c29
c3f0326
5b4bb9a
0d94b53
afdebab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ define([ | |
'./loadImage', | ||
'./Math', | ||
'./RequestScheduler', | ||
'./RequestType', | ||
'./TerrainProvider' | ||
], function( | ||
when, | ||
|
@@ -30,6 +31,7 @@ define([ | |
loadImage, | ||
CesiumMath, | ||
RequestScheduler, | ||
RequestType, | ||
TerrainProvider) { | ||
"use strict"; | ||
|
||
|
@@ -200,11 +202,15 @@ define([ | |
* @param {Number} x The X coordinate of the tile for which to request geometry. | ||
* @param {Number} y The Y coordinate of the tile for which to request geometry. | ||
* @param {Number} level The level of the tile for which to request geometry. | ||
* @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited, | ||
* or false if the request should be initiated regardless of the number of requests | ||
* already in progress. | ||
* @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. | ||
* @returns {Promise.<TerrainData>|undefined} A promise for the requested geometry. If this method | ||
* returns undefined instead of a promise, it is an indication that too many requests are already | ||
* pending and the request will be retried later. | ||
*/ | ||
ArcGisImageServerTerrainProvider.prototype.requestTileGeometry = function(x, y, level) { | ||
ArcGisImageServerTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests, distance) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to look at the rest of the code, but I wonder if At the same time, we don't want to make request attempts too heavily if they are going to be made a lot because of throttling (but there's no concern for the ones that result in an actual request). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or maybe to start, just replace both of these with a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be a breaking change for some functions, but would be easy to deprecate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still think this is a good first step:
But as I look at more of the code, the long-term approach could be that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, leaving this named |
||
var rectangle = this._tilingScheme.tileXYToRectangle(x, y, level); | ||
|
||
// Each pixel in the heightmap represents the height at the center of that | ||
|
@@ -231,9 +237,16 @@ define([ | |
url = proxy.getURL(url); | ||
} | ||
|
||
var promise = RequestScheduler.throttleRequest(url, loadImage); | ||
if (!defined(promise)) { | ||
return undefined; | ||
var promise; | ||
|
||
throttleRequests = defaultValue(throttleRequests, true); | ||
if (throttleRequests) { | ||
promise = RequestScheduler.throttleRequest(url, loadImage, RequestType.TERRAIN, distance); | ||
if (!defined(promise)) { | ||
return undefined; | ||
} | ||
} else { | ||
promise = loadImage(url); | ||
} | ||
|
||
var that = this; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,8 +15,31 @@ define([ | |
DeveloperError) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new code in this file needs unit tests. |
||
"use strict"; | ||
|
||
var RequestTypeBudget = function(type) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not |
||
/** | ||
* Total requests allowed this frame | ||
*/ | ||
this.total = 0; | ||
|
||
/** | ||
* Total requests used this frame | ||
*/ | ||
this.used = 0; | ||
|
||
/** | ||
* Type of request. Used for more fine-grained priority sorting | ||
*/ | ||
this.type = type; | ||
}; | ||
|
||
var activeRequestsByServer = {}; | ||
var activeRequests = 0; | ||
var budgets = {}; | ||
var leftoverRequests = []; | ||
|
||
var stats = { | ||
numberOfRequestsThisFrame : 0 | ||
}; | ||
|
||
/** | ||
* Because browsers throttle the number of parallel requests allowed to each server | ||
|
@@ -32,6 +55,70 @@ define([ | |
function RequestScheduler() { | ||
} | ||
|
||
function handleLeftoverRequest(url, type, distance) { | ||
// TODO : don't create a new object, reuse instead | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Address this and the other |
||
leftoverRequests.push({ | ||
url : url, | ||
type : type, | ||
distance : defaultValue(distance, 0.0) | ||
}); | ||
} | ||
|
||
function printStats() { | ||
if (stats.numberOfRequestsThisFrame > 0) { | ||
console.log('Number of requests attempted: ' + stats.numberOfRequestsThisFrame); | ||
} | ||
|
||
var numberOfActiveRequests = RequestScheduler.maximumRequests - RequestScheduler.getNumberOfAvailableRequests(); | ||
if (numberOfActiveRequests > 0) { | ||
console.log('Number of active requests: ' + numberOfActiveRequests); | ||
} | ||
} | ||
|
||
function distanceSortFunction(a, b) { | ||
return a.distance - b.distance; | ||
} | ||
|
||
RequestScheduler.resetBudgets = function() { | ||
// TODO : Right now, assume that requests made the previous frame will probably be issued again the current frame | ||
// TODO : If this assumption changes, we should introduce stealing from other budgets. | ||
|
||
if (!RequestScheduler.prioritize) { | ||
return; | ||
} | ||
|
||
printStats(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this. Instead, for example, the Sandcastle example could call this each frame (or it could be commented out in the Sandcastle example). |
||
stats.numberOfRequestsThisFrame = 0; | ||
|
||
// Reset budget totals | ||
for (var name in budgets) { | ||
if (budgets.hasOwnProperty(name)) { | ||
budgets[name].total = 0; | ||
budgets[name].used = 0; | ||
} | ||
} | ||
|
||
// Sort all requests made this frame by distance | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all? Or all leftover? |
||
var requests = leftoverRequests; | ||
requests.sort(distanceSortFunction); | ||
|
||
// Allocate new budgets based on the distances of leftover requests | ||
var availableRequests = RequestScheduler.getNumberOfAvailableRequests(); | ||
var requestsLength = requests.length; | ||
for (var j = 0; (j < requestsLength) && (availableRequests > 0); ++j) { | ||
var request = requests[j]; | ||
var server = RequestScheduler.getServer(request.url); | ||
var budget = budgets[server]; | ||
var budgetAvailable = RequestScheduler.getNumberOfAvailableRequestsByServer(request.url); | ||
if (budget.total < budgetAvailable) { | ||
++budget.total; | ||
--availableRequests; | ||
} | ||
} | ||
|
||
requests.length = 0; | ||
}; | ||
|
||
var pageUri = typeof document !== 'undefined' ? new Uri(document.location.href) : new Uri(); | ||
|
||
/** | ||
|
@@ -109,6 +196,9 @@ define([ | |
* @param {String} url The URL to request. | ||
* @param {RequestScheduler~RequestFunction} requestFunction The actual function that | ||
* makes the request. | ||
* @param {RequestType} [requestType] The type of request being issued. | ||
* @param {Number} [distance] The distance from the camera, used to prioritize requests. | ||
* | ||
* @returns {Promise.<Object>|undefined} Either undefined, meaning the request would exceed the maximum number of | ||
* parallel requests, or a Promise for the requested data. | ||
* | ||
|
@@ -129,7 +219,7 @@ define([ | |
* } | ||
* | ||
*/ | ||
RequestScheduler.throttleRequest = function(url, requestFunction) { | ||
RequestScheduler.throttleRequest = function(url, requestFunction, requestType, distance) { | ||
//>>includeStart('debug', pragmas.debug); | ||
if (!defined(url)) { | ||
throw new DeveloperError('url is required.'); | ||
|
@@ -140,16 +230,33 @@ define([ | |
} | ||
//>>includeEnd('debug'); | ||
|
||
++stats.numberOfRequestsThisFrame; | ||
|
||
if (activeRequests >= RequestScheduler.maximumRequests) { | ||
handleLeftoverRequest(url, requestType, distance); | ||
return undefined; | ||
} | ||
|
||
var server = RequestScheduler.getServer(url); | ||
var activeRequestsForServer = defaultValue(activeRequestsByServer[server], 0); | ||
if (activeRequestsForServer >= RequestScheduler.maximumRequestsPerServer) { | ||
handleLeftoverRequest(url, requestType, distance); | ||
return undefined; | ||
} | ||
|
||
if (RequestScheduler.prioritize && defined(requestType)) { | ||
var budget = budgets[server]; | ||
if (!defined(budget)) { | ||
budget = new RequestTypeBudget(requestType); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't two request types go to the same server? |
||
budgets[server] = budget; | ||
} | ||
if (budget.used >= budget.total) { | ||
handleLeftoverRequest(url, requestType, distance); | ||
return undefined; | ||
} | ||
++budget.used; | ||
} | ||
|
||
++activeRequests; | ||
activeRequestsByServer[server] = activeRequestsForServer + 1; | ||
|
||
|
@@ -182,5 +289,12 @@ define([ | |
*/ | ||
RequestScheduler.maximumRequests = 10; | ||
|
||
/** | ||
* Specifies if the request scheduler should prioritize incoming requests | ||
* @type {Boolean} | ||
* @default true | ||
*/ | ||
RequestScheduler.prioritize = true; | ||
|
||
return RequestScheduler; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/*global define*/ | ||
define([ | ||
'../Core/freezeObject' | ||
], function( | ||
freezeObject) { | ||
"use strict"; | ||
|
||
/** | ||
* @private | ||
*/ | ||
var RequestType = { | ||
TERRAIN : 0, | ||
IMAGERY : 1, | ||
TILES3D : 2, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Down the road we'll need a bit more control here, e.g., what if there are two 3D tilesets: one for a city and one for a point cloud. Cesium won't know the best priority, but the user might want to override it. Also, what if a tile has a glTF payload, the glTF requests should be No need to change anything now, but we'll need to think these through. |
||
OTHER : 3 | ||
}; | ||
|
||
return freezeObject(RequestType); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When this goes into master - or sooner - include all changes to the public API in CHANGES.md.