diff --git a/src/Core/CAudioManager.cpp b/src/Core/CAudioManager.cpp index 00dae23b..ed9b53cc 100644 --- a/src/Core/CAudioManager.cpp +++ b/src/Core/CAudioManager.cpp @@ -56,6 +56,14 @@ void CAudioManager::LoadAssets() } } +void CAudioManager::ClearAssets() +{ + mAudioGroups.clear(); + mpAudioLookupTable = nullptr; + mpSfxNameList = nullptr; + mSfxIdMap.clear(); +} + SSoundInfo CAudioManager::GetSoundInfo(u32 SoundID) { SSoundInfo Out; diff --git a/src/Core/CAudioManager.h b/src/Core/CAudioManager.h index 41b4296f..74cd8e30 100644 --- a/src/Core/CAudioManager.h +++ b/src/Core/CAudioManager.h @@ -28,6 +28,7 @@ class CAudioManager public: CAudioManager(CGameProject *pProj); void LoadAssets(); + void ClearAssets(); SSoundInfo GetSoundInfo(u32 SoundID); void LogSoundInfo(u32 SoundID); }; diff --git a/src/Core/Core.pro b/src/Core/Core.pro index 17e724fa..7397c5b8 100644 --- a/src/Core/Core.pro +++ b/src/Core/Core.pro @@ -228,7 +228,8 @@ HEADERS += \ Resource/Animation/CSourceAnimData.h \ Resource/CMapArea.h \ Resource/CSavedStateID.h \ - IProgressNotifier.h + IProgressNotifier.h \ + IUIRelay.h # Source Files SOURCES += \ @@ -334,4 +335,5 @@ SOURCES += \ GameProject/CAssetNameMap.cpp \ GameProject/CGameInfo.cpp \ Resource/CResTypeInfo.cpp \ - CompressionUtil.cpp + CompressionUtil.cpp \ + IUIRelay.cpp diff --git a/src/Core/GameProject/AssetNameGeneration.cpp b/src/Core/GameProject/AssetNameGeneration.cpp index 899e9e22..0ac8caa6 100644 --- a/src/Core/GameProject/AssetNameGeneration.cpp +++ b/src/Core/GameProject/AssetNameGeneration.cpp @@ -76,12 +76,8 @@ void ApplyGeneratedName(CResourceEntry *pEntry, const TString& rkDir, const TStr if (pEntry->Directory() == pNewDir && pEntry->Name() == NewName) return; // Perform the move - CVirtualDirectory *pOldDir = pEntry->Directory(); bool Success = pEntry->Move(pNewDir->FullPath(), NewName, true, true); ASSERT(Success); - - // If the old directory is now empty, delete it - pEntry->ResourceStore()->ConditionalDeleteDirectory(pOldDir); } void GenerateAssetNames(CGameProject *pProj) @@ -657,6 +653,7 @@ void GenerateAssetNames(CGameProject *pProj) } #endif - Log::Write("*** Asset Name Generation FINISHED ***"); + pStore->RootDirectory()->RemoveEmptySubdirectories(); pStore->ConditionalSaveStore(); + Log::Write("*** Asset Name Generation FINISHED ***"); } diff --git a/src/Core/GameProject/CGameProject.cpp b/src/Core/GameProject/CGameProject.cpp index c4d396bb..74b69bbe 100644 --- a/src/Core/GameProject/CGameProject.cpp +++ b/src/Core/GameProject/CGameProject.cpp @@ -1,4 +1,5 @@ #include "CGameProject.h" +#include "IUIRelay.h" #include "Core/Resource/Factory/CTemplateLoader.h" #include "Core/Resource/Script/CMasterTemplate.h" #include @@ -33,7 +34,7 @@ bool CGameProject::Save() return SaveSuccess; } -void CGameProject::Serialize(IArchive& rArc) +bool CGameProject::Serialize(IArchive& rArc) { rArc << SERIAL("Name", mProjectName) << SERIAL("Region", mRegion) @@ -50,7 +51,7 @@ void CGameProject::Serialize(IArchive& rArc) rArc << SERIAL("ResourceDB", mResourceDBPath); - // Packages + // Serialize package list std::vector PackageList; if (!rArc.IsReader()) @@ -61,38 +62,29 @@ void CGameProject::Serialize(IArchive& rArc) rArc << SERIAL_CONTAINER("Packages", PackageList, "Package"); + // Load packages if (rArc.IsReader()) { - // Load resource store - ASSERT(mpResourceStore == nullptr); - mpResourceStore = new CResourceStore(this); + ASSERT(mPackages.empty()); - if (!mpResourceStore->LoadResourceDatabase()) - mLoadSuccess = false; - - else + for (u32 iPkg = 0; iPkg < PackageList.size(); iPkg++) { - // Load packages - ASSERT(mPackages.empty()); + const TString& rkPackagePath = PackageList[iPkg]; + TString PackageName = rkPackagePath.GetFileName(false); + TString PackageDir = rkPackagePath.GetFileDirectory(); - for (u32 iPkg = 0; iPkg < PackageList.size(); iPkg++) + CPackage *pPackage = new CPackage(this, PackageName, PackageDir); + bool PackageLoadSuccess = pPackage->Load(); + mPackages.push_back(pPackage); + + if (!PackageLoadSuccess) { - const TString& rkPackagePath = PackageList[iPkg]; - TString PackageName = rkPackagePath.GetFileName(false); - TString PackageDir = rkPackagePath.GetFileDirectory(); - - CPackage *pPackage = new CPackage(this, PackageName, PackageDir); - bool PackageLoadSuccess = pPackage->Load(); - mPackages.push_back(pPackage); - - if (!PackageLoadSuccess) - { - mLoadSuccess = false; - break; - } + return false; } } } + + return true; } bool CGameProject::BuildISO(const TString& rkIsoPath, IProgressNotifier *pProgress) @@ -213,16 +205,23 @@ CGameProject* CGameProject::CreateProjectForExport( pProj->mProjectRoot.Replace("\\", "/"); pProj->mpResourceStore = new CResourceStore(pProj); pProj->mpGameInfo->LoadGameInfo(Game); - pProj->mLoadSuccess = true; return pProj; } -CGameProject* CGameProject::LoadProject(const TString& rkProjPath) +CGameProject* CGameProject::LoadProject(const TString& rkProjPath, IProgressNotifier *pProgress) { + // Init project CGameProject *pProj = new CGameProject; pProj->mProjectRoot = rkProjPath.GetFileDirectory(); pProj->mProjectRoot.Replace("\\", "/"); + // Init progress + pProgress->SetTask(0, "Loading project: " + rkProjPath.GetFileName()); + + // Load main project file + pProgress->Report("Loading project settings"); + bool LoadSuccess = false; + TString ProjPath = rkProjPath; CXMLReader Reader(ProjPath); @@ -233,9 +232,37 @@ CGameProject* CGameProject::LoadProject(const TString& rkProjPath) } pProj->mGame = Reader.Game(); - pProj->Serialize(Reader); - if (!pProj->mLoadSuccess) + if (pProj->Serialize(Reader)) + { + // Load resource database + pProgress->Report("Loading resource database"); + pProj->mpResourceStore = new CResourceStore(pProj); + LoadSuccess = pProj->mpResourceStore->LoadResourceDatabase(); + + // Validate resource database + if (LoadSuccess) + { + pProgress->Report("Validating resource database"); + bool DatabaseIsValid = pProj->mpResourceStore->AreAllEntriesValid(); + + // Resource database is corrupt. Ask the user if they want to rebuild it. + if (!DatabaseIsValid) + { + bool ShouldRebuild = gpUIRelay->AskYesNoQuestion("Error", "The resource database is corrupt. Attempt to repair it?"); + + if (ShouldRebuild) + { + pProgress->Report("Repairing resource database"); + pProj->mpResourceStore->RebuildFromDirectory(); + } + else + LoadSuccess = false; + } + } + } + + if (!LoadSuccess) { delete pProj; return nullptr; diff --git a/src/Core/GameProject/CGameProject.h b/src/Core/GameProject/CGameProject.h index 02017485..86c34b7c 100644 --- a/src/Core/GameProject/CGameProject.h +++ b/src/Core/GameProject/CGameProject.h @@ -36,7 +36,6 @@ class CGameProject // Keep file handle open for the .prj file to prevent users from opening the same project // in multiple instances of PWE CFileLock mProjFileLock; - bool mLoadSuccess; enum EProjectVersion { @@ -55,7 +54,6 @@ class CGameProject , mBuildVersion(0.f) , mResourceDBPath("ResourceDB.rdb") , mpResourceStore(nullptr) - , mLoadSuccess(true) { mpGameInfo = new CGameInfo(); mpAudioManager = new CAudioManager(this); @@ -65,7 +63,7 @@ public: ~CGameProject(); bool Save(); - void Serialize(IArchive& rArc); + bool Serialize(IArchive& rArc); bool BuildISO(const TString& rkIsoPath, IProgressNotifier *pProgress); void GetWorldList(std::list& rOut) const; CAssetID FindNamedResource(const TString& rkName) const; @@ -84,7 +82,7 @@ public: u32 FstAddress ); - static CGameProject* LoadProject(const TString& rkProjPath); + static CGameProject* LoadProject(const TString& rkProjPath, IProgressNotifier *pProgress); // Directory Handling inline TString ProjectRoot() const { return mProjectRoot; } diff --git a/src/Core/GameProject/CResourceEntry.cpp b/src/Core/GameProject/CResourceEntry.cpp index e129b9c5..d355227a 100644 --- a/src/Core/GameProject/CResourceEntry.cpp +++ b/src/Core/GameProject/CResourceEntry.cpp @@ -566,7 +566,7 @@ bool CResourceEntry::Move(const TString& rkDir, const TString& rkName, bool IsAu Log::Error("MOVE FAILED: " + MoveFailReason); mpDirectory = pOldDir; mName = OldName; - mpStore->ConditionalDeleteDirectory(pNewDir); + mpStore->ConditionalDeleteDirectory(pNewDir, false); return false; } } diff --git a/src/Core/GameProject/CResourceIterator.h b/src/Core/GameProject/CResourceIterator.h index 22a7f913..dea7fb30 100644 --- a/src/Core/GameProject/CResourceIterator.h +++ b/src/Core/GameProject/CResourceIterator.h @@ -7,22 +7,22 @@ class CResourceIterator { protected: - CResourceStore *mpStore; - std::map::iterator mIter; + const CResourceStore *mpkStore; + std::map::const_iterator mIter; CResourceEntry *mpCurEntry; public: - CResourceIterator(CResourceStore *pStore = gpResourceStore) - : mpStore(pStore) + CResourceIterator(const CResourceStore *pkStore = gpResourceStore) + : mpkStore(pkStore) , mpCurEntry(nullptr) { - mIter = mpStore->mResourceEntries.begin(); + mIter = mpkStore->mResourceEntries.begin(); Next(); } virtual CResourceEntry* Next() { - if (mIter != mpStore->mResourceEntries.end()) + if (mIter != mpkStore->mResourceEntries.end()) { mpCurEntry = mIter->second; mIter++; @@ -73,7 +73,7 @@ public: TResourceIterator(CResourceStore *pStore = gpResourceStore) : CResourceIterator(pStore) { - if (mpCurEntry->ResourceType() != ResType) + if (mpCurEntry && mpCurEntry->ResourceType() != ResType) Next(); } diff --git a/src/Core/GameProject/CResourceStore.cpp b/src/Core/GameProject/CResourceStore.cpp index 4423dfd2..20ac6f45 100644 --- a/src/Core/GameProject/CResourceStore.cpp +++ b/src/Core/GameProject/CResourceStore.cpp @@ -46,7 +46,7 @@ CResourceStore::~CResourceStore() delete It->second; } -void CResourceStore::SerializeResourceDatabase(IArchive& rArc) +bool CResourceStore::SerializeResourceDatabase(IArchive& rArc) { struct SDatabaseResource { @@ -83,6 +83,8 @@ void CResourceStore::SerializeResourceDatabase(IArchive& rArc) RegisterResource(rRes.ID, rRes.pType->Type(), rRes.Directory, rRes.Name); } } + + return true; } bool CResourceStore::LoadResourceDatabase() @@ -105,7 +107,7 @@ bool CResourceStore::LoadResourceDatabase() ASSERT(mpProj->Game() == Reader.Game()); mGame = Reader.Game(); - SerializeResourceDatabase(Reader); + if (!SerializeResourceDatabase(Reader)) return false; return LoadCacheFile(); } @@ -293,19 +295,17 @@ CVirtualDirectory* CResourceStore::GetVirtualDirectory(const TString& rkPath, bo return nullptr; } -void CResourceStore::ConditionalDeleteDirectory(CVirtualDirectory *pDir) +void CResourceStore::ConditionalDeleteDirectory(CVirtualDirectory *pDir, bool Recurse) { - if (pDir->IsEmpty()) + if (pDir->IsEmpty() && !pDir->IsRoot()) { - // If this directory is part of the project, then we should delete the corresponding filesystem directory - if (pDir->GetRoot() == mpDatabaseRoot && !pDir->IsRoot()) - { - FileUtil::DeleteDirectory(ResourcesDir() + pDir->FullPath(), true); - } - CVirtualDirectory *pParent = pDir->Parent(); pParent->RemoveChildDirectory(pDir); - ConditionalDeleteDirectory(pParent); + + if (Recurse) + { + ConditionalDeleteDirectory(pParent, true); + } } } @@ -322,6 +322,98 @@ CResourceEntry* CResourceStore::FindEntry(const TString& rkPath) const return (mpDatabaseRoot ? mpDatabaseRoot->FindChildResource(rkPath) : nullptr); } +bool CResourceStore::AreAllEntriesValid() const +{ + for (CResourceIterator Iter(this); Iter; ++Iter) + { + if (!Iter->HasCookedVersion() && !Iter->HasRawVersion()) + return false; + } + + return true; +} + +void CResourceStore::ClearDatabase() +{ + // THIS OPERATION REQUIRES THAT ALL RESOURCES ARE UNREFERENCED + DestroyUnreferencedResources(); + ASSERT(mLoadedResources.empty()); + + // Clear out existing resource entries and directories + for (auto Iter = mResourceEntries.begin(); Iter != mResourceEntries.end(); Iter++) + delete Iter->second; + mResourceEntries.clear(); + + delete mpDatabaseRoot; + mpDatabaseRoot = new CVirtualDirectory(this); + + mDatabaseDirty = true; + mCacheFileDirty = true; +} + +void CResourceStore::RebuildFromDirectory() +{ + ASSERT(mpProj != nullptr); + mpProj->AudioManager()->ClearAssets(); + ClearDatabase(); + + // Get list of resources + TString ResDir = ResourcesDir(); + TStringList ResourceList; + FileUtil::GetDirectoryContents(ResDir, ResourceList); + + for (auto Iter = ResourceList.begin(); Iter != ResourceList.end(); Iter++) + { + TString Path = *Iter; + TString RelPath = FileUtil::MakeRelative(Path, ResDir); + + if (FileUtil::IsFile(Path) && Path.GetFileExtension() == "rsmeta") + { + // Determine resource name + TString DirPath = RelPath.GetFileDirectory(); + TString CookedFilename = RelPath.GetFileName(false); // This call removes the .rsmeta extension + TString ResName = CookedFilename.GetFileName(false); // This call removes the cooked extension + ASSERT( IsValidResourcePath(DirPath, ResName) ); + + // Determine resource type + TString CookedExtension = CookedFilename.GetFileExtension(); + CResTypeInfo *pTypeInfo = CResTypeInfo::TypeForCookedExtension( Game(), CFourCC(CookedExtension) ); + + if (!pTypeInfo) + { + Log::Error("Found resource but couldn't register because failed to identify resource type: " + RelPath); + continue; + } + + // Create resource entry + CResourceEntry *pEntry = new CResourceEntry(this, CAssetID::InvalidID(mGame), DirPath, ResName, pTypeInfo->Type()); + pEntry->LoadMetadata(); + + // Validate the entry + CAssetID ID = pEntry->ID(); + ASSERT( mResourceEntries.find(ID) == mResourceEntries.end() ); + ASSERT( ID.Length() == CAssetID::GameIDLength(mGame) ); + + mResourceEntries[ID] = pEntry; + } + + else if (FileUtil::IsDirectory(Path)) + GetVirtualDirectory(RelPath, true); + } + + // Make sure audio manager is loaded correctly so AGSC dependencies can be looked up + mpProj->AudioManager()->LoadAssets(); + + // Update dependencies + for (CResourceIterator It(this); It; ++It) + It->UpdateDependencies(); + + // Update database files + mDatabaseDirty = true; + mCacheFileDirty = true; + ConditionalSaveStore(); +} + bool CResourceStore::IsResourceRegistered(const CAssetID& rkID) const { return FindEntry(rkID) != nullptr; diff --git a/src/Core/GameProject/CResourceStore.h b/src/Core/GameProject/CResourceStore.h index f9cb26ee..d49bc61a 100644 --- a/src/Core/GameProject/CResourceStore.h +++ b/src/Core/GameProject/CResourceStore.h @@ -43,7 +43,7 @@ public: CResourceStore(const TString& rkDatabasePath); CResourceStore(CGameProject *pProject); ~CResourceStore(); - void SerializeResourceDatabase(IArchive& rArc); + bool SerializeResourceDatabase(IArchive& rArc); bool LoadResourceDatabase(); bool SaveResourceDatabase(); bool LoadCacheFile(); @@ -52,12 +52,15 @@ public: void SetProject(CGameProject *pProj); void CloseProject(); CVirtualDirectory* GetVirtualDirectory(const TString& rkPath, bool AllowCreate); - void ConditionalDeleteDirectory(CVirtualDirectory *pDir); + void ConditionalDeleteDirectory(CVirtualDirectory *pDir, bool Recurse); bool IsResourceRegistered(const CAssetID& rkID) const; CResourceEntry* RegisterResource(const CAssetID& rkID, EResType Type, const TString& rkDir, const TString& rkName); CResourceEntry* FindEntry(const CAssetID& rkID) const; CResourceEntry* FindEntry(const TString& rkPath) const; + bool AreAllEntriesValid() const; + void ClearDatabase(); + void RebuildFromDirectory(); template ResType* LoadResource(const CAssetID& rkID) { return static_cast(LoadResource(rkID, ResType::StaticType())); } CResource* LoadResource(const CAssetID& rkID); diff --git a/src/Core/GameProject/CVirtualDirectory.cpp b/src/Core/GameProject/CVirtualDirectory.cpp index 59f323d8..a3392cf3 100644 --- a/src/Core/GameProject/CVirtualDirectory.cpp +++ b/src/Core/GameProject/CVirtualDirectory.cpp @@ -155,6 +155,7 @@ bool CVirtualDirectory::AddChild(const TString &rkPath, CResourceEntry *pEntry) { // Create new subdirectory pSubdir = new CVirtualDirectory(this, DirName, mpStore); + FileUtil::MakeDirectory(mpStore->ResourcesDir() + pSubdir->FullPath()); mSubdirectories.push_back(pSubdir); std::sort(mSubdirectories.begin(), mSubdirectories.end(), [](CVirtualDirectory *pLeft, CVirtualDirectory *pRight) -> bool { @@ -199,6 +200,14 @@ bool CVirtualDirectory::RemoveChildDirectory(CVirtualDirectory *pSubdir) if (*It == pSubdir) { mSubdirectories.erase(It); + + // If this is part of the resource store, delete the corresponding filesystem directory + if (mpStore && pSubdir->GetRoot() == mpStore->RootDirectory()) + { + TString AbsPath = mpStore->DatabaseRootPath() + pSubdir->FullPath(); + FileUtil::DeleteDirectory(AbsPath, true); + } + delete pSubdir; return true; } @@ -221,6 +230,19 @@ bool CVirtualDirectory::RemoveChildResource(CResourceEntry *pEntry) return false; } +void CVirtualDirectory::RemoveEmptySubdirectories() +{ + for (u32 SubdirIdx = 0; SubdirIdx < mSubdirectories.size(); SubdirIdx++) + { + CVirtualDirectory *pDir = mSubdirectories[SubdirIdx]; + + if (pDir->IsEmpty()) + RemoveChildDirectory(pDir); + else + pDir->RemoveEmptySubdirectories(); + } +} + // ************ STATIC ************ bool CVirtualDirectory::IsValidDirectoryName(const TString& rkName) { diff --git a/src/Core/GameProject/CVirtualDirectory.h b/src/Core/GameProject/CVirtualDirectory.h index 1f1e1e1c..6fb1c543 100644 --- a/src/Core/GameProject/CVirtualDirectory.h +++ b/src/Core/GameProject/CVirtualDirectory.h @@ -33,6 +33,7 @@ public: bool AddChild(const TString& rkPath, CResourceEntry *pEntry); bool RemoveChildDirectory(CVirtualDirectory *pSubdir); bool RemoveChildResource(CResourceEntry *pEntry); + void RemoveEmptySubdirectories(); static bool IsValidDirectoryName(const TString& rkName); static bool IsValidDirectoryPath(TString Path); diff --git a/src/Core/IProgressNotifier.h b/src/Core/IProgressNotifier.h index d1065d4e..859ec5bc 100644 --- a/src/Core/IProgressNotifier.h +++ b/src/Core/IProgressNotifier.h @@ -2,6 +2,7 @@ #define IPROGRESSNOTIFIER_H #include +#include class IProgressNotifier { @@ -26,19 +27,36 @@ public: { mTaskName = TaskName; mTaskIndex = TaskIndex; + mTaskCount = Math::Max(mTaskCount, TaskIndex + 1); } void Report(int StepIndex, int StepCount, const TString& rkStepDesc) { ASSERT(mTaskCount >= 1); + // Make sure TaskCount and StepCount are at least 1 so we don't have divide-by-zero errors + int TaskCount = Math::Max(mTaskCount, 1); + StepCount = Math::Max(StepCount, 1); + // Calculate percentage - float TaskPercent = 1.f / (float) mTaskCount; + float TaskPercent = 1.f / (float) TaskCount; float StepPercent = (StepCount >= 0 ? (float) StepIndex / (float) StepCount : 0.f); float ProgressPercent = (TaskPercent * mTaskIndex) + (TaskPercent * StepPercent); UpdateProgress(mTaskName, rkStepDesc, ProgressPercent); } + void Report(const TString& rkStepDesc) + { + Report(0, 0, rkStepDesc); + } + + void SetOneShotTask(const TString& rkTaskDesc) + { + SetNumTasks(1); + SetTask(0, rkTaskDesc); + Report(0, 0, ""); + } + virtual bool ShouldCancel() const = 0; protected: diff --git a/src/Core/IUIRelay.cpp b/src/Core/IUIRelay.cpp new file mode 100644 index 00000000..998df130 --- /dev/null +++ b/src/Core/IUIRelay.cpp @@ -0,0 +1,3 @@ +#include "IUIRelay.h" + +IUIRelay *gpUIRelay = nullptr; diff --git a/src/Core/IUIRelay.h b/src/Core/IUIRelay.h new file mode 100644 index 00000000..83f1375f --- /dev/null +++ b/src/Core/IUIRelay.h @@ -0,0 +1,13 @@ +#ifndef IUIRELAY_H +#define IUIRELAY_H + +#include + +class IUIRelay +{ +public: + virtual bool AskYesNoQuestion(const TString& rkInfoBoxTitle, const TString& rkQuestion) = 0; +}; +extern IUIRelay *gpUIRelay; + +#endif // IUIRELAY_H diff --git a/src/Editor/CEditorApplication.cpp b/src/Editor/CEditorApplication.cpp index 573421b0..c41c9b7f 100644 --- a/src/Editor/CEditorApplication.cpp +++ b/src/Editor/CEditorApplication.cpp @@ -41,24 +41,28 @@ void CEditorApplication::InitEditor() mpWorldEditor->showMaximized(); } +bool CEditorApplication::CloseAllEditors() +{ + // Close active editor windows. + foreach (IEditor *pEditor, mEditorWindows) + { + if (pEditor != mpWorldEditor && !pEditor->close()) + return false; + } + + // Close world + if (!mpWorldEditor->CloseWorld()) + return false; + + mpResourceBrowser->close(); + mpProjectDialog->close(); + return true; +} + bool CEditorApplication::CloseProject() { - if (mpActiveProject) + if (mpActiveProject && CloseAllEditors()) { - // Close active editor windows. todo: check for unsaved changes - foreach (IEditor *pEditor, mEditorWindows) - { - if (pEditor != mpWorldEditor && !pEditor->close()) - return false; - } - - // Close world - if (!mpWorldEditor->CloseWorld()) - return false; - - mpResourceBrowser->close(); - mpProjectDialog->close(); - // Emit before actually deleting the project to allow editor references to clean up CGameProject *pOldProj = mpActiveProject; mpActiveProject = nullptr; @@ -77,7 +81,12 @@ bool CEditorApplication::OpenProject(const QString& rkProjPath) // Load new project TString Path = TO_TSTRING(rkProjPath); - mpActiveProject = CGameProject::LoadProject(Path); + + CProgressDialog Dialog("Opening " + TO_QSTRING(Path.GetFileName()), true, true, mpWorldEditor); + Dialog.DisallowCanceling(); + QFuture Future = QtConcurrent::run(&CGameProject::LoadProject, Path, &Dialog); + mpActiveProject = Dialog.WaitForResults(Future); + Dialog.close(); if (mpActiveProject) { @@ -169,7 +178,7 @@ bool CEditorApplication::CookPackageList(QList PackageList) { if (!PackageList.isEmpty()) { - CProgressDialog Dialog("Cooking package" + QString(PackageList.size() > 1 ? "s" : ""), true, mpWorldEditor); + CProgressDialog Dialog("Cooking package" + QString(PackageList.size() > 1 ? "s" : ""), false, true, mpWorldEditor); QFuture Future = QtConcurrent::run([&]() { @@ -191,6 +200,45 @@ bool CEditorApplication::CookPackageList(QList PackageList) else return true; } +bool CEditorApplication::RebuildResourceDatabase() +{ + bool BrowserIsOpen = mpResourceBrowser->isVisible(); + + // Make sure all editors are closed + if (mpActiveProject && CloseAllEditors()) + { + // Fake-close the project, but keep it in memory so we can modify the resource store + CGameProject *pProj = mpActiveProject; + mpActiveProject = nullptr; + emit ActiveProjectChanged(nullptr); + + // Rebuild + CProgressDialog Dialog("Rebuilding resource database", true, false, mpWorldEditor); + Dialog.SetOneShotTask("Rebuilding resource database"); + Dialog.DisallowCanceling(); + + QFuture Future = QtConcurrent::run(pProj->ResourceStore(), &CResourceStore::RebuildFromDirectory); + Dialog.WaitForResults(Future); + Dialog.close(); + + // Set project to active again + mpActiveProject = pProj; + emit ActiveProjectChanged(pProj); + + // If the resource browser was open before, then reopen it now + if (BrowserIsOpen) + { + mpResourceBrowser->show(); + mpResourceBrowser->raise(); + } + + UICommon::InfoMsg(mpWorldEditor, "Success", "Resource database rebuilt successfully!"); + return true; + } + + return false; +} + // ************ SLOTS ************ void CEditorApplication::AddEditor(IEditor *pEditor) { diff --git a/src/Editor/CEditorApplication.h b/src/Editor/CEditorApplication.h index ad40b31a..6c86e8c6 100644 --- a/src/Editor/CEditorApplication.h +++ b/src/Editor/CEditorApplication.h @@ -33,6 +33,7 @@ public: CEditorApplication(int& rArgc, char **ppArgv); ~CEditorApplication(); void InitEditor(); + bool CloseAllEditors(); bool CloseProject(); bool OpenProject(const QString& rkProjPath); void EditResource(CResourceEntry *pEntry); @@ -42,6 +43,8 @@ public: bool CookAllDirtyPackages(); bool CookPackageList(QList PackageList); + bool RebuildResourceDatabase(); + // Accessors inline CGameProject* ActiveProject() const { return mpActiveProject; } inline CWorldEditor* WorldEditor() const { return mpWorldEditor; } diff --git a/src/Editor/CExportGameDialog.cpp b/src/Editor/CExportGameDialog.cpp index 57413dc7..b568a0b8 100644 --- a/src/Editor/CExportGameDialog.cpp +++ b/src/Editor/CExportGameDialog.cpp @@ -386,7 +386,7 @@ void CExportGameDialog::Export() TString StrExportDir = TO_TSTRING(ExportDir); StrExportDir.EnsureEndsWith('/'); - CProgressDialog Dialog("Creating new game project", true, parentWidget()); + CProgressDialog Dialog("Creating new game project", false, true, parentWidget()); QFuture Future = QtConcurrent::run(&Exporter, &CGameExporter::Export, mpDisc, StrExportDir, &NameMap, &GameInfo, &Dialog); mExportSuccess = Dialog.WaitForResults(Future); diff --git a/src/Editor/CProgressDialog.cpp b/src/Editor/CProgressDialog.cpp index 8d5746d8..f9ba35f3 100644 --- a/src/Editor/CProgressDialog.cpp +++ b/src/Editor/CProgressDialog.cpp @@ -3,16 +3,17 @@ #include "CEditorApplication.h" #include -CProgressDialog::CProgressDialog(QString OperationName, bool AlertOnFinish, QWidget *pParent) +CProgressDialog::CProgressDialog(QString OperationName, bool UseBusyIndicator, bool AlertOnFinish, QWidget *pParent) : IProgressNotifierUI(pParent) , mpUI(new Ui::CProgressDialog) + , mUseBusyIndicator(UseBusyIndicator) , mAlertOnFinish(AlertOnFinish) , mFinished(false) , mCanceled(false) { mpUI->setupUi(this); mpUI->ProgressBar->setMinimum(0); - mpUI->ProgressBar->setMaximum(10000); + mpUI->ProgressBar->setMaximum(UseBusyIndicator ? 0 : 10000); setWindowTitle(OperationName); #if WIN32 @@ -23,7 +24,7 @@ CProgressDialog::CProgressDialog(QString OperationName, bool AlertOnFinish, QWid mpTaskbarProgress = pButton->progress(); mpTaskbarProgress->setMinimum(0); - mpTaskbarProgress->setMaximum(10000); + mpTaskbarProgress->setMaximum(UseBusyIndicator ? 0 : 10000); mpTaskbarProgress->show(); setWindowFlags(windowFlags() | Qt::MSWindowsFixedSizeDialogHint); @@ -82,10 +83,26 @@ void CProgressDialog::UpdateUI(const QString& rkTaskDesc, const QString& rkStepD mpUI->TaskLabel->setText(rkTaskDesc); mpUI->StepLabel->setText(rkStepDesc); - int ProgressValue = 10000 * ProgressPercent; - mpUI->ProgressBar->setValue(ProgressValue); + if (rkStepDesc.isEmpty() && !mpUI->StepLabel->isHidden()) + { + mpUI->StepLabel->hide(); + mpUI->TaskInfoBoxLayout->removeWidget(mpUI->StepLabel); + mpUI->TaskInfoBoxLayout->removeItem(mpUI->LabelSpacer); + } + else if (!rkStepDesc.isEmpty() && mpUI->StepLabel->isHidden()) + { + mpUI->StepLabel->show(); + mpUI->TaskInfoBoxLayout->addWidget(mpUI->StepLabel); + mpUI->TaskInfoBoxLayout->addItem(mpUI->LabelSpacer); + } + + if (!mUseBusyIndicator) + { + int ProgressValue = 10000 * ProgressPercent; + mpUI->ProgressBar->setValue(ProgressValue); #if WIN32 - mpTaskbarProgress->setValue(ProgressValue); + mpTaskbarProgress->setValue(ProgressValue); #endif + } } diff --git a/src/Editor/CProgressDialog.h b/src/Editor/CProgressDialog.h index 297f6332..98e6abe9 100644 --- a/src/Editor/CProgressDialog.h +++ b/src/Editor/CProgressDialog.h @@ -23,6 +23,7 @@ class CProgressDialog : public IProgressNotifierUI { Q_OBJECT Ui::CProgressDialog *mpUI; + bool mUseBusyIndicator; bool mAlertOnFinish; bool mFinished; bool mCanceled; @@ -32,7 +33,7 @@ class CProgressDialog : public IProgressNotifierUI #endif public: - explicit CProgressDialog(QString OperationName, bool AlertOnFinish, QWidget *pParent = 0); + explicit CProgressDialog(QString OperationName, bool UseBusyIndicator, bool AlertOnFinish, QWidget *pParent = 0); ~CProgressDialog(); void DisallowCanceling(); diff --git a/src/Editor/CProgressDialog.ui b/src/Editor/CProgressDialog.ui index ba63a2bd..18a07a70 100644 --- a/src/Editor/CProgressDialog.ui +++ b/src/Editor/CProgressDialog.ui @@ -36,7 +36,7 @@ - + @@ -62,7 +62,7 @@ - + Qt::Vertical diff --git a/src/Editor/CProjectSettingsDialog.cpp b/src/Editor/CProjectSettingsDialog.cpp index 8b6e33d1..47809fa9 100644 --- a/src/Editor/CProjectSettingsDialog.cpp +++ b/src/Editor/CProjectSettingsDialog.cpp @@ -117,7 +117,7 @@ void CProjectSettingsDialog::BuildISO() { if (gpEdApp->CookAllDirtyPackages()) { - CProgressDialog Dialog("Building ISO", true, this); + CProgressDialog Dialog("Building ISO", false, true, this); Dialog.DisallowCanceling(); QFuture Future = QtConcurrent::run(pProj, &CGameProject::BuildISO, TO_TSTRING(IsoPath), &Dialog); Dialog.WaitForResults(Future); diff --git a/src/Editor/CUIRelay.h b/src/Editor/CUIRelay.h new file mode 100644 index 00000000..ade0c451 --- /dev/null +++ b/src/Editor/CUIRelay.h @@ -0,0 +1,37 @@ +#ifndef CUIRELAY_H +#define CUIRELAY_H + +#include +#include "CEditorApplication.h" +#include "WorldEditor/CWorldEditor.h" +#include "UICommon.h" + +class CUIRelay : public QObject, public IUIRelay +{ + Q_OBJECT + +public: + explicit CUIRelay(QObject *pParent = 0) + : QObject(pParent) + {} + + // Note: All function calls should be deferred with QMetaObject::invokeMethod to ensure + // that they run on the UI thread instead of whatever thread we happen to be on. + virtual bool AskYesNoQuestion(const TString& rkInfoBoxTitle, const TString& rkQuestion) + { + bool RetVal; + QMetaObject::invokeMethod(this, "AskYesNoQuestionSlot", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, RetVal), + Q_ARG(QString, TO_QSTRING(rkInfoBoxTitle)), + Q_ARG(QString, TO_QSTRING(rkQuestion)) ); + return RetVal; + } + +public slots: + bool AskYesNoQuestionSlot(const QString& rkInfoBoxTitle, const QString& rkQuestion) + { + return UICommon::YesNoQuestion(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkQuestion); + } +}; + +#endif // CUIRELAY_H diff --git a/src/Editor/Editor.pro b/src/Editor/Editor.pro index 61eea53c..4c14a548 100644 --- a/src/Editor/Editor.pro +++ b/src/Editor/Editor.pro @@ -182,7 +182,8 @@ HEADERS += \ WorldEditor/CPoiMapSidebar.h \ WorldEditor/CWorldEditorSidebar.h \ CProgressDialog.h \ - IProgressNotifierUI.h + IProgressNotifierUI.h \ + CUIRelay.h # Source Files SOURCES += \ diff --git a/src/Editor/ResourceBrowser/CResourceBrowser.cpp b/src/Editor/ResourceBrowser/CResourceBrowser.cpp index a1f0f165..69b2f789 100644 --- a/src/Editor/ResourceBrowser/CResourceBrowser.cpp +++ b/src/Editor/ResourceBrowser/CResourceBrowser.cpp @@ -1,5 +1,6 @@ #include "CResourceBrowser.h" #include "ui_CResourceBrowser.h" +#include "CProgressDialog.h" #include "Editor/CEditorApplication.h" #include #include @@ -7,6 +8,7 @@ #include #include #include +#include CResourceBrowser::CResourceBrowser(QWidget *pParent) : QDialog(pParent) @@ -96,6 +98,7 @@ CResourceBrowser::CResourceBrowser(QWidget *pParent) connect(pImportFromAssetNameMapAction, SIGNAL(triggered()), this, SLOT(OnImportNamesFromAssetNameMap())); connect(pGenerateAssetNamesAction, SIGNAL(triggered()), this, SLOT(OnGenerateAssetNames())); connect(mpUI->ExportNamesButton, SIGNAL(clicked()), this, SLOT(ExportAssetNames())); + connect(mpUI->RebuildDatabaseButton, SIGNAL(clicked(bool)), this, SLOT(RebuildResourceDB())); connect(&mUpdateFilterTimer, SIGNAL(timeout()), this, SLOT(UpdateFilter())); connect(mpFilterAllBox, SIGNAL(toggled(bool)), this, SLOT(OnFilterTypeBoxTicked(bool))); connect(gpEdApp, SIGNAL(ActiveProjectChanged(CGameProject*)), this, SLOT(UpdateStore())); @@ -327,9 +330,18 @@ void CResourceBrowser::OnImportPakContentsTxt() void CResourceBrowser::OnGenerateAssetNames() { SelectDirectory(nullptr); - GenerateAssetNames(mpStore->Project()); + + CProgressDialog Dialog("Generating asset names", true, true, this); + Dialog.DisallowCanceling(); + Dialog.SetOneShotTask("Generating asset names"); + + QFuture Future = QtConcurrent::run(&GenerateAssetNames, mpStore->Project()); + Dialog.WaitForResults(Future); + RefreshResources(); RefreshDirectories(); + + UICommon::InfoMsg(this, "Complete", "Asset name generation complete!"); } void CResourceBrowser::OnImportNamesFromAssetNameMap() @@ -393,6 +405,14 @@ void CResourceBrowser::ExportAssetNames() UICommon::InfoMsg(this, "Success", "Asset names exported successfully!"); } +void CResourceBrowser::RebuildResourceDB() +{ + if (UICommon::YesNoQuestion(this, "Rebuild resource database", "Are you sure you want to rebuild the resource database?")) + { + gpEdApp->RebuildResourceDatabase(); + } +} + void CResourceBrowser::UpdateFilter() { bool OldIsAssetList = InAssetListMode(); diff --git a/src/Editor/ResourceBrowser/CResourceBrowser.h b/src/Editor/ResourceBrowser/CResourceBrowser.h index af602c86..e479b118 100644 --- a/src/Editor/ResourceBrowser/CResourceBrowser.h +++ b/src/Editor/ResourceBrowser/CResourceBrowser.h @@ -67,6 +67,7 @@ public slots: void OnGenerateAssetNames(); void OnImportNamesFromAssetNameMap(); void ExportAssetNames(); + void RebuildResourceDB(); void UpdateFilter(); void ResetTypeFilter(); diff --git a/src/Editor/ResourceBrowser/CResourceBrowser.ui b/src/Editor/ResourceBrowser/CResourceBrowser.ui index 34c5dc78..2f0c5b0b 100644 --- a/src/Editor/ResourceBrowser/CResourceBrowser.ui +++ b/src/Editor/ResourceBrowser/CResourceBrowser.ui @@ -130,7 +130,7 @@ 0 0 189 - 126 + 117 @@ -189,6 +189,13 @@ + + + + Rebuild Database + + + diff --git a/src/Editor/UICommon.h b/src/Editor/UICommon.h index 5c74a7f5..9f1a41df 100644 --- a/src/Editor/UICommon.h +++ b/src/Editor/UICommon.h @@ -3,6 +3,7 @@ #include "CEditorApplication.h" #include +#include #include #include #include @@ -112,6 +113,12 @@ inline void ErrorMsg(QWidget *pParent, QString ErrorText) QMessageBox::warning(pParent, "Error", ErrorText); } +inline bool YesNoQuestion(QWidget *pParent, QString InfoBoxTitle, QString Question) +{ + QMessageBox::StandardButton Button = QMessageBox::question(pParent, InfoBoxTitle, Question, QMessageBox::Yes | QMessageBox::No); + return Button == QMessageBox::Yes; +} + // Constants const QColor kImportantButtonColor(36, 100, 100); diff --git a/src/Editor/main.cpp b/src/Editor/main.cpp index 670f9431..2bd02103 100644 --- a/src/Editor/main.cpp +++ b/src/Editor/main.cpp @@ -1,4 +1,5 @@ #include "CEditorApplication.h" +#include "CUIRelay.h" #include "UICommon.h" #include #include @@ -36,6 +37,10 @@ int main(int argc, char *argv[]) if (!Initialized) QMessageBox::warning(0, "Error", "Couldn't open log file. Logging will not work for this session."); qInstallMessageHandler(QtLogRedirect); + // Create UI relay + CUIRelay UIRelay(&App); + gpUIRelay = &UIRelay; + // Create editor resource store gpEditorStore = new CResourceStore("../resources/EditorResourceDB.rdb"); gpEditorStore->LoadResourceDatabase();