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.
This commit is contained in:
Leonardo Zide committed 2026-10-14 10:57:30 -07:00
1 parent 3ec8b274b1
commit 6a8c05acc5
7 files changed
+150 -170

No files matched your search

+23 -29
View File
@@ -333,10 +333,10 @@ void lcApplication::ExportClipboard(const QByteArray& Clipboard)
SetClipboard(Clipboard);
}
bool lcApplication::LoadPartsLibrary(const QList<QPair<QString, bool>>& LibraryPaths, bool OnlyUsePaths)
bool lcApplication::LoadPartsLibrary(const QList<QPair<QString, bool>>& 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<QPair<QString, bool>>& 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<QPair<QString, bool>>& 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();
+1 -1
View File
@@ -195,7 +195,7 @@ public:
void ShowPreferencesDialog();
void SaveTabLayout() const;
bool LoadPartsLibrary(const QList<QPair<QString, bool>>& LibraryPaths, bool OnlyUsePaths);
bool LoadPartsLibrary(const QList<QPair<QString, bool>>& LibraryPaths, bool OnlyUsePaths, lcStudStyle StudStyle, bool StudCylinderColorEnabled);
void SetClipboard(const QByteArray& Clipboard);
void ExportClipboard(const QByteArray& Clipboard);
+67 -81
View File
@@ -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<int>(mQueue.size() + mTextureQueue.size() + mSynthQueue.size());
const int Queued = static_cast<int>(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<PieceRequest> RequestedPiece = mStarted.front();
for (const PieceRequestMap::value_type& Entry : mPieceRequests)
{
const std::shared_ptr<PieceRequest>& 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<TextureRequest>& 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<TextureRequest> 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<PieceRequest>& 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<PieceInfo*>& 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<PieceInfo*>& 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<PieceInfo*>& 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<PieceRequest>& 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<PieceRequest>& 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<PieceRequest>& RequestedPiece : mQueue)
for (const std::shared_ptr<PieceRequest>& 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);
}
+7 -10
View File
@@ -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<PieceInfo*>& 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<PieceRequest> 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<const lcPiece*, QString> mFailedSynthErrors;
std::unordered_set<lcPiece*> mSynthPieces;
// Pending worker phases remain here while paused; mPaused prevents dispatch.
std::deque<std::shared_ptr<PieceRequest>> mQueue;
std::deque<std::shared_ptr<PieceRequest>> mPieceQueue;
std::deque<std::shared_ptr<TextureRequest>> mTextureQueue;
std::deque<std::shared_ptr<SynthRequest>> mSynthQueue;
std::deque<Completion> mCompletions;
std::deque<std::shared_ptr<PieceRequest>> mStarted;
std::deque<std::shared_ptr<TextureRequest>> mStartedTextures;
// UI-thread only; public signals are deferred until outside synchronous waits.
std::deque<Notification> mNotifications;
std::deque<PieceNotification> mPieceNotifications;
std::deque<SynthNotification> mSynthNotifications;
std::vector<QFuture<void>> mFutures;
int mActiveWorkers = 0;
+34 -42
View File
@@ -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<lcTextureManager>();
mAssetLoader = std::unique_ptr<lcAssetLoader>(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<std::string, PieceInfo*>::value_type& PieceIt : mPieces)
ReloadPiece(PieceIt.second);
for (const std::unordered_map<PieceInfo*, Project*>::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<std::string, PieceInfo*>::value_type& PieceIt : mPieces)
ReloadPiece(PieceIt.second);
for (const std::unordered_map<PieceInfo*, Project*>::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);
+4 -3
View File
@@ -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<PieceInfo*>& Pieces);
// Also settles container children/synths and updates bounds; caller retains the identity.
@@ -254,7 +255,7 @@ public:
lcResult<void> LoadPrimitive(lcLibraryPrimitive* Primitive);
bool SupportsStudStyle() const;
void SetStudStyle(lcStudStyle StudStyle, bool Reload, bool StudCylinderColorEnabled);
void UpdateStudStyleAndColors(lcStudStyle StudStyle, bool StudCylinderColorEnabled);
lcStudStyle GetStudStyle() const
{
+14 -4
View File
@@ -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.