From 6a8c05acc5cc0cf42d838dab6c5e81ee99a49a07 Mon Sep 17 00:00:00 2001 From: Leonardo Zide Date: Wed, 14 Oct 2026 10:57:30 -0700 Subject: [PATCH] Initialize stud settings and simplify loader bookkeeping Pass effective startup stud settings into the library and apply color preferences before loading. Replace the reload toggle with UpdateStudStyleAndColors, preserve configured settings in the fallback library, and save the selected color config before combined preferences updates. Remove temporary consumer references from synchronous piece waits, the redundant drain mutex, and worker-start queues. Derive loading states from current requests and use explicit piece names for request, queue, and notification storage. Document the updated ownership and settings contracts. Validated with a Debug build, startup style and color exports, 14 asset export checks, and temporary lifecycle checks covering owned waits, failed and duplicate requirements, regeneration, cancellation, pause/resume, reference cleanup, and shutdown. --- common/lc_application.cpp | 52 ++++++-------- common/lc_application.h | 2 +- common/lc_assetloader.cpp | 148 +++++++++++++++++--------------------- common/lc_assetloader.h | 17 ++--- common/lc_library.cpp | 76 +++++++++----------- common/lc_library.h | 7 +- docs/asset-loading.md | 18 +++-- 7 files changed, 150 insertions(+), 170 deletions(-) diff --git a/common/lc_application.cpp b/common/lc_application.cpp index 09e826ff..923eeb96 100644 --- a/common/lc_application.cpp +++ b/common/lc_application.cpp @@ -333,10 +333,10 @@ void lcApplication::ExportClipboard(const QByteArray& Clipboard) SetClipboard(Clipboard); } -bool lcApplication::LoadPartsLibrary(const QList>& LibraryPaths, bool OnlyUsePaths) +bool lcApplication::LoadPartsLibrary(const QList>& LibraryPaths, bool OnlyUsePaths, lcStudStyle StudStyle, bool StudCylinderColorEnabled) { if (mLibrary == nullptr) - mLibrary = new lcPiecesLibrary(); + mLibrary = new lcPiecesLibrary(StudStyle, StudCylinderColorEnabled); const bool ShowProgress = gMainWindow != nullptr; @@ -985,7 +985,21 @@ lcStartupMode lcApplication::Initialize(const QList>& Libra lcLoadDefaultMouseShortcuts(); } - if (!LoadPartsLibrary(Options.LibraryPaths.isEmpty() ? LibraryPaths : Options.LibraryPaths, !Options.LibraryPaths.isEmpty())) + mPreferences.mShadingMode = Options.ShadingMode; + mPreferences.mLineWidth = Options.LineWidth; + mPreferences.mStudCylinderColorEnabled = Options.StudCylinderColorEnabled; + mPreferences.mStudCylinderColor = Options.StudCylinderColor; + mPreferences.mPartEdgeColorEnabled = Options.PartEdgeColorEnabled; + mPreferences.mPartEdgeColor = Options.PartEdgeColor; + mPreferences.mBlackEdgeColorEnabled = Options.BlackEdgeColorEnabled; + mPreferences.mBlackEdgeColor = Options.BlackEdgeColor; + mPreferences.mDarkEdgeColorEnabled = Options.DarkEdgeColorEnabled; + mPreferences.mDarkEdgeColor = Options.DarkEdgeColor; + mPreferences.mPartEdgeContrast = Options.PartEdgeContrast; + mPreferences.mPartColorValueLDIndex = Options.PartColorValueLDIndex; + mPreferences.mAutomateEdgeColor = Options.AutomateEdgeColor; + + if (!LoadPartsLibrary(Options.LibraryPaths.isEmpty() ? LibraryPaths : Options.LibraryPaths, !Options.LibraryPaths.isEmpty(), Options.StudStyle, Options.StudCylinderColorEnabled)) { QString Message; @@ -1003,22 +1017,6 @@ lcStartupMode lcApplication::Initialize(const QList>& Libra } } - mPreferences.mShadingMode = Options.ShadingMode; - mPreferences.mLineWidth = Options.LineWidth; - mPreferences.mStudCylinderColorEnabled = Options.StudCylinderColorEnabled; - mPreferences.mStudCylinderColor = Options.StudCylinderColor; - mPreferences.mPartEdgeColorEnabled = Options.PartEdgeColorEnabled; - mPreferences.mPartEdgeColor = Options.PartEdgeColor; - mPreferences.mBlackEdgeColorEnabled = Options.BlackEdgeColorEnabled; - mPreferences.mBlackEdgeColor = Options.BlackEdgeColor; - mPreferences.mDarkEdgeColorEnabled = Options.DarkEdgeColorEnabled; - mPreferences.mDarkEdgeColor = Options.DarkEdgeColor; - mPreferences.mPartEdgeContrast = Options.PartEdgeContrast; - mPreferences.mPartColorValueLDIndex = Options.PartColorValueLDIndex; - mPreferences.mAutomateEdgeColor = Options.AutomateEdgeColor; - - lcGetPiecesLibrary()->SetStudStyle(Options.StudStyle, false, Options.StudCylinderColorEnabled); - if (!SaveAndExit) gMainWindow->CreateWidgets(); @@ -1492,18 +1490,14 @@ void lcApplication::ShowPreferencesDialog() } } - if (StudStyleChanged || StudCylinderColorEnabledChanged) - { - lcSetProfileStudStyle(Options.StudStyle); - lcGetPiecesLibrary()->SetStudStyle(Options.StudStyle, true, Options.Preferences.mStudCylinderColorEnabled); - } - else if (ColorConfigChanged || EdgeColorSettingsChanged) - { - if (ColorConfigChanged) - lcSetProfileString(LC_PROFILE_COLOR_CONFIG, Options.ColorConfigPath); + // UpdateStudStyleAndColors() reloads colors too; both paths must see the newly selected config. + if (ColorConfigChanged) + lcSetProfileString(LC_PROFILE_COLOR_CONFIG, Options.ColorConfigPath); + if (StudStyleChanged || StudCylinderColorEnabledChanged) + lcGetPiecesLibrary()->UpdateStudStyleAndColors(Options.StudStyle, Options.Preferences.mStudCylinderColorEnabled); + else if (ColorConfigChanged || EdgeColorSettingsChanged) lcGetPiecesLibrary()->LoadColors(); - } gMainWindow->SetShadingMode(Options.Preferences.mShadingMode); lcView::UpdateAllViews(); diff --git a/common/lc_application.h b/common/lc_application.h index c0ab2aeb..41179654 100644 --- a/common/lc_application.h +++ b/common/lc_application.h @@ -195,7 +195,7 @@ public: void ShowPreferencesDialog(); void SaveTabLayout() const; - bool LoadPartsLibrary(const QList>& LibraryPaths, bool OnlyUsePaths); + bool LoadPartsLibrary(const QList>& LibraryPaths, bool OnlyUsePaths, lcStudStyle StudStyle, bool StudCylinderColorEnabled); void SetClipboard(const QByteArray& Clipboard); void ExportClipboard(const QByteArray& Clipboard); diff --git a/common/lc_assetloader.cpp b/common/lc_assetloader.cpp index 479f08b5..d1ec5336 100644 --- a/common/lc_assetloader.cpp +++ b/common/lc_assetloader.cpp @@ -37,8 +37,8 @@ bool lcAssetLoader::LoadPieceInfo(PieceInfo* Info, lcPieceLoadPriority LoadPrior else if (LoadPriority != lcPieceLoadPriority::Low) { mQueueMutex.lock(); - const auto It = mRequests.find(Info); - if (It != mRequests.end()) + const auto It = mPieceRequests.find(Info); + if (It != mPieceRequests.end()) PromoteRequestPriorityLocked(It->second, LoadPriority); mQueueMutex.unlock(); } @@ -53,9 +53,9 @@ void lcAssetLoader::EnqueuePiece(PieceInfo* Info, lcPieceLoadPriority LoadPriori if (mStopping) return; - const auto Existing = mRequests.find(Info); + const auto Existing = mPieceRequests.find(Info); - if (Existing != mRequests.end()) + if (Existing != mPieceRequests.end()) { PromoteRequestPriorityLocked(Existing->second, LoadPriority); @@ -88,9 +88,9 @@ void lcAssetLoader::EnqueuePiece(PieceInfo* Info, lcPieceLoadPriority LoadPriori RequestedPiece->LoadPriority = LoadPriority; RequestedPiece->EnqueuedAt = QDateTime::currentMSecsSinceEpoch(); - mRequests.emplace(Info, RequestedPiece); + mPieceRequests.emplace(Info, RequestedPiece); - mQueue.push_back(RequestedPiece); + mPieceQueue.push_back(RequestedPiece); StartWorkersLocked(); emit mLibrary->AssetRequestsChanged(); @@ -191,7 +191,7 @@ void lcAssetLoader::StartWorkersLocked() }), mFutures.end()); const int MaxWorkers = qMax(1, qMin(4, QThread::idealThreadCount())); - const int Queued = static_cast(mQueue.size() + mTextureQueue.size() + mSynthQueue.size()); + const int Queued = static_cast(mPieceQueue.size() + mTextureQueue.size() + mSynthQueue.size()); const int Additional = mPaused || mStopping ? 0 : qMin(MaxWorkers - mActiveWorkers, Queued); for (int Worker = 0; Worker < Additional; Worker++) @@ -212,7 +212,7 @@ void lcAssetLoader::RunWorker() { QMutexLocker QueueLock(&mQueueMutex); - if (mStopping || mPaused || (mQueue.empty() && mTextureQueue.empty() && mSynthQueue.empty())) + if (mStopping || mPaused || (mPieceQueue.empty() && mTextureQueue.empty() && mSynthQueue.empty())) { mActiveWorkers--; mResultReady.wakeAll(); @@ -234,19 +234,18 @@ void lcAssetLoader::RunWorker() return Score(Left) < Score(Right) || (Score(Left) == Score(Right) && Left->EnqueuedAt > Right->EnqueuedAt); }; - auto PartBest = std::max_element(mQueue.begin(), mQueue.end(), Less); + auto PartBest = std::max_element(mPieceQueue.begin(), mPieceQueue.end(), Less); auto TextureBest = std::max_element(mTextureQueue.begin(), mTextureQueue.end(), Less); auto SynthBest = std::max_element(mSynthQueue.begin(), mSynthQueue.end(), Less); - if (TextureBest != mTextureQueue.end() && (PartBest == mQueue.end() || !Less(*TextureBest, *PartBest)) && + if (TextureBest != mTextureQueue.end() && (PartBest == mPieceQueue.end() || !Less(*TextureBest, *PartBest)) && (SynthBest == mSynthQueue.end() || !Less(*TextureBest, *SynthBest))) { RequestedTexture = *TextureBest; mTextureQueue.erase(TextureBest); RequestedTexture->Running = true; - mStartedTextures.push_back(RequestedTexture); } - else if (SynthBest != mSynthQueue.end() && (PartBest == mQueue.end() || !Less(*SynthBest, *PartBest))) + else if (SynthBest != mSynthQueue.end() && (PartBest == mPieceQueue.end() || !Less(*SynthBest, *PartBest))) { RequestedSynth = *SynthBest; mSynthQueue.erase(SynthBest); @@ -255,9 +254,8 @@ void lcAssetLoader::RunWorker() else { RequestedPiece = *PartBest; - mQueue.erase(PartBest); + mPieceQueue.erase(PartBest); RequestedPiece->Running = true; - mStarted.push_back(RequestedPiece); } } @@ -333,30 +331,25 @@ bool lcAssetLoader::ProcessCompletions() { QMutexLocker QueueLock(&mQueueMutex); - while (!mStarted.empty()) + // Reflect worker starts in asset states on the UI thread. + if (!mStopping) { - const std::shared_ptr RequestedPiece = mStarted.front(); + for (const PieceRequestMap::value_type& Entry : mPieceRequests) + { + const std::shared_ptr& RequestedPiece = Entry.second; - mStarted.pop_front(); + if (RequestedPiece->Running && RequestedPiece->Info->mState == lcPieceInfoState::Queued) + RequestedPiece->Info->mState = lcPieceInfoState::Loading; + } - const auto It = mRequests.find(RequestedPiece->Info); + for (const TextureRequestMap::value_type& Entry : mTextureRequests) + { + const std::shared_ptr& RequestedTexture = Entry.second; - if (!mStopping && It != mRequests.end() && It->second == RequestedPiece && RequestedPiece->Info->mState == lcPieceInfoState::Queued) - RequestedPiece->Info->mState = lcPieceInfoState::Loading; + if (RequestedTexture->Running && RequestedTexture->Texture->mState == lcTextureState::Queued) + RequestedTexture->Texture->mState = lcTextureState::Decoding; + } } - - while (!mStartedTextures.empty()) - { - const std::shared_ptr RequestedTexture = mStartedTextures.front(); - - mStartedTextures.pop_front(); - - const auto It = mTextureRequests.find(RequestedTexture->Texture); - - if (!mStopping && It != mTextureRequests.end() && It->second == RequestedTexture && RequestedTexture->Texture->mState == lcTextureState::Queued) - RequestedTexture->Texture->mState = lcTextureState::Decoding; - } - } constexpr int MaxCompletionsPerPump = 16; @@ -446,10 +439,10 @@ void lcAssetLoader::CompletePiece(Completion& Completed) bool Stale; mQueueMutex.lock(); - const auto It = mRequests.find(Info); - Stale = mStopping || RequestedPiece->Obsolete || Completed.Result.Generation != mGeneration || It == mRequests.end() || It->second != RequestedPiece; - if (Stale && It != mRequests.end() && It->second == RequestedPiece) - mRequests.erase(It); + const auto It = mPieceRequests.find(Info); + Stale = mStopping || RequestedPiece->Obsolete || Completed.Result.Generation != mGeneration || It == mPieceRequests.end() || It->second != RequestedPiece; + if (Stale && It != mPieceRequests.end() && It->second == RequestedPiece) + mPieceRequests.erase(It); mQueueMutex.unlock(); RequestedPiece->Running = false; @@ -461,7 +454,7 @@ void lcAssetLoader::CompletePiece(Completion& Completed) if (!Stale) { mQueueMutex.lock(); - mRequests.erase(Info); + mPieceRequests.erase(Info); mQueueMutex.unlock(); } @@ -491,7 +484,7 @@ void lcAssetLoader::CompletePiece(Completion& Completed) QMutexLocker QueueLock(&mQueueMutex); - mQueue.push_back(RequestedPiece); + mPieceQueue.push_back(RequestedPiece); StartWorkersLocked(); return; @@ -506,7 +499,7 @@ void lcAssetLoader::CompletePiece(Completion& Completed) QMutexLocker QueueLock(&mQueueMutex); - mQueue.push_back(RequestedPiece); + mPieceQueue.push_back(RequestedPiece); StartWorkersLocked(); return; @@ -810,7 +803,7 @@ void lcAssetLoader::FinishPiecesWithSettledTextures() { QMutexLocker QueueLock(&mQueueMutex); - for (const PieceRequestMap::value_type& Entry : mRequests) + for (const PieceRequestMap::value_type& Entry : mPieceRequests) if (Entry.second->StagedMesh) Waiting.push_back(Entry.second); } @@ -878,16 +871,16 @@ void lcAssetLoader::FinishPiece(const std::shared_ptr& RequestedPi } mQueueMutex.lock(); - const auto It = mRequests.find(Info); - if (It != mRequests.end() && It->second == RequestedPiece) - mRequests.erase(It); + const auto It = mPieceRequests.find(Info); + if (It != mPieceRequests.end() && It->second == RequestedPiece) + mPieceRequests.erase(It); RequestedPiece->Terminal = true; RequestedPiece->Succeeded = Loaded; mResultReady.wakeAll(); mQueueMutex.unlock(); mLibrary->ScheduleBufferRepack(); - mNotifications.push_back({ RequestedPiece, Loaded, std::move(LoadError) }); + mPieceNotifications.push_back({ RequestedPiece, Loaded, std::move(LoadError) }); QCoreApplication::postEvent(this, new QEvent(lcAssetLoaderCompletionEvent)); } @@ -934,11 +927,11 @@ void lcAssetLoader::DispatchNotifications() constexpr int MaxNotificationsPerPump = 32; - for (int Delivered = 0; Delivered < MaxNotificationsPerPump && !mNotifications.empty(); Delivered++) + for (int Delivered = 0; Delivered < MaxNotificationsPerPump && !mPieceNotifications.empty(); Delivered++) { - Notification Notification = std::move(mNotifications.front()); + PieceNotification Notification = std::move(mPieceNotifications.front()); - mNotifications.pop_front(); + mPieceNotifications.pop_front(); PieceInfo* Info = Notification.RequestedPiece->Info; @@ -987,7 +980,7 @@ void lcAssetLoader::DispatchNotifications() mLibrary->ReleasePieceLoadHold(Info); } - if (!mNotifications.empty() || !mSynthNotifications.empty()) + if (!mPieceNotifications.empty() || !mSynthNotifications.empty()) QCoreApplication::postEvent(this, new QEvent(lcAssetLoaderCompletionEvent)); if (!SettledSynths.empty()) @@ -1006,6 +999,7 @@ bool lcAssetLoader::event(QEvent* Event) return QObject::event(Event); } + void lcAssetLoader::WaitForResultsLocked() { if (!mCompletions.empty()) @@ -1041,9 +1035,9 @@ bool lcAssetLoader::WaitForRequest(PieceInfo* Info) { QMutexLocker QueueLock(&mQueueMutex); - const auto It = mRequests.find(Info); + const auto It = mPieceRequests.find(Info); - if (It != mRequests.end()) + if (It != mPieceRequests.end()) { RequestedPiece = It->second; @@ -1088,9 +1082,6 @@ bool lcAssetLoader::WaitForPieces(const std::vector& Pieces) Required.erase(std::unique(Required.begin(), Required.end()), Required.end()); - for (PieceInfo* Info : Required) - mLibrary->AddPieceReference(Info); - for (PieceInfo* Info : Required) { if (Info->mState != lcPieceInfoState::Unloaded) @@ -1107,9 +1098,9 @@ bool lcAssetLoader::WaitForPieces(const std::vector& Pieces) for (PieceInfo* Info : Required) { - const auto It = mRequests.find(Info); + const auto It = mPieceRequests.find(Info); - if (It == mRequests.end()) + if (It == mPieceRequests.end()) continue; PromoteRequestPriorityLocked(It->second, lcPieceLoadPriority::Blocking); @@ -1123,9 +1114,6 @@ bool lcAssetLoader::WaitForPieces(const std::vector& Pieces) for (PieceInfo* Info : Required) Ready = WaitForRequest(Info) && Ready; - for (PieceInfo* Info : Required) - mLibrary->ReleasePieceInfo(Info); - return Ready; } @@ -1225,15 +1213,13 @@ void lcAssetLoader::WaitForLoadQueue() { Q_ASSERT(QThread::currentThread() == thread()); - QMutexLocker DrainLock(&mDrainMutex); - for (;;) { ProcessCompletions(); QMutexLocker QueueLock(&mQueueMutex); - if (mRequests.empty() && mTextureRequests.empty() && mSynthRequests.empty() && mActiveWorkers == 0 && mCompletions.empty()) + if (mPieceRequests.empty() && mTextureRequests.empty() && mSynthRequests.empty() && mActiveWorkers == 0 && mCompletions.empty()) break; WaitForResultsLocked(); @@ -1248,7 +1234,7 @@ void lcAssetLoader::WaitForLoadQueue() bool lcAssetLoader::HasPendingWork() { mQueueMutex.lock(); - const bool Pending = !mRequests.empty() || !mTextureRequests.empty() || !mSynthRequests.empty() || !mCompletions.empty(); + const bool Pending = !mPieceRequests.empty() || !mTextureRequests.empty() || !mSynthRequests.empty() || !mCompletions.empty(); mQueueMutex.unlock(); return Pending; @@ -1294,21 +1280,21 @@ void lcAssetLoader::InvalidatePiece(PieceInfo* Info) { QMutexLocker QueueLock(&mQueueMutex); - const auto It = mRequests.find(Info); + const auto It = mPieceRequests.find(Info); - if (It != mRequests.end()) + if (It != mPieceRequests.end()) { Removed = It->second; Removed->Obsolete = true; Removed->Terminal = true; Removed->Succeeded = false; ReleaseHold = !Removed->Running; - mRequests.erase(It); + mPieceRequests.erase(It); - mQueue.erase(std::remove(mQueue.begin(), mQueue.end(), Removed), mQueue.end()); + mPieceQueue.erase(std::remove(mPieceQueue.begin(), mPieceQueue.end(), Removed), mPieceQueue.end()); } - for (Notification& Pending : mNotifications) + for (PieceNotification& Pending : mPieceNotifications) if (Pending.RequestedPiece->Info == Info) Pending.RequestedPiece->Obsolete = true; @@ -1343,9 +1329,9 @@ void lcAssetLoader::OnConsumerReleased(PieceInfo* Info) { QMutexLocker QueueLock(&mQueueMutex); - const auto It = mRequests.find(Info); + const auto It = mPieceRequests.find(Info); - if (It == mRequests.end()) + if (It == mPieceRequests.end()) return; const std::shared_ptr& RequestedPiece = It->second; @@ -1361,9 +1347,9 @@ void lcAssetLoader::OnConsumerReleased(PieceInfo* Info) Info->mState = lcPieceInfoState::Cancelled; - mRequests.erase(It); + mPieceRequests.erase(It); - mQueue.erase(std::remove(mQueue.begin(), mQueue.end(), Removed), mQueue.end()); + mPieceQueue.erase(std::remove(mPieceQueue.begin(), mPieceQueue.end(), Removed), mPieceQueue.end()); mResultReady.wakeAll(); } @@ -1487,7 +1473,7 @@ void lcAssetLoader::PauseQueuedWork() // A mesh awaiting textures was built with the previous stud settings. // Rebuild it after the settings change instead of publishing stale geometry. - for (const PieceRequestMap::value_type& Entry : mRequests) + for (const PieceRequestMap::value_type& Entry : mPieceRequests) { const std::shared_ptr& RequestedPiece = Entry.second; @@ -1505,7 +1491,7 @@ void lcAssetLoader::PauseQueuedWork() RequestedPiece->StagedMesh.reset(); RequestedPiece->Info->mState = lcPieceInfoState::Queued; RequestedPiece->Running = false; - mQueue.push_back(RequestedPiece); + mPieceQueue.push_back(RequestedPiece); } // Pending generated meshes are restarted after the new stud style is installed. @@ -1532,7 +1518,7 @@ void lcAssetLoader::ResumeQueuedWork() QMutexLocker QueueLock(&mQueueMutex); - for (const std::shared_ptr& RequestedPiece : mQueue) + for (const std::shared_ptr& RequestedPiece : mPieceQueue) { const bool SkipCache = RequestedPiece->Source.SkipCache; @@ -1560,13 +1546,13 @@ void lcAssetLoader::CancelAndDrain() mStopping = true; mGeneration++; - mQueue.clear(); + mPieceQueue.clear(); mTextureQueue.clear(); mSynthQueue.clear(); mSynthPieces.clear(); mFailedSynthErrors.clear(); - for (auto It = mRequests.begin(); It != mRequests.end();) + for (auto It = mPieceRequests.begin(); It != mPieceRequests.end();) { if (It->second->Running) { @@ -1581,7 +1567,7 @@ void lcAssetLoader::CancelAndDrain() Cancelled.push_back(It->second); - It = mRequests.erase(It); + It = mPieceRequests.erase(It); } } @@ -1622,11 +1608,11 @@ void lcAssetLoader::CancelAndDrain() WaitForLoadQueue(); - while (!mNotifications.empty()) + while (!mPieceNotifications.empty()) { - Notification Notification = std::move(mNotifications.front()); + PieceNotification Notification = std::move(mPieceNotifications.front()); - mNotifications.pop_front(); + mPieceNotifications.pop_front(); mLibrary->ReleasePieceLoadHold(Notification.RequestedPiece->Info); } diff --git a/common/lc_assetloader.h b/common/lc_assetloader.h index fc6da341..b43875ba 100644 --- a/common/lc_assetloader.h +++ b/common/lc_assetloader.h @@ -35,6 +35,7 @@ struct lcPieceBuildResult { lcPieceBuildResult() = default; ~lcPieceBuildResult(); + lcPieceBuildResult(lcPieceBuildResult&&) = default; lcPieceBuildResult& operator=(lcPieceBuildResult&&) = default; lcPieceBuildResult(const lcPieceBuildResult&) = delete; @@ -59,7 +60,7 @@ public: lcAssetLoader(lcPiecesLibrary* Library); ~lcAssetLoader() override; - // Load a piece the library has already acquired for the caller; this adds no consumer reference. + // Load a piece the library has already acquired for the caller. 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); @@ -68,7 +69,7 @@ public: // Cancel any older request and keep the previous mesh visible while the replacement is built. void QueueSynthMesh(lcPiece* Piece); - // Start or promote loading as needed, then wait. Returns true if all pieces are ready. + // Start or promote loading, then wait and returns true if all pieces are ready. Adds no consumer references. // Waits for these identities only, without traversing container children or generated meshes. bool WaitForPieces(const std::vector& Pieces); @@ -174,7 +175,7 @@ private: }; // UI-thread delivery retains the load hold after publication, until callbacks finish. - struct Notification + struct PieceNotification { std::shared_ptr RequestedPiece; bool Loaded; @@ -229,23 +230,19 @@ private: // Private build data passes between worker and UI phases through completions. QMutex mQueueMutex; QWaitCondition mResultReady; - // Serializes full queue drains; scoped readiness waits do not acquire this mutex. - QMutex mDrainMutex; - PieceRequestMap mRequests; + PieceRequestMap mPieceRequests; TextureRequestMap mTextureRequests; SynthRequestMap mSynthRequests; // UI-thread diagnostics and instance tracking; these do not own the pieces. std::unordered_map mFailedSynthErrors; std::unordered_set mSynthPieces; // Pending worker phases remain here while paused; mPaused prevents dispatch. - std::deque> mQueue; + std::deque> mPieceQueue; std::deque> mTextureQueue; std::deque> mSynthQueue; std::deque mCompletions; - std::deque> mStarted; - std::deque> mStartedTextures; // UI-thread only; public signals are deferred until outside synchronous waits. - std::deque mNotifications; + std::deque mPieceNotifications; std::deque mSynthNotifications; std::vector> mFutures; int mActiveWorkers = 0; diff --git a/common/lc_library.cpp b/common/lc_library.cpp index 09e88798..7c19f146 100644 --- a/common/lc_library.cpp +++ b/common/lc_library.cpp @@ -37,7 +37,8 @@ enum class lcLibraryCacheFlag : quint32 Directory = 0x0002 }; -lcPiecesLibrary::lcPiecesLibrary() +lcPiecesLibrary::lcPiecesLibrary(lcStudStyle StudStyle, bool StudCylinderColorEnabled) + : mStudStyle(StudStyle), mStudCylinderColorEnabled(StudCylinderColorEnabled) { mTextureManager = std::make_unique(); mAssetLoader = std::unique_ptr(new lcAssetLoader(this)); @@ -53,8 +54,6 @@ lcPiecesLibrary::lcPiecesLibrary() mNumOfficialPieces = 0; mBuffersDirty = false; mHasUnofficial = false; - mStudStyle = lcGetProfileStudStyle(); - mStudCylinderColorEnabled = lcGetProfileInt(LC_PROFILE_STUD_CYLINDER_COLOR_ENABLED); } lcPiecesLibrary::~lcPiecesLibrary() @@ -2279,7 +2278,7 @@ bool lcPiecesLibrary::SupportsStudStyle() const return true; } -void lcPiecesLibrary::SetStudStyle(lcStudStyle StudStyle, bool Reload, bool StudCylinderColorEnabled) +void lcPiecesLibrary::UpdateStudStyleAndColors(lcStudStyle StudStyle, bool StudCylinderColorEnabled) { if (mStudStyle == StudStyle && mStudCylinderColorEnabled == StudCylinderColorEnabled) return; @@ -2289,7 +2288,6 @@ void lcPiecesLibrary::SetStudStyle(lcStudStyle StudStyle, bool Reload, bool Stud mAssetLoader->PauseQueuedWork(); mStudStyle = StudStyle; - mStudCylinderColorEnabled = StudCylinderColorEnabled; LoadColors(); @@ -2308,47 +2306,40 @@ void lcPiecesLibrary::SetStudStyle(lcStudStyle StudStyle, bool Reload, bool Stud } } + const auto ReloadPiece = [this](PieceInfo* Info) + { + if (Info->mState != lcPieceInfoState::Loaded || !Info->GetMesh() || !(Info->GetMesh()->mFlags & lcMeshFlag::HasStyleStud)) + return; + + if (Info->IsModel()) + { + Info->ReleaseMesh(); + Info->mState = lcPieceInfoState::Unloaded; + + mAssetLoader->LoadPieceInfo(Info, lcPieceLoadPriority::Low); + } + else if (!Info->IsProject()) + { + Info->Unload(); + mAssetLoader->LoadPieceInfo(Info, lcPieceLoadPriority::Low); + } + + mBuffersDirty = true; + }; + + for (const std::map::value_type& PieceIt : mPieces) + ReloadPiece(PieceIt.second); + + for (const std::unordered_map::value_type& Local : mProjectPieces) + ReloadPiece(Local.first); + mLoadMutex.unlock(); - if (Reload) - { - mLoadMutex.lock(); - - const auto ReloadPiece = [this](PieceInfo* Info) - { - if (Info->mState != lcPieceInfoState::Loaded || !Info->GetMesh() || !(Info->GetMesh()->mFlags & lcMeshFlag::HasStyleStud)) - return; - - if (Info->IsModel()) - { - Info->ReleaseMesh(); - Info->mState = lcPieceInfoState::Unloaded; - - mAssetLoader->LoadPieceInfo(Info, lcPieceLoadPriority::Low); - } - else if (!Info->IsProject()) - { - Info->Unload(); - mAssetLoader->LoadPieceInfo(Info, lcPieceLoadPriority::Low); - } - - mBuffersDirty = true; - }; - - for (const std::map::value_type& PieceIt : mPieces) - ReloadPiece(PieceIt.second); - - for (const std::unordered_map::value_type& Local : mProjectPieces) - ReloadPiece(Local.first); - - mLoadMutex.unlock(); - mAssetLoader->ReloadSynthMeshes(); - } + mAssetLoader->ReloadSynthMeshes(); mAssetLoader->ResumeQueuedWork(); - if (Reload) - WaitForLoadQueue(); + WaitForLoadQueue(); } bool lcPiecesLibrary::IsPrimitive(const char* Name) const @@ -2720,8 +2711,9 @@ bool lcPiecesLibrary::LoadBuiltinPieces() } } - lcLoadDefaultColors(lcStudStyle::Plain); + lcLoadDefaultColors(mStudStyle); UpdateLoadingMeshColors(); + UpdateStudStyleSource(); lcLoadDefaultCategories(true); lcSynthInit(); lcTrainTrackInfo::Initialize(this); diff --git a/common/lc_library.h b/common/lc_library.h index 1d88bd2a..cc8b51af 100644 --- a/common/lc_library.h +++ b/common/lc_library.h @@ -159,7 +159,7 @@ class lcPiecesLibrary : public QObject Q_OBJECT public: - lcPiecesLibrary(); + lcPiecesLibrary(lcStudStyle StudStyle, bool StudCylinderColorEnabled); ~lcPiecesLibrary(); lcPiecesLibrary(const lcPiecesLibrary&) = delete; @@ -193,7 +193,8 @@ public: // Blocking waits and reports success; Low and Normal return immediately. See docs/asset-loading.md. bool LoadPieceInfo(PieceInfo* Info, lcPieceLoadPriority LoadPriority); void NotifyConsumersChanged(); - // Start or promote loading as needed, then wait with temporary holds; false means failure/cancellation. + // Start or promote loading, then wait. Adds no consumer references. + // Returns false for failed or cancelled pieces. // Does not traverse container children or wait for per-instance synth geometry. bool WaitForPieces(const std::vector& Pieces); // Also settles container children/synths and updates bounds; caller retains the identity. @@ -254,7 +255,7 @@ public: lcResult LoadPrimitive(lcLibraryPrimitive* Primitive); bool SupportsStudStyle() const; - void SetStudStyle(lcStudStyle StudStyle, bool Reload, bool StudCylinderColorEnabled); + void UpdateStudStyleAndColors(lcStudStyle StudStyle, bool StudCylinderColorEnabled); lcStudStyle GetStudStyle() const { diff --git a/docs/asset-loading.md b/docs/asset-loading.md index 6dfaac7b..9d974d7e 100644 --- a/docs/asset-loading.md +++ b/docs/asset-loading.md @@ -339,8 +339,8 @@ 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. `WaitForPieces()` deduplicates its input, temporarily holds -each part, and releases those holds before returning. +starting work. `WaitForPieces()` deduplicates its input without acquiring consumer +references; callers retain every required identity and its owner through the wait. A part request owns an additional in-flight reference. Removing one instance cannot destroy an asset still used by another instance or pending work. On the @@ -404,7 +404,9 @@ shares its request rather than submitting another build. `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()` +requirements and does not expand model containers itself. Callers retain every +required identity through the wait; it does not acquire consumer references. +`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. @@ -421,7 +423,7 @@ workers, including unrelated assets; it is not a scoped success check. | 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 | `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 an explicit part set | `WaitForPieces()` (use `{ Info }` for one identity), check the boolean for strict consumers. Retain every identity and its owner throughout the wait; this call acquires no consumer references. | | 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()` / `WaitForSynthMeshes()`. Supply live `lcPiece` instances; a shared part mesh or a retained previous generated mesh is not readiness for the replacement. | @@ -522,6 +524,14 @@ Pending jobs remain in the normal piece, texture, and synth queues while paused; `mPaused` prevents dispatch. Resume refreshes queued piece source snapshots before allowing workers to take jobs again. +The library constructor receives the effective startup stud settings; application +color preferences are applied before loading sources and colors, including the +built-in fallback library. `UpdateStudStyleAndColors()` handles runtime changes and always +rebuilds affected geometry and drains the resulting work when settings change. +The preferences dialog saves a changed color-config path before this setter runs, +so simultaneous stud/color changes load the new configuration once. Color-only +changes call `LoadColors()` directly. + Stud-style changes pause dispatch, finish running phases, discard staging built with old settings, refresh sources, and rebuild affected published meshes and generated geometry. The existing style-change path resumes and drains work.