diff --git a/.eslintrc b/.eslintrc
deleted file mode 100644
index f68d9fb0e26..00000000000
--- a/.eslintrc
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "extends": "eslint:recommended",
- "env": {
- "browser": true
- },
- "globals": {
- "DataView": false,
- "ArrayBuffer": false,
- "Float32Array": false,
- "Float64Array": false,
- "Int16Array": false,
- "Int32Array": false,
- "Int8Array": false,
- "Uint16Array": false,
- "Uint32Array": false,
- "Uint8Array": false,
- "Uint8ClampedArray": false
- },
- "plugins": [
- "html"
- ],
- "rules": {
- "curly": ["error"],
- "eqeqeq": ["error"],
- "guard-for-in": ["error"],
- "new-cap": ["error", {"properties": false}],
- "no-caller": ["error"],
- "no-console": "off",
- "no-empty": ["error"],
- "no-extend-native": ["error"],
- "no-extra-boolean-cast": "off",
- "no-irregular-whitespace": ["error"],
- "no-new": ["error"],
- "no-undef": ["error"],
- "no-unused-vars": ["error", {"vars": "all", "args": "none"}],
- "semi": ["error"],
- "strict": ["error"],
- "wrap-iife": ["error", "any"]
- }
-}
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 00000000000..5559ce5b6e8
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,6 @@
+{
+ "extends": "./Tools/eslint-config-cesium/browser.js",
+ "rules": {
+ "no-unused-vars": ["error", {"vars": "all", "args": "none"}]
+ }
+}
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
index 4bb85da06f3..13a58bed92c 100644
--- a/.idea/jsLibraryMappings.xml
+++ b/.idea/jsLibraryMappings.xml
@@ -1,7 +1,7 @@
-
+
-
+
\ No newline at end of file
diff --git a/Apps/Sandcastle/.eslintrc b/Apps/Sandcastle/.eslintrc.json
similarity index 84%
rename from Apps/Sandcastle/.eslintrc
rename to Apps/Sandcastle/.eslintrc.json
index 2fb0cba37fb..b9795620faa 100644
--- a/Apps/Sandcastle/.eslintrc
+++ b/Apps/Sandcastle/.eslintrc.json
@@ -1,5 +1,5 @@
{
- "extends": "../../.eslintrc",
+ "extends": "../../.eslintrc.json",
"globals": {
"JSON": true,
"require": true,
diff --git a/Apps/Sandcastle/gallery/Callback Property.html b/Apps/Sandcastle/gallery/Callback Property.html
new file mode 100644
index 00000000000..e4c00147d16
--- /dev/null
+++ b/Apps/Sandcastle/gallery/Callback Property.html
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+ Cesium Demo
+
+
+
+
+
+
+
+
Loading...
+
+
+
+
diff --git a/Apps/Sandcastle/gallery/Callback Property.jpg b/Apps/Sandcastle/gallery/Callback Property.jpg
new file mode 100644
index 00000000000..3a5cf438d74
Binary files /dev/null and b/Apps/Sandcastle/gallery/Callback Property.jpg differ
diff --git a/CHANGES.md b/CHANGES.md
index 6fe1c7fc568..95c0637dbb6 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -5,9 +5,14 @@ Change Log
* Deprecated
* `GoogleEarthImageryProvider` has been deprecated and will be removed in Cesium 1.37, use `GoogleEarthEnterpriseMapsProvider` instead.
+ * The `throttleRequest` parameter for `TerrainProvider.requestTileGeometry`, `CesiumTerrainProvider.requestTileGeometry`, `VRTheWorldTerrainProvider.requestTileGeometry`, and `EllipsoidTerrainProvider.requestTileGeometry` is deprecated and will be replaced with an optional `Request` object. The `throttleRequests` parameter will be removed in 1.37. Instead to throttle requests set the request's `throttle` property to `true`.
+ * The ability to provide a Promise for the `options.url` parameter of `loadWithXhr` and for the `url` parameter of `loadArrayBuffer`, `loadBlob`, `loadImageViaBlob`, `loadText`, `loadJson`, `loadXML`, `loadImage`, `loadCRN`, `loadKTX`, and `loadCubeMap` is deprecated. This will be removed in 1.37, instead `url` must be a string.
+* Added an `options.request` parameter to `loadWithXhr` and a `request` parameter to `loadArrayBuffer`, `loadBlob`, `loadImageViaBlob`, `loadText`, `loadJson`, `loadJsonp`, `loadXML`, `loadImageFromTypedArray`, `loadImage`, `loadCRN`, and `loadKTX`.
* Fixed bug where if polylines were set to follow the surface of an undefined globe, Cesium would crash [#5413] https://github.com/AnalyticalGraphicsInc/cesium/pull/5413
* Fixed a bug where picking clusters would return undefined instead of a list of the clustered entities. [#5286](https://github.com/AnalyticalGraphicsInc/cesium/issues/5286)
* Reduced the amount of Sun bloom post-process effect near the horizon. [#5381](https://github.com/AnalyticalGraphicsInc/cesium/issues/5381)
+* Updated glTF/glb MIME types. [#5420](https://github.com/AnalyticalGraphicsInc/cesium/issues/5420)
+* Fixed a bug where camera zooming worked incorrectly when the display height was greater than the display width [#5421] (https://github.com/AnalyticalGraphicsInc/cesium/pull/5421)
* Added Sandcastle demo for ArcticDEM data. [#5224](https://github.com/AnalyticalGraphicsInc/cesium/issues/5224)
* `CzmlDataSource` and `KmlDataSource` load functions now take an optional `query` object, which will append query parameters to all network requests. [#5419](https://github.com/AnalyticalGraphicsInc/cesium/pull/5419), [#5434](https://github.com/AnalyticalGraphicsInc/cesium/pull/5434)
* Fixed geocoder bug so geocoder can accurately handle NSEW inputs [#5407] (https://github.com/AnalyticalGraphicsInc/cesium/pull/5407)
diff --git a/Source/Core/CesiumTerrainProvider.js b/Source/Core/CesiumTerrainProvider.js
index 289fa6b37e1..11fb68504de 100644
--- a/Source/Core/CesiumTerrainProvider.js
+++ b/Source/Core/CesiumTerrainProvider.js
@@ -8,6 +8,7 @@ define([
'./defaultValue',
'./defined',
'./defineProperties',
+ './deprecationWarning',
'./DeveloperError',
'./Event',
'./GeographicTilingScheme',
@@ -19,8 +20,9 @@ define([
'./Math',
'./OrientedBoundingBox',
'./QuantizedMeshTerrainData',
+ './Request',
+ './RequestType',
'./TerrainProvider',
- './throttleRequestByServer',
'./TileAvailability',
'./TileProviderError'
], function(
@@ -32,6 +34,7 @@ define([
defaultValue,
defined,
defineProperties,
+ deprecationWarning,
DeveloperError,
Event,
GeographicTilingScheme,
@@ -43,14 +46,15 @@ define([
CesiumMath,
OrientedBoundingBox,
QuantizedMeshTerrainData,
+ Request,
+ RequestType,
TerrainProvider,
- throttleRequestByServer,
TileAvailability,
TileProviderError) {
'use strict';
/**
- * A {@link TerrainProvider} that access terrain data in a Cesium terrain format.
+ * A {@link TerrainProvider} that accesses terrain data in a Cesium terrain format.
* The format is described on the
* {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Cesium-Terrain-Server|Cesium wiki}.
*
@@ -489,9 +493,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. Intended for internal use only.
+ *
* @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.
@@ -499,7 +502,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.');
@@ -522,8 +525,6 @@ define([
url = proxy.getURL(url);
}
- var promise;
-
var extensionList = [];
if (this._requestVertexNormals && this._hasVertexNormals) {
extensionList.push(this._littleEndianExtensionSize ? 'octvertexnormals' : 'vertexnormals');
@@ -532,17 +533,19 @@ define([
extensionList.push('watermask');
}
- function tileLoader(tileUrl) {
- return loadArrayBuffer(tileUrl, getRequestHeader(extensionList));
+ if (typeof request === 'boolean') {
+ deprecationWarning('throttleRequests', 'The throttleRequest parameter for requestTileGeometry was deprecated in Cesium 1.35. It will be removed in 1.37.');
+ request = new Request({
+ throttle : request,
+ throttleByServer : request,
+ type : RequestType.TERRAIN
+ });
}
- throttleRequests = defaultValue(throttleRequests, true);
- if (throttleRequests) {
- promise = throttleRequestByServer(url, tileLoader);
- if (!defined(promise)) {
- return undefined;
- }
- } else {
- promise = tileLoader(url);
+
+ var promise = loadArrayBuffer(url, getRequestHeader(extensionList), request);
+
+ if (!defined(promise)) {
+ return undefined;
}
var that = this;
diff --git a/Source/Core/Ellipsoid.js b/Source/Core/Ellipsoid.js
index 6f3eba4bcfc..d31c69aefaf 100644
--- a/Source/Core/Ellipsoid.js
+++ b/Source/Core/Ellipsoid.js
@@ -1,5 +1,6 @@
/*global define*/
define([
+ './Check',
'./Cartesian3',
'./Cartographic',
'./defaultValue',
@@ -10,6 +11,7 @@ define([
'./Math',
'./scaleToGeodeticSurface'
], function(
+ Check,
Cartesian3,
Cartographic,
defaultValue,
@@ -27,9 +29,9 @@ define([
z = defaultValue(z, 0.0);
//>>includeStart('debug', pragmas.debug);
- if (x < 0.0 || y < 0.0 || z < 0.0) {
- throw new DeveloperError('All radii components must be greater than or equal to zero.');
- }
+ Check.typeOf.number.greaterThanOrEquals('x', x, 0.0);
+ Check.typeOf.number.greaterThanOrEquals('y', y, 0.0);
+ Check.typeOf.number.greaterThanOrEquals('z', z, 0.0);
//>>includeEnd('debug');
ellipsoid._radii = new Cartesian3(x, y, z);
@@ -283,12 +285,8 @@ define([
*/
Ellipsoid.pack = function(value, array, startingIndex) {
//>>includeStart('debug', pragmas.debug);
- if (!defined(value)) {
- throw new DeveloperError('value is required');
- }
- if (!defined(array)) {
- throw new DeveloperError('array is required');
- }
+ Check.typeOf.object('value', value);
+ Check.defined('array', array);
//>>includeEnd('debug');
startingIndex = defaultValue(startingIndex, 0);
@@ -308,9 +306,7 @@ define([
*/
Ellipsoid.unpack = function(array, startingIndex, result) {
//>>includeStart('debug', pragmas.debug);
- if (!defined(array)) {
- throw new DeveloperError('array is required');
- }
+ Check.defined('array', array);
//>>includeEnd('debug');
startingIndex = defaultValue(startingIndex, 0);
@@ -338,9 +334,7 @@ define([
*/
Ellipsoid.prototype.geodeticSurfaceNormalCartographic = function(cartographic, result) {
//>>includeStart('debug', pragmas.debug);
- if (!defined(cartographic)) {
- throw new DeveloperError('cartographic is required.');
- }
+ Check.typeOf.object('cartographic', cartographic);
//>>includeEnd('debug');
var longitude = cartographic.longitude;
@@ -422,10 +416,8 @@ define([
*/
Ellipsoid.prototype.cartographicArrayToCartesianArray = function(cartographics, result) {
//>>includeStart('debug', pragmas.debug);
- if (!defined(cartographics)) {
- throw new DeveloperError('cartographics is required.');
- }
- //>>includeEnd('debug');
+ Check.defined('cartographics', cartographics);
+ //>>includeEnd('debug')
var length = cartographics.length;
if (!defined(result)) {
@@ -496,9 +488,7 @@ define([
*/
Ellipsoid.prototype.cartesianArrayToCartographicArray = function(cartesians, result) {
//>>includeStart('debug', pragmas.debug);
- if (!defined(cartesians)) {
- throw new DeveloperError('cartesians is required.');
- }
+ Check.defined('cartesians', cartesians);
//>>includeEnd('debug');
var length = cartesians.length;
@@ -536,9 +526,7 @@ define([
*/
Ellipsoid.prototype.scaleToGeocentricSurface = function(cartesian, result) {
//>>includeStart('debug', pragmas.debug);
- if (!defined(cartesian)) {
- throw new DeveloperError('cartesian is required.');
- }
+ Check.typeOf.object('cartesian', cartesian);
//>>includeEnd('debug');
if (!defined(result)) {
@@ -633,15 +621,13 @@ define([
*/
Ellipsoid.prototype.getSurfaceNormalIntersectionWithZAxis = function(position, buffer, result) {
//>>includeStart('debug', pragmas.debug);
- if (!defined(position)) {
- throw new DeveloperError('position is required.');
- }
+ Check.typeOf.object('position', position);
+
if (!CesiumMath.equalsEpsilon(this._radii.x, this._radii.y, CesiumMath.EPSILON15)) {
throw new DeveloperError('Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y)');
}
- if (this._radii.z === 0) {
- throw new DeveloperError('Ellipsoid.radii.z must be greater than 0');
- }
+
+ Check.typeOf.number.greaterThan('Ellipsoid.radii.z', this._radii.z, 0);
//>>includeEnd('debug');
buffer = defaultValue(buffer, 0.0);
diff --git a/Source/Core/EllipsoidTerrainProvider.js b/Source/Core/EllipsoidTerrainProvider.js
index c0011c62799..58adf683ca8 100644
--- a/Source/Core/EllipsoidTerrainProvider.js
+++ b/Source/Core/EllipsoidTerrainProvider.js
@@ -152,14 +152,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. Intended for internal use only.
+ *
* @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) {
var width = 16;
var height = 16;
return new HeightmapTerrainData({
diff --git a/Source/Core/GoogleEarthEnterpriseMetadata.js b/Source/Core/GoogleEarthEnterpriseMetadata.js
index 86bd8cb1570..d5ee5dd6185 100644
--- a/Source/Core/GoogleEarthEnterpriseMetadata.js
+++ b/Source/Core/GoogleEarthEnterpriseMetadata.js
@@ -13,9 +13,10 @@ define([
'./joinUrls',
'./loadArrayBuffer',
'./Math',
+ './Request',
+ './RequestType',
'./RuntimeError',
- './TaskProcessor',
- './throttleRequestByServer'
+ './TaskProcessor'
], function(
dbrootParser,
when,
@@ -30,9 +31,10 @@ define([
joinUrls,
loadArrayBuffer,
CesiumMath,
+ Request,
+ RequestType,
RuntimeError,
- TaskProcessor,
- throttleRequestByServer) {
+ TaskProcessor) {
'use strict';
function stringToBuffer(str) {
@@ -130,7 +132,7 @@ define([
var that = this;
this._readyPromise = requestDbRoot(this)
.then(function() {
- return that.getQuadTreePacket('', that._quadPacketVersion, false);
+ return that.getQuadTreePacket('', that._quadPacketVersion);
})
.then(function() {
return true;
@@ -292,30 +294,23 @@ define([
*
* @param {String} [quadKey=''] The quadkey to retrieve the packet for.
* @param {Number} [version=1] The cnode version to be used in the request.
- * @param {Boolean} [throttle=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. Intended for internal use only.
*
* @private
*/
- GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket = function(quadKey, version, throttle) {
+ GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket = function(quadKey, version, request) {
version = defaultValue(version, 1);
quadKey = defaultValue(quadKey, '');
- throttle = defaultValue(throttle, true);
var url = getMetadataUrl(this, quadKey, version);
var proxy = this._proxy;
if (defined(proxy)) {
url = proxy.getURL(url);
}
- var promise;
- if (throttle) {
- promise = throttleRequestByServer(url, loadArrayBuffer);
- if (!defined(promise)) {
- return undefined;
- }
- } else {
- promise = loadArrayBuffer(url);
+ var promise = loadArrayBuffer(url, undefined, request);
+
+ if (!defined(promise)) {
+ return undefined; // Throttled
}
var tileInfo = this._tileInfo;
@@ -378,21 +373,18 @@ define([
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
- * @param {Boolean} [throttle=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. Intended for internal use only.
*
* @returns {Promise} A promise that resolves to the tile info for the requested quad key
*
* @private
*/
- GoogleEarthEnterpriseMetadata.prototype.populateSubtree = function(x, y, level, throttle) {
- throttle = defaultValue(throttle, true);
+ GoogleEarthEnterpriseMetadata.prototype.populateSubtree = function(x, y, level, request) {
var quadkey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level);
- return populateSubtree(this, quadkey, throttle);
+ return populateSubtree(this, quadkey, request);
};
- function populateSubtree(that, quadKey, throttle) {
+ function populateSubtree(that, quadKey, request) {
var tileInfo = that._tileInfo;
var q = quadKey;
var t = tileInfo[q];
@@ -406,13 +398,20 @@ define([
t = tileInfo[q];
}
+ var subtreeRequest;
var subtreePromises = that._subtreePromises;
var promise = subtreePromises[q];
if (defined(promise)) {
return promise
.then(function() {
- // Recursively call this incase we need multiple subtree requests
- return populateSubtree(that, quadKey, throttle);
+ // Recursively call this in case we need multiple subtree requests
+ subtreeRequest = new Request({
+ throttle : request.throttle,
+ throttleByServer : request.throttleByServer,
+ type : request.type,
+ priorityFunction : request.priorityFunction
+ });
+ return populateSubtree(that, quadKey, subtreeRequest);
});
}
@@ -427,7 +426,7 @@ define([
// We need to split up the promise here because when will execute syncronously if getQuadTreePacket
// is already resolved (like in the tests), so subtreePromises will never get cleared out.
// Only the initial request will also remove the promise from subtreePromises.
- promise = that.getQuadTreePacket(q, t.cnodeVersion, throttle);
+ promise = that.getQuadTreePacket(q, t.cnodeVersion, request);
if (!defined(promise)) {
return undefined;
}
@@ -435,8 +434,14 @@ define([
return promise
.then(function() {
- // Recursively call this incase we need multiple subtree requests
- return populateSubtree(that, quadKey, throttle);
+ // Recursively call this in case we need multiple subtree requests
+ subtreeRequest = new Request({
+ throttle : request.throttle,
+ throttleByServer : request.throttleByServer,
+ type : request.type,
+ priorityFunction : request.priorityFunction
+ });
+ return populateSubtree(that, quadKey, subtreeRequest);
})
.always(function() {
delete subtreePromises[q];
diff --git a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js
index 052d1c99af3..d5e2928633b 100644
--- a/Source/Core/GoogleEarthEnterpriseTerrainProvider.js
+++ b/Source/Core/GoogleEarthEnterpriseTerrainProvider.js
@@ -5,6 +5,7 @@ define([
'./defaultValue',
'./defined',
'./defineProperties',
+ './deprecationWarning',
'./DeveloperError',
'./Event',
'./GeographicTilingScheme',
@@ -15,9 +16,11 @@ define([
'./loadArrayBuffer',
'./Math',
'./Rectangle',
+ './Request',
+ './RequestState',
+ './RequestType',
'./RuntimeError',
'./TaskProcessor',
- './throttleRequestByServer',
'./TileProviderError'
], function(
when,
@@ -25,6 +28,7 @@ define([
defaultValue,
defined,
defineProperties,
+ deprecationWarning,
DeveloperError,
Event,
GeographicTilingScheme,
@@ -35,9 +39,11 @@ define([
loadArrayBuffer,
CesiumMath,
Rectangle,
+ Request,
+ RequestState,
+ RequestType,
RuntimeError,
TaskProcessor,
- throttleRequestByServer,
TileProviderError) {
'use strict';
@@ -152,6 +158,7 @@ define([
this._terrainCache = new TerrainCache();
this._terrainPromises = {};
+ this._terrainRequests = {};
this._errorEvent = new Event();
@@ -339,9 +346,7 @@ define([
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
- * @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
- * or false if the request should be initiated regardless of the number of requests
- * already in progress.
+ * @param {Request} [request] The request object. Intended for internal use only.
* @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.
@@ -349,7 +354,7 @@ define([
* @exception {DeveloperError} This function must not be called before {@link GoogleEarthEnterpriseProvider#ready}
* returns true.
*/
- GoogleEarthEnterpriseTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) {
+ GoogleEarthEnterpriseTerrainProvider.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.');
@@ -434,23 +439,31 @@ define([
// Load that terrain
var terrainPromises = this._terrainPromises;
+ var terrainRequests = this._terrainRequests;
var url = buildTerrainUrl(this, q, terrainVersion);
- var promise;
+ var sharedPromise;
+ var sharedRequest;
if (defined(terrainPromises[q])) { // Already being loaded possibly from another child, so return existing promise
- promise = terrainPromises[q];
+ sharedPromise = terrainPromises[q];
+ sharedRequest = terrainRequests[q];
} else { // Create new request for terrain
- var requestPromise;
- throttleRequests = defaultValue(throttleRequests, true);
- if (throttleRequests) {
- requestPromise = throttleRequestByServer(url, loadArrayBuffer);
- if (!defined(requestPromise)) {
- return undefined; // Throttled
- }
- } else {
- requestPromise = loadArrayBuffer(url);
+ if (typeof request === 'boolean') {
+ deprecationWarning('throttleRequests', 'The throttleRequest parameter for requestTileGeometry was deprecated in Cesium 1.35. It will be removed in 1.37.');
+ request = new Request({
+ throttle : request,
+ throttleByServer : request,
+ type : RequestType.TERRAIN
+ });
}
- promise = requestPromise
+ sharedRequest = request;
+ var requestPromise = loadArrayBuffer(url, undefined, sharedRequest);
+
+ if (!defined(requestPromise)) {
+ return undefined; // Throttled
+ }
+
+ sharedPromise = requestPromise
.then(function(terrain) {
if (defined(terrain)) {
return taskProcessor.scheduleTask({
@@ -482,22 +495,20 @@ define([
}
return when.reject(new RuntimeError('Failed to load terrain.'));
- })
- .otherwise(function(error) {
- info.terrainState = TerrainState.NONE;
- return when.reject(error);
});
- terrainPromises[q] = promise; // Store promise without delete from terrainPromises
+ terrainPromises[q] = sharedPromise; // Store promise without delete from terrainPromises
+ terrainRequests[q] = sharedRequest;
// Set promise so we remove from terrainPromises just one time
- promise = promise
+ sharedPromise = sharedPromise
.always(function() {
delete terrainPromises[q];
+ delete terrainRequests[q];
});
}
- return promise
+ return sharedPromise
.then(function() {
var buffer = terrainCache.get(quadKey);
if (defined(buffer)) {
@@ -509,10 +520,17 @@ define([
negativeAltitudeExponentBias: metadata.negativeAltitudeExponentBias,
negativeElevationThreshold: metadata.negativeAltitudeThreshold
});
- } else {
- info.terrainState = TerrainState.NONE;
- return when.reject(new RuntimeError('Failed to load terrain.'));
}
+
+ return when.reject(new RuntimeError('Failed to load terrain.'));
+ })
+ .otherwise(function(error) {
+ if (sharedRequest.state === RequestState.CANCELLED) {
+ request.state = sharedRequest.state;
+ return when.reject(error);
+ }
+ info.terrainState = TerrainState.NONE;
+ return when.reject(error);
});
};
@@ -569,7 +587,12 @@ define([
if (metadata.isValid(quadKey)) {
// We will need this tile, so request metadata and return false for now
- metadata.populateSubtree(x, y, level);
+ var request = new Request({
+ throttle : true,
+ throttleByServer : true,
+ type : RequestType.TERRAIN
+ });
+ metadata.populateSubtree(x, y, level, request);
}
return false;
};
diff --git a/Source/Core/Heap.js b/Source/Core/Heap.js
index 166e4fe0f3d..11f379213a3 100644
--- a/Source/Core/Heap.js
+++ b/Source/Core/Heap.js
@@ -12,202 +12,224 @@ define([
'use strict';
/**
+ * Array implementation of a heap.
+ *
* @alias Heap
* @constructor
* @private
*
- * @param {Function} comparator The comparator to use for the heap. If comparator(a, b) is less than 0, sort a to a lower index than b, otherwise sort to a higher index.
+ * @param {Object} options Object with the following properties:
+ * @param {Heap~ComparatorCallback} options.comparator The comparator to use for the heap. If comparator(a, b) is less than 0, sort a to a lower index than b, otherwise sort to a higher index.
*/
- function Heap(comparator) {
+ function Heap(options) {
//>>includeStart('debug', pragmas.debug);
- Check.defined('comparator', comparator);
+ Check.typeOf.object('options', options);
+ Check.defined('options.comparator', options.comparator);
//>>includeEnd('debug');
- this._comparator = comparator;
- this._data = [];
+ this._comparator = options.comparator;
+ this._array = [];
this._length = 0;
- this._maximumSize = 0;
+ this._maximumLength = undefined;
}
defineProperties(Heap.prototype, {
/**
- * Gets the internal data in the heap.
+ * Gets the length of the heap.
*
* @memberof Heap.prototype
*
- * @type {Array}
+ * @type {Number}
* @readonly
*/
- data : {
+ length : {
get : function() {
- return this._data;
+ return this._length;
}
},
/**
- * Gets the length of the heap.
+ * Gets the internal array.
*
* @memberof Heap.prototype
*
- * @type {Number}
+ * @type {Array}
* @readonly
*/
- length : {
+ internalArray : {
get : function() {
- return this._length;
+ return this._array;
}
},
/**
- * Gets or sets the maximum size of the heap.
+ * Gets and sets the maximum length of the heap.
*
* @memberof Heap.prototype
*
* @type {Number}
*/
- maximumSize : {
- get: function() {
- return this._maximumSize;
+ maximumLength : {
+ get : function() {
+ return this._maximumLength;
},
-
- set: function(value) {
- this._maximumSize = value;
- if (this._length > this._maximumSize && this._maximumSize > 0) {
- this._length = this._maximumSize;
- this._data.length = this._maximumSize;
+ set : function(value) {
+ this._maximumLength = value;
+ if (this._length > value && value > 0) {
+ this._length = value;
+ this._array.length = value;
}
}
+ },
+
+ /**
+ * The comparator to use for the heap. If comparator(a, b) is less than 0, sort a to a lower index than b, otherwise sort to a higher index.
+ *
+ * @memberof Heap.prototype
+ *
+ * @type {Heap~ComparatorCallback}
+ */
+ comparator : {
+ get : function() {
+ return this._comparator;
+ }
}
});
- function swap(data, a, b) {
- var temp = data[a];
- data[a] = data[b];
- data[b] = temp;
+ function swap(array, a, b) {
+ var temp = array[a];
+ array[a] = array[b];
+ array[b] = temp;
}
/**
* Resizes the internal array of the heap.
*
- * @param {Number} [length] The length to resize internal array to. Defaults to the current size of the heap.
+ * @param {Number} [length] The length to resize internal array to. Defaults to the current length of the heap.
*/
Heap.prototype.reserve = function(length) {
length = defaultValue(length, this._length);
- this._data.length = length;
+ this._array.length = length;
};
/**
- * Heapify. Update the heap so that index and all descendants satisfy the heap property.
+ * Update the heap so that index and all descendants satisfy the heap property.
*
- * @param {Number} index The starting index to heapify from.
+ * @param {Number} [index=0] The starting index to heapify from.
*/
Heap.prototype.heapify = function(index) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.number.greaterThanOrEquals('index', index, 0);
- //>>includeEnd('debug');
-
+ index = defaultValue(index, 0);
var length = this._length;
var comparator = this._comparator;
- var data = this._data;
+ var array = this._array;
var candidate = -1;
+ var inserting = true;
- while (true) { // eslint-disable-line no-constant-condition
+ while (inserting) {
var right = 2 * (index + 1);
var left = right - 1;
- if (left < length && comparator(data[left], data[index]) < 0) {
+ if (left < length && comparator(array[left], array[index]) < 0) {
candidate = left;
} else {
candidate = index;
}
- if (right < length && comparator(data[right], data[candidate]) < 0) {
+ if (right < length && comparator(array[right], array[candidate]) < 0) {
candidate = right;
}
if (candidate !== index) {
- swap(data, candidate, index);
+ swap(array, candidate, index);
index = candidate;
} else {
- break;
+ inserting = false;
}
}
-
- if (this._length > this._maximumSize && this._maximumSize > 0) {
- this._length = this._maximumSize;
- this._data.length = this._maximumSize;
- }
};
/**
- * Create a heap from an existing array. This will modify the original array.
- *
- * @param {Array} data The array to convert to a heap.
+ * Resort the heap.
*/
- Heap.prototype.buildHeap = function(data) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined('data', data);
- //>>includeEnd('debug');
-
- var length = data.length;
- this._data = data;
- this._length = length;
-
+ Heap.prototype.resort = function() {
+ var length = this._length;
for (var i = Math.ceil(length / 2); i >= 0; --i) {
this.heapify(i);
}
};
/**
- * Insert an element into the heap. If the length would grow greater than maximumSize
+ * Insert an element into the heap. If the length would grow greater than maximumLength
* of the heap, extra elements are removed.
*
- * @param {*} value The element to insert
+ * @param {*} element The element to insert
+ *
+ * @return {*} The element that was removed from the heap if the heap is at full capacity.
*/
- Heap.prototype.insert = function(value) {
+ Heap.prototype.insert = function(element) {
//>>includeStart('debug', pragmas.debug);
- Check.defined('value', value);
+ Check.defined('element', element);
//>>includeEnd('debug');
- var data = this._data;
+ var array = this._array;
var comparator = this._comparator;
+ var maximumLength = this._maximumLength;
var index = this._length++;
- if (index < data.length) {
- data[index] = value;
+ if (index < array.length) {
+ array[index] = element;
} else {
- data.push(value);
+ array.push(element);
}
while (index !== 0) {
var parent = Math.floor((index - 1) / 2);
- if (comparator(data[index], data[parent]) < 0) {
- swap(data, index, parent);
+ if (comparator(array[index], array[parent]) < 0) {
+ swap(array, index, parent);
index = parent;
} else {
break;
}
}
- if (this._length > this._maximumSize && this._maximumSize > 0) {
- this._length = this._maximumSize;
+ var removedElement;
+
+ if (defined(maximumLength) && (this._length > maximumLength)) {
+ removedElement = array[maximumLength];
+ this._length = maximumLength;
}
+
+ return removedElement;
};
/**
- * Remove the top element from the heap and return it.
+ * Remove the element specified by index from the heap and return it.
*
- * @returns {*} The top element of the heap.
+ * @param {Number} [index=0] The index to remove.
+ * @returns {*} The specified element of the heap.
*/
- Heap.prototype.pop = function() {
+ Heap.prototype.pop = function(index) {
+ index = defaultValue(index, 0);
if (this._length === 0) {
return undefined;
}
- var data = this._data;
- var root = data[0];
- swap(data, 0, --this._length);
- this.heapify(0);
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number.lessThan('index', index, this._length);
+ //>>includeEnd('debug');
+
+ var array = this._array;
+ var root = array[index];
+ swap(array, index, --this._length);
+ this.heapify(index);
return root;
};
+ /**
+ * The comparator to use for the heap.
+ * @callback Heap~ComparatorCallback
+ * @param {*} a An element in the heap.
+ * @param {*} b An element in the heap.
+ * @returns {Number} If the result of the comparison is less than 0, sort a to a lower index than b, otherwise sort to a higher index.
+ */
+
return Heap;
});
diff --git a/Source/Core/Request.js b/Source/Core/Request.js
index 55873ad1703..42ed344b1c5 100644
--- a/Source/Core/Request.js
+++ b/Source/Core/Request.js
@@ -1,68 +1,174 @@
/*global define*/
define([
- './defaultValue'
+ './defaultValue',
+ './defined',
+ './defineProperties',
+ './RequestState',
+ './RequestType'
], function(
- defaultValue) {
+ defaultValue,
+ defined,
+ defineProperties,
+ RequestState,
+ RequestType) {
'use strict';
/**
- * Stores information for making a request using {@link RequestScheduler}.
+ * Stores information for making a request. In general this does not need to be constructed directly.
*
- * @exports Request
+ * @alias Request
+ * @constructor
*
- * @private
+ * @param {Object} [options] An object with the following properties:
+ * @param {Boolean} [options.url] The url to request.
+ * @param {Request~RequestCallback} [options.requestFunction] The function that makes the actual data request.
+ * @param {Request~CancelCallback} [options.cancelFunction] The function that is called when the request is cancelled.
+ * @param {Request~PriorityCallback} [options.priorityFunction] The function that is called to update the request's priority, which occurs once per frame.
+ * @param {Number} [options.priority=0.0] The initial priority of the request.
+ * @param {Boolean} [options.throttle=false] Whether to throttle and prioritize the request. If false, the request will be sent immediately. If true, the request will be throttled and sent based on priority.
+ * @param {Boolean} [options.throttleByServer=false] Whether to throttle the request by server.
+ * @param {RequestType} [options.type=RequestType.OTHER] The type of request.
*/
function Request(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+ var throttleByServer = defaultValue(options.throttleByServer, false);
+ var throttle = throttleByServer || defaultValue(options.throttle, false);
+
/**
* The URL to request.
+ *
+ * @type {String}
*/
this.url = options.url;
/**
- * Extra parameters to send with the request. For example, HTTP headers or jsonp parameters.
+ * The function that makes the actual data request.
+ *
+ * @type {Request~RequestCallback}
+ */
+ this.requestFunction = options.requestFunction;
+
+ /**
+ * The function that is called when the request is cancelled.
+ *
+ * @type {Request~CancelCallback}
*/
- this.parameters = options.parameters;
+ this.cancelFunction = options.cancelFunction;
/**
- * The actual function that makes the request.
+ * The function that is called to update the request's priority, which occurs once per frame.
+ *
+ * @type {Request~PriorityCallback}
*/
- this.requestFunction = options.requestFunction;
+ this.priorityFunction = options.priorityFunction;
/**
- * Type of request. Used for more fine-grained priority sorting.
+ * Priority is a unit-less value where lower values represent higher priority.
+ * For world-based objects, this is usually the distance from the camera.
+ * A request that does not have a priority function defaults to a priority of 0.
+ *
+ * If priorityFunction is defined, this value is updated every frame with the result of that call.
+ *
+ * @type {Number}
+ * @default 0.0
*/
- this.type = options.type;
+ this.priority = defaultValue(options.priority, 0.0);
/**
- * 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.
+ * Whether to throttle and prioritize the request. If false, the request will be sent immediately. If true, the
+ * request will be throttled and sent based on priority.
+ *
+ * @type {Boolean}
+ * @readonly
+ *
+ * @default false
*/
- this.defer = defaultValue(options.defer, false);
+ this.throttle = throttle;
/**
- * The distance from the camera, used to prioritize requests.
+ * Whether to throttle the request by server. Browsers typically support about 6-8 parallel connections
+ * for HTTP/1 servers, and an unlimited amount of connections for HTTP/2 servers. Setting this value
+ * to true is preferable for requests going through HTTP/1 servers.
+ *
+ * @type {Boolean}
+ * @readonly
+ *
+ * @default false
*/
- this.distance = defaultValue(options.distance, 0.0);
+ this.throttleByServer = throttleByServer;
- // Helper members for RequestScheduler
+ /**
+ * Type of request.
+ *
+ * @type {RequestType}
+ * @readonly
+ *
+ * @default RequestType.OTHER
+ */
+ this.type = defaultValue(options.type, RequestType.OTHER);
/**
- * A promise for when a deferred request can start.
+ * A key used to identify the server that a request is going to. It is derived from the url's authority and scheme.
+ *
+ * @type {String}
*
* @private
*/
- this.startPromise = undefined;
+ this.serverKey = undefined;
/**
- * Reference to a {@link RequestScheduler~RequestServer}.
+ * The current state of the request.
+ *
+ * @type {RequestState}
+ * @readonly
+ */
+ this.state = RequestState.UNISSUED;
+
+ /**
+ * The requests's deferred promise.
+ *
+ * @type {Object}
+ *
+ * @private
+ */
+ this.deferred = undefined;
+
+ /**
+ * Whether the request was explicitly cancelled.
+ *
+ * @type {Boolean}
*
* @private
*/
- this.server = options.server;
+ this.cancelled = false;
}
+ /**
+ * Mark the request as cancelled.
+ *
+ * @private
+ */
+ Request.prototype.cancel = function() {
+ this.cancelled = true;
+ };
+
+ /**
+ * The function that makes the actual data request.
+ * @callback Request~RequestCallback
+ * @returns {Promise} A promise for the requested data.
+ */
+
+ /**
+ * The function that is called when the request is cancelled.
+ * @callback Request~CancelCallback
+ */
+
+ /**
+ * The function that is called to update the request's priority, which occurs once per frame.
+ * @callback Request~PriorityCallback
+ * @returns {Number} The updated priority value.
+ */
+
return Request;
});
diff --git a/Source/Core/RequestScheduler.js b/Source/Core/RequestScheduler.js
index b401193e4f5..264838a5b82 100644
--- a/Source/Core/RequestScheduler.js
+++ b/Source/Core/RequestScheduler.js
@@ -1,440 +1,424 @@
/*global define*/
define([
- '../ThirdParty/Uri',
- '../ThirdParty/when',
- './defaultValue',
+ './clone',
+ './Check',
'./defined',
- './DeveloperError',
+ './defineProperties',
+ './Heap',
+ './isBlobUri',
'./isDataUri',
- './Queue',
- './Request',
- './RequestType'
+ './RequestState',
+ '../ThirdParty/Uri',
+ '../ThirdParty/when'
], function(
- Uri,
- when,
- defaultValue,
+ clone,
+ Check,
defined,
- DeveloperError,
+ defineProperties,
+ Heap,
+ isBlobUri,
isDataUri,
- Queue,
- Request,
- RequestType) {
+ RequestState,
+ Uri,
+ when) {
'use strict';
- function RequestBudget(request) {
- /**
- * Total requests allowed this frame.
- */
- this.total = 0;
+ function sortRequests(a, b) {
+ return a.priority - b.priority;
+ }
- /**
- * Total requests used this frame.
- */
- this.used = 0;
+ var statistics = {
+ numberOfAttemptedRequests : 0,
+ numberOfActiveRequests : 0,
+ numberOfCancelledRequests : 0,
+ numberOfCancelledActiveRequests : 0,
+ numberOfFailedRequests : 0,
+ numberOfActiveRequestsEver : 0
+ };
- /**
- * Server of the request.
- */
- this.server = request.server;
+ var priorityHeapLength = 20;
+ var requestHeap = new Heap({
+ comparator : sortRequests
+ });
+ requestHeap.maximumLength = priorityHeapLength;
+ requestHeap.reserve(priorityHeapLength);
- /**
- * Type of request. Used for more fine-grained priority sorting.
- */
- this.type = request.type;
+ var activeRequests = [];
+ var numberOfActiveRequestsByServer = {};
+
+ var pageUri = typeof document !== 'undefined' ? new Uri(document.location.href) : new Uri();
+
+ /**
+ * Tracks the number of active requests and prioritizes incoming requests.
+ *
+ * @exports RequestScheduler
+ *
+ * @private
+ */
+ function RequestScheduler() {
}
/**
- * 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).
+ * The maximum number of simultaneous active requests. Un-throttled requests do not observe this limit.
+ * @type {Number}
+ * @default 50
+ */
+ RequestScheduler.maximumRequests = 50;
+
+ /**
+ * The maximum number of simultaneous active requests per server. Un-throttled requests do not observe this limit.
+ * @type {Number}
+ * @default 6
+ */
+ RequestScheduler.maximumRequestsPerServer = 6;
+
+ /**
+ * Specifies if the request scheduler should throttle incoming requests, or let the browser queue requests under its control.
+ * @type {Boolean}
+ * @default true
*/
- function RequestServer(serverName) {
+ RequestScheduler.throttleRequests = true;
+
+ /**
+ * When true, log statistics to the console every frame
+ * @type {Boolean}
+ * @default false
+ */
+ RequestScheduler.debugShowStatistics = false;
+
+ defineProperties(RequestScheduler, {
/**
- * Number of active requests at this server.
+ * Returns the statistics used by the request scheduler.
+ *
+ * @memberof RequestScheduler
+ *
+ * @type Object
+ * @readonly
*/
- this.activeRequests = 0;
+ statistics : {
+ get : function() {
+ return statistics;
+ }
+ },
/**
- * The name of the server.
+ * The maximum size of the priority heap. This limits the number of requests that are sorted by priority. Only applies to requests that are not yet active.
+ *
+ * @memberof RequestScheduler
+ *
+ * @type {Number}
+ * @default 20
*/
- this.serverName = serverName;
- }
+ priorityHeapLength : {
+ get : function() {
+ return priorityHeapLength;
+ },
+ set : function(value) {
+ // If the new length shrinks the heap, need to cancel some of the requests.
+ // Since this value is not intended to be tweaked regularly it is fine to just cancel the high priority requests.
+ if (value < priorityHeapLength) {
+ while (requestHeap.length > value) {
+ var request = requestHeap.pop();
+ cancelRequest(request);
+ }
+ }
+ priorityHeapLength = value;
+ requestHeap.maximumLength = value;
+ requestHeap.reserve(value);
+ }
+ }
+ });
- RequestServer.prototype.hasAvailableRequests = function() {
- return RequestScheduler.hasAvailableRequests() && (this.activeRequests < RequestScheduler.maximumRequestsPerServer);
- };
+ function updatePriority(request) {
+ if (defined(request.priorityFunction)) {
+ request.priority = request.priorityFunction();
+ }
+ }
- RequestServer.prototype.getNumberOfAvailableRequests = function() {
- return RequestScheduler.maximumRequestsPerServer - this.activeRequests;
- };
+ function serverHasOpenSlots(serverKey) {
+ return numberOfActiveRequestsByServer[serverKey] < RequestScheduler.maximumRequestsPerServer;
+ }
- var activeRequestsByServer = {};
- var activeRequests = 0;
- var budgets = [];
- var leftoverRequests = [];
- var deferredRequests = new Queue();
+ function issueRequest(request) {
+ if (request.state === RequestState.UNISSUED) {
+ request.state = RequestState.ISSUED;
+ request.deferred = when.defer();
+ }
+ return request.deferred.promise;
+ }
- var stats = {
- numberOfRequestsThisFrame : 0
- };
+ function getRequestReceivedFunction(request) {
+ return function(results) {
+ if (request.state === RequestState.CANCELLED) {
+ // If the data request comes back but the request is cancelled, ignore it.
+ return;
+ }
+ --statistics.numberOfActiveRequests;
+ --numberOfActiveRequestsByServer[request.serverKey];
+ request.state = RequestState.RECEIVED;
+ request.deferred.resolve(results);
+ };
+ }
- /**
- * Because browsers throttle the number of parallel requests allowed to each server
- * and across all servers, this class tracks the number of active requests in progress
- * and prioritizes incoming requests.
- *
- * @exports RequestScheduler
- *
- * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
- *
- * @private
- */
- function RequestScheduler() {
+ function getRequestFailedFunction(request) {
+ return function(error) {
+ if (request.state === RequestState.CANCELLED) {
+ // If the data request comes back but the request is cancelled, ignore it.
+ return;
+ }
+ ++statistics.numberOfFailedRequests;
+ --statistics.numberOfActiveRequests;
+ --numberOfActiveRequestsByServer[request.serverKey];
+ request.state = RequestState.FAILED;
+ request.deferred.reject(error);
+ };
}
- function distanceSortFunction(a, b) {
- return a.distance - b.distance;
+ function startRequest(request) {
+ var promise = issueRequest(request);
+ request.state = RequestState.ACTIVE;
+ activeRequests.push(request);
+ ++statistics.numberOfActiveRequests;
+ ++statistics.numberOfActiveRequestsEver;
+ ++numberOfActiveRequestsByServer[request.serverKey];
+ request.requestFunction().then(getRequestReceivedFunction(request)).otherwise(getRequestFailedFunction(request));
+ return promise;
}
- 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;
- }
+ function cancelRequest(request) {
+ var active = request.state === RequestState.ACTIVE;
+ request.state = RequestState.CANCELLED;
+ ++statistics.numberOfCancelledRequests;
+ request.deferred.reject();
+
+ if (active) {
+ --statistics.numberOfActiveRequests;
+ --numberOfActiveRequestsByServer[request.serverKey];
+ ++statistics.numberOfCancelledActiveRequests;
}
- // Not found, create a new budget
- budget = new RequestBudget(request);
- budgets.push(budget);
- return budget;
- }
- RequestScheduler.resetBudgets = function() {
- showStats();
- clearStats();
+ if (defined(request.cancelFunction)) {
+ request.cancelFunction();
+ }
+ }
- if (!RequestScheduler.prioritize || !RequestScheduler.throttle) {
- return;
+ /**
+ * Sort requests by priority and start requests.
+ */
+ RequestScheduler.update = function() {
+ var i;
+ var request;
+
+ // Loop over all active requests. Cancelled, failed, or received requests are removed from the array to make room for new requests.
+ var removeCount = 0;
+ var activeLength = activeRequests.length;
+ for (i = 0; i < activeLength; ++i) {
+ request = activeRequests[i];
+ if (request.cancelled) {
+ // Request was explicitly cancelled
+ cancelRequest(request);
+ }
+ if (request.state !== RequestState.ACTIVE) {
+ // Request is no longer active, remove from array
+ ++removeCount;
+ continue;
+ }
+ if (removeCount > 0) {
+ // Shift back to fill in vacated slots from completed requests
+ activeRequests[i - removeCount] = request;
+ }
}
+ activeRequests.length -= removeCount;
- // Reset budget totals
- var length = budgets.length;
- for (var i = 0; i < length; ++i) {
- budgets[i].total = 0;
- budgets[i].used = 0;
+ // Update priority of issued requests and resort the heap
+ var issuedRequests = requestHeap.internalArray;
+ var issuedLength = requestHeap.length;
+ for (i = 0; i < issuedLength; ++i) {
+ updatePriority(issuedRequests[i]);
}
+ requestHeap.resort();
+
+ // Get the number of open slots and fill with the highest priority requests.
+ // Un-throttled requests are automatically added to activeRequests, so activeRequests.length may exceed maximumRequests
+ var openSlots = Math.max(RequestScheduler.maximumRequests - activeRequests.length, 0);
+ var filledSlots = 0;
+ while (filledSlots < openSlots && requestHeap.length > 0) {
+ // Loop until all open slots are filled or the heap becomes empty
+ request = requestHeap.pop();
+ if (request.cancelled) {
+ // Request was explicitly cancelled
+ cancelRequest(request);
+ continue;
+ }
- // 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;
+ if (request.throttleByServer && !serverHasOpenSlots(request.serverKey)) {
+ // Open slots are available, but the request is throttled by its server. Cancel and try again later.
+ cancelRequest(request);
+ continue;
}
+
+ startRequest(request);
+ ++filledSlots;
}
- requests.length = 0;
+ updateStatistics();
};
- var pageUri = typeof document !== 'undefined' ? new Uri(document.location.href) : new Uri();
-
/**
- * Get the server name from a given url.
+ * Get the server key from a given url.
*
* @param {String} url The url.
- * @returns {String} The server name.
+ * @returns {String} The server key.
*/
- RequestScheduler.getServerName = function(url) {
+ RequestScheduler.getServerKey = function(url) {
//>>includeStart('debug', pragmas.debug);
- if (!defined(url)) {
- throw new DeveloperError('url is required.');
- }
+ Check.typeOf.string('url', url);
//>>includeEnd('debug');
var uri = new Uri(url).resolve(pageUri);
uri.normalize();
- var serverName = uri.authority;
- if (!/:/.test(serverName)) {
- serverName = serverName + ':' + (uri.scheme === 'https' ? '443' : '80');
+ var serverKey = uri.authority;
+ if (!/:/.test(serverKey)) {
+ // If the authority does not contain a port number, add port 443 for https or port 80 for http
+ serverKey = serverKey + ':' + (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;
+ var length = numberOfActiveRequestsByServer[serverKey];
+ if (!defined(length)) {
+ numberOfActiveRequestsByServer[serverKey] = 0;
}
- return 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) {
- return RequestScheduler.getRequestServer(url).getNumberOfAvailableRequests();
- };
- /**
- * Get the number of available slots across all servers.
- *
- * @returns {Number} The number of available slots.
- */
- RequestScheduler.getNumberOfAvailableRequests = function() {
- return RequestScheduler.maximumRequests - activeRequests;
+ return serverKey;
};
/**
- * Checks if there are available slots to make a request at the server pointed to by the url.
+ * Issue a request. If request.throttle is false, the request is sent immediately. Otherwise the request will be
+ * queued and sorted by priority before being sent.
*
- * @param {String} [url] The url to check.
- * @returns {Boolean} Returns true if there are available slots, otherwise false.
- */
- RequestScheduler.hasAvailableRequestsByServer = function(url) {
- return RequestScheduler.getRequestServer(url).hasAvailableRequests();
- };
-
- /**
- * Checks if there are available slots to make a request, considering the total
- * number of available slots across all servers.
+ * @param {Request} request The request object.
*
- * @returns {Boolean} Returns true if there are available slots, otherwise false.
+ * @returns {Promise|undefined} A Promise for the requested data, or undefined if this request does not have high enough priority to be issued.
*/
- RequestScheduler.hasAvailableRequests = function() {
- return activeRequests < RequestScheduler.maximumRequests;
- };
-
- function requestComplete(request) {
- --activeRequests;
- --request.server.activeRequests;
+ RequestScheduler.request = function(request) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object('request', request);
+ Check.typeOf.string('request.url', request.url);
+ Check.typeOf.func('request.requestFunction', request.requestFunction);
+ //>>includeEnd('debug');
- // Start a deferred request immediately now that a slot is open
- var deferredRequest = deferredRequests.dequeue();
- if (defined(deferredRequest)) {
- deferredRequest.startPromise.resolve(deferredRequest);
+ if (isDataUri(request.url) || isBlobUri(request.url)) {
+ request.state = RequestState.RECEIVED;
+ return request.requestFunction();
}
- }
- 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);
- });
- }
+ ++statistics.numberOfAttemptedRequests;
- 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);
+ if (!defined(request.serverKey)) {
+ request.serverKey = RequestScheduler.getServerKey(request.url);
}
- }
- /**
- * 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 {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.
- *
- * @example
- * // throttle requests for an image
- * var url = 'http://madeupserver.example.com/myImage.png';
- * var requestFunction = function(url) {
- * // in this simple example, loadImage could be used directly as requestFunction.
- * return Cesium.loadImage(url);
- * };
- * 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 {
- * promise.then(function(image) {
- * // handle loaded image
- * });
- * }
- *
- */
- RequestScheduler.schedule = function(request) {
- //>>includeStart('debug', pragmas.debug);
- if (!defined(request)) {
- throw new DeveloperError('request is required.');
- }
- if (!defined(request.url)) {
- throw new DeveloperError('request.url is required.');
+ if (!RequestScheduler.throttleRequests || !request.throttle) {
+ return startRequest(request);
}
- if (!defined(request.requestFunction)) {
- throw new DeveloperError('request.requestFunction is required.');
- }
- //>>includeEnd('debug');
-
- ++stats.numberOfRequestsThisFrame;
- if (!RequestScheduler.throttle || isDataUri(request.url)) {
- return request.requestFunction(request.url, request.parameters);
+ if (activeRequests.length >= RequestScheduler.maximumRequests) {
+ // Active requests are saturated. Try again later.
+ return undefined;
}
- if (!defined(request.server)) {
- request.server = RequestScheduler.getRequestServer(request.url);
+ if (request.throttleByServer && !serverHasOpenSlots(request.serverKey)) {
+ // Server is saturated. Try again later.
+ return undefined;
}
- 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);
- }
- }
+ // Insert into the priority heap and see if a request was bumped off. If this request is the lowest
+ // priority it will be returned.
+ updatePriority(request);
+ var removedRequest = requestHeap.insert(request);
- 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);
+ if (defined(removedRequest)) {
+ if (removedRequest === request) {
+ // Request does not have high enough priority to be issued
return undefined;
}
- ++budget.used;
+ // A previously issued request has been bumped off the priority heap, so cancel it
+ cancelRequest(removedRequest);
}
- return startRequest(request);
+ return issueRequest(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 clearStatistics() {
+ statistics.numberOfAttemptedRequests = 0;
+ statistics.numberOfCancelledRequests = 0;
+ statistics.numberOfCancelledActiveRequests = 0;
}
- function showStats() {
+ function updateStatistics() {
if (!RequestScheduler.debugShowStatistics) {
return;
}
- if (stats.numberOfRequestsThisFrame > 0) {
- console.log('Number of requests attempted: ' + stats.numberOfRequestsThisFrame);
+ if (statistics.numberOfAttemptedRequests > 0) {
+ console.log('Number of attempted requests: ' + statistics.numberOfAttemptedRequests);
}
-
- var numberOfActiveRequests = RequestScheduler.maximumRequests - RequestScheduler.getNumberOfAvailableRequests();
- if (numberOfActiveRequests > 0) {
- console.log('Number of active requests: ' + numberOfActiveRequests);
+ if (statistics.numberOfActiveRequests > 0) {
+ console.log('Number of active requests: ' + statistics.numberOfActiveRequests);
+ }
+ if (statistics.numberOfCancelledRequests > 0) {
+ console.log('Number of cancelled requests: ' + statistics.numberOfCancelledRequests);
+ }
+ if (statistics.numberOfCancelledActiveRequests > 0) {
+ console.log('Number of cancelled active requests: ' + statistics.numberOfCancelledActiveRequests);
+ }
+ if (statistics.numberOfFailedRequests > 0) {
+ console.log('Number of failed requests: ' + statistics.numberOfFailedRequests);
}
+
+ clearStatistics();
}
/**
- * Clears the request scheduler before each spec.
+ * For testing only. Clears any requests that may not have completed from previous tests.
*
* @private
*/
RequestScheduler.clearForSpecs = function() {
- activeRequestsByServer = {};
- activeRequests = 0;
- budgets = [];
- leftoverRequests = [];
- deferredRequests = new Queue();
- stats = {
- numberOfRequestsThisFrame : 0
- };
+ while (requestHeap.length > 0) {
+ var request = requestHeap.pop();
+ cancelRequest(request);
+ }
+ var length = activeRequests.length;
+ for (var i = 0; i < length; ++i) {
+ cancelRequest(activeRequests[i]);
+ }
+ activeRequests.length = 0;
+ numberOfActiveRequestsByServer = {};
+
+ // Clear stats
+ statistics.numberOfAttemptedRequests = 0;
+ statistics.numberOfActiveRequests = 0;
+ statistics.numberOfCancelledRequests = 0;
+ statistics.numberOfCancelledActiveRequests = 0;
+ statistics.numberOfFailedRequests = 0;
+ statistics.numberOfActiveRequestsEver = 0;
};
/**
- * 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
- * affected.
- * @type {Number}
- * @default 6
- */
- RequestScheduler.maximumRequestsPerServer = 6;
-
- /**
- * Specifies the maximum number of requests that can be simultaneously open for all servers. If this value is higher than
- * the number of requests actually allowed by the web browser, Cesium's ability to prioritize requests will be adversely
- * affected.
- * @type {Number}
- * @default 10
- */
- 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
+ * For testing only.
+ *
+ * @private
*/
- RequestScheduler.throttle = true;
+ RequestScheduler.numberOfActiveRequestsByServer = function(serverKey) {
+ return numberOfActiveRequestsByServer[serverKey];
+ };
/**
- * When true, log statistics to the console every frame
- * @type {Boolean}
- * @default false
+ * For testing only.
+ *
+ * @private
*/
- RequestScheduler.debugShowStatistics = false;
+ RequestScheduler.requestHeap = requestHeap;
return RequestScheduler;
});
diff --git a/Source/Core/RequestState.js b/Source/Core/RequestState.js
new file mode 100644
index 00000000000..23498ef57fc
--- /dev/null
+++ b/Source/Core/RequestState.js
@@ -0,0 +1,64 @@
+/*global define*/
+define([
+ '../Core/freezeObject'
+], function(
+ freezeObject) {
+ 'use strict';
+
+ /**
+ * State of the request.
+ *
+ * @exports RequestState
+ */
+ var RequestState = {
+ /**
+ * Initial unissued state.
+ *
+ * @type Number
+ * @constant
+ */
+ UNISSUED : 0,
+
+ /**
+ * Issued but not yet active. Will become active when open slots are available.
+ *
+ * @type Number
+ * @constant
+ */
+ ISSUED : 1,
+
+ /**
+ * Actual http request has been sent.
+ *
+ * @type Number
+ * @constant
+ */
+ ACTIVE : 2,
+
+ /**
+ * Request completed successfully.
+ *
+ * @type Number
+ * @constant
+ */
+ RECEIVED : 3,
+
+ /**
+ * Request was cancelled, either explicitly or automatically because of low priority.
+ *
+ * @type Number
+ * @constant
+ */
+ CANCELLED : 4,
+
+ /**
+ * Request failed.
+ *
+ * @type Number
+ * @constant
+ */
+ FAILED : 5
+ };
+
+ return freezeObject(RequestState);
+});
diff --git a/Source/Core/RequestType.js b/Source/Core/RequestType.js
index 599ee485963..d01348b54d5 100644
--- a/Source/Core/RequestType.js
+++ b/Source/Core/RequestType.js
@@ -6,12 +6,41 @@ define([
'use strict';
/**
- * @private
+ * An enum identifying the type of request. Used for finer grained logging and priority sorting.
+ *
+ * @exports RequestType
*/
var RequestType = {
+ /**
+ * Terrain request.
+ *
+ * @type Number
+ * @constant
+ */
TERRAIN : 0,
+
+ /**
+ * Imagery request.
+ *
+ * @type Number
+ * @constant
+ */
IMAGERY : 1,
+
+ /**
+ * 3D Tiles request.
+ *
+ * @type Number
+ * @constant
+ */
TILES3D : 2,
+
+ /**
+ * Other request.
+ *
+ * @type Number
+ * @constant
+ */
OTHER : 3
};
diff --git a/Source/Core/TerrainProvider.js b/Source/Core/TerrainProvider.js
index 5163bae2986..6bdc2e00001 100644
--- a/Source/Core/TerrainProvider.js
+++ b/Source/Core/TerrainProvider.js
@@ -21,6 +21,7 @@ define([
*
* @see EllipsoidTerrainProvider
* @see CesiumTerrainProvider
+ * @see VRTheWorldTerrainProvider
*/
function TerrainProvider() {
DeveloperError.throwInstantiationError();
@@ -197,9 +198,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. Intended for internal use only.
+ *
* @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 4c6524b01f4..5339deb9c6c 100644
--- a/Source/Core/VRTheWorldTerrainProvider.js
+++ b/Source/Core/VRTheWorldTerrainProvider.js
@@ -5,6 +5,7 @@ define([
'./defaultValue',
'./defined',
'./defineProperties',
+ './deprecationWarning',
'./DeveloperError',
'./Ellipsoid',
'./Event',
@@ -15,8 +16,9 @@ define([
'./loadXML',
'./Math',
'./Rectangle',
+ './Request',
+ './RequestType',
'./TerrainProvider',
- './throttleRequestByServer',
'./TileProviderError'
], function(
when,
@@ -24,6 +26,7 @@ define([
defaultValue,
defined,
defineProperties,
+ deprecationWarning,
DeveloperError,
Ellipsoid,
Event,
@@ -34,8 +37,9 @@ define([
loadXML,
CesiumMath,
Rectangle,
+ Request,
+ RequestType,
TerrainProvider,
- throttleRequestByServer,
TileProviderError) {
'use strict';
@@ -256,14 +260,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. Intended for internal use only.
* @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) {
//>>includeStart('debug', pragmas.debug);
if (!this.ready) {
throw new DeveloperError('requestTileGeometry must not be called before ready returns true.');
@@ -278,16 +280,18 @@ define([
url = proxy.getURL(url);
}
- var promise;
+ if (typeof request === 'boolean') {
+ deprecationWarning('throttleRequests', 'The throttleRequest parameter for requestTileGeometry was deprecated in Cesium 1.35. It will be removed in 1.37.');
+ request = new Request({
+ throttle : request,
+ throttleByServer : request,
+ type : RequestType.TERRAIN
+ });
+ }
- throttleRequests = defaultValue(throttleRequests, true);
- if (throttleRequests) {
- promise = throttleRequestByServer(url, loadImage);
- if (!defined(promise)) {
- return undefined;
- }
- } else {
- promise = loadImage(url);
+ var promise = loadImage(url, undefined, request);
+ if (!defined(promise)) {
+ return undefined;
}
var that = this;
diff --git a/Source/Core/isBlobUri.js b/Source/Core/isBlobUri.js
new file mode 100644
index 00000000000..dc4afb78502
--- /dev/null
+++ b/Source/Core/isBlobUri.js
@@ -0,0 +1,29 @@
+/*global define*/
+define([
+ './Check'
+ ], function(
+ Check) {
+ 'use strict';
+
+ var blobUriRegex = /^blob:/i;
+
+ /**
+ * Determines if the specified uri is a blob uri.
+ *
+ * @exports isBlobUri
+ *
+ * @param {String} uri The uri to test.
+ * @returns {Boolean} true when the uri is a blob uri; otherwise, false.
+ *
+ * @private
+ */
+ function isBlobUri(uri) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.string('uri', uri);
+ //>>includeEnd('debug');
+
+ return blobUriRegex.test(uri);
+ }
+
+ return isBlobUri;
+});
diff --git a/Source/Core/isDataUri.js b/Source/Core/isDataUri.js
index 116630ced11..0cd24a228c1 100644
--- a/Source/Core/isDataUri.js
+++ b/Source/Core/isDataUri.js
@@ -1,8 +1,8 @@
/*global define*/
define([
- './defined'
+ './Check'
], function(
- defined) {
+ Check) {
'use strict';
var dataUriRegex = /^data:/i;
@@ -18,11 +18,11 @@ define([
* @private
*/
function isDataUri(uri) {
- if (defined(uri)) {
- return dataUriRegex.test(uri);
- }
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.string('uri', uri);
+ //>>includeEnd('debug');
- return false;
+ return dataUriRegex.test(uri);
}
return isDataUri;
diff --git a/Source/Core/loadArrayBuffer.js b/Source/Core/loadArrayBuffer.js
index ff557505b73..cc56d573ff6 100644
--- a/Source/Core/loadArrayBuffer.js
+++ b/Source/Core/loadArrayBuffer.js
@@ -13,10 +13,10 @@ define([
*
* @exports loadArrayBuffer
*
- * @param {String|Promise.} url The URL of the binary data, or a promise for the URL.
+ * @param {String} url The URL of the binary data.
* @param {Object} [headers] HTTP headers to send with the requests.
- * @returns {Promise.} a promise that will resolve to the requested data when loaded.
- *
+ * @param {Request} [request] The request object. Intended for internal use only.
+ * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority.
*
* @example
* // load a single URL asynchronously
@@ -25,15 +25,16 @@ define([
* }).otherwise(function(error) {
* // an error occurred
* });
- *
+ *
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
- function loadArrayBuffer(url, headers) {
+ function loadArrayBuffer(url, headers, request) {
return loadWithXhr({
url : url,
responseType : 'arraybuffer',
- headers : headers
+ headers : headers,
+ request : request
});
}
diff --git a/Source/Core/loadBlob.js b/Source/Core/loadBlob.js
index 7d6ef776d5c..0e136d22c37 100644
--- a/Source/Core/loadBlob.js
+++ b/Source/Core/loadBlob.js
@@ -13,10 +13,10 @@ define([
*
* @exports loadBlob
*
- * @param {String|Promise.} url The URL of the data, or a promise for the URL.
+ * @param {String} url The URL of the data.
* @param {Object} [headers] HTTP headers to send with the requests.
- * @returns {Promise.} a promise that will resolve to the requested data when loaded.
- *
+ * @param {Request} [request] The request object. Intended for internal use only.
+ * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority.
*
* @example
* // load a single URL asynchronously
@@ -25,15 +25,16 @@ define([
* }).otherwise(function(error) {
* // an error occurred
* });
- *
+ *
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
- function loadBlob(url, headers) {
+ function loadBlob(url, headers, request) {
return loadWithXhr({
url : url,
responseType : 'blob',
- headers : headers
+ headers : headers,
+ request : request
});
}
diff --git a/Source/Core/loadCRN.js b/Source/Core/loadCRN.js
index f50ebefbfb3..eac7121e100 100644
--- a/Source/Core/loadCRN.js
+++ b/Source/Core/loadCRN.js
@@ -26,9 +26,10 @@ define([
*
* @exports loadCRN
*
- * @param {String|Promise.|ArrayBuffer} urlOrBuffer The URL of the binary data, a promise for the URL, or an ArrayBuffer.
+ * @param {String|ArrayBuffer} urlOrBuffer The URL of the binary data or an ArrayBuffer.
* @param {Object} [headers] HTTP headers to send with the requests.
- * @returns {Promise.} A promise that will resolve to the requested data when loaded.
+ * @param {Request} [request] The request object. Intended for internal use only.
+ * @returns {Promise.|undefined} A promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority.
*
* @exception {RuntimeError} Unsupported compressed format.
*
@@ -48,7 +49,7 @@ define([
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
- function loadCRN(urlOrBuffer, headers) {
+ function loadCRN(urlOrBuffer, headers, request) {
//>>includeStart('debug', pragmas.debug);
if (!defined(urlOrBuffer)) {
throw new DeveloperError('urlOrBuffer is required.');
@@ -59,7 +60,11 @@ define([
if (urlOrBuffer instanceof ArrayBuffer || ArrayBuffer.isView(urlOrBuffer)) {
loadPromise = when.resolve(urlOrBuffer);
} else {
- loadPromise = loadArrayBuffer(urlOrBuffer, headers);
+ loadPromise = loadArrayBuffer(urlOrBuffer, headers, request);
+ }
+
+ if (!defined(loadPromise)) {
+ return undefined;
}
return loadPromise.then(function(data) {
@@ -81,4 +86,4 @@ define([
}
return loadCRN;
-});
\ No newline at end of file
+});
diff --git a/Source/Core/loadImage.js b/Source/Core/loadImage.js
index ba299a24c86..79c0c95673a 100644
--- a/Source/Core/loadImage.js
+++ b/Source/Core/loadImage.js
@@ -1,33 +1,42 @@
/*global define*/
define([
'../ThirdParty/when',
+ './Check',
'./defaultValue',
'./defined',
+ './deprecationWarning',
'./DeveloperError',
'./isCrossOriginUrl',
+ './isDataUri',
+ './Request',
+ './RequestScheduler',
'./TrustedServers'
], function(
when,
+ Check,
defaultValue,
defined,
+ deprecationWarning,
DeveloperError,
isCrossOriginUrl,
+ isDataUri,
+ Request,
+ RequestScheduler,
TrustedServers) {
'use strict';
- var dataUriRegex = /^data:/;
-
/**
* Asynchronously loads the given image URL. Returns a promise that will resolve to
* an {@link Image} once loaded, or reject if the image failed to load.
*
* @exports loadImage
*
- * @param {String|Promise.} url The source of the image, or a promise for the URL.
+ * @param {String} url The source URL of the image.
* @param {Boolean} [allowCrossOrigin=true] Whether to request the image using Cross-Origin
* Resource Sharing (CORS). CORS is only actually used if the image URL is actually cross-origin.
* Data URIs are never requested using CORS.
- * @returns {Promise.} a promise that will resolve to the requested data when loaded.
+ * @param {Request} [request] The request object. Intended for internal use only.
+ * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority.
*
*
* @example
@@ -42,24 +51,37 @@ define([
* when.all([loadImage('image1.png'), loadImage('image2.png')]).then(function(images) {
* // images is an array containing all the loaded images
* });
- *
+ *
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
- function loadImage(url, allowCrossOrigin) {
+ function loadImage(url, allowCrossOrigin, request) {
//>>includeStart('debug', pragmas.debug);
- if (!defined(url)) {
- throw new DeveloperError('url is required.');
- }
+ Check.defined('url', url);
//>>includeEnd('debug');
allowCrossOrigin = defaultValue(allowCrossOrigin, true);
- return when(url, function(url) {
+ if (typeof url !== 'string') {
+ // Returning a promise here is okay because it is unlikely that anyone using the deprecated functionality is also
+ // providing a Request object marked as throttled.
+ deprecationWarning('url promise', 'url as a Promise is deprecated and will be removed in 1.37');
+ return url.then(function(url) {
+ return makeRequest(url, allowCrossOrigin, request);
+ });
+ }
+
+ return makeRequest(url, allowCrossOrigin, request);
+ }
+
+ function makeRequest(url, allowCrossOrigin, request) {
+ request = defined(request) ? request : new Request();
+ request.url = url;
+ request.requestFunction = function() {
var crossOrigin;
// data URIs can't have allowCrossOrigin set.
- if (dataUriRegex.test(url) || !allowCrossOrigin) {
+ if (isDataUri(url) || !allowCrossOrigin) {
crossOrigin = false;
} else {
crossOrigin = isCrossOriginUrl(url);
@@ -70,7 +92,9 @@ define([
loadImage.createImage(url, crossOrigin, deferred);
return deferred.promise;
- });
+ };
+
+ return RequestScheduler.request(request);
}
// This is broken out into a separate function so that it can be mocked for testing purposes.
diff --git a/Source/Core/loadImageFromTypedArray.js b/Source/Core/loadImageFromTypedArray.js
index 9be887ab39c..e7f636031aa 100644
--- a/Source/Core/loadImageFromTypedArray.js
+++ b/Source/Core/loadImageFromTypedArray.js
@@ -1,11 +1,13 @@
/*global define*/
define([
'../ThirdParty/when',
+ './Check',
'./defined',
'./DeveloperError',
'./loadImage'
], function(
when,
+ Check,
defined,
DeveloperError,
loadImage) {
@@ -14,15 +16,10 @@ define([
/**
* @private
*/
- function loadImageFromTypedArray(uint8Array, format) {
+ function loadImageFromTypedArray(uint8Array, format, request) {
//>>includeStart('debug', pragmas.debug);
- if (!defined(uint8Array)) {
- throw new DeveloperError('uint8Array is required.');
- }
-
- if (!defined(format)) {
- throw new DeveloperError('format is required.');
- }
+ Check.typeOf.object('uint8Array', uint8Array);
+ Check.typeOf.string('format', format);
//>>includeEnd('debug');
var blob = new Blob([uint8Array], {
@@ -30,7 +27,7 @@ define([
});
var blobUrl = window.URL.createObjectURL(blob);
- return loadImage(blobUrl, false).then(function(image) {
+ return loadImage(blobUrl, false, request).then(function(image) {
window.URL.revokeObjectURL(blobUrl);
return image;
}, function(error) {
diff --git a/Source/Core/loadImageViaBlob.js b/Source/Core/loadImageViaBlob.js
index 35118d55658..9d2d3292453 100644
--- a/Source/Core/loadImageViaBlob.js
+++ b/Source/Core/loadImageViaBlob.js
@@ -1,16 +1,18 @@
/*global define*/
define([
'../ThirdParty/when',
+ './defined',
+ './isDataUri',
'./loadBlob',
'./loadImage'
], function(
when,
+ defined,
+ isDataUri,
loadBlob,
loadImage) {
'use strict';
- var dataUriRegex = /^data:/;
-
/**
* Asynchronously loads the given image URL by first downloading it as a blob using
* XMLHttpRequest and then loading the image from the buffer via a blob URL.
@@ -25,8 +27,9 @@ define([
*
* @exports loadImageViaBlob
*
- * @param {String|Promise.} url The source of the image, or a promise for the URL.
- * @returns {Promise.} a promise that will resolve to the requested data when loaded.
+ * @param {String} url The source URL of the image.
+ * @param {Request} [request] The request object. Intended for internal use only.
+ * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority.
*
*
* @example
@@ -42,16 +45,21 @@ define([
* when.all([loadImageViaBlob('image1.png'), loadImageViaBlob('image2.png')]).then(function(images) {
* // images is an array containing all the loaded images
* });
- *
+ *
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
- function loadImageViaBlob(url) {
- if (dataUriRegex.test(url)) {
- return loadImage(url);
+ function loadImageViaBlob(url, request) {
+ if (!xhrBlobSupported || isDataUri(url)) {
+ return loadImage(url, undefined, request);
+ }
+
+ var blobPromise = loadBlob(url, undefined, request);
+ if (!defined(blobPromise)) {
+ return undefined;
}
- return loadBlob(url).then(function(blob) {
+ return blobPromise.then(function(blob) {
var blobUrl = window.URL.createObjectURL(blob);
return loadImage(blobUrl, false).then(function(image) {
@@ -76,5 +84,5 @@ define([
}
})();
- return xhrBlobSupported ? loadImageViaBlob : loadImage;
+ return loadImageViaBlob;
});
diff --git a/Source/Core/loadJson.js b/Source/Core/loadJson.js
index 01cce7033f1..1fd1de5133c 100644
--- a/Source/Core/loadJson.js
+++ b/Source/Core/loadJson.js
@@ -26,11 +26,12 @@ define([
*
* @exports loadJson
*
- * @param {String|Promise.} url The URL to request, or a promise for the URL.
+ * @param {String} url The URL to request.
* @param {Object} [headers] HTTP headers to send with the request.
* 'Accept: application/json,*/*;q=0.01' is added to the request headers automatically
* if not specified.
- * @returns {Promise.} a promise that will resolve to the requested data when loaded.
+ * @param {Request} [request] The request object. Intended for internal use only.
+ * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority.
*
*
* @example
@@ -39,12 +40,12 @@ define([
* }).otherwise(function(error) {
* // an error occurred
* });
- *
+ *
* @see loadText
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
- function loadJson(url, headers) {
+ function loadJson(url, headers, request) {
//>>includeStart('debug', pragmas.debug);
if (!defined(url)) {
throw new DeveloperError('url is required.');
@@ -59,7 +60,12 @@ define([
headers.Accept = defaultHeaders.Accept;
}
- return loadText(url, headers).then(function(value) {
+ var textPromise = loadText(url, headers, request);
+ if (!defined(textPromise)) {
+ return undefined;
+ }
+
+ return textPromise.then(function(value) {
return JSON.parse(value);
});
}
diff --git a/Source/Core/loadJsonp.js b/Source/Core/loadJsonp.js
index 730c19b865b..c63f3b58b0b 100644
--- a/Source/Core/loadJsonp.js
+++ b/Source/Core/loadJsonp.js
@@ -7,7 +7,9 @@ define([
'./defined',
'./DeveloperError',
'./objectToQuery',
- './queryToObject'
+ './queryToObject',
+ './Request',
+ './RequestScheduler'
], function(
Uri,
when,
@@ -16,7 +18,9 @@ define([
defined,
DeveloperError,
objectToQuery,
- queryToObject) {
+ queryToObject,
+ Request,
+ RequestScheduler) {
'use strict';
/**
@@ -29,7 +33,8 @@ define([
* @param {Object} [options.parameters] Any extra query parameters to append to the URL.
* @param {String} [options.callbackParameterName='callback'] The callback parameter name that the server expects.
* @param {Proxy} [options.proxy] A proxy to use for the request. This object is expected to have a getURL function which returns the proxied URL, if needed.
- * @returns {Promise.} a promise that will resolve to the requested data when loaded.
+ * @param {Request} [request] The request object. Intended for internal use only.
+ * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority.
*
*
* @example
@@ -39,10 +44,10 @@ define([
* }).otherwise(function(error) {
* // an error occurred
* });
- *
+ *
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
- function loadJsonp(url, options) {
+ function loadJsonp(url, options, request) {
//>>includeStart('debug', pragmas.debug);
if (!defined(url)) {
throw new DeveloperError('url is required.');
@@ -57,19 +62,6 @@ define([
functionName = 'loadJsonp' + Math.random().toString().substring(2, 8);
} while (defined(window[functionName]));
- var deferred = when.defer();
-
- //assign a function with that name in the global scope
- window[functionName] = function(data) {
- deferred.resolve(data);
-
- try {
- delete window[functionName];
- } catch (e) {
- window[functionName] = undefined;
- }
- };
-
var uri = new Uri(url);
var queryOptions = queryToObject(defaultValue(uri.query, ''));
@@ -90,9 +82,27 @@ define([
url = proxy.getURL(url);
}
- loadJsonp.loadAndExecuteScript(url, functionName, deferred);
+ request = defined(request) ? request : new Request();
+ request.url = url;
+ request.requestFunction = function() {
+ var deferred = when.defer();
+
+ //assign a function with that name in the global scope
+ window[functionName] = function(data) {
+ deferred.resolve(data);
+
+ try {
+ delete window[functionName];
+ } catch (e) {
+ window[functionName] = undefined;
+ }
+ };
+
+ loadJsonp.loadAndExecuteScript(url, functionName, deferred);
+ return deferred.promise;
+ };
- return deferred.promise;
+ return RequestScheduler.request(request);
}
// This is broken out into a separate function so that it can be mocked for testing purposes.
diff --git a/Source/Core/loadKTX.js b/Source/Core/loadKTX.js
index 5baa4b493df..cd8600b10d1 100644
--- a/Source/Core/loadKTX.js
+++ b/Source/Core/loadKTX.js
@@ -37,9 +37,10 @@ define([
*
* @exports loadKTX
*
- * @param {String|Promise.|ArrayBuffer} urlOrBuffer The URL of the binary data, a promise for the URL, or an ArrayBuffer.
+ * @param {String|ArrayBuffer} urlOrBuffer The URL of the binary data or an ArrayBuffer.
* @param {Object} [headers] HTTP headers to send with the requests.
- * @returns {Promise.} A promise that will resolve to the requested data when loaded.
+ * @param {Request} [request] The request object. Intended for internal use only.
+ * @returns {Promise.|undefined} A promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority.
*
* @exception {RuntimeError} Invalid KTX file.
* @exception {RuntimeError} File is the wrong endianness.
@@ -69,7 +70,7 @@ define([
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
- function loadKTX(urlOrBuffer, headers) {
+ function loadKTX(urlOrBuffer, headers, request) {
//>>includeStart('debug', pragmas.debug);
if (!defined(urlOrBuffer)) {
throw new DeveloperError('urlOrBuffer is required.');
@@ -80,7 +81,11 @@ define([
if (urlOrBuffer instanceof ArrayBuffer || ArrayBuffer.isView(urlOrBuffer)) {
loadPromise = when.resolve(urlOrBuffer);
} else {
- loadPromise = loadArrayBuffer(urlOrBuffer, headers);
+ loadPromise = loadArrayBuffer(urlOrBuffer, headers, request);
+ }
+
+ if (!defined(loadPromise)) {
+ return undefined;
}
return loadPromise.then(function(data) {
diff --git a/Source/Core/loadText.js b/Source/Core/loadText.js
index 342963ae9b2..8ca386fe1bf 100644
--- a/Source/Core/loadText.js
+++ b/Source/Core/loadText.js
@@ -13,9 +13,10 @@ define([
*
* @exports loadText
*
- * @param {String|Promise.} url The URL to request, or a promise for the URL.
+ * @param {String} url The URL to request.
* @param {Object} [headers] HTTP headers to send with the request.
- * @returns {Promise.} a promise that will resolve to the requested data when loaded.
+ * @param {Request} [request] The request object. Intended for internal use only.
+ * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority.
*
*
* @example
@@ -27,15 +28,16 @@ define([
* }).otherwise(function(error) {
* // an error occurred
* });
- *
+ *
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest}
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
- function loadText(url, headers) {
+ function loadText(url, headers, request) {
return loadWithXhr({
url : url,
- headers : headers
+ headers : headers,
+ request : request
});
}
diff --git a/Source/Core/loadWithXhr.js b/Source/Core/loadWithXhr.js
index a44debf511d..f5e2690aa77 100644
--- a/Source/Core/loadWithXhr.js
+++ b/Source/Core/loadWithXhr.js
@@ -1,18 +1,26 @@
/*global define*/
define([
'../ThirdParty/when',
+ './Check',
'./defaultValue',
'./defined',
+ './deprecationWarning',
'./DeveloperError',
+ './Request',
'./RequestErrorEvent',
+ './RequestScheduler',
'./RuntimeError',
'./TrustedServers'
], function(
when,
+ Check,
defaultValue,
defined,
+ deprecationWarning,
DeveloperError,
+ Request,
RequestErrorEvent,
+ RequestScheduler,
RuntimeError,
TrustedServers) {
'use strict';
@@ -26,13 +34,14 @@ define([
* @exports loadWithXhr
*
* @param {Object} options Object with the following properties:
- * @param {String|Promise.} options.url The URL of the data, or a promise for the URL.
+ * @param {String} options.url The URL of the data.
* @param {String} [options.responseType] The type of response. This controls the type of item returned.
* @param {String} [options.method='GET'] The HTTP method to use.
* @param {String} [options.data] The data to send with the request, if any.
* @param {Object} [options.headers] HTTP headers to send with the request, if any.
* @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
- * @returns {Promise.} a promise that will resolve to the requested data when loaded.
+ * @param {Request} [options.request] The request object.
+ * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority.
*
*
* @example
@@ -57,24 +66,45 @@ define([
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
//>>includeStart('debug', pragmas.debug);
- if (!defined(options.url)) {
- throw new DeveloperError('options.url is required.');
- }
+ Check.defined('options.url', options.url);
//>>includeEnd('debug');
+ var url = options.url;
+
+ if (typeof url !== 'string') {
+ // Returning a promise here is okay because it is unlikely that anyone using the deprecated functionality is also
+ // providing a Request object marked as throttled.
+ deprecationWarning('url promise', 'options.url as a Promise is deprecated and will be removed in Cesium 1.37');
+ return url.then(function(url) {
+ return makeRequest(options, url);
+ });
+ }
+
+ return makeRequest(options);
+ }
+
+ function makeRequest(options, url) {
var responseType = options.responseType;
var method = defaultValue(options.method, 'GET');
var data = options.data;
var headers = options.headers;
var overrideMimeType = options.overrideMimeType;
+ url = defaultValue(url, options.url);
- return when(options.url, function(url) {
+ var request = defined(options.request) ? options.request : new Request();
+ request.url = url;
+ request.requestFunction = function() {
var deferred = when.defer();
-
- loadWithXhr.load(url, responseType, method, data, headers, deferred, overrideMimeType);
-
+ var xhr = loadWithXhr.load(url, responseType, method, data, headers, deferred, overrideMimeType);
+ if (defined(xhr) && defined(xhr.abort)) {
+ request.cancelFunction = function() {
+ xhr.abort();
+ };
+ }
return deferred.promise;
- });
+ };
+
+ return RequestScheduler.request(request);
}
var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
@@ -192,6 +222,8 @@ define([
};
xhr.send(data);
+
+ return xhr;
};
loadWithXhr.defaultLoad = loadWithXhr.load;
diff --git a/Source/Core/loadXML.js b/Source/Core/loadXML.js
index b9f6915d4e3..780ff6ad5ee 100644
--- a/Source/Core/loadXML.js
+++ b/Source/Core/loadXML.js
@@ -13,9 +13,10 @@ define([
*
* @exports loadXML
*
- * @param {String|Promise.} url The URL to request, or a promise for the URL.
+ * @param {String} url The URL to request.
* @param {Object} [headers] HTTP headers to send with the request.
- * @returns {Promise.} a promise that will resolve to the requested data when loaded.
+ * @param {Request} [request] The request object. Intended for internal use only.
+ * @returns {Promise.|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if request.throttle is true and the request does not have high enough priority.
*
*
* @example
@@ -27,17 +28,18 @@ define([
* }).otherwise(function(error) {
* // an error occurred
* });
- *
+ *
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest}
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
- function loadXML(url, headers) {
+ function loadXML(url, headers, request) {
return loadWithXhr({
url : url,
responseType : 'document',
headers : headers,
- overrideMimeType : 'text/xml'
+ overrideMimeType : 'text/xml',
+ request : request
});
}
diff --git a/Source/Core/sampleTerrain.js b/Source/Core/sampleTerrain.js
index 909ba8020da..8a43a84b862 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/Core/throttleRequestByServer.js b/Source/Core/throttleRequestByServer.js
deleted file mode 100644
index 14debf754c5..00000000000
--- a/Source/Core/throttleRequestByServer.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/*global define*/
-define([
- '../ThirdParty/Uri',
- '../ThirdParty/when',
- './defaultValue'
- ], function(
- Uri,
- when,
- defaultValue) {
- 'use strict';
-
- var activeRequests = {};
-
- var pageUri = typeof document !== 'undefined' ? new Uri(document.location.href) : new Uri();
- function getServer(url) {
- var uri = new Uri(url).resolve(pageUri);
- uri.normalize();
- var server = uri.authority;
- if (!/:/.test(server)) {
- server = server + ':' + (uri.scheme === 'https' ? '443' : '80');
- }
- return server;
- }
-
- /**
- * Because browsers throttle the number of parallel requests allowed to each server,
- * this function tracks the number of active requests in progress to each server, and
- * 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.
- *
- * @exports throttleRequestByServer
- *
- * @param {String} url The URL to request.
- * @param {throttleRequestByServer~RequestFunction} requestFunction The actual function that
- * makes the request.
- * @returns {Promise.|undefined} Either undefined, meaning the request would exceed the maximum number of
- * parallel requests, or a Promise for the requested data.
- *
- *
- * @example
- * // throttle requests for an image
- * var url = 'http://madeupserver.example.com/myImage.png';
- * function requestFunction(url) {
- * // in this simple example, loadImage could be used directly as requestFunction.
- * return Cesium.loadImage(url);
- * };
- * var promise = Cesium.throttleRequestByServer(url, requestFunction);
- * if (!Cesium.defined(promise)) {
- * // too many active requests in progress, try again later.
- * } else {
- * promise.then(function(image) {
- * // handle loaded image
- * });
- * }
- *
- * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
- */
- function throttleRequestByServer(url, requestFunction) {
- var server = getServer(url);
-
- var activeRequestsForServer = defaultValue(activeRequests[server], 0);
- if (activeRequestsForServer >= throttleRequestByServer.maximumRequestsPerServer) {
- return undefined;
- }
-
- activeRequests[server] = activeRequestsForServer + 1;
-
- return when(requestFunction(url), function(result) {
- activeRequests[server]--;
- return result;
- }).otherwise(function(error) {
- activeRequests[server]--;
- return when.reject(error);
- });
- }
-
- /**
- * 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
- * affected.
- * @type {Number}
- * @default 6
- */
- throttleRequestByServer.maximumRequestsPerServer = 6;
-
- /**
- * A function that will make a request if there are available slots to the server.
- * @callback throttleRequestByServer~RequestFunction
- *
- * @param {String} url The url to request.
- * @returns {Promise.} A promise for the requested data.
- */
-
- return throttleRequestByServer;
-});
diff --git a/Source/Renderer/loadCubeMap.js b/Source/Renderer/loadCubeMap.js
index d576abf3993..81d6528aff5 100644
--- a/Source/Renderer/loadCubeMap.js
+++ b/Source/Renderer/loadCubeMap.js
@@ -20,7 +20,7 @@ define([
* @exports loadCubeMap
*
* @param {Context} context The context to use to create the cube map.
- * @param {Object} urls The source of each image, or a promise for each URL. See the example below.
+ * @param {Object} urls The source URL of each image. See the example below.
* @param {Boolean} [allowCrossOrigin=true] Whether to request the image using Cross-Origin
* Resource Sharing (CORS). CORS is only actually used if the image URL is actually cross-origin.
* Data URIs are never requested using CORS.
@@ -43,7 +43,7 @@ define([
* }).otherwise(function(error) {
* // an error occurred
* });
- *
+ *
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*
diff --git a/Source/Scene/ArcGisMapServerImageryProvider.js b/Source/Scene/ArcGisMapServerImageryProvider.js
index fd0165ccdde..fa53f26b766 100644
--- a/Source/Scene/ArcGisMapServerImageryProvider.js
+++ b/Source/Scene/ArcGisMapServerImageryProvider.js
@@ -579,6 +579,7 @@ define([
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
+ * @param {Request} [request] The request object. Intended for internal use only.
* @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
@@ -586,7 +587,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, request) {
//>>includeStart('debug', pragmas.debug);
if (!this._ready) {
throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
@@ -594,7 +595,7 @@ define([
//>>includeEnd('debug');
var url = buildImageUrl(this, x, y, level);
- return ImageryProvider.loadImage(this, url);
+ return ImageryProvider.loadImage(this, url, request);
};
/**
diff --git a/Source/Scene/BingMapsImageryProvider.js b/Source/Scene/BingMapsImageryProvider.js
index ed6ffc92bad..fdcf1531b7c 100644
--- a/Source/Scene/BingMapsImageryProvider.js
+++ b/Source/Scene/BingMapsImageryProvider.js
@@ -522,6 +522,7 @@ define([
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
+ * @param {Request} [request] The request object. Intended for internal use only.
* @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
@@ -529,7 +530,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, request) {
//>>includeStart('debug', pragmas.debug);
if (!this._ready) {
throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
@@ -537,7 +538,7 @@ define([
//>>includeEnd('debug');
var url = buildImageUrl(this, x, y, level);
- return ImageryProvider.loadImage(this, url);
+ return ImageryProvider.loadImage(this, url, request);
};
/**
diff --git a/Source/Scene/Camera.js b/Source/Scene/Camera.js
index 5e3180a55f5..029b8dfc6ad 100644
--- a/Source/Scene/Camera.js
+++ b/Source/Scene/Camera.js
@@ -608,14 +608,17 @@ define([
if (directionChanged || transformChanged) {
camera._directionWC = Matrix4.multiplyByPointAsVector(transform, direction, camera._directionWC);
+ Cartesian3.normalize(camera._directionWC, camera._directionWC);
}
if (upChanged || transformChanged) {
camera._upWC = Matrix4.multiplyByPointAsVector(transform, up, camera._upWC);
+ Cartesian3.normalize(camera._upWC, camera._upWC);
}
if (rightChanged || transformChanged) {
camera._rightWC = Matrix4.multiplyByPointAsVector(transform, right, camera._rightWC);
+ Cartesian3.normalize(camera._rightWC, camera._rightWC);
}
if (positionChanged || directionChanged || upChanged || rightChanged || transformChanged) {
@@ -1817,30 +1820,57 @@ define([
}
//>>includeEnd('debug');
+ var ratio;
amount = amount * 0.5;
- var newRight = frustum.right - amount;
- var newLeft = frustum.left + amount;
- var maxRight = camera._maxCoord.x;
- if (camera._scene.mapMode2D === MapMode2D.ROTATE) {
- maxRight *= camera.maximumZoomFactor;
- }
+ if((Math.abs(frustum.top) + Math.abs(frustum.bottom)) > (Math.abs(frustum.left) + Math.abs(frustum.right))) {
+ var newTop = frustum.top - amount;
+ var newBottom = frustum.bottom + amount;
- if (newRight > maxRight) {
- newRight = maxRight;
- newLeft = -maxRight;
- }
+ var maxBottom = camera._maxCoord.y;
+ if (camera._scene.mapMode2D === MapMode2D.ROTATE) {
+ maxBottom *= camera.maximumZoomFactor;
+ }
- if (newRight <= newLeft) {
- newRight = 1.0;
- newLeft = -1.0;
- }
+ if (newBottom > maxBottom) {
+ newBottom = maxBottom;
+ newTop = -maxBottom;
+ }
+
+ if (newTop <= newBottom) {
+ newTop = 1.0;
+ newBottom = -1.0;
+ }
+
+ ratio = frustum.right / frustum.top;
+ frustum.top = newTop;
+ frustum.bottom = newBottom;
+ frustum.right = frustum.top * ratio;
+ frustum.left = -frustum.right;
+ } else {
+ var newRight = frustum.right - amount;
+ var newLeft = frustum.left + amount;
- var ratio = frustum.top / frustum.right;
- frustum.right = newRight;
- frustum.left = newLeft;
- frustum.top = frustum.right * ratio;
- frustum.bottom = -frustum.top;
+ var maxRight = camera._maxCoord.x;
+ if (camera._scene.mapMode2D === MapMode2D.ROTATE) {
+ maxRight *= camera.maximumZoomFactor;
+ }
+
+ if (newRight > maxRight) {
+ newRight = maxRight;
+ newLeft = -maxRight;
+ }
+
+ if (newRight <= newLeft) {
+ newRight = 1.0;
+ newLeft = -1.0;
+ }
+ ratio = frustum.top / frustum.right;
+ frustum.right = newRight;
+ frustum.left = newLeft;
+ frustum.top = frustum.right * ratio;
+ frustum.bottom = -frustum.top;
+ }
}
function zoom3D(camera, amount) {
diff --git a/Source/Scene/GlobeSurfaceTile.js b/Source/Scene/GlobeSurfaceTile.js
index d386273ab93..1e27a4df4f8 100644
--- a/Source/Scene/GlobeSurfaceTile.js
+++ b/Source/Scene/GlobeSurfaceTile.js
@@ -237,10 +237,39 @@ 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
+ });
+ }
+
+ function createPriorityFunction(surfaceTile, frameState) {
+ return function() {
+ return surfaceTile.tileBoundingRegion.distanceToCamera(frameState);
+ };
+ }
+
GlobeSurfaceTile.processStateMachine = function(tile, frameState, terrainProvider, imageryLayerCollection, vertexArraysToDestroy) {
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);
+ }
+
+ if (!defined(tile._priorityFunction)) {
+ // The priority function is used to prioritize requests among all requested tiles
+ tile._priorityFunction = createPriorityFunction(surfaceTile, frameState);
}
if (tile.state === QuadtreeTileLoadState.START) {
@@ -307,6 +336,7 @@ define([
if (isDoneLoading) {
tile.state = QuadtreeTileLoadState.DONE;
+ tile._priorityFunction = undefined;
}
}
};
@@ -339,7 +369,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._priorityFunction);
// 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/GoogleEarthEnterpriseImageryProvider.js b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js
index 962914509b2..3dfb87bbcf2 100644
--- a/Source/Scene/GoogleEarthEnterpriseImageryProvider.js
+++ b/Source/Scene/GoogleEarthEnterpriseImageryProvider.js
@@ -13,8 +13,9 @@ define([
'../Core/loadImageFromTypedArray',
'../Core/Math',
'../Core/Rectangle',
+ '../Core/Request',
+ '../Core/RequestType',
'../Core/RuntimeError',
- '../Core/throttleRequestByServer',
'../Core/TileProviderError',
'../ThirdParty/protobuf-minimal',
'../ThirdParty/when'
@@ -32,8 +33,9 @@ define([
loadImageFromTypedArray,
CesiumMath,
Rectangle,
+ Request,
+ RequestType,
RuntimeError,
- throttleRequestByServer,
TileProviderError,
protobuf,
when) {
@@ -430,6 +432,7 @@ define([
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
+ * @param {Request} [request] The request object. Intended for internal use only.
* @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
@@ -437,7 +440,7 @@ define([
*
* @exception {DeveloperError} requestImage must not be called before the imagery provider is ready.
*/
- GoogleEarthEnterpriseImageryProvider.prototype.requestImage = function(x, y, level) {
+ GoogleEarthEnterpriseImageryProvider.prototype.requestImage = function(x, y, level, request) {
//>>includeStart('debug', pragmas.debug);
if (!this._ready) {
throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
@@ -450,7 +453,13 @@ define([
var info = metadata.getTileInformation(x, y, level);
if (!defined(info)) {
if (metadata.isValid(quadKey)) {
- metadata.populateSubtree(x, y, level);
+ var metadataRequest = new Request({
+ throttle : request.throttle,
+ throttleByServer : request.throttleByServer,
+ type : request.type,
+ priorityFunction : request.priorityFunction
+ });
+ metadata.populateSubtree(x, y, level, metadataRequest);
return undefined; // No metadata so return undefined so we can be loaded later
} else {
return invalidImage; // Image doesn't exist
@@ -463,9 +472,9 @@ define([
}
// Load the
var url = buildImageUrl(this, info, x, y, level);
- var promise = throttleRequestByServer(url, loadArrayBuffer);
+ var promise = loadArrayBuffer(url, undefined, request);
if (!defined(promise)) {
- return undefined; //Throttled
+ return undefined; // Throttled
}
return promise
diff --git a/Source/Scene/GoogleEarthEnterpriseMapsProvider.js b/Source/Scene/GoogleEarthEnterpriseMapsProvider.js
index a1657c86ded..4251d3d2d40 100644
--- a/Source/Scene/GoogleEarthEnterpriseMapsProvider.js
+++ b/Source/Scene/GoogleEarthEnterpriseMapsProvider.js
@@ -538,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 {Request} [request] The request object. Intended for internal use only.
* @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
@@ -545,7 +546,7 @@ define([
*
* @exception {DeveloperError} requestImage must not be called before the imagery provider is ready.
*/
- GoogleEarthEnterpriseMapsProvider.prototype.requestImage = function(x, y, level) {
+ GoogleEarthEnterpriseMapsProvider.prototype.requestImage = function(x, y, level, request) {
//>>includeStart('debug', pragmas.debug);
if (!this._ready) {
throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
@@ -553,7 +554,7 @@ define([
//>>includeEnd('debug');
var url = buildImageUrl(this, x, y, level);
- return ImageryProvider.loadImage(this, url);
+ return ImageryProvider.loadImage(this, url, request);
};
/**
diff --git a/Source/Scene/GoogleEarthImageryProvider.js b/Source/Scene/GoogleEarthImageryProvider.js
index 7f1e076533c..cca47c60bf3 100644
--- a/Source/Scene/GoogleEarthImageryProvider.js
+++ b/Source/Scene/GoogleEarthImageryProvider.js
@@ -11,7 +11,7 @@ define([
* Provides tiled imagery using the Google Earth Imagery API.
*
* Notes: This imagery provider was deprecated in Cesium 1.35 and replaced with {@link GoogleEarthEnterpriseMapsProvider}.
- * These are for use with the 2D Maps API. For 3D Earth API uses, see {@link GoogleEarthEngerpriseImageryProvider}.
+ * These are for use with the 2D Maps API. For 3D Earth API uses, see {@link GoogleEarthEnterpriseImageryProvider}.
* GoogleEarthImageryProvider will be removed in Cesium 1.37.
*
* @alias GoogleEarthImageryProvider
diff --git a/Source/Scene/GridImageryProvider.js b/Source/Scene/GridImageryProvider.js
index 779449aa53b..7bfa4f16485 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 {Request} [request] The request object. Intended for internal use only.
* @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, request) {
return this._canvas;
};
diff --git a/Source/Scene/Imagery.js b/Source/Scene/Imagery.js
index 607fb1319cc..07414e2ac3e 100644
--- a/Source/Scene/Imagery.js
+++ b/Source/Scene/Imagery.js
@@ -2,10 +2,12 @@
define([
'../Core/defined',
'../Core/destroyObject',
+ '../Core/RequestState',
'./ImageryState'
], function(
defined,
destroyObject,
+ RequestState,
ImageryState) {
'use strict';
@@ -20,6 +22,7 @@ define([
this.x = x;
this.y = y;
this.level = level;
+ this.request = undefined;
if (level !== 0) {
var parentX = x / 2 | 0;
@@ -84,10 +87,10 @@ define([
return this.referenceCount;
};
- Imagery.prototype.processStateMachine = function(frameState, needGeographicProjection) {
+ Imagery.prototype.processStateMachine = function(frameState, needGeographicProjection, priorityFunction) {
if (this.state === ImageryState.UNLOADED) {
this.state = ImageryState.TRANSITIONING;
- this.imageryLayer._requestImagery(this);
+ this.imageryLayer._requestImagery(this, priorityFunction);
}
if (this.state === ImageryState.RECEIVED) {
diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js
index 9fef8eeac77..39d7014322f 100644
--- a/Source/Scene/ImageryLayer.js
+++ b/Source/Scene/ImageryLayer.js
@@ -12,6 +12,9 @@ define([
'../Core/Math',
'../Core/PixelFormat',
'../Core/Rectangle',
+ '../Core/Request',
+ '../Core/RequestState',
+ '../Core/RequestType',
'../Core/TerrainProvider',
'../Core/TileProviderError',
'../Core/WebMercatorProjection',
@@ -49,6 +52,9 @@ define([
CesiumMath,
PixelFormat,
Rectangle,
+ Request,
+ RequestState,
+ RequestType,
TerrainProvider,
TileProviderError,
WebMercatorProjection,
@@ -656,8 +662,9 @@ define([
* @private
*
* @param {Imagery} imagery The imagery to request.
+ * @param {Function} [priorityFunction] The priority function used for sorting the imagery request.
*/
- ImageryLayer.prototype._requestImagery = function(imagery) {
+ ImageryLayer.prototype._requestImagery = function(imagery, priorityFunction) {
var imageryProvider = this._imageryProvider;
var that = this;
@@ -669,14 +676,23 @@ define([
imagery.image = image;
imagery.state = ImageryState.RECEIVED;
+ imagery.request = undefined;
TileProviderError.handleSuccess(that._requestImageError);
}
function failure(e) {
+ if (imagery.request.state === RequestState.CANCELLED) {
+ // Cancelled due to low priority - try again later.
+ imagery.state = ImageryState.UNLOADED;
+ imagery.request = undefined;
+ return;
+ }
+
// Initially assume failure. handleError may retry, in which case the state will
// change to TRANSITIONING.
imagery.state = ImageryState.FAILED;
+ imagery.request = undefined;
var message = 'Failed to obtain image tile X: ' + imagery.x + ' Y: ' + imagery.y + ' Level: ' + imagery.level + '.';
that._requestImageError = TileProviderError.handleError(
@@ -690,12 +706,20 @@ define([
}
function doRequest() {
+ var request = new Request({
+ throttle : true,
+ throttleByServer : true,
+ type : RequestType.IMAGERY,
+ priorityFunction : priorityFunction
+ });
+ imagery.request = request;
imagery.state = ImageryState.TRANSITIONING;
- var imagePromise = imageryProvider.requestImage(imagery.x, imagery.y, imagery.level);
+ var imagePromise = imageryProvider.requestImage(imagery.x, imagery.y, imagery.level, request);
if (!defined(imagePromise)) {
// Too many parallel requests, so postpone loading tile.
imagery.state = ImageryState.UNLOADED;
+ imagery.request = undefined;
return;
}
diff --git a/Source/Scene/ImageryProvider.js b/Source/Scene/ImageryProvider.js
index 9d52032c6c1..36c5a2eb573 100644
--- a/Source/Scene/ImageryProvider.js
+++ b/Source/Scene/ImageryProvider.js
@@ -6,8 +6,7 @@ define([
'../Core/loadCRN',
'../Core/loadImage',
'../Core/loadImageViaBlob',
- '../Core/loadKTX',
- '../Core/throttleRequestByServer'
+ '../Core/loadKTX'
], function(
defined,
defineProperties,
@@ -15,8 +14,7 @@ define([
loadCRN,
loadImage,
loadImageViaBlob,
- loadKTX,
- throttleRequestByServer) {
+ loadKTX) {
'use strict';
/**
@@ -268,6 +266,7 @@ define([
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
+ * @param {Request} [request] The request object. Intended for internal use only.
* @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
@@ -308,20 +307,22 @@ define([
*
* @param {ImageryProvider} imageryProvider The imagery provider for the URL.
* @param {String} url The URL of the image.
+ * @param {Request} [request] The request object. Intended for internal use only.
* @returns {Promise.|undefined} A promise for the image that will resolve when the image is available, or
* undefined if there are too many active requests to the server, and the request
* should be retried later. The resolved image may be either an
* Image or a Canvas DOM object.
*/
- ImageryProvider.loadImage = function(imageryProvider, url) {
+ ImageryProvider.loadImage = function(imageryProvider, url, request) {
if (ktxRegex.test(url)) {
- return throttleRequestByServer(url, loadKTX);
+ return loadKTX(url, undefined, request);
} else if (crnRegex.test(url)) {
- return throttleRequestByServer(url, loadCRN);
+ return loadCRN(url, undefined, request);
} else if (defined(imageryProvider.tileDiscardPolicy)) {
- return throttleRequestByServer(url, loadImageViaBlob);
+ return loadImageViaBlob(url, request);
}
- return throttleRequestByServer(url, loadImage);
+
+ return loadImage(url, undefined, request);
};
return ImageryProvider;
diff --git a/Source/Scene/MapboxImageryProvider.js b/Source/Scene/MapboxImageryProvider.js
index 4e725a2023f..ac2949df0c5 100644
--- a/Source/Scene/MapboxImageryProvider.js
+++ b/Source/Scene/MapboxImageryProvider.js
@@ -317,6 +317,7 @@ define([
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
+ * @param {Request} [request] The request object. Intended for internal use only.
* @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
@@ -324,8 +325,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, request) {
+ return this._imageryProvider.requestImage(x, y, level, request);
};
/**
diff --git a/Source/Scene/Model.js b/Source/Scene/Model.js
index 2b18b81397a..01f858ad35c 100644
--- a/Source/Scene/Model.js
+++ b/Source/Scene/Model.js
@@ -156,8 +156,8 @@ define([
FAILED : 3
};
- // GLTF_SPEC: Figure out correct mime types (https://github.com/KhronosGroup/glTF/issues/412)
- var defaultModelAccept = 'model/vnd.gltf.binary,model/vnd.gltf+json,model/gltf.binary,model/gltf+json;q=0.8,application/json;q=0.2,*/*;q=0.01';
+ // glTF MIME types discussed in https://github.com/KhronosGroup/glTF/issues/412 and https://github.com/KhronosGroup/glTF/issues/943
+ var defaultModelAccept = 'model/gltf-binary,model/gltf+json;q=0.8,application/json;q=0.2,*/*;q=0.01';
function LoadResources() {
this.vertexBuffersToCreate = new Queue();
diff --git a/Source/Scene/QuadtreePrimitive.js b/Source/Scene/QuadtreePrimitive.js
index c5b73611277..faab9989d1f 100644
--- a/Source/Scene/QuadtreePrimitive.js
+++ b/Source/Scene/QuadtreePrimitive.js
@@ -100,7 +100,6 @@ define([
this._tileLoadQueueLow = []; // low priority tiles were refined past or are non-visible parts of quads.
this._tileReplacementQueue = new TileReplacementQueue();
this._levelZeroTiles = undefined;
- this._levelZeroTilesReady = false;
this._loadQueueTimeSlice = 5.0;
this._addHeightCallbacks = [];
diff --git a/Source/Scene/QuadtreeTile.js b/Source/Scene/QuadtreeTile.js
index 5ebea3fe005..65c45f7274a 100644
--- a/Source/Scene/QuadtreeTile.js
+++ b/Source/Scene/QuadtreeTile.js
@@ -67,6 +67,7 @@ define([
// distance - for example, by using the natural ordering of a quadtree.
// QuadtreePrimitive gets/sets this private property.
this._distance = 0.0;
+ this._priorityFunction = undefined;
this._customData = [];
this._frameUpdated = undefined;
diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js
index ac453266528..2489e3a3d19 100644
--- a/Source/Scene/Scene.js
+++ b/Source/Scene/Scene.js
@@ -2587,7 +2587,6 @@ define([
scene._preRender.raiseEvent(scene, time);
scene._jobScheduler.resetBudgets();
- RequestScheduler.resetBudgets();
var context = scene.context;
var us = context.uniformState;
@@ -2654,6 +2653,7 @@ define([
}
context.endFrame();
+ RequestScheduler.update();
callAfterRenderFunctions(frameState);
scene._postRender.raiseEvent(scene, time);
diff --git a/Source/Scene/SingleTileImageryProvider.js b/Source/Scene/SingleTileImageryProvider.js
index 69b70830a52..9e869c6b5b6 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 {Request} [request] The request object. Intended for internal use only.
* @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, request) {
//>>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 35531470ac0..0ecf383b481 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 {Request} [request] The request object. Intended for internal use only.
* @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, request) {
var canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
diff --git a/Source/Scene/TileImagery.js b/Source/Scene/TileImagery.js
index a38510ebae2..d3ffdd839df 100644
--- a/Source/Scene/TileImagery.js
+++ b/Source/Scene/TileImagery.js
@@ -50,7 +50,7 @@ define([
var loadingImagery = this.loadingImagery;
var imageryLayer = loadingImagery.imageryLayer;
- loadingImagery.processStateMachine(frameState, !this.useWebMercatorT);
+ loadingImagery.processStateMachine(frameState, !this.useWebMercatorT, tile._priorityFunction);
if (loadingImagery.state === ImageryState.READY) {
if (defined(this.readyImagery)) {
@@ -92,7 +92,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, !this.useWebMercatorT);
+ closestAncestorThatNeedsLoading.processStateMachine(frameState, !this.useWebMercatorT, tile._priorityFunction);
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/TileTerrain.js b/Source/Scene/TileTerrain.js
index d97b629b6ef..98c315518c9 100644
--- a/Source/Scene/TileTerrain.js
+++ b/Source/Scene/TileTerrain.js
@@ -6,13 +6,15 @@ define([
'../Core/DeveloperError',
'../Core/IndexDatatype',
'../Core/OrientedBoundingBox',
+ '../Core/Request',
+ '../Core/RequestState',
+ '../Core/RequestType',
'../Core/TileProviderError',
'../Renderer/Buffer',
'../Renderer/BufferUsage',
'../Renderer/VertexArray',
'../ThirdParty/when',
- './TerrainState',
- './TileBoundingRegion'
+ './TerrainState'
], function(
BoundingSphere,
Cartesian3,
@@ -20,13 +22,15 @@ define([
DeveloperError,
IndexDatatype,
OrientedBoundingBox,
+ Request,
+ RequestState,
+ RequestType,
TileProviderError,
Buffer,
BufferUsage,
VertexArray,
when,
- TerrainState,
- TileBoundingRegion) {
+ TerrainState) {
'use strict';
/**
@@ -52,6 +56,7 @@ define([
this.mesh = undefined;
this.vertexArray = undefined;
this.upsampleDetails = upsampleDetails;
+ this.request = undefined;
}
TileTerrain.prototype.freeResources = function() {
@@ -83,18 +88,14 @@ 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);
};
- TileTerrain.prototype.processLoadStateMachine = function(frameState, terrainProvider, x, y, level) {
+ TileTerrain.prototype.processLoadStateMachine = function(frameState, terrainProvider, x, y, level, priorityFunction) {
if (this.state === TerrainState.UNLOADED) {
- requestTileGeometry(this, terrainProvider, x, y, level);
+ requestTileGeometry(this, terrainProvider, x, y, level, priorityFunction);
}
if (this.state === TerrainState.RECEIVED) {
@@ -106,16 +107,26 @@ define([
}
};
- function requestTileGeometry(tileTerrain, terrainProvider, x, y, level) {
+ function requestTileGeometry(tileTerrain, terrainProvider, x, y, level, priorityFunction) {
function success(terrainData) {
tileTerrain.data = terrainData;
tileTerrain.state = TerrainState.RECEIVED;
+ tileTerrain.request = undefined;
}
function failure() {
+ if (tileTerrain.request.state === RequestState.CANCELLED) {
+ // Cancelled due to low priority - try again later.
+ tileTerrain.data = undefined;
+ tileTerrain.state = TerrainState.UNLOADED;
+ tileTerrain.request = undefined;
+ return;
+ }
+
// Initially assume failure. handleError may retry, in which case the state will
// change to RECEIVING or UNLOADED.
tileTerrain.state = TerrainState.FAILED;
+ tileTerrain.request = undefined;
var message = 'Failed to obtain terrain tile X: ' + x + ' Y: ' + y + ' Level: ' + level + '.';
terrainProvider._requestError = TileProviderError.handleError(
@@ -129,17 +140,24 @@ define([
function doRequest() {
// Request the terrain from the terrain provider.
- tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level);
+ var request = new Request({
+ throttle : true,
+ throttleByServer : true,
+ type : RequestType.TERRAIN,
+ priorityFunction : priorityFunction
+ });
+ tileTerrain.request = request;
+ tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level, request);
// If the request method returns undefined (instead of a promise), the request
// has been deferred.
if (defined(tileTerrain.data)) {
tileTerrain.state = TerrainState.RECEIVING;
-
when(tileTerrain.data, success, failure);
} else {
// Deferred - try again later.
tileTerrain.state = TerrainState.UNLOADED;
+ tileTerrain.request = undefined;
}
}
diff --git a/Source/Scene/UrlTemplateImageryProvider.js b/Source/Scene/UrlTemplateImageryProvider.js
index aa7aee267f0..6d1edf87cca 100644
--- a/Source/Scene/UrlTemplateImageryProvider.js
+++ b/Source/Scene/UrlTemplateImageryProvider.js
@@ -265,7 +265,6 @@ define([
}
},
-
/**
* Gets the URL template to use to use to pick features. If this property is not specified,
* {@link UrlTemplateImageryProvider#pickFeatures} will immediately returned undefined, indicating no
@@ -602,19 +601,20 @@ define([
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
+ * @param {Request} [request] The request object. Intended for internal use only.
* @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, request) {
//>>includeStart('debug', pragmas.debug);
if (!this.ready) {
throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
}
//>>includeEnd('debug');
var url = buildImageUrl(this, x, y, level);
- return ImageryProvider.loadImage(this, url);
+ return ImageryProvider.loadImage(this, url, request);
};
/**
@@ -669,8 +669,8 @@ define([
return loadText(url).then(format.callback).otherwise(doRequest);
} else {
return loadWithXhr({
- url: url,
- responseType: format.format
+ url : url,
+ responseType : format.format
}).then(handleResponse.bind(undefined, format)).otherwise(doRequest);
}
}
diff --git a/Source/Scene/WebMapServiceImageryProvider.js b/Source/Scene/WebMapServiceImageryProvider.js
index 3f76e7622ae..93e98bc4a01 100644
--- a/Source/Scene/WebMapServiceImageryProvider.js
+++ b/Source/Scene/WebMapServiceImageryProvider.js
@@ -433,6 +433,7 @@ define([
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
+ * @param {Request} [request] The request object. Intended for internal use only.
* @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
@@ -440,8 +441,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, request) {
+ return this._tileProvider.requestImage(x, y, level, request);
};
/**
diff --git a/Source/Scene/WebMapTileServiceImageryProvider.js b/Source/Scene/WebMapTileServiceImageryProvider.js
index 624f1123fcf..21986a61866 100644
--- a/Source/Scene/WebMapTileServiceImageryProvider.js
+++ b/Source/Scene/WebMapTileServiceImageryProvider.js
@@ -432,6 +432,7 @@ define([
* @param {Number} x The tile X coordinate.
* @param {Number} y The tile Y coordinate.
* @param {Number} level The tile level.
+ * @param {Request} [request] The request object. Intended for internal use only.
* @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
@@ -439,9 +440,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, request) {
var url = buildImageUrl(this, x, y, level);
- return ImageryProvider.loadImage(this, url);
+ return ImageryProvider.loadImage(this, url, request);
};
/**
diff --git a/Specs/.eslintrc b/Specs/.eslintrc.json
similarity index 56%
rename from Specs/.eslintrc
rename to Specs/.eslintrc.json
index c9b69aa5b6b..297a41b8f96 100644
--- a/Specs/.eslintrc
+++ b/Specs/.eslintrc.json
@@ -1,5 +1,5 @@
{
- "extends": "../.eslintrc",
+ "extends": "../.eslintrc.json",
"env": {
"jasmine": true
}
diff --git a/Specs/Core/CesiumTerrainProviderSpec.js b/Specs/Core/CesiumTerrainProviderSpec.js
index 9238b813569..04cb24bff55 100644
--- a/Specs/Core/CesiumTerrainProviderSpec.js
+++ b/Specs/Core/CesiumTerrainProviderSpec.js
@@ -9,6 +9,8 @@ defineSuite([
'Core/loadWithXhr',
'Core/Math',
'Core/QuantizedMeshTerrainData',
+ 'Core/Request',
+ 'Core/RequestScheduler',
'Core/TerrainProvider',
'Specs/pollToPromise',
'ThirdParty/when'
@@ -22,11 +24,17 @@ defineSuite([
loadWithXhr,
CesiumMath,
QuantizedMeshTerrainData,
+ Request,
+ RequestScheduler,
TerrainProvider,
pollToPromise,
when) {
'use strict';
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
+ });
+
afterEach(function() {
loadWithXhr.load = loadWithXhr.defaultLoad;
});
@@ -84,6 +92,12 @@ defineSuite([
});
}
+ function createRequest() {
+ return new Request({
+ throttleByServer : true
+ });
+ }
+
it('conforms to TerrainProvider interface', function() {
expect(CesiumTerrainProvider).toConformToInterface(TerrainProvider);
});
@@ -575,15 +589,15 @@ defineSuite([
return pollToPromise(function() {
return terrainProvider.ready;
}).then(function() {
- var promise = terrainProvider.requestTileGeometry(0, 0, 0);
- expect(promise).toBeDefined();
-
+ var promise;
var i;
- for (i = 0; i < 10; ++i) {
- promise = terrainProvider.requestTileGeometry(0, 0, 0);
+ for (i = 0; i < RequestScheduler.maximumRequestsPerServer; ++i) {
+ promise = terrainProvider.requestTileGeometry(0, 0, 0, createRequest());
}
+ RequestScheduler.update();
+ expect(promise).toBeDefined();
- promise = terrainProvider.requestTileGeometry(0, 0, 0);
+ promise = terrainProvider.requestTileGeometry(0, 0, 0, createRequest());
expect(promise).toBeUndefined();
for (i = 0; i < deferreds.length; ++i) {
diff --git a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js
index f1ce40b3498..d3b2237c40a 100644
--- a/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js
+++ b/Specs/Core/GoogleEarthEnterpriseMetadataSpec.js
@@ -7,6 +7,7 @@ defineSuite([
'Core/defaultValue',
'Core/loadWithXhr',
'Core/Math',
+ 'Core/Request',
'ThirdParty/when'
], function(
GoogleEarthEnterpriseMetadata,
@@ -16,6 +17,7 @@ defineSuite([
defaultValue,
loadWithXhr,
CesiumMath,
+ Request,
when) {
'use strict';
@@ -114,7 +116,7 @@ defineSuite([
it('populateSubtree', function() {
var quad = '0123';
var index = 0;
- spyOn(GoogleEarthEnterpriseMetadata.prototype, 'getQuadTreePacket').and.callFake(function(quadKey, version) {
+ spyOn(GoogleEarthEnterpriseMetadata.prototype, 'getQuadTreePacket').and.callFake(function(quadKey, version, request) {
quadKey = defaultValue(quadKey, '') + index.toString();
this._tileInfo[quadKey] = new GoogleEarthEnterpriseTileInformation(0xFF, 1, 1, 1);
index = (index + 1) % 4;
@@ -125,17 +127,20 @@ defineSuite([
var metadata = new GoogleEarthEnterpriseMetadata({
url : 'http://test.server'
});
+ var request = new Request({
+ throttle : true
+ });
return metadata.readyPromise
.then(function() {
var tileXY = GoogleEarthEnterpriseMetadata.quadKeyToTileXY(quad);
- return metadata.populateSubtree(tileXY.x, tileXY.y, tileXY.level);
+ return metadata.populateSubtree(tileXY.x, tileXY.y, tileXY.level, request);
})
.then(function() {
expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket.calls.count()).toEqual(4);
- expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('', 1, false);
- expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('0', 1, true);
- expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('01', 1, true);
- expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('012', 1, true);
+ expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('', 1);
+ expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('0', 1, request);
+ expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('01', 1, request);
+ expect(GoogleEarthEnterpriseMetadata.prototype.getQuadTreePacket).toHaveBeenCalledWith('012', 1, request);
var tileInfo = metadata._tileInfo;
expect(tileInfo['0']).toBeDefined();
diff --git a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js
index 00d2ca6d731..9917d46c18b 100644
--- a/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js
+++ b/Specs/Core/GoogleEarthEnterpriseTerrainProviderSpec.js
@@ -12,6 +12,8 @@ defineSuite([
'Core/loadImage',
'Core/loadWithXhr',
'Core/Math',
+ 'Core/Request',
+ 'Core/RequestScheduler',
'Core/TerrainProvider',
'Specs/pollToPromise',
'ThirdParty/when'
@@ -28,6 +30,8 @@ defineSuite([
loadImage,
loadWithXhr,
CesiumMath,
+ Request,
+ RequestScheduler,
TerrainProvider,
pollToPromise,
when) {
@@ -74,6 +78,16 @@ defineSuite([
});
}
+ function createRequest() {
+ return new Request({
+ throttleByServer : true
+ });
+ }
+
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
+ });
+
afterEach(function() {
loadWithXhr.load = loadWithXhr.defaultLoad;
});
@@ -304,17 +318,15 @@ defineSuite([
});
})
.then(function() {
- var promise = terrainProvider.requestTileGeometry(1, 2, 3);
- expect(promise).toBeDefined();
- return promise;
- })
- .then(function(terrainData) {
- expect(terrainData).toBeDefined();
- for (var i = 0; i < 10; ++i) {
- promises.push(terrainProvider.requestTileGeometry(i, i, i));
+ var promise;
+ for (var i = 0; i < RequestScheduler.maximumRequestsPerServer; ++i) {
+ promise = terrainProvider.requestTileGeometry(i, i, i, createRequest());
+ promises.push(promise);
}
+ RequestScheduler.update();
+ expect(promise).toBeDefined();
- return terrainProvider.requestTileGeometry(1, 2, 3);
+ return terrainProvider.requestTileGeometry(1, 2, 3, createRequest());
})
.then(function(terrainData) {
expect(terrainData).toBeUndefined();
diff --git a/Specs/Core/HeapSpec.js b/Specs/Core/HeapSpec.js
index 209324d5c74..4d41e7e48fb 100644
--- a/Specs/Core/HeapSpec.js
+++ b/Specs/Core/HeapSpec.js
@@ -8,16 +8,17 @@ defineSuite([
var length = 100;
function checkHeap(heap, comparator) {
- var data = heap.data;
+ var array = heap.internalArray;
var pass = true;
- for (var i = 0; i < heap.length; ++i) {
+ var length = heap.length;
+ for (var i = 0; i < length; ++i) {
var left = 2 * (i + 1) - 1;
var right = 2 * (i + 1);
if (left < heap.length) {
- pass = pass && (comparator(data[i], data[left]) <= 0);
+ pass = pass && (comparator(array[i], array[left]) <= 0);
}
if (right < heap.length) {
- pass = pass && (comparator(data[i], data[right]) <= 0);
+ pass = pass && (comparator(array[i], array[right]) <= 0);
}
}
return pass;
@@ -29,17 +30,22 @@ defineSuite([
}
it('maintains heap property on insert', function() {
- var heap = new Heap(comparator);
+ var heap = new Heap({
+ comparator : comparator
+ });
var pass = true;
for (var i = 0; i < length; ++i) {
heap.insert(Math.random());
pass = pass && checkHeap(heap, comparator);
}
+
expect(pass).toBe(true);
});
it('maintains heap property on pop', function() {
- var heap = new Heap(comparator);
+ var heap = new Heap({
+ comparator : comparator
+ });
var i;
for (i = 0; i < length; ++i) {
heap.insert(Math.random());
@@ -52,32 +58,26 @@ defineSuite([
expect(pass).toBe(true);
});
- it('can build heap', function() {
- var heap = new Heap(comparator);
- var arr = new Array(length);
- for (var i = 0; i < length; ++i) {
- arr[i] = Math.random();
- }
- heap.buildHeap(arr);
- expect(checkHeap(heap, comparator)).toBe(true);
- });
-
- it('limited by maximum size', function() {
- var heap = new Heap(comparator);
- heap.maximumSize = length / 2;
+ it('limited by maximum length', function() {
+ var heap = new Heap({
+ comparator : comparator
+ });
+ heap.maximumLength = length / 2;
var pass = true;
for (var i = 0; i < length; ++i) {
heap.insert(Math.random());
pass = pass && checkHeap(heap, comparator);
}
expect(pass).toBe(true);
- expect(heap.length <= heap.maximumSize).toBe(true);
+ expect(heap.length <= heap.maximumLength).toBe(true);
// allowed one extra slot for swapping
- expect(heap.data.length <= heap.maximumSize + 1).toBe(true);
+ expect(heap.internalArray.length).toBeLessThanOrEqualTo(heap.maximumLength + 1);
});
it('pops in sorted order', function() {
- var heap = new Heap(comparator);
+ var heap = new Heap({
+ comparator : comparator
+ });
var i;
for (i = 0; i < length; ++i) {
heap.insert(Math.random());
@@ -91,4 +91,85 @@ defineSuite([
}
expect(pass).toBe(true);
});
+
+ it('insert returns the removed element when maximumLength is set', function() {
+ var heap = new Heap({
+ comparator : comparator
+ });
+ heap.maximumLength = length;
+
+ var i;
+ var max = 0.0;
+ var min = 1.0;
+ var values = new Array(length);
+ for (i = 0; i < length; ++i) {
+ var value = Math.random();
+ max = Math.max(max, value);
+ min = Math.min(min, value);
+ values[i] = value;
+ }
+
+ // Push 99 values
+ for (i = 0; i < length - 1; ++i) {
+ heap.insert(values[i]);
+ }
+
+ // Push 100th, nothing is removed so it returns undefined
+ var removed = heap.insert(values[length - 1]);
+ expect(removed).toBeUndefined();
+
+ // Insert value, an element is removed
+ removed = heap.insert(max - 0.1);
+ expect(removed).toBeDefined();
+
+ // If this value is the least priority it will be returned
+ removed = heap.insert(max + 0.1);
+ expect(removed).toBe(max + 0.1);
+ });
+
+ it('resort', function() {
+ function comparator(a, b) {
+ return a.distance - b.distance;
+ }
+
+ var i;
+ var heap = new Heap({
+ comparator : comparator
+ });
+ for (i = 0; i < length; ++i) {
+ heap.insert({
+ distance : i / (length - 1),
+ id : i
+ });
+ }
+
+ // Check that elements are initially sorted
+ var element;
+ var elements = [];
+ var currentId = 0;
+ while (heap.length > 0) {
+ element = heap.pop();
+ elements.push(element);
+ expect(element.id).toBeGreaterThanOrEqualTo(currentId);
+ currentId = element.id;
+ }
+
+ // Add back into heap
+ for (i = 0; i < length; ++i) {
+ heap.insert(elements[i]);
+ }
+
+ // Invert priority
+ for (i = 0; i < length; ++i) {
+ elements[i].distance = 1.0 - elements[i].distance;
+ }
+
+ // Resort and check the the elements are popped in the opposite order now
+ heap.resort();
+ while (heap.length > 0) {
+ element = heap.pop();
+ expect(element.id).toBeLessThanOrEqualTo(currentId);
+ currentId = element.id;
+ }
+ });
});
diff --git a/Specs/Core/RequestSchedulerSpec.js b/Specs/Core/RequestSchedulerSpec.js
index b9fc5485334..4dfff78ecff 100644
--- a/Specs/Core/RequestSchedulerSpec.js
+++ b/Specs/Core/RequestSchedulerSpec.js
@@ -2,37 +2,46 @@
defineSuite([
'Core/RequestScheduler',
'Core/Request',
+ 'Core/RequestState',
'Core/RequestType',
'ThirdParty/when'
], function(
RequestScheduler,
Request,
+ RequestState,
RequestType,
when) {
'use strict';
var originalMaximumRequests;
var originalMaximumRequestsPerServer;
+ var originalPriorityHeapLength;
- beforeEach(function() {
+ beforeAll(function() {
originalMaximumRequests = RequestScheduler.maximumRequests;
originalMaximumRequestsPerServer = RequestScheduler.maximumRequestsPerServer;
+ originalPriorityHeapLength = RequestScheduler.priorityHeapLength;
+ });
+
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
});
afterEach(function() {
RequestScheduler.maximumRequests = originalMaximumRequests;
RequestScheduler.maximumRequestsPerServer = originalMaximumRequestsPerServer;
+ RequestScheduler.priorityHeapLength = originalPriorityHeapLength;
});
- it('schedule throws when request is undefined', function() {
+ it('request throws when request is undefined', function() {
expect(function() {
- RequestScheduler.schedule();
+ RequestScheduler.request();
}).toThrowDeveloperError();
});
- it('schedule throws when request.url is undefined', function() {
+ it('request throws when request.url is undefined', function() {
expect(function() {
- RequestScheduler.schedule(new Request({
+ RequestScheduler.request(new Request({
requestFunction : function(url) {
return undefined;
}
@@ -40,57 +49,94 @@ defineSuite([
}).toThrowDeveloperError();
});
- it('schedule throws when request.requestFunction is undefined', function() {
+ it('request throws when request.requestFunction is undefined', function() {
expect(function() {
- RequestScheduler.schedule(new Request({
+ RequestScheduler.request(new Request({
url : 'file/path'
}));
}).toThrowDeveloperError();
});
+ it('getServer throws if url is undefined', function() {
+ expect(function() {
+ RequestScheduler.getServerKey();
+ }).toThrowDeveloperError();
+ });
+
+ it('getServer with https', function() {
+ var server = RequestScheduler.getServerKey('https://foo.com/1');
+ expect(server).toEqual('foo.com:443');
+ });
+
+ it('getServer with http', function() {
+ var server = RequestScheduler.getServerKey('http://foo.com/1');
+ expect(server).toEqual('foo.com:80');
+ });
+
it('honors maximumRequests', function() {
RequestScheduler.maximumRequests = 2;
+ var statistics = RequestScheduler.statistics;
var deferreds = [];
- function requestFunction(url) {
+ function requestFunction() {
var deferred = when.defer();
deferreds.push(deferred);
return deferred.promise;
}
- var request = new Request({
- url : 'http://foo.com/1',
- requestFunction : requestFunction
- });
+ function createRequest() {
+ return new Request({
+ url : 'http://foo.com/1',
+ requestFunction : requestFunction,
+ throttle : true
+ });
+ }
+
+ var promise1 = RequestScheduler.request(createRequest());
+ var promise2 = RequestScheduler.request(createRequest());
+ RequestScheduler.update();
- var promise1 = RequestScheduler.schedule(request);
- var promise2 = RequestScheduler.schedule(request);
- var promise3 = RequestScheduler.schedule(request);
+ // Scheduler is full, promise3 will be undefined
+ var promise3 = RequestScheduler.request(createRequest());
+ RequestScheduler.update();
- expect(deferreds.length).toBe(2);
+ expect(statistics.numberOfActiveRequests).toBe(2);
expect(promise1).toBeDefined();
expect(promise2).toBeDefined();
expect(promise3).not.toBeDefined();
+ // Scheduler now has an empty slot, promise4 goes through
deferreds[0].resolve();
+ RequestScheduler.update();
+
+ expect(statistics.numberOfActiveRequests).toBe(1);
+
+ var promise4 = RequestScheduler.request(createRequest());
+ RequestScheduler.update();
- var promise4 = RequestScheduler.schedule(request);
- expect(deferreds.length).toBe(3);
+ expect(statistics.numberOfActiveRequests).toBe(2);
expect(promise4).toBeDefined();
- var promise5 = RequestScheduler.schedule(request);
- expect(deferreds.length).toBe(3);
+ // Scheduler is full, promise5 will be undefined
+ var promise5 = RequestScheduler.request(createRequest());
+ RequestScheduler.update();
+
+ expect(statistics.numberOfActiveRequests).toBe(2);
expect(promise5).not.toBeDefined();
+ // maximumRequests increases, promise6 goes through
RequestScheduler.maximumRequests = 3;
- var promise6 = RequestScheduler.schedule(request);
- expect(deferreds.length).toBe(4);
+ var promise6 = RequestScheduler.request(createRequest());
+ RequestScheduler.update();
+
+ expect(statistics.numberOfActiveRequests).toBe(3);
expect(promise6).toBeDefined();
- deferreds[1].resolve();
- deferreds[2].resolve();
- deferreds[3].resolve();
+ var length = deferreds.length;
+ for (var i = 0; i < length; ++i) {
+ deferreds[i].resolve();
+ }
});
it('honors maximumRequestsPerServer', function() {
@@ -98,357 +144,561 @@ defineSuite([
var deferreds = [];
- function requestFunction(url) {
+ function requestFunction() {
var deferred = when.defer();
deferreds.push(deferred);
return deferred.promise;
}
- var request = new Request({
- url : 'http://foo.com/1',
- requestFunction : requestFunction
- });
+ var url = 'http://foo.com/1';
+ var server = RequestScheduler.getServerKey(url);
- var promise1 = RequestScheduler.schedule(request);
- var promise2 = RequestScheduler.schedule(request);
- var promise3 = RequestScheduler.schedule(request);
+ function createRequest() {
+ return new Request({
+ url : url,
+ requestFunction : requestFunction,
+ throttleByServer : true
+ });
+ }
+
+ var promise1 = RequestScheduler.request(createRequest());
+ var promise2 = RequestScheduler.request(createRequest());
+ RequestScheduler.update();
+
+ // Scheduler is full, promise3 will be undefined
+ var promise3 = RequestScheduler.request(createRequest());
+ RequestScheduler.update();
- expect(deferreds.length).toBe(2);
+ expect(RequestScheduler.numberOfActiveRequestsByServer(server)).toBe(2);
expect(promise1).toBeDefined();
expect(promise2).toBeDefined();
expect(promise3).not.toBeDefined();
+ // Scheduler now has an empty slot, promise4 goes through
deferreds[0].resolve();
+ RequestScheduler.update();
+
+ expect(RequestScheduler.numberOfActiveRequestsByServer(server)).toBe(1);
+
+ var promise4 = RequestScheduler.request(createRequest());
+ RequestScheduler.update();
- var promise4 = RequestScheduler.schedule(request);
- expect(deferreds.length).toBe(3);
+ expect(RequestScheduler.numberOfActiveRequestsByServer(server)).toBe(2);
expect(promise4).toBeDefined();
- var promise5 = RequestScheduler.schedule(request);
- expect(deferreds.length).toBe(3);
+ // Scheduler is full, promise5 will be undefined
+ var promise5 = RequestScheduler.request(createRequest());
+ RequestScheduler.update();
+
+ expect(RequestScheduler.numberOfActiveRequestsByServer(server)).toBe(2);
expect(promise5).not.toBeDefined();
+ // maximumRequests increases, promise6 goes through
RequestScheduler.maximumRequestsPerServer = 3;
- var promise6 = RequestScheduler.schedule(request);
- expect(deferreds.length).toBe(4);
+ var promise6 = RequestScheduler.request(createRequest());
+ RequestScheduler.update();
+
+ expect(RequestScheduler.numberOfActiveRequestsByServer(server)).toBe(3);
expect(promise6).toBeDefined();
- deferreds[1].resolve();
- deferreds[2].resolve();
- deferreds[3].resolve();
+ var length = deferreds.length;
+ for (var i = 0; i < length; ++i) {
+ deferreds[i].resolve();
+ }
});
- it('getNumberOfAvailableRequests', function() {
+ it('honors priorityHeapLength', function() {
var deferreds = [];
+ var requests = [];
- function requestFunction(url) {
+ function requestFunction() {
var deferred = when.defer();
deferreds.push(deferred);
return deferred.promise;
}
- 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);
-
- deferreds[0].resolve();
- deferreds[1].resolve();
- deferreds[2].resolve();
+ function createRequest(priority) {
+ var request = new Request({
+ url : 'http://foo.com/1',
+ requestFunction : requestFunction,
+ throttle : true,
+ priority : priority
+ });
+ requests.push(request);
+ return request;
+ }
- expect(RequestScheduler.getNumberOfAvailableRequests()).toBe(10);
+ RequestScheduler.priorityHeapLength = 1;
+ var firstRequest = createRequest(0.0);
+ var promise = RequestScheduler.request(firstRequest);
+ expect(promise).toBeDefined();
+ promise = RequestScheduler.request(createRequest(1.0));
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.priorityHeapLength = 3;
+ promise = RequestScheduler.request(createRequest(2.0));
+ promise = RequestScheduler.request(createRequest(3.0));
+ expect(promise).toBeDefined();
+ promise = RequestScheduler.request(createRequest(4.0));
+ expect(promise).toBeUndefined();
+
+ // A request is cancelled to accommodate the new heap length
+ RequestScheduler.priorityHeapLength = 2;
+ expect(firstRequest.state).toBe(RequestState.CANCELLED);
+
+ var length = deferreds.length;
+ for (var i = 0; i < length; ++i) {
+ deferreds[i].resolve();
+ }
});
- it('getNumberOfAvailableRequestsByServer', function() {
+ function testImmediateRequest(url, dataOrBlobUri) {
+ var statistics = RequestScheduler.statistics;
var deferreds = [];
- function requestFunction(url) {
+ function requestFunction() {
var deferred = when.defer();
deferreds.push(deferred);
return deferred.promise;
}
- var requestFoo = new Request({
- url : 'http://foo.com/1',
- requestFunction : requestFunction
- });
-
- var requestBar = new Request({
- url : 'http://bar.com/1',
+ var request = new Request({
+ url : url,
requestFunction : requestFunction
});
- RequestScheduler.schedule(requestFoo);
- RequestScheduler.schedule(requestFoo);
- RequestScheduler.schedule(requestBar);
+ var promise = RequestScheduler.request(request);
+ expect(promise).toBeDefined();
- expect(RequestScheduler.maximumRequestsPerServer).toBe(6);
- expect(RequestScheduler.getNumberOfAvailableRequestsByServer('http://foo.com')).toBe(4);
- expect(RequestScheduler.getNumberOfAvailableRequestsByServer('http://bar.com')).toBe(5);
+ if (dataOrBlobUri) {
+ expect(request.serverKey).toBeUndefined();
+ expect(statistics.numberOfActiveRequests).toBe(0);
+ } else {
+ expect(statistics.numberOfActiveRequests).toBe(1);
+ expect(RequestScheduler.numberOfActiveRequestsByServer(request.serverKey)).toBe(1);
+ }
deferreds[0].resolve();
- deferreds[1].resolve();
- deferreds[2].resolve();
- expect(RequestScheduler.getNumberOfAvailableRequestsByServer('http://foo.com')).toBe(6);
- expect(RequestScheduler.getNumberOfAvailableRequestsByServer('http://bar.com')).toBe(6);
- });
+ return promise.then(function() {
+ expect(request.state).toBe(RequestState.RECEIVED);
+ expect(statistics.numberOfActiveRequests).toBe(0);
+ if (!dataOrBlobUri) {
+ expect(RequestScheduler.numberOfActiveRequestsByServer(request.serverKey)).toBe(0);
+ }
+ });
+ }
- it('getServerName with https', function() {
- var server = RequestScheduler.getServerName('https://foo.com/1');
- expect(server).toEqual('foo.com:443');
+ it('data uri goes through immediately', function() {
+ var dataUri = 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D';
+ testImmediateRequest(dataUri, true);
});
- it('getServerName with http', function() {
- var server = RequestScheduler.getServerName('http://foo.com/1');
- expect(server).toEqual('foo.com:80');
- });
+ it('blob uri goes through immediately', function() {
+ var uint8Array = new Uint8Array(4);
+ var blob = new Blob([uint8Array], {
+ type : 'application/octet-stream'
+ });
- it('getServerName throws if url is undefined', function() {
- expect(function() {
- return RequestScheduler.getServerName();
- }).toThrowDeveloperError();
+ var blobUrl = window.URL.createObjectURL(blob);
+ testImmediateRequest(blobUrl, true);
});
- it('hasAvailableRequests and hasAvailableRequestsByServer', function() {
- RequestScheduler.maximumRequestsPerServer = 2;
- RequestScheduler.maximumRequests = 3;
+ it('request goes through immediately when throttle is false', function() {
+ var url = 'https://foo.com/1';
+ testImmediateRequest(url, false);
+ });
+ it('makes a throttled request', function() {
var deferreds = [];
- function requestFunction(url) {
+ function requestFunction() {
var deferred = when.defer();
deferreds.push(deferred);
return deferred.promise;
}
- var requestFoo = new Request({
- url : 'http://foo.com/1',
+ var request = new Request({
+ throttle : true,
+ url : 'https://foo.com/1',
requestFunction : requestFunction
});
+ expect(request.state).toBe(RequestState.UNISSUED);
+
+ var promise = RequestScheduler.request(request);
+ expect(promise).toBeDefined();
+ expect(request.state).toBe(RequestState.ISSUED);
+
+ RequestScheduler.update();
+ expect(request.state).toBe(RequestState.ACTIVE);
- var requestBar = new Request({
- url : 'http://bar.com/1',
+ deferreds[0].resolve();
+ expect(request.state).toBe(RequestState.RECEIVED);
+ });
+
+ it('cancels an issued request', function() {
+ var statistics = RequestScheduler.statistics;
+
+ function requestFunction() {
+ return when.resolve();
+ }
+
+ var request = new Request({
+ throttle : true,
+ url : 'https://foo.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);
+ var promise = RequestScheduler.request(request);
+ expect(request.state).toBe(RequestState.ISSUED);
- expect(RequestScheduler.hasAvailableRequests()).toEqual(true);
- RequestScheduler.schedule(requestBar);
- expect(RequestScheduler.hasAvailableRequests()).toEqual(false);
+ request.cancel();
+ RequestScheduler.update();
- expect(RequestScheduler.getNumberOfAvailableRequests()).toEqual(0);
+ expect(request.state).toBe(RequestState.CANCELLED);
+ expect(statistics.numberOfCancelledRequests).toBe(1);
+ expect(statistics.numberOfCancelledActiveRequests).toBe(0);
- deferreds[0].resolve();
- deferreds[1].resolve();
- deferreds[2].resolve();
+ return promise.then(function() {
+ fail('should not be called');
+ }).otherwise(function(error) {
+ expect(request.state).toBe(RequestState.CANCELLED);
+ });
});
- it('defers request when request scheduler is full', function() {
- RequestScheduler.maximumRequests = 3;
+ it('cancels an active request', function() {
+ var statistics = RequestScheduler.statistics;
+ var cancelFunction = jasmine.createSpy('cancelFunction');
+ function requestFunction() {
+ return when.defer().promise;
+ }
+
+ var request = new Request({
+ throttle : true,
+ url : 'https://foo.com/1',
+ requestFunction : requestFunction,
+ cancelFunction : cancelFunction
+ });
+
+ var promise = RequestScheduler.request(request);
+ RequestScheduler.update();
+ expect(request.state).toBe(RequestState.ACTIVE);
+
+ request.cancel();
+ RequestScheduler.update();
+
+ expect(request.state).toBe(RequestState.CANCELLED);
+ expect(statistics.numberOfCancelledRequests).toBe(1);
+ expect(statistics.numberOfCancelledActiveRequests).toBe(1);
+ expect(RequestScheduler.numberOfActiveRequestsByServer(request.serverKey)).toBe(0);
+ expect(cancelFunction).toHaveBeenCalled();
+
+ return promise.then(function() {
+ fail('should not be called');
+ }).otherwise(function(error) {
+ expect(request.state).toBe(RequestState.CANCELLED);
+ });
+ });
+
+ it('handles request failure', function() {
+ var statistics = RequestScheduler.statistics;
var deferreds = [];
- function requestFunction(url) {
+ function requestFunction() {
var deferred = when.defer();
deferreds.push(deferred);
return deferred.promise;
}
var request = new Request({
- url : 'http://foo.com/1',
+ url : 'https://foo.com/1',
requestFunction : requestFunction
});
- var requestDeferred = new Request({
- url : 'http://foo.com/1',
- requestFunction : requestFunction,
- defer : true
+ var promise = RequestScheduler.request(request);
+ expect(request.state).toBe(RequestState.ACTIVE);
+ expect(statistics.numberOfActiveRequests).toBe(1);
+
+ deferreds[0].reject('Request failed');
+ RequestScheduler.update();
+ expect(statistics.numberOfActiveRequests).toBe(0);
+
+ return promise.then(function() {
+ fail('should not be called');
+ }).otherwise(function(error) {
+ expect(error).toBe('Request failed');
});
+ });
- RequestScheduler.schedule(request);
- RequestScheduler.schedule(request);
- RequestScheduler.schedule(request);
- expect(RequestScheduler.hasAvailableRequests()).toEqual(false);
+ it('prioritizes requests', function() {
+ var currentPriority = 0.0;
- // 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();
+ function getRequestFunction(priority) {
+ return function() {
+ expect(priority).toBeGreaterThan(currentPriority);
+ currentPriority = priority;
+ return when.resolve();
+ };
+ }
- // When the first request completes, the deferred promise starts
- deferreds[0].resolve();
- expect(deferreds[3]).toBeDefined();
+ function createRequest(priority) {
+ return new Request({
+ throttle : true,
+ url : 'https://foo.com/1',
+ requestFunction : getRequestFunction(priority),
+ priority : priority
+ });
+ }
- deferreds[1].resolve();
- deferreds[2].resolve();
- deferreds[3].resolve();
+ var length = RequestScheduler.priorityHeapLength;
+ for (var i = 0; i < length; ++i) {
+ var priority = Math.random();
+ RequestScheduler.request(createRequest(priority));
+ }
+
+ RequestScheduler.update();
+ expect(currentPriority).toBeGreaterThan(0.0); // Ensures that the expect in getRequestFunction is actually called
});
- it('makes a basic request', function() {
- RequestScheduler.maximumRequests = 2;
+ it('updates priority', function() {
+ var invertPriority = false;
- var deferreds = [];
+ function getPriorityFunction(priority) {
+ return function() {
+ if (invertPriority) {
+ return 1.0 - priority;
+ }
+ return priority;
+ };
+ }
- function requestFunction(url) {
- var deferred = when.defer();
- deferreds.push(deferred);
- return deferred.promise;
+ function requestFunction() {
+ return when.resolve();
}
- 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);
+ function createRequest(priority) {
+ return new Request({
+ throttle : true,
+ url : 'https://foo.com/1',
+ requestFunction : requestFunction,
+ priorityFunction : getPriorityFunction(priority)
+ });
+ }
- expect(promise1).toBeDefined();
- expect(promise2).toBeDefined();
- expect(promise3).toBeDefined();
+ var i;
+ var request;
+ var length = RequestScheduler.priorityHeapLength;
+ for (i = 0; i < length; ++i) {
+ var priority = i / (length - 1);
+ request = createRequest(priority);
+ request.testId = i;
+ RequestScheduler.request(request);
+ }
- expect(deferreds[2]).not.toBeDefined();
+ // Update priorities while not letting any requests go through
+ RequestScheduler.maximumRequests = 0;
+ RequestScheduler.update();
+
+ var requestHeap = RequestScheduler.requestHeap;
+ var requests = [];
+ var currentTestId = 0;
+ while (requestHeap.length > 0) {
+ request = requestHeap.pop();
+ requests.push(request);
+ expect(request.testId).toBeGreaterThanOrEqualTo(currentTestId);
+ currentTestId = request.testId;
+ }
- // When the first request completes, the last request starts
- deferreds[0].resolve();
- expect(deferreds[2]).toBeDefined();
+ for (i = 0; i < length; ++i) {
+ requestHeap.insert(requests[i]);
+ }
- deferreds[1].resolve();
- deferreds[2].resolve();
+ invertPriority = true;
+ RequestScheduler.update();
+
+ while (requestHeap.length > 0) {
+ request = requestHeap.pop();
+ expect(request.testId).toBeLessThanOrEqualTo(currentTestId);
+ currentTestId = request.testId;
+ }
});
- it('prioritize requests', function() {
- RequestScheduler.prioritize = true;
- RequestScheduler.maximumRequests = 2;
+ it('handles low priority requests', function() {
+ function requestFunction() {
+ return when.resolve();
+ }
- var deferreds = [];
+ function createRequest(priority) {
+ return new Request({
+ throttle : true,
+ url : 'https://foo.com/1',
+ requestFunction : requestFunction,
+ priority : priority
+ });
+ }
- function requestFunction(url) {
- var deferred = when.defer();
- deferreds.push(deferred);
- return deferred.promise;
+ var highPriority = 0.0;
+ var mediumPriority = 0.5;
+ var lowPriority = 1.0;
+
+ var length = RequestScheduler.priorityHeapLength;
+ for (var i = 0; i < length; ++i) {
+ RequestScheduler.request(createRequest(mediumPriority));
}
- var terrainRequest1 = new Request({
- url : 'http://foo.com/1',
- type : RequestType.TERRAIN,
- requestFunction : requestFunction,
- distance : 10.0
- });
+ // Heap is full so low priority request is not even issued
+ var promise = RequestScheduler.request(createRequest(lowPriority));
+ expect(promise).toBeUndefined();
+ expect(RequestScheduler.statistics.numberOfCancelledRequests).toBe(0);
- var terrainRequest2 = new Request({
- url : 'http://foo.com/2',
- type : RequestType.TERRAIN,
- requestFunction : requestFunction,
- distance : 20.0
- });
+ // Heap is full so high priority request bumps off lower priority request
+ promise = RequestScheduler.request(createRequest(highPriority));
+ expect(promise).toBeDefined();
+ expect(RequestScheduler.statistics.numberOfCancelledRequests).toBe(1);
+ });
- var imageryRequest = new Request({
- url : 'http://bar.com/1',
- type : RequestType.IMAGERY,
- requestFunction : requestFunction,
- distance : 15.0
- });
+ it('unthrottled requests starve throttled requests', function() {
+ var deferreds = [];
- var promise1 = RequestScheduler.schedule(terrainRequest1);
- var promise2 = RequestScheduler.schedule(terrainRequest2);
- var promise3 = RequestScheduler.schedule(imageryRequest);
+ function requestFunction() {
+ var deferred = when.defer();
+ deferreds.push(deferred);
+ return deferred.promise;
+ }
- // 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();
+ function createRequest(throttle) {
+ return new Request({
+ url : 'http://foo.com/1',
+ requestFunction : requestFunction,
+ throttle : throttle
+ });
+ }
- // Budgets should now allow one terrain request and one imagery request (based on their distances)
- RequestScheduler.resetBudgets();
+ var throttledRequest = createRequest(true);
+ RequestScheduler.request(throttledRequest);
- promise1 = RequestScheduler.schedule(terrainRequest1);
- promise2 = RequestScheduler.schedule(terrainRequest2);
- promise3 = RequestScheduler.schedule(imageryRequest);
+ for (var i = 0; i < RequestScheduler.maximumRequests; ++i) {
+ RequestScheduler.request(createRequest(false));
+ }
+ RequestScheduler.update();
- expect(promise1).toBeDefined();
- expect(promise2).not.toBeDefined();
- expect(promise3).toBeDefined();
+ expect(throttledRequest.state).toBe(RequestState.ISSUED);
+ // Resolve one of the unthrottled requests
deferreds[0].resolve();
- deferreds[1].resolve();
+ RequestScheduler.update();
+ expect(throttledRequest.state).toBe(RequestState.ACTIVE);
- RequestScheduler.resetBudgets();
- RequestScheduler.prioritize = false;
+ var length = deferreds.length;
+ for (var j = 0; j < length; ++j) {
+ deferreds[j].resolve();
+ }
});
- it('does not throttle requests when RequestScheduler.throttle is false', function() {
- RequestScheduler.throttle = false;
- RequestScheduler.maximumRequests = 2;
-
+ it('request throttled by server is cancelled', function() {
var deferreds = [];
- function requestFunction(url) {
+ function requestFunction() {
var deferred = when.defer();
deferreds.push(deferred);
return deferred.promise;
}
+ function createRequest(throttleByServer) {
+ return new Request({
+ url : 'http://foo.com/1',
+ requestFunction : requestFunction,
+ throttleByServer : throttleByServer
+ });
+ }
+
+ for (var i = 0; i < RequestScheduler.maximumRequestsPerServer - 1; ++i) {
+ RequestScheduler.request(createRequest(false));
+ }
+
+ var throttledRequest = createRequest(true);
+ RequestScheduler.request(throttledRequest);
+ RequestScheduler.request(createRequest(false));
+
+ RequestScheduler.update();
+ expect(throttledRequest.state).toBe(RequestState.CANCELLED);
+
+ var length = deferreds.length;
+ for (var j = 0; j < length; ++j) {
+ deferreds[j].resolve();
+ }
+ });
+
+ it('throttleRequests', function() {
+ RequestScheduler.maximumRequests = 0;
+
+ function requestFunction() {
+ return when.resolve();
+ }
+
+ RequestScheduler.throttleRequests = true;
var request = new Request({
- url : 'http://foo.com/',
+ throttle : true,
+ url : 'https://foo.com/1',
requestFunction : requestFunction
});
+ var promise = RequestScheduler.request(request);
+ expect(promise).toBeUndefined();
- 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.throttleRequests = false;
+ request = new Request({
+ throttle : true,
+ url : 'https://foo.com/1',
+ requestFunction : requestFunction
+ });
+ promise = RequestScheduler.request(request);
+ expect(promise).toBeDefined();
- RequestScheduler.throttle = true;
+ RequestScheduler.throttleRequests = true;
});
it('debugShowStatistics', function() {
spyOn(console, 'log');
+ RequestScheduler.debugShowStatistics = true;
var deferreds = [];
- function requestFunction(url) {
+ function requestFunction() {
var deferred = when.defer();
deferreds.push(deferred);
return deferred.promise;
}
- var request = new Request({
- url : 'http://foo.com/1',
- requestFunction : requestFunction
- });
+ function createRequest() {
+ return new Request({
+ url : 'https://foo.com/1',
+ requestFunction : requestFunction
+ });
+ }
- RequestScheduler.debugShowStatistics = false;
- RequestScheduler.schedule(request);
- RequestScheduler.resetBudgets();
- expect(console.log).not.toHaveBeenCalled();
+ var requestToCancel = createRequest();
+ RequestScheduler.request(createRequest());
+ RequestScheduler.request(createRequest());
+ RequestScheduler.request(requestToCancel);
+ RequestScheduler.update();
- RequestScheduler.debugShowStatistics = true;
- RequestScheduler.schedule(request);
- RequestScheduler.resetBudgets();
- expect(console.log).toHaveBeenCalled();
+ expect(console.log).toHaveBeenCalledWith('Number of attempted requests: 3');
+ expect(console.log).toHaveBeenCalledWith('Number of active requests: 3');
- deferreds[0].resolve();
- deferreds[1].resolve();
+ deferreds[0].reject();
+ requestToCancel.cancel();
+ RequestScheduler.update();
+
+ expect(console.log).toHaveBeenCalledWith('Number of cancelled requests: 1');
+ expect(console.log).toHaveBeenCalledWith('Number of cancelled active requests: 1');
+ expect(console.log).toHaveBeenCalledWith('Number of failed requests: 1');
+
+ var length = deferreds.length;
+ for (var i = 0; i < length; ++i) {
+ deferreds[i].resolve();
+ }
RequestScheduler.debugShowStatistics = false;
- RequestScheduler.resetBudgets();
});
});
diff --git a/Specs/Core/VRTheWorldTerrainProviderSpec.js b/Specs/Core/VRTheWorldTerrainProviderSpec.js
index f04ccee3a01..cf5858abc29 100644
--- a/Specs/Core/VRTheWorldTerrainProviderSpec.js
+++ b/Specs/Core/VRTheWorldTerrainProviderSpec.js
@@ -7,6 +7,8 @@ defineSuite([
'Core/loadImage',
'Core/loadWithXhr',
'Core/Math',
+ 'Core/Request',
+ 'Core/RequestScheduler',
'Core/TerrainProvider',
'Specs/pollToPromise',
'ThirdParty/when'
@@ -18,12 +20,15 @@ defineSuite([
loadImage,
loadWithXhr,
CesiumMath,
+ Request,
+ RequestScheduler,
TerrainProvider,
pollToPromise,
when) {
'use strict';
beforeEach(function() {
+ RequestScheduler.clearForSpecs();
loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) {
setTimeout(function() {
var parser = new DOMParser();
@@ -58,6 +63,12 @@ defineSuite([
loadWithXhr.load = loadWithXhr.defaultLoad;
});
+ function createRequest() {
+ return new Request({
+ throttleByServer : true
+ });
+ }
+
it('conforms to TerrainProvider interface', function() {
expect(VRTheWorldTerrainProvider).toConformToInterface(TerrainProvider);
});
@@ -273,15 +284,15 @@ defineSuite([
return pollToPromise(function() {
return terrainProvider.ready;
}).then(function() {
- var promise = terrainProvider.requestTileGeometry(0, 0, 0);
- expect(promise).toBeDefined();
-
+ var promise;
var i;
- for (i = 0; i < 10; ++i) {
- promise = terrainProvider.requestTileGeometry(0, 0, 0);
+ for (i = 0; i < RequestScheduler.maximumRequestsPerServer; ++i) {
+ promise = terrainProvider.requestTileGeometry(0, 0, 0, createRequest());
}
+ RequestScheduler.update();
+ expect(promise).toBeDefined();
- promise = terrainProvider.requestTileGeometry(0, 0, 0);
+ promise = terrainProvider.requestTileGeometry(0, 0, 0, createRequest());
expect(promise).toBeUndefined();
for (i = 0; i < deferreds.length; ++i) {
diff --git a/Specs/Core/isBlobUriSpec.js b/Specs/Core/isBlobUriSpec.js
new file mode 100644
index 00000000000..d319b0a7bf5
--- /dev/null
+++ b/Specs/Core/isBlobUriSpec.js
@@ -0,0 +1,27 @@
+/*global defineSuite*/
+defineSuite([
+ 'Core/isBlobUri'
+ ], function(
+ isBlobUri) {
+ 'use strict';
+
+ it('Throws if url is undefined', function() {
+ expect(function() {
+ isBlobUri(undefined);
+ }).toThrowDeveloperError();
+ });
+
+ it('Determines that a uri is not a blob uri', function() {
+ expect(isBlobUri('http://cesiumjs.org/')).toEqual(false);
+ });
+
+ it('Determines that a uri is a blob uri', function() {
+ var uint8Array = new Uint8Array(4);
+ var blob = new Blob([uint8Array], {
+ type : 'application/octet-stream'
+ });
+
+ var blobUrl = window.URL.createObjectURL(blob);
+ expect(isBlobUri(blobUrl)).toEqual(true);
+ });
+});
diff --git a/Specs/Core/isDataUriSpec.js b/Specs/Core/isDataUriSpec.js
index 9f2b4294de1..b3864848630 100644
--- a/Specs/Core/isDataUriSpec.js
+++ b/Specs/Core/isDataUriSpec.js
@@ -5,8 +5,13 @@ defineSuite([
isDataUri) {
'use strict';
+ it('Throws if url is undefined', function() {
+ expect(function() {
+ isDataUri(undefined);
+ }).toThrowDeveloperError();
+ });
+
it('Determines that a uri is not a data uri', function() {
- expect(isDataUri(undefined)).toEqual(false);
expect(isDataUri('http://cesiumjs.org/')).toEqual(false);
});
diff --git a/Specs/Core/loadArrayBufferSpec.js b/Specs/Core/loadArrayBufferSpec.js
index b6c6ad825d7..11b5bdd5407 100644
--- a/Specs/Core/loadArrayBufferSpec.js
+++ b/Specs/Core/loadArrayBufferSpec.js
@@ -1,10 +1,14 @@
/*global defineSuite*/
defineSuite([
'Core/loadArrayBuffer',
- 'Core/RequestErrorEvent'
+ 'Core/Request',
+ 'Core/RequestErrorEvent',
+ 'Core/RequestScheduler'
], function(
loadArrayBuffer,
- RequestErrorEvent) {
+ Request,
+ RequestErrorEvent,
+ RequestScheduler) {
'use strict';
var fakeXHR;
@@ -135,4 +139,19 @@ defineSuite([
expect(rejectedError.statusCode).toEqual(404);
expect(rejectedError.response).toEqual(error);
});
+
+ it('returns undefined if the request is throttled', function() {
+ var oldMaximumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0;
+
+ var request = new Request({
+ throttle : true
+ });
+
+ var testUrl = 'http://example.invalid/testuri';
+ var promise = loadArrayBuffer(testUrl, undefined, request);
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.maximumRequests = oldMaximumRequests;
+ });
});
diff --git a/Specs/Core/loadBlobSpec.js b/Specs/Core/loadBlobSpec.js
index 449fd2edc6a..af7f4059988 100644
--- a/Specs/Core/loadBlobSpec.js
+++ b/Specs/Core/loadBlobSpec.js
@@ -1,10 +1,14 @@
/*global defineSuite*/
defineSuite([
'Core/loadBlob',
- 'Core/RequestErrorEvent'
+ 'Core/Request',
+ 'Core/RequestErrorEvent',
+ 'Core/RequestScheduler'
], function(
loadBlob,
- RequestErrorEvent) {
+ Request,
+ RequestErrorEvent,
+ RequestScheduler) {
'use strict';
var fakeXHR;
@@ -135,4 +139,19 @@ defineSuite([
expect(rejectedError.statusCode).toEqual(404);
expect(rejectedError.response).toEqual(error);
});
+
+ it('returns undefined if the request is throttled', function() {
+ var oldMaximumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0;
+
+ var request = new Request({
+ throttle : true
+ });
+
+ var testUrl = 'http://example.invalid/testuri';
+ var promise = loadBlob(testUrl, undefined, request);
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.maximumRequests = oldMaximumRequests;
+ });
});
diff --git a/Specs/Core/loadCRNSpec.js b/Specs/Core/loadCRNSpec.js
index 7377bb260ba..c184d0ab71e 100644
--- a/Specs/Core/loadCRNSpec.js
+++ b/Specs/Core/loadCRNSpec.js
@@ -2,11 +2,15 @@
defineSuite([
'Core/loadCRN',
'Core/PixelFormat',
- 'Core/RequestErrorEvent'
+ 'Core/Request',
+ 'Core/RequestErrorEvent',
+ 'Core/RequestScheduler'
], function(
loadCRN,
PixelFormat,
- RequestErrorEvent) {
+ Request,
+ RequestErrorEvent,
+ RequestScheduler) {
'use strict';
var validCompressed = new Uint8Array([72, 120, 0, 74, 227, 123, 0, 0, 0, 138, 92, 167, 0, 4, 0, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 22, 0, 1, 0, 0, 96, 0, 0, 12, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 108, 0, 0, 0, 137, 0, 10, 96, 0, 0, 0, 0, 0, 0, 16, 4, 9, 130, 0, 0, 0, 0, 0, 0, 109, 4, 0, 0, 198, 96, 128, 0, 0, 0, 0, 0, 26, 80, 0, 0, 6, 96, 0, 0, 0, 0, 0, 0, 16, 0, 51, 0, 0, 0, 0, 0, 0, 0, 128, 1, 152, 0, 0, 0, 0, 0, 0, 4, 0]);
@@ -145,4 +149,19 @@ defineSuite([
expect(rejectedError).toBeUndefined();
});
});
+
+ it('returns undefined if the request is throttled', function() {
+ var oldMaximumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0;
+
+ var request = new Request({
+ throttle : true
+ });
+
+ var testUrl = 'http://example.invalid/testuri';
+ var promise = loadCRN(testUrl, undefined, request);
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.maximumRequests = oldMaximumRequests;
+ });
});
diff --git a/Specs/Core/loadImageSpec.js b/Specs/Core/loadImageSpec.js
index 2a31f4cb2bd..9837b4e7941 100644
--- a/Specs/Core/loadImageSpec.js
+++ b/Specs/Core/loadImageSpec.js
@@ -1,9 +1,13 @@
/*global defineSuite*/
defineSuite([
'Core/loadImage',
+ 'Core/Request',
+ 'Core/RequestScheduler',
'ThirdParty/when'
], function(
loadImage,
+ Request,
+ RequestScheduler,
when) {
'use strict';
@@ -114,4 +118,19 @@ defineSuite([
expect(failure).toEqual(true);
expect(loadedImage).toBeUndefined();
});
+
+ it('returns undefined if the request is throttled', function() {
+ var oldMaximumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0;
+
+ var request = new Request({
+ throttle : true
+ });
+
+ var testUrl = 'http://example.invalid/testuri';
+ var promise = loadImage(testUrl, undefined, request);
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.maximumRequests = oldMaximumRequests;
+ });
});
diff --git a/Specs/Core/loadImageViaBlobSpec.js b/Specs/Core/loadImageViaBlobSpec.js
index bfbe82e5b96..440a4b6c574 100644
--- a/Specs/Core/loadImageViaBlobSpec.js
+++ b/Specs/Core/loadImageViaBlobSpec.js
@@ -1,9 +1,13 @@
/*global defineSuite*/
defineSuite([
'Core/loadImageViaBlob',
+ 'Core/Request',
+ 'Core/RequestScheduler',
'ThirdParty/when'
], function(
loadImageViaBlob,
+ Request,
+ RequestScheduler,
when) {
'use strict';
@@ -78,4 +82,19 @@ defineSuite([
expect(failure).toEqual(true);
expect(loadedImage).toBeUndefined();
});
+
+ it('returns undefined if the request is throttled', function() {
+ var oldMaximumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0;
+
+ var request = new Request({
+ throttle : true
+ });
+
+ var testUrl = 'http://example.invalid/testuri';
+ var promise = loadImageViaBlob(testUrl, request);
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.maximumRequests = oldMaximumRequests;
+ });
});
diff --git a/Specs/Core/loadJsonSpec.js b/Specs/Core/loadJsonSpec.js
index 123763021d5..ac2618220a3 100644
--- a/Specs/Core/loadJsonSpec.js
+++ b/Specs/Core/loadJsonSpec.js
@@ -1,10 +1,14 @@
/*global defineSuite*/
defineSuite([
'Core/loadJson',
- 'Core/RequestErrorEvent'
+ 'Core/Request',
+ 'Core/RequestErrorEvent',
+ 'Core/RequestScheduler'
], function(
loadJson,
- RequestErrorEvent) {
+ Request,
+ RequestErrorEvent,
+ RequestScheduler) {
'use strict';
var fakeXHR;
@@ -139,4 +143,19 @@ defineSuite([
expect(rejectedError.statusCode).toEqual(404);
expect(rejectedError.response).toEqual(error);
});
+
+ it('returns undefined if the request is throttled', function() {
+ var oldMaximumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0;
+
+ var request = new Request({
+ throttle : true
+ });
+
+ var testUrl = 'http://example.invalid/testuri';
+ var promise = loadJson(testUrl, undefined, request);
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.maximumRequests = oldMaximumRequests;
+ });
});
diff --git a/Specs/Core/loadJsonpSpec.js b/Specs/Core/loadJsonpSpec.js
index 9b735b69217..7cc442fe470 100644
--- a/Specs/Core/loadJsonpSpec.js
+++ b/Specs/Core/loadJsonpSpec.js
@@ -1,10 +1,14 @@
/*global defineSuite*/
defineSuite([
'Core/loadJsonp',
- 'Core/DefaultProxy'
+ 'Core/DefaultProxy',
+ 'Core/Request',
+ 'Core/RequestScheduler'
], function(
loadJsonp,
- DefaultProxy) {
+ DefaultProxy,
+ Request,
+ RequestScheduler) {
'use strict';
it('throws with no url', function() {
@@ -66,4 +70,19 @@ defineSuite([
});
loadJsonp(testUrl, options);
});
+
+ it('returns undefined if the request is throttled', function() {
+ var oldMaximumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0;
+
+ var request = new Request({
+ throttle : true
+ });
+
+ var testUrl = 'http://example.invalid/testuri';
+ var promise = loadJsonp(testUrl, undefined, request);
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.maximumRequests = oldMaximumRequests;
+ });
});
diff --git a/Specs/Core/loadKTXSpec.js b/Specs/Core/loadKTXSpec.js
index 3718ca996ca..387854c68e7 100644
--- a/Specs/Core/loadKTXSpec.js
+++ b/Specs/Core/loadKTXSpec.js
@@ -2,12 +2,16 @@
defineSuite([
'Core/loadKTX',
'Core/PixelFormat',
+ 'Core/Request',
'Core/RequestErrorEvent',
+ 'Core/RequestScheduler',
'Core/RuntimeError'
], function(
loadKTX,
PixelFormat,
+ Request,
RequestErrorEvent,
+ RequestScheduler,
RuntimeError) {
'use strict';
@@ -333,7 +337,7 @@ defineSuite([
expect(rejectedError.message).toEqual('3D textures are unsupported.');
});
- it('Texture arrays are unsupported', function() {
+ it('texture arrays are unsupported', function() {
var reinterprestBuffer = new Uint32Array(validUncompressed.buffer);
var invalidKTX = new Uint32Array(reinterprestBuffer);
invalidKTX[12] = 15;
@@ -353,7 +357,7 @@ defineSuite([
expect(rejectedError.message).toEqual('Texture arrays are unsupported.');
});
- it('Cubemaps are unsupported', function() {
+ it('cubemaps are unsupported', function() {
var reinterprestBuffer = new Uint32Array(validUncompressed.buffer);
var invalidKTX = new Uint32Array(reinterprestBuffer);
invalidKTX[13] = 6;
@@ -372,4 +376,19 @@ defineSuite([
expect(rejectedError instanceof RuntimeError).toEqual(true);
expect(rejectedError.message).toEqual('Cubemaps are unsupported.');
});
+
+ it('returns undefined if the request is throttled', function() {
+ var oldMaximumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0;
+
+ var request = new Request({
+ throttle : true
+ });
+
+ var testUrl = 'http://example.invalid/testuri';
+ var promise = loadKTX(testUrl, undefined, request);
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.maximumRequests = oldMaximumRequests;
+ });
});
diff --git a/Specs/Core/loadTextSpec.js b/Specs/Core/loadTextSpec.js
index 74c417d48a7..62bc5620170 100644
--- a/Specs/Core/loadTextSpec.js
+++ b/Specs/Core/loadTextSpec.js
@@ -1,10 +1,14 @@
/*global defineSuite*/
defineSuite([
'Core/loadText',
- 'Core/RequestErrorEvent'
+ 'Core/Request',
+ 'Core/RequestErrorEvent',
+ 'Core/RequestScheduler'
], function(
loadText,
- RequestErrorEvent) {
+ Request,
+ RequestErrorEvent,
+ RequestScheduler) {
'use strict';
var fakeXHR;
@@ -135,4 +139,19 @@ defineSuite([
expect(rejectedError.statusCode).toEqual(404);
expect(rejectedError.response).toEqual(error);
});
+
+ it('returns undefined if the request is throttled', function() {
+ var oldMaximumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0;
+
+ var request = new Request({
+ throttle : true
+ });
+
+ var testUrl = 'http://example.invalid/testuri';
+ var promise = loadText(testUrl, undefined, request);
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.maximumRequests = oldMaximumRequests;
+ });
});
diff --git a/Specs/Core/loadWithXhrSpec.js b/Specs/Core/loadWithXhrSpec.js
index 6b2b5a5742e..84aa74f13f0 100644
--- a/Specs/Core/loadWithXhrSpec.js
+++ b/Specs/Core/loadWithXhrSpec.js
@@ -2,11 +2,15 @@
defineSuite([
'Core/loadWithXhr',
'Core/loadImage',
- 'Core/RequestErrorEvent'
+ 'Core/Request',
+ 'Core/RequestErrorEvent',
+ 'Core/RequestScheduler'
], function(
loadWithXhr,
loadImage,
- RequestErrorEvent) {
+ Request,
+ RequestErrorEvent,
+ RequestScheduler) {
'use strict';
it('throws with no url', function() {
@@ -15,6 +19,24 @@ defineSuite([
}).toThrowDeveloperError();
});
+ it('returns undefined if the request is throttled', function() {
+ var oldMaximumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0;
+
+ var request = new Request({
+ throttle : true
+ });
+
+ var testUrl = 'http://example.invalid/testuri';
+ var promise = loadWithXhr({
+ url : testUrl,
+ request : request
+ });
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.maximumRequests = oldMaximumRequests;
+ });
+
describe('data URI loading', function() {
it('can load URI escaped text with default response type', function() {
return loadWithXhr({
diff --git a/Specs/Core/loadXMLSpec.js b/Specs/Core/loadXMLSpec.js
index 8c023a8df2b..b79a58909b9 100644
--- a/Specs/Core/loadXMLSpec.js
+++ b/Specs/Core/loadXMLSpec.js
@@ -1,10 +1,14 @@
/*global defineSuite*/
defineSuite([
'Core/loadXML',
- 'Core/RequestErrorEvent'
+ 'Core/Request',
+ 'Core/RequestErrorEvent',
+ 'Core/RequestScheduler'
], function(
loadXML,
- RequestErrorEvent) {
+ Request,
+ RequestErrorEvent,
+ RequestScheduler) {
'use strict';
var fakeXHR;
@@ -137,4 +141,19 @@ defineSuite([
expect(rejectedError.statusCode).toEqual(404);
expect(rejectedError.response).toEqual(error);
});
+
+ it('returns undefined if the request is throttled', function() {
+ var oldMaximumRequests = RequestScheduler.maximumRequests;
+ RequestScheduler.maximumRequests = 0;
+
+ var request = new Request({
+ throttle : true
+ });
+
+ var testUrl = 'http://example.invalid/testuri';
+ var promise = loadXML(testUrl, undefined, request);
+ expect(promise).toBeUndefined();
+
+ RequestScheduler.maximumRequests = oldMaximumRequests;
+ });
});
diff --git a/Specs/DomEventSimulator.js b/Specs/DomEventSimulator.js
index 75c99d375db..bf84b47fdb3 100644
--- a/Specs/DomEventSimulator.js
+++ b/Specs/DomEventSimulator.js
@@ -215,8 +215,18 @@ define([
var gamma = defaultValue(options.gamma, 0.0);
var absolute = defaultValue(options.absolute, false);
- var event = document.createEvent('DeviceOrientationEvent');
- event.initDeviceOrientationEvent(type, canBubble, cancelable, alpha, beta, gamma, absolute);
+ var event;
+ event = document.createEvent('DeviceOrientationEvent');
+ if (typeof event.initDeviceOrientationEvent === 'function') {
+ event.initDeviceOrientationEvent(type, canBubble, cancelable, alpha, beta, gamma, absolute);
+ } else {
+ event = new DeviceOrientationEvent('deviceorientation', {
+ alpha : alpha,
+ beta : beta,
+ gamma : gamma,
+ absolute : absolute
+ });
+ }
return event;
}
diff --git a/Specs/Scene/ArcGisMapServerImageryProviderSpec.js b/Specs/Scene/ArcGisMapServerImageryProviderSpec.js
index 14276f52a1f..1dcde473ebb 100644
--- a/Specs/Scene/ArcGisMapServerImageryProviderSpec.js
+++ b/Specs/Scene/ArcGisMapServerImageryProviderSpec.js
@@ -12,6 +12,7 @@ defineSuite([
'Core/loadWithXhr',
'Core/queryToObject',
'Core/Rectangle',
+ 'Core/RequestScheduler',
'Core/WebMercatorProjection',
'Core/WebMercatorTilingScheme',
'Scene/DiscardMissingTileImagePolicy',
@@ -35,6 +36,7 @@ defineSuite([
loadWithXhr,
queryToObject,
Rectangle,
+ RequestScheduler,
WebMercatorProjection,
WebMercatorTilingScheme,
DiscardMissingTileImagePolicy,
@@ -47,6 +49,10 @@ defineSuite([
Uri) {
'use strict';
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
+ });
+
afterEach(function() {
loadJsonp.loadAndExecuteScript = loadJsonp.defaultLoadAndExecuteScript;
loadImage.createImage = loadImage.defaultCreateImage;
@@ -602,6 +608,9 @@ defineSuite([
if (tries < 3) {
error.retry = true;
}
+ setTimeout(function() {
+ RequestScheduler.update();
+ }, 1);
});
loadImage.createImage = function(url, crossOrigin, deferred) {
@@ -622,6 +631,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
diff --git a/Specs/Scene/BingMapsImageryProviderSpec.js b/Specs/Scene/BingMapsImageryProviderSpec.js
index ca2f087ef34..192a04c2374 100644
--- a/Specs/Scene/BingMapsImageryProviderSpec.js
+++ b/Specs/Scene/BingMapsImageryProviderSpec.js
@@ -6,6 +6,7 @@ defineSuite([
'Core/loadImage',
'Core/loadJsonp',
'Core/loadWithXhr',
+ 'Core/RequestScheduler',
'Core/WebMercatorTilingScheme',
'Scene/BingMapsStyle',
'Scene/DiscardMissingTileImagePolicy',
@@ -21,6 +22,7 @@ defineSuite([
loadImage,
loadJsonp,
loadWithXhr,
+ RequestScheduler,
WebMercatorTilingScheme,
BingMapsStyle,
DiscardMissingTileImagePolicy,
@@ -31,6 +33,10 @@ defineSuite([
pollToPromise) {
'use strict';
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
+ });
+
afterEach(function() {
loadJsonp.loadAndExecuteScript = loadJsonp.defaultLoadAndExecuteScript;
loadImage.createImage = loadImage.defaultCreateImage;
@@ -364,6 +370,9 @@ defineSuite([
if (tries < 3) {
error.retry = true;
}
+ setTimeout(function() {
+ RequestScheduler.update();
+ }, 1);
});
loadImage.createImage = function(url, crossOrigin, deferred) {
@@ -399,6 +408,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
diff --git a/Specs/Scene/CameraSpec.js b/Specs/Scene/CameraSpec.js
index 21403eb8147..63e083a0e03 100644
--- a/Specs/Scene/CameraSpec.js
+++ b/Specs/Scene/CameraSpec.js
@@ -1223,6 +1223,25 @@ defineSuite([
expect(camera.frustum.bottom).toEqual(-1.25, CesiumMath.EPSILON10);
});
+ it('zooms out 2D when frustrum has greater height than width', function() {
+ var frustum = new OrthographicOffCenterFrustum();
+ frustum.near = 1.0;
+ frustum.far = 2.0;
+ frustum.left = -1.0;
+ frustum.right = 1.0;
+ frustum.top = 2.0;
+ frustum.bottom = -2.0;
+ camera.frustum = frustum;
+
+ camera.update(SceneMode.SCENE2D);
+
+ camera.zoomOut(zoomAmount);
+ expect(camera.frustum.right).toEqualEpsilon(1.25, CesiumMath.EPSILON10);
+ expect(camera.frustum.left).toEqual(-1.25, CesiumMath.EPSILON10);
+ expect(camera.frustum.top).toEqual(2.5, CesiumMath.EPSILON10);
+ expect(camera.frustum.bottom).toEqual(-2.5, CesiumMath.EPSILON10);
+ });
+
it('zooms in 2D', function() {
var frustum = new OrthographicOffCenterFrustum();
frustum.near = 1.0;
@@ -1242,6 +1261,25 @@ defineSuite([
expect(camera.frustum.bottom).toEqual(-0.75, CesiumMath.EPSILON10);
});
+ it('zooms in 2D when frustrum has greater height than width', function() {
+ var frustum = new OrthographicOffCenterFrustum();
+ frustum.near = 1.0;
+ frustum.far = 2.0;
+ frustum.left = -1.0;
+ frustum.right = 1.0;
+ frustum.top = 2.0;
+ frustum.bottom = -2.0;
+ camera.frustum = frustum;
+
+ camera.update(SceneMode.SCENE2D);
+
+ camera.zoomIn(zoomAmount);
+ expect(camera.frustum.right).toEqualEpsilon(0.75, CesiumMath.EPSILON10);
+ expect(camera.frustum.left).toEqual(-0.75, CesiumMath.EPSILON10);
+ expect(camera.frustum.top).toEqual(1.5, CesiumMath.EPSILON10);
+ expect(camera.frustum.bottom).toEqual(-1.5, CesiumMath.EPSILON10);
+ });
+
it('clamps zoom out in 2D', function() {
var frustum = new OrthographicOffCenterFrustum();
frustum.near = 1.0;
@@ -2850,4 +2888,12 @@ defineSuite([
camera.switchToPerspectiveFrustum();
expect(camera.frustum instanceof OrthographicOffCenterFrustum).toEqual(true);
});
+
+ it('normalizes WC members', function() {
+ var transform = Matrix4.fromScale(new Cartesian3(2, 2, 2));
+ camera.lookAtTransform(transform);
+ expect(Cartesian3.magnitude(camera.directionWC)).toEqualEpsilon(1.0, CesiumMath.EPSILON15);
+ expect(Cartesian3.magnitude(camera.rightWC)).toEqualEpsilon(1.0, CesiumMath.EPSILON15);
+ expect(Cartesian3.magnitude(camera.upWC)).toEqualEpsilon(1.0, CesiumMath.EPSILON15);
+ });
});
diff --git a/Specs/Scene/GlobeSurfaceTileSpec.js b/Specs/Scene/GlobeSurfaceTileSpec.js
index a0fd47f861f..4268b3f602e 100644
--- a/Specs/Scene/GlobeSurfaceTileSpec.js
+++ b/Specs/Scene/GlobeSurfaceTileSpec.js
@@ -9,6 +9,7 @@ defineSuite([
'Core/GeographicTilingScheme',
'Core/Ray',
'Core/Rectangle',
+ 'Core/RequestScheduler',
'Scene/Imagery',
'Scene/ImageryLayer',
'Scene/ImageryLayerCollection',
@@ -30,6 +31,7 @@ defineSuite([
GeographicTilingScheme,
Ray,
Rectangle,
+ RequestScheduler,
Imagery,
ImageryLayer,
ImageryLayerCollection,
@@ -143,6 +145,7 @@ defineSuite([
it('once a root tile is loaded, its children get both loadedTerrain and upsampledTerrain', function() {
return pollToPromise(function() {
GlobeSurfaceTile.processStateMachine(rootTile, scene.frameState, realTerrainProvider, imageryLayerCollection, []);
+ RequestScheduler.update();
return rootTile.state === QuadtreeTileLoadState.DONE;
}).then(function() {
var children = rootTile.children;
@@ -157,6 +160,7 @@ defineSuite([
it('loaded terrainData is copied to the tile once it is available', function() {
return pollToPromise(function() {
GlobeSurfaceTile.processStateMachine(rootTile, scene.frameState, realTerrainProvider, imageryLayerCollection, []);
+ RequestScheduler.update();
return rootTile.data.loadedTerrain.state >= TerrainState.RECEIVED;
}).then(function() {
expect(rootTile.data.terrainData).toBeDefined();
@@ -281,6 +285,7 @@ defineSuite([
return pollToPromise(function() {
GlobeSurfaceTile.processStateMachine(rootTile, scene.frameState, realTerrainProvider, imageryLayerCollection, []);
GlobeSurfaceTile.processStateMachine(childTile, scene.frameState, alwaysDeferTerrainProvider, imageryLayerCollection, []);
+ RequestScheduler.update();
return rootTile.renderable && childTile.renderable;
}).then(function() {
expect(childTile.data.waterMaskTexture).toBeDefined();
@@ -290,6 +295,7 @@ defineSuite([
return pollToPromise(function() {
GlobeSurfaceTile.processStateMachine(childTile, scene.frameState, realTerrainProvider, imageryLayerCollection, vertexArraysToDestroy);
+ RequestScheduler.update();
return childTile.state === QuadtreeTileLoadState.DONE;
}).then(function() {
expect(childTile.data.waterMaskTexture).toBeDefined();
@@ -306,6 +312,7 @@ defineSuite([
return pollToPromise(function() {
GlobeSurfaceTile.processStateMachine(rootTile, scene.frameState, realTerrainProvider, imageryLayerCollection, []);
GlobeSurfaceTile.processStateMachine(childTile, scene.frameState, alwaysFailTerrainProvider, imageryLayerCollection, []);
+ RequestScheduler.update();
return rootTile.renderable && childTile.renderable;
}).then(function() {
expect(childTile.data.loadedTerrain).toBeUndefined();
@@ -320,6 +327,7 @@ defineSuite([
return pollToPromise(function() {
GlobeSurfaceTile.processStateMachine(rootTile, scene.frameState, realTerrainProvider, imageryLayerCollection, []);
GlobeSurfaceTile.processStateMachine(childTile, scene.frameState, alwaysDeferTerrainProvider, imageryLayerCollection, []);
+ RequestScheduler.update();
return defined(rootTile.data.terrainData) && defined(rootTile.data.terrainData._mesh) &&
defined(childTile.data.terrainData);
}).then(function() {
@@ -328,6 +336,7 @@ defineSuite([
return pollToPromise(function() {
GlobeSurfaceTile.processStateMachine(grandchildTile, scene.frameState, realTerrainProvider, imageryLayerCollection, []);
+ RequestScheduler.update();
return grandchildTile.state === QuadtreeTileLoadState.DONE;
}).then(function() {
expect(grandchildTile.data.loadedTerrain).toBeUndefined();
@@ -337,6 +346,7 @@ defineSuite([
return pollToPromise(function() {
GlobeSurfaceTile.processStateMachine(childTile, scene.frameState, realTerrainProvider, imageryLayerCollection, vertexArraysToDestroy);
+ RequestScheduler.update();
return childTile.state === QuadtreeTileLoadState.DONE;
}).then(function() {
expect(grandchildTile.state).toBe(QuadtreeTileLoadState.DONE);
@@ -357,6 +367,7 @@ defineSuite([
GlobeSurfaceTile.processStateMachine(rootTile, scene.frameState, realTerrainProvider, imageryLayerCollection, []);
GlobeSurfaceTile.processStateMachine(childTile, scene.frameState, alwaysDeferTerrainProvider, imageryLayerCollection, []);
GlobeSurfaceTile.processStateMachine(grandchildTile, scene.frameState, alwaysDeferTerrainProvider, imageryLayerCollection, []);
+ RequestScheduler.update();
return defined(rootTile.data.terrainData) && defined(rootTile.data.terrainData._mesh) &&
defined(childTile.data.terrainData) && defined(childTile.data.terrainData._mesh) &&
defined(grandchildTile.data.terrainData);
@@ -366,6 +377,7 @@ defineSuite([
return pollToPromise(function() {
GlobeSurfaceTile.processStateMachine(greatGrandchildTile, scene.frameState, realTerrainProvider, imageryLayerCollection, []);
+ RequestScheduler.update();
return greatGrandchildTile.state === QuadtreeTileLoadState.DONE;
}).then(function() {
expect(greatGrandchildTile.data.loadedTerrain).toBeUndefined();
@@ -376,6 +388,7 @@ defineSuite([
return pollToPromise(function() {
GlobeSurfaceTile.processStateMachine(childTile, scene.frameState, realTerrainProvider, imageryLayerCollection, vertexArraysToBeDestroyed);
GlobeSurfaceTile.processStateMachine(grandchildTile, scene.frameState, alwaysDeferTerrainProvider, imageryLayerCollection, vertexArraysToBeDestroyed);
+ RequestScheduler.update();
return childTile.state === QuadtreeTileLoadState.DONE &&
!defined(grandchildTile.data.upsampledTerrain);
}).then(function() {
@@ -394,6 +407,7 @@ defineSuite([
return pollToPromise(function() {
GlobeSurfaceTile.processStateMachine(rootTile, scene.frameState, realTerrainProvider, imageryLayerCollection, []);
GlobeSurfaceTile.processStateMachine(childTile, scene.frameState, alwaysFailTerrainProvider, imageryLayerCollection, []);
+ RequestScheduler.update();
return rootTile.state >= QuadtreeTileLoadState.DONE &&
childTile.state >= QuadtreeTileLoadState.DONE;
}).then(function() {
@@ -538,6 +552,7 @@ defineSuite([
}
GlobeSurfaceTile.processStateMachine(tile, scene.frameState, terrainProvider, imageryLayerCollection, []);
+ RequestScheduler.update();
return tile.state === QuadtreeTileLoadState.DONE;
}).then(function() {
var ray = new Ray(
diff --git a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js
index 0e23da6755b..00eec62d9ac 100644
--- a/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js
+++ b/Specs/Scene/GoogleEarthEnterpriseImageryProviderSpec.js
@@ -11,6 +11,7 @@ defineSuite([
'Core/loadImage',
'Core/loadWithXhr',
'Core/Rectangle',
+ 'Core/RequestScheduler',
'Scene/DiscardMissingTileImagePolicy',
'Scene/Imagery',
'Scene/ImageryLayer',
@@ -30,6 +31,7 @@ defineSuite([
loadImage,
loadWithXhr,
Rectangle,
+ RequestScheduler,
DiscardMissingTileImagePolicy,
Imagery,
ImageryLayer,
@@ -39,6 +41,10 @@ defineSuite([
when) {
'use strict';
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
+ });
+
beforeAll(function() {
decodeGoogleEarthEnterpriseData.passThroughDataForTesting = true;
});
@@ -259,6 +265,9 @@ defineSuite([
if (tries < 3) {
error.retry = true;
}
+ setTimeout(function() {
+ RequestScheduler.update();
+ }, 1);
});
loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) {
@@ -279,6 +288,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
diff --git a/Specs/Scene/GoogleEarthEnterpriseMapsProviderSpec.js b/Specs/Scene/GoogleEarthEnterpriseMapsProviderSpec.js
index 5fec67730cf..7fa6649ef47 100644
--- a/Specs/Scene/GoogleEarthEnterpriseMapsProviderSpec.js
+++ b/Specs/Scene/GoogleEarthEnterpriseMapsProviderSpec.js
@@ -6,6 +6,7 @@ defineSuite([
'Core/loadImage',
'Core/loadWithXhr',
'Core/Rectangle',
+ 'Core/RequestScheduler',
'Core/WebMercatorTilingScheme',
'Scene/Imagery',
'Scene/ImageryLayer',
@@ -19,6 +20,7 @@ defineSuite([
loadImage,
loadWithXhr,
Rectangle,
+ RequestScheduler,
WebMercatorTilingScheme,
Imagery,
ImageryLayer,
@@ -307,6 +309,9 @@ defineSuite([
if (tries < 3) {
error.retry = true;
}
+ setTimeout(function() {
+ RequestScheduler.update();
+ }, 1);
});
loadImage.createImage = function(url, crossOrigin, deferred) {
@@ -342,6 +347,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
diff --git a/Specs/Scene/ImageryLayerSpec.js b/Specs/Scene/ImageryLayerSpec.js
index 4c9ec1fcbcb..151b91d5d0c 100644
--- a/Specs/Scene/ImageryLayerSpec.js
+++ b/Specs/Scene/ImageryLayerSpec.js
@@ -6,6 +6,7 @@ defineSuite([
'Core/loadJsonp',
'Core/loadWithXhr',
'Core/Rectangle',
+ 'Core/RequestScheduler',
'Renderer/ComputeEngine',
'Scene/ArcGisMapServerImageryProvider',
'Scene/BingMapsImageryProvider',
@@ -28,6 +29,7 @@ defineSuite([
loadJsonp,
loadWithXhr,
Rectangle,
+ RequestScheduler,
ComputeEngine,
ArcGisMapServerImageryProvider,
BingMapsImageryProvider,
@@ -104,6 +106,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
@@ -166,6 +169,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
@@ -202,6 +206,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 1, 3);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
@@ -237,6 +242,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 1, 3);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
@@ -292,6 +298,7 @@ defineSuite([
var imagery = new Imagery(layer, 4400, 2686, 13);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
@@ -327,6 +334,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
@@ -386,6 +394,7 @@ defineSuite([
return provider.ready;
}).then(function() {
imageryLayer._requestImagery(new Imagery(imageryLayer, 0, 0, 0));
+ RequestScheduler.update();
return pollToPromise(function() {
return errorRaised;
diff --git a/Specs/Scene/MapboxImageryProviderSpec.js b/Specs/Scene/MapboxImageryProviderSpec.js
index 60e382b7b09..50b681bd441 100644
--- a/Specs/Scene/MapboxImageryProviderSpec.js
+++ b/Specs/Scene/MapboxImageryProviderSpec.js
@@ -5,6 +5,7 @@ defineSuite([
'Core/loadImage',
'Core/Math',
'Core/Rectangle',
+ 'Core/RequestScheduler',
'Core/WebMercatorTilingScheme',
'Scene/Imagery',
'Scene/ImageryLayer',
@@ -17,6 +18,7 @@ defineSuite([
loadImage,
CesiumMath,
Rectangle,
+ RequestScheduler,
WebMercatorTilingScheme,
Imagery,
ImageryLayer,
@@ -25,6 +27,10 @@ defineSuite([
pollToPromise) {
'use strict';
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
+ });
+
afterEach(function() {
loadImage.createImage = loadImage.defaultCreateImage;
});
@@ -250,6 +256,9 @@ defineSuite([
if (tries < 3) {
error.retry = true;
}
+ setTimeout(function() {
+ RequestScheduler.update();
+ }, 1);
});
loadImage.createImage = function(url, crossOrigin, deferred) {
@@ -270,6 +279,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
diff --git a/Specs/Scene/UrlTemplateImageryProviderSpec.js b/Specs/Scene/UrlTemplateImageryProviderSpec.js
index 41b3af874c2..f3fbe8d77e9 100644
--- a/Specs/Scene/UrlTemplateImageryProviderSpec.js
+++ b/Specs/Scene/UrlTemplateImageryProviderSpec.js
@@ -7,6 +7,7 @@ defineSuite([
'Core/loadImage',
'Core/Math',
'Core/Rectangle',
+ 'Core/RequestScheduler',
'Core/WebMercatorProjection',
'Core/WebMercatorTilingScheme',
'Scene/GetFeatureInfoFormat',
@@ -24,6 +25,7 @@ defineSuite([
loadImage,
CesiumMath,
Rectangle,
+ RequestScheduler,
WebMercatorProjection,
WebMercatorTilingScheme,
GetFeatureInfoFormat,
@@ -35,6 +37,10 @@ defineSuite([
when) {
'use strict';
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
+ });
+
afterEach(function() {
loadImage.createImage = loadImage.defaultCreateImage;
});
@@ -204,6 +210,9 @@ defineSuite([
if (tries < 3) {
error.retry = true;
}
+ setTimeout(function() {
+ RequestScheduler.update();
+ }, 1);
});
loadImage.createImage = function(url, crossOrigin, deferred) {
@@ -224,6 +233,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
diff --git a/Specs/Scene/WebMapServiceImageryProviderSpec.js b/Specs/Scene/WebMapServiceImageryProviderSpec.js
index e97d27bbe36..3f729fd6469 100644
--- a/Specs/Scene/WebMapServiceImageryProviderSpec.js
+++ b/Specs/Scene/WebMapServiceImageryProviderSpec.js
@@ -10,6 +10,7 @@ defineSuite([
'Core/Math',
'Core/queryToObject',
'Core/Rectangle',
+ 'Core/RequestScheduler',
'Core/WebMercatorTilingScheme',
'Scene/GetFeatureInfoFormat',
'Scene/Imagery',
@@ -30,6 +31,7 @@ defineSuite([
CesiumMath,
queryToObject,
Rectangle,
+ RequestScheduler,
WebMercatorTilingScheme,
GetFeatureInfoFormat,
Imagery,
@@ -41,6 +43,10 @@ defineSuite([
Uri) {
'use strict';
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
+ });
+
afterEach(function() {
loadImage.createImage = loadImage.defaultCreateImage;
loadWithXhr.load = loadWithXhr.defaultLoad;
@@ -703,6 +709,9 @@ defineSuite([
if (tries < 3) {
error.retry = true;
}
+ setTimeout(function() {
+ RequestScheduler.update();
+ }, 1);
});
loadImage.createImage = function(url, crossOrigin, deferred) {
@@ -720,6 +729,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
diff --git a/Specs/Scene/WebMapTileServiceImageryProviderSpec.js b/Specs/Scene/WebMapTileServiceImageryProviderSpec.js
index 078a18542a6..11398eba7f4 100644
--- a/Specs/Scene/WebMapTileServiceImageryProviderSpec.js
+++ b/Specs/Scene/WebMapTileServiceImageryProviderSpec.js
@@ -6,6 +6,7 @@ defineSuite([
'Core/GeographicTilingScheme',
'Core/loadImage',
'Core/queryToObject',
+ 'Core/RequestScheduler',
'Core/WebMercatorTilingScheme',
'Scene/Imagery',
'Scene/ImageryLayer',
@@ -20,6 +21,7 @@ defineSuite([
GeographicTilingScheme,
loadImage,
queryToObject,
+ RequestScheduler,
WebMercatorTilingScheme,
Imagery,
ImageryLayer,
@@ -29,6 +31,10 @@ defineSuite([
Uri) {
'use strict';
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
+ });
+
afterEach(function() {
loadImage.createImage = loadImage.defaultCreateImage;
});
@@ -361,6 +367,9 @@ defineSuite([
if (tries < 3) {
error.retry = true;
}
+ setTimeout(function() {
+ RequestScheduler.update();
+ }, 1);
});
loadImage.createImage = function(url, crossOrigin, deferred) {
@@ -381,6 +390,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
diff --git a/Specs/Scene/createOpenStreetMapImageryProviderSpec.js b/Specs/Scene/createOpenStreetMapImageryProviderSpec.js
index 77800b4d298..041b175ca4c 100644
--- a/Specs/Scene/createOpenStreetMapImageryProviderSpec.js
+++ b/Specs/Scene/createOpenStreetMapImageryProviderSpec.js
@@ -5,6 +5,7 @@ defineSuite([
'Core/loadImage',
'Core/Math',
'Core/Rectangle',
+ 'Core/RequestScheduler',
'Core/WebMercatorTilingScheme',
'Scene/Imagery',
'Scene/ImageryLayer',
@@ -17,6 +18,7 @@ defineSuite([
loadImage,
CesiumMath,
Rectangle,
+ RequestScheduler,
WebMercatorTilingScheme,
Imagery,
ImageryLayer,
@@ -25,6 +27,10 @@ defineSuite([
pollToPromise) {
'use strict';
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
+ });
+
afterEach(function() {
loadImage.createImage = loadImage.defaultCreateImage;
});
@@ -223,6 +229,9 @@ defineSuite([
if (tries < 3) {
error.retry = true;
}
+ setTimeout(function() {
+ RequestScheduler.update();
+ }, 1);
});
loadImage.createImage = function(url, crossOrigin, deferred) {
@@ -243,6 +252,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
diff --git a/Specs/Scene/createTileMapServiceImageryProviderSpec.js b/Specs/Scene/createTileMapServiceImageryProviderSpec.js
index 0a804a78138..fb1cfc52369 100644
--- a/Specs/Scene/createTileMapServiceImageryProviderSpec.js
+++ b/Specs/Scene/createTileMapServiceImageryProviderSpec.js
@@ -11,6 +11,7 @@ defineSuite([
'Core/loadWithXhr',
'Core/Math',
'Core/Rectangle',
+ 'Core/RequestScheduler',
'Core/WebMercatorProjection',
'Core/WebMercatorTilingScheme',
'Scene/Imagery',
@@ -31,6 +32,7 @@ defineSuite([
loadWithXhr,
CesiumMath,
Rectangle,
+ RequestScheduler,
WebMercatorProjection,
WebMercatorTilingScheme,
Imagery,
@@ -41,6 +43,10 @@ defineSuite([
when) {
'use strict';
+ beforeEach(function() {
+ RequestScheduler.clearForSpecs();
+ });
+
afterEach(function() {
loadImage.createImage = loadImage.defaultCreateImage;
loadWithXhr.load = loadWithXhr.defaultLoad;
@@ -360,6 +366,9 @@ defineSuite([
if (tries < 3) {
error.retry = true;
}
+ setTimeout(function() {
+ RequestScheduler.update();
+ }, 1);
});
loadImage.createImage = function(url, crossOrigin, deferred) {
@@ -380,6 +389,7 @@ defineSuite([
var imagery = new Imagery(layer, 0, 0, 0);
imagery.addReference();
layer._requestImagery(imagery);
+ RequestScheduler.update();
return pollToPromise(function() {
return imagery.state === ImageryState.RECEIVED;
diff --git a/Tools/eslint-config-cesium/.eslintrc.json b/Tools/eslint-config-cesium/.eslintrc.json
new file mode 100644
index 00000000000..eb0e708be52
--- /dev/null
+++ b/Tools/eslint-config-cesium/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "./node.js"
+}
diff --git a/Tools/eslint-config-cesium/CHANGES.md b/Tools/eslint-config-cesium/CHANGES.md
new file mode 100644
index 00000000000..e4de4fad870
--- /dev/null
+++ b/Tools/eslint-config-cesium/CHANGES.md
@@ -0,0 +1,6 @@
+Change Log
+==========
+
+### 1.0.0 - 2017-06-12
+
+* Initial release.
diff --git a/Tools/eslint-config-cesium/README.md b/Tools/eslint-config-cesium/README.md
new file mode 100644
index 00000000000..16ded425aaa
--- /dev/null
+++ b/Tools/eslint-config-cesium/README.md
@@ -0,0 +1,29 @@
+The official [shareable ESLint config](http://eslint.org/docs/developer-guide/shareable-configs) for the [Cesium](https://cesiumjs.org/) ecosystem.
+
+## Usage
+
+---
+
+We export three ESLint configurations.
+
+### eslint-config-cesium
+
+This config contains basic Cesium syntax and style config, from which `browser` and `node` extend. Extends `eslint:recommended` with additional rules.
+
+### eslint-config-cesium/browser
+
+For use in [`AMD`](http://requirejs.org/docs/whyamd.html) modules and browser code.
+
+### eslint-config-cesium/node
+
+For use in `node` packages.
+
+---
+
+To use any of these configs,
+
+1. `npm install eslint-config-cesium --save-dev`
+
+ If using the `browser` config: `npm install eslint-plugin-html --save-dev`
+
+2. Add `"extends": "cesium"` to your `.eslintrc.*` files
diff --git a/Tools/eslint-config-cesium/browser.js b/Tools/eslint-config-cesium/browser.js
new file mode 100644
index 00000000000..fc021d6350f
--- /dev/null
+++ b/Tools/eslint-config-cesium/browser.js
@@ -0,0 +1,12 @@
+'use strict';
+
+module.exports = {
+ extends: './index.js',
+ env: {
+ amd: true,
+ browser: true
+ },
+ plugins: [
+ 'html'
+ ]
+};
diff --git a/Tools/eslint-config-cesium/index.js b/Tools/eslint-config-cesium/index.js
new file mode 100644
index 00000000000..b3a82e9a376
--- /dev/null
+++ b/Tools/eslint-config-cesium/index.js
@@ -0,0 +1,37 @@
+'use strict';
+
+module.exports = {
+ extends: 'eslint:recommended',
+ globals: {
+ DataView: false,
+ ArrayBuffer: false,
+ Float32Array: false,
+ Float64Array: false,
+ Int16Array: false,
+ Int32Array: false,
+ Int8Array: false,
+ Uint16Array: false,
+ Uint32Array: false,
+ Uint8Array: false,
+ Uint8ClampedArray: false
+ },
+ rules: {
+ curly: ['error'],
+ eqeqeq: ['error'],
+ 'guard-for-in': ['error'],
+ 'new-cap': ['error', {properties: false}],
+ 'no-caller': ['error'],
+ 'no-console': 'off',
+ 'no-empty': ['error'],
+ 'no-extend-native': ['error'],
+ 'no-extra-boolean-cast': 'off',
+ 'no-irregular-whitespace': ['error'],
+ 'no-new': ['error'],
+ 'no-undef': ['error'],
+ 'no-unused-vars': ['error', {vars: 'all', args: 'all'}],
+ 'no-useless-escape': 'off',
+ semi: ['error'],
+ strict: ['error'],
+ 'wrap-iife': ['error', 'any']
+ }
+};
diff --git a/Tools/eslint-config-cesium/node.js b/Tools/eslint-config-cesium/node.js
new file mode 100644
index 00000000000..2686d2251d8
--- /dev/null
+++ b/Tools/eslint-config-cesium/node.js
@@ -0,0 +1,8 @@
+'use strict';
+
+module.exports = {
+ extends: './index.js',
+ env: {
+ node: true
+ }
+};
diff --git a/Tools/eslint-config-cesium/package.json b/Tools/eslint-config-cesium/package.json
new file mode 100644
index 00000000000..afa7027fbe8
--- /dev/null
+++ b/Tools/eslint-config-cesium/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "eslint-config-cesium",
+ "version": "1.0.0",
+ "description": "ESLint shareable configs for Cesium",
+ "homepage": "http://cesiumjs.org",
+ "license": "Apache-2.0",
+ "author": {
+ "name": "Analytical Graphics, Inc.",
+ "url": "http://www.agi.com"
+ },
+ "contributors": [
+ {
+ "name": "Cesium community",
+ "url": "https://github.com/AnalyticalGraphicsInc/cesium/blob/master/CONTRIBUTORS.md"
+ }
+ ],
+ "keywords": [
+ "eslint",
+ "cesium"
+ ],
+ "peerDependencies": {
+ "eslint": ">= 4",
+ "eslint-plugin-html": ">= 3"
+ }
+}
diff --git a/package.json b/package.json
index ac2988c0a2a..9e52c4aa98f 100644
--- a/package.json
+++ b/package.json
@@ -39,13 +39,13 @@
"compressible": "^2.0.9",
"compression": "^1.6.2",
"electron": "^1.6.1",
- "eslint-plugin-html": "^2.0.3",
+ "eslint-plugin-html": "^3.0.0",
"event-stream": "^3.3.4",
"express": "^4.15.0",
"globby": "^6.1.0",
"glsl-strip-comments": "^1.0.0",
"gulp": "^3.9.1",
- "gulp-eslint": "^3.0.1",
+ "gulp-eslint": "^4.0.0",
"gulp-insert": "^0.5.0",
"gulp-rename": "^1.2.2",
"gulp-replace": "^0.5.4",
diff --git a/server.js b/server.js
index 98e30d9a624..0aee619e1c7 100644
--- a/server.js
+++ b/server.js
@@ -1,7 +1,6 @@
/*eslint-env node*/
+'use strict';
(function() {
- 'use strict';
-
var express = require('express');
var compression = require('compression');
var url = require('url');
@@ -43,7 +42,7 @@
'image/crn' : ['crn'],
'image/ktx' : ['ktx'],
'model/gltf+json' : ['gltf'],
- 'model/gltf.binary' : ['bgltf', 'glb'],
+ 'model/gltf-binary' : ['bgltf', 'glb'],
'application/octet-stream' : ['b3dm', 'pnts', 'i3dm', 'cmpt'],
'text/plain' : ['glsl']
});
diff --git a/web.config b/web.config
index 3091100d3d1..46951578bb0 100644
--- a/web.config
+++ b/web.config
@@ -17,9 +17,9 @@
-
+
-
+