From 825689e6f34ff1f68980f970688250478ccd4c7e Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 4 Jan 2016 17:45:39 -0500 Subject: [PATCH 01/11] Prioritize requests by distance --- .../Core/ArcGisImageServerTerrainProvider.js | 4 +- Source/Core/CesiumTerrainProvider.js | 4 +- Source/Core/RequestScheduler.js | 85 ++++++++++++++++++- Source/Core/RequestType.js | 20 +++++ Source/Core/VRTheWorldTerrainProvider.js | 4 +- .../Batched3DModel3DTileContentProvider.js | 4 +- Source/Scene/Cesium3DTileset.js | 4 +- .../Scene/Composite3DTileContentProvider.js | 4 +- Source/Scene/ImageryProvider.js | 10 ++- .../Instanced3DModel3DTileContentProvider.js | 4 +- Source/Scene/Points3DTileContentProvider.js | 4 +- Source/Scene/Scene.js | 3 + 12 files changed, 137 insertions(+), 13 deletions(-) create mode 100644 Source/Core/RequestType.js diff --git a/Source/Core/ArcGisImageServerTerrainProvider.js b/Source/Core/ArcGisImageServerTerrainProvider.js index 3f6572957844..2c60bbb1f33f 100644 --- a/Source/Core/ArcGisImageServerTerrainProvider.js +++ b/Source/Core/ArcGisImageServerTerrainProvider.js @@ -14,6 +14,7 @@ define([ './loadImage', './Math', './RequestScheduler', + './RequestType', './TerrainProvider' ], function( when, @@ -30,6 +31,7 @@ define([ loadImage, CesiumMath, RequestScheduler, + RequestType, TerrainProvider) { "use strict"; @@ -231,7 +233,7 @@ define([ url = proxy.getURL(url); } - var promise = RequestScheduler.throttleRequest(url, loadImage); + var promise = RequestScheduler.throttleRequest(url, loadImage, RequestType.TERRAIN, 0.0); if (!defined(promise)) { return undefined; } diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 6b04ad56f4d8..ee80d630fb8e 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -21,6 +21,7 @@ define([ './OrientedBoundingBox', './QuantizedMeshTerrainData', './RequestScheduler', + './RequestType', './RuntimeError', './TerrainProvider', './TileProviderError' @@ -46,6 +47,7 @@ define([ OrientedBoundingBox, QuantizedMeshTerrainData, RequestScheduler, + RequestType, RuntimeError, TerrainProvider, TileProviderError) { @@ -522,7 +524,7 @@ define([ } throttleRequests = defaultValue(throttleRequests, true); if (throttleRequests) { - promise = RequestScheduler.throttleRequest(url, tileLoader); + promise = RequestScheduler.throttleRequest(url, tileLoader, RequestType.TERRAIN, 0.0); if (!defined(promise)) { return undefined; } diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js index 0079f3e0d295..bb840fdf641d 100644 --- a/Source/Core/RequestScheduler.js +++ b/Source/Core/RequestScheduler.js @@ -15,8 +15,27 @@ define([ DeveloperError) { "use strict"; + var RequestTypeBudget = function(type) { + /** + * 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 = []; /** * Because browsers throttle the number of parallel requests allowed to each server @@ -32,6 +51,52 @@ define([ function RequestScheduler() { } + function handleLeftoverRequest(url, type, distance) { + // TODO : don't create a new object, reuse instead + leftoverRequests.push({ + url : url, + type : type, + distance : defaultValue(distance, 0.0) + }); + } + + 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. + + // 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 + 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 +174,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.|undefined} Either undefined, meaning the request would exceed the maximum number of * parallel requests, or a Promise for the requested data. * @@ -129,7 +197,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.'); @@ -141,15 +209,30 @@ define([ //>>includeEnd('debug'); 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 (defined(requestType)) { + var budget = budgets[server]; + if (!defined(budget)) { + budget = new RequestTypeBudget(requestType); + budgets[server] = budget; + } + if (budget.used >= budget.total) { + handleLeftoverRequest(url, requestType, distance); + return undefined; + } + ++budget.used; + } + ++activeRequests; activeRequestsByServer[server] = activeRequestsForServer + 1; diff --git a/Source/Core/RequestType.js b/Source/Core/RequestType.js new file mode 100644 index 000000000000..47ae5a8085e9 --- /dev/null +++ b/Source/Core/RequestType.js @@ -0,0 +1,20 @@ +/*global define*/ +define([ + '../Core/freezeObject' + ], function( + freezeObject) { + "use strict"; + + /** + * @private + */ + var RequestType = { + TERRAIN : 0, + IMAGERY : 1, + TILES3D : 2, + OTHER : 3, + NUMBER_OF_REQUEST_TYPES : 4 + }; + + return freezeObject(RequestType); +}); diff --git a/Source/Core/VRTheWorldTerrainProvider.js b/Source/Core/VRTheWorldTerrainProvider.js index da7dc915a12e..e8e58093adc1 100644 --- a/Source/Core/VRTheWorldTerrainProvider.js +++ b/Source/Core/VRTheWorldTerrainProvider.js @@ -16,6 +16,7 @@ define([ './Math', './Rectangle', './RequestScheduler', + './RequestType', './TerrainProvider', './TileProviderError' ], function( @@ -35,6 +36,7 @@ define([ CesiumMath, Rectangle, RequestScheduler, + RequestType, TerrainProvider, TileProviderError) { "use strict"; @@ -274,7 +276,7 @@ define([ throttleRequests = defaultValue(throttleRequests, true); if (throttleRequests) { - promise = RequestScheduler.throttleRequest(url, loadImage); + promise = RequestScheduler.throttleRequest(url, loadImage, RequestType.TERRAIN, 0.0); if (!defined(promise)) { return undefined; } diff --git a/Source/Scene/Batched3DModel3DTileContentProvider.js b/Source/Scene/Batched3DModel3DTileContentProvider.js index d92618e9c2ae..c565e31a51d5 100644 --- a/Source/Scene/Batched3DModel3DTileContentProvider.js +++ b/Source/Scene/Batched3DModel3DTileContentProvider.js @@ -9,6 +9,7 @@ define([ '../Core/getStringFromTypedArray', '../Core/loadArrayBuffer', '../Core/RequestScheduler', + '../Core/RequestType', '../ThirdParty/when', './BatchedModel', './Cesium3DTileBatchTableResources', @@ -24,6 +25,7 @@ define([ getStringFromTypedArray, loadArrayBuffer, RequestScheduler, + RequestType, when, BatchedModel, Cesium3DTileBatchTableResources, @@ -121,7 +123,7 @@ define([ Batched3DModel3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer); + var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, 0.0); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 9b70a6484c16..15cb01e6a6ed 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -11,6 +11,7 @@ define([ '../Core/loadJson', '../Core/Math', '../Core/RequestScheduler', + '../Core/RequestType', '../ThirdParty/Uri', '../ThirdParty/when', './Cesium3DTile', @@ -30,6 +31,7 @@ define([ loadJson, CesiumMath, RequestScheduler, + RequestType, Uri, when, Cesium3DTile, @@ -226,7 +228,7 @@ define([ */ Cesium3DTileset.prototype.loadTilesJson = function(tilesJson, parentTile) { var tileset = this; - var promise = RequestScheduler.throttleRequest(tilesJson, loadJson); + var promise = RequestScheduler.throttleRequest(tilesJson, loadJson, RequestType.TILES3D, 0.0); if (!defined(promise)) { return undefined; diff --git a/Source/Scene/Composite3DTileContentProvider.js b/Source/Scene/Composite3DTileContentProvider.js index dcf09ebae1bd..ff7ee8c23a46 100644 --- a/Source/Scene/Composite3DTileContentProvider.js +++ b/Source/Scene/Composite3DTileContentProvider.js @@ -7,6 +7,7 @@ define([ '../Core/getMagic', '../Core/loadArrayBuffer', '../Core/RequestScheduler', + '../Core/RequestType', '../ThirdParty/when', './Cesium3DTileContentState' ], function( @@ -17,6 +18,7 @@ define([ getMagic, loadArrayBuffer, RequestScheduler, + RequestType, when, Cesium3DTileContentState) { "use strict"; @@ -57,7 +59,7 @@ define([ Composite3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer); + var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, 0.0); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/ImageryProvider.js b/Source/Scene/ImageryProvider.js index f542f5d26707..1ecaeba9c42d 100644 --- a/Source/Scene/ImageryProvider.js +++ b/Source/Scene/ImageryProvider.js @@ -5,14 +5,16 @@ define([ '../Core/DeveloperError', '../Core/loadImage', '../Core/loadImageViaBlob', - '../Core/RequestScheduler' + '../Core/RequestScheduler', + '../Core/RequestType' ], function( defined, defineProperties, DeveloperError, loadImage, loadImageViaBlob, - RequestScheduler) { + RequestScheduler, + RequestType) { "use strict"; /** @@ -307,9 +309,9 @@ define([ */ ImageryProvider.loadImage = function(imageryProvider, url) { if (defined(imageryProvider.tileDiscardPolicy)) { - return RequestScheduler.throttleRequest(url, loadImageViaBlob); + return RequestScheduler.throttleRequest(url, loadImageViaBlob, RequestType.IMAGERY, 0.0); } - return RequestScheduler.throttleRequest(url, loadImage); + return RequestScheduler.throttleRequest(url, loadImage, RequestType.IMAGERY, 0.0); }; return ImageryProvider; diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index e31a1cd311bf..e1969db4d015 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -12,6 +12,7 @@ define([ '../Core/loadArrayBuffer', '../Core/Matrix4', '../Core/RequestScheduler', + '../Core/RequestType', '../Core/Transforms', '../ThirdParty/Uri', '../ThirdParty/when', @@ -32,6 +33,7 @@ define([ loadArrayBuffer, Matrix4, RequestScheduler, + RequestType, Transforms, Uri, when, @@ -130,7 +132,7 @@ define([ Instanced3DModel3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer); + var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, 0.0); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/Points3DTileContentProvider.js b/Source/Scene/Points3DTileContentProvider.js index c6773f3b7561..e9388962d01b 100644 --- a/Source/Scene/Points3DTileContentProvider.js +++ b/Source/Scene/Points3DTileContentProvider.js @@ -12,6 +12,7 @@ define([ '../Core/loadArrayBuffer', '../Core/PointGeometry', '../Core/RequestScheduler', + '../Core/RequestType', '../ThirdParty/when', './Cesium3DTileContentState', './PointAppearance', @@ -29,6 +30,7 @@ define([ loadArrayBuffer, PointGeometry, RequestScheduler, + RequestType, when, Cesium3DTileContentState, PointAppearance, @@ -73,7 +75,7 @@ define([ Points3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer); + var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, 0.0); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 13c1e85bf042..e04be25f2e9a 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -27,6 +27,7 @@ define([ '../Core/Matrix4', '../Core/mergeSort', '../Core/Occluder', + '../Core/RequestScheduler', '../Core/ShowGeometryInstanceAttribute', '../Renderer/ClearCommand', '../Renderer/ComputeEngine', @@ -89,6 +90,7 @@ define([ Matrix4, mergeSort, Occluder, + RequestScheduler, ShowGeometryInstanceAttribute, ClearCommand, ComputeEngine, @@ -1785,6 +1787,7 @@ define([ scene._preRender.raiseEvent(scene, time); scene._jobScheduler.resetBudgets(); + RequestScheduler.resetBudgets(); var context = scene.context; var us = context.uniformState; From 40082feebf39d089ad47e10165f9873621c042e8 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 7 Jan 2016 09:16:07 -0500 Subject: [PATCH 02/11] Pass distance to the Request Scheduler --- .../Core/ArcGisImageServerTerrainProvider.js | 19 ++++++++--- Source/Core/CesiumTerrainProvider.js | 6 ++-- Source/Core/EllipsoidTerrainProvider.js | 4 ++- Source/Core/RequestScheduler.js | 33 ++++++++++++++++++- Source/Core/RequestType.js | 3 +- Source/Core/TerrainProvider.js | 2 ++ Source/Core/VRTheWorldTerrainProvider.js | 5 +-- .../Scene/ArcGisMapServerImageryProvider.js | 5 +-- .../Batched3DModel3DTileContentProvider.js | 4 ++- Source/Scene/BingMapsImageryProvider.js | 5 +-- Source/Scene/Cesium3DTileset.js | 4 ++- .../Scene/Composite3DTileContentProvider.js | 3 +- Source/Scene/GlobeSurfaceTile.js | 9 ++++- Source/Scene/GoogleEarthImageryProvider.js | 5 +-- Source/Scene/GridImageryProvider.js | 3 +- Source/Scene/Imagery.js | 4 +-- Source/Scene/ImageryLayer.js | 4 +-- Source/Scene/ImageryProvider.js | 9 +++-- .../Instanced3DModel3DTileContentProvider.js | 4 ++- Source/Scene/MapboxImageryProvider.js | 5 +-- Source/Scene/OpenStreetMapImageryProvider.js | 5 +-- Source/Scene/Points3DTileContentProvider.js | 5 ++- Source/Scene/SingleTileImageryProvider.js | 3 +- .../Scene/TileCoordinatesImageryProvider.js | 3 +- Source/Scene/TileImagery.js | 4 +-- Source/Scene/TileMapServiceImageryProvider.js | 5 +-- Source/Scene/TileTerrain.js | 16 ++++----- Source/Scene/UrlTemplateImageryProvider.js | 5 +-- Source/Scene/WebMapServiceImageryProvider.js | 5 +-- .../Scene/WebMapTileServiceImageryProvider.js | 5 +-- Specs/spec-main.js | 2 ++ 31 files changed, 136 insertions(+), 58 deletions(-) diff --git a/Source/Core/ArcGisImageServerTerrainProvider.js b/Source/Core/ArcGisImageServerTerrainProvider.js index 2c60bbb1f33f..31e8420de621 100644 --- a/Source/Core/ArcGisImageServerTerrainProvider.js +++ b/Source/Core/ArcGisImageServerTerrainProvider.js @@ -202,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.|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) { var rectangle = this._tilingScheme.tileXYToRectangle(x, y, level); // Each pixel in the heightmap represents the height at the center of that @@ -233,9 +237,16 @@ define([ url = proxy.getURL(url); } - var promise = RequestScheduler.throttleRequest(url, loadImage, RequestType.TERRAIN, 0.0); - 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; diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index ee80d630fb8e..8af237bbbe02 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -479,6 +479,8 @@ define([ * @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.|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. @@ -486,7 +488,7 @@ define([ * @exception {DeveloperError} This function must not be called before {@link CesiumTerrainProvider#ready} * returns true. */ - CesiumTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) { + CesiumTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests, distance) { //>>includeStart('debug', pragmas.debug) if (!this._ready) { throw new DeveloperError('requestTileGeometry must not be called before the terrain provider is ready.'); @@ -524,7 +526,7 @@ define([ } throttleRequests = defaultValue(throttleRequests, true); if (throttleRequests) { - promise = RequestScheduler.throttleRequest(url, tileLoader, RequestType.TERRAIN, 0.0); + promise = RequestScheduler.throttleRequest(url, tileLoader, RequestType.TERRAIN, distance); if (!defined(promise)) { return undefined; } diff --git a/Source/Core/EllipsoidTerrainProvider.js b/Source/Core/EllipsoidTerrainProvider.js index e18a125c9b09..6f35f1c4c78d 100644 --- a/Source/Core/EllipsoidTerrainProvider.js +++ b/Source/Core/EllipsoidTerrainProvider.js @@ -163,11 +163,13 @@ define([ * @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.|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. */ - EllipsoidTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) { + EllipsoidTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests, distance) { return this._terrainData; }; diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js index bb840fdf641d..0524d2a18567 100644 --- a/Source/Core/RequestScheduler.js +++ b/Source/Core/RequestScheduler.js @@ -37,6 +37,10 @@ define([ var budgets = {}; var leftoverRequests = []; + var stats = { + numberOfRequestsThisFrame : 0 + }; + /** * Because browsers throttle the number of parallel requests allowed to each server * and across all servers, this class tracks the number of active requests in progress @@ -60,6 +64,17 @@ define([ }); } + 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; } @@ -68,6 +83,13 @@ define([ // 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(); + stats.numberOfRequestsThisFrame = 0; + // Reset budget totals for (var name in budgets) { if (budgets.hasOwnProperty(name)) { @@ -208,6 +230,8 @@ define([ } //>>includeEnd('debug'); + ++stats.numberOfRequestsThisFrame; + if (activeRequests >= RequestScheduler.maximumRequests) { handleLeftoverRequest(url, requestType, distance); return undefined; @@ -220,7 +244,7 @@ define([ return undefined; } - if (defined(requestType)) { + if (RequestScheduler.prioritize && defined(requestType)) { var budget = budgets[server]; if (!defined(budget)) { budget = new RequestTypeBudget(requestType); @@ -265,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; }); diff --git a/Source/Core/RequestType.js b/Source/Core/RequestType.js index 47ae5a8085e9..195be9693ce3 100644 --- a/Source/Core/RequestType.js +++ b/Source/Core/RequestType.js @@ -12,8 +12,7 @@ define([ TERRAIN : 0, IMAGERY : 1, TILES3D : 2, - OTHER : 3, - NUMBER_OF_REQUEST_TYPES : 4 + OTHER : 3 }; return freezeObject(RequestType); diff --git a/Source/Core/TerrainProvider.js b/Source/Core/TerrainProvider.js index 2520a78e408f..97e486bcf00c 100644 --- a/Source/Core/TerrainProvider.js +++ b/Source/Core/TerrainProvider.js @@ -189,6 +189,8 @@ define([ * @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.|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. diff --git a/Source/Core/VRTheWorldTerrainProvider.js b/Source/Core/VRTheWorldTerrainProvider.js index e8e58093adc1..119e89c77f40 100644 --- a/Source/Core/VRTheWorldTerrainProvider.js +++ b/Source/Core/VRTheWorldTerrainProvider.js @@ -255,11 +255,12 @@ define([ * @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.|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. */ - VRTheWorldTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) { + VRTheWorldTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests, distance) { if (!this.ready) { throw new DeveloperError('requestTileGeometry must not be called before ready returns true.'); } @@ -276,7 +277,7 @@ define([ throttleRequests = defaultValue(throttleRequests, true); if (throttleRequests) { - promise = RequestScheduler.throttleRequest(url, loadImage, RequestType.TERRAIN, 0.0); + promise = RequestScheduler.throttleRequest(url, loadImage, RequestType.TERRAIN, distance); if (!defined(promise)) { return undefined; } diff --git a/Source/Scene/ArcGisMapServerImageryProvider.js b/Source/Scene/ArcGisMapServerImageryProvider.js index 6cf1f31dfc46..3f38396c0b0e 100644 --- a/Source/Scene/ArcGisMapServerImageryProvider.js +++ b/Source/Scene/ArcGisMapServerImageryProvider.js @@ -578,6 +578,7 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an @@ -585,7 +586,7 @@ define([ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - ArcGisMapServerImageryProvider.prototype.requestImage = function(x, y, level) { + ArcGisMapServerImageryProvider.prototype.requestImage = function(x, y, level, distance) { //>>includeStart('debug', pragmas.debug); if (!this._ready) { throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); @@ -593,7 +594,7 @@ define([ //>>includeEnd('debug'); var url = buildImageUrl(this, x, y, level); - return ImageryProvider.loadImage(this, url); + return ImageryProvider.loadImage(this, url, distance); }; /** diff --git a/Source/Scene/Batched3DModel3DTileContentProvider.js b/Source/Scene/Batched3DModel3DTileContentProvider.js index c565e31a51d5..664dc87de5a0 100644 --- a/Source/Scene/Batched3DModel3DTileContentProvider.js +++ b/Source/Scene/Batched3DModel3DTileContentProvider.js @@ -40,6 +40,7 @@ define([ this._model = undefined; this._url = url; this._tileset = tileset; + this._tile = tile; /** * @readonly @@ -123,7 +124,8 @@ define([ Batched3DModel3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, 0.0); + var distance = this._tile.distanceToCamera; + var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, distance); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/BingMapsImageryProvider.js b/Source/Scene/BingMapsImageryProvider.js index b606cee9539b..f0b79088142d 100644 --- a/Source/Scene/BingMapsImageryProvider.js +++ b/Source/Scene/BingMapsImageryProvider.js @@ -517,6 +517,7 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an @@ -524,7 +525,7 @@ define([ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - BingMapsImageryProvider.prototype.requestImage = function(x, y, level) { + BingMapsImageryProvider.prototype.requestImage = function(x, y, level, distance) { //>>includeStart('debug', pragmas.debug); if (!this._ready) { throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); @@ -532,7 +533,7 @@ define([ //>>includeEnd('debug'); var url = buildImageUrl(this, x, y, level); - return ImageryProvider.loadImage(this, url); + return ImageryProvider.loadImage(this, url, distance); }; /** diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 15cb01e6a6ed..4fbf06bf5452 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -228,7 +228,9 @@ define([ */ Cesium3DTileset.prototype.loadTilesJson = function(tilesJson, parentTile) { var tileset = this; - var promise = RequestScheduler.throttleRequest(tilesJson, loadJson, RequestType.TILES3D, 0.0); + + // We don't know the distance of the tileset until tiles.json is loaded, so use the default distance for now + var promise = RequestScheduler.throttleRequest(tilesJson, loadJson, RequestType.TILES3D); if (!defined(promise)) { return undefined; diff --git a/Source/Scene/Composite3DTileContentProvider.js b/Source/Scene/Composite3DTileContentProvider.js index ff7ee8c23a46..ea6d4c5665d8 100644 --- a/Source/Scene/Composite3DTileContentProvider.js +++ b/Source/Scene/Composite3DTileContentProvider.js @@ -59,7 +59,8 @@ define([ Composite3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, 0.0); + var distance = this._tile.distanceToCamera; + var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, distance); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js index 3aed30c53d6b..e4bfb4d21b6c 100644 --- a/Source/Scene/GlobeSurfaceTile.js +++ b/Source/Scene/GlobeSurfaceTile.js @@ -245,6 +245,13 @@ define([ var surfaceTile = tile.data; if (!defined(surfaceTile)) { surfaceTile = tile.data = new GlobeSurfaceTile(); + surfaceTile.tileBoundingBox = new TileBoundingBox({ + rectangle : tile.rectangle, + ellipsoid : tile.tilingScheme.ellipsoid + }); + // Set the distance of the tile now in order to prioritize the request later. + // Rough estimate, since the tile height isn't known until the terrain is loaded. + tile._distance = surfaceTile.tileBoundingBox.distanceToCamera(frameState); } if (tile.state === QuadtreeTileLoadState.START) { @@ -343,7 +350,7 @@ define([ var suspendUpsampling = false; if (defined(loaded)) { - loaded.processLoadStateMachine(frameState, terrainProvider, tile.x, tile.y, tile.level); + loaded.processLoadStateMachine(frameState, terrainProvider, tile.x, tile.y, tile.level, tile._distance); // Publish the terrain data on the tile as soon as it is available. // We'll potentially need it to upsample child tiles. diff --git a/Source/Scene/GoogleEarthImageryProvider.js b/Source/Scene/GoogleEarthImageryProvider.js index 215c50fa81e7..4b54348c88ee 100644 --- a/Source/Scene/GoogleEarthImageryProvider.js +++ b/Source/Scene/GoogleEarthImageryProvider.js @@ -536,6 +536,7 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an @@ -543,7 +544,7 @@ define([ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - GoogleEarthImageryProvider.prototype.requestImage = function(x, y, level) { + GoogleEarthImageryProvider.prototype.requestImage = function(x, y, level, distance) { //>>includeStart('debug', pragmas.debug); if (!this._ready) { throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); @@ -551,7 +552,7 @@ define([ //>>includeEnd('debug'); var url = buildImageUrl(this, x, y, level); - return ImageryProvider.loadImage(this, url); + return ImageryProvider.loadImage(this, url, distance); }; /** diff --git a/Source/Scene/GridImageryProvider.js b/Source/Scene/GridImageryProvider.js index 18077841beea..d6565cf3a289 100644 --- a/Source/Scene/GridImageryProvider.js +++ b/Source/Scene/GridImageryProvider.js @@ -321,12 +321,13 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an * Image or a Canvas DOM object. */ - GridImageryProvider.prototype.requestImage = function(x, y, level) { + GridImageryProvider.prototype.requestImage = function(x, y, level, distance) { return this._canvas; }; diff --git a/Source/Scene/Imagery.js b/Source/Scene/Imagery.js index 6b3297b413a2..5bb406aeb1cf 100644 --- a/Source/Scene/Imagery.js +++ b/Source/Scene/Imagery.js @@ -79,10 +79,10 @@ define([ return this.referenceCount; }; - Imagery.prototype.processStateMachine = function(frameState) { + Imagery.prototype.processStateMachine = function(frameState, distance) { if (this.state === ImageryState.UNLOADED) { this.state = ImageryState.TRANSITIONING; - this.imageryLayer._requestImagery(this); + this.imageryLayer._requestImagery(this, distance); } if (this.state === ImageryState.RECEIVED) { diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js index 3be76650bc91..2bda8c89db03 100644 --- a/Source/Scene/ImageryLayer.js +++ b/Source/Scene/ImageryLayer.js @@ -589,7 +589,7 @@ define([ * * @param {Imagery} imagery The imagery to request. */ - ImageryLayer.prototype._requestImagery = function(imagery) { + ImageryLayer.prototype._requestImagery = function(imagery, distance) { var imageryProvider = this._imageryProvider; var that = this; @@ -623,7 +623,7 @@ define([ function doRequest() { imagery.state = ImageryState.TRANSITIONING; - var imagePromise = imageryProvider.requestImage(imagery.x, imagery.y, imagery.level); + var imagePromise = imageryProvider.requestImage(imagery.x, imagery.y, imagery.level, distance); if (!defined(imagePromise)) { // Too many parallel requests, so postpone loading tile. diff --git a/Source/Scene/ImageryProvider.js b/Source/Scene/ImageryProvider.js index 1ecaeba9c42d..114a903ec127 100644 --- a/Source/Scene/ImageryProvider.js +++ b/Source/Scene/ImageryProvider.js @@ -266,6 +266,7 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an @@ -301,17 +302,19 @@ define([ * too many requests pending, this function will instead return undefined, indicating * that the request should be retried later. * + * @param {ImageryProvider} imageryProvider The imagery provider * @param {String} url The URL of the image. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an * Image or a Canvas DOM object. */ - ImageryProvider.loadImage = function(imageryProvider, url) { + ImageryProvider.loadImage = function(imageryProvider, url, distance) { if (defined(imageryProvider.tileDiscardPolicy)) { - return RequestScheduler.throttleRequest(url, loadImageViaBlob, RequestType.IMAGERY, 0.0); + return RequestScheduler.throttleRequest(url, loadImageViaBlob, RequestType.IMAGERY, distance); } - return RequestScheduler.throttleRequest(url, loadImage, RequestType.IMAGERY, 0.0); + return RequestScheduler.throttleRequest(url, loadImage, RequestType.IMAGERY, distance); }; return ImageryProvider; diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index e1969db4d015..e8112eb1e248 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -50,6 +50,7 @@ define([ this._modelInstanceCollection = undefined; this._url = url; this._tileset = tileset; + this._tile = tile; this._boundingVolume = tile.orientedBoundingBox; /** @@ -132,7 +133,8 @@ define([ Instanced3DModel3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, 0.0); + var distance = this._tile.distanceToCamera; + var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, distance); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/MapboxImageryProvider.js b/Source/Scene/MapboxImageryProvider.js index df89828e58b7..3e122f72d564 100644 --- a/Source/Scene/MapboxImageryProvider.js +++ b/Source/Scene/MapboxImageryProvider.js @@ -309,6 +309,7 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an @@ -316,8 +317,8 @@ define([ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - MapboxImageryProvider.prototype.requestImage = function(x, y, level) { - return this._imageryProvider.requestImage(x, y, level); + MapboxImageryProvider.prototype.requestImage = function(x, y, level, distance) { + return this._imageryProvider.requestImage(x, y, level, distance); }; /** diff --git a/Source/Scene/OpenStreetMapImageryProvider.js b/Source/Scene/OpenStreetMapImageryProvider.js index 8e1857e29867..c714cd3619cc 100644 --- a/Source/Scene/OpenStreetMapImageryProvider.js +++ b/Source/Scene/OpenStreetMapImageryProvider.js @@ -375,6 +375,7 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an @@ -382,7 +383,7 @@ define([ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - OpenStreetMapImageryProvider.prototype.requestImage = function(x, y, level) { + OpenStreetMapImageryProvider.prototype.requestImage = function(x, y, level, distance) { //>>includeStart('debug', pragmas.debug); if (!this._ready) { throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); @@ -390,7 +391,7 @@ define([ //>>includeEnd('debug'); var url = buildImageUrl(this, x, y, level); - return ImageryProvider.loadImage(this, url); + return ImageryProvider.loadImage(this, url, distance); }; /** diff --git a/Source/Scene/Points3DTileContentProvider.js b/Source/Scene/Points3DTileContentProvider.js index e9388962d01b..79f44b35024f 100644 --- a/Source/Scene/Points3DTileContentProvider.js +++ b/Source/Scene/Points3DTileContentProvider.js @@ -43,6 +43,8 @@ define([ function Points3DTileContentProvider(tileset, tile, url) { this._primitive = undefined; this._url = url; + this._tileset = tileset; + this._tile = tile; /** * @readonly @@ -75,7 +77,8 @@ define([ Points3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, 0.0); + var distance = this._tile.distanceToCamera; + var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, distance); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/SingleTileImageryProvider.js b/Source/Scene/SingleTileImageryProvider.js index 601377cba6e2..e99c09f625d2 100644 --- a/Source/Scene/SingleTileImageryProvider.js +++ b/Source/Scene/SingleTileImageryProvider.js @@ -370,6 +370,7 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an @@ -377,7 +378,7 @@ define([ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - SingleTileImageryProvider.prototype.requestImage = function(x, y, level) { + SingleTileImageryProvider.prototype.requestImage = function(x, y, level, distance) { //>>includeStart('debug', pragmas.debug); if (!this._ready) { throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); diff --git a/Source/Scene/TileCoordinatesImageryProvider.js b/Source/Scene/TileCoordinatesImageryProvider.js index 3ecdd77a37b4..a351c44ec57a 100644 --- a/Source/Scene/TileCoordinatesImageryProvider.js +++ b/Source/Scene/TileCoordinatesImageryProvider.js @@ -240,12 +240,13 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an * Image or a Canvas DOM object. */ - TileCoordinatesImageryProvider.prototype.requestImage = function(x, y, level) { + TileCoordinatesImageryProvider.prototype.requestImage = function(x, y, level, distance) { var canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 256; diff --git a/Source/Scene/TileImagery.js b/Source/Scene/TileImagery.js index 9c301a1d30d3..d42ef1fafa6d 100644 --- a/Source/Scene/TileImagery.js +++ b/Source/Scene/TileImagery.js @@ -48,7 +48,7 @@ define([ var loadingImagery = this.loadingImagery; var imageryLayer = loadingImagery.imageryLayer; - loadingImagery.processStateMachine(frameState); + loadingImagery.processStateMachine(frameState, tile._distance); if (loadingImagery.state === ImageryState.READY) { if (defined(this.readyImagery)) { @@ -90,7 +90,7 @@ define([ // Push the ancestor's load process along a bit. This is necessary because some ancestor imagery // tiles may not be attached directly to a terrain tile. Such tiles will never load if // we don't do it here. - closestAncestorThatNeedsLoading.processStateMachine(frameState); + closestAncestorThatNeedsLoading.processStateMachine(frameState, tile._distance); return false; // not done loading } else { // This imagery tile is failed or invalid, and we have the "best available" substitute. diff --git a/Source/Scene/TileMapServiceImageryProvider.js b/Source/Scene/TileMapServiceImageryProvider.js index 8811f5665124..1049868ac1e0 100644 --- a/Source/Scene/TileMapServiceImageryProvider.js +++ b/Source/Scene/TileMapServiceImageryProvider.js @@ -537,12 +537,13 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an * Image or a Canvas DOM object. */ - TileMapServiceImageryProvider.prototype.requestImage = function(x, y, level) { + TileMapServiceImageryProvider.prototype.requestImage = function(x, y, level, distance) { //>>includeStart('debug', pragmas.debug); if (!this._ready) { throw new DeveloperError('requestImage must not be called before the imagery provider is ready.'); @@ -550,7 +551,7 @@ define([ //>>includeEnd('debug'); var url = buildImageUrl(this, x, y, level); - return ImageryProvider.loadImage(this, url); + return ImageryProvider.loadImage(this, url, distance); }; /** diff --git a/Source/Scene/TileTerrain.js b/Source/Scene/TileTerrain.js index 6a8cf893c946..25a2661254bd 100644 --- a/Source/Scene/TileTerrain.js +++ b/Source/Scene/TileTerrain.js @@ -85,12 +85,8 @@ define([ surfaceTile.maximumHeight = mesh.maximumHeight; surfaceTile.boundingSphere3D = BoundingSphere.clone(mesh.boundingSphere3D, surfaceTile.boundingSphere3D); surfaceTile.orientedBoundingBox = OrientedBoundingBox.clone(mesh.orientedBoundingBox, surfaceTile.orientedBoundingBox); - surfaceTile.tileBoundingBox = new TileBoundingBox({ - rectangle : tile.rectangle, - minimumHeight : mesh.minimumHeight, - maximumHeight : mesh.maximumHeight, - ellipsoid : tile.tilingScheme.ellipsoid - }); + surfaceTile.tileBoundingBox.minimumHeight = mesh.minimumHeight; + surfaceTile.tileBoundingBox.maximumHeight = mesh.maximumHeight; tile.data.occludeePointInScaledSpace = Cartesian3.clone(mesh.occludeePointInScaledSpace, surfaceTile.occludeePointInScaledSpace); @@ -102,9 +98,9 @@ define([ this.vertexArray = undefined; }; - TileTerrain.prototype.processLoadStateMachine = function(frameState, terrainProvider, x, y, level) { + TileTerrain.prototype.processLoadStateMachine = function(frameState, terrainProvider, x, y, level, distance) { if (this.state === TerrainState.UNLOADED) { - requestTileGeometry(this, terrainProvider, x, y, level); + requestTileGeometry(this, terrainProvider, x, y, level, distance); } if (this.state === TerrainState.RECEIVED) { @@ -116,7 +112,7 @@ define([ } }; - function requestTileGeometry(tileTerrain, terrainProvider, x, y, level) { + function requestTileGeometry(tileTerrain, terrainProvider, x, y, level, distance) { function success(terrainData) { tileTerrain.data = terrainData; tileTerrain.state = TerrainState.RECEIVED; @@ -139,7 +135,7 @@ define([ function doRequest() { // Request the terrain from the terrain provider. - tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level); + tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level, true, distance); // If the request method returns undefined (instead of a promise), the request // has been deferred. diff --git a/Source/Scene/UrlTemplateImageryProvider.js b/Source/Scene/UrlTemplateImageryProvider.js index 31252edb92b2..c4710c60dd11 100644 --- a/Source/Scene/UrlTemplateImageryProvider.js +++ b/Source/Scene/UrlTemplateImageryProvider.js @@ -474,14 +474,15 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an * Image or a Canvas DOM object. */ - UrlTemplateImageryProvider.prototype.requestImage = function(x, y, level) { + UrlTemplateImageryProvider.prototype.requestImage = function(x, y, level, distance) { var url = buildImageUrl(this, x, y, level); - return ImageryProvider.loadImage(this, url); + return ImageryProvider.loadImage(this, url, distance); }; /** diff --git a/Source/Scene/WebMapServiceImageryProvider.js b/Source/Scene/WebMapServiceImageryProvider.js index ea34b08181f8..0602dedd0839 100644 --- a/Source/Scene/WebMapServiceImageryProvider.js +++ b/Source/Scene/WebMapServiceImageryProvider.js @@ -420,6 +420,7 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an @@ -427,8 +428,8 @@ define([ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - WebMapServiceImageryProvider.prototype.requestImage = function(x, y, level) { - return this._tileProvider.requestImage(x, y, level); + WebMapServiceImageryProvider.prototype.requestImage = function(x, y, level, distance) { + return this._tileProvider.requestImage(x, y, level, distance); }; /** diff --git a/Source/Scene/WebMapTileServiceImageryProvider.js b/Source/Scene/WebMapTileServiceImageryProvider.js index cd858d6bc6cf..1195467ef61e 100644 --- a/Source/Scene/WebMapTileServiceImageryProvider.js +++ b/Source/Scene/WebMapTileServiceImageryProvider.js @@ -426,6 +426,7 @@ define([ * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. + * @param {Number} [distance] The distance of the tile from the camera, used to prioritize requests. * @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an @@ -433,9 +434,9 @@ define([ * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ - WebMapTileServiceImageryProvider.prototype.requestImage = function(x, y, level) { + WebMapTileServiceImageryProvider.prototype.requestImage = function(x, y, level, distance) { var url = buildImageUrl(this, x, y, level); - return ImageryProvider.loadImage(this, url); + return ImageryProvider.loadImage(this, url, distance); }; /** diff --git a/Specs/spec-main.js b/Specs/spec-main.js index f52ca167f78f..b473df9579e1 100644 --- a/Specs/spec-main.js +++ b/Specs/spec-main.js @@ -58,6 +58,8 @@ /*global jasmineRequire,jasmine,exports,specs*/ + Cesium.RequestScheduler.prioritize = false; + var when = Cesium.when; if (typeof paths !== 'undefined') { From 546f19d65b1bc09610370f44e172bf1377a91d98 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 7 Jan 2016 11:27:42 -0500 Subject: [PATCH 03/11] Handle unissued requests in Cesium3DTileset --- Source/Scene/Cesium3DTileset.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 4fbf06bf5452..67e6f52d8ef2 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -321,14 +321,17 @@ define([ return; } - var stats = tiles3D._statistics; - ++stats.numberOfPendingRequests; - addLoadProgressEvent(tiles3D); - tile.requestContent(); - var removeFunction = removeFromProcessingQueue(tiles3D, tile); - when(tile.processingPromise).then(addToProcessingQueue(tiles3D, tile)).otherwise(removeFunction); - when(tile.readyPromise).then(removeFunction).otherwise(removeFunction); + + if (!tile.isContentUnloaded()) { + var stats = tiles3D._statistics; + ++stats.numberOfPendingRequests; + addLoadProgressEvent(tiles3D); + + var removeFunction = removeFromProcessingQueue(tiles3D, tile); + when(tile.processingPromise).then(addToProcessingQueue(tiles3D, tile)).otherwise(removeFunction); + when(tile.readyPromise).then(removeFunction).otherwise(removeFunction); + } } function hasAvailableRequests(tiles3D) { From 71f6c507280e8d979e9e2989501d8ba9c8df4fda Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 13 Jan 2016 13:52:46 -0500 Subject: [PATCH 04/11] Updated RequestScheduler --- .../Core/ArcGisImageServerTerrainProvider.js | 31 +- Source/Core/CesiumTerrainProvider.js | 35 ++- Source/Core/EarthOrientationParameters.js | 4 +- Source/Core/EllipsoidTerrainProvider.js | 7 +- Source/Core/Iau2006XysData.js | 4 +- Source/Core/Request.js | 64 ++++ Source/Core/RequestScheduler.js | 244 ++++++++++----- Source/Core/TerrainProvider.js | 5 +- Source/Core/VRTheWorldTerrainProvider.js | 32 +- Source/Core/sampleTerrain.js | 2 +- Source/DataSources/CzmlDataSource.js | 4 +- Source/DataSources/GeoJsonDataSource.js | 4 +- Source/DataSources/KmlDataSource.js | 6 +- .../Scene/ArcGisMapServerImageryProvider.js | 4 +- .../Batched3DModel3DTileContentProvider.js | 9 +- Source/Scene/BingMapsImageryProvider.js | 4 +- Source/Scene/Cesium3DTileset.js | 8 +- .../Scene/Composite3DTileContentProvider.js | 9 +- Source/Scene/DiscardMissingTileImagePolicy.js | 4 +- Source/Scene/GlobeSurfaceTile.js | 24 +- Source/Scene/GoogleEarthImageryProvider.js | 4 +- Source/Scene/ImageryProvider.js | 14 +- .../Instanced3DModel3DTileContentProvider.js | 9 +- Source/Scene/JobScheduler.js | 4 +- Source/Scene/Model.js | 10 +- Source/Scene/Points3DTileContentProvider.js | 9 +- Source/Scene/TileMapServiceImageryProvider.js | 4 +- Source/Scene/TileTerrain.js | 6 +- Source/Scene/UrlTemplateImageryProvider.js | 20 +- Source/Widgets/Geocoder/GeocoderViewModel.js | 4 +- .../ArcGisImageServerTerrainProviderSpec.js | 10 +- Specs/Core/CesiumTerrainProviderSpec.js | 32 +- Specs/Core/RequestSchedulerSpec.js | 295 ++++++++++++++++-- Specs/Core/VRTheWorldTerrainProviderSpec.js | 10 +- Specs/spec-main.js | 1 + 35 files changed, 721 insertions(+), 215 deletions(-) create mode 100644 Source/Core/Request.js diff --git a/Source/Core/ArcGisImageServerTerrainProvider.js b/Source/Core/ArcGisImageServerTerrainProvider.js index 31e8420de621..6b755200a1de 100644 --- a/Source/Core/ArcGisImageServerTerrainProvider.js +++ b/Source/Core/ArcGisImageServerTerrainProvider.js @@ -13,6 +13,7 @@ define([ './HeightmapTerrainData', './loadImage', './Math', + './Request', './RequestScheduler', './RequestType', './TerrainProvider' @@ -30,6 +31,7 @@ define([ HeightmapTerrainData, loadImage, CesiumMath, + Request, RequestScheduler, RequestType, TerrainProvider) { @@ -202,15 +204,13 @@ 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. + * @param {Request} [request] The request object. + * * @returns {Promise.|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, throttleRequests, distance) { + ArcGisImageServerTerrainProvider.prototype.requestTileGeometry = function(x, y, level, request) { var rectangle = this._tilingScheme.tileXYToRectangle(x, y, level); // Each pixel in the heightmap represents the height at the center of that @@ -237,16 +237,19 @@ define([ url = proxy.getURL(url); } - var promise; + if (!defined(request)) { + // If a request object isn't provided, perform an immediate request + request = new Request(); + request.defer = true; + } + + request.url = url; + request.requestFunction = loadImage; + request.requestType = RequestType.TERRAIN; - throttleRequests = defaultValue(throttleRequests, true); - if (throttleRequests) { - promise = RequestScheduler.throttleRequest(url, loadImage, RequestType.TERRAIN, distance); - if (!defined(promise)) { - return undefined; - } - } else { - promise = loadImage(url); + var promise = RequestScheduler.throttleRequest(request); + if (!defined(promise)) { + return undefined; } var that = this; diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 8af237bbbe02..94c28304ae25 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -20,6 +20,7 @@ define([ './Matrix3', './OrientedBoundingBox', './QuantizedMeshTerrainData', + './Request', './RequestScheduler', './RequestType', './RuntimeError', @@ -46,6 +47,7 @@ define([ Matrix3, OrientedBoundingBox, QuantizedMeshTerrainData, + Request, RequestScheduler, RequestType, RuntimeError, @@ -248,7 +250,7 @@ define([ } function requestMetadata() { - var metadata = loadJson(metadataUrl); + var metadata = RequestScheduler.request(metadataUrl, loadJson); when(metadata, metadataSuccess, metadataFailure); } @@ -476,10 +478,7 @@ 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. + * @param {Request} [request] The request object. * * @returns {Promise.|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 @@ -488,7 +487,7 @@ define([ * @exception {DeveloperError} This function must not be called before {@link CesiumTerrainProvider#ready} * returns true. */ - CesiumTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests, distance) { + CesiumTerrainProvider.prototype.requestTileGeometry = function(x, y, level, request) { //>>includeStart('debug', pragmas.debug) if (!this._ready) { throw new DeveloperError('requestTileGeometry must not be called before the terrain provider is ready.'); @@ -511,8 +510,6 @@ define([ url = proxy.getURL(url); } - var promise; - var extensionList = []; if (this._requestVertexNormals && this._hasVertexNormals) { extensionList.push(this._littleEndianExtensionSize ? "octvertexnormals" : "vertexnormals"); @@ -524,14 +521,20 @@ define([ function tileLoader(tileUrl) { return loadArrayBuffer(tileUrl, getRequestHeader(extensionList)); } - throttleRequests = defaultValue(throttleRequests, true); - if (throttleRequests) { - promise = RequestScheduler.throttleRequest(url, tileLoader, RequestType.TERRAIN, distance); - if (!defined(promise)) { - return undefined; - } - } else { - promise = tileLoader(url); + + if (!defined(request)) { + // If a request object isn't provided, perform an immediate request + request = new Request(); + request.defer = true; + } + + request.url = url; + request.requestFunction = tileLoader; + request.requestType = RequestType.TERRAIN; + + var promise = RequestScheduler.throttleRequest(request); + if (!defined(promise)) { + return undefined; } var that = this; diff --git a/Source/Core/EarthOrientationParameters.js b/Source/Core/EarthOrientationParameters.js index a9cde1d009f6..f56840c79af5 100644 --- a/Source/Core/EarthOrientationParameters.js +++ b/Source/Core/EarthOrientationParameters.js @@ -9,6 +9,7 @@ define([ './JulianDate', './LeapSecond', './loadJson', + './RequestScheduler', './RuntimeError', './TimeConstants', './TimeStandard' @@ -22,6 +23,7 @@ define([ JulianDate, LeapSecond, loadJson, + RequestScheduler, RuntimeError, TimeConstants, TimeStandard) { @@ -96,7 +98,7 @@ define([ } else if (defined(options.url)) { // Download EOP data. var that = this; - this._downloadPromise = when(loadJson(options.url), function(eopData) { + this._downloadPromise = when(RequestScheduler.request(options.url, loadJson), function(eopData) { onDataReady(that, eopData); }, function() { that._dataError = 'An error occurred while retrieving the EOP data from the URL ' + options.url + '.'; diff --git a/Source/Core/EllipsoidTerrainProvider.js b/Source/Core/EllipsoidTerrainProvider.js index 6f35f1c4c78d..a695677ff34e 100644 --- a/Source/Core/EllipsoidTerrainProvider.js +++ b/Source/Core/EllipsoidTerrainProvider.js @@ -160,16 +160,13 @@ 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. + * @param {Request} [request] The request object. * * @returns {Promise.|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. */ - EllipsoidTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests, distance) { + EllipsoidTerrainProvider.prototype.requestTileGeometry = function(x, y, level, request) { return this._terrainData; }; diff --git a/Source/Core/Iau2006XysData.js b/Source/Core/Iau2006XysData.js index 71e973bcab61..92626feb8b6d 100644 --- a/Source/Core/Iau2006XysData.js +++ b/Source/Core/Iau2006XysData.js @@ -7,6 +7,7 @@ define([ './Iau2006XysSample', './JulianDate', './loadJson', + './RequestScheduler', './TimeStandard' ], function( when, @@ -16,6 +17,7 @@ define([ Iau2006XysSample, JulianDate, loadJson, + RequestScheduler, TimeStandard) { "use strict"; @@ -244,7 +246,7 @@ define([ chunkUrl = buildModuleUrl('Assets/IAU2006_XYS/IAU2006_XYS_' + chunkIndex + '.json'); } - when(loadJson(chunkUrl), function(chunk) { + when(RequestScheduler.request(chunkUrl, loadJson), function(chunk) { xysData._chunkDownloadsInProgress[chunkIndex] = false; var samples = xysData._samples; diff --git a/Source/Core/Request.js b/Source/Core/Request.js new file mode 100644 index 000000000000..7a8b35d6b5c7 --- /dev/null +++ b/Source/Core/Request.js @@ -0,0 +1,64 @@ +/*global define*/ +define([ + 'Core/defaultValue' + ], function( + defaultValue) { + "use strict"; + + /** + * Stores information for making a request using RequestScheduler + * + * @exports Request + * + * @private + */ + function Request(options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + /** + * The URL to request. + */ + this.url = options.url; + + /** + * Extra parameters to send with the request. For example, HTTP headers or jsonp parameters. + */ + this.parameters = options.parameters; + + /** + * The actual function that makes the request. + */ + this.requestFunction = options.requestFunction; + + /** + * Type of request. Used for more fine-grained priority sorting. + */ + this.requestType = options.requestType; + + /** + * Specifies that the request should be deferred until an open slot is available. + * A deferred request will always return a promise, which is suitable for data + * sources and utility functions. + */ + this.defer = defaultValue(options.defer, false); + + /** + * The distance from the camera, used to prioritize requests. + */ + this.distance = defaultValue(options.distance, 0.0); + + // Helper members for RequestScheduler + + /** + * A promise for when a deferred request can start. + */ + this.startPromise = undefined; + + /** + * The server to request from. + */ + this.server = undefined; + } + + return Request; +}); diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js index 0524d2a18567..b8e8e6e810a8 100644 --- a/Source/Core/RequestScheduler.js +++ b/Source/Core/RequestScheduler.js @@ -5,37 +5,47 @@ define([ './defaultValue', './defined', './defineProperties', - './DeveloperError' + './DeveloperError', + './Queue', + './Request' ], function( Uri, when, defaultValue, defined, defineProperties, - DeveloperError) { + DeveloperError, + Queue, + Request) { "use strict"; - var RequestTypeBudget = function(type) { + function RequestTypeBudget(request) { /** - * Total requests allowed this frame + * Total requests allowed this frame. */ this.total = 0; /** - * Total requests used this frame + * Total requests used this frame. */ this.used = 0; /** - * Type of request. Used for more fine-grained priority sorting + * Server of the request. */ - this.type = type; - }; + this.server = request.server; + + /** + * Type of request. Used for more fine-grained priority sorting. + */ + this.requestType = request.requestType; + } var activeRequestsByServer = {}; var activeRequests = 0; - var budgets = {}; + var budgets = []; var leftoverRequests = []; + var deferredRequests = new Queue(); var stats = { numberOfRequestsThisFrame : 0 @@ -55,50 +65,41 @@ define([ function RequestScheduler() { } - function handleLeftoverRequest(url, type, distance) { - // TODO : don't create a new object, reuse instead - leftoverRequests.push({ - url : url, - type : type, - distance : defaultValue(distance, 0.0) - }); + function distanceSortFunction(a, b) { + return a.distance - b.distance; } - 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 getBudget(request) { + var budget; + var length = budgets.length; + for (var i = 0; i < length; ++i) { + budget = budgets[i]; + if ((budget.server === request.server) && (budget.requestType === request.requestType)) { + return budget; + } } - } - - function distanceSortFunction(a, b) { - return a.distance - b.distance; + // Not found, create a new budget + budget = new RequestTypeBudget(request); + budgets.push(budget); + return budget; } 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. + showStats(); + clearStats(); if (!RequestScheduler.prioritize) { return; } - printStats(); - stats.numberOfRequestsThisFrame = 0; - // Reset budget totals - for (var name in budgets) { - if (budgets.hasOwnProperty(name)) { - budgets[name].total = 0; - budgets[name].used = 0; - } + var length = budgets.length; + for (var i = 0; i < length; ++i) { + budgets[i].total = 0; + budgets[i].used = 0; } - // Sort all requests made this frame by distance + // Sort all leftover requests by distance var requests = leftoverRequests; requests.sort(distanceSortFunction); @@ -107,8 +108,7 @@ define([ 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 budget = getBudget(request); var budgetAvailable = RequestScheduler.getNumberOfAvailableRequestsByServer(request.url); if (budget.total < budgetAvailable) { ++budget.total; @@ -188,16 +188,49 @@ define([ return true; }; + function requestComplete(request) { + --activeRequests; + --activeRequestsByServer[request.server]; + + // Start a deferred request immediately now that a slot is open + var deferredRequest = deferredRequests.dequeue(); + if (defined(deferredRequest)) { + deferredRequest.startPromise.resolve(deferredRequest); + } + } + + function startRequest(request) { + ++activeRequests; + ++activeRequestsByServer[request.server]; + + return when(request.requestFunction(request.url, request.parameters), function(result) { + requestComplete(request); + return result; + }).otherwise(function(error) { + requestComplete(request); + return when.reject(error); + }); + } + + function deferRequest(request) { + deferredRequests.enqueue(request); + var deferred = when.defer(); + request.startPromise = deferred; + return deferred.promise.then(startRequest); + } + + function handleLeftoverRequest(request) { + if (RequestScheduler.prioritize) { + leftoverRequests.push(request); + } + } + /** * A function that will make a request if there are available slots to the server. * Returns undefined immediately if the request would exceed the maximum, allowing * the caller to retry later instead of queueing indefinitely under the browser's control. * - * @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. + * @param {Request} request The request object. * * @returns {Promise.|undefined} Either undefined, meaning the request would exceed the maximum number of * parallel requests, or a Promise for the requested data. @@ -209,7 +242,11 @@ define([ * // in this simple example, loadImage could be used directly as requestFunction. * return Cesium.loadImage(url); * }; - * var promise = Cesium.RequestScheduler.throttleRequest(url, requestFunction); + * var request = new Request({ + * url : url, + * requestFunction : requestFunction + * }); + * var promise = Cesium.RequestScheduler.throttleRequest(request); * if (!Cesium.defined(promise)) { * // too many active requests in progress, try again later. * } else { @@ -219,58 +256,93 @@ define([ * } * */ - RequestScheduler.throttleRequest = function(url, requestFunction, requestType, distance) { + RequestScheduler.throttleRequest = function(request) { //>>includeStart('debug', pragmas.debug); - if (!defined(url)) { - throw new DeveloperError('url is required.'); + if (!defined(request)) { + throw new DeveloperError('request is required.'); } - - if (!defined(requestFunction)) { - throw new DeveloperError('requestFunction is required.'); + if (!defined(request.url)) { + throw new DeveloperError('request.url is required.'); + } + if (!defined(request.requestFunction)) { + throw new DeveloperError('request.requestFunction is required.'); } //>>includeEnd('debug'); ++stats.numberOfRequestsThisFrame; - if (activeRequests >= RequestScheduler.maximumRequests) { - handleLeftoverRequest(url, requestType, distance); - return undefined; + if (!RequestScheduler.throttle) { + return request.requestFunction(request.url, request.parameters); } - var server = RequestScheduler.getServer(url); - var activeRequestsForServer = defaultValue(activeRequestsByServer[server], 0); - if (activeRequestsForServer >= RequestScheduler.maximumRequestsPerServer) { - handleLeftoverRequest(url, requestType, distance); - return undefined; - } + var server = RequestScheduler.getServer(request.url); + request.server = server; + activeRequestsByServer[server] = defaultValue(activeRequestsByServer[server], 0); - if (RequestScheduler.prioritize && defined(requestType)) { - var budget = budgets[server]; - if (!defined(budget)) { - budget = new RequestTypeBudget(requestType); - budgets[server] = budget; + if (!RequestScheduler.hasAvailableRequests(request.url)) { + if (!request.defer) { + // No available slots to make the request, return undefined + handleLeftoverRequest(request); + return undefined; + } else { + // If no slots are available, the request is deferred until a slot opens up. + // Return a promise even if the request can't be completed immediately. + return deferRequest(request); } + } + + if (RequestScheduler.prioritize && defined(request.requestType) && !request.defer) { + var budget = getBudget(request); if (budget.used >= budget.total) { - handleLeftoverRequest(url, requestType, distance); + // Request does not fit in the budget, return undefined + handleLeftoverRequest(request); return undefined; } ++budget.used; } - ++activeRequests; - activeRequestsByServer[server] = activeRequestsForServer + 1; + return startRequest(request); + }; - return when(requestFunction(url), function(result) { - --activeRequests; - --activeRequestsByServer[server]; - return result; - }).otherwise(function(error) { - --activeRequests; - --activeRequestsByServer[server]; - return when.reject(error); - }); + /** + * A function that will make a request when an open slot is available. Always returns + * a promise, which is suitable for data sources and utility functions. + * + * @param {String} url The URL to request. + * @param {RequestScheduler~RequestFunction} requestFunction The actual function that + * makes the request. + * @param {Object} [parameters] Extra parameters to send with the request. + * + * @returns {Promise.} A Promise for the requested data. + */ + RequestScheduler.request = function(url, requestFunction, parameters) { + return RequestScheduler.throttleRequest(new Request({ + url : url, + parameters : parameters, + requestFunction : requestFunction, + defer : true + })); }; + function clearStats() { + stats.numberOfRequestsThisFrame = 0; + } + + function showStats() { + if (!RequestScheduler.debugShowStatistics) { + return; + } + + 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); + } + } + /** * Specifies the maximum number of requests that can be simultaneously open to a single server. If this value is higher than * the number of requests per server actually allowed by the web browser, Cesium's ability to prioritize requests will be adversely @@ -296,5 +368,19 @@ define([ */ RequestScheduler.prioritize = true; + /** + * Specifies if the request scheduler should throttle incoming requests, or let the browser queue requests under its control. + * @type {Boolean} + * @default true + */ + RequestScheduler.throttle = true; + + /** + * When true, log statistics to the console every frame + * @type {Boolean} + * @default true + */ + RequestScheduler.debugShowStatistics = false; + return RequestScheduler; }); diff --git a/Source/Core/TerrainProvider.js b/Source/Core/TerrainProvider.js index 97e486bcf00c..95b3a1a795e1 100644 --- a/Source/Core/TerrainProvider.js +++ b/Source/Core/TerrainProvider.js @@ -186,10 +186,7 @@ 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. + * @param {Request} [request] The request object. * * @returns {Promise.|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 diff --git a/Source/Core/VRTheWorldTerrainProvider.js b/Source/Core/VRTheWorldTerrainProvider.js index 119e89c77f40..645154093e30 100644 --- a/Source/Core/VRTheWorldTerrainProvider.js +++ b/Source/Core/VRTheWorldTerrainProvider.js @@ -15,6 +15,7 @@ define([ './loadXML', './Math', './Rectangle', + './Request', './RequestScheduler', './RequestType', './TerrainProvider', @@ -35,6 +36,7 @@ define([ loadXML, CesiumMath, Rectangle, + Request, RequestScheduler, RequestType, TerrainProvider, @@ -146,7 +148,7 @@ define([ } function requestMetadata() { - when(loadXML(that._url), metadataSuccess, metadataFailure); + when(RequestScheduler.request(that._url, loadXML), metadataSuccess, metadataFailure); } requestMetadata(); @@ -252,15 +254,12 @@ 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. + * @param {Request} [request] The request object. * @returns {Promise.|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. */ - VRTheWorldTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests, distance) { + VRTheWorldTerrainProvider.prototype.requestTileGeometry = function(x, y, level, request) { if (!this.ready) { throw new DeveloperError('requestTileGeometry must not be called before ready returns true.'); } @@ -273,16 +272,19 @@ define([ url = proxy.getURL(url); } - var promise; + if (!defined(request)) { + // If a request object isn't provided, perform an immediate request + request = new Request(); + request.defer = true; + } - throttleRequests = defaultValue(throttleRequests, true); - if (throttleRequests) { - promise = RequestScheduler.throttleRequest(url, loadImage, RequestType.TERRAIN, distance); - if (!defined(promise)) { - return undefined; - } - } else { - promise = loadImage(url); + request.url = url; + request.requestFunction = loadImage; + request.requestType = RequestType.TERRAIN; + + var promise = RequestScheduler.throttleRequest(request); + if (!defined(promise)) { + return undefined; } var that = this; diff --git a/Source/Core/sampleTerrain.js b/Source/Core/sampleTerrain.js index a58bc0f460ae..e5c2691dd2e7 100644 --- a/Source/Core/sampleTerrain.js +++ b/Source/Core/sampleTerrain.js @@ -107,7 +107,7 @@ define([ var tilePromises = []; for (i = 0; i < tileRequests.length; ++i) { var tileRequest = tileRequests[i]; - var requestPromise = tileRequest.terrainProvider.requestTileGeometry(tileRequest.x, tileRequest.y, tileRequest.level, false); + var requestPromise = tileRequest.terrainProvider.requestTileGeometry(tileRequest.x, tileRequest.y, tileRequest.level); var tilePromise = when(requestPromise, createInterpolateFunction(tileRequest), createMarkFailedFunction(tileRequest)); tilePromises.push(tilePromise); } diff --git a/Source/DataSources/CzmlDataSource.js b/Source/DataSources/CzmlDataSource.js index 5641e5c62c3f..29d10321a3d0 100644 --- a/Source/DataSources/CzmlDataSource.js +++ b/Source/DataSources/CzmlDataSource.js @@ -26,6 +26,7 @@ define([ '../Core/Quaternion', '../Core/Rectangle', '../Core/ReferenceFrame', + '../Core/RequestScheduler', '../Core/RuntimeError', '../Core/Spherical', '../Core/TimeInterval', @@ -97,6 +98,7 @@ define([ Quaternion, Rectangle, ReferenceFrame, + RequestScheduler, RuntimeError, Spherical, TimeInterval, @@ -1515,7 +1517,7 @@ define([ var promise = czml; var sourceUri = options.sourceUri; if (typeof czml === 'string') { - promise = loadJson(czml); + promise = RequestScheduler.request(czml, loadJson); sourceUri = defaultValue(sourceUri, czml); } diff --git a/Source/DataSources/GeoJsonDataSource.js b/Source/DataSources/GeoJsonDataSource.js index 529fb00f6b51..89047850ace5 100644 --- a/Source/DataSources/GeoJsonDataSource.js +++ b/Source/DataSources/GeoJsonDataSource.js @@ -13,6 +13,7 @@ define([ '../Core/loadJson', '../Core/PinBuilder', '../Core/PolygonHierarchy', + '../Core/RequestScheduler', '../Core/RuntimeError', '../Scene/VerticalOrigin', '../ThirdParty/topojson', @@ -40,6 +41,7 @@ define([ loadJson, PinBuilder, PolygonHierarchy, + RequestScheduler, RuntimeError, VerticalOrigin, topojson, @@ -756,7 +758,7 @@ define([ if (!defined(sourceUri)) { sourceUri = data; } - promise = loadJson(data); + promise = RequestScheduler.request(data, loadJson); } options = { diff --git a/Source/DataSources/KmlDataSource.js b/Source/DataSources/KmlDataSource.js index fc5789563b0c..f124eb67eb15 100644 --- a/Source/DataSources/KmlDataSource.js +++ b/Source/DataSources/KmlDataSource.js @@ -24,6 +24,7 @@ define([ '../Core/PinBuilder', '../Core/PolygonHierarchy', '../Core/Rectangle', + '../Core/RequestScheduler', '../Core/RuntimeError', '../Core/TimeInterval', '../Core/TimeIntervalCollection', @@ -76,6 +77,7 @@ define([ PinBuilder, PolygonHierarchy, Rectangle, + RequestScheduler, RuntimeError, TimeInterval, TimeIntervalCollection, @@ -733,7 +735,7 @@ define([ //Asynchronously processes an external style file. function processExternalStyles(dataSource, uri, styleCollection) { - return when(loadXML(proxyUrl(uri, dataSource._proxy)), function(styleKml) { + return when(RequestScheduler.request(proxyUrl(uri, dataSource._proxy), loadXML), function(styleKml) { return processStyles(dataSource, styleKml, styleCollection, uri, true); }); } @@ -1796,7 +1798,7 @@ define([ var promise = data; if (typeof data === 'string') { - promise = loadBlob(proxyUrl(data, this._proxy)); + promise = RequestScheduler.request(proxyUrl(data, this._proxy), loadBlob); sourceUri = defaultValue(sourceUri, data); } diff --git a/Source/Scene/ArcGisMapServerImageryProvider.js b/Source/Scene/ArcGisMapServerImageryProvider.js index 3f38396c0b0e..886dbfea5969 100644 --- a/Source/Scene/ArcGisMapServerImageryProvider.js +++ b/Source/Scene/ArcGisMapServerImageryProvider.js @@ -15,6 +15,7 @@ define([ '../Core/loadJsonp', '../Core/Math', '../Core/Rectangle', + '../Core/RequestScheduler', '../Core/RuntimeError', '../Core/TileProviderError', '../Core/WebMercatorProjection', @@ -39,6 +40,7 @@ define([ loadJsonp, CesiumMath, Rectangle, + RequestScheduler, RuntimeError, TileProviderError, WebMercatorProjection, @@ -230,7 +232,7 @@ define([ parameters.token = that._token; } - var metadata = loadJsonp(that._url, { + var metadata = RequestScheduler.request(that._url, loadJsonp, { parameters : parameters, proxy : that._proxy }); diff --git a/Source/Scene/Batched3DModel3DTileContentProvider.js b/Source/Scene/Batched3DModel3DTileContentProvider.js index 664dc87de5a0..ca6d10d20f09 100644 --- a/Source/Scene/Batched3DModel3DTileContentProvider.js +++ b/Source/Scene/Batched3DModel3DTileContentProvider.js @@ -8,6 +8,7 @@ define([ '../Core/getMagic', '../Core/getStringFromTypedArray', '../Core/loadArrayBuffer', + '../Core/Request', '../Core/RequestScheduler', '../Core/RequestType', '../ThirdParty/when', @@ -24,6 +25,7 @@ define([ getMagic, getStringFromTypedArray, loadArrayBuffer, + Request, RequestScheduler, RequestType, when, @@ -125,7 +127,12 @@ define([ var that = this; var distance = this._tile.distanceToCamera; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, distance); + var promise = RequestScheduler.throttleRequest(new Request({ + url : this._url, + requestFunction : loadArrayBuffer, + requestType : RequestType.TILES3D, + distance : distance + })); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/BingMapsImageryProvider.js b/Source/Scene/BingMapsImageryProvider.js index f0b79088142d..0f8040839379 100644 --- a/Source/Scene/BingMapsImageryProvider.js +++ b/Source/Scene/BingMapsImageryProvider.js @@ -11,6 +11,7 @@ define([ '../Core/loadJsonp', '../Core/Math', '../Core/Rectangle', + '../Core/RequestScheduler', '../Core/RuntimeError', '../Core/TileProviderError', '../Core/WebMercatorTilingScheme', @@ -30,6 +31,7 @@ define([ loadJsonp, CesiumMath, Rectangle, + RequestScheduler, RuntimeError, TileProviderError, WebMercatorTilingScheme, @@ -211,7 +213,7 @@ define([ } function requestMetadata() { - var metadata = loadJsonp(metadataUrl, { + var metadata = RequestScheduler.request(metadataUrl, loadJsonp, { callbackParameterName : 'jsonp', proxy : that._proxy }); diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 67e6f52d8ef2..6cfd75b142a1 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -10,6 +10,7 @@ define([ '../Core/Intersect', '../Core/loadJson', '../Core/Math', + '../Core/Request', '../Core/RequestScheduler', '../Core/RequestType', '../ThirdParty/Uri', @@ -30,6 +31,7 @@ define([ Intersect, loadJson, CesiumMath, + Request, RequestScheduler, RequestType, Uri, @@ -230,7 +232,11 @@ define([ var tileset = this; // We don't know the distance of the tileset until tiles.json is loaded, so use the default distance for now - var promise = RequestScheduler.throttleRequest(tilesJson, loadJson, RequestType.TILES3D); + var promise = RequestScheduler.throttleRequest(new Request({ + url : tilesJson, + requestFunction : loadJson, + requestType : RequestType.TILES3D + })); if (!defined(promise)) { return undefined; diff --git a/Source/Scene/Composite3DTileContentProvider.js b/Source/Scene/Composite3DTileContentProvider.js index ea6d4c5665d8..930821b85bbd 100644 --- a/Source/Scene/Composite3DTileContentProvider.js +++ b/Source/Scene/Composite3DTileContentProvider.js @@ -6,6 +6,7 @@ define([ '../Core/DeveloperError', '../Core/getMagic', '../Core/loadArrayBuffer', + '../Core/Request', '../Core/RequestScheduler', '../Core/RequestType', '../ThirdParty/when', @@ -17,6 +18,7 @@ define([ DeveloperError, getMagic, loadArrayBuffer, + Request, RequestScheduler, RequestType, when, @@ -60,7 +62,12 @@ define([ var that = this; var distance = this._tile.distanceToCamera; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, distance); + var promise = RequestScheduler.throttleRequest(new Request({ + url : this._url, + requestFunction : loadArrayBuffer, + requestType : RequestType.TILES3D, + distance : distance + })); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/DiscardMissingTileImagePolicy.js b/Source/Scene/DiscardMissingTileImagePolicy.js index 662721acb832..ccd940ff3a70 100644 --- a/Source/Scene/DiscardMissingTileImagePolicy.js +++ b/Source/Scene/DiscardMissingTileImagePolicy.js @@ -5,6 +5,7 @@ define([ '../Core/DeveloperError', '../Core/getImagePixels', '../Core/loadImageViaBlob', + '../Core/RequestScheduler', '../ThirdParty/when' ], function( defaultValue, @@ -12,6 +13,7 @@ define([ DeveloperError, getImagePixels, loadImageViaBlob, + RequestScheduler, when) { "use strict"; @@ -86,7 +88,7 @@ define([ that._isReady = true; } - when(loadImageViaBlob(options.missingImageUrl), success, failure); + when(RequestScheduler.request(options.missingImageUrl, loadImageViaBlob), success, failure); } /** diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js index e4bfb4d21b6c..941dbdbf9a0e 100644 --- a/Source/Scene/GlobeSurfaceTile.js +++ b/Source/Scene/GlobeSurfaceTile.js @@ -241,16 +241,28 @@ define([ } }; + function createTileBoundingBox(tile, frameState) { + var minimumHeight; + var maximumHeight; + if (defined(tile.parent) && defined(tile.parent.data)) { + minimumHeight = tile.parent.data.minimumHeight; + maximumHeight = tile.parent.data.maximumHeight; + } + return new TileBoundingBox({ + rectangle : tile.rectangle, + ellipsoid : tile.tilingScheme.ellipsoid, + minimumHeight : minimumHeight, + maximumHeight : maximumHeight + }); + } + GlobeSurfaceTile.processStateMachine = function(tile, frameState, terrainProvider, imageryLayerCollection) { var surfaceTile = tile.data; if (!defined(surfaceTile)) { surfaceTile = tile.data = new GlobeSurfaceTile(); - surfaceTile.tileBoundingBox = new TileBoundingBox({ - rectangle : tile.rectangle, - ellipsoid : tile.tilingScheme.ellipsoid - }); - // Set the distance of the tile now in order to prioritize the request later. - // Rough estimate, since the tile height isn't known until the terrain is loaded. + // Create the TileBoundingBox now in order to estimate the distance, which is used to prioritize the request. + // Since the terrain isn't loaded yet, estimate the heights using its parent's values. + surfaceTile.tileBoundingBox = createTileBoundingBox(tile, frameState); tile._distance = surfaceTile.tileBoundingBox.distanceToCamera(frameState); } diff --git a/Source/Scene/GoogleEarthImageryProvider.js b/Source/Scene/GoogleEarthImageryProvider.js index 4b54348c88ee..4406bfaa940f 100644 --- a/Source/Scene/GoogleEarthImageryProvider.js +++ b/Source/Scene/GoogleEarthImageryProvider.js @@ -9,6 +9,7 @@ define([ '../Core/GeographicTilingScheme', '../Core/loadText', '../Core/Rectangle', + '../Core/RequestScheduler', '../Core/RuntimeError', '../Core/TileProviderError', '../Core/WebMercatorTilingScheme', @@ -24,6 +25,7 @@ define([ GeographicTilingScheme, loadText, Rectangle, + RequestScheduler, RuntimeError, TileProviderError, WebMercatorTilingScheme, @@ -219,7 +221,7 @@ define([ function requestMetadata() { var url = (!defined(that._proxy)) ? metadataUrl : that._proxy.getURL(metadataUrl); - var metadata = loadText(url); + var metadata = RequestScheduler.request(url, loadText); when(metadata, metadataSuccess, metadataFailure); } diff --git a/Source/Scene/ImageryProvider.js b/Source/Scene/ImageryProvider.js index 114a903ec127..e29438676d1f 100644 --- a/Source/Scene/ImageryProvider.js +++ b/Source/Scene/ImageryProvider.js @@ -5,6 +5,7 @@ define([ '../Core/DeveloperError', '../Core/loadImage', '../Core/loadImageViaBlob', + '../Core/Request', '../Core/RequestScheduler', '../Core/RequestType' ], function( @@ -13,6 +14,7 @@ define([ DeveloperError, loadImage, loadImageViaBlob, + Request, RequestScheduler, RequestType) { "use strict"; @@ -311,10 +313,14 @@ define([ * Image or a Canvas DOM object. */ ImageryProvider.loadImage = function(imageryProvider, url, distance) { - if (defined(imageryProvider.tileDiscardPolicy)) { - return RequestScheduler.throttleRequest(url, loadImageViaBlob, RequestType.IMAGERY, distance); - } - return RequestScheduler.throttleRequest(url, loadImage, RequestType.IMAGERY, distance); + var requestFunction = defined(imageryProvider.tileDiscardPolicy) ? loadImageViaBlob : loadImage; + + return RequestScheduler.throttleRequest(new Request({ + url : url, + requestFunction : requestFunction, + requestType : RequestType.IMAGERY, + distance : distance + })); }; return ImageryProvider; diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index e8112eb1e248..25f3b02b378c 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -11,6 +11,7 @@ define([ '../Core/getStringFromTypedArray', '../Core/loadArrayBuffer', '../Core/Matrix4', + '../Core/Request', '../Core/RequestScheduler', '../Core/RequestType', '../Core/Transforms', @@ -32,6 +33,7 @@ define([ getStringFromTypedArray, loadArrayBuffer, Matrix4, + Request, RequestScheduler, RequestType, Transforms, @@ -134,7 +136,12 @@ define([ var that = this; var distance = this._tile.distanceToCamera; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, distance); + var promise = RequestScheduler.throttleRequest(new Request({ + url : this._url, + requestFunction : loadArrayBuffer, + requestType : RequestType.TILES3D, + distance : distance + })); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/JobScheduler.js b/Source/Scene/JobScheduler.js index 51d013ba1930..667dcadb9d3e 100644 --- a/Source/Scene/JobScheduler.js +++ b/Source/Scene/JobScheduler.js @@ -69,7 +69,7 @@ define([ * * @private */ - var JobScheduler = function(budgets) { + function JobScheduler(budgets) { if (defined(budgets) && (budgets.length !== JobType.NUMBER_OF_JOB_TYPES)) { throw new DeveloperError('A budget must be specified for each job type; budgets.length should equal JobType.NUMBER_OF_JOB_TYPES.'); } @@ -98,7 +98,7 @@ define([ this._totalUsedThisFrame = 0.0; this._budgets = jobBudgets; this._executedThisFrame = executedThisFrame; - }; + } // For unit testing JobScheduler.getTimestamp = getTimestamp; diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 603d7e9fe075..9f2042f4704d 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -29,6 +29,8 @@ define([ '../Core/PrimitiveType', '../Core/Quaternion', '../Core/Queue', + '../Core/Request', + '../Core/RequestScheduler', '../Core/RuntimeError', '../Core/TaskProcessor', '../Renderer/Buffer', @@ -87,6 +89,8 @@ define([ PrimitiveType, Quaternion, Queue, + Request, + RequestScheduler, RuntimeError, TaskProcessor, Buffer, @@ -1010,7 +1014,7 @@ define([ setCachedGltf(model, cachedGltf); gltfCache[cacheKey] = cachedGltf; - loadArrayBuffer(url, options.headers).then(function(arrayBuffer) { + RequestScheduler.request(url, loadArrayBuffer, options.headers).then(function(arrayBuffer) { var array = new Uint8Array(arrayBuffer); if (containsGltfMagic(array)) { // Load binary glTF @@ -1198,7 +1202,7 @@ define([ ++model._loadResources.pendingBufferLoads; var uri = new Uri(buffer.uri); var bufferPath = uri.resolve(model._baseUri).toString(); - loadArrayBuffer(bufferPath).then(bufferLoad(model, id)).otherwise(getFailedLoadFunction(model, 'buffer', bufferPath)); + RequestScheduler.request(bufferPath, loadArrayBuffer).then(bufferLoad(model, id)).otherwise(getFailedLoadFunction(model, 'buffer', bufferPath)); } } } @@ -1302,7 +1306,7 @@ define([ ++model._loadResources.pendingShaderLoads; var uri = new Uri(shader.uri); var shaderPath = uri.resolve(model._baseUri).toString(); - loadText(shaderPath).then(shaderLoad(model, shader.type, id)).otherwise(getFailedLoadFunction(model, 'shader', shaderPath)); + RequestScheduler.request(shaderPath, loadText).then(shaderLoad(model, shader.type, id)).otherwise(getFailedLoadFunction(model, 'shader', shaderPath)); } } } diff --git a/Source/Scene/Points3DTileContentProvider.js b/Source/Scene/Points3DTileContentProvider.js index 79f44b35024f..0ffda228bc61 100644 --- a/Source/Scene/Points3DTileContentProvider.js +++ b/Source/Scene/Points3DTileContentProvider.js @@ -11,6 +11,7 @@ define([ '../Core/getMagic', '../Core/loadArrayBuffer', '../Core/PointGeometry', + '../Core/Request', '../Core/RequestScheduler', '../Core/RequestType', '../ThirdParty/when', @@ -29,6 +30,7 @@ define([ getMagic, loadArrayBuffer, PointGeometry, + Request, RequestScheduler, RequestType, when, @@ -78,7 +80,12 @@ define([ var that = this; var distance = this._tile.distanceToCamera; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer, RequestType.TILES3D, distance); + var promise = RequestScheduler.throttleRequest(new Request({ + url : this._url, + requestFunction : loadArrayBuffer, + requestType : RequestType.TILES3D, + distance : distance + })); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/TileMapServiceImageryProvider.js b/Source/Scene/TileMapServiceImageryProvider.js index 1049868ac1e0..4bd50a2fe88b 100644 --- a/Source/Scene/TileMapServiceImageryProvider.js +++ b/Source/Scene/TileMapServiceImageryProvider.js @@ -12,6 +12,7 @@ define([ '../Core/joinUrls', '../Core/loadXML', '../Core/Rectangle', + '../Core/RequestScheduler', '../Core/RuntimeError', '../Core/TileProviderError', '../Core/WebMercatorTilingScheme', @@ -30,6 +31,7 @@ define([ joinUrls, loadXML, Rectangle, + RequestScheduler, RuntimeError, TileProviderError, WebMercatorTilingScheme, @@ -269,7 +271,7 @@ define([ resourceUrl = proxy.getURL(resourceUrl); } // Try to load remaining parameters from XML - when(loadXML(resourceUrl), metadataSuccess, metadataFailure); + when(RequestScheduler.request(resourceUrl, loadXML), metadataSuccess, metadataFailure); } requestMetadata(); diff --git a/Source/Scene/TileTerrain.js b/Source/Scene/TileTerrain.js index 25a2661254bd..1f49a34a8f08 100644 --- a/Source/Scene/TileTerrain.js +++ b/Source/Scene/TileTerrain.js @@ -7,6 +7,7 @@ define([ '../Core/DeveloperError', '../Core/IndexDatatype', '../Core/OrientedBoundingBox', + '../Core/Request', '../Core/TileProviderError', '../Renderer/Buffer', '../Renderer/BufferUsage', @@ -22,6 +23,7 @@ define([ DeveloperError, IndexDatatype, OrientedBoundingBox, + Request, TileProviderError, Buffer, BufferUsage, @@ -135,7 +137,9 @@ define([ function doRequest() { // Request the terrain from the terrain provider. - tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level, true, distance); + var request = new Request(); + request.distance = distance; + tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level, request); // If the request method returns undefined (instead of a promise), the request // has been deferred. diff --git a/Source/Scene/UrlTemplateImageryProvider.js b/Source/Scene/UrlTemplateImageryProvider.js index c4710c60dd11..72436252f10e 100644 --- a/Source/Scene/UrlTemplateImageryProvider.js +++ b/Source/Scene/UrlTemplateImageryProvider.js @@ -18,6 +18,7 @@ define([ '../Core/loadXML', '../Core/Math', '../Core/Rectangle', + '../Core/RequestScheduler', '../Core/TileProviderError', '../Core/WebMercatorTilingScheme', '../ThirdParty/when', @@ -41,6 +42,7 @@ define([ loadXML, CesiumMath, Rectangle, + RequestScheduler, TileProviderError, WebMercatorTilingScheme, when, @@ -523,18 +525,22 @@ define([ ++formatIndex; - if (format.type === 'json') { - return loadJson(url).then(format.callback).otherwise(doRequest); - } else if (format.type === 'xml') { - return loadXML(url).then(format.callback).otherwise(doRequest); - } else if (format.type === 'text' || format.type === 'html') { - return loadText(url).then(format.callback).otherwise(doRequest); - } else { + function doXhrRequest(url) { return loadWithXhr({ url: url, responseType: format.format }).then(handleResponse.bind(undefined, format)).otherwise(doRequest); } + + if (format.type === 'json') { + return RequestScheduler.request(url, loadJson).then(format.callback).otherwise(doRequest); + } else if (format.type === 'xml') { + return RequestScheduler.request(url, loadXML).then(format.callback).otherwise(doRequest); + } else if (format.type === 'text' || format.type === 'html') { + return RequestScheduler.request(url, loadText).then(format.callback).otherwise(doRequest); + } else { + return RequestScheduler.request(url, doXhrRequest); + } } return doRequest(); diff --git a/Source/Widgets/Geocoder/GeocoderViewModel.js b/Source/Widgets/Geocoder/GeocoderViewModel.js index 5524633442d2..25b471ba0b99 100644 --- a/Source/Widgets/Geocoder/GeocoderViewModel.js +++ b/Source/Widgets/Geocoder/GeocoderViewModel.js @@ -10,6 +10,7 @@ define([ '../../Core/loadJsonp', '../../Core/Matrix4', '../../Core/Rectangle', + '../../Core/RequestScheduler', '../../Scene/SceneMode', '../../ThirdParty/knockout', '../../ThirdParty/when', @@ -25,6 +26,7 @@ define([ loadJsonp, Matrix4, Rectangle, + RequestScheduler, SceneMode, knockout, when, @@ -238,7 +240,7 @@ define([ } viewModel._isSearchInProgress = true; - var promise = loadJsonp(viewModel._url + 'REST/v1/Locations', { + var promise = RequestScheduler.request(viewModel._url + 'REST/v1/Locations', loadJsonp, { parameters : { query : query, key : viewModel._key diff --git a/Specs/Core/ArcGisImageServerTerrainProviderSpec.js b/Specs/Core/ArcGisImageServerTerrainProviderSpec.js index aca3a818a051..e14ae11b7aaf 100644 --- a/Specs/Core/ArcGisImageServerTerrainProviderSpec.js +++ b/Specs/Core/ArcGisImageServerTerrainProviderSpec.js @@ -8,6 +8,7 @@ defineSuite([ 'Core/loadImage', 'Core/Math', 'Core/queryToObject', + 'Core/Request', 'Core/TerrainProvider', 'ThirdParty/Uri', 'ThirdParty/when' @@ -20,6 +21,7 @@ defineSuite([ loadImage, CesiumMath, queryToObject, + Request, TerrainProvider, Uri, when) { @@ -238,15 +240,17 @@ defineSuite([ url : baseUrl }); - var promise = terrainProvider.requestTileGeometry(0, 0, 0); + var request = new Request(); + + var promise = terrainProvider.requestTileGeometry(0, 0, 0, request); expect(promise).toBeDefined(); var i; for (i = 0; i < 10; ++i) { - promise = terrainProvider.requestTileGeometry(0, 0, 0); + promise = terrainProvider.requestTileGeometry(0, 0, 0, request); } - promise = terrainProvider.requestTileGeometry(0, 0, 0); + promise = terrainProvider.requestTileGeometry(0, 0, 0, request); expect(promise).toBeUndefined(); for (i = 0; i < deferreds.length; ++i) { diff --git a/Specs/Core/CesiumTerrainProviderSpec.js b/Specs/Core/CesiumTerrainProviderSpec.js index 9a7478efc851..294e837b6794 100644 --- a/Specs/Core/CesiumTerrainProviderSpec.js +++ b/Specs/Core/CesiumTerrainProviderSpec.js @@ -9,6 +9,7 @@ defineSuite([ 'Core/loadWithXhr', 'Core/Math', 'Core/QuantizedMeshTerrainData', + 'Core/Request', 'Core/RequestScheduler', 'Core/TerrainProvider', 'Specs/pollToPromise', @@ -24,6 +25,7 @@ defineSuite([ loadWithXhr, CesiumMath, QuantizedMeshTerrainData, + Request, RequestScheduler, TerrainProvider, pollToPromise, @@ -362,6 +364,8 @@ defineSuite([ describe('requestTileGeometry', function() { it('uses multiple urls specified in layer.json', function() { + RequestScheduler.throttle = false; + returnTileJson('Data/CesiumTerrainTileJson/MultipleUrls.tile.json'); var provider = new CesiumTerrainProvider({ @@ -372,18 +376,21 @@ defineSuite([ return provider.ready; }).then(function() { spyOn(loadWithXhr, 'load'); - provider.requestTileGeometry(0, 0, 0, false); + provider.requestTileGeometry(0, 0, 0); expect(loadWithXhr.load.calls.mostRecent().args[0]).toContain('foo0.com'); - provider.requestTileGeometry(1, 0, 0, false); + provider.requestTileGeometry(1, 0, 0); expect(loadWithXhr.load.calls.mostRecent().args[0]).toContain('foo1.com'); - provider.requestTileGeometry(1, -1, 0, false); + provider.requestTileGeometry(1, -1, 0); expect(loadWithXhr.load.calls.mostRecent().args[0]).toContain('foo2.com'); - provider.requestTileGeometry(1, 0, 1, false); + provider.requestTileGeometry(1, 0, 1); expect(loadWithXhr.load.calls.mostRecent().args[0]).toContain('foo3.com'); + RequestScheduler.throttle = true; }); }); it('supports scheme-less template URLs in layer.json resolved with absolute URL', function() { + RequestScheduler.throttle = false; + returnTileJson('Data/CesiumTerrainTileJson/MultipleUrls.tile.json'); var baseUri = new Uri(document.location.href); @@ -398,14 +405,15 @@ defineSuite([ return provider.ready; }).then(function() { spyOn(loadWithXhr, 'load'); - provider.requestTileGeometry(0, 0, 0, false); + provider.requestTileGeometry(0, 0, 0); expect(loadWithXhr.load.calls.mostRecent().args[0]).toContain('foo0.com'); - provider.requestTileGeometry(1, 0, 0, false); + provider.requestTileGeometry(1, 0, 0); expect(loadWithXhr.load.calls.mostRecent().args[0]).toContain('foo1.com'); - provider.requestTileGeometry(1, -1, 0, false); + provider.requestTileGeometry(1, -1, 0); expect(loadWithXhr.load.calls.mostRecent().args[0]).toContain('foo2.com'); - provider.requestTileGeometry(1, 0, 1, false); + provider.requestTileGeometry(1, 0, 1); expect(loadWithXhr.load.calls.mostRecent().args[0]).toContain('foo3.com'); + RequestScheduler.throttle = true; }); }); @@ -578,18 +586,20 @@ defineSuite([ url : baseUrl }); + var request = new Request(); + return pollToPromise(function() { return terrainProvider.ready; }).then(function() { - var promise = terrainProvider.requestTileGeometry(0, 0, 0); + var promise = terrainProvider.requestTileGeometry(0, 0, 0, request); expect(promise).toBeDefined(); var i; for (i = 0; i < 10; ++i) { - promise = terrainProvider.requestTileGeometry(0, 0, 0); + promise = terrainProvider.requestTileGeometry(0, 0, 0, request); } - promise = terrainProvider.requestTileGeometry(0, 0, 0); + promise = terrainProvider.requestTileGeometry(0, 0, 0, request); expect(promise).toBeUndefined(); for (i = 0; i < deferreds.length; ++i) { diff --git a/Specs/Core/RequestSchedulerSpec.js b/Specs/Core/RequestSchedulerSpec.js index c464c1511b09..52c3228281a2 100644 --- a/Specs/Core/RequestSchedulerSpec.js +++ b/Specs/Core/RequestSchedulerSpec.js @@ -1,9 +1,13 @@ /*global defineSuite*/ defineSuite([ 'Core/RequestScheduler', + 'Core/Request', + 'Core/RequestType', 'ThirdParty/when' ], function( RequestScheduler, + Request, + RequestType, when) { "use strict"; @@ -20,15 +24,27 @@ defineSuite([ RequestScheduler.maximumRequestsPerServer = originalMaximumRequestsPerServer; }); - it('throttleRequest throws when url is undefined', function() { + it('throttleRequest throws when request is undefined', function() { expect(function() { RequestScheduler.throttleRequest(); }).toThrowDeveloperError(); }); - it('throttleRequest throws when requestFunction is undefined', function() { + it('throttleRequest throws when request.url is undefined', function() { expect(function() { - RequestScheduler.throttleRequest('http://foo.com/1'); + RequestScheduler.throttleRequest(new Request({ + requestFunction : function(url) { + return undefined; + } + })); + }).toThrowDeveloperError(); + }); + + it('throttleRequest throws when request.requestFunction is undefined', function() { + expect(function() { + RequestScheduler.throttleRequest(new Request({ + url : 'file/path' + })); }).toThrowDeveloperError(); }); @@ -43,9 +59,14 @@ defineSuite([ return deferred.promise; } - var promise1 = RequestScheduler.throttleRequest('http://foo.com/1', requestFunction); - var promise2 = RequestScheduler.throttleRequest('http://foo.com/2', requestFunction); - var promise3 = RequestScheduler.throttleRequest('http://foo.com/3', requestFunction); + var request = new Request({ + url : 'http://foo.com/1', + requestFunction : requestFunction + }); + + var promise1 = RequestScheduler.throttleRequest(request); + var promise2 = RequestScheduler.throttleRequest(request); + var promise3 = RequestScheduler.throttleRequest(request); expect(deferreds.length).toBe(2); expect(promise1).toBeDefined(); @@ -54,16 +75,16 @@ defineSuite([ deferreds[0].resolve(); - var promise4 = RequestScheduler.throttleRequest('http://foo.com/3', requestFunction); + var promise4 = RequestScheduler.throttleRequest(request); expect(deferreds.length).toBe(3); expect(promise4).toBeDefined(); - var promise5 = RequestScheduler.throttleRequest('http://foo.com/4', requestFunction); + var promise5 = RequestScheduler.throttleRequest(request); expect(deferreds.length).toBe(3); expect(promise5).not.toBeDefined(); RequestScheduler.maximumRequests = 3; - var promise6 = RequestScheduler.throttleRequest('http://foo.com/4', requestFunction); + var promise6 = RequestScheduler.throttleRequest(request); expect(deferreds.length).toBe(4); expect(promise6).toBeDefined(); @@ -83,9 +104,14 @@ defineSuite([ return deferred.promise; } - var promise1 = RequestScheduler.throttleRequest('http://foo.com/1', requestFunction); - var promise2 = RequestScheduler.throttleRequest('http://foo.com/2', requestFunction); - var promise3 = RequestScheduler.throttleRequest('http://foo.com/3', requestFunction); + var request = new Request({ + url : 'http://foo.com/1', + requestFunction : requestFunction + }); + + var promise1 = RequestScheduler.throttleRequest(request); + var promise2 = RequestScheduler.throttleRequest(request); + var promise3 = RequestScheduler.throttleRequest(request); expect(deferreds.length).toBe(2); expect(promise1).toBeDefined(); @@ -94,16 +120,16 @@ defineSuite([ deferreds[0].resolve(); - var promise4 = RequestScheduler.throttleRequest('http://foo.com/3', requestFunction); + var promise4 = RequestScheduler.throttleRequest(request); expect(deferreds.length).toBe(3); expect(promise4).toBeDefined(); - var promise5 = RequestScheduler.throttleRequest('http://foo.com/4', requestFunction); + var promise5 = RequestScheduler.throttleRequest(request); expect(deferreds.length).toBe(3); expect(promise5).not.toBeDefined(); RequestScheduler.maximumRequestsPerServer = 3; - var promise6 = RequestScheduler.throttleRequest('http://foo.com/4', requestFunction); + var promise6 = RequestScheduler.throttleRequest(request); expect(deferreds.length).toBe(4); expect(promise6).toBeDefined(); @@ -121,9 +147,14 @@ defineSuite([ return deferred.promise; } - RequestScheduler.throttleRequest('http://foo.com/1', requestFunction); - RequestScheduler.throttleRequest('http://foo.com/2', requestFunction); - RequestScheduler.throttleRequest('http://foo.com/3', requestFunction); + var request = new Request({ + url : 'http://foo.com/1', + requestFunction : requestFunction + }); + + RequestScheduler.throttleRequest(request); + RequestScheduler.throttleRequest(request); + RequestScheduler.throttleRequest(request); expect(RequestScheduler.maximumRequests).toBe(10); expect(RequestScheduler.getNumberOfAvailableRequests()).toBe(7); @@ -144,9 +175,19 @@ defineSuite([ return deferred.promise; } - RequestScheduler.throttleRequest('http://foo.com/1', requestFunction); - RequestScheduler.throttleRequest('http://foo.com/2', requestFunction); - RequestScheduler.throttleRequest('http://bar.com/3', requestFunction); + var requestFoo = new Request({ + url : 'http://foo.com/1', + requestFunction : requestFunction + }); + + var requestBar = new Request({ + url : 'http://bar.com/1', + requestFunction : requestFunction + }); + + RequestScheduler.throttleRequest(requestFoo); + RequestScheduler.throttleRequest(requestFoo); + RequestScheduler.throttleRequest(requestBar); expect(RequestScheduler.maximumRequestsPerServer).toBe(6); expect(RequestScheduler.getNumberOfAvailableRequestsByServer('http://foo.com')).toBe(4); @@ -188,13 +229,23 @@ defineSuite([ return deferred.promise; } - RequestScheduler.throttleRequest('http://foo.com/1', requestFunction); + var requestFoo = new Request({ + url : 'http://foo.com/1', + requestFunction : requestFunction + }); + + var requestBar = new Request({ + url : 'http://bar.com/1', + requestFunction : requestFunction + }); + + RequestScheduler.throttleRequest(requestFoo); expect(RequestScheduler.hasAvailableRequests('http://foo.com')).toEqual(true); - RequestScheduler.throttleRequest('http://foo.com/2', requestFunction); + RequestScheduler.throttleRequest(requestFoo); expect(RequestScheduler.hasAvailableRequests('http://foo.com')).toEqual(false); expect(RequestScheduler.hasAvailableRequests()).toEqual(true); - RequestScheduler.throttleRequest('http://bar.com/1', requestFunction); + RequestScheduler.throttleRequest(requestBar); expect(RequestScheduler.hasAvailableRequests()).toEqual(false); expect(RequestScheduler.getNumberOfAvailableRequests()).toEqual(0); @@ -203,4 +254,200 @@ defineSuite([ deferreds[1].resolve(); deferreds[2].resolve(); }); + + it('defers request when request scheduler is full', function() { + RequestScheduler.maximumRequests = 3; + + var deferreds = []; + + function requestFunction(url) { + var deferred = when.defer(); + deferreds.push(deferred); + return deferred.promise; + } + + var request = new Request({ + url : 'http://foo.com/1', + requestFunction : requestFunction + }); + + var requestDeferred = new Request({ + url : 'http://foo.com/1', + requestFunction : requestFunction, + defer : true + }); + + RequestScheduler.throttleRequest(request); + RequestScheduler.throttleRequest(request); + RequestScheduler.throttleRequest(request); + expect(RequestScheduler.hasAvailableRequests()).toEqual(false); + + // A deferred request will always return a promise, however its + // requestFunction is not called until there is an open slot + var deferredPromise = RequestScheduler.throttleRequest(requestDeferred); + expect(deferredPromise).toBeDefined(); + expect(deferreds[3]).not.toBeDefined(); + + // When the first request completes, the deferred promise starts + deferreds[0].resolve(); + expect(deferreds[3]).toBeDefined(); + + deferreds[1].resolve(); + deferreds[2].resolve(); + deferreds[3].resolve(); + }); + + it('makes a basic request', function() { + RequestScheduler.maximumRequests = 2; + + var deferreds = []; + + function requestFunction(url) { + var deferred = when.defer(); + deferreds.push(deferred); + return deferred.promise; + } + + var promise1 = RequestScheduler.request('http://foo.com/1', requestFunction); + var promise2 = RequestScheduler.request('http://foo.com/2', requestFunction); + var promise3 = RequestScheduler.request('http://foo.com/3', requestFunction); + + expect(promise1).toBeDefined(); + expect(promise2).toBeDefined(); + expect(promise3).toBeDefined(); + + expect(deferreds[2]).not.toBeDefined(); + + // When the first request completes, the last request starts + deferreds[0].resolve(); + expect(deferreds[2]).toBeDefined(); + + deferreds[1].resolve(); + deferreds[2].resolve(); + }); + + it('prioritize requests', function() { + RequestScheduler.prioritize = true; + RequestScheduler.maximumRequests = 2; + + var deferreds = []; + + function requestFunction(url) { + var deferred = when.defer(); + deferreds.push(deferred); + return deferred.promise; + } + + var terrainRequest1 = new Request({ + url : 'http://foo.com/1', + requestType : RequestType.TERRAIN, + requestFunction : requestFunction, + distance : 10.0 + }); + + var terrainRequest2 = new Request({ + url : 'http://foo.com/2', + requestType : RequestType.TERRAIN, + requestFunction : requestFunction, + distance : 20.0 + }); + + var imageryRequest = new Request({ + url : 'http://bar.com/1', + requestType : RequestType.IMAGERY, + requestFunction : requestFunction, + distance : 15.0 + }); + + var promise1 = RequestScheduler.throttleRequest(terrainRequest1); + var promise2 = RequestScheduler.throttleRequest(terrainRequest2); + var promise3 = RequestScheduler.throttleRequest(imageryRequest); + + // The requests should all return undefined because the budgets haven't been created yet + expect(promise1).not.toBeDefined(); + expect(promise2).not.toBeDefined(); + expect(promise3).not.toBeDefined(); + + // Budgets should now allow one terrain request and one imagery request (based on their distances) + RequestScheduler.resetBudgets(); + + promise1 = RequestScheduler.throttleRequest(terrainRequest1); + promise2 = RequestScheduler.throttleRequest(terrainRequest2); + promise3 = RequestScheduler.throttleRequest(imageryRequest); + + expect(promise1).toBeDefined(); + expect(promise2).not.toBeDefined(); + expect(promise3).toBeDefined(); + + deferreds[0].resolve(); + deferreds[1].resolve(); + + RequestScheduler.resetBudgets(); + RequestScheduler.prioritize = false; + }); + + it('does not throttle requests when RequestScheduler.throttle is false', function() { + RequestScheduler.throttle = false; + RequestScheduler.maximumRequests = 2; + + var deferreds = []; + + function requestFunction(url) { + var deferred = when.defer(); + deferreds.push(deferred); + return deferred.promise; + } + + var request = new Request({ + url : 'http://foo.com/', + requestFunction : requestFunction + }); + + var promise1 = RequestScheduler.throttleRequest(request); + var promise2 = RequestScheduler.throttleRequest(request); + var promise3 = RequestScheduler.throttleRequest(request); + + // All requests are passed through to the browser + expect(promise1).toBeDefined(); + expect(promise2).toBeDefined(); + expect(promise3).toBeDefined(); + + deferreds[0].resolve(); + deferreds[1].resolve(); + deferreds[2].resolve(); + + RequestScheduler.throttle = true; + }); + + it('debugShowStatistics', function() { + spyOn(console, 'log'); + + var deferreds = []; + + function requestFunction(url) { + var deferred = when.defer(); + deferreds.push(deferred); + return deferred.promise; + } + + var request = new Request({ + url : 'http://foo.com/1', + requestFunction : requestFunction + }); + + RequestScheduler.debugShowStatistics = false; + RequestScheduler.throttleRequest(request); + RequestScheduler.resetBudgets(); + expect(console.log).not.toHaveBeenCalled(); + + RequestScheduler.debugShowStatistics = true; + RequestScheduler.throttleRequest(request); + RequestScheduler.resetBudgets(); + expect(console.log).toHaveBeenCalled(); + + deferreds[0].resolve(); + deferreds[1].resolve(); + + RequestScheduler.resetBudgets(); + }); }); diff --git a/Specs/Core/VRTheWorldTerrainProviderSpec.js b/Specs/Core/VRTheWorldTerrainProviderSpec.js index 2d4347ce2ac0..d0e3a79b8b98 100644 --- a/Specs/Core/VRTheWorldTerrainProviderSpec.js +++ b/Specs/Core/VRTheWorldTerrainProviderSpec.js @@ -8,6 +8,7 @@ defineSuite([ 'Core/loadImage', 'Core/loadWithXhr', 'Core/Math', + 'Core/Request', 'Core/TerrainProvider', 'Specs/pollToPromise', 'ThirdParty/when' @@ -20,6 +21,7 @@ defineSuite([ loadImage, loadWithXhr, CesiumMath, + Request, TerrainProvider, pollToPromise, when) { @@ -272,18 +274,20 @@ defineSuite([ url : baseUrl }); + var request = new Request(); + return pollToPromise(function() { return terrainProvider.ready; }).then(function() { - var promise = terrainProvider.requestTileGeometry(0, 0, 0); + var promise = terrainProvider.requestTileGeometry(0, 0, 0, request); expect(promise).toBeDefined(); var i; for (i = 0; i < 10; ++i) { - promise = terrainProvider.requestTileGeometry(0, 0, 0); + promise = terrainProvider.requestTileGeometry(0, 0, 0, request); } - promise = terrainProvider.requestTileGeometry(0, 0, 0); + promise = terrainProvider.requestTileGeometry(0, 0, 0, request); expect(promise).toBeUndefined(); for (i = 0; i < deferreds.length; ++i) { diff --git a/Specs/spec-main.js b/Specs/spec-main.js index b473df9579e1..80acbde558ec 100644 --- a/Specs/spec-main.js +++ b/Specs/spec-main.js @@ -58,6 +58,7 @@ /*global jasmineRequire,jasmine,exports,specs*/ + // Disable request prioritization since it interferes with tests that expect a request to go through immediately. Cesium.RequestScheduler.prioritize = false; var when = Cesium.when; From 712881e23eccbaa6222434378b9867c7079b2bb6 Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Thu, 14 Jan 2016 10:49:42 -0500 Subject: [PATCH 05/11] Add doc link --- Source/Core/Request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Request.js b/Source/Core/Request.js index 7a8b35d6b5c7..cdc748f3dda6 100644 --- a/Source/Core/Request.js +++ b/Source/Core/Request.js @@ -6,7 +6,7 @@ define([ "use strict"; /** - * Stores information for making a request using RequestScheduler + * Stores information for making a request using {@link RequestScheduler}. * * @exports Request * From e15dfaef2e365a147fc962ca8a6cee3724fc34e8 Mon Sep 17 00:00:00 2001 From: Patrick Cozzi Date: Thu, 14 Jan 2016 12:16:36 -0500 Subject: [PATCH 06/11] Fix doc --- Source/Core/RequestScheduler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js index b8e8e6e810a8..0839402f4842 100644 --- a/Source/Core/RequestScheduler.js +++ b/Source/Core/RequestScheduler.js @@ -378,7 +378,7 @@ define([ /** * When true, log statistics to the console every frame * @type {Boolean} - * @default true + * @default false */ RequestScheduler.debugShowStatistics = false; From a7286e60da3a9f2ce1bafd2faf5692526f5baece Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 18 Jan 2016 16:47:53 -0500 Subject: [PATCH 07/11] RequestScheduler updates --- CHANGES.md | 2 + .../Core/ArcGisImageServerTerrainProvider.js | 12 +- Source/Core/CesiumTerrainProvider.js | 12 +- Source/Core/Request.js | 24 +++- Source/Core/RequestScheduler.js | 126 ++++++++++++------ Source/Core/VRTheWorldTerrainProvider.js | 12 +- .../Batched3DModel3DTileContentProvider.js | 5 +- Source/Scene/Cesium3DTile.js | 5 + Source/Scene/Cesium3DTileset.js | 14 +- .../Scene/Composite3DTileContentProvider.js | 5 +- Source/Scene/ImageryProvider.js | 4 +- .../Instanced3DModel3DTileContentProvider.js | 6 +- Source/Scene/Model.js | 9 +- Source/Scene/ModelInstanceCollection.js | 3 + Source/Scene/Points3DTileContentProvider.js | 15 +-- Source/Scene/TileTerrain.js | 5 +- Specs/Core/RequestSchedulerSpec.js | 108 +++++++-------- Specs/Scene/Cesium3DTilesetSpec.js | 2 +- Specs/Scene/GlobeSurfaceTileSpec.js | 2 +- 19 files changed, 225 insertions(+), 146 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ec40f038cd30..5cfffbd20f3f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,8 @@ Change Log * Breaking changes * Removed `OpenStreetMapImageryProvider`. Use `createOpenStreetMapImageryProvider` instead. +* Deprecated + * Deprecated `throttleRequests` flag in `TerrainProvider`. It is replaced by an optional `Request` object that stores information used to prioritize requests. * Reduced the amount of CPU memory used by terrain by ~25% in Chrome. * Fixed a picking problem ([#3386](https://github.com/AnalyticalGraphicsInc/cesium/issues/3386)) that sometimes prevented objects being selected. * Added `Scene.useDepthPicking` to enable or disable picking using the depth buffer. [#3390](https://github.com/AnalyticalGraphicsInc/cesium/pull/3390) diff --git a/Source/Core/ArcGisImageServerTerrainProvider.js b/Source/Core/ArcGisImageServerTerrainProvider.js index 6b755200a1de..49b7aa60dde6 100644 --- a/Source/Core/ArcGisImageServerTerrainProvider.js +++ b/Source/Core/ArcGisImageServerTerrainProvider.js @@ -237,17 +237,19 @@ define([ url = proxy.getURL(url); } - if (!defined(request)) { + // TODO : request used to be a boolean called throttleRequest. Continue to handle that case until it is deprecated. + if (!defined(request) || (request === false)) { // If a request object isn't provided, perform an immediate request - request = new Request(); - request.defer = true; + request = new Request({ + defer : true + }); } request.url = url; request.requestFunction = loadImage; - request.requestType = RequestType.TERRAIN; + request.type = RequestType.TERRAIN; - var promise = RequestScheduler.throttleRequest(request); + var promise = RequestScheduler.schedule(request); if (!defined(promise)) { return undefined; } diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 94c28304ae25..99909f8ce8b6 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -522,17 +522,19 @@ define([ return loadArrayBuffer(tileUrl, getRequestHeader(extensionList)); } - if (!defined(request)) { + // TODO : request used to be a boolean called throttleRequest. Continue to handle that case until it is deprecated. + if (!defined(request) || (request === false)) { // If a request object isn't provided, perform an immediate request - request = new Request(); - request.defer = true; + request = new Request({ + defer : true + }); } request.url = url; request.requestFunction = tileLoader; - request.requestType = RequestType.TERRAIN; + request.type = RequestType.TERRAIN; - var promise = RequestScheduler.throttleRequest(request); + var promise = RequestScheduler.schedule(request); if (!defined(promise)) { return undefined; } diff --git a/Source/Core/Request.js b/Source/Core/Request.js index cdc748f3dda6..85f3a1348064 100644 --- a/Source/Core/Request.js +++ b/Source/Core/Request.js @@ -1,6 +1,6 @@ /*global define*/ define([ - 'Core/defaultValue' + './defaultValue' ], function( defaultValue) { "use strict"; @@ -17,33 +17,45 @@ define([ /** * The URL to request. + * + * @private */ this.url = options.url; /** * Extra parameters to send with the request. For example, HTTP headers or jsonp parameters. + * + * @private */ this.parameters = options.parameters; /** * The actual function that makes the request. + * + * @private */ this.requestFunction = options.requestFunction; /** * Type of request. Used for more fine-grained priority sorting. + * + * @private */ - this.requestType = options.requestType; + this.type = options.type; /** * Specifies that the request should be deferred until an open slot is available. * A deferred request will always return a promise, which is suitable for data * sources and utility functions. + * + * @private */ this.defer = defaultValue(options.defer, false); /** * The distance from the camera, used to prioritize requests. + * + * @private */ this.distance = defaultValue(options.distance, 0.0); @@ -51,13 +63,17 @@ define([ /** * A promise for when a deferred request can start. + * + * @private */ this.startPromise = undefined; /** - * The server to request from. + * Reference to a {@link RequestScheduler~RequestServer}. + * + * @private */ - this.server = undefined; + this.server = options.server; } return Request; diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js index 0839402f4842..c788a713e380 100644 --- a/Source/Core/RequestScheduler.js +++ b/Source/Core/RequestScheduler.js @@ -7,7 +7,8 @@ define([ './defineProperties', './DeveloperError', './Queue', - './Request' + './Request', + './RequestType' ], function( Uri, when, @@ -16,10 +17,11 @@ define([ defineProperties, DeveloperError, Queue, - Request) { + Request, + RequestType) { "use strict"; - function RequestTypeBudget(request) { + function RequestBudget(request) { /** * Total requests allowed this frame. */ @@ -38,10 +40,34 @@ define([ /** * Type of request. Used for more fine-grained priority sorting. */ - this.requestType = request.requestType; + this.type = request.type; } - var activeRequestsByServer = {}; + /** + * Stores the number of active requests at a particular server. Areas that commonly makes requests may store + * a reference to this object in order to quickly determine whether a request can be issued (e.g. Cesium3DTile). + */ + function RequestServer(serverName) { + /** + * Number of active requests at this server. + */ + this.activeRequests = 0; + + /** + * The name of the server. + */ + this.serverName = serverName; + } + + RequestServer.prototype.hasAvailableRequests = function() { + return RequestScheduler.hasAvailableRequests() && (this.activeRequests < RequestScheduler.maximumRequestsPerServer); + }; + + RequestServer.prototype.getNumberOfAvailableRequests = function() { + return RequestScheduler.maximumRequestsPerServer - this.activeRequests; + }; + + var activeRequestsByServer = []; var activeRequests = 0; var budgets = []; var leftoverRequests = []; @@ -74,12 +100,12 @@ define([ var length = budgets.length; for (var i = 0; i < length; ++i) { budget = budgets[i]; - if ((budget.server === request.server) && (budget.requestType === request.requestType)) { + if ((budget.server === request.server) && (budget.type === request.type)) { return budget; } } // Not found, create a new budget - budget = new RequestTypeBudget(request); + budget = new RequestBudget(request); budgets.push(budget); return budget; } @@ -88,7 +114,7 @@ define([ showStats(); clearStats(); - if (!RequestScheduler.prioritize) { + if (!RequestScheduler.prioritize || !RequestScheduler.throttle) { return; } @@ -109,7 +135,7 @@ define([ for (var j = 0; (j < requestsLength) && (availableRequests > 0); ++j) { var request = requests[j]; var budget = getBudget(request); - var budgetAvailable = RequestScheduler.getNumberOfAvailableRequestsByServer(request.url); + var budgetAvailable = budget.server.getNumberOfAvailableRequests(); if (budget.total < budgetAvailable) { ++budget.total; --availableRequests; @@ -127,7 +153,7 @@ define([ * @param {String} url The url. * @returns {String} The server name. */ - RequestScheduler.getServer = function(url) { + RequestScheduler.getServerName = function(url) { //>>includeStart('debug', pragmas.debug); if (!defined(url)) { throw new DeveloperError('url is required.'); @@ -143,6 +169,27 @@ define([ return server; }; + /** + * Get the request server from a given url. + * + * @param {String} url The url. + * @returns {RequestServer} The request server. + */ + RequestScheduler.getRequestServer = function(url) { + var requestServer; + var serverName = RequestScheduler.getServerName(url); + var length = activeRequestsByServer.length; + for (var i = 0; i < length; ++i) { + requestServer = activeRequestsByServer[i]; + if (requestServer.serverName === serverName) { + return requestServer; + } + } + requestServer = new RequestServer(serverName); + activeRequestsByServer.push(requestServer); + return requestServer; + }; + /** * Get the number of available slots for the given server. * @@ -150,9 +197,7 @@ define([ * @returns {Number} The number of available slots. */ RequestScheduler.getNumberOfAvailableRequestsByServer = function(url) { - var server = RequestScheduler.getServer(url); - var activeRequestsForServer = defaultValue(activeRequestsByServer[server], 0); - return RequestScheduler.maximumRequestsPerServer - activeRequestsForServer; + return RequestScheduler.getRequestServer(url).getNumberOfAvailableRequests(); }; /** @@ -165,32 +210,29 @@ define([ }; /** - * Checks if there are available slots to make a request. - * It considers the total number of available slots across all servers, and - * if a url is provided, the total number of available slots at the url's server. + * Checks if there are available slots to make a request at the server pointed to by the url. * * @param {String} [url] The url to check. * @returns {Boolean} Returns true if there are available slots, otherwise false. */ - RequestScheduler.hasAvailableRequests = function(url) { - if (activeRequests >= RequestScheduler.maximumRequests) { - return false; - } - - if (defined(url)) { - var server = RequestScheduler.getServer(url); - var activeRequestsForServer = defaultValue(activeRequestsByServer[server], 0); - if (activeRequestsForServer >= RequestScheduler.maximumRequestsPerServer) { - return false; - } - } + RequestScheduler.hasAvailableRequestsByServer = function(url) { + return RequestScheduler.getRequestServer(url).hasAvailableRequests(); + }; - return true; + /** + * Checks if there are available slots to make a request, considering the total + * number of available slots across all servers. + * + * @param {String} [url] The url to check. + * @returns {Boolean} Returns true if there are available slots, otherwise false. + */ + RequestScheduler.hasAvailableRequests = function() { + return activeRequests < RequestScheduler.maximumRequests; }; function requestComplete(request) { --activeRequests; - --activeRequestsByServer[request.server]; + --request.server.activeRequests; // Start a deferred request immediately now that a slot is open var deferredRequest = deferredRequests.dequeue(); @@ -201,7 +243,7 @@ define([ function startRequest(request) { ++activeRequests; - ++activeRequestsByServer[request.server]; + ++request.server.activeRequests; return when(request.requestFunction(request.url, request.parameters), function(result) { requestComplete(request); @@ -246,7 +288,7 @@ define([ * url : url, * requestFunction : requestFunction * }); - * var promise = Cesium.RequestScheduler.throttleRequest(request); + * var promise = Cesium.RequestScheduler.schedule(request); * if (!Cesium.defined(promise)) { * // too many active requests in progress, try again later. * } else { @@ -256,7 +298,7 @@ define([ * } * */ - RequestScheduler.throttleRequest = function(request) { + RequestScheduler.schedule = function(request) { //>>includeStart('debug', pragmas.debug); if (!defined(request)) { throw new DeveloperError('request is required.'); @@ -275,11 +317,11 @@ define([ return request.requestFunction(request.url, request.parameters); } - var server = RequestScheduler.getServer(request.url); - request.server = server; - activeRequestsByServer[server] = defaultValue(activeRequestsByServer[server], 0); + if (!defined(request.server)) { + request.server = RequestScheduler.getRequestServer(request.url); + } - if (!RequestScheduler.hasAvailableRequests(request.url)) { + if (!request.server.hasAvailableRequests()) { if (!request.defer) { // No available slots to make the request, return undefined handleLeftoverRequest(request); @@ -291,7 +333,7 @@ define([ } } - if (RequestScheduler.prioritize && defined(request.requestType) && !request.defer) { + if (RequestScheduler.prioritize && defined(request.type) && !request.defer) { var budget = getBudget(request); if (budget.used >= budget.total) { // Request does not fit in the budget, return undefined @@ -312,15 +354,17 @@ define([ * @param {RequestScheduler~RequestFunction} requestFunction The actual function that * makes the request. * @param {Object} [parameters] Extra parameters to send with the request. + * @param {RequestType} [requestType] Type of request. Used for more fine-grained priority sorting. * * @returns {Promise.} A Promise for the requested data. */ - RequestScheduler.request = function(url, requestFunction, parameters) { - return RequestScheduler.throttleRequest(new Request({ + RequestScheduler.request = function(url, requestFunction, parameters, requestType) { + return RequestScheduler.schedule(new Request({ url : url, parameters : parameters, requestFunction : requestFunction, - defer : true + defer : true, + type : defaultValue(requestType, RequestType.OTHER) })); }; diff --git a/Source/Core/VRTheWorldTerrainProvider.js b/Source/Core/VRTheWorldTerrainProvider.js index 645154093e30..8c835ae37237 100644 --- a/Source/Core/VRTheWorldTerrainProvider.js +++ b/Source/Core/VRTheWorldTerrainProvider.js @@ -272,17 +272,19 @@ define([ url = proxy.getURL(url); } - if (!defined(request)) { + // TODO : request used to be a boolean called throttleRequest. Continue to handle that case until it is deprecated. + if (!defined(request) || (request === false)) { // If a request object isn't provided, perform an immediate request - request = new Request(); - request.defer = true; + request = new Request({ + defer : true + }); } request.url = url; request.requestFunction = loadImage; - request.requestType = RequestType.TERRAIN; + request.type = RequestType.TERRAIN; - var promise = RequestScheduler.throttleRequest(request); + var promise = RequestScheduler.schedule(request); if (!defined(promise)) { return undefined; } diff --git a/Source/Scene/Batched3DModel3DTileContentProvider.js b/Source/Scene/Batched3DModel3DTileContentProvider.js index ca6d10d20f09..1e98e1b25d0b 100644 --- a/Source/Scene/Batched3DModel3DTileContentProvider.js +++ b/Source/Scene/Batched3DModel3DTileContentProvider.js @@ -127,10 +127,11 @@ define([ var that = this; var distance = this._tile.distanceToCamera; - var promise = RequestScheduler.throttleRequest(new Request({ + var promise = RequestScheduler.schedule(new Request({ url : this._url, + server : this._tile._requestServer, requestFunction : loadArrayBuffer, - requestType : RequestType.TILES3D, + type : RequestType.TILES3D, distance : distance })); if (defined(promise)) { diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index 6d4354c53cbc..61141c5f3d12 100644 --- a/Source/Scene/Cesium3DTile.js +++ b/Source/Scene/Cesium3DTile.js @@ -18,6 +18,7 @@ define([ '../Core/OrientedBoundingBox', '../Core/Rectangle', '../Core/RectangleOutlineGeometry', + '../Core/RequestScheduler', '../Core/SphereOutlineGeometry', '../ThirdParty/Uri', '../ThirdParty/when', @@ -50,6 +51,7 @@ define([ OrientedBoundingBox, Rectangle, RectangleOutlineGeometry, + RequestScheduler, SphereOutlineGeometry, Uri, when, @@ -155,9 +157,11 @@ define([ this.readyPromise = when.defer(); var content; + var requestServer; if (defined(contentHeader)) { var contentUrl = contentHeader.url; var url = getAbsoluteUri(contentUrl, baseUrl); + requestServer = RequestScheduler.getRequestServer(url); var type = getExtensionFromUri(url); var contentFactory = Cesium3DTileContentProviderFactory[type]; @@ -179,6 +183,7 @@ define([ this.hasContent = false; } this._content = content; + this._requestServer = requestServer; function setRefinable(tile) { var parent = tile.parent; diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index fcab7c838f60..cc9cfa786b54 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -220,10 +220,10 @@ define([ var tileset = this; // We don't know the distance of the tileset until tiles.json is loaded, so use the default distance for now - var promise = RequestScheduler.throttleRequest(new Request({ + var promise = RequestScheduler.schedule(new Request({ url : tilesJson, requestFunction : loadJson, - requestType : RequestType.TILES3D + type : RequestType.TILES3D })); if (!defined(promise)) { @@ -311,7 +311,7 @@ define([ if (!outOfCore) { return; } - if (!hasAvailableRequests(tiles3D)) { + if (!hasAvailableRequests(tile)) { return; } @@ -328,8 +328,8 @@ define([ } } - function hasAvailableRequests(tiles3D) { - return RequestScheduler.hasAvailableRequests(tiles3D._url); + function hasAvailableRequests(tile) { + return !defined(tile._requestServer) || tile._requestServer.hasAvailableRequests(); } function selectTile(selectedTiles, tile, fullyVisible, frameState) { @@ -426,7 +426,7 @@ define([ // Only sort and refine (render or request children) if any // children are loaded or request slots are available. var anyChildrenLoaded = (t.numberOfChildrenWithoutContent < childrenLength); - if (anyChildrenLoaded || hasAvailableRequests(tiles3D)) { + if (anyChildrenLoaded || hasAvailableRequests(t)) { // Distance is used for sorting now and for computing SSE when the tile comes off the stack. computeDistanceToCamera(children, frameState); @@ -476,7 +476,7 @@ define([ // tile (and can't make child requests because no slots are available) // then the children do not need to be sorted. var allChildrenLoaded = t.numberOfChildrenWithoutContent === 0; - if (allChildrenLoaded || hasAvailableRequests(tiles3D)) { + if (allChildrenLoaded || hasAvailableRequests(t)) { // Distance is used for sorting now and for computing SSE when the tile comes off the stack. computeDistanceToCamera(children, frameState); diff --git a/Source/Scene/Composite3DTileContentProvider.js b/Source/Scene/Composite3DTileContentProvider.js index 930821b85bbd..31e773e56717 100644 --- a/Source/Scene/Composite3DTileContentProvider.js +++ b/Source/Scene/Composite3DTileContentProvider.js @@ -62,10 +62,11 @@ define([ var that = this; var distance = this._tile.distanceToCamera; - var promise = RequestScheduler.throttleRequest(new Request({ + var promise = RequestScheduler.schedule(new Request({ url : this._url, + server : this._tile._requestServer, requestFunction : loadArrayBuffer, - requestType : RequestType.TILES3D, + type : RequestType.TILES3D, distance : distance })); if (defined(promise)) { diff --git a/Source/Scene/ImageryProvider.js b/Source/Scene/ImageryProvider.js index e29438676d1f..9f3fb89d39e6 100644 --- a/Source/Scene/ImageryProvider.js +++ b/Source/Scene/ImageryProvider.js @@ -315,10 +315,10 @@ define([ ImageryProvider.loadImage = function(imageryProvider, url, distance) { var requestFunction = defined(imageryProvider.tileDiscardPolicy) ? loadImageViaBlob : loadImage; - return RequestScheduler.throttleRequest(new Request({ + return RequestScheduler.schedule(new Request({ url : url, requestFunction : requestFunction, - requestType : RequestType.IMAGERY, + type : RequestType.IMAGERY, distance : distance })); }; diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index f4275b81144e..a72ee814277a 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -137,10 +137,11 @@ define([ var that = this; var distance = this._tile.distanceToCamera; - var promise = RequestScheduler.throttleRequest(new Request({ + var promise = RequestScheduler.schedule(new Request({ url : this._url, + server : this._tile._requestServer, requestFunction : loadArrayBuffer, - requestType : RequestType.TILES3D, + type : RequestType.TILES3D, distance : distance })); if (defined(promise)) { @@ -230,6 +231,7 @@ define([ cull : false, url : undefined, headers : undefined, + type : RequestType.TILES3D, gltf : undefined, basePath : undefined }; diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js index 4379d5d72797..254fee124206 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -547,6 +547,7 @@ define([ this._pickFragmentShaderLoaded = options.pickFragmentShaderLoaded; this._pickUniformMapLoaded = options.pickUniformMapLoaded; this._ignoreCommands = defaultValue(options.ignoreCommands, false); + this._requestType = options.requestType; /** * @private @@ -1010,7 +1011,7 @@ define([ setCachedGltf(model, cachedGltf); gltfCache[cacheKey] = cachedGltf; - RequestScheduler.request(url, loadArrayBuffer, options.headers).then(function(arrayBuffer) { + RequestScheduler.request(url, loadArrayBuffer, options.headers, options.requestType).then(function(arrayBuffer) { var array = new Uint8Array(arrayBuffer); if (containsGltfMagic(array)) { // Load binary glTF @@ -1198,7 +1199,8 @@ define([ ++model._loadResources.pendingBufferLoads; var uri = new Uri(buffer.uri); var bufferPath = uri.resolve(model._baseUri).toString(); - RequestScheduler.request(bufferPath, loadArrayBuffer).then(bufferLoad(model, id)).otherwise(getFailedLoadFunction(model, 'buffer', bufferPath)); + var promise = RequestScheduler.request(bufferPath, loadArrayBuffer, undefined, model._requestType); + promise.then(bufferLoad(model, id)).otherwise(getFailedLoadFunction(model, 'buffer', bufferPath)); } } } @@ -1302,7 +1304,8 @@ define([ ++model._loadResources.pendingShaderLoads; var uri = new Uri(shader.uri); var shaderPath = uri.resolve(model._baseUri).toString(); - RequestScheduler.request(shaderPath, loadText).then(shaderLoad(model, shader.type, id)).otherwise(getFailedLoadFunction(model, 'shader', shaderPath)); + var promise = RequestScheduler.request(shaderPath, loadText, undefined, model.requestType); + promise.then(shaderLoad(model, shader.type, id)).otherwise(getFailedLoadFunction(model, 'shader', shaderPath)); } } } diff --git a/Source/Scene/ModelInstanceCollection.js b/Source/Scene/ModelInstanceCollection.js index a098a6e66bdc..c8259115ef0e 100644 --- a/Source/Scene/ModelInstanceCollection.js +++ b/Source/Scene/ModelInstanceCollection.js @@ -58,6 +58,7 @@ define([ * @param {Object} options Object with the following properties: * @param {String} [options.url] The url to the .gltf file. * @param {Object} [options.headers] HTTP headers to send with the request. + * @param {Object} [options.requestType] The request type, used for budget scheduling in {@link RequestScheduler}. * @param {Object|ArrayBuffer|Uint8Array} [options.gltf] The object for the glTF JSON or an arraybuffer of Binary glTF defined by the CESIUM_binary_glTF extension. * @param {String} [options.basePath=''] The base path that paths in the glTF JSON are relative to. * @param {Boolean} [options.dynamic] Collection is set to stream instance data every frame. @@ -122,6 +123,7 @@ define([ // Passed on to Model this._url = options.url; this._headers = options.headers; + this._requestType = options.requestType; this._gltf = options.gltf; this._basePath = options.basePath; this._asynchronous = options.asynchronous; @@ -523,6 +525,7 @@ define([ var modelOptions = { url : collection._url, headers : collection._headers, + requestType : collection._requestType, gltf : collection._gltf, basePath : collection._basePath, cacheKey : undefined, diff --git a/Source/Scene/Points3DTileContentProvider.js b/Source/Scene/Points3DTileContentProvider.js index 884b66357a62..c66e8dafee47 100644 --- a/Source/Scene/Points3DTileContentProvider.js +++ b/Source/Scene/Points3DTileContentProvider.js @@ -18,10 +18,7 @@ define([ '../ThirdParty/when', './Cesium3DTileContentState', './PointAppearance', - './Primitive', - './TileBoundingRegion', - './TileBoundingSphere', - './TileOrientedBoundingBox' + './Primitive' ], function( BoundingSphere, Cartesian3, @@ -41,10 +38,7 @@ define([ when, Cesium3DTileContentState, PointAppearance, - Primitive, - TileBoundingRegion, - TileBoundingSphere, - TileOrientedBoundingBox) { + Primitive) { "use strict"; /** @@ -95,10 +89,11 @@ define([ var that = this; var distance = this._tile.distanceToCamera; - var promise = RequestScheduler.throttleRequest(new Request({ + var promise = RequestScheduler.schedule(new Request({ url : this._url, + server : this._tile._requestServer, requestFunction : loadArrayBuffer, - requestType : RequestType.TILES3D, + type : RequestType.TILES3D, distance : distance })); if (defined(promise)) { diff --git a/Source/Scene/TileTerrain.js b/Source/Scene/TileTerrain.js index 4489419e0251..4d4beb38171e 100644 --- a/Source/Scene/TileTerrain.js +++ b/Source/Scene/TileTerrain.js @@ -137,8 +137,9 @@ define([ function doRequest() { // Request the terrain from the terrain provider. - var request = new Request(); - request.distance = distance; + var request = new Request({ + distance : distance + }); tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level, request); // If the request method returns undefined (instead of a promise), the request diff --git a/Specs/Core/RequestSchedulerSpec.js b/Specs/Core/RequestSchedulerSpec.js index 52c3228281a2..8fbb00f18523 100644 --- a/Specs/Core/RequestSchedulerSpec.js +++ b/Specs/Core/RequestSchedulerSpec.js @@ -24,15 +24,15 @@ defineSuite([ RequestScheduler.maximumRequestsPerServer = originalMaximumRequestsPerServer; }); - it('throttleRequest throws when request is undefined', function() { + it('schedule throws when request is undefined', function() { expect(function() { - RequestScheduler.throttleRequest(); + RequestScheduler.schedule(); }).toThrowDeveloperError(); }); - it('throttleRequest throws when request.url is undefined', function() { + it('schedule throws when request.url is undefined', function() { expect(function() { - RequestScheduler.throttleRequest(new Request({ + RequestScheduler.schedule(new Request({ requestFunction : function(url) { return undefined; } @@ -40,9 +40,9 @@ defineSuite([ }).toThrowDeveloperError(); }); - it('throttleRequest throws when request.requestFunction is undefined', function() { + it('schedule throws when request.requestFunction is undefined', function() { expect(function() { - RequestScheduler.throttleRequest(new Request({ + RequestScheduler.schedule(new Request({ url : 'file/path' })); }).toThrowDeveloperError(); @@ -64,9 +64,9 @@ defineSuite([ requestFunction : requestFunction }); - var promise1 = RequestScheduler.throttleRequest(request); - var promise2 = RequestScheduler.throttleRequest(request); - var promise3 = RequestScheduler.throttleRequest(request); + var promise1 = RequestScheduler.schedule(request); + var promise2 = RequestScheduler.schedule(request); + var promise3 = RequestScheduler.schedule(request); expect(deferreds.length).toBe(2); expect(promise1).toBeDefined(); @@ -75,16 +75,16 @@ defineSuite([ deferreds[0].resolve(); - var promise4 = RequestScheduler.throttleRequest(request); + var promise4 = RequestScheduler.schedule(request); expect(deferreds.length).toBe(3); expect(promise4).toBeDefined(); - var promise5 = RequestScheduler.throttleRequest(request); + var promise5 = RequestScheduler.schedule(request); expect(deferreds.length).toBe(3); expect(promise5).not.toBeDefined(); RequestScheduler.maximumRequests = 3; - var promise6 = RequestScheduler.throttleRequest(request); + var promise6 = RequestScheduler.schedule(request); expect(deferreds.length).toBe(4); expect(promise6).toBeDefined(); @@ -109,9 +109,9 @@ defineSuite([ requestFunction : requestFunction }); - var promise1 = RequestScheduler.throttleRequest(request); - var promise2 = RequestScheduler.throttleRequest(request); - var promise3 = RequestScheduler.throttleRequest(request); + var promise1 = RequestScheduler.schedule(request); + var promise2 = RequestScheduler.schedule(request); + var promise3 = RequestScheduler.schedule(request); expect(deferreds.length).toBe(2); expect(promise1).toBeDefined(); @@ -120,16 +120,16 @@ defineSuite([ deferreds[0].resolve(); - var promise4 = RequestScheduler.throttleRequest(request); + var promise4 = RequestScheduler.schedule(request); expect(deferreds.length).toBe(3); expect(promise4).toBeDefined(); - var promise5 = RequestScheduler.throttleRequest(request); + var promise5 = RequestScheduler.schedule(request); expect(deferreds.length).toBe(3); expect(promise5).not.toBeDefined(); RequestScheduler.maximumRequestsPerServer = 3; - var promise6 = RequestScheduler.throttleRequest(request); + var promise6 = RequestScheduler.schedule(request); expect(deferreds.length).toBe(4); expect(promise6).toBeDefined(); @@ -152,9 +152,9 @@ defineSuite([ requestFunction : requestFunction }); - RequestScheduler.throttleRequest(request); - RequestScheduler.throttleRequest(request); - RequestScheduler.throttleRequest(request); + RequestScheduler.schedule(request); + RequestScheduler.schedule(request); + RequestScheduler.schedule(request); expect(RequestScheduler.maximumRequests).toBe(10); expect(RequestScheduler.getNumberOfAvailableRequests()).toBe(7); @@ -185,9 +185,9 @@ defineSuite([ requestFunction : requestFunction }); - RequestScheduler.throttleRequest(requestFoo); - RequestScheduler.throttleRequest(requestFoo); - RequestScheduler.throttleRequest(requestBar); + RequestScheduler.schedule(requestFoo); + RequestScheduler.schedule(requestFoo); + RequestScheduler.schedule(requestBar); expect(RequestScheduler.maximumRequestsPerServer).toBe(6); expect(RequestScheduler.getNumberOfAvailableRequestsByServer('http://foo.com')).toBe(4); @@ -201,23 +201,23 @@ defineSuite([ expect(RequestScheduler.getNumberOfAvailableRequestsByServer('http://bar.com')).toBe(6); }); - it('getServer with https', function() { - var server = RequestScheduler.getServer('https://foo.com/1'); + it('getServerName with https', function() { + var server = RequestScheduler.getServerName('https://foo.com/1'); expect(server).toEqual('foo.com:443'); }); - it('getServer with http', function() { - var server = RequestScheduler.getServer('http://foo.com/1'); + it('getServerName with http', function() { + var server = RequestScheduler.getServerName('http://foo.com/1'); expect(server).toEqual('foo.com:80'); }); - it('getServer throws if url is undefined', function() { + it('getServerName throws if url is undefined', function() { expect(function() { - return RequestScheduler.getServer(); + return RequestScheduler.getServerName(); }).toThrowDeveloperError(); }); - it('hasAvailableRequests', function() { + it('hasAvailableRequests and hasAvailableRequestsByServer', function() { RequestScheduler.maximumRequestsPerServer = 2; RequestScheduler.maximumRequests = 3; @@ -239,13 +239,13 @@ defineSuite([ requestFunction : requestFunction }); - RequestScheduler.throttleRequest(requestFoo); - expect(RequestScheduler.hasAvailableRequests('http://foo.com')).toEqual(true); - RequestScheduler.throttleRequest(requestFoo); - expect(RequestScheduler.hasAvailableRequests('http://foo.com')).toEqual(false); + RequestScheduler.schedule(requestFoo); + expect(RequestScheduler.hasAvailableRequestsByServer('http://foo.com')).toEqual(true); + RequestScheduler.schedule(requestFoo); + expect(RequestScheduler.hasAvailableRequestsByServer('http://foo.com')).toEqual(false); expect(RequestScheduler.hasAvailableRequests()).toEqual(true); - RequestScheduler.throttleRequest(requestBar); + RequestScheduler.schedule(requestBar); expect(RequestScheduler.hasAvailableRequests()).toEqual(false); expect(RequestScheduler.getNumberOfAvailableRequests()).toEqual(0); @@ -277,14 +277,14 @@ defineSuite([ defer : true }); - RequestScheduler.throttleRequest(request); - RequestScheduler.throttleRequest(request); - RequestScheduler.throttleRequest(request); + RequestScheduler.schedule(request); + RequestScheduler.schedule(request); + RequestScheduler.schedule(request); expect(RequestScheduler.hasAvailableRequests()).toEqual(false); // A deferred request will always return a promise, however its // requestFunction is not called until there is an open slot - var deferredPromise = RequestScheduler.throttleRequest(requestDeferred); + var deferredPromise = RequestScheduler.schedule(requestDeferred); expect(deferredPromise).toBeDefined(); expect(deferreds[3]).not.toBeDefined(); @@ -340,28 +340,28 @@ defineSuite([ var terrainRequest1 = new Request({ url : 'http://foo.com/1', - requestType : RequestType.TERRAIN, + type : RequestType.TERRAIN, requestFunction : requestFunction, distance : 10.0 }); var terrainRequest2 = new Request({ url : 'http://foo.com/2', - requestType : RequestType.TERRAIN, + type : RequestType.TERRAIN, requestFunction : requestFunction, distance : 20.0 }); var imageryRequest = new Request({ url : 'http://bar.com/1', - requestType : RequestType.IMAGERY, + type : RequestType.IMAGERY, requestFunction : requestFunction, distance : 15.0 }); - var promise1 = RequestScheduler.throttleRequest(terrainRequest1); - var promise2 = RequestScheduler.throttleRequest(terrainRequest2); - var promise3 = RequestScheduler.throttleRequest(imageryRequest); + var promise1 = RequestScheduler.schedule(terrainRequest1); + var promise2 = RequestScheduler.schedule(terrainRequest2); + var promise3 = RequestScheduler.schedule(imageryRequest); // The requests should all return undefined because the budgets haven't been created yet expect(promise1).not.toBeDefined(); @@ -371,9 +371,9 @@ defineSuite([ // Budgets should now allow one terrain request and one imagery request (based on their distances) RequestScheduler.resetBudgets(); - promise1 = RequestScheduler.throttleRequest(terrainRequest1); - promise2 = RequestScheduler.throttleRequest(terrainRequest2); - promise3 = RequestScheduler.throttleRequest(imageryRequest); + promise1 = RequestScheduler.schedule(terrainRequest1); + promise2 = RequestScheduler.schedule(terrainRequest2); + promise3 = RequestScheduler.schedule(imageryRequest); expect(promise1).toBeDefined(); expect(promise2).not.toBeDefined(); @@ -403,9 +403,9 @@ defineSuite([ requestFunction : requestFunction }); - var promise1 = RequestScheduler.throttleRequest(request); - var promise2 = RequestScheduler.throttleRequest(request); - var promise3 = RequestScheduler.throttleRequest(request); + var promise1 = RequestScheduler.schedule(request); + var promise2 = RequestScheduler.schedule(request); + var promise3 = RequestScheduler.schedule(request); // All requests are passed through to the browser expect(promise1).toBeDefined(); @@ -436,12 +436,12 @@ defineSuite([ }); RequestScheduler.debugShowStatistics = false; - RequestScheduler.throttleRequest(request); + RequestScheduler.schedule(request); RequestScheduler.resetBudgets(); expect(console.log).not.toHaveBeenCalled(); RequestScheduler.debugShowStatistics = true; - RequestScheduler.throttleRequest(request); + RequestScheduler.schedule(request); RequestScheduler.resetBudgets(); expect(console.log).toHaveBeenCalled(); diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index ce5c8d05e421..f4ec0ef083f5 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -670,7 +670,7 @@ defineSuite([ // and the second tileset to use the remaining two expect(tilesets[0]._statistics.numberOfPendingRequests).toEqual(4); expect(tilesets[1]._statistics.numberOfPendingRequests).toEqual(2); - expect(RequestScheduler.hasAvailableRequests(tilesets[0]._url)).toEqual(false); + expect(RequestScheduler.hasAvailableRequestsByServer(tilesets[0]._url)).toEqual(false); }); }); diff --git a/Specs/Scene/GlobeSurfaceTileSpec.js b/Specs/Scene/GlobeSurfaceTileSpec.js index 53ef7f32fd92..f71df0e9b032 100644 --- a/Specs/Scene/GlobeSurfaceTileSpec.js +++ b/Specs/Scene/GlobeSurfaceTileSpec.js @@ -99,7 +99,7 @@ defineSuite([ }); beforeEach(function() { - tilingScheme = new WebMercatorTilingScheme(); + tilingScheme = new GeographicTilingScheme(); alwaysDeferTerrainProvider.tilingScheme = tilingScheme; alwaysFailTerrainProvider.tilingScheme = tilingScheme; rootTiles = QuadtreeTile.createLevelZeroTiles(tilingScheme); From c3f032609f5706e06cc86f173d6ccdd80d25a7ef Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 20 Jan 2016 17:33:33 -0500 Subject: [PATCH 08/11] RequestScheduler cleanup --- CHANGES.md | 2 +- .../Core/ArcGisImageServerTerrainProvider.js | 1 - Source/Core/CesiumTerrainProvider.js | 1 - Source/Core/Request.js | 12 --------- Source/Core/RequestScheduler.js | 27 ++++++++----------- Source/Core/VRTheWorldTerrainProvider.js | 1 - Source/Scene/Cesium3DTile.js | 11 ++++++++ Source/Scene/Cesium3DTileset.js | 10 +++---- 8 files changed, 26 insertions(+), 39 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5cfffbd20f3f..3a8693841f02 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,7 +11,7 @@ Change Log * Breaking changes * Removed `OpenStreetMapImageryProvider`. Use `createOpenStreetMapImageryProvider` instead. * Deprecated - * Deprecated `throttleRequests` flag in `TerrainProvider`. It is replaced by an optional `Request` object that stores information used to prioritize requests. + * Deprecated `throttleRequests` flag in `TerrainProvider`. It will be removed in 1.19. It is replaced by an optional `Request` object that stores information used to prioritize requests. * Reduced the amount of CPU memory used by terrain by ~25% in Chrome. * Fixed a picking problem ([#3386](https://github.com/AnalyticalGraphicsInc/cesium/issues/3386)) that sometimes prevented objects being selected. * Added `Scene.useDepthPicking` to enable or disable picking using the depth buffer. [#3390](https://github.com/AnalyticalGraphicsInc/cesium/pull/3390) diff --git a/Source/Core/ArcGisImageServerTerrainProvider.js b/Source/Core/ArcGisImageServerTerrainProvider.js index 49b7aa60dde6..162a4084976c 100644 --- a/Source/Core/ArcGisImageServerTerrainProvider.js +++ b/Source/Core/ArcGisImageServerTerrainProvider.js @@ -237,7 +237,6 @@ define([ url = proxy.getURL(url); } - // TODO : request used to be a boolean called throttleRequest. Continue to handle that case until it is deprecated. if (!defined(request) || (request === false)) { // If a request object isn't provided, perform an immediate request request = new Request({ diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 99909f8ce8b6..8cf3b10369a0 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -522,7 +522,6 @@ define([ return loadArrayBuffer(tileUrl, getRequestHeader(extensionList)); } - // TODO : request used to be a boolean called throttleRequest. Continue to handle that case until it is deprecated. if (!defined(request) || (request === false)) { // If a request object isn't provided, perform an immediate request request = new Request({ diff --git a/Source/Core/Request.js b/Source/Core/Request.js index 85f3a1348064..2a4d50503448 100644 --- a/Source/Core/Request.js +++ b/Source/Core/Request.js @@ -17,29 +17,21 @@ define([ /** * The URL to request. - * - * @private */ this.url = options.url; /** * Extra parameters to send with the request. For example, HTTP headers or jsonp parameters. - * - * @private */ this.parameters = options.parameters; /** * The actual function that makes the request. - * - * @private */ this.requestFunction = options.requestFunction; /** * Type of request. Used for more fine-grained priority sorting. - * - * @private */ this.type = options.type; @@ -47,15 +39,11 @@ define([ * Specifies that the request should be deferred until an open slot is available. * A deferred request will always return a promise, which is suitable for data * sources and utility functions. - * - * @private */ this.defer = defaultValue(options.defer, false); /** * The distance from the camera, used to prioritize requests. - * - * @private */ this.distance = defaultValue(options.distance, 0.0); diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js index c788a713e380..d6a2f4aaa016 100644 --- a/Source/Core/RequestScheduler.js +++ b/Source/Core/RequestScheduler.js @@ -67,7 +67,7 @@ define([ return RequestScheduler.maximumRequestsPerServer - this.activeRequests; }; - var activeRequestsByServer = []; + var activeRequestsByServer = {}; var activeRequests = 0; var budgets = []; var leftoverRequests = []; @@ -162,11 +162,11 @@ define([ var uri = new Uri(url).resolve(pageUri); uri.normalize(); - var server = uri.authority; - if (!/:/.test(server)) { - server = server + ':' + (uri.scheme === 'https' ? '443' : '80'); + var serverName = uri.authority; + if (!/:/.test(serverName)) { + serverName = serverName + ':' + (uri.scheme === 'https' ? '443' : '80'); } - return server; + return serverName; }; /** @@ -176,22 +176,17 @@ define([ * @returns {RequestServer} The request server. */ RequestScheduler.getRequestServer = function(url) { - var requestServer; var serverName = RequestScheduler.getServerName(url); - var length = activeRequestsByServer.length; - for (var i = 0; i < length; ++i) { - requestServer = activeRequestsByServer[i]; - if (requestServer.serverName === serverName) { - return requestServer; - } + var server = activeRequestsByServer[serverName]; + if (!defined(server)) { + server = new RequestServer(serverName); + activeRequestsByServer[serverName] = server; } - requestServer = new RequestServer(serverName); - activeRequestsByServer.push(requestServer); - return requestServer; + return server; }; /** - * Get the number of available slots for the given server. + * Get the number of available slots at the server pointed to by the url. * * @param {String} url The url to check. * @returns {Number} The number of available slots. diff --git a/Source/Core/VRTheWorldTerrainProvider.js b/Source/Core/VRTheWorldTerrainProvider.js index 8c835ae37237..5bb58c468b52 100644 --- a/Source/Core/VRTheWorldTerrainProvider.js +++ b/Source/Core/VRTheWorldTerrainProvider.js @@ -272,7 +272,6 @@ define([ url = proxy.getURL(url); } - // TODO : request used to be a boolean called throttleRequest. Continue to handle that case until it is deprecated. if (!defined(request) || (request === false)) { // If a request object isn't provided, perform an immediate request request = new Request({ diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index 61141c5f3d12..ba1346427ef4 100644 --- a/Source/Scene/Cesium3DTile.js +++ b/Source/Scene/Cesium3DTile.js @@ -299,6 +299,17 @@ define([ this._content.request(); }; + /** + * DOC_TBA + */ + Cesium3DTile.prototype.canRequestContent = function() { + if (!defined(this._requestServer)) { + // If tile does not have a request server, then it does not have content to load. + return true; + } + return this._requestServer.hasAvailableRequests(); + }; + /** * DOC_TBA */ diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 68839dc92d71..8612ab12c728 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -354,7 +354,7 @@ define([ if (!outOfCore) { return; } - if (!hasAvailableRequests(tile)) { + if (!tile.canRequestContent()) { return; } @@ -371,10 +371,6 @@ define([ } } - function hasAvailableRequests(tile) { - return !defined(tile._requestServer) || tile._requestServer.hasAvailableRequests(); - } - function selectTile(selectedTiles, tile, fullyVisible, frameState) { // There may also be a tight box around just the tile's contents, e.g., for a city, we may be // zoomed into a neighborhood and can cull the skyscrapers in the root node. @@ -469,7 +465,7 @@ define([ // Only sort and refine (render or request children) if any // children are loaded or request slots are available. var anyChildrenLoaded = (t.numberOfChildrenWithoutContent < childrenLength); - if (anyChildrenLoaded || hasAvailableRequests(t)) { + if (anyChildrenLoaded || t.canRequestContent()) { // Distance is used for sorting now and for computing SSE when the tile comes off the stack. computeDistanceToCamera(children, frameState); @@ -519,7 +515,7 @@ define([ // tile (and can't make child requests because no slots are available) // then the children do not need to be sorted. var allChildrenLoaded = t.numberOfChildrenWithoutContent === 0; - if (allChildrenLoaded || hasAvailableRequests(t)) { + if (allChildrenLoaded || t.canRequestContent()) { // Distance is used for sorting now and for computing SSE when the tile comes off the stack. computeDistanceToCamera(children, frameState); From 5b4bb9a7e9643567b17050ff1c534f58c0ab9b70 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 21 Jan 2016 09:10:38 -0500 Subject: [PATCH 09/11] Added asset property to other test tilesets --- .../Data/Cesium3DTiles/Batched/BatchedNoBuildings/tileset.json | 3 +++ .../Batched/BatchedWithoutBatchTable/tileset.json | 3 +++ Specs/Data/Cesium3DTiles/Composite/Composite/tileset.json | 3 +++ .../Cesium3DTiles/Composite/CompositeOfComposite/tileset.json | 3 +++ .../Cesium3DTiles/Instanced/InstancedGltfEmbedded/tileset.json | 3 +++ .../Cesium3DTiles/Instanced/InstancedGltfExternal/tileset.json | 3 +++ .../Instanced/InstancedWithBatchTable/tileset.json | 3 +++ .../Instanced/InstancedWithoutBatchTable/tileset.json | 3 +++ Specs/Data/Cesium3DTiles/Points/Points/tileset.json | 3 +++ 9 files changed, 27 insertions(+) diff --git a/Specs/Data/Cesium3DTiles/Batched/BatchedNoBuildings/tileset.json b/Specs/Data/Cesium3DTiles/Batched/BatchedNoBuildings/tileset.json index 10b2438cc0ef..6024669ece55 100644 --- a/Specs/Data/Cesium3DTiles/Batched/BatchedNoBuildings/tileset.json +++ b/Specs/Data/Cesium3DTiles/Batched/BatchedNoBuildings/tileset.json @@ -1,4 +1,7 @@ { + "asset" : { + "version": "0.0" + }, "properties": { "id": { "minimum": 0, diff --git a/Specs/Data/Cesium3DTiles/Batched/BatchedWithoutBatchTable/tileset.json b/Specs/Data/Cesium3DTiles/Batched/BatchedWithoutBatchTable/tileset.json index b53af38b2f34..ca63be5e907d 100644 --- a/Specs/Data/Cesium3DTiles/Batched/BatchedWithoutBatchTable/tileset.json +++ b/Specs/Data/Cesium3DTiles/Batched/BatchedWithoutBatchTable/tileset.json @@ -1,4 +1,7 @@ { + "asset" : { + "version": "0.0" + }, "properties": { "id": { "minimum": 0, diff --git a/Specs/Data/Cesium3DTiles/Composite/Composite/tileset.json b/Specs/Data/Cesium3DTiles/Composite/Composite/tileset.json index 168e9cdeb172..4eabeb279524 100644 --- a/Specs/Data/Cesium3DTiles/Composite/Composite/tileset.json +++ b/Specs/Data/Cesium3DTiles/Composite/Composite/tileset.json @@ -1,4 +1,7 @@ { + "asset" : { + "version": "0.0" + }, "properties": { "id": { "minimum": 0, diff --git a/Specs/Data/Cesium3DTiles/Composite/CompositeOfComposite/tileset.json b/Specs/Data/Cesium3DTiles/Composite/CompositeOfComposite/tileset.json index af33227b6908..c6507864f884 100644 --- a/Specs/Data/Cesium3DTiles/Composite/CompositeOfComposite/tileset.json +++ b/Specs/Data/Cesium3DTiles/Composite/CompositeOfComposite/tileset.json @@ -1,4 +1,7 @@ { + "asset" : { + "version": "0.0" + }, "properties": { "id": { "minimum": 0, diff --git a/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfEmbedded/tileset.json b/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfEmbedded/tileset.json index d6622665c9e6..26422d2fc53f 100644 --- a/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfEmbedded/tileset.json +++ b/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfEmbedded/tileset.json @@ -1,4 +1,7 @@ { + "asset" : { + "version": "0.0" + }, "properties": { "id": { "minimum": 0, diff --git a/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/tileset.json b/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/tileset.json index 7136029e8f02..212a31a238ac 100644 --- a/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/tileset.json +++ b/Specs/Data/Cesium3DTiles/Instanced/InstancedGltfExternal/tileset.json @@ -1,4 +1,7 @@ { + "asset" : { + "version": "0.0" + }, "properties": { "id": { "minimum": 0, diff --git a/Specs/Data/Cesium3DTiles/Instanced/InstancedWithBatchTable/tileset.json b/Specs/Data/Cesium3DTiles/Instanced/InstancedWithBatchTable/tileset.json index 12a0d3e773e6..f9f82dcc065e 100644 --- a/Specs/Data/Cesium3DTiles/Instanced/InstancedWithBatchTable/tileset.json +++ b/Specs/Data/Cesium3DTiles/Instanced/InstancedWithBatchTable/tileset.json @@ -1,4 +1,7 @@ { + "asset" : { + "version": "0.0" + }, "properties": { "id": { "minimum": 0, diff --git a/Specs/Data/Cesium3DTiles/Instanced/InstancedWithoutBatchTable/tileset.json b/Specs/Data/Cesium3DTiles/Instanced/InstancedWithoutBatchTable/tileset.json index e5a1b1c09725..cc90becd8897 100644 --- a/Specs/Data/Cesium3DTiles/Instanced/InstancedWithoutBatchTable/tileset.json +++ b/Specs/Data/Cesium3DTiles/Instanced/InstancedWithoutBatchTable/tileset.json @@ -1,4 +1,7 @@ { + "asset" : { + "version": "0.0" + }, "properties": { "id": { "minimum": 0, diff --git a/Specs/Data/Cesium3DTiles/Points/Points/tileset.json b/Specs/Data/Cesium3DTiles/Points/Points/tileset.json index 91d7aaa4c6cb..f2257c12a875 100644 --- a/Specs/Data/Cesium3DTiles/Points/Points/tileset.json +++ b/Specs/Data/Cesium3DTiles/Points/Points/tileset.json @@ -1,4 +1,7 @@ { + "asset": { + "version": "0.0" + }, "geometricError": 346.4, "root": { "boundingVolume": { From 0d94b53898b7d2c5975b7c482e5ee0ac1f673ee9 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 21 Jan 2016 09:11:44 -0500 Subject: [PATCH 10/11] private getter for requestServer --- Source/Scene/Batched3DModel3DTileContentProvider.js | 2 +- Source/Scene/Cesium3DTile.js | 12 ++++++++++++ Source/Scene/Composite3DTileContentProvider.js | 2 +- .../Scene/Instanced3DModel3DTileContentProvider.js | 2 +- Source/Scene/Points3DTileContentProvider.js | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContentProvider.js b/Source/Scene/Batched3DModel3DTileContentProvider.js index 1e98e1b25d0b..96ccf1787418 100644 --- a/Source/Scene/Batched3DModel3DTileContentProvider.js +++ b/Source/Scene/Batched3DModel3DTileContentProvider.js @@ -129,7 +129,7 @@ define([ var distance = this._tile.distanceToCamera; var promise = RequestScheduler.schedule(new Request({ url : this._url, - server : this._tile._requestServer, + server : this._tile.requestServer, requestFunction : loadArrayBuffer, type : RequestType.TILES3D, distance : distance diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index ba1346427ef4..24b9bc64ed37 100644 --- a/Source/Scene/Cesium3DTile.js +++ b/Source/Scene/Cesium3DTile.js @@ -268,6 +268,18 @@ define([ get : function() { return this._content.processingPromise; } + }, + + /** + * DOC_TBA + * + * @type {RequestScheduler~RequestServer} + * @readonly + */ + requestServer : { + get : function() { + return this._requestServer; + } } }); diff --git a/Source/Scene/Composite3DTileContentProvider.js b/Source/Scene/Composite3DTileContentProvider.js index 31e773e56717..48616066131b 100644 --- a/Source/Scene/Composite3DTileContentProvider.js +++ b/Source/Scene/Composite3DTileContentProvider.js @@ -64,7 +64,7 @@ define([ var distance = this._tile.distanceToCamera; var promise = RequestScheduler.schedule(new Request({ url : this._url, - server : this._tile._requestServer, + server : this._tile.requestServer, requestFunction : loadArrayBuffer, type : RequestType.TILES3D, distance : distance diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index a72ee814277a..fbd84f381c80 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -139,7 +139,7 @@ define([ var distance = this._tile.distanceToCamera; var promise = RequestScheduler.schedule(new Request({ url : this._url, - server : this._tile._requestServer, + server : this._tile.requestServer, requestFunction : loadArrayBuffer, type : RequestType.TILES3D, distance : distance diff --git a/Source/Scene/Points3DTileContentProvider.js b/Source/Scene/Points3DTileContentProvider.js index c66e8dafee47..8b6a9d2b933f 100644 --- a/Source/Scene/Points3DTileContentProvider.js +++ b/Source/Scene/Points3DTileContentProvider.js @@ -91,7 +91,7 @@ define([ var distance = this._tile.distanceToCamera; var promise = RequestScheduler.schedule(new Request({ url : this._url, - server : this._tile._requestServer, + server : this._tile.requestServer, requestFunction : loadArrayBuffer, type : RequestType.TILES3D, distance : distance From afdebab8addf171be10bef391cfbd24d8cdfa9bf Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 21 Jan 2016 10:39:29 -0500 Subject: [PATCH 11/11] Added baseUrl to Cesium3DTileset to fix loading issues --- Source/Scene/Cesium3DTileset.js | 17 ++++++++++++++++- .../Instanced3DModel3DTileContentProvider.js | 2 +- Specs/Cesium3DTilesTester.js | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 8612ab12c728..c63c23168507 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -91,6 +91,7 @@ define([ } this._url = url; + this._baseUrl = baseUrl; this._tilesetUrl = tilesetUrl; this._state = Cesium3DTilesetState.UNLOADED; this._root = undefined; @@ -249,6 +250,20 @@ define([ get : function() { return this._url; } + }, + + /** + * DOC_TBA + * + * @memberof Cesium3DTileset.prototype + * + * @type {String} + * @readonly + */ + baseUrl : { + get : function() { + return this._baseUrl; + } } }); @@ -278,7 +293,7 @@ define([ return when.reject('The tileset must be 3D Tiles version 0.0. See https://github.com/AnalyticalGraphicsInc/3d-tiles#spec-status'); } - var baseUrl = tileset.url; + var baseUrl = tileset._baseUrl; var rootTile = new Cesium3DTile(tileset, baseUrl, tilesetJson.root, parentTile); // If there is a parentTile, add the root of the currently loading tileset diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index fbd84f381c80..5319c5aadb64 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -238,7 +238,7 @@ define([ if (gltfFormat === 0) { var gltfUrl = getStringFromTypedArray(gltfView); - collectionOptions.url = getAbsoluteUri(gltfUrl, this._tileset.url); + collectionOptions.url = getAbsoluteUri(gltfUrl, this._tileset.baseUrl); } else { collectionOptions.gltf = gltfView; collectionOptions.basePath = this._url; diff --git a/Specs/Cesium3DTilesTester.js b/Specs/Cesium3DTilesTester.js index 0e369b5f6dff..256dbf7d40a5 100644 --- a/Specs/Cesium3DTilesTester.js +++ b/Specs/Cesium3DTilesTester.js @@ -103,7 +103,7 @@ define([ var counter = 0; Cesium3DTilesTester.rejectsReadyPromiseOnError = function(scene, arrayBuffer, type) { var tileset = { - url : counter++ + baseUrl : counter++ }; var tile = { contentBoundingVolume : new TileBoundingSphere()