Unify asset loading priorities and clarify wait APIs

Replace load flags and the loader's separate priority enum with Low, Normal, and Blocking priorities. Blocking loads wait for completion; asynchronous loads and internal scheduling retain promotion-only behavior.

Acquire consumer references in the library, reuse loader loading for stud-style rebuilds, and remove QueuePiece, single-piece readiness wrappers, and the library texture readiness wrapper. Rename scoped readiness APIs to WaitFor functions and document their scheduling and lifetime contracts.

Validated with a Debug build and a temporary lifecycle harness covering priority promotion, blocking loads, reference cleanup, selective style reloads, cancellation, pause/resume, and shutdown.
This commit is contained in:
Leonardo Zide committed 2026-10-13 23:27:29 -07:00
1 parent 2051eeed42
commit 3ec8b274b1
12 files changed
+117 -133

No files matched your search

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