From 74df410ddbcc630673790b329d36ba1505da2bac Mon Sep 17 00:00:00 2001 From: Brian L <130494071+csciguy8@users.noreply.github.com> Date: Fri, 12 Jan 2024 16:15:33 -0700 Subject: [PATCH] Add framework to let raster tiles complete work before parent tile --- .../Cesium3DTilesSelection/TileWorkManager.h | 8 ++ .../src/TileWorkManager.cpp | 101 ++++++++++++++---- .../src/TilesetContentManager.cpp | 71 ++++++++---- .../src/TilesetContentManager.h | 16 ++- 4 files changed, 153 insertions(+), 43 deletions(-) diff --git a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TileWorkManager.h b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TileWorkManager.h index ec0fe4b95..e8323fdc7 100644 --- a/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TileWorkManager.h +++ b/Cesium3DTilesSelection/include/Cesium3DTilesSelection/TileWorkManager.h @@ -29,6 +29,8 @@ struct TileLoadWork { ResponseDataMap responsesByUrl; + std::vector childWork; + bool operator<(const TileLoadWork& rhs) const noexcept { if (this->group == rhs.group) return this->priority < rhs.priority; @@ -62,6 +64,8 @@ class TileWorkManager { std::vector& outCompleted, std::vector& outFailed); + void SignalWorkComplete(const TileLoadWork& work); + size_t GetPendingRequestsCount(); size_t GetTotalPendingCount(); @@ -85,6 +89,10 @@ class TileWorkManager { bool isRequestAlreadyInFlight(const TileLoadWork& newRequest); bool isWorkAlreadyProcessing(const TileLoadWork& newProcessing); + void eraseMatchingChildWork( + const TileLoadWork& work, + std::vector& childWork); + // Thread safe members std::mutex _requestsLock; bool _exitSignaled = false; diff --git a/Cesium3DTilesSelection/src/TileWorkManager.cpp b/Cesium3DTilesSelection/src/TileWorkManager.cpp index f3192eb59..132df9c04 100644 --- a/Cesium3DTilesSelection/src/TileWorkManager.cpp +++ b/Cesium3DTilesSelection/src/TileWorkManager.cpp @@ -92,10 +92,8 @@ void TileWorkManager::QueueBatch( std::lock_guard lock(_requestsLock); for (TileLoadWork* element : requestWork) { - if (isRequestAlreadyQueued(*element)) - continue; - if (isRequestAlreadyInFlight(*element)) - continue; + assert(!isRequestAlreadyQueued(*element)); + assert(!isRequestAlreadyInFlight(*element)); _requestQueue.push_back(std::move(*element)); } @@ -112,15 +110,70 @@ void TileWorkManager::QueueBatch( void TileWorkManager::QueueSingleRequest(const TileLoadWork& requestWork) { { std::lock_guard lock(_requestsLock); + + // TODO - This needs to be an assertion if (!isRequestAlreadyQueued(requestWork) && - !isRequestAlreadyInFlight(requestWork)) { + !isRequestAlreadyInFlight(requestWork)) _requestQueue.push_back(std::move(requestWork)); - } } transitionQueuedWork(); } +void TileWorkManager::eraseMatchingChildWork( + const TileLoadWork& work, + std::vector& childWork) { + std::vector::iterator childIt; + for (childIt = childWork.begin(); childIt != childWork.end(); ++childIt) { + bool baseWorkEqual = childIt->childWork.size() == work.childWork.size() && + childIt->group == work.group && + childIt->priority == work.priority; + bool workHasTileProcessing = + std::holds_alternative(work.processingData); + bool childHasTileProcessing = + std::holds_alternative(childIt->processingData); + + if (!baseWorkEqual || workHasTileProcessing != childHasTileProcessing) + continue; + + if (workHasTileProcessing) { + TileProcessingData workTileProcessing = + std::get(work.processingData); + TileProcessingData childTileProcessing = + std::get(childIt->processingData); + if (workTileProcessing.pTile != childTileProcessing.pTile) + continue; + } else { + RasterProcessingData workRasterProcessing = + std::get(work.processingData); + RasterProcessingData childRasterProcessing = + std::get(childIt->processingData); + if (workRasterProcessing.pRasterTile != childRasterProcessing.pRasterTile) + continue; + } + + childWork.erase(childIt); + break; + } +} + +void TileWorkManager::SignalWorkComplete(const TileLoadWork& work) { + std::lock_guard lock(_requestsLock); + + // Look for any work whose child matches this. And remove ourselves + for (TileLoadWork& existingRequest : _requestQueue) + eraseMatchingChildWork(work, existingRequest.childWork); + + std::map>::iterator mapIt; + for (mapIt = _inFlightRequests.begin(); mapIt != _inFlightRequests.end(); + ++mapIt) + for (TileLoadWork& inFlightWork : mapIt->second) + eraseMatchingChildWork(work, inFlightWork.childWork); + + for (TileLoadWork& doneRequest : _processingQueue) + eraseMatchingChildWork(work, doneRequest.childWork); +} + void TileWorkManager::onRequestFinished( uint16_t responseStatusCode, gsl::span responseBytes, @@ -259,22 +312,34 @@ void TileWorkManager::TakeProcessingWork( if (processingCount == 0) return; + // TODO - This list should be a map so it is always sorted + // Reverse sort so highest priority is at back + std::sort(_processingQueue.rbegin(), _processingQueue.rend()); + size_t numberToTake = std::min(processingCount, maxCount); - // If not taking everything, sort so more important work goes first - if (numberToTake < processingCount) - std::sort(_processingQueue.begin(), _processingQueue.end()); + // Start from the back + std::vector::iterator it = _processingQueue.end(); + while (1) { + --it; + + TileLoadWork& work = *it; + if (!work.childWork.empty()) { + // Can't take this work yet + // Child work has to register completion first + } else { + // Move this work to output. Erase from queue + std::vector::iterator eraseIt = it; + outCompleted.push_back(std::move(*eraseIt)); + _processingQueue.erase(eraseIt); + } - // Move work to output - for (auto workIt = _processingQueue.begin(); - workIt != _processingQueue.begin() + numberToTake; - ++workIt) - outCompleted.push_back(std::move(*workIt)); + if (outCompleted.size() >= numberToTake) + break; - // Remove these entries from the source - _processingQueue.erase( - _processingQueue.begin(), - _processingQueue.begin() + numberToTake); + if (it == _processingQueue.begin()) + break; + } } void TileWorkManager::transitionQueuedWork() { diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.cpp b/Cesium3DTilesSelection/src/TilesetContentManager.cpp index 0c1a1daed..247f94d50 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.cpp +++ b/Cesium3DTilesSelection/src/TilesetContentManager.cpp @@ -257,11 +257,10 @@ void createQuadtreeSubdividedChildren( std::vector mapOverlaysToTile( Tile& tile, - size_t depthIndex, RasterOverlayCollection& overlays, double maximumScreenSpaceError, const std::vector& defaultHeaders, - std::vector& outWork) { + std::vector& outWork) { // when tile fails temporarily, it may still have mapped raster tiles, so // clear it here tile.getMappedRasterTiles().clear(); @@ -293,11 +292,11 @@ std::vector mapOverlaysToTile( pMapped->getLoadThrottledWork(requestData, rasterCallback); if (!requestData.url.empty() || rasterCallback != nullptr) { - TilesetContentManager::ParsedTileWork newWork = { - depthIndex, + TilesetContentManager::RasterWorkChain newWorkChain = { + pMapped, requestData, - RasterProcessingData{pMapped, rasterCallback}}; - outWork.push_back(newWork); + rasterCallback}; + outWork.push_back(newWorkChain); } } } @@ -900,13 +899,30 @@ void TilesetContentManager::discoverLoadWork( TilesetContentManager::ParsedTileWork& work = parsedTileWork[workIndex]; double priorityBias = double(maxDepth - work.depthIndex); + double resultPriority = loadRequest.priority + priorityBias; TileLoadWork newWorkUnit = { - work.requestData, - work.processingData, + work.tileWorkChain.requestData, + TileProcessingData{ + work.tileWorkChain.pTile, + work.tileWorkChain.tileCallback}, work.projections, loadRequest.group, - loadRequest.priority + priorityBias}; + resultPriority}; + + for (auto rasterWorkChain : work.rasterWorkChains) { + TileLoadWork rasterWorkUnit = { + rasterWorkChain.requestData, + RasterProcessingData{ + rasterWorkChain.pRasterTile, + rasterWorkChain.rasterCallback}, + work.projections, + loadRequest.group, + resultPriority}; + + // Embed child work in parent + newWorkUnit.childWork.push_back(rasterWorkUnit); + } outLoadWork.push_back(newWorkUnit); } @@ -921,11 +937,20 @@ void TilesetContentManager::addWorkToManager( _tileWorkManager.SetMaxSimultaneousRequests(maxSimultaneousRequests); + // Expand any child work + std::vector flattenedWork; + for (TileLoadWork& work : loadWork) { + for (TileLoadWork& child : work.childWork) { + flattenedWork.push_back(child); + } + flattenedWork.push_back(work); + } + // Request work will always go to that queue first // Work with only processing can bypass it std::vector requestWork; std::vector processingWork; - for (TileLoadWork& work : loadWork) { + for (TileLoadWork& work : flattenedWork) { if (work.requestData.url.empty()) processingWork.push_back(&work); else @@ -1103,6 +1128,8 @@ void TilesetContentManager::dispatchProcessingWork( _work.requestData.headers = newRequestData.headers; _this->_tileWorkManager.QueueSingleRequest(_work); + } else { + _this->_tileWorkManager.SignalWorkComplete(_work); } _this->notifyRasterDoneLoading(); @@ -1171,10 +1198,11 @@ void TilesetContentManager::parseTileWork( rasterTile.getLoadThrottledWork(requestData, rasterCallback); if (!requestData.url.empty() || rasterCallback != nullptr) { - TilesetContentManager::ParsedTileWork newWork = { - depthIndex, - requestData, - RasterProcessingData{&rasterTile, rasterCallback}}; + // TODO - This needs a different solution for continuation + // We can't pick up with an empty tile work chain + ParsedTileWork newWork = {depthIndex}; + newWork.rasterWorkChains.push_back( + RasterWorkChain{&rasterTile, requestData, rasterCallback}); outWork.push_back(newWork); } } @@ -1234,20 +1262,17 @@ void TilesetContentManager::parseTileWork( pLoader->getLoadWork(pTile, requestData, tileCallback); - // map raster overlay to tile - std::vector projections = mapOverlaysToTile( - *pTile, + ParsedTileWork newWork = { depthIndex, + TileWorkChain{pTile, requestData, tileCallback}}; + + newWork.projections = mapOverlaysToTile( + *pTile, this->_overlayCollection, maximumScreenSpaceError, this->_requestHeaders, - outWork); + newWork.rasterWorkChains); - ParsedTileWork newWork = { - depthIndex, - requestData, - TileProcessingData{pTile, tileCallback}, - projections}; outWork.push_back(newWork); } diff --git a/Cesium3DTilesSelection/src/TilesetContentManager.h b/Cesium3DTilesSelection/src/TilesetContentManager.h index 9639c7589..7e1a9f289 100644 --- a/Cesium3DTilesSelection/src/TilesetContentManager.h +++ b/Cesium3DTilesSelection/src/TilesetContentManager.h @@ -62,12 +62,24 @@ class TilesetContentManager ~TilesetContentManager() noexcept; + struct TileWorkChain { + Tile* pTile; + RequestData requestData; + TileProcessingCallback tileCallback; + }; + + struct RasterWorkChain { + RasterMappedTo3DTile* pRasterTile; + RequestData requestData; + RasterProcessingCallback rasterCallback; + }; + struct ParsedTileWork { size_t depthIndex; - RequestData requestData; + TileWorkChain tileWorkChain; - ProcessingData processingData; + std::vector rasterWorkChains; std::vector projections;