-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Changes from all commits
6aaff1b
5fb5f91
63b899d
18e641b
a46ca4c
d6a1463
11bede6
b5c719d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ define([ | |
'../Core/getStringFromTypedArray', | ||
'../Core/Intersect', | ||
'../Core/joinUrls', | ||
'../Core/JulianDate', | ||
'../Core/loadArrayBuffer', | ||
'../Core/Matrix3', | ||
'../Core/Matrix4', | ||
|
@@ -60,6 +61,7 @@ define([ | |
getStringFromTypedArray, | ||
Intersect, | ||
joinUrls, | ||
JulianDate, | ||
loadArrayBuffer, | ||
Matrix3, | ||
Matrix4, | ||
|
@@ -118,8 +120,6 @@ define([ | |
*/ | ||
this.computedTransform = computedTransform; | ||
|
||
this._transformDirty = true; | ||
|
||
this._boundingVolume = this.createBoundingVolume(header.boundingVolume, computedTransform); | ||
this._boundingVolume2D = undefined; | ||
|
||
|
@@ -212,6 +212,7 @@ define([ | |
this._contentState = contentState; | ||
this._contentReadyToProcessPromise = undefined; | ||
this._contentReadyPromise = undefined; | ||
this._expiredContent = undefined; | ||
|
||
this._requestServer = requestServer; | ||
|
||
|
@@ -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: | ||
|
||
/** | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} | ||
}, | ||
|
||
|
@@ -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 | ||
|
@@ -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> | ||
|
@@ -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, | ||
|
@@ -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); | ||
|
@@ -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) { | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
||
|
@@ -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 | ||
|
@@ -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. | ||
* | ||
|
@@ -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; | ||
}; | ||
|
||
|
@@ -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(); | ||
|
There was a problem hiding this comment.
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.