From 9bada905cde6a04a9bb863bb9fce2eced70a99d3 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 2 May 2024 16:42:40 -0400 Subject: [PATCH 01/10] Working for non-P3DT datasets --- .../include/Cesium3DTilesSelection/Tile.h | 10 ++++ Cesium3DTilesSelection/src/Tile.cpp | 19 +++++++ Cesium3DTilesSelection/src/Tileset.cpp | 1 - .../src/TilesetContentManager.cpp | 54 ++++++++++++------- 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h index 3071acb29..96cb28b06 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h @@ -475,6 +475,16 @@ class CESIUM3DTILESSELECTION_API Tile final { */ bool isEmptyContent() const noexcept; + /** + * @brief Determines if this tile has unknown content. + */ + bool isUnknownContent() const noexcept; + + /** + * Determines if this tile and all of its children are ready to unload. + */ + bool isReadyToUnload() const noexcept; + /** * @brief get the loader that is used to load the tile content. */ diff --git a/Cesium3DTilesSelection/src/Tile.cpp b/Cesium3DTilesSelection/src/Tile.cpp index 932dd5b0d..16af8d8ac 100644 --- a/Cesium3DTilesSelection/src/Tile.cpp +++ b/Cesium3DTilesSelection/src/Tile.cpp @@ -217,6 +217,25 @@ bool Tile::isEmptyContent() const noexcept { return this->_content.isEmptyContent(); } +bool Tile::isUnknownContent() const noexcept { + return this->_content.isUnknownContent(); +} + +bool Tile::isReadyToUnload() const noexcept { + if (this->getState() == TileLoadState::ContentLoading || + this->getState() == TileLoadState::Unloading) { + return false; + } + + for (const Tile& child : this->_children) { + if (!child.isReadyToUnload()) { + return false; + } + } + + return true; +} + TilesetContentLoader* Tile::getLoader() const noexcept { return this->_pLoader; } diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 1ecb0e45f..b49830f53 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -89,7 +89,6 @@ Tileset::Tileset( ionAssetEndpointUrl)} {} Tileset::~Tileset() noexcept { - this->_pTilesetContentManager->unloadAll(); if (this->_externals.pTileOcclusionProxyPool) { this->_externals.pTileOcclusionProxyPool->destroyPool(); } diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index edcbc8c66..07a83eb79 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -76,9 +76,6 @@ void unloadTileRecursively( Tile& tile, TilesetContentManager& tilesetContentManager) { tilesetContentManager.unloadTileContent(tile); - for (Tile& child : tile.getChildren()) { - unloadTileRecursively(child, tilesetContentManager); - } } bool anyRasterOverlaysNeedLoading(const Tile& tile) noexcept { @@ -1037,9 +1034,11 @@ bool TilesetContentManager::unloadTileContent(Tile& tile) { } TileContent& content = tile.getContent(); + const bool isReadyToUnload = tile.isReadyToUnload(); - // don't unload external or empty tile - if (content.isExternalContent() || content.isEmptyContent()) { + // don't unload external or empty tile while they're still loading + if ((content.isExternalContent() || content.isEmptyContent()) && + !isReadyToUnload) { return false; } @@ -1050,18 +1049,20 @@ bool TilesetContentManager::unloadTileContent(Tile& tile) { } tile.getMappedRasterTiles().clear(); - // Unload the renderer resources and clear any raster overlay tiles. We can do - // this even if the tile can't be fully unloaded because this tile's geometry - // is being using by an async upsample operation (checked below). - switch (state) { - case TileLoadState::ContentLoaded: - unloadContentLoadedState(tile); - break; - case TileLoadState::Done: - unloadDoneState(tile); - break; - default: - break; + if (!tile.isEmptyContent() && !tile.isUnknownContent()) { + // Unload the renderer resources and clear any raster overlay tiles. We can + // do this even if the tile can't be fully unloaded because this tile's + // geometry is being using by an async upsample operation (checked below). + switch (state) { + case TileLoadState::ContentLoaded: + unloadContentLoadedState(tile); + break; + case TileLoadState::Done: + unloadDoneState(tile); + break; + default: + break; + } } // Are any children currently being upsampled from this tile? @@ -1078,6 +1079,13 @@ bool TilesetContentManager::unloadTileContent(Tile& tile) { } } + // Make sure we unload all children + if (isReadyToUnload) { + for (Tile& child : tile.getChildren()) { + this->unloadTileContent(child); + } + } + // If we make it this far, the tile's content will be fully unloaded. notifyTileUnloading(&tile); content.setContentKind(TileUnknownContent{}); @@ -1089,7 +1097,7 @@ void TilesetContentManager::unloadAll() { // TODO: use the linked-list of loaded tiles instead of walking the entire // tile tree. if (this->_pRootTile) { - unloadTileRecursively(*this->_pRootTile, *this); + this->unloadTileContent(*this->_pRootTile); } } @@ -1424,7 +1432,10 @@ void TilesetContentManager::updateDoneState( void TilesetContentManager::unloadContentLoadedState(Tile& tile) { TileContent& content = tile.getContent(); TileRenderContent* pRenderContent = content.getRenderContent(); - assert(pRenderContent && "Tile must have render content to be unloaded"); + if (pRenderContent == nullptr) { + // No resources we need to clean up + return; + } void* pWorkerRenderResources = pRenderContent->getRenderResources(); this->_externals.pPrepareRendererResources->free( @@ -1437,7 +1448,10 @@ void TilesetContentManager::unloadContentLoadedState(Tile& tile) { void TilesetContentManager::unloadDoneState(Tile& tile) { TileContent& content = tile.getContent(); TileRenderContent* pRenderContent = content.getRenderContent(); - assert(pRenderContent && "Tile must have render content to be unloaded"); + if (pRenderContent == nullptr) { + // No resources to clean up + return; + } void* pMainThreadRenderResources = pRenderContent->getRenderResources(); this->_externals.pPrepareRendererResources->free( From 24ef6aad99184aef14164e86c739f55c017cb209 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Fri, 3 May 2024 10:51:22 -0400 Subject: [PATCH 02/10] Fix Google P3DT --- Cesium3DTilesSelection/src/Tile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tile.cpp b/Cesium3DTilesSelection/src/Tile.cpp index 16af8d8ac..3bf5f083b 100644 --- a/Cesium3DTilesSelection/src/Tile.cpp +++ b/Cesium3DTilesSelection/src/Tile.cpp @@ -222,8 +222,8 @@ bool Tile::isUnknownContent() const noexcept { } bool Tile::isReadyToUnload() const noexcept { - if (this->getState() == TileLoadState::ContentLoading || - this->getState() == TileLoadState::Unloading) { + if (this->getState() != TileLoadState::ContentLoaded && + this->getState() != TileLoadState::Done) { return false; } From 1089d32be93170566dafac3877af7db1c2dd1af7 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Mon, 6 May 2024 15:58:24 -0400 Subject: [PATCH 03/10] Format file --- Cesium3DTilesSelection/src/Tile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cesium3DTilesSelection/src/Tile.cpp b/Cesium3DTilesSelection/src/Tile.cpp index 3bf5f083b..3c4e940a5 100644 --- a/Cesium3DTilesSelection/src/Tile.cpp +++ b/Cesium3DTilesSelection/src/Tile.cpp @@ -226,7 +226,7 @@ bool Tile::isReadyToUnload() const noexcept { this->getState() != TileLoadState::Done) { return false; } - + for (const Tile& child : this->_children) { if (!child.isReadyToUnload()) { return false; From 685dee64104713aebd39977c4d6b366847481df8 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Fri, 10 May 2024 16:40:07 -0400 Subject: [PATCH 04/10] Ever closer and yet ever farther from solving this tileset issue... --- .../include/Cesium3DTilesSelection/Tile.h | 6 ++ .../include/Cesium3DTilesSelection/Tileset.h | 2 + Cesium3DTilesSelection/src/Tile.cpp | 3 +- Cesium3DTilesSelection/src/Tileset.cpp | 32 ++++++++ .../src/TilesetContentManager.cpp | 75 ++++++++++++------- .../src/TilesetContentManager.h | 5 ++ 6 files changed, 95 insertions(+), 28 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h index 96cb28b06..1028d2be2 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tile.h @@ -188,6 +188,11 @@ class CESIUM3DTILESSELECTION_API Tile final { return gsl::span(this->_children); } + /** + * Clears the list of this tile's children. + */ + void clearChildren() { this->_children.clear(); } + /** * @brief Assigns the given child tiles to this tile. * @@ -545,6 +550,7 @@ class CESIUM3DTILESSELECTION_API Tile final { std::vector _rasterTiles; friend class TilesetContentManager; + friend class Tileset; friend class MockTilesetContentManagerTestFixture; public: diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index f8bb44fb7..978d297cf 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -416,6 +416,7 @@ class CESIUM3DTILESSELECTION_API Tileset final { void _unloadCachedTiles(double timeBudget) noexcept; void _markTileVisited(Tile& tile) noexcept; + void _unloadPendingChildren(Tile& tile) noexcept; void _updateLodTransitions( const FrameState& frameState, @@ -485,6 +486,7 @@ class CESIUM3DTILESSELECTION_API Tileset final { std::vector _workerThreadLoadQueue; Tile::LoadedLinkedList _loadedTiles; + Tile::LoadedLinkedList _externalTilesPendingClear; // Holds computed distances, to avoid allocating them on the heap during tile // selection. diff --git a/Cesium3DTilesSelection/src/Tile.cpp b/Cesium3DTilesSelection/src/Tile.cpp index 3c4e940a5..2258f2c65 100644 --- a/Cesium3DTilesSelection/src/Tile.cpp +++ b/Cesium3DTilesSelection/src/Tile.cpp @@ -223,7 +223,8 @@ bool Tile::isUnknownContent() const noexcept { bool Tile::isReadyToUnload() const noexcept { if (this->getState() != TileLoadState::ContentLoaded && - this->getState() != TileLoadState::Done) { + this->getState() != TileLoadState::Done && + this->getState() != TileLoadState::Unloaded) { return false; } diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index b49830f53..a0cea1fa3 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -48,6 +48,7 @@ Tileset::Tileset( _externals, _options, RasterOverlayCollection{_loadedTiles, externals}, + _loadedTiles, std::vector{}, std::move(pCustomLoader), std::move(pRootTile))} {} @@ -66,6 +67,7 @@ Tileset::Tileset( _externals, _options, RasterOverlayCollection{_loadedTiles, externals}, + _loadedTiles, url)} {} Tileset::Tileset( @@ -84,6 +86,7 @@ Tileset::Tileset( _externals, _options, RasterOverlayCollection{_loadedTiles, externals}, + _loadedTiles, ionAssetID, ionAccessToken, ionAssetEndpointUrl)} {} @@ -1460,7 +1463,28 @@ void Tileset::_processMainThreadLoadQueue() { this->_mainThreadLoadQueue.clear(); } +void Tileset::_unloadPendingChildren(Tile& tile) noexcept { + for (Tile& childTile : tile.getChildren()) { + this->_externalTilesPendingClear.remove(childTile); + childTile.setState(TileLoadState::Unloaded); + this->_unloadPendingChildren(childTile); + } +} + void Tileset::_unloadCachedTiles(double timeBudget) noexcept { + // Clear children of external tilesets unloaded last frame + Tile* pPendingExternalTile; + while ((pPendingExternalTile = this->_externalTilesPendingClear.head()) != + nullptr) { + this->_externalTilesPendingClear.remove(*pPendingExternalTile); + // We need to remove children recursively, as children of this tile might + // also be in the _externalTilesPendingClear list + this->_unloadPendingChildren(*pPendingExternalTile); + } + + // Clear list of pending external tiles + this->_externalTilesPendingClear = Tile::LoadedLinkedList(); + const int64_t maxBytes = this->getOptions().maximumCachedBytes; const Tile* pRootTile = this->_pTilesetContentManager->getRootTile(); @@ -1491,10 +1515,18 @@ void Tileset::_unloadCachedTiles(double timeBudget) noexcept { Tile* pNext = this->_loadedTiles.next(*pTile); + // Check for external content before unloading, as an unloaded tile will + // always have Unknown content set + const bool wasExternalTile = pTile->isExternalContent(); const bool removed = this->_pTilesetContentManager->unloadTileContent(*pTile); if (removed) { this->_loadedTiles.remove(*pTile); + if (wasExternalTile) { + // The Unreal implementation, at the least, requires a frame between a + // tile being unloaded and its pointers becoming invalidated. + this->_externalTilesPendingClear.insertAtTail(*pTile); + } } pTile = pNext; diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index 07a83eb79..d52fdd50d 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -72,12 +72,6 @@ struct ContentKindSetter { void* pRenderResources; }; -void unloadTileRecursively( - Tile& tile, - TilesetContentManager& tilesetContentManager) { - tilesetContentManager.unloadTileContent(tile); -} - bool anyRasterOverlaysNeedLoading(const Tile& tile) noexcept { for (const RasterMappedTo3DTile& mapped : tile.getMappedRasterTiles()) { const RasterOverlayTile* pLoading = mapped.getLoadingTile(); @@ -596,6 +590,7 @@ TilesetContentManager::TilesetContentManager( const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, + Tile::LoadedLinkedList& loadedTiles, std::vector&& requestHeaders, std::unique_ptr&& pLoader, std::unique_ptr&& pRootTile) @@ -613,6 +608,7 @@ TilesetContentManager::TilesetContentManager( _overlayCollection{std::move(overlayCollection)}, _tileLoadsInProgress{0}, _loadedTilesCount{0}, + _loadedTiles(loadedTiles), _tilesDataUsed{0}, _destructionCompletePromise{externals.asyncSystem.createPromise()}, _destructionCompleteFuture{ @@ -627,6 +623,7 @@ TilesetContentManager::TilesetContentManager( const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, + Tile::LoadedLinkedList& loadedTiles, const std::string& url) : _externals{externals}, _requestHeaders{}, @@ -642,6 +639,7 @@ TilesetContentManager::TilesetContentManager( _overlayCollection{std::move(overlayCollection)}, _tileLoadsInProgress{0}, _loadedTilesCount{0}, + _loadedTiles(loadedTiles), _tilesDataUsed{0}, _destructionCompletePromise{externals.asyncSystem.createPromise()}, _destructionCompleteFuture{ @@ -764,6 +762,7 @@ TilesetContentManager::TilesetContentManager( const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, + Tile::LoadedLinkedList& loadedTiles, int64_t ionAssetID, const std::string& ionAccessToken, const std::string& ionAssetEndpointUrl) @@ -781,6 +780,7 @@ TilesetContentManager::TilesetContentManager( _overlayCollection{std::move(overlayCollection)}, _tileLoadsInProgress{0}, _loadedTilesCount{0}, + _loadedTiles(loadedTiles), _tilesDataUsed{0}, _destructionCompletePromise{externals.asyncSystem.createPromise()}, _destructionCompleteFuture{ @@ -1023,6 +1023,27 @@ void TilesetContentManager::updateTileContent( } } +bool TilesetContentManager::handleUpsampledTileChildren(Tile& tile) { + for (Tile& child : tile.getChildren()) { + if (child.getState() == TileLoadState::ContentLoading && + std::holds_alternative( + child.getTileID())) { + // Yes, a child is upsampling from this tile, so it may be using the + // tile's content from another thread via lambda capture. We can't unload + // it right now. So mark the tile as in the process of unloading and stop + // here. + tile.setState(TileLoadState::Unloading); + return false; + } + + if (!this->handleUpsampledTileChildren(child)) { + return false; + } + } + + return true; +} + bool TilesetContentManager::unloadTileContent(Tile& tile) { TileLoadState state = tile.getState(); if (state == TileLoadState::Unloaded) { @@ -1034,11 +1055,16 @@ bool TilesetContentManager::unloadTileContent(Tile& tile) { } TileContent& content = tile.getContent(); - const bool isReadyToUnload = tile.isReadyToUnload(); + bool isReadyToUnload = tile.isReadyToUnload(); + bool isExternalContent = tile.isExternalContent(); - // don't unload external or empty tile while they're still loading - if ((content.isExternalContent() || content.isEmptyContent()) && - !isReadyToUnload) { + // don't unload external or empty tile while children are still loading + if ((isExternalContent || content.isEmptyContent()) && !isReadyToUnload) { + return false; + } + + // Don't unload this tile if children are still upsampling + if (!this->handleUpsampledTileChildren(tile)) { return false; } @@ -1065,23 +1091,10 @@ bool TilesetContentManager::unloadTileContent(Tile& tile) { } } - // Are any children currently being upsampled from this tile? - for (const Tile& child : tile.getChildren()) { - if (child.getState() == TileLoadState::ContentLoading && - std::holds_alternative( - child.getTileID())) { - // Yes, a child is upsampling from this tile, so it may be using the - // tile's content from another thread via lambda capture. We can't unload - // it right now. So mark the tile as in the process of unloading and stop - // here. - tile.setState(TileLoadState::Unloading); - return false; - } - } - - // Make sure we unload all children if (isReadyToUnload) { - for (Tile& child : tile.getChildren()) { + // Make sure we unload all children + gsl::span children = tile.getChildren(); + for (Tile& child : children) { this->unloadTileContent(child); } } @@ -1089,7 +1102,15 @@ bool TilesetContentManager::unloadTileContent(Tile& tile) { // If we make it this far, the tile's content will be fully unloaded. notifyTileUnloading(&tile); content.setContentKind(TileUnknownContent{}); - tile.setState(TileLoadState::Unloaded); + if (isExternalContent) { + // We don't want to set external tilesets as Unloaded quite yet, because + // then they might get reloaded before we've had a chance to clear their + // children and cause an error. They'll get their children cleared and their + // state set to Unloaded before next clean up + tile.setState(TileLoadState::Done); + } else { + tile.setState(TileLoadState::Unloaded); + } return true; } diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.h b/Cesium3DTilesSelection/src/TilesetContentManager.h index d750b2207..9d46363d1 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.h +++ b/Cesium3DTilesSelection/src/TilesetContentManager.h @@ -26,6 +26,7 @@ class TilesetContentManager const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, + Tile::LoadedLinkedList& loadedTiles, std::vector&& requestHeaders, std::unique_ptr&& pLoader, std::unique_ptr&& pRootTile); @@ -34,12 +35,14 @@ class TilesetContentManager const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, + Tile::LoadedLinkedList& loadedTiles, const std::string& url); TilesetContentManager( const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, + Tile::LoadedLinkedList& loadedTiles, int64_t ionAssetID, const std::string& ionAccessToken, const std::string& ionAssetEndpointUrl = "https://api.cesium.com/"); @@ -65,6 +68,7 @@ class TilesetContentManager void updateTileContent(Tile& tile, const TilesetOptions& tilesetOptions); bool unloadTileContent(Tile& tile); + bool handleUpsampledTileChildren(Tile& tile); void waitUntilIdle(); @@ -145,6 +149,7 @@ class TilesetContentManager int32_t _tileLoadsInProgress; int32_t _loadedTilesCount; int64_t _tilesDataUsed; + Tile::LoadedLinkedList& _loadedTiles; CesiumAsync::Promise _destructionCompletePromise; CesiumAsync::SharedFuture _destructionCompleteFuture; From 7592a2eda7f07c610270be2d1bf84dcbcfe5d72a Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Fri, 10 May 2024 16:58:23 -0400 Subject: [PATCH 05/10] Actually clear children... --- Cesium3DTilesSelection/src/Tileset.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index a0cea1fa3..6d559bce1 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -1480,6 +1480,8 @@ void Tileset::_unloadCachedTiles(double timeBudget) noexcept { // We need to remove children recursively, as children of this tile might // also be in the _externalTilesPendingClear list this->_unloadPendingChildren(*pPendingExternalTile); + pPendingExternalTile->setState(TileLoadState::Unloaded); + pPendingExternalTile->clearChildren(); } // Clear list of pending external tiles From d805293e456b8be57794683644d317351e772025 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Tue, 14 May 2024 11:06:42 -0400 Subject: [PATCH 06/10] Use STL list for now --- .../include/Cesium3DTilesSelection/Tileset.h | 2 +- Cesium3DTilesSelection/src/Tileset.cpp | 15 ++++++--------- .../src/TilesetContentManager.cpp | 8 +------- .../src/TilesetContentManager.h | 4 ---- 4 files changed, 8 insertions(+), 21 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h index 978d297cf..647b52edb 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/Tileset.h @@ -486,7 +486,7 @@ class CESIUM3DTILESSELECTION_API Tileset final { std::vector _workerThreadLoadQueue; Tile::LoadedLinkedList _loadedTiles; - Tile::LoadedLinkedList _externalTilesPendingClear; + std::list _externalTilesPendingClear; // Holds computed distances, to avoid allocating them on the heap during tile // selection. diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 6d559bce1..6006884fc 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -48,7 +48,6 @@ Tileset::Tileset( _externals, _options, RasterOverlayCollection{_loadedTiles, externals}, - _loadedTiles, std::vector{}, std::move(pCustomLoader), std::move(pRootTile))} {} @@ -67,7 +66,6 @@ Tileset::Tileset( _externals, _options, RasterOverlayCollection{_loadedTiles, externals}, - _loadedTiles, url)} {} Tileset::Tileset( @@ -86,7 +84,6 @@ Tileset::Tileset( _externals, _options, RasterOverlayCollection{_loadedTiles, externals}, - _loadedTiles, ionAssetID, ionAccessToken, ionAssetEndpointUrl)} {} @@ -1465,7 +1462,7 @@ void Tileset::_processMainThreadLoadQueue() { void Tileset::_unloadPendingChildren(Tile& tile) noexcept { for (Tile& childTile : tile.getChildren()) { - this->_externalTilesPendingClear.remove(childTile); + this->_externalTilesPendingClear.remove(&childTile); childTile.setState(TileLoadState::Unloaded); this->_unloadPendingChildren(childTile); } @@ -1474,9 +1471,9 @@ void Tileset::_unloadPendingChildren(Tile& tile) noexcept { void Tileset::_unloadCachedTiles(double timeBudget) noexcept { // Clear children of external tilesets unloaded last frame Tile* pPendingExternalTile; - while ((pPendingExternalTile = this->_externalTilesPendingClear.head()) != - nullptr) { - this->_externalTilesPendingClear.remove(*pPendingExternalTile); + while (!this->_externalTilesPendingClear.empty()) { + pPendingExternalTile = this->_externalTilesPendingClear.front(); + this->_externalTilesPendingClear.pop_front(); // We need to remove children recursively, as children of this tile might // also be in the _externalTilesPendingClear list this->_unloadPendingChildren(*pPendingExternalTile); @@ -1485,7 +1482,7 @@ void Tileset::_unloadCachedTiles(double timeBudget) noexcept { } // Clear list of pending external tiles - this->_externalTilesPendingClear = Tile::LoadedLinkedList(); + this->_externalTilesPendingClear.clear(); const int64_t maxBytes = this->getOptions().maximumCachedBytes; @@ -1527,7 +1524,7 @@ void Tileset::_unloadCachedTiles(double timeBudget) noexcept { if (wasExternalTile) { // The Unreal implementation, at the least, requires a frame between a // tile being unloaded and its pointers becoming invalidated. - this->_externalTilesPendingClear.insertAtTail(*pTile); + this->_externalTilesPendingClear.push_back(pTile); } } diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index d52fdd50d..07a7438b3 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -590,7 +590,6 @@ TilesetContentManager::TilesetContentManager( const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, - Tile::LoadedLinkedList& loadedTiles, std::vector&& requestHeaders, std::unique_ptr&& pLoader, std::unique_ptr&& pRootTile) @@ -608,7 +607,6 @@ TilesetContentManager::TilesetContentManager( _overlayCollection{std::move(overlayCollection)}, _tileLoadsInProgress{0}, _loadedTilesCount{0}, - _loadedTiles(loadedTiles), _tilesDataUsed{0}, _destructionCompletePromise{externals.asyncSystem.createPromise()}, _destructionCompleteFuture{ @@ -623,7 +621,6 @@ TilesetContentManager::TilesetContentManager( const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, - Tile::LoadedLinkedList& loadedTiles, const std::string& url) : _externals{externals}, _requestHeaders{}, @@ -639,7 +636,6 @@ TilesetContentManager::TilesetContentManager( _overlayCollection{std::move(overlayCollection)}, _tileLoadsInProgress{0}, _loadedTilesCount{0}, - _loadedTiles(loadedTiles), _tilesDataUsed{0}, _destructionCompletePromise{externals.asyncSystem.createPromise()}, _destructionCompleteFuture{ @@ -762,7 +758,6 @@ TilesetContentManager::TilesetContentManager( const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, - Tile::LoadedLinkedList& loadedTiles, int64_t ionAssetID, const std::string& ionAccessToken, const std::string& ionAssetEndpointUrl) @@ -780,7 +775,6 @@ TilesetContentManager::TilesetContentManager( _overlayCollection{std::move(overlayCollection)}, _tileLoadsInProgress{0}, _loadedTilesCount{0}, - _loadedTiles(loadedTiles), _tilesDataUsed{0}, _destructionCompletePromise{externals.asyncSystem.createPromise()}, _destructionCompleteFuture{ @@ -1107,7 +1101,7 @@ bool TilesetContentManager::unloadTileContent(Tile& tile) { // then they might get reloaded before we've had a chance to clear their // children and cause an error. They'll get their children cleared and their // state set to Unloaded before next clean up - tile.setState(TileLoadState::Done); + tile.setState(TileLoadState::Unloading); } else { tile.setState(TileLoadState::Unloaded); } diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.h b/Cesium3DTilesSelection/src/TilesetContentManager.h index 9d46363d1..fec9d9974 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.h +++ b/Cesium3DTilesSelection/src/TilesetContentManager.h @@ -26,7 +26,6 @@ class TilesetContentManager const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, - Tile::LoadedLinkedList& loadedTiles, std::vector&& requestHeaders, std::unique_ptr&& pLoader, std::unique_ptr&& pRootTile); @@ -35,14 +34,12 @@ class TilesetContentManager const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, - Tile::LoadedLinkedList& loadedTiles, const std::string& url); TilesetContentManager( const TilesetExternals& externals, const TilesetOptions& tilesetOptions, RasterOverlayCollection&& overlayCollection, - Tile::LoadedLinkedList& loadedTiles, int64_t ionAssetID, const std::string& ionAccessToken, const std::string& ionAssetEndpointUrl = "https://api.cesium.com/"); @@ -149,7 +146,6 @@ class TilesetContentManager int32_t _tileLoadsInProgress; int32_t _loadedTilesCount; int64_t _tilesDataUsed; - Tile::LoadedLinkedList& _loadedTiles; CesiumAsync::Promise _destructionCompletePromise; CesiumAsync::SharedFuture _destructionCompleteFuture; From c1de433b7969534686e4e5b3f359e1edb745dba1 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 15 May 2024 16:09:59 -0400 Subject: [PATCH 07/10] Remove from _loadedTiles when clearing children --- Cesium3DTilesSelection/src/Tileset.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 6006884fc..e0ac860e0 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -1462,6 +1462,7 @@ void Tileset::_processMainThreadLoadQueue() { void Tileset::_unloadPendingChildren(Tile& tile) noexcept { for (Tile& childTile : tile.getChildren()) { + this->_loadedTiles.remove(childTile); this->_externalTilesPendingClear.remove(&childTile); childTile.setState(TileLoadState::Unloaded); this->_unloadPendingChildren(childTile); From 0700c72d779b8f222cfa38c543ea37ddfe206abb Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Wed, 15 May 2024 17:03:20 -0400 Subject: [PATCH 08/10] Fix 'children already created' errors, again --- Cesium3DTilesSelection/src/Tileset.cpp | 5 ++++- Cesium3DTilesSelection/src/TilesetContentManager.cpp | 10 +--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index e0ac860e0..9db348e80 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -1467,6 +1467,8 @@ void Tileset::_unloadPendingChildren(Tile& tile) noexcept { childTile.setState(TileLoadState::Unloaded); this->_unloadPendingChildren(childTile); } + + tile.clearChildren(); } void Tileset::_unloadCachedTiles(double timeBudget) noexcept { @@ -1479,7 +1481,6 @@ void Tileset::_unloadCachedTiles(double timeBudget) noexcept { // also be in the _externalTilesPendingClear list this->_unloadPendingChildren(*pPendingExternalTile); pPendingExternalTile->setState(TileLoadState::Unloaded); - pPendingExternalTile->clearChildren(); } // Clear list of pending external tiles @@ -1540,6 +1541,8 @@ void Tileset::_unloadCachedTiles(double timeBudget) noexcept { void Tileset::_markTileVisited(Tile& tile) noexcept { this->_loadedTiles.insertAtTail(tile); + // Don't clear the children of this tile next frame + this->_externalTilesPendingClear.remove(&tile); } void Tileset::addTileToLoadQueue( diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index 07a7438b3..44ce6d32b 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -1096,15 +1096,7 @@ bool TilesetContentManager::unloadTileContent(Tile& tile) { // If we make it this far, the tile's content will be fully unloaded. notifyTileUnloading(&tile); content.setContentKind(TileUnknownContent{}); - if (isExternalContent) { - // We don't want to set external tilesets as Unloaded quite yet, because - // then they might get reloaded before we've had a chance to clear their - // children and cause an error. They'll get their children cleared and their - // state set to Unloaded before next clean up - tile.setState(TileLoadState::Unloading); - } else { - tile.setState(TileLoadState::Unloaded); - } + tile.setState(TileLoadState::Done); return true; } From 6bf7f24d12e0f82801c883166206f1e087dca4c7 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 16 May 2024 16:33:44 -0400 Subject: [PATCH 09/10] Set tile state to Unloaded --- Cesium3DTilesSelection/src/Tileset.cpp | 7 +++++++ Cesium3DTilesSelection/src/TilesetContentManager.cpp | 10 +--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index 9db348e80..a45e44d13 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -1543,6 +1543,13 @@ void Tileset::_markTileVisited(Tile& tile) noexcept { this->_loadedTiles.insertAtTail(tile); // Don't clear the children of this tile next frame this->_externalTilesPendingClear.remove(&tile); + if (tile.getState() == TileLoadState::Unloaded && + !tile.getChildren().empty()) { + // We were going to clear this tile's children, but it's still in use, so we + // should restore it to Done instead. + tile.setState(TileLoadState::Done); + tile.setContentShouldContinueUpdating(false); + } } void Tileset::addTileToLoadQueue( diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index 44ce6d32b..524583b4d 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -1085,18 +1085,10 @@ bool TilesetContentManager::unloadTileContent(Tile& tile) { } } - if (isReadyToUnload) { - // Make sure we unload all children - gsl::span children = tile.getChildren(); - for (Tile& child : children) { - this->unloadTileContent(child); - } - } - // If we make it this far, the tile's content will be fully unloaded. notifyTileUnloading(&tile); content.setContentKind(TileUnknownContent{}); - tile.setState(TileLoadState::Done); + tile.setState(TileLoadState::Unloaded); return true; } From 4e6d0646e49828b89c304c06bfd51e76d8caffd6 Mon Sep 17 00:00:00 2001 From: Ashley Rogers Date: Thu, 20 Jun 2024 15:57:40 -0400 Subject: [PATCH 10/10] Change removal from list - still not enough --- Cesium3DTilesSelection/src/Tileset.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Cesium3DTilesSelection/src/Tileset.cpp b/Cesium3DTilesSelection/src/Tileset.cpp index a45e44d13..bbc35c2d9 100644 --- a/Cesium3DTilesSelection/src/Tileset.cpp +++ b/Cesium3DTilesSelection/src/Tileset.cpp @@ -1541,8 +1541,18 @@ void Tileset::_unloadCachedTiles(double timeBudget) noexcept { void Tileset::_markTileVisited(Tile& tile) noexcept { this->_loadedTiles.insertAtTail(tile); - // Don't clear the children of this tile next frame - this->_externalTilesPendingClear.remove(&tile); + + // If the tile is present in _externalTilesPendingClear, it needs to be removed since we're still using it. + // This way lets us do the find and remove in one search. + auto it = std::find(this->_externalTilesPendingClear.begin(), this->_externalTilesPendingClear.end(), &tile); + if (it == this->_externalTilesPendingClear.end()) { + // Tile isn't in _externalTilesPendingClear, nothing to do. + return; + } + + // Actually remove the tile from the pending list + this->_externalTilesPendingClear.erase(it); + if (tile.getState() == TileLoadState::Unloaded && !tile.getChildren().empty()) { // We were going to clear this tile's children, but it's still in use, so we