Added ability to rebuild the resource database from the project resources folder. Editor can detect if the resource database is corrupt on load and if so prompts the user to repair it.

This commit is contained in:
Aruki 2017-07-04 04:59:22 -06:00
parent 1f3df14b02
commit 9a52fe52d4
29 changed files with 425 additions and 93 deletions

View File

@ -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;

View File

@ -28,6 +28,7 @@ class CAudioManager
public:
CAudioManager(CGameProject *pProj);
void LoadAssets();
void ClearAssets();
SSoundInfo GetSoundInfo(u32 SoundID);
void LogSoundInfo(u32 SoundID);
};

View File

@ -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

View File

@ -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 ***");
}

View File

@ -1,4 +1,5 @@
#include "CGameProject.h"
#include "IUIRelay.h"
#include "Core/Resource/Factory/CTemplateLoader.h"
#include "Core/Resource/Script/CMasterTemplate.h"
#include <Common/Serialization/XML.h>
@ -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<TString> PackageList;
if (!rArc.IsReader())
@ -61,18 +62,9 @@ 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);
if (!mpResourceStore->LoadResourceDatabase())
mLoadSuccess = false;
else
{
// Load packages
ASSERT(mPackages.empty());
for (u32 iPkg = 0; iPkg < PackageList.size(); iPkg++)
@ -87,12 +79,12 @@ void CGameProject::Serialize(IArchive& rArc)
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;

View File

@ -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<CAssetID>& 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; }

View File

@ -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;
}
}

View File

@ -7,22 +7,22 @@
class CResourceIterator
{
protected:
CResourceStore *mpStore;
std::map<CAssetID, CResourceEntry*>::iterator mIter;
const CResourceStore *mpkStore;
std::map<CAssetID, CResourceEntry*>::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();
}

View File

@ -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;

View File

@ -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<typename ResType> ResType* LoadResource(const CAssetID& rkID) { return static_cast<ResType*>(LoadResource(rkID, ResType::StaticType())); }
CResource* LoadResource(const CAssetID& rkID);

View File

@ -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)
{

View File

@ -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);

View File

@ -2,6 +2,7 @@
#define IPROGRESSNOTIFIER_H
#include <Common/Common.h>
#include <Math/MathUtil.h>
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:

3
src/Core/IUIRelay.cpp Normal file
View File

@ -0,0 +1,3 @@
#include "IUIRelay.h"
IUIRelay *gpUIRelay = nullptr;

13
src/Core/IUIRelay.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef IUIRELAY_H
#define IUIRELAY_H
#include <Common/TString.h>
class IUIRelay
{
public:
virtual bool AskYesNoQuestion(const TString& rkInfoBoxTitle, const TString& rkQuestion) = 0;
};
extern IUIRelay *gpUIRelay;
#endif // IUIRELAY_H

View File

@ -41,11 +41,9 @@ void CEditorApplication::InitEditor()
mpWorldEditor->showMaximized();
}
bool CEditorApplication::CloseProject()
bool CEditorApplication::CloseAllEditors()
{
if (mpActiveProject)
{
// Close active editor windows. todo: check for unsaved changes
// Close active editor windows.
foreach (IEditor *pEditor, mEditorWindows)
{
if (pEditor != mpWorldEditor && !pEditor->close())
@ -58,7 +56,13 @@ bool CEditorApplication::CloseProject()
mpResourceBrowser->close();
mpProjectDialog->close();
return true;
}
bool CEditorApplication::CloseProject()
{
if (mpActiveProject && CloseAllEditors())
{
// 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<CGameProject*> Future = QtConcurrent::run(&CGameProject::LoadProject, Path, &Dialog);
mpActiveProject = Dialog.WaitForResults(Future);
Dialog.close();
if (mpActiveProject)
{
@ -169,7 +178,7 @@ bool CEditorApplication::CookPackageList(QList<CPackage*> 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<void> Future = QtConcurrent::run([&]()
{
@ -191,6 +200,45 @@ bool CEditorApplication::CookPackageList(QList<CPackage*> 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<void> 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)
{

View File

@ -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<CPackage*> PackageList);
bool RebuildResourceDatabase();
// Accessors
inline CGameProject* ActiveProject() const { return mpActiveProject; }
inline CWorldEditor* WorldEditor() const { return mpWorldEditor; }

View File

@ -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<bool> Future = QtConcurrent::run(&Exporter, &CGameExporter::Export, mpDisc, StrExportDir, &NameMap, &GameInfo, &Dialog);
mExportSuccess = Dialog.WaitForResults(Future);

View File

@ -3,16 +3,17 @@
#include "CEditorApplication.h"
#include <QCloseEvent>
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);
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);
#endif
}
}

View File

@ -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();

View File

@ -36,7 +36,7 @@
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="TaskInfoBoxLayout">
<item>
<widget class="QLabel" name="TaskLabel">
<property name="font">
@ -62,7 +62,7 @@
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<spacer name="LabelSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>

View File

@ -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<void> Future = QtConcurrent::run(pProj, &CGameProject::BuildISO, TO_TSTRING(IsoPath), &Dialog);
Dialog.WaitForResults(Future);

37
src/Editor/CUIRelay.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef CUIRELAY_H
#define CUIRELAY_H
#include <Core/IUIRelay.h>
#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

View File

@ -182,7 +182,8 @@ HEADERS += \
WorldEditor/CPoiMapSidebar.h \
WorldEditor/CWorldEditorSidebar.h \
CProgressDialog.h \
IProgressNotifierUI.h
IProgressNotifierUI.h \
CUIRelay.h
# Source Files
SOURCES += \

View File

@ -1,5 +1,6 @@
#include "CResourceBrowser.h"
#include "ui_CResourceBrowser.h"
#include "CProgressDialog.h"
#include "Editor/CEditorApplication.h"
#include <Core/GameProject/AssetNameGeneration.h>
#include <Core/GameProject/CAssetNameMap.h>
@ -7,6 +8,7 @@
#include <QFileDialog>
#include <QMenu>
#include <QMessageBox>
#include <QtConcurrent/QtConcurrentRun>
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<void> 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();

View File

@ -67,6 +67,7 @@ public slots:
void OnGenerateAssetNames();
void OnImportNamesFromAssetNameMap();
void ExportAssetNames();
void RebuildResourceDB();
void UpdateFilter();
void ResetTypeFilter();

View File

@ -130,7 +130,7 @@
<x>0</x>
<y>0</y>
<width>189</width>
<height>126</height>
<height>117</height>
</rect>
</property>
</widget>
@ -189,6 +189,13 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="RebuildDatabaseButton">
<property name="text">
<string>Rebuild Database</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -3,6 +3,7 @@
#include "CEditorApplication.h"
#include <Common/TString.h>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QMap>
#include <QMessageBox>
@ -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);

View File

@ -1,4 +1,5 @@
#include "CEditorApplication.h"
#include "CUIRelay.h"
#include "UICommon.h"
#include <Common/Log.h>
#include <Core/Resource/Factory/CTemplateLoader.h>
@ -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();