diff --git a/CHANGES.md b/CHANGES.md index ec40f038cd30..3a8693841f02 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 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 3f6572957844..162a4084976c 100644 --- a/Source/Core/ArcGisImageServerTerrainProvider.js +++ b/Source/Core/ArcGisImageServerTerrainProvider.js @@ -13,7 +13,9 @@ define([ './HeightmapTerrainData', './loadImage', './Math', + './Request', './RequestScheduler', + './RequestType', './TerrainProvider' ], function( when, @@ -29,7 +31,9 @@ define([ HeightmapTerrainData, loadImage, CesiumMath, + Request, RequestScheduler, + RequestType, TerrainProvider) { "use strict"; @@ -200,11 +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 {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) { + 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 @@ -231,7 +237,18 @@ define([ url = proxy.getURL(url); } - var promise = RequestScheduler.throttleRequest(url, loadImage); + if (!defined(request) || (request === false)) { + // If a request object isn't provided, perform an immediate request + request = new Request({ + defer : true + }); + } + + request.url = url; + request.requestFunction = loadImage; + request.type = RequestType.TERRAIN; + + var promise = RequestScheduler.schedule(request); if (!defined(promise)) { return undefined; } diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js index 6b04ad56f4d8..8cf3b10369a0 100644 --- a/Source/Core/CesiumTerrainProvider.js +++ b/Source/Core/CesiumTerrainProvider.js @@ -20,7 +20,9 @@ define([ './Matrix3', './OrientedBoundingBox', './QuantizedMeshTerrainData', + './Request', './RequestScheduler', + './RequestType', './RuntimeError', './TerrainProvider', './TileProviderError' @@ -45,7 +47,9 @@ define([ Matrix3, OrientedBoundingBox, QuantizedMeshTerrainData, + Request, RequestScheduler, + RequestType, RuntimeError, TerrainProvider, TileProviderError) { @@ -246,7 +250,7 @@ define([ } function requestMetadata() { - var metadata = loadJson(metadataUrl); + var metadata = RequestScheduler.request(metadataUrl, loadJson); when(metadata, metadataSuccess, metadataFailure); } @@ -474,9 +478,8 @@ 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 {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. @@ -484,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) { + 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.'); @@ -507,8 +510,6 @@ define([ url = proxy.getURL(url); } - var promise; - var extensionList = []; if (this._requestVertexNormals && this._hasVertexNormals) { extensionList.push(this._littleEndianExtensionSize ? "octvertexnormals" : "vertexnormals"); @@ -520,14 +521,21 @@ define([ function tileLoader(tileUrl) { return loadArrayBuffer(tileUrl, getRequestHeader(extensionList)); } - throttleRequests = defaultValue(throttleRequests, true); - if (throttleRequests) { - promise = RequestScheduler.throttleRequest(url, tileLoader); - if (!defined(promise)) { - return undefined; - } - } else { - promise = tileLoader(url); + + if (!defined(request) || (request === false)) { + // If a request object isn't provided, perform an immediate request + request = new Request({ + defer : true + }); + } + + request.url = url; + request.requestFunction = tileLoader; + request.type = RequestType.TERRAIN; + + var promise = RequestScheduler.schedule(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 e18a125c9b09..a695677ff34e 100644 --- a/Source/Core/EllipsoidTerrainProvider.js +++ b/Source/Core/EllipsoidTerrainProvider.js @@ -160,14 +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 {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) { + 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..2a4d50503448 --- /dev/null +++ b/Source/Core/Request.js @@ -0,0 +1,68 @@ +/*global define*/ +define([ + './defaultValue' + ], function( + defaultValue) { + "use strict"; + + /** + * Stores information for making a request using {@link 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.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. + */ + 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. + * + * @private + */ + this.startPromise = undefined; + + /** + * Reference to a {@link RequestScheduler~RequestServer}. + * + * @private + */ + this.server = options.server; + } + + return Request; +}); diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js index 0079f3e0d295..d6a2f4aaa016 100644 --- a/Source/Core/RequestScheduler.js +++ b/Source/Core/RequestScheduler.js @@ -5,18 +5,77 @@ define([ './defaultValue', './defined', './defineProperties', - './DeveloperError' + './DeveloperError', + './Queue', + './Request', + './RequestType' ], function( Uri, when, defaultValue, defined, defineProperties, - DeveloperError) { + DeveloperError, + Queue, + Request, + RequestType) { "use strict"; + function RequestBudget(request) { + /** + * Total requests allowed this frame. + */ + this.total = 0; + + /** + * Total requests used this frame. + */ + this.used = 0; + + /** + * Server of the request. + */ + this.server = request.server; + + /** + * Type of request. Used for more fine-grained priority sorting. + */ + this.type = request.type; + } + + /** + * 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 = []; + var deferredRequests = new Queue(); + + var stats = { + numberOfRequestsThisFrame : 0 + }; /** * Because browsers throttle the number of parallel requests allowed to each server @@ -32,6 +91,60 @@ define([ function RequestScheduler() { } + function distanceSortFunction(a, b) { + return a.distance - b.distance; + } + + 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.type === request.type)) { + return budget; + } + } + // Not found, create a new budget + budget = new RequestBudget(request); + budgets.push(budget); + return budget; + } + + RequestScheduler.resetBudgets = function() { + showStats(); + clearStats(); + + if (!RequestScheduler.prioritize || !RequestScheduler.throttle) { + return; + } + + // Reset budget totals + var length = budgets.length; + for (var i = 0; i < length; ++i) { + budgets[i].total = 0; + budgets[i].used = 0; + } + + // Sort all leftover requests 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 budget = getBudget(request); + var budgetAvailable = budget.server.getNumberOfAvailableRequests(); + if (budget.total < budgetAvailable) { + ++budget.total; + --availableRequests; + } + } + + requests.length = 0; + }; + var pageUri = typeof document !== 'undefined' ? new Uri(document.location.href) : new Uri(); /** @@ -40,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.'); @@ -49,23 +162,37 @@ 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 serverName; + }; + + /** + * Get the request server from a given url. + * + * @param {String} url The url. + * @returns {RequestServer} The request server. + */ + RequestScheduler.getRequestServer = function(url) { + var serverName = RequestScheduler.getServerName(url); + var server = activeRequestsByServer[serverName]; + if (!defined(server)) { + server = new RequestServer(serverName); + activeRequestsByServer[serverName] = server; } 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. */ RequestScheduler.getNumberOfAvailableRequestsByServer = function(url) { - var server = RequestScheduler.getServer(url); - var activeRequestsForServer = defaultValue(activeRequestsByServer[server], 0); - return RequestScheduler.maximumRequestsPerServer - activeRequestsForServer; + return RequestScheduler.getRequestServer(url).getNumberOfAvailableRequests(); }; /** @@ -78,37 +205,70 @@ 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; - } + RequestScheduler.hasAvailableRequestsByServer = function(url) { + return RequestScheduler.getRequestServer(url).hasAvailableRequests(); + }; - if (defined(url)) { - var server = RequestScheduler.getServer(url); - var activeRequestsForServer = defaultValue(activeRequestsByServer[server], 0); - if (activeRequestsForServer >= RequestScheduler.maximumRequestsPerServer) { - return false; - } + /** + * 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; + --request.server.activeRequests; + + // Start a deferred request immediately now that a slot is open + var deferredRequest = deferredRequests.dequeue(); + if (defined(deferredRequest)) { + deferredRequest.startPromise.resolve(deferredRequest); } + } - return true; - }; + function startRequest(request) { + ++activeRequests; + ++request.server.activeRequests; + + 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 {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. * @@ -119,7 +279,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.schedule(request); * if (!Cesium.defined(promise)) { * // too many active requests in progress, try again later. * } else { @@ -129,41 +293,95 @@ define([ * } * */ - RequestScheduler.throttleRequest = function(url, requestFunction) { + RequestScheduler.schedule = 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'); - if (activeRequests >= RequestScheduler.maximumRequests) { - return undefined; + ++stats.numberOfRequestsThisFrame; + + 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) { - return undefined; + if (!defined(request.server)) { + request.server = RequestScheduler.getRequestServer(request.url); } - ++activeRequests; - activeRequestsByServer[server] = activeRequestsForServer + 1; + if (!request.server.hasAvailableRequests()) { + 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); + } + } - return when(requestFunction(url), function(result) { - --activeRequests; - --activeRequestsByServer[server]; - return result; - }).otherwise(function(error) { - --activeRequests; - --activeRequestsByServer[server]; - return when.reject(error); - }); + 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 + handleLeftoverRequest(request); + return undefined; + } + ++budget.used; + } + + return startRequest(request); }; + /** + * 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. + * @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, requestType) { + return RequestScheduler.schedule(new Request({ + url : url, + parameters : parameters, + requestFunction : requestFunction, + defer : true, + type : defaultValue(requestType, RequestType.OTHER) + })); + }; + + 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 @@ -182,5 +400,26 @@ define([ */ RequestScheduler.maximumRequests = 10; + /** + * Specifies if the request scheduler should prioritize incoming requests + * @type {Boolean} + * @default true + */ + 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 false + */ + RequestScheduler.debugShowStatistics = false; + return RequestScheduler; }); diff --git a/Source/Core/RequestType.js b/Source/Core/RequestType.js new file mode 100644 index 000000000000..195be9693ce3 --- /dev/null +++ b/Source/Core/RequestType.js @@ -0,0 +1,19 @@ +/*global define*/ +define([ + '../Core/freezeObject' + ], function( + freezeObject) { + "use strict"; + + /** + * @private + */ + var RequestType = { + TERRAIN : 0, + IMAGERY : 1, + TILES3D : 2, + OTHER : 3 + }; + + return freezeObject(RequestType); +}); diff --git a/Source/Core/TerrainProvider.js b/Source/Core/TerrainProvider.js index 2520a78e408f..95b3a1a795e1 100644 --- a/Source/Core/TerrainProvider.js +++ b/Source/Core/TerrainProvider.js @@ -186,9 +186,8 @@ 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 {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. diff --git a/Source/Core/VRTheWorldTerrainProvider.js b/Source/Core/VRTheWorldTerrainProvider.js index da7dc915a12e..5bb58c468b52 100644 --- a/Source/Core/VRTheWorldTerrainProvider.js +++ b/Source/Core/VRTheWorldTerrainProvider.js @@ -15,7 +15,9 @@ define([ './loadXML', './Math', './Rectangle', + './Request', './RequestScheduler', + './RequestType', './TerrainProvider', './TileProviderError' ], function( @@ -34,7 +36,9 @@ define([ loadXML, CesiumMath, Rectangle, + Request, RequestScheduler, + RequestType, TerrainProvider, TileProviderError) { "use strict"; @@ -144,7 +148,7 @@ define([ } function requestMetadata() { - when(loadXML(that._url), metadataSuccess, metadataFailure); + when(RequestScheduler.request(that._url, loadXML), metadataSuccess, metadataFailure); } requestMetadata(); @@ -250,14 +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 {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) { + VRTheWorldTerrainProvider.prototype.requestTileGeometry = function(x, y, level, request) { if (!this.ready) { throw new DeveloperError('requestTileGeometry must not be called before ready returns true.'); } @@ -270,16 +272,20 @@ define([ url = proxy.getURL(url); } - var promise; + if (!defined(request) || (request === false)) { + // If a request object isn't provided, perform an immediate request + request = new Request({ + defer : true + }); + } - throttleRequests = defaultValue(throttleRequests, true); - if (throttleRequests) { - promise = RequestScheduler.throttleRequest(url, loadImage); - if (!defined(promise)) { - return undefined; - } - } else { - promise = loadImage(url); + request.url = url; + request.requestFunction = loadImage; + request.type = RequestType.TERRAIN; + + var promise = RequestScheduler.schedule(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 d4acb74a8d58..d843e04e3462 100644 --- a/Source/DataSources/CzmlDataSource.js +++ b/Source/DataSources/CzmlDataSource.js @@ -27,6 +27,7 @@ define([ '../Core/Quaternion', '../Core/Rectangle', '../Core/ReferenceFrame', + '../Core/RequestScheduler', '../Core/RuntimeError', '../Core/Spherical', '../Core/TimeInterval', @@ -99,6 +100,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 70aa5f9b23c0..f37f828123f8 100644 --- a/Source/DataSources/KmlDataSource.js +++ b/Source/DataSources/KmlDataSource.js @@ -26,6 +26,7 @@ define([ '../Core/PinBuilder', '../Core/PolygonHierarchy', '../Core/Rectangle', + '../Core/RequestScheduler', '../Core/RuntimeError', '../Core/TimeInterval', '../Core/TimeIntervalCollection', @@ -80,6 +81,7 @@ define([ PinBuilder, PolygonHierarchy, Rectangle, + RequestScheduler, RuntimeError, TimeInterval, TimeIntervalCollection, @@ -735,7 +737,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 6cf1f31dfc46..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 }); @@ -578,6 +580,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 +588,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 +596,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 d92618e9c2ae..96ccf1787418 100644 --- a/Source/Scene/Batched3DModel3DTileContentProvider.js +++ b/Source/Scene/Batched3DModel3DTileContentProvider.js @@ -8,7 +8,9 @@ define([ '../Core/getMagic', '../Core/getStringFromTypedArray', '../Core/loadArrayBuffer', + '../Core/Request', '../Core/RequestScheduler', + '../Core/RequestType', '../ThirdParty/when', './BatchedModel', './Cesium3DTileBatchTableResources', @@ -23,7 +25,9 @@ define([ getMagic, getStringFromTypedArray, loadArrayBuffer, + Request, RequestScheduler, + RequestType, when, BatchedModel, Cesium3DTileBatchTableResources, @@ -38,6 +42,7 @@ define([ this._model = undefined; this._url = url; this._tileset = tileset; + this._tile = tile; /** * @readonly @@ -121,7 +126,14 @@ define([ Batched3DModel3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer); + var distance = this._tile.distanceToCamera; + var promise = RequestScheduler.schedule(new Request({ + url : this._url, + server : this._tile.requestServer, + requestFunction : loadArrayBuffer, + type : 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 b606cee9539b..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 }); @@ -517,6 +519,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 +527,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 +535,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/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js index 6d4354c53cbc..24b9bc64ed37 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; @@ -263,6 +268,18 @@ define([ get : function() { return this._content.processingPromise; } + }, + + /** + * DOC_TBA + * + * @type {RequestScheduler~RequestServer} + * @readonly + */ + requestServer : { + get : function() { + return this._requestServer; + } } }); @@ -294,6 +311,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 34dfe84b62bb..c63c23168507 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -13,7 +13,9 @@ define([ '../Core/isDataUri', '../Core/loadJson', '../Core/Math', + '../Core/Request', '../Core/RequestScheduler', + '../Core/RequestType', '../ThirdParty/Uri', '../ThirdParty/when', './Cesium3DTile', @@ -35,7 +37,9 @@ define([ isDataUri, loadJson, CesiumMath, + Request, RequestScheduler, + RequestType, Uri, when, Cesium3DTile, @@ -87,6 +91,7 @@ define([ } this._url = url; + this._baseUrl = baseUrl; this._tilesetUrl = tilesetUrl; this._state = Cesium3DTilesetState.UNLOADED; this._root = undefined; @@ -245,6 +250,20 @@ define([ get : function() { return this._url; } + }, + + /** + * DOC_TBA + * + * @memberof Cesium3DTileset.prototype + * + * @type {String} + * @readonly + */ + baseUrl : { + get : function() { + return this._baseUrl; + } } }); @@ -253,7 +272,13 @@ define([ */ Cesium3DTileset.prototype.loadTileset = function(tilesetUrl, parentTile) { var tileset = this; - var promise = RequestScheduler.throttleRequest(tilesetUrl, loadJson); + + // We don't know the distance of the tileset until tiles.json is loaded, so use the default distance for now + var promise = RequestScheduler.schedule(new Request({ + url : tilesetUrl, + requestFunction : loadJson, + type : RequestType.TILES3D + })); if (!defined(promise)) { return undefined; @@ -268,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 @@ -344,22 +369,21 @@ define([ if (!outOfCore) { return; } - if (!hasAvailableRequests(tiles3D)) { + if (!tile.canRequestContent()) { 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); - } - function hasAvailableRequests(tiles3D) { - return RequestScheduler.hasAvailableRequests(tiles3D._url); + 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 selectTile(selectedTiles, tile, fullyVisible, frameState) { @@ -456,7 +480,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 || t.canRequestContent()) { // Distance is used for sorting now and for computing SSE when the tile comes off the stack. computeDistanceToCamera(children, frameState); @@ -506,7 +530,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 || t.canRequestContent()) { // 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 dcf09ebae1bd..48616066131b 100644 --- a/Source/Scene/Composite3DTileContentProvider.js +++ b/Source/Scene/Composite3DTileContentProvider.js @@ -6,7 +6,9 @@ define([ '../Core/DeveloperError', '../Core/getMagic', '../Core/loadArrayBuffer', + '../Core/Request', '../Core/RequestScheduler', + '../Core/RequestType', '../ThirdParty/when', './Cesium3DTileContentState' ], function( @@ -16,7 +18,9 @@ define([ DeveloperError, getMagic, loadArrayBuffer, + Request, RequestScheduler, + RequestType, when, Cesium3DTileContentState) { "use strict"; @@ -57,7 +61,14 @@ define([ Composite3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer); + var distance = this._tile.distanceToCamera; + var promise = RequestScheduler.schedule(new Request({ + url : this._url, + server : this._tile.requestServer, + requestFunction : loadArrayBuffer, + type : 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 53f012a85d10..208ecc5fa5f5 100644 --- a/Source/Scene/GlobeSurfaceTile.js +++ b/Source/Scene/GlobeSurfaceTile.js @@ -241,10 +241,29 @@ define([ } }; + function createTileBoundingRegion(tile) { + var minimumHeight; + var maximumHeight; + if (defined(tile.parent) && defined(tile.parent.data)) { + minimumHeight = tile.parent.data.minimumHeight; + maximumHeight = tile.parent.data.maximumHeight; + } + return new TileBoundingRegion({ + 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(); + // Create the TileBoundingRegion 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.tileBoundingRegion = createTileBoundingRegion(tile, frameState); + tile._distance = surfaceTile.tileBoundingRegion.distanceToCamera(frameState); } if (tile.state === QuadtreeTileLoadState.START) { @@ -343,7 +362,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..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); } @@ -536,6 +538,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 +546,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 +554,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 f542f5d26707..9f3fb89d39e6 100644 --- a/Source/Scene/ImageryProvider.js +++ b/Source/Scene/ImageryProvider.js @@ -5,14 +5,18 @@ define([ '../Core/DeveloperError', '../Core/loadImage', '../Core/loadImageViaBlob', - '../Core/RequestScheduler' + '../Core/Request', + '../Core/RequestScheduler', + '../Core/RequestType' ], function( defined, defineProperties, DeveloperError, loadImage, loadImageViaBlob, - RequestScheduler) { + Request, + RequestScheduler, + RequestType) { "use strict"; /** @@ -264,6 +268,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 @@ -299,17 +304,23 @@ 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) { - if (defined(imageryProvider.tileDiscardPolicy)) { - return RequestScheduler.throttleRequest(url, loadImageViaBlob); - } - return RequestScheduler.throttleRequest(url, loadImage); + ImageryProvider.loadImage = function(imageryProvider, url, distance) { + var requestFunction = defined(imageryProvider.tileDiscardPolicy) ? loadImageViaBlob : loadImage; + + return RequestScheduler.schedule(new Request({ + url : url, + requestFunction : requestFunction, + type : RequestType.IMAGERY, + distance : distance + })); }; return ImageryProvider; diff --git a/Source/Scene/Instanced3DModel3DTileContentProvider.js b/Source/Scene/Instanced3DModel3DTileContentProvider.js index 3f5de371a00d..5319c5aadb64 100644 --- a/Source/Scene/Instanced3DModel3DTileContentProvider.js +++ b/Source/Scene/Instanced3DModel3DTileContentProvider.js @@ -12,7 +12,9 @@ define([ '../Core/getStringFromTypedArray', '../Core/loadArrayBuffer', '../Core/Matrix4', + '../Core/Request', '../Core/RequestScheduler', + '../Core/RequestType', '../Core/Transforms', '../ThirdParty/Uri', '../ThirdParty/when', @@ -33,7 +35,9 @@ define([ getStringFromTypedArray, loadArrayBuffer, Matrix4, + Request, RequestScheduler, + RequestType, Transforms, Uri, when, @@ -132,7 +136,14 @@ define([ Instanced3DModel3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer); + var distance = this._tile.distanceToCamera; + var promise = RequestScheduler.schedule(new Request({ + url : this._url, + server : this._tile.requestServer, + requestFunction : loadArrayBuffer, + type : RequestType.TILES3D, + distance : distance + })); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { @@ -220,13 +231,14 @@ define([ cull : false, url : undefined, headers : undefined, + type : RequestType.TILES3D, gltf : undefined, basePath : undefined }; 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/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/Model.js b/Source/Scene/Model.js index c5516b6baa6c..254fee124206 100644 --- a/Source/Scene/Model.js +++ b/Source/Scene/Model.js @@ -30,6 +30,8 @@ define([ '../Core/PrimitiveType', '../Core/Quaternion', '../Core/Queue', + '../Core/Request', + '../Core/RequestScheduler', '../Core/RuntimeError', '../Core/TaskProcessor', '../Renderer/Buffer', @@ -89,6 +91,8 @@ define([ PrimitiveType, Quaternion, Queue, + Request, + RequestScheduler, RuntimeError, TaskProcessor, Buffer, @@ -543,6 +547,7 @@ define([ this._pickFragmentShaderLoaded = options.pickFragmentShaderLoaded; this._pickUniformMapLoaded = options.pickUniformMapLoaded; this._ignoreCommands = defaultValue(options.ignoreCommands, false); + this._requestType = options.requestType; /** * @private @@ -1006,7 +1011,7 @@ define([ setCachedGltf(model, cachedGltf); gltfCache[cacheKey] = cachedGltf; - loadArrayBuffer(url, 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 @@ -1194,7 +1199,8 @@ 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)); + var promise = RequestScheduler.request(bufferPath, loadArrayBuffer, undefined, model._requestType); + promise.then(bufferLoad(model, id)).otherwise(getFailedLoadFunction(model, 'buffer', bufferPath)); } } } @@ -1298,7 +1304,8 @@ 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)); + 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 452a3d34d76b..8b6a9d2b933f 100644 --- a/Source/Scene/Points3DTileContentProvider.js +++ b/Source/Scene/Points3DTileContentProvider.js @@ -12,14 +12,13 @@ define([ '../Core/loadArrayBuffer', '../Core/OrientedBoundingBox', '../Core/PointGeometry', + '../Core/Request', '../Core/RequestScheduler', + '../Core/RequestType', '../ThirdParty/when', './Cesium3DTileContentState', './PointAppearance', - './Primitive', - './TileBoundingRegion', - './TileBoundingSphere', - './TileOrientedBoundingBox' + './Primitive' ], function( BoundingSphere, Cartesian3, @@ -33,14 +32,13 @@ define([ loadArrayBuffer, OrientedBoundingBox, PointGeometry, + Request, RequestScheduler, + RequestType, when, Cesium3DTileContentState, PointAppearance, - Primitive, - TileBoundingRegion, - TileBoundingSphere, - TileOrientedBoundingBox) { + Primitive) { "use strict"; /** @@ -49,6 +47,8 @@ define([ function Points3DTileContentProvider(tileset, tile, url) { this._primitive = undefined; this._url = url; + this._tileset = tileset; + this._tile = tile; /** * @readonly @@ -88,7 +88,14 @@ define([ Points3DTileContentProvider.prototype.request = function() { var that = this; - var promise = RequestScheduler.throttleRequest(this._url, loadArrayBuffer); + var distance = this._tile.distanceToCamera; + var promise = RequestScheduler.schedule(new Request({ + url : this._url, + server : this._tile.requestServer, + requestFunction : loadArrayBuffer, + type : RequestType.TILES3D, + distance : distance + })); if (defined(promise)) { this.state = Cesium3DTileContentState.LOADING; promise.then(function(arrayBuffer) { diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 7d43e9b97214..861a324da35f 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; 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..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(); @@ -537,12 +539,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 +553,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 6752d2572608..4d4beb38171e 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, @@ -85,12 +87,8 @@ define([ surfaceTile.maximumHeight = mesh.maximumHeight; surfaceTile.boundingSphere3D = BoundingSphere.clone(mesh.boundingSphere3D, surfaceTile.boundingSphere3D); surfaceTile.orientedBoundingBox = OrientedBoundingBox.clone(mesh.orientedBoundingBox, surfaceTile.orientedBoundingBox); - surfaceTile.tileBoundingRegion = new TileBoundingRegion({ - rectangle : tile.rectangle, - minimumHeight : mesh.minimumHeight, - maximumHeight : mesh.maximumHeight, - ellipsoid : tile.tilingScheme.ellipsoid - }); + surfaceTile.tileBoundingRegion.minimumHeight = mesh.minimumHeight; + surfaceTile.tileBoundingRegion.maximumHeight = mesh.maximumHeight; tile.data.occludeePointInScaledSpace = Cartesian3.clone(mesh.occludeePointInScaledSpace, surfaceTile.occludeePointInScaledSpace); @@ -102,9 +100,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 +114,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 +137,10 @@ define([ function doRequest() { // Request the terrain from the terrain provider. - tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level); + 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 // has been deferred. diff --git a/Source/Scene/UrlTemplateImageryProvider.js b/Source/Scene/UrlTemplateImageryProvider.js index 31252edb92b2..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, @@ -474,14 +476,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); }; /** @@ -522,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/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/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/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() 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 814a7794fb96..f9f0c8b05010 100644 --- a/Specs/Core/CesiumTerrainProviderSpec.js +++ b/Specs/Core/CesiumTerrainProviderSpec.js @@ -10,6 +10,7 @@ defineSuite([ 'Core/loadWithXhr', 'Core/Math', 'Core/QuantizedMeshTerrainData', + 'Core/Request', 'Core/RequestScheduler', 'Core/TerrainProvider', 'Specs/pollToPromise', @@ -26,6 +27,7 @@ defineSuite([ loadWithXhr, CesiumMath, QuantizedMeshTerrainData, + Request, RequestScheduler, TerrainProvider, pollToPromise, @@ -364,6 +366,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({ @@ -374,18 +378,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 url = getAbsoluteUri('Data/CesiumTerrainTileJson'); @@ -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..8fbb00f18523 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('schedule throws when request is undefined', function() { expect(function() { - RequestScheduler.throttleRequest(); + RequestScheduler.schedule(); }).toThrowDeveloperError(); }); - it('throttleRequest throws when requestFunction is undefined', function() { + it('schedule throws when request.url is undefined', function() { expect(function() { - RequestScheduler.throttleRequest('http://foo.com/1'); + RequestScheduler.schedule(new Request({ + requestFunction : function(url) { + return undefined; + } + })); + }).toThrowDeveloperError(); + }); + + it('schedule throws when request.requestFunction is undefined', function() { + expect(function() { + RequestScheduler.schedule(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.schedule(request); + var promise2 = RequestScheduler.schedule(request); + var promise3 = RequestScheduler.schedule(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.schedule(request); expect(deferreds.length).toBe(3); expect(promise4).toBeDefined(); - var promise5 = RequestScheduler.throttleRequest('http://foo.com/4', requestFunction); + var promise5 = RequestScheduler.schedule(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.schedule(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.schedule(request); + var promise2 = RequestScheduler.schedule(request); + var promise3 = RequestScheduler.schedule(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.schedule(request); expect(deferreds.length).toBe(3); expect(promise4).toBeDefined(); - var promise5 = RequestScheduler.throttleRequest('http://foo.com/4', requestFunction); + var promise5 = RequestScheduler.schedule(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.schedule(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.schedule(request); + RequestScheduler.schedule(request); + RequestScheduler.schedule(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.schedule(requestFoo); + RequestScheduler.schedule(requestFoo); + RequestScheduler.schedule(requestBar); expect(RequestScheduler.maximumRequestsPerServer).toBe(6); expect(RequestScheduler.getNumberOfAvailableRequestsByServer('http://foo.com')).toBe(4); @@ -160,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; @@ -188,13 +229,23 @@ defineSuite([ return deferred.promise; } - RequestScheduler.throttleRequest('http://foo.com/1', requestFunction); - expect(RequestScheduler.hasAvailableRequests('http://foo.com')).toEqual(true); - RequestScheduler.throttleRequest('http://foo.com/2', requestFunction); - expect(RequestScheduler.hasAvailableRequests('http://foo.com')).toEqual(false); + var requestFoo = new Request({ + url : 'http://foo.com/1', + requestFunction : requestFunction + }); + + var requestBar = new Request({ + url : 'http://bar.com/1', + requestFunction : requestFunction + }); + + 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('http://bar.com/1', requestFunction); + RequestScheduler.schedule(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.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.schedule(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', + type : RequestType.TERRAIN, + requestFunction : requestFunction, + distance : 10.0 + }); + + var terrainRequest2 = new Request({ + url : 'http://foo.com/2', + type : RequestType.TERRAIN, + requestFunction : requestFunction, + distance : 20.0 + }); + + var imageryRequest = new Request({ + url : 'http://bar.com/1', + type : RequestType.IMAGERY, + requestFunction : requestFunction, + distance : 15.0 + }); + + 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(); + 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.schedule(terrainRequest1); + promise2 = RequestScheduler.schedule(terrainRequest2); + promise3 = RequestScheduler.schedule(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.schedule(request); + var promise2 = RequestScheduler.schedule(request); + var promise3 = RequestScheduler.schedule(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.schedule(request); + RequestScheduler.resetBudgets(); + expect(console.log).not.toHaveBeenCalled(); + + RequestScheduler.debugShowStatistics = true; + RequestScheduler.schedule(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/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": { diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index 4d603e45c2ae..82b7311a96a7 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -732,7 +732,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); diff --git a/Specs/spec-main.js b/Specs/spec-main.js index f52ca167f78f..80acbde558ec 100644 --- a/Specs/spec-main.js +++ b/Specs/spec-main.js @@ -58,6 +58,9 @@ /*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; if (typeof paths !== 'undefined') {