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.