Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3D Tiles - Support tile expiration #4136

Merged
merged 8 commits into from
May 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 125 additions & 23 deletions Source/Scene/Cesium3DTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ define([
'../Core/getStringFromTypedArray',
'../Core/Intersect',
'../Core/joinUrls',
'../Core/JulianDate',
'../Core/loadArrayBuffer',
'../Core/Matrix3',
'../Core/Matrix4',
Expand Down Expand Up @@ -60,6 +61,7 @@ define([
getStringFromTypedArray,
Intersect,
joinUrls,
JulianDate,
loadArrayBuffer,
Matrix3,
Matrix4,
Expand Down Expand Up @@ -118,8 +120,6 @@ define([
*/
this.computedTransform = computedTransform;

this._transformDirty = true;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this since it was only used in point clouds and it was easy to get around.


this._boundingVolume = this.createBoundingVolume(header.boundingVolume, computedTransform);
this._boundingVolume2D = undefined;

Expand Down Expand Up @@ -212,6 +212,7 @@ define([
this._contentState = contentState;
this._contentReadyToProcessPromise = undefined;
this._contentReadyPromise = undefined;
this._expiredContent = undefined;

this._requestServer = requestServer;

Expand Down Expand Up @@ -261,6 +262,30 @@ define([
*/
this.replacementNode = undefined;

var expire = header.expire;
var expireDuration;
var expireDate;
if (defined(expire)) {
expireDuration = expire.duration;
if (defined(expire.date)) {
expireDate = JulianDate.fromIso8601(expire.date);
}
}

/**
* The time in seconds after the tile's content is ready when the content expires and new content is requested.
*
* @type {Number}
*/
this.expireDuration = expireDuration;

/**
* The date when the content expires and new content is requested.
*
* @type {JulianDate}
*/
this.expireDate = expireDate;

// Members that are updated every frame for tree traversal and rendering optimizations:

/**
Expand Down Expand Up @@ -393,26 +418,28 @@ define([
},

/**
* Whether the computedTransform has changed this frame.
*
* @memberof Cesium3DTile.prototype
*
* @type {Boolean}
* @readonly
* @private
*/
transformDirty : {
requestServer : {
get : function() {
return this._transformDirty;
return this._requestServer;
}
},

/**
* Determines if the tile has available content to render. <code>true</code> if the tile's
* content is ready or if it has expired content that renders while new content loads; otherwise,
* <code>false</code>.
*
* @memberof Cesium3DTile.prototype
*
* @type {Boolean}
* @readonly
* @private
*/
requestServer : {
contentAvailable : {
get : function() {
return this._requestServer;
return this.contentReady || (defined(this._expiredContent) && this._contentState !== Cesium3DTileContentState.FAILED);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be tweaked depending on the behavior we want. If a request fails do we want the tile to not be rendered, or do we want to render the expired content? This is doing the former.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The former is OK for now. The spec might need some flexibility though, e.g., would some apps want to show the previous content but mark that it is expired? I suspect so. At some point we might need to provide app-level hooks to support this.

}
},

Expand Down Expand Up @@ -446,6 +473,21 @@ define([
}
},

/**
* Determines if the tile's content is expired. <code>true</code> if tile's
* content is expired; otherwise, <code>false</code>.
*
* @memberof Cesium3DTile.prototype
*
* @type {Boolean}
* @readonly
*/
contentExpired : {
get : function() {
return this._contentState === Cesium3DTileContentState.EXPIRED;
}
},

/**
* 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
Expand Down Expand Up @@ -487,6 +529,38 @@ define([
}
});

var scratchJulianDate = new JulianDate();

/**
* Update whether the tile has expired.
*
* @private
*/
Cesium3DTile.prototype.updateExpiration = function() {
if (defined(this.expireDate) && this.contentReady && !this.hasEmptyContent) {
var now = JulianDate.now(scratchJulianDate);
if (JulianDate.lessThan(this.expireDate, now)) {
this._contentState = Cesium3DTileContentState.EXPIRED;
this._expiredContent = this._content;
}
}
};

function updateExpireDate(tile) {
if (defined(tile.expireDuration)) {
var expireDurationDate = JulianDate.now(scratchJulianDate);
JulianDate.addSeconds(expireDurationDate, tile.expireDuration, expireDurationDate);

if (defined(tile.expireDate)) {
if (JulianDate.lessThan(tile.expireDate, expireDurationDate)) {
JulianDate.clone(expireDurationDate, tile.expireDate);
}
} else {
tile.expireDate = JulianDate.clone(expireDurationDate);
}
}
}

/**
* Requests the tile's content.
* <p>
Expand All @@ -506,9 +580,16 @@ define([
return false;
}

var url = this._contentUrl;
if (defined(this.expireDate)) {
// Append a query parameter of the tile expiration date to prevent caching
var timestampQuery = '?expired=' + this.expireDate.toString();
url = joinUrls(url, timestampQuery, false);
}

var distance = this.distanceToCamera;
var promise = RequestScheduler.schedule(new Request({
url : this._contentUrl,
url : url,
server : this._requestServer,
requestFunction : loadArrayBuffer,
type : RequestType.TILES3D,
Expand All @@ -525,7 +606,8 @@ define([

promise.then(function(arrayBuffer) {
if (that.isDestroyed()) {
return when.reject('tileset is destroyed');
// Tile is unloaded before the content finishes loading
return when.reject('tile is destroyed');
}
var uint8Array = new Uint8Array(arrayBuffer);
var magic = getMagic(uint8Array);
Expand All @@ -546,6 +628,16 @@ define([
that._contentReadyToProcessPromise.resolve(content);

content.readyPromise.then(function(content) {
if (that.isDestroyed()) {
// Tile is unloaded before the content finishes processing
return when.reject('tile is destroyed');
}
updateExpireDate(that);

// Refresh style for expired content
that.lastSelectedFrameNumber = 0;
that.lastStyleTime = 0;

that._contentState = Cesium3DTileContentState.READY;
that._contentReadyPromise.resolve(content);
}).otherwise(function(error) {
Expand Down Expand Up @@ -595,10 +687,6 @@ define([

this.replacementNode = undefined;

// Restore properties set per frame to their defaults
this.distanceToCamera = 0;
this.visibilityPlaneMask = 0;
this.selected = false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These aren't needed to be reset anymore since they are always updated right away in the traversal.

this.lastSelectedFrameNumber = 0;
this.lastStyleTime = 0;

Expand Down Expand Up @@ -786,9 +874,8 @@ define([
Cesium3DTile.prototype.updateTransform = function(parentTransform) {
parentTransform = defaultValue(parentTransform, Matrix4.IDENTITY);
var computedTransform = Matrix4.multiply(parentTransform, this.transform, scratchTransform);
var transformDirty = !Matrix4.equals(computedTransform, this.computedTransform);
if (transformDirty) {
this._transformDirty = true;
var transformChanged = !Matrix4.equals(computedTransform, this.computedTransform);
if (transformChanged) {
Matrix4.clone(computedTransform, this.computedTransform);

// Update the bounding volumes
Expand Down Expand Up @@ -851,6 +938,20 @@ define([
}
}

function updateContent(tile, tileset, frameState) {
var content = tile._content;
var expiredContent = tile._expiredContent;

if (defined(expiredContent) && !tile.contentReady) {
// Render the expired content while the content loads
expiredContent.update(tileset, frameState);
return;
}

tile._expiredContent = tile._expiredContent && tile._expiredContent.destroy();
content.update(tileset, frameState);
}

/**
* Get the draw commands needed to render this tile.
*
Expand All @@ -859,8 +960,7 @@ define([
Cesium3DTile.prototype.update = function(tileset, frameState) {
var initCommandLength = frameState.commandList.length;
applyDebugSettings(this, tileset, frameState);
this._content.update(tileset, frameState);
this._transformDirty = false;
updateContent(this, tileset, frameState);
this._commandsLength = frameState.commandList.length - initCommandLength;
};

Expand Down Expand Up @@ -895,7 +995,9 @@ define([
* @private
*/
Cesium3DTile.prototype.destroy = function() {
// For the interval between new content being requested and downloaded, expiredContent === content, so don't destroy twice
this._content = this._content && this._content.destroy();
this._expiredContent = this._expiredContent && !this._expiredContent.isDestroyed() && this._expiredContent.destroy();
this._debugBoundingVolume = this._debugBoundingVolume && this._debugBoundingVolume.destroy();
this._debugContentBoundingVolume = this._debugContentBoundingVolume && this._debugContentBoundingVolume.destroy();
this._debugViewerRequestVolume = this._debugViewerRequestVolume && this._debugViewerRequestVolume.destroy();
Expand Down
5 changes: 3 additions & 2 deletions Source/Scene/Cesium3DTileContentState.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ define([
LOADING : 1, // Is waiting on a pending request
PROCESSING : 2, // Request received. Contents are being processed for rendering. Depending on the content, it might make its own requests for external data.
READY : 3, // Ready to render.
FAILED : 4 // Request failed.
EXPIRED : 4, // Is expired and will be unloaded once new content is loaded.
FAILED : 5 // Request failed.
};

return freezeObject(Cesium3DTileContentState);
});
});
Loading