From 3a4f1407a25c9dad3e4aa4f942682df83a77c631 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Fri, 20 Dec 2024 11:11:31 -0500 Subject: [PATCH 01/13] add tile load and unload events --- .../engine/Source/Scene/VoxelPrimitive.js | 43 +++++++++++++++++++ .../engine/Source/Scene/VoxelTraversal.js | 2 + 2 files changed, 45 insertions(+) diff --git a/packages/engine/Source/Scene/VoxelPrimitive.js b/packages/engine/Source/Scene/VoxelPrimitive.js index 0942fbb817a4..fae01a097617 100644 --- a/packages/engine/Source/Scene/VoxelPrimitive.js +++ b/packages/engine/Source/Scene/VoxelPrimitive.js @@ -450,6 +450,49 @@ function VoxelPrimitive(options) { } } + /** + * The event fired to indicate that a tile's content was loaded. + *

+ * The loaded tile is passed to the event listener. + *

+ *

+ * This event is fired during the tileset traversal while the frame is being rendered + * so that updates to the tile take effect in the same frame. Do not create or modify + * Cesium entities or primitives during the event listener. + *

+ * + * @type {Event} + * @default new Event() + * + * @example + * voxelPrimitive.tileLoad.addEventListener(function(tile) { + * console.log('A tile was loaded.'); + * }); + */ + this.tileLoad = new Event(); + + /** + * The event fired to indicate that a tile's content was unloaded. + *

+ * The unloaded {@link Cesium3DTile} is passed to the event listener. + *

+ *

+ * 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. + *

+ * + * @type {Event} + * @default new Event() + * + * @example + * primitive.tileUnload.addEventListener(function(tile) { + * console.log('A tile was unloaded from the cache.'); + * }); + * + */ + this.tileUnload = new Event(); + // If the provider fails to initialize the primitive will fail too. const provider = this._provider; initialize(this, provider); diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index b7e02d06f1dd..f0922ffadfd9 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -422,6 +422,7 @@ function requestData(that, keyframeNode) { function postRequestSuccess(result) { that._simultaneousRequestCount--; const length = provider.types.length; + that._primitive.tileLoad.raiseEvent(); if (!defined(result)) { keyframeNode.state = KeyframeNode.LoadState.UNAVAILABLE; @@ -645,6 +646,7 @@ function loadAndUnload(that, frameState) { destroyedCount++; const discardNode = keyframeNodesInMegatexture[addNodeIndex]; + that._primitive.tileUnload.raiseEvent(); discardNode.spatialNode.destroyKeyframeNode( discardNode, that.megatextures, From f36ad9cc3dc989c4a981dba5b43290973b6f9018 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Fri, 10 Jan 2025 15:31:44 -0500 Subject: [PATCH 02/13] add load progress event --- .../engine/Source/Scene/VoxelPrimitive.js | 34 +++++++++++++++++++ .../engine/Source/Scene/VoxelTraversal.js | 27 +++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/packages/engine/Source/Scene/VoxelPrimitive.js b/packages/engine/Source/Scene/VoxelPrimitive.js index fae01a097617..5b82e9783ef4 100644 --- a/packages/engine/Source/Scene/VoxelPrimitive.js +++ b/packages/engine/Source/Scene/VoxelPrimitive.js @@ -25,6 +25,7 @@ import CustomShader from "./Model/CustomShader.js"; import Cartographic from "../Core/Cartographic.js"; import Ellipsoid from "../Core/Ellipsoid.js"; import VerticalExaggeration from "../Core/VerticalExaggeration.js"; +import Cesium3DTilesetStatistics from "./Cesium3DTilesetStatistics.js"; /** * A primitive that renders voxel data from a {@link VoxelProvider}. @@ -70,6 +71,12 @@ function VoxelPrimitive(options) { */ this._traversal = undefined; + /** + * @type {Cesium3DTilesetStatistics} + * @private + */ + this._statistics = new Cesium3DTilesetStatistics(); + /** * This member is not created until the provider is ready. * @@ -493,6 +500,33 @@ function VoxelPrimitive(options) { */ this.tileUnload = new Event(); + /** + * The event fired to indicate progress of loading new tiles. This event is fired when a new tile + * is requested, when a requested tile is finished downloading, and when a downloaded tile has been + * processed and is ready to render. + *

+ * The number of pending tile requests, numberOfPendingRequests, and number of tiles + * processing, numberOfTilesProcessing are passed to the event listener. + *

+ *

+ * This event is fired at the end of the frame after the scene is rendered. + *

+ * + * @type {Event} + * @default new Event() + * + * @example + * tileset.loadProgress.addEventListener(function(numberOfPendingRequests, numberOfTilesProcessing) { + * if ((numberOfPendingRequests === 0) && (numberOfTilesProcessing === 0)) { + * console.log('Stopped loading'); + * return; + * } + * + * console.log(`Loading: requests: ${numberOfPendingRequests}, processing: ${numberOfTilesProcessing}`); + * }); + */ + this.loadProgress = new Event(); + // If the provider fails to initialize the primitive will fail too. const provider = this._provider; initialize(this, provider); diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index f0922ffadfd9..add58650ef07 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -323,6 +323,7 @@ VoxelTraversal.prototype.update = function ( const totalTimeMs = timestamp2 - timestamp0; printDebugInformation( this, + frameState, loadAndUnloadTimeMs, generateOctreeTimeMs, totalTimeMs, @@ -707,6 +708,7 @@ function keyframePriority(previousKeyframe, keyframe, nextKeyframe, traversal) { */ function printDebugInformation( that, + frameState, loadAndUnloadTimeMs, generateOctreeTimeMs, totalTimeMs, @@ -760,6 +762,31 @@ function printDebugInformation( } traverseRecursive(rootNode); + const numberOfPendingRequests = + loadStateByCount[KeyframeNode.LoadState.RECEIVING]; + const numberOfTilesProcessing = + loadStateByCount[KeyframeNode.LoadState.RECEIVED]; + + const progressChanged = + numberOfPendingRequests !== + that._primitive._statistics.numberOfPendingRequests || + numberOfTilesProcessing !== + that._primitive._statistics.numberOfTilesProcessing; + + if (progressChanged) { + frameState.afterRender.push(function () { + that._primitive.loadProgress.raiseEvent( + numberOfPendingRequests, + numberOfTilesProcessing, + ); + + return true; + }); + } + + that._primitive._statistics.numberOfPendingRequests = numberOfPendingRequests; + that._primitive._statistics.numberOfTilesProcessing = numberOfTilesProcessing; + const loadedKeyframeStatistics = `KEYFRAMES: ${ loadStatesByKeyframe[KeyframeNode.LoadState.LOADED] }`; From ceaf56c4d29d918677c1ccf0318c1a621e2167b4 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Fri, 10 Jan 2025 16:05:20 -0500 Subject: [PATCH 03/13] add all tiles loaded and initial tiles loaded events --- .../engine/Source/Scene/VoxelPrimitive.js | 36 +++++++++++++++++++ .../engine/Source/Scene/VoxelTraversal.js | 27 +++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/VoxelPrimitive.js b/packages/engine/Source/Scene/VoxelPrimitive.js index 5b82e9783ef4..34028327fc9f 100644 --- a/packages/engine/Source/Scene/VoxelPrimitive.js +++ b/packages/engine/Source/Scene/VoxelPrimitive.js @@ -527,6 +527,42 @@ function VoxelPrimitive(options) { */ this.loadProgress = new Event(); + /** + * The event fired to indicate that all tiles that meet the screen space error this frame are loaded. The voxel + * primitive is completely loaded for this view. + *

+ * This event is fired at the end of the frame after the scene is rendered. + *

+ * + * @type {Event} + * @default new Event() + * + * @example + * tileset.allTilesLoaded.addEventListener(function() { + * console.log('All tiles are loaded'); + * }); + */ + this.allTilesLoaded = new Event(); + + /** + * The event fired to indicate that all tiles that meet the screen space error this frame are loaded. This event + * is fired once when all tiles in the initial view are loaded. + *

+ * This event is fired at the end of the frame after the scene is rendered. + *

+ * + * @type {Event} + * @default new Event() + * + * @example + * tileset.initialTilesLoaded.addEventListener(function() { + * console.log('Initial tiles are loaded'); + * }); + * + * @see Cesium3DTileset#allTilesLoaded + */ + this.initialTilesLoaded = new Event(); + // If the provider fails to initialize the primitive will fail too. const provider = this._provider; initialize(this, provider); diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index add58650ef07..8bb57b65104b 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -143,6 +143,12 @@ function VoxelTraversal( */ this._binaryTreeKeyframeWeighting = new Array(keyframeCount); + /** + * @type {boolean} + * @private + */ + this._initialTilesLoaded = false; + const binaryTreeKeyframeWeighting = this._binaryTreeKeyframeWeighting; binaryTreeKeyframeWeighting[0] = 0; binaryTreeKeyframeWeighting[keyframeCount - 1] = 0; @@ -316,7 +322,6 @@ VoxelTraversal.prototype.update = function ( const timestamp1 = getTimestamp(); generateOctree(this, sampleCount, levelBlendFactor); const timestamp2 = getTimestamp(); - if (this._debugPrint) { const loadAndUnloadTimeMs = timestamp1 - timestamp0; const generateOctreeTimeMs = timestamp2 - timestamp1; @@ -787,6 +792,26 @@ function printDebugInformation( that._primitive._statistics.numberOfPendingRequests = numberOfPendingRequests; that._primitive._statistics.numberOfTilesProcessing = numberOfTilesProcessing; + const tilesLoaded = + numberOfPendingRequests === 0 && numberOfTilesProcessing === 0; + + // 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 readyEvent + if (progressChanged && tilesLoaded) { + frameState.afterRender.push(function () { + that._primitive.allTilesLoaded.raiseEvent(); + return true; + }); + if (!that._initialTilesLoaded) { + that._initialTilesLoaded = true; + frameState.afterRender.push(function () { + that._primitive.initialTilesLoaded.raiseEvent(); + return true; + }); + } + } + const loadedKeyframeStatistics = `KEYFRAMES: ${ loadStatesByKeyframe[KeyframeNode.LoadState.LOADED] }`; From bfe4c110221fe6a0ae75bfa3c2b7a8d66c19fc7d Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Tue, 14 Jan 2025 16:01:56 -0500 Subject: [PATCH 04/13] add tile failed event --- .../engine/Source/Scene/VoxelPrimitive.js | 26 ++++++++++++ .../engine/Source/Scene/VoxelTraversal.js | 42 ++++++++++++++----- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/packages/engine/Source/Scene/VoxelPrimitive.js b/packages/engine/Source/Scene/VoxelPrimitive.js index 34028327fc9f..ad825eaf5989 100644 --- a/packages/engine/Source/Scene/VoxelPrimitive.js +++ b/packages/engine/Source/Scene/VoxelPrimitive.js @@ -478,6 +478,32 @@ function VoxelPrimitive(options) { */ this.tileLoad = new Event(); + /** + * The event fired to indicate that a tile's content failed to load. + *

+ * If there are no event listeners, error messages will be logged to the console. + *

+ *

+ * The error object passed to the listener contains two properties: + *

    + *
  • url: the url of the failed tile.
  • + *
  • message: the error message.
  • + *
+ *

+ * If multiple contents are present, this event is raised once per inner content with errors. + *

+ * + * @type {Event} + * @default new Event() + * + * @example + * tileset.tileFailed.addEventListener(function(error) { + * console.log(`An error occurred loading tile: ${error.url}`); + * console.log(`Error: ${error.message}`); + * }); + */ + this.tileFailed = new Event(); + /** * The event fired to indicate that a tile's content was unloaded. *

diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index 8bb57b65104b..6835e1811b7d 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -424,6 +424,18 @@ function requestData(that, keyframeNode) { } const provider = that._primitive._provider; + const { keyframe, spatialNode } = keyframeNode; + if (spatialNode.level >= provider._implicitTileset.availableLevels) { + return; + } + + const requestOptions = { + tileLevel: spatialNode.level, + tileX: spatialNode.x, + tileY: spatialNode.y, + tileZ: spatialNode.z, + keyframe: keyframe, + }; function postRequestSuccess(result) { that._simultaneousRequestCount--; @@ -458,24 +470,34 @@ function requestData(that, keyframeNode) { } } - function postRequestFailure() { + function postRequestFailure(options) { + const { requestOptions, error } = options; + const message = defined(error.message) ? error.message : error.toString(); + const url = `${requestOptions.tileLevel}/${requestOptions.tileX}/${requestOptions.tileY}/${requestOptions.tileZ}`; that._simultaneousRequestCount--; + if (that._primitive.tileFailed.numberOfListeners > 0) { + that._primitive.tileFailed.raiseEvent({ + url: url, + message: message, + }); + } else { + console.log(`A 3D tile failed to load: ${url}`); + console.log(`Error: ${message}`); + if (defined(error.stack)) { + console.log(error.stack); + } + } keyframeNode.state = KeyframeNode.LoadState.FAILED; } - const { keyframe, spatialNode } = keyframeNode; - const promise = provider.requestData({ - tileLevel: spatialNode.level, - tileX: spatialNode.x, - tileY: spatialNode.y, - tileZ: spatialNode.z, - keyframe: keyframe, - }); + const promise = provider.requestData(requestOptions); if (defined(promise)) { that._simultaneousRequestCount++; keyframeNode.state = KeyframeNode.LoadState.RECEIVING; - promise.then(postRequestSuccess).catch(postRequestFailure); + promise + .then(postRequestSuccess) + .catch((error) => postRequestFailure({ requestOptions, error })); } else { keyframeNode.state = KeyframeNode.LoadState.FAILED; } From fcd9a9c0ac5bcec4bfef3fa553607f19de914803 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Wed, 15 Jan 2025 16:37:51 -0500 Subject: [PATCH 05/13] add tile visible event --- packages/engine/Source/Scene/VoxelPrimitive.js | 16 ++++++++++++++++ packages/engine/Source/Scene/VoxelTraversal.js | 1 + 2 files changed, 17 insertions(+) diff --git a/packages/engine/Source/Scene/VoxelPrimitive.js b/packages/engine/Source/Scene/VoxelPrimitive.js index ad825eaf5989..54c9f529bf18 100644 --- a/packages/engine/Source/Scene/VoxelPrimitive.js +++ b/packages/engine/Source/Scene/VoxelPrimitive.js @@ -478,6 +478,22 @@ function VoxelPrimitive(options) { */ this.tileLoad = new Event(); + /** + * This event fires once for each visible tile in a frame. + *

+ * This event is fired during the traversal while the frame is being rendered. + * + * @type {Event} + * @default new Event() + * + * @example + * tileset.tileVisible.addEventListener(function() { + * console.log('A tile is visible.'); + * }); + * + */ + this.tileVisible = new Event(); + /** * The event fired to indicate that a tile's content failed to load. *

diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index 6835e1811b7d..91260dbf544d 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -968,6 +968,7 @@ function generateOctree(that, sampleCount, levelBlendFactor) { } else { // Store the leaf node information instead // Recursion stops here because there are no renderable children + that._primitive.tileVisible.raiseEvent(); if (useLeafNodes) { const baseIdx = leafNodeCount * 5; const keyframeNode = node.renderableKeyframeNodePrevious; From 61f4771d8a9ff49ab8cb3b566180c4bafb34f340 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Thu, 16 Jan 2025 14:35:31 -0500 Subject: [PATCH 06/13] reorganize post passes function --- packages/engine/Source/Scene/VoxelTraversal.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index 91260dbf544d..ced91c2b63d6 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -322,11 +322,15 @@ VoxelTraversal.prototype.update = function ( const timestamp1 = getTimestamp(); generateOctree(this, sampleCount, levelBlendFactor); const timestamp2 = getTimestamp(); - if (this._debugPrint) { + const checkEventListeners = + primitive.loadProgress.numberOfListeners > 0 || + primitive.allTilesLoaded.numberOfListeners > 0 || + primitive.initialTilesLoaded.numberOfListeners > 0; + if (this._debugPrint || checkEventListeners) { const loadAndUnloadTimeMs = timestamp1 - timestamp0; const generateOctreeTimeMs = timestamp2 - timestamp1; const totalTimeMs = timestamp2 - timestamp0; - printDebugInformation( + postPassesUpdate( this, frameState, loadAndUnloadTimeMs, @@ -733,7 +737,7 @@ function keyframePriority(previousKeyframe, keyframe, nextKeyframe, traversal) { * * @private */ -function printDebugInformation( +function postPassesUpdate( that, frameState, loadAndUnloadTimeMs, @@ -834,6 +838,10 @@ function printDebugInformation( } } + if (!that._debugPrint) { + return; + } + const loadedKeyframeStatistics = `KEYFRAMES: ${ loadStatesByKeyframe[KeyframeNode.LoadState.LOADED] }`; From 335414d69db93eb649edecdcfac388f82dbdab85 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Thu, 16 Jan 2025 15:21:30 -0500 Subject: [PATCH 07/13] add spec for initial and all tiles loaded --- packages/engine/Specs/Scene/VoxelPrimitiveSpec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js b/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js index 3c55516d198b..bb539bd5664b 100644 --- a/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js +++ b/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js @@ -58,6 +58,21 @@ describe( expect(primitive.maximumValues).toBe(provider.maximumValues); }); + it("initial tiles loaded and all tiles loaded events are raised", async function () { + const spyUpdate1 = jasmine.createSpy("listener"); + const spyUpdate2 = jasmine.createSpy("listener"); + const primitive = new VoxelPrimitive({ provider }); + primitive.allTilesLoaded.addEventListener(spyUpdate1); + primitive.initialTilesLoaded.addEventListener(spyUpdate2); + scene.primitives.add(primitive); + await pollToPromise(() => { + scene.renderForSpecs(); + return primitive._traversal._initialTilesLoaded; + }); + expect(spyUpdate1.calls.count()).toEqual(1); + expect(spyUpdate2.calls.count()).toEqual(1); + }); + it("toggles render options that require shader rebuilds", async function () { const primitive = new VoxelPrimitive({ provider }); scene.primitives.add(primitive); From 156d050de918461b39c7ac4a26c4f8d929e855e9 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Fri, 17 Jan 2025 13:26:18 -0500 Subject: [PATCH 08/13] add spec for tile load and load progress events --- packages/engine/Specs/Scene/VoxelPrimitiveSpec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js b/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js index bb539bd5664b..e38b5907e039 100644 --- a/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js +++ b/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js @@ -73,6 +73,21 @@ describe( expect(spyUpdate2.calls.count()).toEqual(1); }); + it("tile load and load progress events are raised", async function () { + const spyUpdate1 = jasmine.createSpy("listener"); + const spyUpdate2 = jasmine.createSpy("listener"); + const primitive = new VoxelPrimitive({ provider }); + primitive.tileLoad.addEventListener(spyUpdate1); + primitive.loadProgress.addEventListener(spyUpdate2); + scene.primitives.add(primitive); + await pollToPromise(() => { + scene.renderForSpecs(); + return primitive._traversal._initialTilesLoaded; + }); + expect(spyUpdate1.calls.count()).toEqual(1); + expect(spyUpdate2.calls.count()).toBeGreaterThan(0); + }); + it("toggles render options that require shader rebuilds", async function () { const primitive = new VoxelPrimitive({ provider }); scene.primitives.add(primitive); From 3c79fadb8b1bf10b11f008c180dd6ec7db723bce Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Fri, 17 Jan 2025 16:16:39 -0500 Subject: [PATCH 09/13] add tile failed spec --- .../engine/Source/Scene/VoxelTraversal.js | 28 ++++++------------- .../engine/Specs/Scene/VoxelTraversalSpec.js | 26 ++++++++++++++++- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/packages/engine/Source/Scene/VoxelTraversal.js b/packages/engine/Source/Scene/VoxelTraversal.js index ced91c2b63d6..b5ba12db6064 100644 --- a/packages/engine/Source/Scene/VoxelTraversal.js +++ b/packages/engine/Source/Scene/VoxelTraversal.js @@ -444,7 +444,6 @@ function requestData(that, keyframeNode) { function postRequestSuccess(result) { that._simultaneousRequestCount--; const length = provider.types.length; - that._primitive.tileLoad.raiseEvent(); if (!defined(result)) { keyframeNode.state = KeyframeNode.LoadState.UNAVAILABLE; @@ -466,32 +465,22 @@ function requestData(that, keyframeNode) { keyframeNode.metadata[i] = data; // State is received only when all metadata requests have been received keyframeNode.state = KeyframeNode.LoadState.RECEIVED; + that._primitive.tileLoad.raiseEvent(); } else { keyframeNode.state = KeyframeNode.LoadState.FAILED; break; } } } + if (keyframeNode.state === KeyframeNode.LoadState.FAILED) { + that._primitive.tileFailed.raiseEvent(); + } } - function postRequestFailure(options) { - const { requestOptions, error } = options; - const message = defined(error.message) ? error.message : error.toString(); - const url = `${requestOptions.tileLevel}/${requestOptions.tileX}/${requestOptions.tileY}/${requestOptions.tileZ}`; + function postRequestFailure() { that._simultaneousRequestCount--; - if (that._primitive.tileFailed.numberOfListeners > 0) { - that._primitive.tileFailed.raiseEvent({ - url: url, - message: message, - }); - } else { - console.log(`A 3D tile failed to load: ${url}`); - console.log(`Error: ${message}`); - if (defined(error.stack)) { - console.log(error.stack); - } - } keyframeNode.state = KeyframeNode.LoadState.FAILED; + that._primitive.tileFailed.raiseEvent(); } const promise = provider.requestData(requestOptions); @@ -499,11 +488,10 @@ function requestData(that, keyframeNode) { if (defined(promise)) { that._simultaneousRequestCount++; keyframeNode.state = KeyframeNode.LoadState.RECEIVING; - promise - .then(postRequestSuccess) - .catch((error) => postRequestFailure({ requestOptions, error })); + promise.then(postRequestSuccess).catch(postRequestFailure); } else { keyframeNode.state = KeyframeNode.LoadState.FAILED; + that._primitive.tileFailed.raiseEvent(); } } diff --git a/packages/engine/Specs/Scene/VoxelTraversalSpec.js b/packages/engine/Specs/Scene/VoxelTraversalSpec.js index c70e0dc1b73c..233b5981ec20 100644 --- a/packages/engine/Specs/Scene/VoxelTraversalSpec.js +++ b/packages/engine/Specs/Scene/VoxelTraversalSpec.js @@ -166,6 +166,30 @@ describe( expect(megatexture.occupiedCount).toBe(1); }); + it("tile failed event is raised", async function () { + const keyFrameLocation = 0; + const recomputeBoundingVolumes = true; + const pauseUpdate = false; + const spyFailed = jasmine.createSpy("listener"); + traversal._primitive.tileFailed.addEventListener(spyFailed); + spyOn(traversal._primitive._provider, "requestData").and.callFake(() => { + return Promise.reject(); + }); + let counter = 0; + const target = 3; + await pollToPromise(function () { + traversal.update( + scene.frameState, + keyFrameLocation, + recomputeBoundingVolumes, + pauseUpdate, + ); + counter++; + return counter === target; + }); + expect(spyFailed.calls.count()).toBeGreaterThan(1); + }); + it("finds keyframe node with expected metadata values", async function () { const keyFrameLocation = 0; const recomputeBoundingVolumes = true; @@ -203,7 +227,7 @@ describe( pauseUpdate, ); } - for (let i = 0; i < 10; i++) { + for (let i = 0; i < 15; i++) { updateTraversal(); } } From 5f9a85a1da5d124b84979b4597ba8f9ff3090fef Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Fri, 17 Jan 2025 16:41:41 -0500 Subject: [PATCH 10/13] update changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 5c745014715f..80837d334164 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ - Expanded integration with the [iTwin Platform](https://developer.bentley.com/) to load GeoJSON and KML data from the Reality Management API. Use `ITwinData.createDataSourceForRealityDataId` to load data as either GeoJSON or KML`. [#12344](https://github.com/CesiumGS/cesium/pull/12344) - Added `environmentMapOptions` to `ModelGraphics`. For performance reasons by default, the environment map will not update if the entity position change. If environment map updates based on entity position are desired, provide an appropriate `environmentMapOptions.maximumPositionEpsilon` value. [#12358](https://github.com/CesiumGS/cesium/pull/12358) +- Added events to `VoxelPrimitive` to match `Cesium3DTileset`, including `allTilesLoaded`, `initialTilesLoaded`, `loadProgress`, `tileFailed`, `tileLoad`, `tileVisible`, `tileUnload`. ##### Fixes :wrench: From 2b38dbbe249bb6e8d649bdae9612042030de86da Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Tue, 21 Jan 2025 10:14:52 -0500 Subject: [PATCH 11/13] add tile visible to specs --- packages/engine/Specs/Scene/VoxelPrimitiveSpec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js b/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js index e38b5907e039..bdd25a24c209 100644 --- a/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js +++ b/packages/engine/Specs/Scene/VoxelPrimitiveSpec.js @@ -73,12 +73,14 @@ describe( expect(spyUpdate2.calls.count()).toEqual(1); }); - it("tile load and load progress events are raised", async function () { + it("tile load, load progress and tile visible events are raised", async function () { const spyUpdate1 = jasmine.createSpy("listener"); const spyUpdate2 = jasmine.createSpy("listener"); + const spyUpdate3 = jasmine.createSpy("listener"); const primitive = new VoxelPrimitive({ provider }); primitive.tileLoad.addEventListener(spyUpdate1); primitive.loadProgress.addEventListener(spyUpdate2); + primitive.tileVisible.addEventListener(spyUpdate3); scene.primitives.add(primitive); await pollToPromise(() => { scene.renderForSpecs(); @@ -86,6 +88,7 @@ describe( }); expect(spyUpdate1.calls.count()).toEqual(1); expect(spyUpdate2.calls.count()).toBeGreaterThan(0); + expect(spyUpdate3.calls.count()).toEqual(1); }); it("toggles render options that require shader rebuilds", async function () { From f589fb028e55c9a103776a848f1653b79a2d5416 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Tue, 21 Jan 2025 10:53:59 -0500 Subject: [PATCH 12/13] fixup docs --- .../engine/Source/Scene/VoxelPrimitive.js | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/packages/engine/Source/Scene/VoxelPrimitive.js b/packages/engine/Source/Scene/VoxelPrimitive.js index 54c9f529bf18..fe52ee7e6a9d 100644 --- a/packages/engine/Source/Scene/VoxelPrimitive.js +++ b/packages/engine/Source/Scene/VoxelPrimitive.js @@ -472,7 +472,7 @@ function VoxelPrimitive(options) { * @default new Event() * * @example - * voxelPrimitive.tileLoad.addEventListener(function(tile) { + * voxelPrimitive.tileLoad.addEventListener(function() { * console.log('A tile was loaded.'); * }); */ @@ -487,7 +487,7 @@ function VoxelPrimitive(options) { * @default new Event() * * @example - * tileset.tileVisible.addEventListener(function() { + * voxelPrimitive.tileVisible.addEventListener(function() { * console.log('A tile is visible.'); * }); * @@ -499,43 +499,25 @@ function VoxelPrimitive(options) { *

* If there are no event listeners, error messages will be logged to the console. *

- *

- * The error object passed to the listener contains two properties: - *

    - *
  • url: the url of the failed tile.
  • - *
  • message: the error message.
  • - *
- *

- * If multiple contents are present, this event is raised once per inner content with errors. - *

* * @type {Event} * @default new Event() * * @example - * tileset.tileFailed.addEventListener(function(error) { - * console.log(`An error occurred loading tile: ${error.url}`); - * console.log(`Error: ${error.message}`); + * voxelPrimitive.tileFailed.addEventListener(function() { + * console.log('An error occurred loading tile.'); * }); */ this.tileFailed = new Event(); /** * The event fired to indicate that a tile's content was unloaded. - *

- * The unloaded {@link Cesium3DTile} is passed to the event listener. - *

- *

- * 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. - *

* * @type {Event} * @default new Event() * * @example - * primitive.tileUnload.addEventListener(function(tile) { + * voxelPrimitive.tileUnload.addEventListener(function() { * console.log('A tile was unloaded from the cache.'); * }); * @@ -543,7 +525,7 @@ function VoxelPrimitive(options) { this.tileUnload = new Event(); /** - * The event fired to indicate progress of loading new tiles. This event is fired when a new tile + * The event fired to indicate progress of loading new tiles. This event is fired when a new tile * is requested, when a requested tile is finished downloading, and when a downloaded tile has been * processed and is ready to render. *

@@ -558,7 +540,7 @@ function VoxelPrimitive(options) { * @default new Event() * * @example - * tileset.loadProgress.addEventListener(function(numberOfPendingRequests, numberOfTilesProcessing) { + * voxelPrimitive.loadProgress.addEventListener(function(numberOfPendingRequests, numberOfTilesProcessing) { * if ((numberOfPendingRequests === 0) && (numberOfTilesProcessing === 0)) { * console.log('Stopped loading'); * return; @@ -580,7 +562,7 @@ function VoxelPrimitive(options) { * @default new Event() * * @example - * tileset.allTilesLoaded.addEventListener(function() { + * voxelPrimitive.allTilesLoaded.addEventListener(function() { * console.log('All tiles are loaded'); * }); */ @@ -597,7 +579,7 @@ function VoxelPrimitive(options) { * @default new Event() * * @example - * tileset.initialTilesLoaded.addEventListener(function() { + * voxelPrimitive.initialTilesLoaded.addEventListener(function() { * console.log('Initial tiles are loaded'); * }); * From f0c014968435157c3c3bca503ba32b52bf21b8e9 Mon Sep 17 00:00:00 2001 From: lukemckinstry Date: Fri, 24 Jan 2025 11:56:15 -0500 Subject: [PATCH 13/13] doc updates from pr feedback --- packages/engine/Source/Scene/VoxelPrimitive.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/engine/Source/Scene/VoxelPrimitive.js b/packages/engine/Source/Scene/VoxelPrimitive.js index fe52ee7e6a9d..3445556d9736 100644 --- a/packages/engine/Source/Scene/VoxelPrimitive.js +++ b/packages/engine/Source/Scene/VoxelPrimitive.js @@ -460,16 +460,12 @@ function VoxelPrimitive(options) { /** * The event fired to indicate that a tile's content was loaded. *

- * The loaded tile is passed to the event listener. - *

- *

* This event is fired during the tileset traversal while the frame is being rendered * so that updates to the tile take effect in the same frame. Do not create or modify * Cesium entities or primitives during the event listener. *

* * @type {Event} - * @default new Event() * * @example * voxelPrimitive.tileLoad.addEventListener(function() { @@ -484,7 +480,6 @@ function VoxelPrimitive(options) { * This event is fired during the traversal while the frame is being rendered. * * @type {Event} - * @default new Event() * * @example * voxelPrimitive.tileVisible.addEventListener(function() { @@ -496,12 +491,8 @@ function VoxelPrimitive(options) { /** * The event fired to indicate that a tile's content failed to load. - *

- * If there are no event listeners, error messages will be logged to the console. - *

* * @type {Event} - * @default new Event() * * @example * voxelPrimitive.tileFailed.addEventListener(function() { @@ -514,7 +505,6 @@ function VoxelPrimitive(options) { * The event fired to indicate that a tile's content was unloaded. * * @type {Event} - * @default new Event() * * @example * voxelPrimitive.tileUnload.addEventListener(function() { @@ -537,12 +527,11 @@ function VoxelPrimitive(options) { *

* * @type {Event} - * @default new Event() * * @example * voxelPrimitive.loadProgress.addEventListener(function(numberOfPendingRequests, numberOfTilesProcessing) { * if ((numberOfPendingRequests === 0) && (numberOfTilesProcessing === 0)) { - * console.log('Stopped loading'); + * console.log('Finished loading'); * return; * } * @@ -559,7 +548,6 @@ function VoxelPrimitive(options) { *

* * @type {Event} - * @default new Event() * * @example * voxelPrimitive.allTilesLoaded.addEventListener(function() { @@ -576,7 +564,6 @@ function VoxelPrimitive(options) { *

* * @type {Event} - * @default new Event() * * @example * voxelPrimitive.initialTilesLoaded.addEventListener(function() {