Files
leocad/common/lc_library.h
T
Leonardo Zide 29c1c142df Simplify model metadata updates and discovery names
Update model filenames and descriptions directly in SetModel, removing
metadata-only forwarding through the library and piece manager. Registry
renames remain synchronized in the manager.

Reuse the bounded piece-name normalizer for archive and directory discovery
to remove duplicated conversion loops and avoid writing a terminator past
the name buffer. Preserve original source filenames and archive offsets.

Validated with a warning-free Qt 6.10 Debug build and all 51 offscreen
lifecycle groups; diff whitespace checks pass.
2026-10-19 22:31:17 -07:00

447 lines
18 KiB
C++

#pragma once
#include "lc_context.h"
#include "lc_math.h"
#include "lc_meshloader.h"
#include "lc_result.h"
#include "lc_assettypes.h"
#include "lc_string.h"
class lcPieceData;
class lcPiece;
class lcMesh;
class lcTrainTrackInfo;
struct lcTrainTrackConnectionType;
class lcZipFile;
class lcLibraryMeshData;
struct lcEmbeddedData;
enum class lcStudStyle
{
Plain,
ThinLinesLogo,
OutlineLogo,
SharpTopLogo,
RoundedTopLogo,
FlattenedLogo,
HighContrast,
HighContrastLogo,
Count
};
constexpr bool lcIsHighContrast(lcStudStyle StudStyle)
{
return StudStyle == lcStudStyle::HighContrast || StudStyle == lcStudStyle::HighContrastLogo;
}
enum class lcZipFileType
{
Official,
Unofficial,
StudStyle,
Count
};
enum class lcLibraryFolderType
{
Official,
Unofficial,
Count
};
enum class lcPrimitiveState
{
NotLoaded,
Loading,
Loaded
};
class lcLibraryPrimitive
{
public:
explicit lcLibraryPrimitive(QString&& FileName, const char* Name, lcZipFileType ZipFileType, quint32 ZipFileIndex, bool Stud, bool StudStyle, bool SubFile)
: mFileName(std::move(FileName))
{
lcstrcpy(mName, Name);
mZipFileType = ZipFileType;
mZipFileIndex = ZipFileIndex;
mState = lcPrimitiveState::NotLoaded;
mStud = Stud;
mStudStyle = StudStyle;
mSubFile = SubFile;
}
void SetZipFile(lcZipFileType ZipFileType, quint32 ZipFileIndex)
{
mZipFileType = ZipFileType;
mZipFileIndex = ZipFileIndex;
}
void Unload()
{
mState = lcPrimitiveState::NotLoaded;
mMeshData.Clear();
mLoadError.clear();
}
QString mFileName;
char mName[LC_MAXNAME];
lcZipFileType mZipFileType;
quint32 mZipFileIndex;
lcPrimitiveState mState;
bool mStud;
bool mStudStyle;
bool mSubFile;
lcLibraryMeshData mMeshData;
QString mLoadError;
};
enum class lcLibrarySourceType
{
Library,
StudStyle
};
struct lcLibrarySource
{
lcLibrarySource() = default;
~lcLibrarySource()
{
for (const auto& PrimitiveIt : Primitives)
delete PrimitiveIt.second;
}
lcLibrarySource(const lcLibrarySource&) = delete;
lcLibrarySource(lcLibrarySource&&) = delete;
lcLibrarySource& operator=(const lcLibrarySource&) = delete;
lcLibrarySource& operator=(lcLibrarySource&&) = delete;
lcLibrarySourceType Type;
std::map<std::string, lcLibraryPrimitive*> Primitives;
};
// Coordinates asset sources, loading, settings and shared render buffers.
// Piece/texture managers own identities and references; the loader owns pending work.
// Acquisition, scheduling and publication run on the UI thread; workers use source snapshots.
class lcPiecesLibrary : public QObject
{
Q_OBJECT
public:
lcPiecesLibrary(lcStudStyle StudStyle, bool StudCylinderColorEnabled);
~lcPiecesLibrary();
lcPiecesLibrary(const lcPiecesLibrary&) = delete;
lcPiecesLibrary(lcPiecesLibrary&&) = delete;
lcPiecesLibrary& operator=(const lcPiecesLibrary&) = delete;
lcPiecesLibrary& operator=(lcPiecesLibrary&&) = delete;
// Replaces the current library with an archive or directory and initializes its metadata.
// OfficialLibrary enables archive update checks; directories remain excluded.
bool Load(const QString& LibraryPath, bool ShowProgress, bool OfficialLibrary);
// Loads the bundled fallback library and its default settings.
bool LoadBuiltinPieceLibrary();
// Resolves a file path relative to the library directory without opening it.
QString GetLibraryFilePath(const QString& FileName) const
{
return mLibraryDir.absoluteFilePath(FileName);
}
// Returns the official archive count for update checks, or zero for custom/directory libraries.
int GetOfficialPieceCount() const
{
return mOfficialPieceCount;
}
// Reloads the configured colors and updates the loading mesh.
void LoadColors();
lcStudStyle GetStudStyle() const
{
return mStudStyle;
}
// Drains running workers before changing settings; queues replacement meshes asynchronously.
void UpdateStudStyleAndColors(lcStudStyle StudStyle, bool StudCylinderColorEnabled);
// Normalizes case and slashes, preserving the piece name length limit.
static std::string NormalizePieceDataName(const char* PieceName);
// Returns a borrowed project identity, falling back to the library. No I/O or creation.
lcPieceData* FindPieceData(const char* PieceName, Project* Project);
// Resolves or creates an identity and returns one scoped reference; does not ensure readiness.
lcPieceDataRef AcquirePieceData(const char* PieceName, Project* Project, bool CreateMissing, bool SearchProjectFolder);
// Creates or reuses a local identity and returns one scoped reference for the model.
// Reused data is invalidated before returning.
lcPieceDataRef AcquireModelPieceData(const char* PieceName, Project* Project, bool& Reused);
// Renames the model identity and its index key, rejecting collisions in the same scope.
bool RenameModelPieceData(lcPieceData* PieceData, const char* NewName);
// Rechecks an external/missing identity against the project directory; returns whether it changed.
bool RemapProjectPieceData(lcPieceData* PieceData, const QString& ProjectDirectory, bool IsPreview);
// Removes unreferenced non-library identities.
void RemoveTemporaryPieceData();
// Detaches the project's borrowed index and deletes its unreferenced local identities.
void ReleaseProjectPieceData(Project* OwnerProject);
// Transfers registrations and borrowed indexes, preserving destination name collisions.
void TransferProjectPieceData(Project* Source, Project* Destination);
// Starts or promotes asynchronous loading. Adds no consumer references and never waits.
// Priority controls scheduling only; the caller retains the identity while it needs the result.
void RequestPieceDataLoad(lcPieceData* PieceData, lcLoadPriority LoadPriority);
// Starts or promotes loading, then waits. Adds no consumer references; false means failure/cancellation.
// Does not traverse container children or wait for per-instance synth geometry.
bool WaitForPieceData(const lcPieceDataRef& PieceDataRef);
bool WaitForPieceData(const std::vector<lcPieceDataRef>& PieceDataRefs);
// Also settles container children/synths and updates bounds; the caller retains the identity.
bool EnsurePieceDataAssetsReady(const lcPieceDataRef& PieceData);
// Obsoletes pending work and resets readiness without acquiring a reference.
void InvalidatePieceDataLoad(lcPieceData* PieceData);
// Invalidates the model mesh, releases old geometry and requests an asynchronous rebuild.
void RebuildModelPieceData(lcPieceData* PieceData);
// Sparse diagnostics; an empty error does not imply readiness.
void SetPieceDataLoadError(const lcPieceData* PieceData, QString Error);
QString GetPieceDataLoadError(const lcPieceData* PieceData) const;
void ClearPieceDataLoadError(const lcPieceData* PieceData);
// Returns a borrowed registered texture for a resolved source. No I/O or acquisition.
lcTexture* FindTexture(const lcFileSource& Source);
// Resolves or creates a texture and returns one scoped reference without decoding or uploading.
lcTextureRef AcquireTexture(const char* TextureName, Project* CurrentProject, bool SearchProjectFolder);
lcTextureRef AcquireTexture(const char* TextureName, const std::vector<QString>& SearchDirectories, const std::map<QString, std::shared_ptr<const lcEmbeddedData>>& EmbeddedData, lcFileSource& Source);
// Starts or promotes asynchronous loading; the caller retains the texture. Never waits.
void RequestTextureLoad(lcTexture* Texture, lcLoadPriority LoadPriority);
// Starts or promotes loading, then waits for settlement; the caller retains the texture.
bool WaitForTexture(const lcTextureRef& TextureRef);
// Replaces pending generation for this instance; snapshots control points for workers.
void RequestSynthMesh(lcPiece* Piece);
// Cancels pending generation and removes the instance's diagnostic entry.
void CancelSynthMesh(lcPiece* Piece);
// Promotes and waits for queued generations; false means missing or outdated generated geometry.
// Call RequestSynthMesh() to start a generation; keep the instances alive through the wait.
bool WaitForSynthMeshes(const std::vector<lcPiece*>& Pieces);
// Returns the last generation error; an empty error does not imply readiness.
QString GetSynthMeshError(const lcPiece* Piece) const;
// Returns a borrowed library snapshot in name order. Caller prevents identity teardown.
std::vector<lcPieceData*> GetParts() const;
// Matches library-part descriptions against category keywords.
bool PieceDataInCategory(lcPieceData* PieceData, const char* CategoryKeywords) const;
// Fills borrowed category entries, optionally grouping patterned pieces under their parent.
void GetCategoryEntries(int CategoryIndex, bool GroupPieces, std::vector<lcPieceData*>& SinglePieces, std::vector<lcPieceData*>& GroupedPieces);
void GetCategoryEntries(const char* CategoryKeywords, bool GroupPieces, std::vector<lcPieceData*>& SinglePieces, std::vector<lcPieceData*>& GroupedPieces);
// Returns borrowed visible track parts compatible with the connection type.
std::vector<lcPieceData*> GetVisibleTrainTrackParts(const lcTrainTrackConnectionType& ConnectionType) const;
// Resolves normalized library IDs, preserving input order and duplicates; missing IDs are skipped.
std::vector<lcPieceData*> GetPartsFromSet(const std::vector<std::string>& PartIds) const;
// Returns the library index key, or an empty string for an unregistered/local identity.
std::string GetPartId(const lcPieceData* PieceData) const;
// Queries primitive sources; FindPrimitive() returns a borrowed entry.
bool IsPrimitive(const char* Name) const;
lcLibraryPrimitive* FindPrimitive(const char* Name) const;
// Loads shared primitive data, waiting for another worker when necessary.
lcResult<void> LoadPrimitive(lcLibraryPrimitive* Primitive);
// Opens an owned include on the reading thread, positioned at zero, or returns an error.
// Piece lookup preserves official-first search order; callers decide whether missing includes are fatal.
lcResult<std::unique_ptr<lcFile>> OpenPrimitiveFile(const lcLibraryPrimitive* Primitive);
lcResult<std::unique_ptr<lcFile>> OpenPieceDataFile(const char* PieceName);
// Worker builds poll this shutdown flag without consulting live UI state.
bool ShouldCancelLoading() const
{
return mCancelLoading.load();
}
// Notifies consumers to refresh pending/failed asset status.
void NotifyAssetStatusChanged();
// Returns the library-owned asynchronous thumbnail service.
lcThumbnailManager* GetThumbnailManager() const
{
return mThumbnailManager.get();
}
// Rebuilds shared mesh buffers when dirty, using the supplied render context.
void UpdateBuffers(lcContext* Context);
// Returns borrowed read-only references to the shared vertex and index buffers.
std::pair<const lcVertexBuffer&, const lcIndexBuffer&> GetBuffers() const
{
return { mVertexBuffer, mIndexBuffer };
}
// Returns the borrowed placeholder mesh for pending or failed piece geometry.
lcMesh* GetLoadingMesh() const;
signals:
void AssetStatusChanged();
void PartLoaded(lcPieceData* PieceData);
void PartLoadFailed(lcPieceData* PieceData, const QString& Error);
void GeneratedMeshSettled(lcPieceData* PieceData);
void ColorsLoaded();
protected:
friend class lcAssetLoader;
template<typename T> friend class lcAssetRef;
// Drains the loader and clears pieces before sources, textures and archives.
// Consumers must release references before teardown; the library remains alive during callbacks.
void Unload();
// Internal retain/release primitives for scoped references. Retaining never starts work.
// Piece release also lets the loader cancel unused queued work.
void AddPieceDataReference(lcPieceData* PieceData);
void ReleasePieceData(lcPieceData* PieceData);
void AddTextureReference(lcTexture* Texture);
void ReleaseTexture(lcTexture* Texture);
// Snapshots live piece/model sources on the UI thread for a private worker build.
lcPieceSourceSnapshot SnapshotPieceDataSource(const lcPieceData* PieceData) const;
// Returns a converted private mesh or an error without publishing asset state.
lcPieceBuildResult BuildPieceData(const lcPieceSourceSnapshot& Source);
// Saves geometry using the settings and cache location captured for the build.
void SaveBuiltPieceDataCache(const lcPieceSourceSnapshot& Source, lcMesh& Mesh);
// Returns private decoded pixels/errors without GL upload or publication.
lcTextureBuildResult BuildTextureData(const lcFileSource& Source);
// The loader calls this only when cancelling the current request, before releasing its hold.
void PieceDataLoadCancelled(lcPieceData* PieceData);
// Publishes current results on the UI thread; the loader rejects stale requests first.
// A successful piece result may have no mesh when its geometry is empty.
bool PublishPieceDataMesh(lcPieceData* PieceData, std::unique_ptr<lcMesh> Mesh, QString Error);
bool PublishSynthMesh(lcPiece* Piece, std::unique_ptr<lcMesh> Mesh, QString Error);
// Applies decoded pixels or a decode failure; upload scheduling and retries stay in the loader.
void PublishTexture(lcTexture* Texture, lcTextureBuildResult Result);
// Opens a resolved source on the reading thread, positioned at zero.
// Caller owns the file; archive replacement requires draining workers first.
lcResult<std::unique_ptr<lcFile>> OpenFile(const lcFileSource& Source);
// Loads an external project, rejecting recursive include cycles.
lcResult<Project*> LoadExternalProject(const QFileInfo& FileInfo, bool Preview, bool DeferModelMeshRequests);
// Discovers source entries before workers consume their metadata.
bool OpenArchive(const QString& FileName, lcZipFileType ZipFileType);
bool OpenArchive(std::unique_ptr<lcFile> File, lcZipFileType ZipFileType);
bool OpenDirectory(const QDir& LibraryDir, bool ShowProgress);
// Initializes descriptions from source files and compatible description caches.
void ReadArchiveDescriptions(const QString& OfficialFileName, const QString& UnofficialFileName);
void ReadDirectoryDescriptions(const QFileInfoList (&FileLists)[static_cast<int>(lcLibraryFolderType::Count)], bool ShowProgress);
// Reads/writes compressed archive caches validated against archive checksums.
bool ReadArchiveCacheFile(const QString& FileName, lcMemFile& CacheFile);
bool WriteArchiveCacheFile(const QString& FileName, lcMemFile& CacheFile);
// Restores/saves descriptions in library index order.
bool LoadCacheIndex(const QString& FileName);
bool SaveArchiveCacheIndex(const QString& FileName);
// Reads/writes compressed directory caches with version and size checks.
bool ReadDirectoryCacheFile(const QString& FileName, lcMemFile& CacheFile);
bool WriteDirectoryCacheFile(const QString& FileName, lcMemFile& CacheFile);
// Builds primitive data after dependency tracking selects this worker as its loader.
lcResult<void> LoadPrimitiveData(lcLibraryPrimitive* Primitive);
static bool IsStudPrimitive(const char* FileName);
static bool IsStudStylePrimitive(const char* FileName);
static qint32 MeshCacheSettingsKey(lcStudStyle StudStyle, bool StudCylinderColorEnabled);
// Updates placeholder colors and the active stud-style source after settings change.
void UpdateLoadingMeshColors();
void UpdateStudStyleSource();
// Resolves embedded, project and library texture sources on the UI thread.
static QString FindProjectTextureFile(const QString& ProjectPath, const QString& TextureName);
lcFileSource ResolveTextureSource(const char* TextureName, const std::vector<QString>& SearchDirectories, const std::map<QString, std::shared_ptr<const lcEmbeddedData>>& EmbeddedData, QString& ProjectPath);
// Defers repacking until work settles, then marks buffers dirty and refreshes views.
void ScheduleBufferRepack();
void ReleaseBuffers();
// Library directory, archive readers and primitive sources; workers drain before replacement or teardown.
QDir mLibraryDir;
std::unique_ptr<lcZipFile> mZipFiles[static_cast<int>(lcZipFileType::Count)];
std::vector<std::unique_ptr<lcLibrarySource>> mSources;
bool mHasUnofficial;
// Captured before unofficial additions/overrides; custom libraries skip update checks.
int mOfficialPieceCount = 0;
// Settings captured by worker source snapshots and mesh cache keys.
lcStudStyle mStudStyle;
bool mStudCylinderColorEnabled;
// Identity managers and asynchronous services. Unload() clears them in dependency order.
std::unique_ptr<lcPieceManager> mPieceManager;
std::unique_ptr<lcTextureManager> mTextureManager;
std::unique_ptr<lcAssetLoader> mAssetLoader;
std::unique_ptr<lcThumbnailManager> mThumbnailManager;
// UI-thread live synth instances and their last generation errors (empty on success or retry).
// Instances unregister before destruction; the library does not own them.
std::unordered_map<lcPiece*, QString> mSynthPieces;
// Active dependency edges include both nested loads and waits on other workers.
QMutex mPrimitiveMutex;
QWaitCondition mPrimitiveLoaded;
std::unordered_map<QThread*, std::vector<lcLibraryPrimitive*>> mPrimitiveLoadStacks;
std::unordered_map<lcLibraryPrimitive*, lcLibraryPrimitive*> mPrimitiveDependencies;
// Normalized texture names mapped to disk paths or archive entry indices.
std::map<QString, QString> mTextureDiskEntries;
std::map<QString, int> mTextureArchiveEntries[static_cast<int>(lcZipFileType::Count)];
// Cache location and official/unofficial archive size/timestamp validation data.
QString mCachePath;
qint64 mArchiveCheckSum[4];
// Shared GPU buffers, placeholder geometry and deferred repacking state.
lcVertexBuffer mVertexBuffer;
lcIndexBuffer mIndexBuffer;
bool mBuffersDirty;
std::unique_ptr<lcMesh> mLoadingMesh;
bool mStreamingBuffersPending = false;
bool mBufferRepackScheduled = false;
// Shutdown flag polled by workers while the UI thread drains outstanding builds.
std::atomic_bool mCancelLoading{ false };
};