mirror of
https://github.com/leozide/leocad.git
synced 2026-10-26 22:10:32 +00:00
Move sparse piece load errors and registry locking into lcPieceManager so registry access, reference bookkeeping and copied worker metadata share one owner. Release registry and error locks before resource teardown, recursive project deletion or loader cancellation; remove library lock scopes around UI source resolution and worker-drained settings changes. Add HasOtherReferences to the shared asset reference for piece and texture consumer checks, removing the library/manager consumer-query wrappers. Keep the post-release cancellation count check and existing lifetime policies. Rename the sparse error map to mPieceDataLoadErrors and document the completed manager boundary and thread/teardown contracts. Validation: warning-free macOS Qt 6.10 Debug build and all 43 offscreen lifecycle checks pass, including concurrent metadata/error reads, settings changes with pending requests and shared reference queries. Qt 5, ASan and successful GPU rendering were not tested.
418 lines
9.3 KiB
C++
418 lines
9.3 KiB
C++
#include "lc_global.h"
|
|
#include "lc_piecemanager.h"
|
|
#include "lc_piecedata.h"
|
|
#include "lc_string.h"
|
|
#include "project.h"
|
|
|
|
lcPieceManager::~lcPieceManager()
|
|
{
|
|
// Library teardown must unregister identities before destroying this registry.
|
|
Q_ASSERT(mPieces.empty() && mProjectPieces.empty() && mPieceDataLoadErrors.empty());
|
|
}
|
|
|
|
std::string lcPieceManager::NormalizePieceDataName(const char* PieceName)
|
|
{
|
|
std::string Name;
|
|
|
|
for (const char* Src = PieceName; *Src && Name.size() < LC_PIECE_NAME_LEN - 1; Src++)
|
|
{
|
|
if (*Src == '\\')
|
|
Name.push_back('/');
|
|
else if (*Src >= 'a' && *Src <= 'z')
|
|
Name.push_back(*Src + 'A' - 'a');
|
|
else
|
|
Name.push_back(*Src);
|
|
}
|
|
|
|
return Name;
|
|
}
|
|
|
|
void lcPieceManager::Clear()
|
|
{
|
|
// Workers have drained. External project destruction may reenter through the facade.
|
|
std::unordered_set<Project*> ExternalProjects;
|
|
mMutex.lock();
|
|
|
|
for (const auto& [PieceData, Owner] : mProjectPieces)
|
|
if (PieceData->IsProject())
|
|
ExternalProjects.insert(PieceData->GetProject());
|
|
|
|
mMutex.unlock();
|
|
|
|
for (;;)
|
|
{
|
|
mMutex.lock();
|
|
|
|
const auto External = std::find_if(mProjectPieces.begin(), mProjectPieces.end(), [&ExternalProjects](const auto& Entry)
|
|
{
|
|
return Entry.first->IsProject() && ExternalProjects.find(Entry.second) == ExternalProjects.end();
|
|
});
|
|
lcPieceData* PieceData = nullptr;
|
|
|
|
if (External != mProjectPieces.end())
|
|
PieceData = External->first;
|
|
else if (!mProjectPieces.empty())
|
|
PieceData = mProjectPieces.begin()->first;
|
|
else if (!mPieces.empty())
|
|
PieceData = mPieces.begin()->second;
|
|
|
|
mMutex.unlock();
|
|
|
|
if (!PieceData)
|
|
break;
|
|
|
|
RemovePieceData(PieceData);
|
|
}
|
|
|
|
mErrorMutex.lock();
|
|
mPieceDataLoadErrors.clear();
|
|
mErrorMutex.unlock();
|
|
}
|
|
|
|
lcPieceData* lcPieceManager::FindPieceData(const std::string& Name, Project* OwnerProject) const
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
if (OwnerProject)
|
|
if (lcPieceData* PieceData = OwnerProject->FindPieceData(Name))
|
|
return PieceData;
|
|
|
|
const auto PieceIt = mPieces.find(Name);
|
|
|
|
return PieceIt != mPieces.end() ? PieceIt->second : nullptr;
|
|
}
|
|
|
|
lcPieceData* lcPieceManager::CreateLibraryPieceData(const std::string& Name, const char* FileName)
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
lcPieceData* PieceData = new lcPieceData(true);
|
|
|
|
lcstrcpy(PieceData->mFileName, FileName);
|
|
|
|
RegisterLibraryPieceData(Name, PieceData);
|
|
|
|
return PieceData;
|
|
}
|
|
|
|
lcPieceData* lcPieceManager::CreatePieceData(const char* PieceName, Project* OwnerProject)
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
lcPieceData* PieceData = new lcPieceData(false);
|
|
const std::string Name = NormalizePieceDataName(PieceName);
|
|
|
|
PieceData->CreatePart(PieceName);
|
|
|
|
if (OwnerProject)
|
|
RegisterProjectPieceData(OwnerProject, Name, PieceData);
|
|
else
|
|
RegisterLibraryPieceData(Name, PieceData);
|
|
|
|
return PieceData;
|
|
}
|
|
|
|
lcPieceData* lcPieceManager::AcquireModelPieceData(const char* PieceName, Project* OwnerProject, bool& Reused)
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
lcPieceData* PieceData = OwnerProject->FindPieceData(NormalizePieceDataName(PieceName));
|
|
|
|
Reused = PieceData && !PieceData->IsModel();
|
|
|
|
if (!Reused)
|
|
PieceData = CreatePieceData(PieceName, OwnerProject);
|
|
|
|
// Keep the identity alive through invalidation and adoption by the model.
|
|
AddPieceDataReference(PieceData);
|
|
|
|
return PieceData;
|
|
}
|
|
|
|
void lcPieceManager::SetModelPieceDataName(lcPieceData* PieceData, const char* Name)
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
lcstrcpy(PieceData->mFileName, Name);
|
|
lcstrcpy(PieceData->m_strDescription, Name);
|
|
}
|
|
|
|
void lcPieceManager::AddPieceDataReference(lcPieceData* PieceData)
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
PieceData->AddRef();
|
|
}
|
|
|
|
int lcPieceManager::ReleasePieceData(lcPieceData* PieceData)
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
if (PieceData->GetRefCount() == 0)
|
|
return 0;
|
|
|
|
const int Remaining = PieceData->Release();
|
|
|
|
Lock.unlock();
|
|
|
|
if (Remaining == 0)
|
|
UnloadPieceData(PieceData);
|
|
|
|
return Remaining;
|
|
}
|
|
|
|
void lcPieceManager::RemoveTemporaryPieceData()
|
|
{
|
|
// Requery after each deletion, since external projects can release other identities.
|
|
for (;;)
|
|
{
|
|
mMutex.lock();
|
|
lcPieceData* Unused = nullptr;
|
|
|
|
for (const auto& [Name, PieceData] : mPieces)
|
|
if (!PieceData->IsLibraryPiece() && PieceData->GetRefCount() == 0)
|
|
{
|
|
Unused = PieceData;
|
|
break;
|
|
}
|
|
|
|
if (!Unused)
|
|
for (const auto& [PieceData, Owner] : mProjectPieces)
|
|
if (PieceData->GetRefCount() == 0)
|
|
{
|
|
Unused = PieceData;
|
|
break;
|
|
}
|
|
|
|
mMutex.unlock();
|
|
|
|
if (!Unused)
|
|
break;
|
|
|
|
RemovePieceData(Unused);
|
|
}
|
|
}
|
|
|
|
void lcPieceManager::RemovePieceData(lcPieceData* PieceData)
|
|
{
|
|
mMutex.lock();
|
|
UnregisterPieceData(PieceData);
|
|
mMutex.unlock();
|
|
|
|
delete PieceData;
|
|
}
|
|
|
|
void lcPieceManager::UnloadPieceData(lcPieceData* PieceData)
|
|
{
|
|
if (PieceData->IsLibraryPiece())
|
|
PieceData->Unload();
|
|
else
|
|
RemovePieceData(PieceData);
|
|
}
|
|
|
|
void lcPieceManager::RegisterLibraryPieceData(const std::string& Name, lcPieceData* PieceData)
|
|
{
|
|
mPieces[Name] = PieceData;
|
|
}
|
|
|
|
void lcPieceManager::RegisterProjectPieceData(Project* OwnerProject, const std::string& Name, lcPieceData* PieceData)
|
|
{
|
|
OwnerProject->RegisterPieceData(Name, PieceData);
|
|
|
|
mProjectPieces[PieceData] = OwnerProject;
|
|
}
|
|
|
|
void lcPieceManager::UnregisterPieceData(lcPieceData* PieceData)
|
|
{
|
|
const auto Local = mProjectPieces.find(PieceData);
|
|
|
|
if (Local != mProjectPieces.end())
|
|
{
|
|
if (Local->second)
|
|
Local->second->UnregisterPieceData(PieceData);
|
|
|
|
mProjectPieces.erase(Local);
|
|
}
|
|
else
|
|
{
|
|
const auto PieceIt = std::find_if(mPieces.begin(), mPieces.end(), [PieceData](const auto& Entry)
|
|
{
|
|
return Entry.second == PieceData;
|
|
});
|
|
|
|
if (PieceIt != mPieces.end())
|
|
mPieces.erase(PieceIt);
|
|
}
|
|
}
|
|
|
|
bool lcPieceManager::RenameModelPieceData(lcPieceData* PieceData, const char* NewName)
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
const std::string Name = NormalizePieceDataName(NewName);
|
|
const auto Local = mProjectPieces.find(PieceData);
|
|
|
|
if (Local != mProjectPieces.end())
|
|
{
|
|
if (Project* OwnerProject = Local->second)
|
|
{
|
|
lcPieceData* Existing = OwnerProject->FindPieceData(Name);
|
|
|
|
if (Existing && Existing != PieceData)
|
|
return false;
|
|
|
|
OwnerProject->UnregisterPieceData(PieceData);
|
|
OwnerProject->RegisterPieceData(Name, PieceData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lcPieceData* Existing = FindPieceData(Name, nullptr);
|
|
|
|
if (Existing && Existing != PieceData)
|
|
return false;
|
|
|
|
UnregisterPieceData(PieceData);
|
|
RegisterLibraryPieceData(Name, PieceData);
|
|
}
|
|
|
|
lcstrcpy(PieceData->mFileName, NewName);
|
|
lcstrcpy(PieceData->m_strDescription, NewName);
|
|
|
|
return true;
|
|
}
|
|
|
|
void lcPieceManager::ReleaseProjectPieceData(Project* OwnerProject)
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
std::vector<lcPieceData*> Unused;
|
|
|
|
OwnerProject->ClearPieceDataIndex();
|
|
|
|
for (auto& [PieceData, Owner] : mProjectPieces)
|
|
{
|
|
if (Owner == OwnerProject)
|
|
{
|
|
Owner = nullptr;
|
|
|
|
if (PieceData->GetRefCount() == 0)
|
|
Unused.push_back(PieceData);
|
|
}
|
|
}
|
|
|
|
Lock.unlock();
|
|
|
|
for (lcPieceData* PieceData : Unused)
|
|
UnloadPieceData(PieceData);
|
|
}
|
|
|
|
void lcPieceManager::TransferProjectPieceData(Project* Source, Project* Destination)
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
std::vector<lcPieceData*> Unused;
|
|
|
|
Source->TransferPieceDataIndexTo(Destination);
|
|
|
|
for (auto& [PieceData, Owner] : mProjectPieces)
|
|
{
|
|
if (Owner == Source)
|
|
{
|
|
Owner = Destination;
|
|
|
|
if (PieceData->GetRefCount() == 0)
|
|
Unused.push_back(PieceData);
|
|
}
|
|
}
|
|
|
|
Lock.unlock();
|
|
|
|
for (lcPieceData* PieceData : Unused)
|
|
UnloadPieceData(PieceData);
|
|
}
|
|
|
|
size_t lcPieceManager::GetLibraryPieceDataCount() const
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
return mPieces.size();
|
|
}
|
|
|
|
std::string lcPieceManager::GetLibraryPieceDataName(const lcPieceData* PieceData) const
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
const auto PieceIt = std::find_if(mPieces.begin(), mPieces.end(), [PieceData](const auto& Entry)
|
|
{
|
|
return Entry.second == PieceData;
|
|
});
|
|
|
|
return PieceIt != mPieces.end() ? PieceIt->first : std::string();
|
|
}
|
|
|
|
std::vector<lcPieceData*> lcPieceManager::GetLibraryPieceData() const
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
std::vector<lcPieceData*> LibraryPieceData;
|
|
|
|
LibraryPieceData.reserve(mPieces.size());
|
|
|
|
for (const auto& [Name, LibraryData] : mPieces)
|
|
LibraryPieceData.push_back(LibraryData);
|
|
|
|
return LibraryPieceData;
|
|
}
|
|
|
|
std::vector<lcPieceData*> lcPieceManager::GetAllPieceData() const
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
std::vector<lcPieceData*> AllPieceData = GetLibraryPieceData();
|
|
AllPieceData.reserve(AllPieceData.size() + mProjectPieces.size());
|
|
|
|
for (const auto& [LocalData, Owner] : mProjectPieces)
|
|
AllPieceData.push_back(LocalData);
|
|
|
|
return AllPieceData;
|
|
}
|
|
|
|
std::optional<lcPieceManager::FileInfo> lcPieceManager::GetPieceDataFileInfo(const std::string& Name) const
|
|
{
|
|
QMutexLocker Lock(&mMutex);
|
|
|
|
const lcPieceData* PieceData = FindPieceData(Name, nullptr);
|
|
|
|
if (!PieceData)
|
|
return std::nullopt;
|
|
|
|
return FileInfo{ PieceData->mFileName, PieceData->mZipFileType, PieceData->mZipFileIndex };
|
|
}
|
|
|
|
void lcPieceManager::SetPieceDataLoadError(const lcPieceData* PieceData, QString Error)
|
|
{
|
|
QMutexLocker ErrorLock(&mErrorMutex);
|
|
|
|
if (Error.isEmpty())
|
|
mPieceDataLoadErrors.erase(PieceData);
|
|
else
|
|
mPieceDataLoadErrors.insert_or_assign(PieceData, std::move(Error));
|
|
}
|
|
|
|
QString lcPieceManager::GetPieceDataLoadError(const lcPieceData* PieceData) const
|
|
{
|
|
QMutexLocker ErrorLock(&mErrorMutex);
|
|
|
|
const auto It = mPieceDataLoadErrors.find(PieceData);
|
|
|
|
return It == mPieceDataLoadErrors.end() ? QString() : It->second;
|
|
}
|
|
|
|
void lcPieceManager::ClearPieceDataLoadError(const lcPieceData* PieceData)
|
|
{
|
|
QMutexLocker ErrorLock(&mErrorMutex);
|
|
|
|
mPieceDataLoadErrors.erase(PieceData);
|
|
}
|