From 078e88a71a417a667034d2f50f400e79541edc0e Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Thu, 31 Mar 2016 08:40:32 -0400
Subject: [PATCH 01/22] Added number of ready tiles to stats
---
Source/Scene/Cesium3DTileset.js | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 62475a600cf7..b4bfab8de70a 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -145,12 +145,14 @@ define([
// Loading stats
numberOfPendingRequests : 0,
numberProcessing : 0,
+ numberReady : 0, // Number of tiles with content loaded
lastSelected : -1,
lastVisited : -1,
lastNumberOfCommands : -1,
lastNumberOfPendingRequests : -1,
- lastNumberProcessing : -1
+ lastNumberProcessing : -1,
+ lastNumberReady : -1
};
/**
@@ -883,6 +885,7 @@ define([
// Remove from processing queue
tileset._processingQueue.splice(index, 1);
--tileset._statistics.numberProcessing;
+ ++tileset._statistics.numberReady;
} else {
// Not in processing queue
// For example, when a url request fails and the ready promise is rejected
@@ -924,6 +927,7 @@ define([
stats.lastSelected !== tileset._selectedTiles.length ||
stats.lastNumberOfPendingRequests !== stats.numberOfPendingRequests ||
stats.lastNumberProcessing !== stats.numberProcessing ||
+ stats.lastNumberReady !== stats.numberReady ||
styleStats.lastNumberOfTilesStyled !== styleStats.numberOfTilesStyled ||
styleStats.lastNumberOfFeaturesStyled !== styleStats.numberOfFeaturesStyled)) {
@@ -940,6 +944,7 @@ define([
', Commands: ' + stats.numberOfCommands +
', Requests: ' + stats.numberOfPendingRequests +
', Processing: ' + stats.numberProcessing +
+ ', Ready: ' + stats.numberReady +
', Tiles styled: ' + styleStats.numberOfTilesStyled +
', Features styled: ' + styleStats.numberOfFeaturesStyled;
@@ -952,6 +957,7 @@ define([
stats.lastSelected = tileset._selectedTiles.length;
stats.lastNumberOfPendingRequests = stats.numberOfPendingRequests;
stats.lastNumberProcessing = stats.numberProcessing;
+ stats.lastNumberReady = stats.numberReady;
styleStats.lastNumberOfTilesStyled = styleStats.numberOfTilesStyled;
styleStats.lastNumberOfFeaturesStyled = styleStats.numberOfFeaturesStyled;
}
From af7b359e9806652273970284d480ec0cf01b8882 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Thu, 31 Mar 2016 09:19:01 -0400
Subject: [PATCH 02/22] Add total tiles debug stat
---
Source/Scene/Cesium3DTileset.js | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index b4bfab8de70a..e7aafb80856a 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -146,13 +146,15 @@ define([
numberOfPendingRequests : 0,
numberProcessing : 0,
numberReady : 0, // Number of tiles with content loaded
+ numberTotal : 0, // Number of tiles in tileset.json (and other tileset.json files as they are loaded)
lastSelected : -1,
lastVisited : -1,
lastNumberOfCommands : -1,
lastNumberOfPendingRequests : -1,
lastNumberProcessing : -1,
- lastNumberReady : -1
+ lastNumberReady : -1,
+ lastNumberTotal : -1
};
/**
@@ -500,6 +502,8 @@ define([
throw new DeveloperError('The tileset must be 3D Tiles version 0.0. See https://github.com/AnalyticalGraphicsInc/3d-tiles#spec-status');
}
+ var stats = that._statistics;
+
// Append the version to the baseUrl
var versionQuery = '?v=' + defaultValue(tilesetJson.asset.tilesetVersion, '0.0');
that._baseUrl = joinUrls(that._baseUrl, versionQuery);
@@ -516,6 +520,7 @@ define([
parentTile.children.push(rootTile);
++parentTile.numberOfChildrenWithoutContent;
}
+ ++stats.numberTotal;
var refiningTiles = [];
@@ -536,6 +541,7 @@ define([
var childHeader = children[k];
var childTile = new Cesium3DTile(that, baseUrl, childHeader, tile3D);
tile3D.children.push(childTile);
+ ++stats.numberTotal;
stack.push({
header : childHeader,
cesium3DTile : childTile
@@ -928,6 +934,7 @@ define([
stats.lastNumberOfPendingRequests !== stats.numberOfPendingRequests ||
stats.lastNumberProcessing !== stats.numberProcessing ||
stats.lastNumberReady !== stats.numberReady ||
+ stats.lastNumberTotal !== stats.numberTotal ||
styleStats.lastNumberOfTilesStyled !== styleStats.numberOfTilesStyled ||
styleStats.lastNumberOfFeaturesStyled !== styleStats.numberOfFeaturesStyled)) {
@@ -945,6 +952,9 @@ define([
', Requests: ' + stats.numberOfPendingRequests +
', Processing: ' + stats.numberProcessing +
', Ready: ' + stats.numberReady +
+ // Total number of tiles includes tiles without content, so "Ready" may never reach
+ // "Total." Total also will increase when a tile with a tileset.json content is loaded.
+ ', Total: ' + stats.numberTotal +
', Tiles styled: ' + styleStats.numberOfTilesStyled +
', Features styled: ' + styleStats.numberOfFeaturesStyled;
@@ -958,6 +968,7 @@ define([
stats.lastNumberOfPendingRequests = stats.numberOfPendingRequests;
stats.lastNumberProcessing = stats.numberProcessing;
stats.lastNumberReady = stats.numberReady;
+ stats.lastNumberTotal = stats.numberTotal;
styleStats.lastNumberOfTilesStyled = styleStats.numberOfTilesStyled;
styleStats.lastNumberOfFeaturesStyled = styleStats.numberOfFeaturesStyled;
}
From 6dfa3bb5bc4ac47678c3447c9c480877ac1db114 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Thu, 31 Mar 2016 10:07:52 -0400
Subject: [PATCH 03/22] Separate color and pick stats
---
Source/Scene/Cesium3DTileStyleEngine.js | 22 ++----
Source/Scene/Cesium3DTileset.js | 99 ++++++++++++++++---------
Specs/Scene/Cesium3DTilesetSpec.js | 16 ++++
3 files changed, 86 insertions(+), 51 deletions(-)
diff --git a/Source/Scene/Cesium3DTileStyleEngine.js b/Source/Scene/Cesium3DTileStyleEngine.js
index e205b68e35e7..7d00c0723cf3 100644
--- a/Source/Scene/Cesium3DTileStyleEngine.js
+++ b/Source/Scene/Cesium3DTileStyleEngine.js
@@ -16,14 +16,6 @@ define([
this._style = undefined; // The style provided by the user
this._styleDirty = false; // true when the style is reassigned
this._lastStyleTime = 0; // The "time" when the last style was assigned
-
- this.statistics = {
- numberOfTilesStyled : 0,
- numberOfFeaturesStyled : 0,
-
- lastNumberOfTilesStyled : -1,
- lastNumberOfFeaturesStyled : -1
- };
}
defineProperties(Cesium3DTileStyleEngine.prototype, {
@@ -64,7 +56,7 @@ define([
}
var lastStyleTime = this._lastStyleTime;
- var stats = this.statistics;
+ var stats = tileset._statistics;
// If a new style was assigned, loop through all the visible tiles; otherwise, loop through
// only the tiles that are newly visible, i.e., they are visible this frame, but were not
@@ -82,7 +74,7 @@ define([
// 2) this tile is now visible, but it wasn't visible when the style was first assigned
if (tile.lastStyleTime !== lastStyleTime) {
tile.lastStyleTime = lastStyleTime;
- styleCompositeContent(this, tile.content);
+ styleCompositeContent(this, tile.content, stats);
++stats.numberOfTilesStyled;
}
@@ -90,27 +82,27 @@ define([
}
};
- function styleCompositeContent(styleEngine, content) {
+ function styleCompositeContent(styleEngine, content, stats) {
var innerContents = content.innerContents;
if (defined(innerContents)) {
var length = innerContents.length;
for (var i = 0; i < length; ++i) {
// Recurse for composites of composites
- styleCompositeContent(styleEngine, innerContents[i]);
+ styleCompositeContent(styleEngine, innerContents[i], stats);
}
} else {
// Not a composite tile
- styleContent(styleEngine, content);
+ styleContent(styleEngine, content, stats);
}
}
var scratchColor = new Color();
- function styleContent(styleEngine, content) {
+ function styleContent(styleEngine, content, stats) {
var length = content.featuresLength;
var style = styleEngine._style;
- styleEngine.statistics.numberOfFeaturesStyled += length;
+ stats.numberOfFeaturesStyled += length;
if (!defined(style)) {
clearStyle(content);
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index e7aafb80856a..393ded806a5d 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -61,6 +61,7 @@ define([
* @param {Boolean} [options.show=true] Determines if the tileset will be shown.
* @param {Number} [options.maximumScreenSpaceError=16] The maximum screen-space error used to drive level-of-detail refinement.
* @param {Boolean} [options.debugShowStatistics=false] For debugging only. Determines if rendering statistics are output to the console.
+ * @param {Boolean} [options.debugShowPickStatistics=false] For debugging only. Determines if rendering statistics for picking are output to the console.
* @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering.
* @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. When true, renders the bounding volume for each tile.
@@ -138,6 +139,18 @@ define([
* @default false
*/
this.debugShowStatistics = defaultValue(options.debugShowStatistics, false);
+
+ /**
+ * This property is for debugging only; it is not optimized for production use.
+ *
+ * Determines if rendering statistics for picking are output to the console.
+ *
+ *
+ * @type {Boolean}
+ * @default false
+ */
+ this.debugShowPickStatistics = defaultValue(options.debugShowPickStatistics, false);
+
this._statistics = {
// Rendering stats
visited : 0,
@@ -147,14 +160,12 @@ define([
numberProcessing : 0,
numberReady : 0, // Number of tiles with content loaded
numberTotal : 0, // Number of tiles in tileset.json (and other tileset.json files as they are loaded)
+ // Styling stats
+ numberOfTilesStyled : 0,
+ numberOfFeaturesStyled : 0,
- lastSelected : -1,
- lastVisited : -1,
- lastNumberOfCommands : -1,
- lastNumberOfPendingRequests : -1,
- lastNumberProcessing : -1,
- lastNumberReady : -1,
- lastNumberTotal : -1
+ lastColor : new Cesium3DTilesetStatistics(),
+ lastPick : new Cesium3DTilesetStatistics()
};
/**
@@ -282,6 +293,18 @@ define([
});
}
+ function Cesium3DTilesetStatistics() {
+ this.selected = -1;
+ this.visited = -1;
+ this.numberOfCommands = -1;
+ this.numberOfPendingRequests = -1;
+ this.numberProcessing = -1;
+ this.numberReady = -1;
+ this.numberTotal = -1;
+ this.numberOfTilesStyled = -1;
+ this.numberOfFeaturesStyled = -1;
+ };
+
defineProperties(Cesium3DTileset.prototype, {
/**
* Gets the tileset's asset object property, which contains metadata about the tileset.
@@ -917,31 +940,41 @@ define([
var stats = tileset._statistics;
stats.visited = 0;
stats.numberOfCommands = 0;
-
- var styleStats = tileset._styleEngine.statistics;
- styleStats.numberOfTilesStyled = 0;
- styleStats.numberOfFeaturesStyled = 0;
+ stats.numberOfTilesStyled = 0;
+ stats.numberOfFeaturesStyled = 0;
}
function showStats(tileset, isPick) {
var stats = tileset._statistics;
- var styleStats = tileset._styleEngine.statistics;
-
- if (tileset.debugShowStatistics && (
- stats.lastVisited !== stats.visited ||
- stats.lastNumberOfCommands !== stats.numberOfCommands ||
- stats.lastSelected !== tileset._selectedTiles.length ||
- stats.lastNumberOfPendingRequests !== stats.numberOfPendingRequests ||
- stats.lastNumberProcessing !== stats.numberProcessing ||
- stats.lastNumberReady !== stats.numberReady ||
- stats.lastNumberTotal !== stats.numberTotal ||
- styleStats.lastNumberOfTilesStyled !== styleStats.numberOfTilesStyled ||
- styleStats.lastNumberOfFeaturesStyled !== styleStats.numberOfFeaturesStyled)) {
+ var last = isPick ? stats.lastPick : stats.lastColor;
+
+ if (((tileset.debugShowStatistics && !isPick) ||
+ (tileset.debugShowPickStatistics && isPick)) &&
+ (last.visited !== stats.visited ||
+ last.numberOfCommands !== stats.numberOfCommands ||
+ last.selected !== tileset._selectedTiles.length ||
+ last.numberOfPendingRequests !== stats.numberOfPendingRequests ||
+ last.numberProcessing !== stats.numberProcessing ||
+ last.numberReady !== stats.numberReady ||
+ last.numberTotal !== stats.numberTotal ||
+ last.numberOfTilesStyled !== stats.numberOfTilesStyled ||
+ last.numberOfFeaturesStyled !== stats.numberOfFeaturesStyled)) {
+
+ last.visited = stats.visited;
+ last.numberOfCommands = stats.numberOfCommands;
+ last.selected = tileset._selectedTiles.length;
+ last.numberOfPendingRequests = stats.numberOfPendingRequests;
+ last.numberProcessing = stats.numberProcessing;
+ last.numberReady = stats.numberReady;
+ last.numberTotal = stats.numberTotal;
+ last.numberOfTilesStyled = stats.numberOfTilesStyled;
+ last.numberOfFeaturesStyled = stats.numberOfFeaturesStyled;
// Since the pick pass uses a smaller frustum around the pixel of interest,
// the stats will be different than the normal render pass.
var s = isPick ? '[Pick ]: ' : '[Color]: ';
s +=
+ // --- Rendering stats
'Visited: ' + stats.visited +
// Number of commands returned is likely to be higher than the number of tiles selected
// because of tiles that create multiple commands.
@@ -949,28 +982,22 @@ define([
// Number of commands executed is likely to be higher because of commands overlapping
// multiple frustums.
', Commands: ' + stats.numberOfCommands +
- ', Requests: ' + stats.numberOfPendingRequests +
+
+ // --- Cache/loading stats
+ ' | Requests: ' + stats.numberOfPendingRequests +
', Processing: ' + stats.numberProcessing +
', Ready: ' + stats.numberReady +
// Total number of tiles includes tiles without content, so "Ready" may never reach
// "Total." Total also will increase when a tile with a tileset.json content is loaded.
', Total: ' + stats.numberTotal +
- ', Tiles styled: ' + styleStats.numberOfTilesStyled +
- ', Features styled: ' + styleStats.numberOfFeaturesStyled;
+
+ // --- Styling stats
+ ' | Tiles styled: ' + stats.numberOfTilesStyled +
+ ', Features styled: ' + stats.numberOfFeaturesStyled;
/*global console*/
console.log(s);
}
-
- stats.lastVisited = stats.visited;
- stats.lastNumberOfCommands = stats.numberOfCommands;
- stats.lastSelected = tileset._selectedTiles.length;
- stats.lastNumberOfPendingRequests = stats.numberOfPendingRequests;
- stats.lastNumberProcessing = stats.numberProcessing;
- stats.lastNumberReady = stats.numberReady;
- stats.lastNumberTotal = stats.numberTotal;
- styleStats.lastNumberOfTilesStyled = styleStats.numberOfTilesStyled;
- styleStats.lastNumberOfFeaturesStyled = styleStats.numberOfFeaturesStyled;
}
function updateTiles(tileset, frameState) {
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index f0aa5b5da958..58bf9a694ea4 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -677,12 +677,28 @@ defineSuite([
spyOn(console, 'log');
return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
+ scene.renderForSpecs();
+ expect(console.log).not.toHaveBeenCalled();
+
tileset.debugShowStatistics = true;
scene.renderForSpecs();
expect(console.log).toHaveBeenCalled();
});
});
+ it('debugShowPickStatistics', function() {
+ spyOn(console, 'log');
+
+ return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
+ scene.pickForSpecs();
+ expect(console.log).not.toHaveBeenCalled();
+
+ tileset.debugShowPickStatistics = true;
+ scene.pickForSpecs();
+ expect(console.log).toHaveBeenCalled();
+ });
+ });
+
it('debugColorizeTiles', function() {
// More precise test is in Cesium3DTileBatchTableResourcesSpec
return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
From cd4cca67f62e65a35330d2c90981e197879a0889 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Thu, 31 Mar 2016 11:44:59 -0400
Subject: [PATCH 04/22] Fix test
---
Source/Scene/Cesium3DTileset.js | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 393ded806a5d..cffd446c9537 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -1025,10 +1025,11 @@ define([
///////////////////////////////////////////////////////////////////////////
function raiseLoadProgressEvent(tileset, frameState) {
- var numberOfPendingRequests = tileset._statistics.numberOfPendingRequests;
- var numberProcessing = tileset._statistics.numberProcessing;
- var lastNumberOfPendingRequest = tileset._statistics.lastNumberOfPendingRequests;
- var lastNumberProcessing = tileset._statistics.lastNumberProcessing;
+ var stats = tileset._statistics;
+ var numberOfPendingRequests = stats.numberOfPendingRequests;
+ var numberProcessing = stats.numberProcessing;
+ var lastNumberOfPendingRequest = stats.lastColor.numberOfPendingRequests;
+ var lastNumberProcessing = stats.lastColor.numberProcessing;
if ((numberOfPendingRequests !== lastNumberOfPendingRequest) || (numberProcessing !== lastNumberProcessing)) {
frameState.afterRender.push(function() {
From 6c10764178405d55a5f08dcbe5f12e1fa7428e0a Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Mon, 4 Apr 2016 15:33:54 -0400
Subject: [PATCH 05/22] Fix Cesium3DTileBatchTableResources tests
---
Source/Scene/Cesium3DTileBatchTableResources.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Source/Scene/Cesium3DTileBatchTableResources.js b/Source/Scene/Cesium3DTileBatchTableResources.js
index 7dab3520360b..fb84458e8d91 100644
--- a/Source/Scene/Cesium3DTileBatchTableResources.js
+++ b/Source/Scene/Cesium3DTileBatchTableResources.js
@@ -506,14 +506,14 @@ define([
' { \n' +
' if (!isStyleTranslucent && !tile_translucentCommand) \n' + // Do not render opaque features in the translucent pass
' { \n' +
- ' gl_Position *= 0.0; \n' +
+ ' discard; \n' +
' } \n' +
' } \n' +
' else \n' +
' { \n' +
' if (isStyleTranslucent) \n' + // Do not render translucent features in the opaque pass
' { \n' +
- ' gl_Position *= 0.0; \n' +
+ ' discard; \n' +
' } \n' +
' } \n' +
' tile_main(); \n' +
From 831ae5e8245a9f14105070d597dc15265e680b49 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Mon, 4 Apr 2016 16:17:20 -0400
Subject: [PATCH 06/22] Add missing interface function
---
Source/Scene/Empty3DTileContent.js | 6 ++++++
Source/Scene/Tileset3DTileContent.js | 6 ++++++
2 files changed, 12 insertions(+)
diff --git a/Source/Scene/Empty3DTileContent.js b/Source/Scene/Empty3DTileContent.js
index 11f067e2a950..b5f936ff5eb2 100644
--- a/Source/Scene/Empty3DTileContent.js
+++ b/Source/Scene/Empty3DTileContent.js
@@ -82,6 +82,12 @@ define([
Empty3DTileContent.prototype.request = function() {
};
+ /**
+ * Part of the {@link Cesium3DTileContent} interface.
+ */
+ Empty3DTileContent.prototype.initialize = function(arrayBuffer, byteOffset) {
+ };
+
/**
* Part of the {@link Cesium3DTileContent} interface.
*/
diff --git a/Source/Scene/Tileset3DTileContent.js b/Source/Scene/Tileset3DTileContent.js
index 4099ed1d17b1..933735efd1c7 100644
--- a/Source/Scene/Tileset3DTileContent.js
+++ b/Source/Scene/Tileset3DTileContent.js
@@ -92,6 +92,12 @@ define([
});
};
+ /**
+ * Part of the {@link Cesium3DTileContent} interface.
+ */
+ Tileset3DTileContent.prototype.initialize = function(arrayBuffer, byteOffset) {
+ };
+
/**
* Part of the {@link Cesium3DTileContent} interface.
*/
From cb6719c258244e07d13cd0a5c5df779546b3d67d Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Tue, 5 Apr 2016 09:47:36 -0400
Subject: [PATCH 07/22] Add doubly linked list
---
Source/Core/DoublyLinkedList.js | 111 +++++++++
Specs/Core/DoublyLinkedListSpec.js | 381 +++++++++++++++++++++++++++++
2 files changed, 492 insertions(+)
create mode 100644 Source/Core/DoublyLinkedList.js
create mode 100644 Specs/Core/DoublyLinkedListSpec.js
diff --git a/Source/Core/DoublyLinkedList.js b/Source/Core/DoublyLinkedList.js
new file mode 100644
index 000000000000..02dd8d86ffbd
--- /dev/null
+++ b/Source/Core/DoublyLinkedList.js
@@ -0,0 +1,111 @@
+/*global define*/
+define([
+ '../Core/defined',
+ '../Core/defineProperties',
+ '../Core/DeveloperError'
+ ], function(
+ defined,
+ defineProperties,
+ DeveloperError) {
+ 'use strict';
+
+ /**
+ * @private
+ */
+ function DoublyLinkedList() {
+ this.head = undefined;
+ this.tail = undefined;
+ this._length = 0;
+ }
+
+ defineProperties(DoublyLinkedList.prototype, {
+ length : {
+ get : function() {
+ return this._length;
+ }
+ }
+ });
+
+ function DoublyLinkedListNode(item, previous, next) {
+ this.item = item;
+ this.previous = previous;
+ this.next = next;
+ }
+
+ DoublyLinkedList.prototype.add = function(item) {
+ var node = new DoublyLinkedListNode(item, this.tail, undefined);
+
+ if (defined(this.tail)) {
+ this.tail.next = node;
+ this.tail = node;
+ } else {
+ // Insert into empty linked list
+ this.head = node;
+ this.tail = node;
+ }
+
+ ++this._length;
+
+ return node;
+ };
+
+ function remove(list, node) {
+ if (defined(node.previous) && defined(node.next)) {
+ node.previous.next = node.next;
+ node.next.previous = node.previous;
+ } else if (defined(node.previous)) {
+ // Remove last node
+ node.previous.next = undefined;
+ list.tail = node.previous;
+ } else if (defined(node.next)) {
+ // Remove first node
+ node.next.previous = undefined;
+ list.head = node.next;
+ } else {
+ // Remove last node in the linked list
+ list.head = undefined;
+ list.tail = undefined;
+ }
+
+ node.next = undefined;
+ node.previous = undefined;
+ }
+
+ DoublyLinkedList.prototype.remove = function(node) {
+ if (!defined(node)) {
+ return;
+ }
+
+ remove(this, node);
+
+ --this._length;
+ };
+
+ DoublyLinkedList.prototype.splice = function(node, nextNode) {
+ if (!defined(node) || !defined(nextNode)) {
+ throw new DeveloperError('node and nextNode are required.');
+ }
+
+ if (node === nextNode) {
+ return;
+ }
+
+ // Remove nextNode, then insert after node
+ remove(this, nextNode);
+
+ var oldNodeNext = node.next;
+ node.next = nextNode;
+
+ // nextNode is the new tail
+ if (this.tail === node) {
+ this.tail = nextNode;
+ } else {
+ oldNodeNext.previous = nextNode;
+ }
+
+ nextNode.next = oldNodeNext;
+ nextNode.previous = node;
+ }
+
+ return DoublyLinkedList;
+});
diff --git a/Specs/Core/DoublyLinkedListSpec.js b/Specs/Core/DoublyLinkedListSpec.js
new file mode 100644
index 000000000000..027ea189b5f8
--- /dev/null
+++ b/Specs/Core/DoublyLinkedListSpec.js
@@ -0,0 +1,381 @@
+/*global defineSuite*/
+fdefineSuite([
+ 'Core/DoublyLinkedList'
+ ], function(
+ DoublyLinkedList) {
+ 'use strict';
+
+ it('constructs', function() {
+ var list = new DoublyLinkedList();
+ expect(list.head).not.toBeDefined();
+ expect(list.tail).not.toBeDefined();
+ expect(list.length).toEqual(0);
+ });
+
+ it('adds items', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+
+ // node
+ // ^ ^
+ // | |
+ // head tail
+ expect(list.head).toEqual(node);
+ expect(list.tail).toEqual(node);
+ expect(list.length).toEqual(1);
+
+ expect(node).toBeDefined();
+ expect(node.item).toEqual(1);
+ expect(node.previous).not.toBeDefined();
+ expect(node.next).not.toBeDefined();
+
+ var node2 = list.add(2);
+
+ // node <-> node2
+ // ^ ^
+ // | |
+ // head tail
+ expect(list.head).toEqual(node);
+ expect(list.tail).toEqual(node2);
+ expect(list.length).toEqual(2);
+
+ expect(node2).toBeDefined();
+ expect(node2.item).toEqual(2);
+ expect(node2.previous).toEqual(node);
+ expect(node2.next).not.toBeDefined();
+
+ expect(node.next).toEqual(node2);
+
+ var node3 = list.add(3);
+
+ // node <-> node2 <-> node3
+ // ^ ^
+ // | |
+ // head tail
+ expect(list.head).toEqual(node);
+ expect(list.tail).toEqual(node3);
+ expect(list.length).toEqual(3);
+
+ expect(node3).toBeDefined();
+ expect(node3.item).toEqual(3);
+ expect(node3.previous).toEqual(node2);
+ expect(node3.next).not.toBeDefined();
+
+ expect(node2.next).toEqual(node3);
+ });
+
+ it('removes from a list with one item', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+
+ list.remove(node);
+
+ expect(list.head).not.toBeDefined();
+ expect(list.tail).not.toBeDefined();
+ expect(list.length).toEqual(0);
+ });
+
+ it('removes head of list', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+
+ list.remove(node);
+
+ expect(list.head).toEqual(node2);
+ expect(list.tail).toEqual(node2);
+ expect(list.length).toEqual(1);
+ });
+
+ it('removes tail of list', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+
+ list.remove(node2);
+
+ expect(list.head).toEqual(node);
+ expect(list.tail).toEqual(node);
+ expect(list.length).toEqual(1);
+ });
+
+ it('removes middle of list', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+ var node3 = list.add(2);
+
+ list.remove(node2);
+
+ expect(list.head).toEqual(node);
+ expect(list.tail).toEqual(node3);
+ expect(list.length).toEqual(2);
+ });
+
+ it('removes nothing', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+
+ list.remove(undefined);
+
+ expect(list.head).toEqual(node);
+ expect(list.tail).toEqual(node);
+ expect(list.length).toEqual(1);
+ });
+
+ function expectOrder(list, nodes) {
+ // Assumes at least one node is in the list
+ var length = nodes.length;
+
+ expect(list.length).toEqual(length);
+
+ // Verify head and tail pointers
+ expect(list.head).toEqual(nodes[0]);
+ expect(list.tail).toEqual(nodes[length - 1]);
+
+ // Verify that linked list has nodes in the expected order
+ var node = list.head;
+ for (var i = 0; i < length; ++i) {
+ var nextNode = (i === length - 1) ? undefined : nodes[i + 1];
+ var previousNode = (i === 0) ? undefined : nodes[i - 1];
+
+ expect(node).toEqual(nodes[i]);
+ expect(node.next).toEqual(nextNode);
+ expect(node.previous).toEqual(previousNode);
+
+ node = node.next;
+ }
+ }
+
+ it('splices nextNode before node', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+ var node3 = list.add(3);
+ var node4 = list.add(4);
+ var node5 = list.add(5);
+
+ // Before:
+ //
+ // node <-> node2 <-> node3 <-> node4 <-> node5
+ // ^ ^ ^ ^
+ // | | | |
+ // head nextNode node tail
+
+ // After:
+ //
+ // node <-> node3 <-> node4 <-> node2 <-> node5
+ // ^ ^
+ // | |
+ // head tail
+
+ // Move node2 after node4
+ list.splice(node4, node2);
+ expectOrder(list, [node, node3, node4, node2, node5]);
+ });
+
+ it('splices nextNode after node', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+ var node3 = list.add(3);
+ var node4 = list.add(4);
+ var node5 = list.add(5);
+
+ // Before:
+ //
+ // node <-> node2 <-> node3 <-> node4 <-> node5
+ // ^ ^ ^ ^
+ // | | | |
+ // head node nextNode tail
+
+ // After:
+ //
+ // node <-> node2 <-> node4 <-> node3 <-> node5
+ // ^ ^
+ // | |
+ // head tail
+
+ // Move node4 after node2
+ list.splice(node2, node4);
+ expectOrder(list, [node, node2, node4, node3, node5]);
+ });
+
+ it('splices nextNode immediately before node', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+ var node3 = list.add(3);
+ var node4 = list.add(4);
+
+ // Before:
+ //
+ // node <-> node2 <-> node3 <-> node4
+ // ^ ^ ^ ^
+ // | | | |
+ // head nextNode node tail
+
+ // After:
+ //
+ // node <-> node3 <-> node2 <-> node4
+ // ^ ^
+ // | |
+ // head tail
+
+ // Move node2 after node4
+ list.splice(node3, node2);
+ expectOrder(list, [node, node3, node2, node4]);
+ });
+
+ it('splices nextNode immediately after node', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+ var node3 = list.add(3);
+ var node4 = list.add(4);
+
+ // Before:
+ //
+ // node <-> node2 <-> node3 <-> node4
+ // ^ ^ ^ ^
+ // | | | |
+ // head node nextNode tail
+
+ // After: does not change
+
+ list.splice(node2, node3);
+ expectOrder(list, [node, node2, node3, node4]);
+ });
+
+ it('splices node === nextNode', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+ var node3 = list.add(3);
+
+ // Before:
+ //
+ // node <-> node2 <-> node3
+ // ^ ^ ^
+ // | | |
+ // head node/nextNode tail
+
+ // After: does not change
+
+ list.splice(node2, node2);
+ expectOrder(list, [node, node2, node3]);
+ });
+
+ it('splices when nextNode was tail', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+ var node3 = list.add(3);
+ var node4 = list.add(4);
+
+ // Before:
+ //
+ // node <-> node2 <-> node3 <-> node4
+ // ^ ^ ^
+ // | | |
+ // head node tail/nextNode
+
+ // After:
+ //
+ // node <-> node2 <-> node4 <-> node3
+ // ^ ^
+ // | |
+ // head tail
+
+ list.splice(node2, node4);
+ expectOrder(list, [node, node2, node4, node3]);
+ });
+
+ it('splices when node was tail', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+ var node3 = list.add(3);
+ var node4 = list.add(4);
+
+ // Before:
+ //
+ // node <-> node2 <-> node3 <-> node4
+ // ^ ^ ^
+ // | | |
+ // head nextNode tail/node
+
+ // After:
+ //
+ // node <-> node3 <-> node4 <-> node2
+ // ^ ^
+ // | |
+ // head tail/node
+
+ list.splice(node4, node2);
+ expectOrder(list, [node, node3, node4, node2]);
+ });
+
+ it('splices when nextNode was head', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+ var node3 = list.add(3);
+ var node4 = list.add(4);
+
+ // Before:
+ //
+ // node <-> node2 <-> node3 <-> node4
+ // ^ ^ ^
+ // | | |
+ // head/nextNode node tail
+
+ // After:
+ //
+ // node2 <-> node3 <-> node <-> node4
+ // ^ ^
+ // | |
+ // head tail
+
+ list.splice(node3, node);
+ expectOrder(list, [node2, node3, node, node4]);
+ });
+
+ it('splices when node was head', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+ var node2 = list.add(2);
+ var node3 = list.add(3);
+ var node4 = list.add(4);
+
+ // Before:
+ //
+ // node <-> node2 <-> node3 <-> node4
+ // ^ ^ ^
+ // | | |
+ // head/node nextNode tail
+
+ // After:
+ //
+ // node <-> node3 <-> node2 <-> node4
+ // ^ ^
+ // | |
+ // head tail
+
+ list.splice(node, node3);
+ expectOrder(list, [node, node3, node2, node4]);
+ });
+
+ it('splice throws without nodes', function() {
+ var list = new DoublyLinkedList();
+ var node = list.add(1);
+
+ expect(function() {
+ list.splice(undefined, node);
+ }).toThrowDeveloperError();
+
+ expect(function() {
+ list.splice(node, undefined);
+ }).toThrowDeveloperError();
+ });
+});
From 8c47fe0e6c6fa3b77f56020eb11f5c4fb1ded253 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Tue, 5 Apr 2016 09:48:09 -0400
Subject: [PATCH 08/22] Remove fdefineSuite
---
Specs/Core/DoublyLinkedListSpec.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Specs/Core/DoublyLinkedListSpec.js b/Specs/Core/DoublyLinkedListSpec.js
index 027ea189b5f8..e66cd339f3da 100644
--- a/Specs/Core/DoublyLinkedListSpec.js
+++ b/Specs/Core/DoublyLinkedListSpec.js
@@ -1,5 +1,5 @@
/*global defineSuite*/
-fdefineSuite([
+defineSuite([
'Core/DoublyLinkedList'
], function(
DoublyLinkedList) {
From 1fd0a2afc9c1406ef8ac2249b70559a3080fdd2c Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Tue, 5 Apr 2016 12:17:37 -0400
Subject: [PATCH 09/22] Start of cache replacement for 3D Tiles
---
Source/Scene/Cesium3DTile.js | 123 ++++++++++++++++-----------
Source/Scene/Cesium3DTileset.js | 130 +++++++++++++++++++++++++----
Specs/Scene/Cesium3DTilesetSpec.js | 23 ++---
3 files changed, 193 insertions(+), 83 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 550dddf3312d..40ba4b1c7134 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -166,20 +166,10 @@ define([
*/
this.numberOfChildrenWithoutContent = defined(header.children) ? header.children.length : 0;
- /**
- * Gets the promise that will be resolved when the tile's content is ready to render.
- *
- * @type {Promise.}
- * @readonly
- *
- * @private
- */
- this.contentReadyPromise = when.defer();
-
- var content;
var hasContent;
var hasTilesetContent;
var requestServer;
+ var createContent;
if (defined(contentHeader)) {
var contentUrl = contentHeader.url;
@@ -202,14 +192,23 @@ define([
}
//>>includeEnd('debug');
- content = contentFactory(tileset, this, url);
+ var that = this;
+ createContent = function() {
+ return contentFactory(tileset, that, url);
+ };
} else {
- content = new Empty3DTileContent();
hasContent = false;
hasTilesetContent = false;
+
+ createContent = function() {
+ return new Empty3DTileContent();
+ };
}
- this._content = content;
+ this._createContent = createContent;
+ this._content = createContent();
+ addContentReadyPromise(this);
+
this._requestServer = requestServer;
/**
@@ -235,21 +234,12 @@ define([
*/
this.hasTilesetContent = hasTilesetContent;
- var that = this;
-
- // Content enters the READY state
- when(content.readyPromise).then(function(content) {
- if (defined(that.parent)) {
- --that.parent.numberOfChildrenWithoutContent;
- }
-
- that.contentReadyPromise.resolve(that);
- }).otherwise(function(error) {
- // In this case, that.parent.numberOfChildrenWithoutContent will never reach zero
- // and therefore that.parent will never refine. If this becomes an issue, failed
- // requests can be reissued.
- that.contentReadyPromise.reject(error);
- });
+ /**
+ * DOC_TBA
+ *
+ * @private
+ */
+ this.replacementNode = undefined;
// Members that are updated every frame for tree traversal and rendering optimizations:
@@ -281,13 +271,24 @@ define([
this.selected = false;
/**
- * The last frame number the tile was visible in.
+ * The last frame number the tile was selected in.
*
* @type {Number}
*
* @private
*/
- this.lastFrameNumber = 0;
+ this.lastSelectedFrameNumber = 0;
+
+ /**
+ * The last frame number the tile was visited during tile selection. A tile may be touched,
+ * but not selected because, for example, it is a parent using replacement refinement and
+ * its children are selected. A selected tile will always be touched.
+ *
+ * @type {Number}
+ *
+ * @private
+ */
+ this.lastTouchedFrameNumber = 0;
/**
* The time when a style was last applied to this tile.
@@ -336,24 +337,6 @@ define([
}
},
- /**
- * Gets the promise that will be resolved when the tile's content is ready to process.
- * This happens after the content is downloaded but before the content is ready
- * to render.
- *
- * @memberof Cesium3DTile.prototype
- *
- * @type {Promise.}
- * @readonly
- *
- * @private
- */
- contentReadyToProcessPromise : {
- get : function() {
- return this._content.contentReadyToProcessPromise;
- }
- },
-
/**
* @readonly
* @private
@@ -395,6 +378,19 @@ define([
}
});
+ function addContentReadyPromise(tile) {
+ // Content enters the READY state
+ when(tile._content.readyPromise).then(function(content) {
+ if (defined(tile.parent)) {
+ --tile.parent.numberOfChildrenWithoutContent;
+ }
+ }).otherwise(function(error) {
+ // In this case, that.parent.numberOfChildrenWithoutContent will never reach zero
+ // and therefore that.parent will never refine. If this becomes an issue, failed
+ // requests can be reissued.
+ });
+ }
+
/**
* Requests the tile's content.
*
@@ -423,6 +419,35 @@ define([
return this._requestServer.hasAvailableRequests();
};
+ /**
+ * Unloads the tile's content and returns the tile's state to the state of when
+ * it was first created, before its content were loaded.
+ *
+ * @private
+ */
+ Cesium3DTile.prototype.unloadContent = function() {
+ if (defined(this.parent)) {
+ ++this.parent.numberOfChildrenWithoutContent;
+ }
+
+ this._content = this._content && this._content.destroy();
+ this._content = this._createContent();
+ addContentReadyPromise(this);
+
+ this.replacementNode = undefined;
+
+ // Restore properties set per frame to their defaults
+ this.distanceToCamera = 0;
+ this.parentPlaneMask = 0;
+ this.selected = false;
+ this.lastSelectedFrameNumber = 0;
+ this.lastTouchedFrameNumber = 0;
+ this.lastStyleTime = 0;
+
+ this._debugBoundingVolume = this._debugBoundingVolume && this._debugBoundingVolume.destroy();
+ this._debugContentBoundingVolume = this._debugContentBoundingVolume && this._debugContentBoundingVolume.destroy();
+ };
+
/**
* Determines whether the tile's bounding volume intersects the culling volume.
*
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index cffd446c9537..ca48c971a807 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -5,6 +5,7 @@ define([
'../Core/defineProperties',
'../Core/destroyObject',
'../Core/DeveloperError',
+ '../Core/DoublyLinkedList',
'../Core/Event',
'../Core/getBaseUri',
'../Core/getExtensionFromUri',
@@ -29,6 +30,7 @@ define([
defineProperties,
destroyObject,
DeveloperError,
+ DoublyLinkedList,
Event,
getBaseUri,
getExtensionFromUri,
@@ -49,6 +51,15 @@ define([
SceneMode) {
'use strict';
+// TODO: unload events
+// TODO: unit tests
+// TODO: test with replacement refinement
+// TODO: Refactor TileReplacementQueue to use DoublyLinkedList?
+// TODO: track stats for cache trashing
+// TODO: good default for maximumNumberOfLoadedTiles
+// TODO: More precise size than number of tiles? Count composite tiles as more? Include geometry/texture cost with each tile?
+// TODO: unload sub-trees from tiles with tileset.json content
+
/**
* A {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles tileset},
* used for streaming massive heterogeneous 3D geospatial datasets.
@@ -110,6 +121,13 @@ define([
this._selectedTiles = [];
this._selectedTilesToStyle = [];
+ var replacementList = new DoublyLinkedList();
+
+ // [head, sentinel) -> tiles that weren't selected this frame and may be replaced
+ // (sentinel, tail] -> tiles that were selected this frame
+ this._replacementList = replacementList; // Tiles with content loaded. For cache management
+ this._replacementSentinel = replacementList.add();
+
/**
* Determines if the tileset will be shown.
*
@@ -127,6 +145,22 @@ define([
*/
this.maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 16);
+ /**
+ * The maximum number of tiles to load. Tiles not in view are unloaded to enforce this.
+ *
+ * If more tiles than maximumNumberOfLoadedTiles
are needed
+ * to meet the desired screen-space error, determined by {@link Cesium3DTileset#maximumScreenSpaceError},
+ * for the current view than the number of tiles loaded will exceed
+ * maximumNumberOfLoadedTiles
. For example, if the maximum is 128 tiles, but
+ * 150 tiles are needed to meet the screen-space error, then 150 tiles may be loaded. When
+ * these tiles go out of view, they will be unloaded.
+ *
+ *
+ * @type {Number}
+ * @default 256
+ */
+ this.maximumNumberOfLoadedTiles = defaultValue(options.maximumNumberOfLoadedTiles, 256);
+
this._styleEngine = new Cesium3DTileStyleEngine();
/**
@@ -139,6 +173,7 @@ define([
* @default false
*/
this.debugShowStatistics = defaultValue(options.debugShowStatistics, false);
+ this._debugShowStatistics = false;
/**
* This property is for debugging only; it is not optimized for production use.
@@ -150,6 +185,7 @@ define([
* @default false
*/
this.debugShowPickStatistics = defaultValue(options.debugShowPickStatistics, false);
+ this._debugShowPickStatistics = false;
this._statistics = {
// Rendering stats
@@ -660,8 +696,8 @@ define([
++stats.numberOfPendingRequests;
var removeFunction = removeFromProcessingQueue(tileset, tile);
- when(tile.contentReadyToProcessPromise).then(addToProcessingQueue(tileset, tile)).otherwise(removeFunction);
- when(tile.contentReadyPromise).then(removeFunction).otherwise(removeFunction);
+ when(tile.content.contentReadyToProcessPromise).then(addToProcessingQueue(tileset, tile)).otherwise(removeFunction);
+ when(tile.content.readyPromise).then(removeFunction).otherwise(removeFunction);
}
}
@@ -678,11 +714,19 @@ define([
tileContent.featurePropertiesDirty = false;
tile.lastStyleTime = 0; // Force applying the style to this tile
tileset._selectedTilesToStyle.push(tile);
- } else if ((tile.lastFrameNumber !== frameState.frameNumber - 1)) {
- // Tile is newly visible; it is visible this frame, but was not visible last frame.
+ } else if ((tile.lastSelectedFrameNumber !== frameState.frameNumber - 1)) {
+ // Tile is newly selected; it is selected this frame, but was not selected last frame.
tileset._selectedTilesToStyle.push(tile);
}
- tile.lastFrameNumber = frameState.frameNumber;
+ tile.lastSelectedFrameNumber = frameState.frameNumber;
+ }
+ }
+
+ function touch(tileset, tile, frameState) {
+ var node = tile.replacementNode;
+ if (defined(node)) {
+ tile.lastTouchedFrameNumber = frameState.frameNumber;
+ tileset._replacementList.splice(tileset._replacementSentinel, node);
}
}
@@ -702,6 +746,12 @@ define([
scratchRefiningTiles.length = 0;
+ // Move sentinel node to the tail so, at the start of the frame, all tiles
+ // may be potentially replaced. Tiles are moved to the right of the sentinel
+ // when they are selected so they will not be replaced.
+ var replacementList = tileset._replacementList;
+ tileset._replacementList.splice(replacementList.tail, tileset._replacementSentinel);
+
var root = tileset._root;
root.distanceToCamera = root.distanceToTile(frameState);
root.parentPlaneMask = CullingVolume.MASK_INDETERMINATE;
@@ -734,6 +784,8 @@ define([
}
var fullyVisible = (planeMask === CullingVolume.MASK_INSIDE);
+ touch(tileset, t, frameState);
+
// Tile is inside/intersects the view frustum. How many pixels is its geometric error?
var sse = getScreenSpaceError(t.geometricError, t, frameState);
// TODO: refine also based on (1) occlusion/VMSSE and/or (2) center of viewport
@@ -915,6 +967,7 @@ define([
tileset._processingQueue.splice(index, 1);
--tileset._statistics.numberProcessing;
++tileset._statistics.numberReady;
+ tile.replacementNode = tileset._replacementList.add(tile);
} else {
// Not in processing queue
// For example, when a url request fails and the ready promise is rejected
@@ -948,8 +1001,11 @@ define([
var stats = tileset._statistics;
var last = isPick ? stats.lastPick : stats.lastColor;
- if (((tileset.debugShowStatistics && !isPick) ||
- (tileset.debugShowPickStatistics && isPick)) &&
+ var outputStats = (tileset.debugShowStatistics && !isPick) || (tileset.debugShowPickStatistics && isPick);
+ var showStatsThisFrame =
+ ((tileset._debugShowStatistics !== tileset.debugShowStatistics) ||
+ (tileset._debugShowPickStatistics !== tileset.debugShowPickStatistics));
+ var statsChanged =
(last.visited !== stats.visited ||
last.numberOfCommands !== stats.numberOfCommands ||
last.selected !== tileset._selectedTiles.length ||
@@ -958,17 +1014,14 @@ define([
last.numberReady !== stats.numberReady ||
last.numberTotal !== stats.numberTotal ||
last.numberOfTilesStyled !== stats.numberOfTilesStyled ||
- last.numberOfFeaturesStyled !== stats.numberOfFeaturesStyled)) {
-
- last.visited = stats.visited;
- last.numberOfCommands = stats.numberOfCommands;
- last.selected = tileset._selectedTiles.length;
- last.numberOfPendingRequests = stats.numberOfPendingRequests;
- last.numberProcessing = stats.numberProcessing;
- last.numberReady = stats.numberReady;
- last.numberTotal = stats.numberTotal;
- last.numberOfTilesStyled = stats.numberOfTilesStyled;
- last.numberOfFeaturesStyled = stats.numberOfFeaturesStyled;
+ last.numberOfFeaturesStyled !== stats.numberOfFeaturesStyled);
+
+ if (outputStats && (showStatsThisFrame || statsChanged)) {
+ // The shadowed properties are used to ensure that when a show stats properties
+ // is set to true, it outputs the stats on the next frame even if they didn't
+ // change from the previous frame.
+ tileset._debugShowStatistics = tileset.debugShowStatistics;
+ tileset._debugShowPickStatistics = tileset.debugShowPickStatistics;
// Since the pick pass uses a smaller frustum around the pixel of interest,
// the stats will be different than the normal render pass.
@@ -998,6 +1051,16 @@ define([
/*global console*/
console.log(s);
}
+
+ last.visited = stats.visited;
+ last.numberOfCommands = stats.numberOfCommands;
+ last.selected = tileset._selectedTiles.length;
+ last.numberOfPendingRequests = stats.numberOfPendingRequests;
+ last.numberProcessing = stats.numberProcessing;
+ last.numberReady = stats.numberReady;
+ last.numberTotal = stats.numberTotal;
+ last.numberOfTilesStyled = stats.numberOfTilesStyled;
+ last.numberOfFeaturesStyled = stats.numberOfFeaturesStyled;
}
function updateTiles(tileset, frameState) {
@@ -1022,6 +1085,32 @@ define([
tileset._statistics.numberOfCommands = (commandList.length - numberOfInitialCommands);
}
+ function unloadTiles(tileset, frameState) {
+ var stats = tileset._statistics;
+ var frameNumber = frameState.frameNumber;
+ var maximumNumberOfLoadedTiles = tileset.maximumNumberOfLoadedTiles + 1; // + 1 to account for sentinel
+ var replacementList = tileset._replacementList;
+
+// TODO: explore proactively clearing the cache - automatically or provide a function for the user.
+// TODO: could check last n frames using lastTouchedFrameNumber
+
+ // Traverse the list only to the sentinel since tiles/nodes to the
+ // right of the sentinel were used this frame.
+ //
+ // The sub-list to the left of the sentinel is ordered from LRU to MRU.
+ var sentinel = tileset._replacementSentinel;
+ var node = replacementList.head;
+ while ((node !== sentinel) && (replacementList.length > maximumNumberOfLoadedTiles)) {
+ node.item.unloadContent();
+
+ var currentNode = node;
+ node = node.next;
+ replacementList.remove(currentNode);
+
+ --stats.numberReady;
+ }
+ }
+
///////////////////////////////////////////////////////////////////////////
function raiseLoadProgressEvent(tileset, frameState) {
@@ -1067,9 +1156,14 @@ define([
if (outOfCore) {
processTiles(this, frameState);
}
+
selectTiles(this, frameState, outOfCore);
updateTiles(this, frameState);
+ if (outOfCore) {
+ unloadTiles(this, frameState);
+ }
+
// Events are raised (added to the afterRender queue) here since promises
// may resolve outside of the update loop that then raise events, e.g.,
// model's readyPromise.
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index 58bf9a694ea4..7e4d15a1452f 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -603,7 +603,7 @@ defineSuite([
// Set view so that root's content is requested
viewRootOnly();
scene.renderForSpecs();
- return root.contentReadyPromise.then(function() {
+ return root.content.readyPromise.then(function() {
// Root has one child now, the root of the external tileset
expect(root.children.length).toEqual(1);
@@ -639,7 +639,7 @@ defineSuite([
viewRootOnly();
scene.renderForSpecs();
- return tileset._root.contentReadyPromise;
+ return tileset._root.content.readyPromise;
}).then(function() {
//Make sure tileset2.json was requested with query parameters and version
var queryParamsWithVersion = queryParams + '&v=0.0';
@@ -760,7 +760,7 @@ defineSuite([
viewRootOnly();
scene.renderForSpecs(); // Request root
expect(tileset._statistics.numberOfPendingRequests).toEqual(1);
- return tileset._root.contentReadyToProcessPromise.then(function() {
+ return tileset._root.content.contentReadyToProcessPromise.then(function() {
scene.pickForSpecs();
expect(spy).not.toHaveBeenCalled();
scene.renderForSpecs();
@@ -864,13 +864,9 @@ defineSuite([
expect(stats.numberOfPendingRequests).toEqual(1);
scene.primitives.remove(tileset);
- return root.contentReadyPromise.then(function(root) {
- fail('should not resolve');
- }).otherwise(function(error) {
- // Expect the root to not have added any children from the external tileset.json
- expect(root.children.length).toEqual(0);
- expect(RequestScheduler.getNumberOfAvailableRequests()).toEqual(RequestScheduler.maximumRequests);
- });
+ expect(root.content).not.toBeDefined();
+ // Expect the root to not have added any children from the external tileset.json
+ expect(root.children.length).toEqual(0);
});
});
@@ -884,12 +880,7 @@ defineSuite([
scene.renderForSpecs(); // Request root
scene.primitives.remove(tileset);
- return root.contentReadyPromise.then(function(root) {
- fail('should not resolve');
- }).otherwise(function(error) {
- expect(content.state).toEqual(Cesium3DTileContentState.FAILED);
- expect(RequestScheduler.getNumberOfAvailableRequests()).toEqual(RequestScheduler.maximumRequests);
- });
+ expect(root.content).not.toBeDefined();
});
});
From aa439c1ee7df08eb3e102f4f215d2e87fb81e416 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Tue, 5 Apr 2016 14:41:13 -0400
Subject: [PATCH 10/22] Added trimLoadedTiles
---
Source/Scene/Cesium3DTileset.js | 28 +++++++++++++++++++++++-----
1 file changed, 23 insertions(+), 5 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index ca48c971a807..9d6b1123ed97 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -56,6 +56,7 @@ define([
// TODO: test with replacement refinement
// TODO: Refactor TileReplacementQueue to use DoublyLinkedList?
// TODO: track stats for cache trashing
+// TODO: research strategies for proactive cache trimming: number of seconds/frame a tile was not selected, how far out of view a tile is, etc.
// TODO: good default for maximumNumberOfLoadedTiles
// TODO: More precise size than number of tiles? Count composite tiles as more? Include geometry/texture cost with each tile?
// TODO: unload sub-trees from tiles with tileset.json content
@@ -127,6 +128,7 @@ define([
// (sentinel, tail] -> tiles that were selected this frame
this._replacementList = replacementList; // Tiles with content loaded. For cache management
this._replacementSentinel = replacementList.add();
+ this._trimTiles = false;
/**
* Determines if the tileset will be shown.
@@ -148,6 +150,9 @@ define([
/**
* The maximum number of tiles to load. Tiles not in view are unloaded to enforce this.
*
+ * If decreasing this value results in unloading tiles, the tiles are unloaded the next frame.
+ *
+ *
* If more tiles than maximumNumberOfLoadedTiles
are needed
* to meet the desired screen-space error, determined by {@link Cesium3DTileset#maximumScreenSpaceError},
* for the current view than the number of tiles loaded will exceed
@@ -1086,21 +1091,20 @@ define([
}
function unloadTiles(tileset, frameState) {
+ var trimTiles = tileset._trimTiles;
+ tileset._trimTiles = false;
+
var stats = tileset._statistics;
- var frameNumber = frameState.frameNumber;
var maximumNumberOfLoadedTiles = tileset.maximumNumberOfLoadedTiles + 1; // + 1 to account for sentinel
var replacementList = tileset._replacementList;
-// TODO: explore proactively clearing the cache - automatically or provide a function for the user.
-// TODO: could check last n frames using lastTouchedFrameNumber
-
// Traverse the list only to the sentinel since tiles/nodes to the
// right of the sentinel were used this frame.
//
// The sub-list to the left of the sentinel is ordered from LRU to MRU.
var sentinel = tileset._replacementSentinel;
var node = replacementList.head;
- while ((node !== sentinel) && (replacementList.length > maximumNumberOfLoadedTiles)) {
+ while ((node !== sentinel) && ((replacementList.length > maximumNumberOfLoadedTiles) || trimTiles)) {
node.item.unloadContent();
var currentNode = node;
@@ -1111,6 +1115,20 @@ define([
}
}
+ /**
+ * Unloads all tiles that weren't selected the previous frame. This can be used to
+ * explicitly manage the tile cache and reduce the total number of tiles loaded below
+ * {@link Cesium3DTileset#maximumNumberOfLoadedTiles}.
+ *
+ * Tile unloads occur at the next frame to keep all the WebGL delete calls
+ * within the render loop.
+ *
+ */
+ Cesium3DTileset.prototype.trimLoadedTiles = function() {
+ // Defer to next frame so WebGL delete calls happen inside the render loop
+ this._trimTiles = true;
+ };
+
///////////////////////////////////////////////////////////////////////////
function raiseLoadProgressEvent(tileset, frameState) {
From 00ebcfbd307750845da91805009a2cdeee77ee86 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Tue, 5 Apr 2016 14:41:27 -0400
Subject: [PATCH 11/22] Updated Sandcastle app
---
Apps/Sandcastle/gallery/3D Tiles.html | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/Apps/Sandcastle/gallery/3D Tiles.html b/Apps/Sandcastle/gallery/3D Tiles.html
index 3c9f61df7e10..342979c620bd 100644
--- a/Apps/Sandcastle/gallery/3D Tiles.html
+++ b/Apps/Sandcastle/gallery/3D Tiles.html
@@ -52,7 +52,9 @@
reset();
tileset = scene.primitives.add(new Cesium.Cesium3DTileset({
- url : url
+ url : url,
+ debugShowStatistics : true,
+ maximumNumberOfLoadedTiles : 3
}));
return Cesium.when(tileset.readyPromise).then(function(tileset) {
@@ -103,7 +105,7 @@
return;
}
- console.log('Loading: requests: ' + numberOfPendingRequests + ', processing: ' + numberProcessing);
+ //console.log('Loading: requests: ' + numberOfPendingRequests + ', processing: ' + numberProcessing);
});
addExpressionUI();
@@ -374,6 +376,10 @@
console.log('New max SSE: ' + tileset.maximumScreenSpaceError);
});
+Sandcastle.addToolbarButton('Trim tiles (cache)', function() {
+ tileset.trimLoadedTiles();
+});
+
// Styling ////////////////////////////////////////////////////////////////////
var numberofColors = 6;
From 6b648caacd6f44c87e809c513a5f829b817dcb6e Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Tue, 5 Apr 2016 14:44:59 -0400
Subject: [PATCH 12/22] Remove unused lastTouchedFrameNumber
---
Source/Scene/Cesium3DTile.js | 12 ------------
Source/Scene/Cesium3DTileset.js | 5 ++---
2 files changed, 2 insertions(+), 15 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 40ba4b1c7134..8f1096469c02 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -279,17 +279,6 @@ define([
*/
this.lastSelectedFrameNumber = 0;
- /**
- * The last frame number the tile was visited during tile selection. A tile may be touched,
- * but not selected because, for example, it is a parent using replacement refinement and
- * its children are selected. A selected tile will always be touched.
- *
- * @type {Number}
- *
- * @private
- */
- this.lastTouchedFrameNumber = 0;
-
/**
* The time when a style was last applied to this tile.
*
@@ -441,7 +430,6 @@ define([
this.parentPlaneMask = 0;
this.selected = false;
this.lastSelectedFrameNumber = 0;
- this.lastTouchedFrameNumber = 0;
this.lastStyleTime = 0;
this._debugBoundingVolume = this._debugBoundingVolume && this._debugBoundingVolume.destroy();
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 9d6b1123ed97..b28898d9593c 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -727,10 +727,9 @@ define([
}
}
- function touch(tileset, tile, frameState) {
+ function touch(tileset, tile) {
var node = tile.replacementNode;
if (defined(node)) {
- tile.lastTouchedFrameNumber = frameState.frameNumber;
tileset._replacementList.splice(tileset._replacementSentinel, node);
}
}
@@ -789,7 +788,7 @@ define([
}
var fullyVisible = (planeMask === CullingVolume.MASK_INSIDE);
- touch(tileset, t, frameState);
+ touch(tileset, t);
// Tile is inside/intersects the view frustum. How many pixels is its geometric error?
var sse = getScreenSpaceError(t.geometricError, t, frameState);
From 2520cdcd90d9d7f38abb5af88375137d03050b04 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Tue, 5 Apr 2016 14:50:43 -0400
Subject: [PATCH 13/22] Fix JSHint warnings
---
Source/Core/DoublyLinkedList.js | 2 +-
Source/Scene/Cesium3DTileset.js | 2 +-
Specs/Scene/Cesium3DTilesetSpec.js | 1 -
3 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/Source/Core/DoublyLinkedList.js b/Source/Core/DoublyLinkedList.js
index 02dd8d86ffbd..02afaae6ec54 100644
--- a/Source/Core/DoublyLinkedList.js
+++ b/Source/Core/DoublyLinkedList.js
@@ -105,7 +105,7 @@ define([
nextNode.next = oldNodeNext;
nextNode.previous = node;
- }
+ };
return DoublyLinkedList;
});
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index b28898d9593c..90567ba92010 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -344,7 +344,7 @@ define([
this.numberTotal = -1;
this.numberOfTilesStyled = -1;
this.numberOfFeaturesStyled = -1;
- };
+ }
defineProperties(Cesium3DTileset.prototype, {
/**
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index 7e4d15a1452f..c41ce3574f9f 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -874,7 +874,6 @@ defineSuite([
viewNothing();
return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
var root = tileset._root;
- var content = root.content;
viewRootOnly();
scene.renderForSpecs(); // Request root
From 78579d964ad7ee12e2c20873e12ab327fad1a5e6 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Wed, 6 Apr 2016 07:51:39 -0400
Subject: [PATCH 14/22] Add reference doc
---
Source/Scene/Cesium3DTile.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 8f1096469c02..759a0f5cc482 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -235,7 +235,10 @@ define([
this.hasTilesetContent = hasTilesetContent;
/**
- * DOC_TBA
+ * The corresponding node in the cache replacement list.
+ *
+ * @type {DoublyLinkedList}
+ * @readonly
*
* @private
*/
From 3d24ecfa65775c3b1b05daf8a47a94f7200a9688 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Wed, 6 Apr 2016 07:51:50 -0400
Subject: [PATCH 15/22] Add tileUnload event
---
Source/Scene/Cesium3DTileset.js | 47 ++++++++++++++++++++++++++-------
1 file changed, 38 insertions(+), 9 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 90567ba92010..e5bb226c1a3a 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -51,15 +51,24 @@ define([
SceneMode) {
'use strict';
-// TODO: unload events
// TODO: unit tests
// TODO: test with replacement refinement
+
+// TODO: since the show/color/setProperty values set with Cesium3DTileFeature only have the
+// lifetime of the tile's content (e.g., if the content is unloaded, then reloaded later, the
+// values wll be gown), we need to expose an event like loadProgress for when content is unloaded.
+//
+// We might also want to keep a separate data structure - or flag - so we know what property
+// values changed (other than those derived from declarative styling, which can easily be
+// reapplied).
+
// TODO: Refactor TileReplacementQueue to use DoublyLinkedList?
// TODO: track stats for cache trashing
// TODO: research strategies for proactive cache trimming: number of seconds/frame a tile was not selected, how far out of view a tile is, etc.
// TODO: good default for maximumNumberOfLoadedTiles
// TODO: More precise size than number of tiles? Count composite tiles as more? Include geometry/texture cost with each tile?
// TODO: unload sub-trees from tiles with tileset.json content
+// TODO: vertex/texture cache across tiles
/**
* A {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles tileset},
@@ -286,13 +295,29 @@ define([
*/
this.loadProgress = new Event();
- // TODO: since the show/color/setProperty values set with Cesium3DTileFeature only have the
- // lifetime of the tile's content (e.g., if the content is unloaded, then reloaded later, the
- // values wll be gown), we need to expose an event like loadProgress for when content is unloaded.
- //
- // We might also want to keep a separate data structure - or flag - so we know what property
- // values changed (other than those derived from declarative styling, which can easily be
- // reapplied).
+ /**
+ * The event fired to indicate that a tile's content was unloaded from the cache.
+ *
+ * The unloaded {@link Cesium3DTile} is passed to the event listener.
+ *
+ *
+ * This event is immediately before the tile's content is unloaded while the frame is being
+ * rendered so that the event listener has access to the tile's content. Do not create
+ * or modify Cesium entities or primitives during the event listener.
+ *
+ *
+ * @type {Event}
+ * @default new Event()
+ *
+ * @example
+ * tileset.tileUnload.addEventListener(function(tile) {
+ * console.log('A tile was unloaded from the cache.');
+ * });
+ *
+ * @see Cesium3DTileset#maximumNumberOfLoadedTiles
+ * @see Cesium3DTileset#trimLoadedTiles
+ */
+ this.tileUnload = new Event();
/**
* This event fires once for each visible tile in a frame. This can be used to manually
@@ -1096,6 +1121,7 @@ define([
var stats = tileset._statistics;
var maximumNumberOfLoadedTiles = tileset.maximumNumberOfLoadedTiles + 1; // + 1 to account for sentinel
var replacementList = tileset._replacementList;
+ var tileUnload = tileset.tileUnload;
// Traverse the list only to the sentinel since tiles/nodes to the
// right of the sentinel were used this frame.
@@ -1104,7 +1130,10 @@ define([
var sentinel = tileset._replacementSentinel;
var node = replacementList.head;
while ((node !== sentinel) && ((replacementList.length > maximumNumberOfLoadedTiles) || trimTiles)) {
- node.item.unloadContent();
+ var tile = node.item;
+
+ tileUnload.raiseEvent(tile);
+ tile.unloadContent();
var currentNode = node;
node = node.next;
From 4f352cf8d2ae9411320a4cea65e4fb95861563d8 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Wed, 6 Apr 2016 07:52:01 -0400
Subject: [PATCH 16/22] Update Sandcastle example
---
Apps/Sandcastle/gallery/3D Tiles.html | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Apps/Sandcastle/gallery/3D Tiles.html b/Apps/Sandcastle/gallery/3D Tiles.html
index 342979c620bd..001a14b4b0d0 100644
--- a/Apps/Sandcastle/gallery/3D Tiles.html
+++ b/Apps/Sandcastle/gallery/3D Tiles.html
@@ -108,6 +108,10 @@
//console.log('Loading: requests: ' + numberOfPendingRequests + ', processing: ' + numberProcessing);
});
+ tileset.tileUnload.addEventListener(function(tile) {
+ //console.log('Tile unloaded.')
+ });
+
addExpressionUI();
});
}
From befe17110668b88d62062472b748f8487a742df7 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Wed, 6 Apr 2016 09:48:02 -0400
Subject: [PATCH 17/22] Added cache replacement tests
---
Source/Scene/Cesium3DTileset.js | 107 +++++++----
Specs/Scene/Cesium3DTilesetSpec.js | 284 ++++++++++++++++++++++++++++-
2 files changed, 346 insertions(+), 45 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index e5bb226c1a3a..78f7464b4cba 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -51,9 +51,6 @@ define([
SceneMode) {
'use strict';
-// TODO: unit tests
-// TODO: test with replacement refinement
-
// TODO: since the show/color/setProperty values set with Cesium3DTileFeature only have the
// lifetime of the tile's content (e.g., if the content is unloaded, then reloaded later, the
// values wll be gown), we need to expose an event like loadProgress for when content is unloaded.
@@ -63,9 +60,9 @@ define([
// reapplied).
// TODO: Refactor TileReplacementQueue to use DoublyLinkedList?
+// TODO: good default for maximumNumberOfLoadedTiles
// TODO: track stats for cache trashing
// TODO: research strategies for proactive cache trimming: number of seconds/frame a tile was not selected, how far out of view a tile is, etc.
-// TODO: good default for maximumNumberOfLoadedTiles
// TODO: More precise size than number of tiles? Count composite tiles as more? Include geometry/texture cost with each tile?
// TODO: unload sub-trees from tiles with tileset.json content
// TODO: vertex/texture cache across tiles
@@ -147,34 +144,8 @@ define([
*/
this.show = defaultValue(options.show, true);
- /**
- * The maximum screen-space error used to drive level-of-detail refinement. Higher
- * values will provide better performance but lower visual quality.
- *
- * @type {Number}
- * @default 16
- */
- this.maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 16);
-
- /**
- * The maximum number of tiles to load. Tiles not in view are unloaded to enforce this.
- *
- * If decreasing this value results in unloading tiles, the tiles are unloaded the next frame.
- *
- *
- * If more tiles than maximumNumberOfLoadedTiles
are needed
- * to meet the desired screen-space error, determined by {@link Cesium3DTileset#maximumScreenSpaceError},
- * for the current view than the number of tiles loaded will exceed
- * maximumNumberOfLoadedTiles
. For example, if the maximum is 128 tiles, but
- * 150 tiles are needed to meet the screen-space error, then 150 tiles may be loaded. When
- * these tiles go out of view, they will be unloaded.
- *
- *
- * @type {Number}
- * @default 256
- */
- this.maximumNumberOfLoadedTiles = defaultValue(options.maximumNumberOfLoadedTiles, 256);
-
+ this._maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 16);
+ this._maximumNumberOfLoadedTiles = defaultValue(options.maximumNumberOfLoadedTiles, 256);
this._styleEngine = new Cesium3DTileStyleEngine();
/**
@@ -554,6 +525,68 @@ define([
}
},
+ /**
+ * The maximum screen-space error used to drive level-of-detail refinement. Higher
+ * values will provide better performance but lower visual quality.
+ *
+ * @memberof Cesium3DTileset.prototype
+ *
+ * @type {Number}
+ * @default 16
+ *
+ * @exception {DeveloperError} maximumScreenSpaceError
must be greater than or equal to zero.
+ */
+ maximumScreenSpaceError : {
+ get : function() {
+ return this._maximumScreenSpaceError;
+ },
+ set : function(value) {
+ //>>includeStart('debug', pragmas.debug);
+ if (value < 0) {
+ throw new DeveloperError('maximumScreenSpaceError must be greater than or equal to zero');
+ }
+ //>>includeEnd('debug');
+
+ this._maximumScreenSpaceError = value;
+ }
+ },
+
+ /**
+ * The maximum number of tiles to load. Tiles not in view are unloaded to enforce this.
+ *
+ * If decreasing this value results in unloading tiles, the tiles are unloaded the next frame.
+ *
+ *
+ * If more tiles than maximumNumberOfLoadedTiles
are needed
+ * to meet the desired screen-space error, determined by {@link Cesium3DTileset#maximumScreenSpaceError},
+ * for the current view than the number of tiles loaded will exceed
+ * maximumNumberOfLoadedTiles
. For example, if the maximum is 128 tiles, but
+ * 150 tiles are needed to meet the screen-space error, then 150 tiles may be loaded. When
+ * these tiles go out of view, they will be unloaded.
+ *
+ *
+ * @memberof Cesium3DTileset.prototype
+ *
+ * @type {Number}
+ * @default 256
+ *
+ * @exception {DeveloperError} maximumNumberOfLoadedTiles
must be greater than or equal to zero.
+ */
+ maximumNumberOfLoadedTiles : {
+ get : function() {
+ return this._maximumNumberOfLoadedTiles;
+ },
+ set : function(value) {
+ //>>includeStart('debug', pragmas.debug);
+ if (value < 0) {
+ throw new DeveloperError('maximumNumberOfLoadedTiles must be greater than or equal to zero');
+ }
+ //>>includeEnd('debug');
+
+ this._maximumNumberOfLoadedTiles = value;
+ }
+ },
+
/**
* @private
*/
@@ -767,7 +800,7 @@ define([
return;
}
- var maximumScreenSpaceError = tileset.maximumScreenSpaceError;
+ var maximumScreenSpaceError = tileset._maximumScreenSpaceError;
var cullingVolume = frameState.cullingVolume;
tileset._selectedTiles.length = 0;
@@ -996,7 +1029,11 @@ define([
tileset._processingQueue.splice(index, 1);
--tileset._statistics.numberProcessing;
++tileset._statistics.numberReady;
- tile.replacementNode = tileset._replacementList.add(tile);
+ if (tile.hasContent) {
+ // RESEARCH_IDEA: ability to unload tiles (without content) for an
+ // external tileset when all the tiles are unloaded.
+ tile.replacementNode = tileset._replacementList.add(tile);
+ }
} else {
// Not in processing queue
// For example, when a url request fails and the ready promise is rejected
@@ -1119,7 +1156,7 @@ define([
tileset._trimTiles = false;
var stats = tileset._statistics;
- var maximumNumberOfLoadedTiles = tileset.maximumNumberOfLoadedTiles + 1; // + 1 to account for sentinel
+ var maximumNumberOfLoadedTiles = tileset._maximumNumberOfLoadedTiles + 1; // + 1 to account for sentinel
var replacementList = tileset._replacementList;
var tileUnload = tileset.tileUnload;
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index c41ce3574f9f..968f33b466b1 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -423,8 +423,6 @@ defineSuite([
it('additive refinement - selects root when sse is met', function() {
return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
- tileset._root.refine = Cesium3DTileRefine.ADD;
-
// Meets screen space error, only root tile is rendered
viewRootOnly();
scene.renderForSpecs();
@@ -437,8 +435,6 @@ defineSuite([
it('additive refinement - selects all tiles when sse is not met', function() {
return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
- tileset._root.refine = Cesium3DTileRefine.ADD;
-
// Does not meet screen space error, all tiles are visible
viewAllTiles();
scene.renderForSpecs();
@@ -883,6 +879,9 @@ defineSuite([
});
});
+ ///////////////////////////////////////////////////////////////////////////
+ // Styling tests
+
it('applies show style to a tileset', function() {
return Cesium3DTilesTester.loadTileset(scene, withoutBatchTableUrl).then(function(tileset) {
var showColor = scene.renderForSpecs();
@@ -983,12 +982,6 @@ defineSuite([
expect(color).toEqual(originalColor);
}
- xit('applies color style to a tileset with translucent tiles', function() {
- return Cesium3DTilesTester.loadTileset(scene, translucentUrl).then(function(tileset) {
- expectColorStyle(tileset);
- });
- });
-
it('applies color style to a tileset with translucent tiles', function() {
return Cesium3DTilesTester.loadTileset(scene, translucentUrl).then(function(tileset) {
expectColorStyle(tileset);
@@ -1127,4 +1120,275 @@ defineSuite([
});
});
+ ///////////////////////////////////////////////////////////////////////////
+ // Cache replacement tests
+
+ it('Unload all cached tiles not required to meet SSE', function() {
+ return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
+ tileset.maximumNumberOfLoadedTiles = 1;
+
+ // Render parent and four children (using additive refinement)
+ viewAllTiles();
+ scene.renderForSpecs();
+
+ var stats = tileset._statistics;
+ expect(stats.numberOfCommands).toEqual(5);
+ expect(stats.numberReady).toEqual(5); // Five loaded tiles
+
+ // Zoom out so only root tile is needed to meet SSE. This unloads
+ // the four children since the max number of loaded tiles is one.
+ viewRootOnly();
+ scene.renderForSpecs();
+
+ expect(stats.numberOfCommands).toEqual(1);
+ expect(stats.numberReady).toEqual(1);
+
+ // Zoom back in so all four children are re-requested.
+ viewAllTiles();
+
+ return Cesium3DTilesTester.waitForPendingRequests(scene, tileset).then(function() {
+ scene.renderForSpecs();
+ expect(stats.numberOfCommands).toEqual(5);
+ expect(stats.numberReady).toEqual(5); // Five loaded tiles
+ });
+ });
+ });
+
+ it('Unload some cached tiles not required to meet SSE', function() {
+ return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
+ tileset.maximumNumberOfLoadedTiles = 3;
+
+ // Render parent and four children (using additive refinement)
+ viewAllTiles();
+ scene.renderForSpecs();
+
+ var stats = tileset._statistics;
+ expect(stats.numberOfCommands).toEqual(5);
+ expect(stats.numberReady).toEqual(5); // Five loaded tiles
+
+ // Zoom out so only root tile is needed to meet SSE. This unloads
+ // two of the four children so three tiles are still loaded (the
+ // root and two children) since the max number of loaded tiles is three.
+ viewRootOnly();
+ scene.renderForSpecs();
+
+ expect(stats.numberOfCommands).toEqual(1);
+ expect(stats.numberReady).toEqual(3);
+
+ // Zoom back in so the two children are re-requested.
+ viewAllTiles();
+
+ return Cesium3DTilesTester.waitForPendingRequests(scene, tileset).then(function() {
+ scene.renderForSpecs();
+ expect(stats.numberOfCommands).toEqual(5);
+ expect(stats.numberReady).toEqual(5); // Five loaded tiles
+ });
+ });
+ });
+
+ it('Unloads cached tiles outside of the view frustum', function() {
+ return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
+ tileset.maximumNumberOfLoadedTiles = 0;
+
+ scene.renderForSpecs();
+ var stats = tileset._statistics;
+ expect(stats.numberOfCommands).toEqual(5);
+ expect(stats.numberReady).toEqual(5);
+
+ // Orient camera to face the sky
+ var center = Cartesian3.fromRadians(centerLongitude, centerLatitude, 100);
+ scene.camera.lookAt(center, new HeadingPitchRange(0.0, 1.57, 10.0));
+
+ // All tiles are unloaded
+ scene.renderForSpecs();
+ expect(stats.numberOfCommands).toEqual(0);
+ expect(stats.numberReady).toEqual(0);
+
+ // Reset camera so all tiles are reloaded
+ viewAllTiles();
+
+ return Cesium3DTilesTester.waitForPendingRequests(scene, tileset).then(function() {
+ scene.renderForSpecs();
+ expect(stats.numberOfCommands).toEqual(5);
+ expect(stats.numberReady).toEqual(5);
+ });
+ });
+ });
+
+ it('Unloads cached tiles in a tileset with external tileset.json', function() {
+ return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then(function(tileset) {
+ var stats = tileset._statistics;
+ var replacementList = tileset._replacementList;
+
+ tileset.maximumNumberOfLoadedTiles = 2;
+
+ scene.renderForSpecs();
+ expect(stats.numberOfCommands).toEqual(5);
+ expect(stats.numberReady).toEqual(7); // 5 with b3dm content + 2 empty/tileset.json
+ expect(replacementList.length - 1).toEqual(5); // Only tiles with content are on the replacement list. -1 for sentinel.
+
+ // Zoom out so only root tile is needed to meet SSE. This unloads
+ // all tiles except the root and one of the b3dm children
+ viewRootOnly();
+ scene.renderForSpecs();
+
+ expect(stats.numberOfCommands).toEqual(1);
+ expect(stats.numberReady).toEqual(4); // 2 with b3dm content + 2 empty/tileset.json
+ expect(replacementList.length - 1).toEqual(2);
+
+ // Reset camera so all tiles are reloaded
+ viewAllTiles();
+
+ return Cesium3DTilesTester.waitForPendingRequests(scene, tileset).then(function() {
+ scene.renderForSpecs();
+ expect(stats.numberOfCommands).toEqual(5);
+ expect(stats.numberReady).toEqual(7);
+ expect(replacementList.length - 1).toEqual(5);
+ });
+ });
+ });
+
+ it('Unloads cached tiles in a tileset with empty tiles', function() {
+ return Cesium3DTilesTester.loadTileset(scene, tilesetEmptyRootUrl).then(function(tileset) {
+ var stats = tileset._statistics;
+ var replacementList = tileset._replacementList;
+
+ tileset.maximumNumberOfLoadedTiles = 2;
+
+ scene.renderForSpecs();
+ expect(stats.numberOfCommands).toEqual(4);
+ expect(stats.numberReady).toEqual(4); // 4 children with b3dm content (does not include empty root)
+
+ // Orient camera to face the sky
+ var center = Cartesian3.fromRadians(centerLongitude, centerLatitude, 100);
+ scene.camera.lookAt(center, new HeadingPitchRange(0.0, 1.57, 10.0));
+
+ // Unload tiles to meet cache size
+ scene.renderForSpecs();
+ expect(stats.numberOfCommands).toEqual(0);
+ expect(stats.numberReady).toEqual(2); // 2 children with b3dm content (does not include empty root)
+
+ // Reset camera so all tiles are reloaded
+ viewAllTiles();
+
+ return Cesium3DTilesTester.waitForPendingRequests(scene, tileset).then(function() {
+ scene.renderForSpecs();
+ expect(stats.numberOfCommands).toEqual(4);
+ expect(stats.numberReady).toEqual(4);
+ });
+ });
+ });
+
+ it('Unload cached tiles when a tileset uses replacement refinement', function() {
+ // No children have content, but all grandchildren have content
+ //
+ // C
+ // E E
+ // C C C C
+ //
+ return Cesium3DTilesTester.loadTileset(scene, tilesetReplacement1Url).then(function(tileset) {
+ tileset.maximumNumberOfLoadedTiles = 1;
+
+ // Render parent and four children (using additive refinement)
+ viewAllTiles();
+ scene.renderForSpecs();
+
+ var stats = tileset._statistics;
+ expect(stats.numberOfCommands).toEqual(4); // 4 grandchildren. Root is replaced.
+ expect(stats.numberReady).toEqual(5); // Root + four grandchildren (does not include empty children)
+
+ // Zoom out so only root tile is needed to meet SSE. This unloads
+ // two of the four children so three tiles are still loaded (the
+ // root and two children) since the max number of loaded tiles is three.
+ viewRootOnly();
+ scene.renderForSpecs();
+
+ expect(stats.numberOfCommands).toEqual(1);
+ expect(stats.numberReady).toEqual(1);
+
+ // Zoom back in so the two children are re-requested.
+ viewAllTiles();
+
+ return Cesium3DTilesTester.waitForPendingRequests(scene, tileset).then(function() {
+ scene.renderForSpecs();
+ expect(stats.numberOfCommands).toEqual(4);
+ expect(stats.numberReady).toEqual(5);
+ });
+ });
+ });
+
+ it('Explicitly unloads cached tiles with trimLoadedTiles', function() {
+ return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
+ tileset.maximumNumberOfLoadedTiles = 5;
+
+ // Render parent and four children (using additive refinement)
+ viewAllTiles();
+ scene.renderForSpecs();
+
+ var stats = tileset._statistics;
+ expect(stats.numberOfCommands).toEqual(5);
+ expect(stats.numberReady).toEqual(5); // Five loaded tiles
+
+ // Zoom out so only root tile is needed to meet SSE. The children
+ // are not unloaded since max number of loaded tiles is five.
+ viewRootOnly();
+ scene.renderForSpecs();
+
+ expect(stats.numberOfCommands).toEqual(1);
+ expect(stats.numberReady).toEqual(5);
+
+ tileset.trimLoadedTiles();
+ scene.renderForSpecs();
+
+ expect(stats.numberOfCommands).toEqual(1);
+ expect(stats.numberReady).toEqual(1);
+ });
+ });
+
+ it('tileUnload event is raised', function() {
+ return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
+ tileset.maximumNumberOfLoadedTiles = 1;
+
+ // Render parent and four children (using additive refinement)
+ viewAllTiles();
+ scene.renderForSpecs();
+
+ var stats = tileset._statistics;
+ expect(stats.numberOfCommands).toEqual(5);
+ expect(stats.numberReady).toEqual(5); // Five loaded tiles
+
+ // Zoom out so only root tile is needed to meet SSE. All the
+ // children are unloaded since max number of loaded tiles is one.
+ viewRootOnly();
+ var spyUpdate = jasmine.createSpy('listener');
+ tileset.tileUnload.addEventListener(spyUpdate);
+ scene.renderForSpecs();
+
+ expect(tileset._root.visibility(scene.frameState.cullingVolume)).not.toEqual(CullingVolume.MASK_OUTSIDE);
+ expect(spyUpdate.calls.count()).toEqual(4);
+ expect(spyUpdate.calls.argsFor(0)[0]).toBe(tileset._root.children[0]);
+ expect(spyUpdate.calls.argsFor(1)[0]).toBe(tileset._root.children[1]);
+ expect(spyUpdate.calls.argsFor(2)[0]).toBe(tileset._root.children[2]);
+ expect(spyUpdate.calls.argsFor(3)[0]).toBe(tileset._root.children[3]);
+ });
+ });
+
+ it('maximumNumberOfLoadedTiles throws when negative', function() {
+ var tileset = new Cesium3DTileset({
+ url : tilesetUrl
+ });
+ expect(function() {
+ tileset.maximumNumberOfLoadedTiles = -1;
+ }).toThrowDeveloperError();
+ });
+
+ it('maximumScreenSpaceError throws when negative', function() {
+ var tileset = new Cesium3DTileset({
+ url : tilesetUrl
+ });
+ expect(function() {
+ tileset.maximumScreenSpaceError = -1;
+ }).toThrowDeveloperError();
+ });
+
}, 'WebGL');
From 98e50d4770156d00d5d585f71f24c1bda090287f Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Wed, 6 Apr 2016 11:36:06 -0400
Subject: [PATCH 18/22] Fix tests when all tests are ran
---
Specs/Scene/Cesium3DTilesetSpec.js | 6 ------
1 file changed, 6 deletions(-)
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index 968f33b466b1..1453c24a1cc1 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -673,9 +673,6 @@ defineSuite([
spyOn(console, 'log');
return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
- scene.renderForSpecs();
- expect(console.log).not.toHaveBeenCalled();
-
tileset.debugShowStatistics = true;
scene.renderForSpecs();
expect(console.log).toHaveBeenCalled();
@@ -686,9 +683,6 @@ defineSuite([
spyOn(console, 'log');
return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
- scene.pickForSpecs();
- expect(console.log).not.toHaveBeenCalled();
-
tileset.debugShowPickStatistics = true;
scene.pickForSpecs();
expect(console.log).toHaveBeenCalled();
From 71fa03ffccbf20e6b615c5c2c4008073349d2662 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Wed, 6 Apr 2016 11:42:13 -0400
Subject: [PATCH 19/22] Remove TODOs that are now part of the roadmap
---
Source/Scene/Cesium3DTileset.js | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 78f7464b4cba..3bec165c8557 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -51,22 +51,6 @@ define([
SceneMode) {
'use strict';
-// TODO: since the show/color/setProperty values set with Cesium3DTileFeature only have the
-// lifetime of the tile's content (e.g., if the content is unloaded, then reloaded later, the
-// values wll be gown), we need to expose an event like loadProgress for when content is unloaded.
-//
-// We might also want to keep a separate data structure - or flag - so we know what property
-// values changed (other than those derived from declarative styling, which can easily be
-// reapplied).
-
-// TODO: Refactor TileReplacementQueue to use DoublyLinkedList?
-// TODO: good default for maximumNumberOfLoadedTiles
-// TODO: track stats for cache trashing
-// TODO: research strategies for proactive cache trimming: number of seconds/frame a tile was not selected, how far out of view a tile is, etc.
-// TODO: More precise size than number of tiles? Count composite tiles as more? Include geometry/texture cost with each tile?
-// TODO: unload sub-trees from tiles with tileset.json content
-// TODO: vertex/texture cache across tiles
-
/**
* A {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/README.md|3D Tiles tileset},
* used for streaming massive heterogeneous 3D geospatial datasets.
From 9d942a47fe6dd8ed9c586d3da88f66810b8fd32f Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 7 Apr 2016 15:46:08 -0400
Subject: [PATCH 20/22] Small tweaks
---
Source/Scene/Cesium3DTileset.js | 4 ++--
Specs/Core/DoublyLinkedListSpec.js | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 3bec165c8557..a1a3162d0598 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -116,7 +116,7 @@ define([
// [head, sentinel) -> tiles that weren't selected this frame and may be replaced
// (sentinel, tail] -> tiles that were selected this frame
- this._replacementList = replacementList; // Tiles with content loaded. For cache management
+ this._replacementList = replacementList; // Tiles with content loaded. For cache management.
this._replacementSentinel = replacementList.add();
this._trimTiles = false;
@@ -256,7 +256,7 @@ define([
* The unloaded {@link Cesium3DTile} is passed to the event listener.
*
*
- * This event is immediately before the tile's content is unloaded while the frame is being
+ * This event is fired immediately before the tile's content is unloaded while the frame is being
* rendered so that the event listener has access to the tile's content. Do not create
* or modify Cesium entities or primitives during the event listener.
*
diff --git a/Specs/Core/DoublyLinkedListSpec.js b/Specs/Core/DoublyLinkedListSpec.js
index e66cd339f3da..9cd6fceaefd7 100644
--- a/Specs/Core/DoublyLinkedListSpec.js
+++ b/Specs/Core/DoublyLinkedListSpec.js
@@ -2,7 +2,7 @@
defineSuite([
'Core/DoublyLinkedList'
], function(
- DoublyLinkedList) {
+ DoublyLinkedList) {
'use strict';
it('constructs', function() {
@@ -103,7 +103,7 @@ defineSuite([
var list = new DoublyLinkedList();
var node = list.add(1);
var node2 = list.add(2);
- var node3 = list.add(2);
+ var node3 = list.add(3);
list.remove(node2);
From de9b45ae19c0240ae795877c516fc66d2aa83910 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Mon, 11 Apr 2016 13:54:40 -0400
Subject: [PATCH 21/22] Changes based on review
---
Source/Scene/Cesium3DTile.js | 2 +-
Specs/Scene/Cesium3DTilesetSpec.js | 5 ++---
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index 759a0f5cc482..789fe51d36dc 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -237,7 +237,7 @@ define([
/**
* The corresponding node in the cache replacement list.
*
- * @type {DoublyLinkedList}
+ * @type {DoublyLinkedListNode}
* @readonly
*
* @private
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index 1453c24a1cc1..cdb74467b7f4 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -1292,15 +1292,14 @@ defineSuite([
expect(stats.numberReady).toEqual(5); // Root + four grandchildren (does not include empty children)
// Zoom out so only root tile is needed to meet SSE. This unloads
- // two of the four children so three tiles are still loaded (the
- // root and two children) since the max number of loaded tiles is three.
+ // all grandchildren since the max number of loaded tiles is one.
viewRootOnly();
scene.renderForSpecs();
expect(stats.numberOfCommands).toEqual(1);
expect(stats.numberReady).toEqual(1);
- // Zoom back in so the two children are re-requested.
+ // Zoom back in so the four children are re-requested.
viewAllTiles();
return Cesium3DTilesTester.waitForPendingRequests(scene, tileset).then(function() {
From 515f3bd05aebe679872943c3ed0956773f5b2ed4 Mon Sep 17 00:00:00 2001
From: Patrick Cozzi
Date: Tue, 12 Apr 2016 10:56:19 -0400
Subject: [PATCH 22/22] Updates based on review
---
Specs/Scene/Cesium3DTilesetSpec.js | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js
index cdb74467b7f4..a9bd3173e388 100644
--- a/Specs/Scene/Cesium3DTilesetSpec.js
+++ b/Specs/Scene/Cesium3DTilesetSpec.js
@@ -846,6 +846,7 @@ defineSuite([
viewNothing();
return Cesium3DTilesTester.loadTileset(scene, tilesetOfTilesetsUrl).then(function(tileset) {
var root = tileset._root;
+ var content = root.content;
viewRootOnly();
scene.renderForSpecs(); // Request external tileset.json
@@ -854,9 +855,13 @@ defineSuite([
expect(stats.numberOfPendingRequests).toEqual(1);
scene.primitives.remove(tileset);
- expect(root.content).not.toBeDefined();
- // Expect the root to not have added any children from the external tileset.json
- expect(root.children.length).toEqual(0);
+ return content.readyPromise.then(function(root) {
+ fail('should not resolve');
+ }).otherwise(function(error) {
+ // Expect the root to not have added any children from the external tileset.json
+ expect(root.children.length).toEqual(0);
+ expect(RequestScheduler.getNumberOfAvailableRequests()).toEqual(RequestScheduler.maximumRequests);
+ });
});
});
@@ -864,12 +869,18 @@ defineSuite([
viewNothing();
return Cesium3DTilesTester.loadTileset(scene, tilesetUrl).then(function(tileset) {
var root = tileset._root;
+ var content = root.content;
viewRootOnly();
scene.renderForSpecs(); // Request root
scene.primitives.remove(tileset);
- expect(root.content).not.toBeDefined();
+ return content.readyPromise.then(function(root) {
+ fail('should not resolve');
+ }).otherwise(function(error) {
+ expect(content.state).toEqual(Cesium3DTileContentState.FAILED);
+ expect(RequestScheduler.getNumberOfAvailableRequests()).toEqual(RequestScheduler.maximumRequests);
+ });
});
});