diff --git a/common/lc_assetloader.cpp b/common/lc_assetloader.cpp index daf3a1d6..479f08b5 100644 --- a/common/lc_assetloader.cpp +++ b/common/lc_assetloader.cpp @@ -26,36 +26,27 @@ lcAssetLoader::~lcAssetLoader() CancelAndDrain(); } -bool lcAssetLoader::LoadPieceInfo(PieceInfo* Info, lcPieceLoadFlags Flags) +bool lcAssetLoader::LoadPieceInfo(PieceInfo* Info, lcPieceLoadPriority LoadPriority) { Q_ASSERT(QThread::currentThread() == thread()); - mLibrary->AddPieceReference(Info); - if (Info->IsProject() || (Info->IsModel() && !Info->GetModel()->HasDirectGeometry())) Info->mState = lcPieceInfoState::Loaded; else if (Info->mState == lcPieceInfoState::Unloaded) - EnqueuePiece(Info, Flags.testFlag(lcPieceLoadFlag::Visible) ? Priority::Visible : Priority::Background); - else if (Flags.testFlag(lcPieceLoadFlag::Visible)) + EnqueuePiece(Info, LoadPriority); + else if (LoadPriority != lcPieceLoadPriority::Low) { mQueueMutex.lock(); const auto It = mRequests.find(Info); if (It != mRequests.end()) - PromoteRequestPriorityLocked(It->second, Priority::Visible); + PromoteRequestPriorityLocked(It->second, LoadPriority); mQueueMutex.unlock(); } - return !Flags.testFlag(lcPieceLoadFlag::Wait) || WaitForRequest(Info); + return LoadPriority != lcPieceLoadPriority::Blocking || WaitForRequest(Info); } -void lcAssetLoader::QueuePiece(PieceInfo* Info) -{ - Q_ASSERT(QThread::currentThread() == thread()); - - EnqueuePiece(Info, Priority::Background); -} - -void lcAssetLoader::EnqueuePiece(PieceInfo* Info, Priority LoadPriority) +void lcAssetLoader::EnqueuePiece(PieceInfo* Info, lcPieceLoadPriority LoadPriority) { QMutexLocker QueueLock(&mQueueMutex); @@ -105,7 +96,7 @@ void lcAssetLoader::EnqueuePiece(PieceInfo* Info, Priority LoadPriority) emit mLibrary->AssetRequestsChanged(); } -void lcAssetLoader::PromoteRequestPriorityLocked(const std::shared_ptr& RequestedPiece, Priority LoadPriority) +void lcAssetLoader::PromoteRequestPriorityLocked(const std::shared_ptr& RequestedPiece, lcPieceLoadPriority LoadPriority) { RequestedPiece->LoadPriority = std::max(RequestedPiece->LoadPriority, LoadPriority); @@ -140,7 +131,7 @@ void lcAssetLoader::QueueSynthMesh(lcPiece* Piece) RequestedSynth->Piece = Piece; RequestedSynth->SynthInfo = SynthInfo; RequestedSynth->ControlPoints = Piece->GetControlPoints(); - RequestedSynth->LoadPriority = Priority::Visible; + RequestedSynth->LoadPriority = lcPieceLoadPriority::Normal; RequestedSynth->EnqueuedAt = QDateTime::currentMSecsSinceEpoch(); RequestedSynth->Generation = mGeneration; @@ -159,7 +150,7 @@ void lcAssetLoader::QueueSynthMesh(lcPiece* Piece) emit mLibrary->AssetRequestsChanged(); } -void lcAssetLoader::QueueTexture(lcTexture* Texture, Priority LoadPriority) +void lcAssetLoader::QueueTexture(lcTexture* Texture, lcPieceLoadPriority LoadPriority) { QMutexLocker QueueLock(&mQueueMutex); @@ -234,7 +225,7 @@ void lcAssetLoader::RunWorker() const auto Score = [Now](const auto& Item) { // Each ten seconds of waiting promotes one priority level. - return qMin(static_cast(Priority::Blocking), + return qMin(static_cast(lcPieceLoadPriority::Blocking), static_cast(Item->LoadPriority) + qMax(0, Now - Item->EnqueuedAt) / 10000); }; @@ -661,7 +652,7 @@ void lcAssetLoader::ResolveMeshColors(lcLibraryMeshData& MeshData, std::vector& SearchDirectories, - const std::map>& EmbeddedData, Priority LoadPriority, QString& Error) + const std::map>& EmbeddedData, lcPieceLoadPriority LoadPriority, QString& Error) { for (lcMeshLod& Lod : Mesh.mLods) for (int SectionIdx = 0; SectionIdx < Lod.NumSections; SectionIdx++) @@ -1056,7 +1047,7 @@ bool lcAssetLoader::WaitForRequest(PieceInfo* Info) { RequestedPiece = It->second; - PromoteRequestPriorityLocked(RequestedPiece, Priority::Blocking); + PromoteRequestPriorityLocked(RequestedPiece, lcPieceLoadPriority::Blocking); } StartWorkersLocked(); } @@ -1088,16 +1079,11 @@ bool lcAssetLoader::WaitForRequest(PieceInfo* Info) return RequestedPiece->Succeeded; } -bool lcAssetLoader::EnsurePieceReady(PieceInfo* Info) -{ - return EnsurePiecesReady({ Info }); -} - -bool lcAssetLoader::EnsurePiecesReady(const std::vector& Parts) +bool lcAssetLoader::WaitForPieces(const std::vector& Pieces) { Q_ASSERT(QThread::currentThread() == thread()); - std::vector Required = Parts; + std::vector Required = Pieces; std::sort(Required.begin(), Required.end()); Required.erase(std::unique(Required.begin(), Required.end()), Required.end()); @@ -1113,7 +1099,7 @@ bool lcAssetLoader::EnsurePiecesReady(const std::vector& Parts) if (Info->IsProject() || (Info->IsModel() && !Info->GetModel()->HasDirectGeometry())) Info->mState = lcPieceInfoState::Loaded; else - EnqueuePiece(Info, Priority::Blocking); + EnqueuePiece(Info, lcPieceLoadPriority::Blocking); } { @@ -1126,7 +1112,7 @@ bool lcAssetLoader::EnsurePiecesReady(const std::vector& Parts) if (It == mRequests.end()) continue; - PromoteRequestPriorityLocked(It->second, Priority::Blocking); + PromoteRequestPriorityLocked(It->second, lcPieceLoadPriority::Blocking); } StartWorkersLocked(); @@ -1143,7 +1129,7 @@ bool lcAssetLoader::EnsurePiecesReady(const std::vector& Parts) return Ready; } -bool lcAssetLoader::EnsureSynthMeshesReady(const std::vector& Pieces) +bool lcAssetLoader::WaitForSynthMeshes(const std::vector& Pieces) { Q_ASSERT(QThread::currentThread() == thread()); @@ -1160,7 +1146,7 @@ bool lcAssetLoader::EnsureSynthMeshesReady(const std::vector& Pieces) continue; const std::shared_ptr& RequestedSynth = It->second; - RequestedSynth->LoadPriority = Priority::Blocking; + RequestedSynth->LoadPriority = lcPieceLoadPriority::Blocking; Required.push_back(RequestedSynth); if (RequestedSynth->StagedMesh) @@ -1170,7 +1156,7 @@ bool lcAssetLoader::EnsureSynthMeshesReady(const std::vector& Pieces) const TextureRequestMap::iterator TextureIt = mTextureRequests.find(Lod.Sections[SectionIdx].Texture); if (TextureIt != mTextureRequests.end()) - TextureIt->second->LoadPriority = Priority::Blocking; + TextureIt->second->LoadPriority = lcPieceLoadPriority::Blocking; } } @@ -1208,14 +1194,14 @@ bool lcAssetLoader::EnsureSynthMeshesReady(const std::vector& Pieces) return true; } -bool lcAssetLoader::EnsureTextureReady(lcTexture* Texture) +bool lcAssetLoader::WaitForTexture(lcTexture* Texture) { Q_ASSERT(QThread::currentThread() == thread()); if (Texture->IsReady()) return true; - QueueTexture(Texture, Priority::Blocking); + QueueTexture(Texture, lcPieceLoadPriority::Blocking); for (;;) { @@ -1295,7 +1281,7 @@ void lcAssetLoader::RebuildModelPiece(PieceInfo* Info) Info->mState = lcPieceInfoState::Unloaded; - EnqueuePiece(Info, Priority::Visible); + EnqueuePiece(Info, lcPieceLoadPriority::Normal); } void lcAssetLoader::InvalidatePiece(PieceInfo* Info) diff --git a/common/lc_assetloader.h b/common/lc_assetloader.h index 2f6dbdd7..fc6da341 100644 --- a/common/lc_assetloader.h +++ b/common/lc_assetloader.h @@ -59,25 +59,35 @@ public: lcAssetLoader(lcPiecesLibrary* Library); ~lcAssetLoader() override; - // Start using a piece: acquire a reference and load it if needed. Release it with ReleasePieceInfo(). - // Visible raises its loading priority. Wait blocks until loading settles and returns whether it succeeded; - // without Wait, this returns true immediately, even if the piece is not ready yet. - bool LoadPieceInfo(PieceInfo* Info, lcPieceLoadFlags Flags); - - // Queue a background build for a piece the caller already owns, without acquiring a consumer reference or waiting. - // Reuse any existing request, keeping its priority. - void QueuePiece(PieceInfo* Info); + // Load a piece the library has already acquired for the caller; this adds no consumer reference. + // Low and Normal return immediately; true does not mean the piece is ready yet. + // Blocking promotes urgent work, waits until loading settles, and returns whether it succeeded. + bool LoadPieceInfo(PieceInfo* Info, lcPieceLoadPriority LoadPriority); // Build this instance's generated mesh from its current control points in the background. // Cancel any older request and keep the previous mesh visible while the replacement is built. void QueueSynthMesh(lcPiece* Piece); - bool EnsurePieceReady(PieceInfo* Info); - bool EnsurePiecesReady(const std::vector& Pieces); - bool EnsureSynthMeshesReady(const std::vector& Pieces); - bool EnsureTextureReady(lcTexture* Texture); + // Start or promote loading as needed, then wait. Returns true if all pieces are ready. + // Waits for these identities only, without traversing container children or generated meshes. + bool WaitForPieces(const std::vector& Pieces); + + // Promote and wait for queued generations. Returns true if every required generated mesh is current. + // Call QueueSynthMesh() to start a generation; the caller keeps the instances alive through the wait. + bool WaitForSynthMeshes(const std::vector& Pieces); + + // Start or promote decoding as needed, then wait through upload and retries. The caller retains the texture. + bool WaitForTexture(lcTexture* Texture); + + // Drain all pending work, including unrelated assets. Completion does not imply success. void WaitForLoadQueue(); + + // True while piece, texture, or generated-mesh requests or worker completions remain unsettled. + // Includes paused work and upload retries; deferred notifications alone do not count. bool HasPendingWork(); + + // Returns this instance's recorded generation error, or an empty string if none is recorded. + // An empty string does not establish readiness; check the generated mesh separately. QString GetSynthMeshError(const lcPiece* Piece) const; void RebuildModelPiece(PieceInfo* Info); @@ -90,14 +100,6 @@ public: void CancelAndDrain(); private: - enum class Priority - { - // Requests only promote; the priority lasts until completion or cancellation. - Background, // Ordinary queued asset work. - Visible, // Assets requested for the visible model. - Blocking // Assets needed by a synchronous wait. - }; - // Owns one PieceInfo load hold through completion and deferred notification. // Parsing and conversion are separate worker phases; textures settle before publication. struct PieceRequest @@ -105,7 +107,7 @@ private: PieceInfo* Info; quint64 Generation; lcPieceSourceSnapshot Source; - Priority LoadPriority; + lcPieceLoadPriority LoadPriority; qint64 EnqueuedAt; bool Running = false; // A worker phase is outstanding, including its completion. bool Obsolete = false; // Its result must not publish, even if the build succeeds. @@ -127,7 +129,7 @@ private: lcTexture* Texture; quint64 Generation; lcFileSource Source; - Priority LoadPriority; + lcPieceLoadPriority LoadPriority; qint64 EnqueuedAt; bool Running = false; int UploadAttempts = 0; @@ -141,7 +143,7 @@ private: lcPiece* Piece; std::shared_ptr SynthInfo; std::vector ControlPoints; - Priority LoadPriority; + lcPieceLoadPriority LoadPriority; qint64 EnqueuedAt; quint64 Generation; bool Running = false; @@ -188,10 +190,10 @@ private: // Enqueue and promote requests. Workers read copied sources and private build data. // Acquires mQueueMutex internally; callers must not already hold it. - void EnqueuePiece(PieceInfo* Info, Priority LoadPriority); + void EnqueuePiece(PieceInfo* Info, lcPieceLoadPriority LoadPriority); // Caller holds mQueueMutex. - void PromoteRequestPriorityLocked(const std::shared_ptr& RequestedPiece, Priority LoadPriority); - void QueueTexture(lcTexture* Texture, Priority LoadPriority); + void PromoteRequestPriorityLocked(const std::shared_ptr& RequestedPiece, lcPieceLoadPriority LoadPriority); + void QueueTexture(lcTexture* Texture, lcPieceLoadPriority LoadPriority); // Caller holds mQueueMutex. void StartWorkersLocked(); void RunWorker(); @@ -203,7 +205,7 @@ private: void CompleteTexture(Completion& Completed); static void ResolveMeshColors(lcLibraryMeshData& MeshData, std::vector& ColorTranslucency, int& DefaultColorIndex); bool ResolveAndQueueMeshTextures(lcMesh& Mesh, const std::vector& SearchDirectories, - const std::map>& EmbeddedData, Priority LoadPriority, QString& Error); + const std::map>& EmbeddedData, lcPieceLoadPriority LoadPriority, QString& Error); static bool MeshTexturesSettled(const lcMesh& Mesh, QString& Error); static QString TextureFailureMessage(const lcTexture* Texture); void UploadTextureRequest(const std::shared_ptr& RequestedTexture); diff --git a/common/lc_library.cpp b/common/lc_library.cpp index c7f3796a..09e88798 100644 --- a/common/lc_library.cpp +++ b/common/lc_library.cpp @@ -526,7 +526,7 @@ lcTexture* lcPiecesLibrary::FindTexture(const char* TextureName, Project* Curren if (!Texture) return nullptr; - if (EnsureTextureReady(Texture)) + if (mAssetLoader->WaitForTexture(Texture)) return Texture; ReleaseTexture(Texture); @@ -1650,9 +1650,13 @@ bool lcPiecesLibrary::SaveArchiveCacheIndex(const QString& FileName) return WriteArchiveCacheFile(FileName, IndexFile); } -bool lcPiecesLibrary::LoadPieceInfo(PieceInfo* Info, lcPieceLoadFlags Flags) +bool lcPiecesLibrary::LoadPieceInfo(PieceInfo* Info, lcPieceLoadPriority LoadPriority) { - return mAssetLoader->LoadPieceInfo(Info, Flags); + Q_ASSERT(QThread::currentThread() == thread()); + + AddPieceReference(Info); + + return mAssetLoader->LoadPieceInfo(Info, LoadPriority); } void lcPiecesLibrary::NotifyConsumersChanged() @@ -1660,14 +1664,9 @@ void lcPiecesLibrary::NotifyConsumersChanged() emit AssetRequestsChanged(); } -bool lcPiecesLibrary::EnsurePieceReady(PieceInfo* Info) -{ - return mAssetLoader->EnsurePieceReady(Info); -} - bool lcPiecesLibrary::EnsurePieceAssetsReady(PieceInfo* Info) { - const bool PieceReady = EnsurePieceReady(Info); + const bool PieceReady = WaitForPieces({ Info }); if (Info->IsModel()) return static_cast(Info->GetModel()->EnsureAssetsReady()) && PieceReady; @@ -1683,9 +1682,9 @@ bool lcPiecesLibrary::EnsurePieceAssetsReady(PieceInfo* Info) return PieceReady; } -bool lcPiecesLibrary::EnsurePiecesReady(const std::vector& Parts) +bool lcPiecesLibrary::WaitForPieces(const std::vector& Pieces) { - return mAssetLoader->EnsurePiecesReady(Parts); + return mAssetLoader->WaitForPieces(Pieces); } void lcPiecesLibrary::QueueSynthMesh(lcPiece* Piece) @@ -1699,9 +1698,9 @@ void lcPiecesLibrary::CancelSynthMesh(lcPiece* Piece) mAssetLoader->CancelSynthMesh(Piece); } -bool lcPiecesLibrary::EnsureSynthMeshesReady(const std::vector& Pieces) +bool lcPiecesLibrary::WaitForSynthMeshes(const std::vector& Pieces) { - return mAssetLoader->EnsureSynthMeshesReady(Pieces); + return mAssetLoader->WaitForSynthMeshes(Pieces); } QString lcPiecesLibrary::GetSynthMeshError(const lcPiece* Piece) const @@ -1725,11 +1724,6 @@ void lcPiecesLibrary::InvalidatePiece(PieceInfo* Info) } } -bool lcPiecesLibrary::EnsureTextureReady(lcTexture* Texture) -{ - return mAssetLoader->EnsureTextureReady(Texture); -} - void lcPiecesLibrary::ReleasePieceInfo(PieceInfo* Info) { int Remaining; @@ -2327,17 +2321,15 @@ void lcPiecesLibrary::SetStudStyle(lcStudStyle StudStyle, bool Reload, bool Stud if (Info->IsModel()) { - lcModel* Model = Info->GetModel(); - Info->SetModel(Model); Info->ReleaseMesh(); Info->mState = lcPieceInfoState::Unloaded; - mAssetLoader->QueuePiece(Info); + mAssetLoader->LoadPieceInfo(Info, lcPieceLoadPriority::Low); } else if (!Info->IsProject()) { Info->Unload(); - mAssetLoader->QueuePiece(Info); + mAssetLoader->LoadPieceInfo(Info, lcPieceLoadPriority::Low); } mBuffersDirty = true; diff --git a/common/lc_library.h b/common/lc_library.h index b478448c..1d88bd2a 100644 --- a/common/lc_library.h +++ b/common/lc_library.h @@ -75,16 +75,15 @@ enum class lcPrimitiveState Loaded }; -enum class lcPieceLoadFlag +// Scheduling urgency. LoadPieceInfo() also waits when Blocking is requested. +// Queued requests only promote and keep their priority until completion or cancellation. +enum class lcPieceLoadPriority { - None = 0, - Wait = 1 << 0, - Visible = 1 << 1 + Low, // Background work, such as thumbnails and settings rebuilds. + Normal, // Visible or interactive work. + Blocking // Assets required by a synchronous operation. }; -Q_DECLARE_FLAGS(lcPieceLoadFlags, lcPieceLoadFlag) -Q_DECLARE_OPERATORS_FOR_FLAGS(lcPieceLoadFlags) - class lcLibraryPrimitive { public: @@ -191,26 +190,24 @@ public: // Asset scheduling, waits, invalidation, and consumer releases below run on the UI thread. // Acquires one reference even on failure; always pair with ReleasePieceInfo(). - // Without Wait, true does not establish readiness. See docs/asset-loading.md. - bool LoadPieceInfo(PieceInfo* Info, lcPieceLoadFlags Flags); + // Blocking waits and reports success; Low and Normal return immediately. See docs/asset-loading.md. + bool LoadPieceInfo(PieceInfo* Info, lcPieceLoadPriority LoadPriority); void NotifyConsumersChanged(); - // Wait for these identities only, with temporary holds; false means failure/cancellation. + // Start or promote loading as needed, then wait with temporary holds; false means failure/cancellation. // Does not traverse container children or wait for per-instance synth geometry. - bool EnsurePieceReady(PieceInfo* Info); - bool EnsurePiecesReady(const std::vector& Parts); + bool WaitForPieces(const std::vector& Pieces); // Also settles container children/synths and updates bounds; caller retains the identity. bool EnsurePieceAssetsReady(PieceInfo* Info); // Replaces any pending generation for this instance; snapshots control points for workers. void QueueSynthMesh(lcPiece* Piece); void CancelSynthMesh(lcPiece* Piece); - // Caller keeps the instances alive through the wait; false means missing generated geometry. - bool EnsureSynthMeshesReady(const std::vector& Pieces); + // Promote and wait for queued generations; false means missing or outdated generated geometry. + // Call QueueSynthMesh() to start a generation; keep the instances alive through the wait. + bool WaitForSynthMeshes(const std::vector& Pieces); QString GetSynthMeshError(const lcPiece* Piece) const; void RebuildModelPiece(PieceInfo* Info); // Obsoletes pending work and resets readiness without acquiring a reference. void InvalidatePiece(PieceInfo* Info); - // Waits through GPU upload; caller retains the texture, and false means not ready. - bool EnsureTextureReady(lcTexture* Texture); // Releases one reference and lets the loader cancel unused queued work. void ReleasePieceInfo(PieceInfo* Info); // Lifetime hold only: does not start loading or establish readiness. diff --git a/common/lc_mainwindow.cpp b/common/lc_mainwindow.cpp index 5421c8b6..24657791 100644 --- a/common/lc_mainwindow.cpp +++ b/common/lc_mainwindow.cpp @@ -2024,7 +2024,7 @@ void lcMainWindow::SetCurrentPieceInfo(PieceInfo* Info) mCurrentPieceInfo = Info; if (mCurrentPieceInfo) - Library->LoadPieceInfo(mCurrentPieceInfo, lcPieceLoadFlag::Visible); + Library->LoadPieceInfo(mCurrentPieceInfo, lcPieceLoadPriority::Normal); } lcVector3 lcMainWindow::GetTransformAmount() diff --git a/common/lc_model.cpp b/common/lc_model.cpp index 91cc366b..67f4ad89 100644 --- a/common/lc_model.cpp +++ b/common/lc_model.cpp @@ -393,7 +393,7 @@ void lcModel::CreatePieceInfo(Project* Project) // until its source lines have been parsed and QueueMeshBuild() runs. if (!Project->DefersModelMeshRequests()) { - Library->LoadPieceInfo(mPieceInfo, mIsPreview ? lcPieceLoadFlag::Visible : lcPieceLoadFlag::Wait | lcPieceLoadFlag::Visible); + Library->LoadPieceInfo(mPieceInfo, mIsPreview ? lcPieceLoadPriority::Normal : lcPieceLoadPriority::Blocking); Library->ReleasePieceInfo(mPieceInfo); } @@ -518,8 +518,8 @@ void lcModel::WaitForAssets() const const std::vector Required = GetRequiredPieces(); lcPiecesLibrary* Library = lcGetPiecesLibrary(); - Library->EnsurePiecesReady(Required); - Library->EnsureSynthMeshesReady(GetRequiredSynthPieces()); + Library->WaitForPieces(Required); + Library->WaitForSynthMeshes(GetRequiredSynthPieces()); // Temporary minifigure and thumbnail models have no container piece to update. if (mPieceInfo) @@ -1369,7 +1369,7 @@ bool lcModel::LoadInventory(const std::vector& SetInventory) for (const std::unique_ptr& Piece : mPieces) Required.push_back(Piece->mPieceInfo); - if (!Library->EnsurePiecesReady(Required)) + if (!Library->WaitForPieces(Required)) return false; Library->mBuffersDirty = true; @@ -3125,7 +3125,7 @@ lcPiece* lcModel::AddPiece(PieceInfo* Info, quint32 Section) lcGetPiecesLibrary()->EnsurePieceAssetsReady(Last->mPieceInfo); if (Last->mPieceInfo->GetSynthInfo()) - lcGetPiecesLibrary()->EnsureSynthMeshesReady({ Last }); + lcGetPiecesLibrary()->WaitForSynthMeshes({ Last }); } const lcBoundingBox& PieceInfoBoundingBox = Info->GetBoundingBox(); @@ -3418,11 +3418,11 @@ void lcModel::SetPreviewInsertPieceInfo(std::vector&& Preview for (lcInsertPieceInfo& InfoTransform : PreviewInsertPieceInfo) { - Library->LoadPieceInfo(InfoTransform.Info, lcPieceLoadFlag::Visible); + Library->LoadPieceInfo(InfoTransform.Info, lcPieceLoadPriority::Normal); Required.push_back(InfoTransform.Info); } - Library->EnsurePiecesReady(Required); + Library->WaitForPieces(Required); for (lcInsertPieceInfo& PreviewPieceInfoTransform : mPreviewInsertPieceInfo) Library->ReleasePieceInfo(PreviewPieceInfoTransform.Info); diff --git a/common/lc_previewwidget.cpp b/common/lc_previewwidget.cpp index b837691f..06355f21 100644 --- a/common/lc_previewwidget.cpp +++ b/common/lc_previewwidget.cpp @@ -111,7 +111,7 @@ void lcPreviewDockWidget::RefreshModel(const lcModel* Model) if (Info->mState != lcPieceInfoState::Unloaded) continue; - Library->LoadPieceInfo(Info, lcPieceLoadFlag::Visible); + Library->LoadPieceInfo(Info, lcPieceLoadPriority::Normal); Library->ReleasePieceInfo(Info); } @@ -294,7 +294,7 @@ bool lcPreview::SetCurrentPiece(PieceInfo* Info, int ColorCode) } lcPiecesLibrary* Library = lcGetPiecesLibrary(); - bool Ready = Library->LoadPieceInfo(Info, lcPieceLoadFlag::Wait | lcPieceLoadFlag::Visible); + bool Ready = Library->LoadPieceInfo(Info, lcPieceLoadPriority::Blocking); if (Ready && Info->IsModel()) Ready = static_cast(Info->GetModel()->EnsureAssetsReady()); diff --git a/common/lc_thumbnailmanager.cpp b/common/lc_thumbnailmanager.cpp index 250ebc70..938687fc 100644 --- a/common/lc_thumbnailmanager.cpp +++ b/common/lc_thumbnailmanager.cpp @@ -136,7 +136,7 @@ void lcThumbnailManager::RefreshRequiredPieces(lcPartThumbnail& Thumbnail) for (PieceInfo* Info : Required) { if (Previous.erase(Info) == 0) - mLibrary->LoadPieceInfo(Info, lcPieceLoadFlag::None); + mLibrary->LoadPieceInfo(Info, lcPieceLoadPriority::Low); } for (PieceInfo* Info : Previous) diff --git a/common/lc_view.cpp b/common/lc_view.cpp index a58b5edb..b24124c3 100644 --- a/common/lc_view.cpp +++ b/common/lc_view.cpp @@ -493,7 +493,7 @@ std::pair, bool> lcView::GetMouseInsertPieceInfo( lcGetPiecesLibrary()->EnsurePieceAssetsReady(Info); if (MovingPiece && Info->GetSynthInfo()) - lcGetPiecesLibrary()->EnsureSynthMeshesReady({ MovingPiece }); + lcGetPiecesLibrary()->WaitForSynthMeshes({ MovingPiece }); lcObjectRayTest ObjectRayTest = RayTest(true, IgnoreSelected); @@ -507,7 +507,7 @@ std::pair, bool> lcView::GetMouseInsertPieceInfo( lcGetPiecesLibrary()->EnsurePieceAssetsReady(MouseInfo); if (MouseInfo->GetSynthInfo()) - lcGetPiecesLibrary()->EnsureSynthMeshesReady({ MousePiece }); + lcGetPiecesLibrary()->WaitForSynthMeshes({ MousePiece }); if (!Retest) break; diff --git a/common/minifig.cpp b/common/minifig.cpp index f26742c2..2099c7bf 100644 --- a/common/minifig.cpp +++ b/common/minifig.cpp @@ -215,7 +215,7 @@ void MinifigWizard::LoadTemplate(const QString& TemplateName) mMinifig.Parts[PartIndex] = Info; if (Info) - Library->LoadPieceInfo(Info, lcPieceLoadFlag::Visible); + Library->LoadPieceInfo(Info, lcPieceLoadPriority::Normal); } std::vector Required; @@ -224,7 +224,7 @@ void MinifigWizard::LoadTemplate(const QString& TemplateName) if (Info) Required.push_back(Info); - Library->EnsurePiecesReady(Required); + Library->WaitForPieces(Required); Calculate(); } @@ -513,7 +513,7 @@ void MinifigWizard::SetPieceInfo(int Type, PieceInfo* Info) mMinifig.Parts[Type] = Info; if (mMinifig.Parts[Type]) - Library->LoadPieceInfo(mMinifig.Parts[Type], lcPieceLoadFlag::Wait | lcPieceLoadFlag::Visible); + Library->LoadPieceInfo(mMinifig.Parts[Type], lcPieceLoadPriority::Blocking); Calculate(); } diff --git a/common/piece.cpp b/common/piece.cpp index b82d2c9d..449642ba 100644 --- a/common/piece.cpp +++ b/common/piece.cpp @@ -86,7 +86,7 @@ void lcPiece::SetPieceInfo(PieceInfo* Info, const QString& ID, bool Wait, bool U mPieceInfo = Info; if (mPieceInfo) - Library->LoadPieceInfo(mPieceInfo, Wait ? lcPieceLoadFlag::Wait | lcPieceLoadFlag::Visible : lcPieceLoadFlag::Visible); + Library->LoadPieceInfo(mPieceInfo, Wait ? lcPieceLoadPriority::Blocking : lcPieceLoadPriority::Normal); if (!ID.isEmpty()) mID = ID; diff --git a/docs/asset-loading.md b/docs/asset-loading.md index 553b9235..6dfaac7b 100644 --- a/docs/asset-loading.md +++ b/docs/asset-loading.md @@ -289,8 +289,9 @@ sequenceDiagram participant Library as Library participant Loader as Loader / UI thread participant Worker as Qt worker - Caller->>Library: LoadPieceInfo(Info, Flags) - Library->>Loader: Acquire reference and queue/promote + Caller->>Library: LoadPieceInfo(Info, LoadPriority) + Library->>Library: Acquire consumer reference + Library->>Loader: Queue/promote Loader->>Worker: Immutable source snapshot Worker-->>Loader: Private mesh data or cached mesh Loader->>Loader: Resolve global colors @@ -338,7 +339,7 @@ buffer upload is not part of asset readiness. `LoadPieceInfo()` acquires one reference even if loading fails. Always pair it with `ReleasePieceInfo()`. Its asynchronous return value means the call did not wait, not that the asset loaded. `AddPieceReference()` retains an identity without -starting work. `EnsurePiecesReady()` deduplicates its input, temporarily holds +starting work. `WaitForPieces()` deduplicates its input, temporarily holds each part, and releases those holds before returning. A part request owns an additional in-flight reference. Removing one instance @@ -384,10 +385,13 @@ ownership. ## Scheduling and waits -Priorities only promote: `Background < Visible < Blocking`. Ordinary thumbnail -requests start Background; asynchronous model pieces and generated meshes start -Visible, regardless of step. Synchronous waits promote their required requests -and pending texture dependencies to Blocking. Shared textures retain the highest +Priorities only promote: `Low < Normal < Blocking`. Ordinary thumbnail +requests start Low; asynchronous model pieces and generated meshes start +Normal, regardless of step. Synchronous waits promote their required requests +and pending texture dependencies to Blocking. The same `lcPieceLoadPriority` enum +is used at the loading entry point and throughout the scheduler. `LoadPieceInfo()` +waits only for Blocking; Low and Normal return immediately. Internal queueing +uses priority solely to rank work and does not wait. Shared textures retain the highest promotion received from any parent or direct wait. There is no consumer identity map, priority demotion, or step-based scheduling. Priority lasts until that request completes or is cancelled, not merely until the requesting view closes. @@ -398,10 +402,12 @@ level per ten seconds of waiting, capped at Blocking, with oldest work winning ties. Completion processing uses promoted priorities. Acquiring a pending asset shares its request rather than submitting another build. -`EnsurePieceReady()` / `EnsurePiecesReady()` wait for the supplied identities, -not the entire library, and return false for failed or cancelled requirements. -They do not expand model containers themselves. `EnsureSynthMeshesReady()` waits -for the specified instances; `EnsureTextureReady()` waits through upload. +`WaitForPieces()` starts or promotes loading as needed, then waits for the supplied +identities, not the entire library. It returns false for failed or cancelled +requirements and does not expand model containers itself. `WaitForSynthMeshes()` +promotes and waits for queued generations of the specified instances; callers +start generations with `QueueSynthMesh()`. The loader's `WaitForTexture()` starts +or promotes decoding as needed and waits through upload and retries. `lcModel::WaitForAssets()` combines recursive part and synth waits and updates bounds, but deliberately ignores their failure results. `EnsureAssetsReady()` adds strict failure reporting. `WaitForLoadQueue()` drains all pending work and @@ -412,13 +418,14 @@ workers, including unrelated assets; it is not a scoped success check. | Need | API and lifetime contract | | --- | --- | | Resolve a name | `FindPiece()` with the intended project and explicit `CreateMissing` / `SearchProjectFolder` arguments. The returned pointer is borrowed; retain it before keeping it beyond its owner's lifetime. | -| Retain and start ordinary asynchronous work | `LoadPieceInfo()` without `lcPieceLoadFlag::Wait`; add `Visible` for model/view work. Pair every acquisition with `ReleasePieceInfo()`, including failures. | +| Retain and start ordinary asynchronous work | `LoadPieceInfo()` with `lcPieceLoadPriority::Low` for background work or `Normal` for model/view work. Pair every acquisition with `ReleasePieceInfo()`, including failures. | +| Retain and synchronously load one identity | `LoadPieceInfo()` with `lcPieceLoadPriority::Blocking`; check its return value and pair the acquisition with `ReleasePieceInfo()`, including failures. | | Retain without starting work | `AddPieceReference()`, paired with `ReleasePieceInfo()`. This does not make geometry ready. | -| Wait for an explicit part set | `EnsurePiecesReady()` (or `EnsurePieceReady()`), check the boolean for strict consumers. Temporary holds end before return, so the caller still needs ownership if it will use the assets afterward. | +| Wait for an explicit part set | `WaitForPieces()` (use `{ Info }` for one identity), check the boolean for strict consumers. Temporary holds end before return, so the caller still needs ownership if it will use the assets afterward. | | Wait for one identity and its container geometry | `EnsurePieceAssetsReady()` settles the identity, then delegates recursive children, synths, and bounds to the model/project API. Retain the identity and its owner throughout. | | Wait for a model, including nested synths | `WaitForAssets()` for permissive output; `EnsureAssetsReady()` for strict preview-style failure reporting. Keep the model and its pieces alive throughout. | -| Replace/wait for generated geometry | `QueueSynthMesh()` / `EnsureSynthMeshesReady()`. Supply live `lcPiece` instances; a shared part mesh or a retained previous generated mesh is not readiness for the replacement. | -| Acquire a texture directly | `FindTexture()` waits for readiness and returns an acquired texture or null. `FindTextureDeferred()` acquires without waiting; `EnsureTextureReady()` waits through GPU upload. Retain the texture during a direct wait and pair acquisition with `ReleaseTexture()`. | +| Replace/wait for generated geometry | `QueueSynthMesh()` / `WaitForSynthMeshes()`. Supply live `lcPiece` instances; a shared part mesh or a retained previous generated mesh is not readiness for the replacement. | +| Acquire a texture directly | `FindTexture()` waits for readiness and returns an acquired texture or null. `FindTextureDeferred()` acquires without waiting. The loader handles GPU upload readiness internally. Retain the texture during a direct wait and pair acquisition with `ReleaseTexture()`. | | Synchronize an entire library operation | `WaitForLoadQueue()`; reserve this for operations that truly need all work drained, such as settings changes. Ordinary previews must use scoped waits. | Scoped waits may process unrelated completions while pumping publication, but