diff --git a/src/Common/Flags.h b/src/Common/Flags.h index bf8554e6..491488b0 100644 --- a/src/Common/Flags.h +++ b/src/Common/Flags.h @@ -32,6 +32,10 @@ public: inline bool HasFlag(FlagEnum Flag) const { return ((mValue & Flag) != 0); } inline bool HasAnyFlags(TFlags Flags) const { return ((mValue & Flags) != 0); } inline bool HasAllFlags(TFlags Flags) const { return ((mValue & Flags) == Flags); } + inline void SetFlag(FlagEnum Flag) { mValue |= Flag; } + inline void SetFlag(TFlags Flags) { mValue |= Flags; } + inline void ClearFlag(FlagEnum Flag) { mValue &= ~Flag; } + inline void ClearFlag(TFlags Flags) { mValue &= ~Flags; } }; #define DECLARE_FLAGS(Enum, FlagTypeName) typedef TFlags FlagTypeName; diff --git a/src/Common/TString.h b/src/Common/TString.h index f933151c..25bbb160 100644 --- a/src/Common/TString.h +++ b/src/Common/TString.h @@ -212,7 +212,7 @@ public: inline void Insert(u32 Pos, CharType Chr) { #ifdef _DEBUG - if (Size() <= Pos) + if (Size() < Pos) throw std::out_of_range("Invalid position passed to TBasicString::Insert()"); #endif mInternalString.insert(Pos, 1, Chr); @@ -221,7 +221,7 @@ public: inline void Insert(u32 Pos, const CharType* pkStr) { #ifdef _DEBUG - if (Size() <= Pos) + if (Size() < Pos) throw std::out_of_range("Invalid position passed to TBasicString::Insert()"); #endif mInternalString.insert(Pos, pkStr); @@ -846,7 +846,10 @@ public: static TBasicString FromFloat(float Value, int MinDecimals = 1) { - _TString Out = std::to_string(Value); + std::basic_stringstream sstream; + sstream << Value; + _TString Out = sstream.str(); + int NumZeroes = Out.Size() - (Out.IndexOf(LITERAL(".")) + 1); while (Out.Back() == CHAR_LITERAL('0') && NumZeroes > MinDecimals) diff --git a/src/Core/Core.pro b/src/Core/Core.pro index a6573d17..251c321b 100644 --- a/src/Core/Core.pro +++ b/src/Core/Core.pro @@ -196,7 +196,10 @@ HEADERS += \ GameProject/CResourceStore.h \ GameProject/CVirtualDirectory.h \ GameProject/CResourceEntry.h \ - GameProject/CResourceIterator.h + GameProject/CResourceIterator.h \ + Resource/CDependencyGroup.h \ + Resource/Factory/CDependencyGroupLoader.h \ + GameProject/CDependencyTree.h # Source Files SOURCES += \ @@ -287,4 +290,6 @@ SOURCES += \ GameProject/CResourceStore.cpp \ GameProject/CVirtualDirectory.cpp \ GameProject/CResourceEntry.cpp \ - GameProject/CPackage.cpp + GameProject/CPackage.cpp \ + Resource/Factory/CDependencyGroupLoader.cpp \ + GameProject/CDependencyTree.cpp diff --git a/src/Core/GameProject/CDependencyTree.cpp b/src/Core/GameProject/CDependencyTree.cpp new file mode 100644 index 00000000..e433fb0c --- /dev/null +++ b/src/Core/GameProject/CDependencyTree.cpp @@ -0,0 +1,320 @@ +#include "CDependencyTree.h" +#include "Core/Resource/Script/CScriptLayer.h" +#include "Core/Resource/Script/CScriptObject.h" + +// ************ CResourceDependency ************ +EDependencyNodeType CResourceDependency::Type() const +{ + return eDNT_ResourceDependency; +} + +void CResourceDependency::Read(IInputStream& rFile, EUIDLength IDLength) +{ + mID = CUniqueID(rFile, IDLength); +} + +void CResourceDependency::Write(IOutputStream& rFile, EUIDLength IDLength) const +{ + if (IDLength == e32Bit) + rFile.WriteLong(mID.ToLong()); + else + rFile.WriteLongLong(mID.ToLongLong()); +} + +// ************ CAnimSetDependency ************ +EDependencyNodeType CAnimSetDependency::Type() const +{ + return eDNT_AnimSet; +} + +void CAnimSetDependency::Read(IInputStream& rFile, EUIDLength IDLength) +{ + CResourceDependency::Read(rFile, IDLength); + mUsedChar = rFile.ReadLong(); +} + +void CAnimSetDependency::Write(IOutputStream& rFile, EUIDLength IDLength) const +{ + CResourceDependency::Write(rFile, IDLength); + rFile.WriteLong(mUsedChar); +} + +// Static +CAnimSetDependency* CAnimSetDependency::BuildDependency(TCharacterProperty *pProp) +{ + ASSERT(pProp && pProp->Type() == eCharacterProperty && pProp->Instance()->Area()->Game() <= eEchoes); + + CAnimationParameters Params = pProp->Get(); + if (!Params.ID().IsValid()) return nullptr; + + CAnimSetDependency *pDepend = new CAnimSetDependency; + pDepend->SetID(Params.ID()); + pDepend->SetUsedChar(Params.CharacterIndex()); + return pDepend; +} + +// ************ CDependencyTree ************ +CDependencyTree::~CDependencyTree() +{ + for (u32 iRef = 0; iRef < mReferencedResources.size(); iRef++) + delete mReferencedResources[iRef]; +} + +EDependencyNodeType CDependencyTree::Type() const +{ + return eDNT_Root; +} + +void CDependencyTree::Read(IInputStream& rFile, EUIDLength IDLength) +{ + mID = CUniqueID(rFile, IDLength); + + u32 NumDepends = rFile.ReadLong(); + mReferencedResources.reserve(NumDepends); + + for (u32 iDep = 0; iDep < NumDepends; iDep++) + { + CResourceDependency *pDepend = new CResourceDependency; + pDepend->Read(rFile, IDLength); + mReferencedResources.push_back(pDepend); + } +} + +void CDependencyTree::Write(IOutputStream& rFile, EUIDLength IDLength) const +{ + if (IDLength == e32Bit) + rFile.WriteLong(mID.ToLong()); + else + rFile.WriteLongLong(mID.ToLongLong()); + + rFile.WriteLong( mReferencedResources.size() ); + + for (u32 iDep = 0; iDep < mReferencedResources.size(); iDep++) + mReferencedResources[iDep]->Write(rFile, IDLength); +} + +u32 CDependencyTree::NumDependencies() const +{ + return mReferencedResources.size(); +} + +bool CDependencyTree::HasDependency(const CUniqueID& rkID) +{ + for (u32 iDep = 0; iDep < mReferencedResources.size(); iDep++) + { + if (mReferencedResources[iDep]->ID() == rkID) + return true; + } + + return false; +} + +CUniqueID CDependencyTree::DependencyByIndex(u32 Index) const +{ + ASSERT(Index >= 0 && Index < mReferencedResources.size()); + return mReferencedResources[Index]->ID(); +} + +void CDependencyTree::AddDependency(CResource *pRes) +{ + if (!pRes || HasDependency(pRes->ResID())) return; + CResourceDependency *pDepend = new CResourceDependency(pRes->ResID()); + mReferencedResources.push_back(pDepend); +} + +void CDependencyTree::AddDependency(const CUniqueID& rkID) +{ + if (!rkID.IsValid() || HasDependency(rkID)) return; + CResourceDependency *pDepend = new CResourceDependency(rkID); + mReferencedResources.push_back(pDepend); +} + +// ************ CAnimSetDependencyTree ************ +EDependencyNodeType CAnimSetDependencyTree::Type() const +{ + return eDNT_AnimSet; +} + +void CAnimSetDependencyTree::Read(IInputStream& rFile, EUIDLength IDLength) +{ + CDependencyTree::Read(rFile, IDLength); + u32 NumChars = rFile.ReadLong(); + mCharacterOffsets.reserve(NumChars); + + for (u32 iChar = 0; iChar < NumChars; iChar++) + mCharacterOffsets.push_back( rFile.ReadLong() ); +} + +void CAnimSetDependencyTree::Write(IOutputStream& rFile, EUIDLength IDLength) const +{ + CDependencyTree::Write(rFile, IDLength); + rFile.WriteLong(mCharacterOffsets.size()); + + for (u32 iChar = 0; iChar < mCharacterOffsets.size(); iChar++) + rFile.WriteLong( mCharacterOffsets[iChar] ); +} + +// ************ CScriptInstanceDependencyTree ************ +CScriptInstanceDependencyTree::~CScriptInstanceDependencyTree() +{ + for (u32 iDep = 0; iDep < mDependencies.size(); iDep++) + delete mDependencies[iDep]; +} + +EDependencyNodeType CScriptInstanceDependencyTree::Type() const +{ + return eDNT_ScriptInstance; +} + +void CScriptInstanceDependencyTree::Read(IInputStream& rFile, EUIDLength IDLength) +{ + mObjectType = rFile.ReadLong(); + u32 NumDepends = rFile.ReadLong(); + mDependencies.reserve(NumDepends); + + for (u32 iDep = 0; iDep < NumDepends; iDep++) + { + CUniqueID ID(rFile, IDLength); + CResourceEntry *pEntry = gpResourceStore->FindEntry(ID); + + if (pEntry && pEntry->ResourceType() == eAnimSet && pEntry->Game() <= eEchoes) + { + CAnimSetDependency *pSet = new CAnimSetDependency(); + pSet->SetID(ID); + pSet->SetUsedChar( rFile.ReadLong() ); + mDependencies.push_back(pSet); + } + + else + { + CResourceDependency *pRes = new CResourceDependency(ID); + mDependencies.push_back(pRes); + } + } +} + +void CScriptInstanceDependencyTree::Write(IOutputStream& rFile, EUIDLength IDLength) const +{ + rFile.WriteLong(mObjectType); + rFile.WriteLong(mDependencies.size()); + + for (u32 iDep = 0; iDep < mDependencies.size(); iDep++) + mDependencies[iDep]->Write(rFile, IDLength); +} + +bool CScriptInstanceDependencyTree::HasDependency(const CUniqueID& rkID) +{ + if (!rkID.IsValid()) return false; + + for (u32 iDep = 0; iDep < mDependencies.size(); iDep++) + { + CResourceDependency *pDep = mDependencies[iDep]; + if (pDep->ID() == rkID) return true; + } + + return false; +} + +// Static +CScriptInstanceDependencyTree* CScriptInstanceDependencyTree::BuildTree(CScriptObject *pInstance) +{ + CScriptInstanceDependencyTree *pTree = new CScriptInstanceDependencyTree(); + pTree->mObjectType = pInstance->ObjectTypeID(); + ParseStructDependencies(pTree, pInstance->Properties()); + return pTree; +} + +void CScriptInstanceDependencyTree::ParseStructDependencies(CScriptInstanceDependencyTree *pTree, CPropertyStruct *pStruct) +{ + for (u32 iProp = 0; iProp < pStruct->Count(); iProp++) + { + IProperty *pProp = pStruct->PropertyByIndex(iProp); + + if (pProp->Type() == eStructProperty || pProp->Type() == eArrayProperty) + ParseStructDependencies(pTree, static_cast(pProp)); + + else if (pProp->Type() == eFileProperty) + { + CUniqueID ID = static_cast(pProp)->Get().ID(); + + if (ID.IsValid() && !pTree->HasDependency(ID)) + { + CResourceDependency *pDep = new CResourceDependency(ID); + pTree->mDependencies.push_back(pDep); + } + } + + else if (pProp->Type() == eCharacterProperty) + { + TCharacterProperty *pChar = static_cast(pProp); + CUniqueID ID = pChar->Get().ID(); + + if (ID.IsValid() && !pTree->HasDependency(ID)) + pTree->mDependencies.push_back( CAnimSetDependency::BuildDependency(pChar) ); + } + } +} + +// ************ CAreaDependencyTree ************ +CAreaDependencyTree::~CAreaDependencyTree() +{ + for (u32 iInst = 0; iInst < mScriptInstances.size(); iInst++) + delete mScriptInstances[iInst]; +} + +EDependencyNodeType CAreaDependencyTree::Type() const +{ + return eDNT_Area; +} + +void CAreaDependencyTree::Read(IInputStream& rFile, EUIDLength IDLength) +{ + // Base dependency list contains non-script dependencies (world geometry textures + PATH/PTLA/EGMC) + CDependencyTree::Read(rFile, IDLength); + u32 NumScriptInstances = rFile.ReadLong(); + mScriptInstances.reserve(NumScriptInstances); + + for (u32 iInst = 0; iInst < NumScriptInstances; iInst++) + { + CScriptInstanceDependencyTree *pInst = new CScriptInstanceDependencyTree; + pInst->Read(rFile, IDLength); + mScriptInstances.push_back(pInst); + } + + u32 NumLayers = rFile.ReadLong(); + mLayerOffsets.reserve(NumLayers); + + for (u32 iLyr = 0; iLyr < NumLayers; iLyr++) + mLayerOffsets.push_back( rFile.ReadLong() ); +} + +void CAreaDependencyTree::Write(IOutputStream& rFile, EUIDLength IDLength) const +{ + CDependencyTree::Write(rFile, IDLength); + rFile.WriteLong(mScriptInstances.size()); + + for (u32 iInst = 0; iInst < mScriptInstances.size(); iInst++) + mScriptInstances[iInst]->Write(rFile, IDLength); + + rFile.WriteLong(mLayerOffsets.size()); + + for (u32 iLyr = 0; iLyr < mLayerOffsets.size(); iLyr++) + rFile.WriteLong(mLayerOffsets[iLyr]); +} + +void CAreaDependencyTree::AddScriptLayer(CScriptLayer *pLayer) +{ + if (!pLayer) return; + mLayerOffsets.push_back(mScriptInstances.size()); + + for (u32 iInst = 0; iInst < pLayer->NumInstances(); iInst++) + { + CScriptInstanceDependencyTree *pTree = CScriptInstanceDependencyTree::BuildTree( pLayer->InstanceByIndex(iInst) ); + ASSERT(pTree != nullptr); + + if (pTree->NumDependencies() > 0) + mScriptInstances.push_back(pTree); + else + delete pTree; + } +} diff --git a/src/Core/GameProject/CDependencyTree.h b/src/Core/GameProject/CDependencyTree.h new file mode 100644 index 00000000..23dbd264 --- /dev/null +++ b/src/Core/GameProject/CDependencyTree.h @@ -0,0 +1,156 @@ +#ifndef CDEPENDENCYTREE +#define CDEPENDENCYTREE + +#include "CResourceEntry.h" +#include +#include +#include + +class CScriptLayer; +class CScriptObject; +class CPropertyStruct; +class TCharacterProperty; + +// Group of node classes forming a tree of cached resource dependencies. +enum EDependencyNodeType +{ + eDNT_Root, + eDNT_AnimSet, + eDNT_ScriptInstance, + eDNT_Area, + eDNT_ResourceDependency, + eDNT_AnimSetDependency +}; + +// Base class providing an interface for reading/writing to cache file and determining type. +class IDependencyNode +{ +public: + virtual ~IDependencyNode() {} + virtual EDependencyNodeType Type() const = 0; + virtual void Read(IInputStream& rFile, EUIDLength IDLength) = 0; + virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const = 0; +}; + +// Node representing a single resource dependency. +class CResourceDependency : public IDependencyNode +{ + CUniqueID mID; + +public: + CResourceDependency() {} + CResourceDependency(const CUniqueID& rkID) : mID(rkID) {} + + virtual EDependencyNodeType Type() const; + virtual void Read(IInputStream& rFile, EUIDLength IDLength); + virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const; + + // Accessors + inline CUniqueID ID() const { return mID; } + inline void SetID(const CUniqueID& rkID) { mID = rkID; } +}; + +// Node representing a single animset dependency contained in a script object. Indicates which character is being used. +class CAnimSetDependency : public CResourceDependency +{ +protected: + u32 mUsedChar; + +public: + CAnimSetDependency() : CResourceDependency(), mUsedChar(-1) {} + + virtual EDependencyNodeType Type() const; + virtual void Read(IInputStream& rFile, EUIDLength IDLength); + virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const; + + // Accessors + inline u32 UsedChar() const { return mUsedChar; } + inline void SetUsedChar(u32 CharIdx) { mUsedChar = CharIdx; } + + // Static + static CAnimSetDependency* BuildDependency(TCharacterProperty *pProp); +}; + +// Tree root node, representing a resource. +class CDependencyTree : public IDependencyNode +{ +protected: + CUniqueID mID; + std::vector mReferencedResources; + +public: + CDependencyTree(const CUniqueID& rkID) : mID(rkID) {} + ~CDependencyTree(); + + virtual EDependencyNodeType Type() const; + virtual void Read(IInputStream& rFile, EUIDLength IDLength); + virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const; + + u32 NumDependencies() const; + bool HasDependency(const CUniqueID& rkID); + CUniqueID DependencyByIndex(u32 Index) const; + void AddDependency(const CUniqueID& rkID); + void AddDependency(CResource *pRes); + + // Accessors + inline void SetID(const CUniqueID& rkID) { mID = rkID; } + inline CUniqueID ID() const { return mID; } + +}; + +// Node representing an animset resource; allows for lookup of dependencies of a particular character in the set. +class CAnimSetDependencyTree : public CDependencyTree +{ +protected: + std::vector mCharacterOffsets; + +public: + CAnimSetDependencyTree(const CUniqueID& rkID) : CDependencyTree(rkID) {} + virtual EDependencyNodeType Type() const; + virtual void Read(IInputStream& rFile, EUIDLength IDLength); + virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const; +}; + +// Node representing a script object. Indicates the type of object. +class CScriptInstanceDependencyTree : public IDependencyNode +{ +protected: + u32 mObjectType; + std::vector mDependencies; + +public: + ~CScriptInstanceDependencyTree(); + + virtual EDependencyNodeType Type() const; + virtual void Read(IInputStream& rFile, EUIDLength IDLength); + virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const; + bool HasDependency(const CUniqueID& rkID); + + // Accessors + u32 NumDependencies() const { return mDependencies.size(); } + + // Static + static CScriptInstanceDependencyTree* BuildTree(CScriptObject *pInstance); + static void ParseStructDependencies(CScriptInstanceDependencyTree *pTree, CPropertyStruct *pStruct); +}; + +// Node representing an area. Tracks dependencies on a per-instance basis and can separate dependencies of different script layers. +class CAreaDependencyTree : public CDependencyTree +{ +protected: + std::vector mScriptInstances; + std::vector mLayerOffsets; + +public: + CAreaDependencyTree(const CUniqueID& rkID) : CDependencyTree(rkID) {} + ~CAreaDependencyTree(); + + virtual EDependencyNodeType Type() const; + virtual void Read(IInputStream& rFile, EUIDLength IDLength); + virtual void Write(IOutputStream& rFile, EUIDLength IDLength) const; + + void AddScriptLayer(CScriptLayer *pLayer); +}; + +#endif // CDEPENDENCYTREE + diff --git a/src/Core/GameProject/CGameExporter.cpp b/src/Core/GameProject/CGameExporter.cpp index 0e97ee49..73b3e6fb 100644 --- a/src/Core/GameProject/CGameExporter.cpp +++ b/src/Core/GameProject/CGameExporter.cpp @@ -1,4 +1,5 @@ #include "CGameExporter.h" +#include "Core/GameProject/CResourceIterator.h" #include "Core/GameProject/CResourceStore.h" #include "Core/Resource/CWorld.h" #include "Core/Resource/Script/CMasterTemplate.h" @@ -14,6 +15,7 @@ #define SAVE_PACKAGE_DEFINITIONS 1 #define EXPORT_WORLDS 1 #define EXPORT_COOKED 1 +#define EXPORT_CACHE 1 CGameExporter::CGameExporter(const TString& rkInputDir, const TString& rkOutputDir) : mStore(this) @@ -476,7 +478,6 @@ void CGameExporter::ExportWorlds() void CGameExporter::ExportCookedResources() { -#if EXPORT_COOKED { SCOPED_TIMER(ExportCookedResources); FileUtil::CreateDirectory(mCookedDir); @@ -487,7 +488,6 @@ void CGameExporter::ExportCookedResources() ExportResource(rRes); } } -#endif { SCOPED_TIMER(SaveResourceDatabase); #if EXPORT_COOKED @@ -495,6 +495,20 @@ void CGameExporter::ExportCookedResources() #endif mpProject->Save(); } +#if EXPORT_CACHE + { + SCOPED_TIMER(SaveCacheData); + + for (CResourceIterator It(&mStore); It; ++It) + { + if (!It->IsTransient()) + { + It->UpdateDependencies(); + It->SaveCacheData(); + } + } + } +#endif } void CGameExporter::ExportResource(SResourceInstance& rRes) @@ -520,6 +534,7 @@ void CGameExporter::ExportResource(SResourceInstance& rRes) // Register resource and write to file CResourceEntry *pEntry = mStore.RegisterResource(rRes.ResourceID, CResource::ResTypeForExtension(rRes.ResourceType), OutDir, OutName); +#if EXPORT_COOKED // Cooked (todo: save raw) TWideString OutPath = pEntry->CookedAssetPath(); FileUtil::CreateDirectory(OutPath.GetFileDirectory()); @@ -530,5 +545,8 @@ void CGameExporter::ExportResource(SResourceInstance& rRes) rRes.Exported = true; ASSERT(pEntry->HasCookedVersion()); +#else + (void) pEntry; // Prevent "unused local variable" compiler warning +#endif } } diff --git a/src/Core/GameProject/CGameProject.cpp b/src/Core/GameProject/CGameProject.cpp index 0a49804a..a5306aae 100644 --- a/src/Core/GameProject/CGameProject.cpp +++ b/src/Core/GameProject/CGameProject.cpp @@ -68,6 +68,7 @@ bool CGameProject::Load(const TWideString& rkPath) // All loaded! mProjectRoot = rkPath.GetFileDirectory(); + mProjectRoot.Replace(L"/", L"\\"); return true; } diff --git a/src/Core/GameProject/CGameProject.h b/src/Core/GameProject/CGameProject.h index 712d47cf..862ff854 100644 --- a/src/Core/GameProject/CGameProject.h +++ b/src/Core/GameProject/CGameProject.h @@ -38,7 +38,9 @@ public: , mProjectName("Unnamed Project") , mProjectRoot(rkProjRootDir) , mResourceDBPath(L"ResourceDB.rdb") - {} + { + mProjectRoot.Replace(L"/", L"\\"); + } ~CGameProject(); @@ -51,6 +53,7 @@ public: inline TWideString ProjectRoot() const { return mProjectRoot; } inline TWideString ResourceDBPath(bool Relative) const { return Relative ? mResourceDBPath : mProjectRoot + mResourceDBPath; } inline TWideString DiscDir(bool Relative) const { return Relative ? L"Disc\\" : mProjectRoot + L"Disc\\"; } + inline TWideString CacheDir(bool Relative) const { return Relative ? L"Cache\\" : mProjectRoot + L"Cache\\"; } inline TWideString ContentDir(bool Relative) const { return Relative ? L"Content\\" : mProjectRoot + L"Content\\"; } inline TWideString CookedDir(bool Relative) const { return Relative ? L"Cooked\\" : mProjectRoot + L"Cooked\\"; } inline TWideString PackagesDir(bool Relative) const { return Relative ? L"Packages\\" : mProjectRoot + L"Packages\\"; } diff --git a/src/Core/GameProject/CResourceEntry.cpp b/src/Core/GameProject/CResourceEntry.cpp index 83c9d973..f319f725 100644 --- a/src/Core/GameProject/CResourceEntry.cpp +++ b/src/Core/GameProject/CResourceEntry.cpp @@ -2,6 +2,7 @@ #include "CGameProject.h" #include "CResourceStore.h" #include "Core/Resource/CResource.h" +#include #include #include @@ -11,6 +12,7 @@ #include "Core/Resource/Factory/CAnimSetLoader.h" #include "Core/Resource/Factory/CAreaLoader.h" #include "Core/Resource/Factory/CCollisionLoader.h" +#include "Core/Resource/Factory/CDependencyGroupLoader.h" #include "Core/Resource/Factory/CFontLoader.h" #include "Core/Resource/Factory/CMaterialLoader.h" #include "Core/Resource/Factory/CModelLoader.h" @@ -26,19 +28,20 @@ CResourceEntry::CResourceEntry(CResourceStore *pStore, const CUniqueID& rkID, const TWideString& rkDir, const TWideString& rkFilename, EResType Type, bool Transient /*= false*/) - : mpStore(pStore) - , mpResource(nullptr) + : mpResource(nullptr) + , mpStore(pStore) + , mpDependencies(nullptr) , mID(rkID) , mName(rkFilename) , mType(Type) - , mNeedsRecook(false) - , mTransient(Transient) , mCachedSize(-1) , mCachedUppercaseName(rkFilename.ToUpper()) { + if (Transient) mFlags |= eREF_Transient; + mpDirectory = mpStore->GetVirtualDirectory(rkDir, Transient, true); if (mpDirectory) mpDirectory->AddChild(L"", this); - mGame = ((mTransient || !mpStore->ActiveProject()) ? eUnknownVersion : mpStore->ActiveProject()->Game()); + mGame = ((Transient || !mpStore->ActiveProject()) ? eUnknownVersion : mpStore->ActiveProject()->Game()); } CResourceEntry::~CResourceEntry() @@ -46,6 +49,104 @@ CResourceEntry::~CResourceEntry() if (mpResource) delete mpResource; } +bool CResourceEntry::LoadCacheData() +{ + ASSERT(!IsTransient()); + + TWideString Path = CacheDataPath(false); + CFileInStream File(Path.ToUTF8().ToStdString(), IOUtil::eLittleEndian); + + if (!File.IsValid()) + { + Log::Error("Unable to load cache data " + Path.ToUTF8() + "; couldn't open file"); + return false; + } + + // Header + TString Magic = File.ReadString(4); + ASSERT(Magic == "CACH"); + File.Seek(0x4, SEEK_CUR); // Skip Version + mFlags = File.ReadLong() & eREF_SavedFlags; + + // Dependency Tree + u32 DepsTreeSize = File.ReadLong(); + + if (mpDependencies) + { + delete mpDependencies; + mpDependencies = nullptr; + } + + if (DepsTreeSize > 0) + { + mpDependencies = new CDependencyTree(mID); + mpDependencies->Read(File, Game() <= eEchoes ? e32Bit : e64Bit); + } + + return true; +} + +bool CResourceEntry::SaveCacheData() +{ + ASSERT(!IsTransient()); + + TWideString Path = CacheDataPath(false); + TWideString Dir = Path.GetFileDirectory(); + FileUtil::CreateDirectory(Dir); + CFileOutStream File(Path.ToUTF8().ToStdString(), IOUtil::eLittleEndian); + + if (!File.IsValid()) + { + Log::Error("Unable to save cache data " + TString(Path.GetFileName()) + "; couldn't open file"); + return false; + } + + // Header + File.WriteString("CACH", 4); + File.WriteLong(0); // Reserved Space (Version) + File.WriteLong(mFlags & eREF_SavedFlags); + + // Dependency Tree + if (!mpDependencies) UpdateDependencies(); + + u32 DepsSizeOffset = File.Tell(); + File.WriteLong(0); + + u32 DepsStart = File.Tell(); + if (mpDependencies) mpDependencies->Write(File, Game() <= eEchoes ? e32Bit : e64Bit); + u32 DepsSize = File.Tell() - DepsStart; + File.Seek(DepsSizeOffset, SEEK_SET); + File.WriteLong(DepsSize); + + return true; +} + +void CResourceEntry::UpdateDependencies() +{ + if (mpDependencies) + { + delete mpDependencies; + mpDependencies = nullptr; + } + + if (!mpResource) + Load(); + + if (!mpResource) + { + Log::Error("Unable to update cached dependencies; failed to load resource"); + return; + } + + mpDependencies = mpResource->BuildDependencyTree(); + gpResourceStore->DestroyUnreferencedResources(); +} + +TWideString CResourceEntry::CacheDataPath(bool Relative) const +{ + return mpStore->ActiveProject()->CacheDir(Relative) + mID.ToString().ToUTF16() + L".rcd"; +} + bool CResourceEntry::HasRawVersion() const { return FileUtil::Exists(RawAssetPath()); @@ -61,7 +162,7 @@ TString CResourceEntry::RawAssetPath(bool Relative) const TWideString Ext = GetResourceRawExtension(mType, mGame).ToUTF16(); TWideString Path = mpDirectory ? mpDirectory->FullPath() : L""; TWideString Name = mName + L"." + Ext; - return ((mTransient || Relative) ? Path + Name : mpStore->ActiveProject()->ContentDir(false) + Path + Name); + return ((IsTransient() || Relative) ? Path + Name : mpStore->ActiveProject()->ContentDir(false) + Path + Name); } TString CResourceEntry::CookedAssetPath(bool Relative) const @@ -69,7 +170,7 @@ TString CResourceEntry::CookedAssetPath(bool Relative) const TWideString Ext = GetResourceCookedExtension(mType, mGame).ToUTF16(); TWideString Path = mpDirectory ? mpDirectory->FullPath() : L""; TWideString Name = mName + L"." + Ext; - return ((mTransient || Relative) ? Path + Name : mpStore->ActiveProject()->CookedDir(false) + Path + Name); + return ((IsTransient() || Relative) ? Path + Name : mpStore->ActiveProject()->CookedDir(false) + Path + Name); } bool CResourceEntry::IsInDirectory(CVirtualDirectory *pDir) const @@ -101,11 +202,11 @@ u64 CResourceEntry::Size() const bool CResourceEntry::NeedsRecook() const { // Assets that do not have a raw version can't be recooked since they will always just be saved cooked to begin with. - // We will recook any asset where the raw version has been updated but not recooked yet. mNeedsRecook can also be + // We will recook any asset where the raw version has been updated but not recooked yet. eREF_NeedsRecook can also be // toggled to arbitrarily flag any asset for recook. if (!HasRawVersion()) return false; if (!HasCookedVersion()) return true; - if (mNeedsRecook) return true; + if (mFlags.HasFlag(eREF_NeedsRecook)) return true; return (FileUtil::LastModifiedTime(CookedAssetPath()) < FileUtil::LastModifiedTime(RawAssetPath())); } @@ -152,6 +253,7 @@ CResource* CResourceEntry::Load(IInputStream& rInput) case eAnimation: mpResource = CAnimationLoader::LoadANIM(rInput, this); break; case eAnimSet: mpResource = CAnimSetLoader::LoadANCSOrCHAR(rInput, this); break; case eArea: mpResource = CAreaLoader::LoadMREA(rInput, this); break; + case eDependencyGroup: mpResource = CDependencyGroupLoader::LoadDGRP(rInput, this);break; case eDynamicCollision: mpResource = CCollisionLoader::LoadDCLN(rInput, this); break; case eFont: mpResource = CFontLoader::LoadFONT(rInput, this); break; case eModel: mpResource = CModelLoader::LoadCMDL(rInput, this); break; @@ -185,7 +287,7 @@ void CResourceEntry::Move(const TWideString& rkDir, const TWideString& rkName) // Set new directory and name bool HasDirectory = mpDirectory != nullptr; - CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, mTransient, true); + CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, IsTransient(), true); if (pNewDir != mpDirectory) { @@ -216,9 +318,9 @@ void CResourceEntry::Move(const TWideString& rkDir, const TWideString& rkName) void CResourceEntry::AddToProject(const TWideString& rkDir, const TWideString& rkName) { - if (mTransient) + if (mFlags.HasFlag(eREF_Transient)) { - mTransient = false; + mFlags.ClearFlag(eREF_Transient); Move(rkDir, rkName); } @@ -230,10 +332,10 @@ void CResourceEntry::AddToProject(const TWideString& rkDir, const TWideString& r void CResourceEntry::RemoveFromProject() { - if (!mTransient) + if (!mFlags.HasFlag(eREF_Transient)) { TString Dir = CookedAssetPath().GetFileDirectory(); - mTransient = true; + mFlags.SetFlag(eREF_Transient); Move(Dir, mName); } diff --git a/src/Core/GameProject/CResourceEntry.h b/src/Core/GameProject/CResourceEntry.h index cb67c78c..5dc2cceb 100644 --- a/src/Core/GameProject/CResourceEntry.h +++ b/src/Core/GameProject/CResourceEntry.h @@ -4,31 +4,49 @@ #include "CVirtualDirectory.h" #include "Core/Resource/EResType.h" #include +#include #include class CResource; class CResourceStore; +class CDependencyTree; + +enum EResEntryFlag +{ + eREF_NeedsRecook = 0x1, + eREF_Transient = 0x2, + eREF_HasThumbnail = 0x4, + // Flags that save to the cache file + eREF_SavedFlags = eREF_NeedsRecook | eREF_HasThumbnail +}; +DECLARE_FLAGS(EResEntryFlag, FResEntryFlags) class CResourceEntry { - CResourceStore *mpStore; CResource *mpResource; + CResourceStore *mpStore; + CDependencyTree *mpDependencies; CUniqueID mID; EResType mType; EGame mGame; CVirtualDirectory *mpDirectory; TWideString mName; - bool mNeedsRecook; - bool mTransient; + FResEntryFlags mFlags; mutable u64 mCachedSize; - mutable TWideString mCachedUppercaseName; // This is used to speed up case-insensitive sorting. + mutable TWideString mCachedUppercaseName; // This is used to speed up case-insensitive sorting and filtering. public: CResourceEntry(CResourceStore *pStore, const CUniqueID& rkID, const TWideString& rkDir, const TWideString& rkFilename, EResType Type, bool Transient = false); ~CResourceEntry(); + + bool LoadCacheData(); + bool SaveCacheData(); + void UpdateDependencies(); + TWideString CacheDataPath(bool Relative = false) const; + bool HasRawVersion() const; bool HasCookedVersion() const; TString RawAssetPath(bool Relative = false) const; @@ -45,17 +63,18 @@ public: void RemoveFromProject(); // Accessors - void SetDirty() { mNeedsRecook = true; } + void SetDirty() { mFlags.SetFlag(eREF_NeedsRecook); } - inline bool IsLoaded() const { return mpResource != nullptr; } - inline CResource* Resource() const { return mpResource; } - inline CUniqueID ID() const { return mID; } - inline EGame Game() const { return mGame; } - inline CVirtualDirectory* Directory() const { return mpDirectory; } - inline TWideString Name() const { return mName; } - inline TWideString UppercaseName() const { return mCachedUppercaseName; } - inline EResType ResourceType() const { return mType; } - inline bool IsTransient() const { return mTransient; } + inline bool IsLoaded() const { return mpResource != nullptr; } + inline CResource* Resource() const { return mpResource; } + inline CDependencyTree* Dependencies() const { return mpDependencies; } + inline CUniqueID ID() const { return mID; } + inline EGame Game() const { return mGame; } + inline CVirtualDirectory* Directory() const { return mpDirectory; } + inline TWideString Name() const { return mName; } + inline const TWideString& UppercaseName() const { return mCachedUppercaseName; } + inline EResType ResourceType() const { return mType; } + inline bool IsTransient() const { return mFlags.HasFlag(eREF_Transient); } protected: CResource* InternalLoad(IInputStream& rInput); diff --git a/src/Core/GameProject/CResourceIterator.h b/src/Core/GameProject/CResourceIterator.h index 171fd778..4ddb72f8 100644 --- a/src/Core/GameProject/CResourceIterator.h +++ b/src/Core/GameProject/CResourceIterator.h @@ -11,7 +11,7 @@ class CResourceIterator CResourceEntry *mpCurEntry; public: - CResourceIterator(CResourceStore *pStore) + CResourceIterator(CResourceStore *pStore = gpResourceStore) : mpStore(pStore) , mpCurEntry(nullptr) { diff --git a/src/Core/GameProject/CResourceStore.cpp b/src/Core/GameProject/CResourceStore.cpp index bc635ab4..b3dcdc7f 100644 --- a/src/Core/GameProject/CResourceStore.cpp +++ b/src/Core/GameProject/CResourceStore.cpp @@ -69,6 +69,14 @@ void CResourceStore::LoadResourceDatabase(const TString& rkPath) pRes = pRes->NextSiblingElement("Resource"); } } + + // All resources registered - load cache data + for (auto It = mResourceEntries.begin(); It != mResourceEntries.end(); It++) + { + CResourceEntry *pEntry = It->second; + if (!pEntry->IsTransient()) + pEntry->LoadCacheData(); + } } void CResourceStore::SaveResourceDatabase(const TString& rkPath) const @@ -108,10 +116,6 @@ void CResourceStore::SaveResourceDatabase(const TString& rkPath) const XMLElement *pName = Doc.NewElement("FileName"); pName->SetText(*pEntry->Name().ToUTF8()); pRes->LinkEndChild(pName); - - XMLElement *pRecook = Doc.NewElement("NeedsRecook"); - pRecook->SetText(pEntry->NeedsRecook() ? "true" : "false"); - pRes->LinkEndChild(pRecook); } Doc.SaveFile(*rkPath); @@ -231,10 +235,7 @@ CResourceEntry* CResourceStore::RegisterTransientResource(EResType Type, const C { CResourceEntry *pEntry = FindEntry(rkID); - if (pEntry) - Log::Error("Attempted to register transient resource that already exists: " + rkID.ToString() + " / Dir: " + rkDir.ToUTF8() + " / Name: " + rkFileName.ToUTF8()); - - else + if (!pEntry) { pEntry = new CResourceEntry(this, rkID, rkDir, rkFileName, Type, true); mResourceEntries[rkID] = pEntry; diff --git a/src/Core/GameProject/CResourceStore.h b/src/Core/GameProject/CResourceStore.h index 965cf56c..87c3be26 100644 --- a/src/Core/GameProject/CResourceStore.h +++ b/src/Core/GameProject/CResourceStore.h @@ -65,6 +65,8 @@ public: // Accessors inline CGameProject* ActiveProject() const { return mpProj; } inline CVirtualDirectory* RootDirectory() const { return mpProjectRoot; } + inline u32 NumTotalResources() const { return mResourceEntries.size(); } + inline u32 NumLoadedResources() const { return mLoadedResources.size(); } }; extern CResourceStore *gpResourceStore; diff --git a/src/Core/Resource/Area/CGameArea.cpp b/src/Core/Resource/Area/CGameArea.cpp index 37e89d38..66a54bb3 100644 --- a/src/Core/Resource/Area/CGameArea.cpp +++ b/src/Core/Resource/Area/CGameArea.cpp @@ -10,7 +10,7 @@ CGameArea::CGameArea(CResourceEntry *pEntry /*= 0*/) , mTerrainMerged(false) , mOriginalWorldMeshCount(0) , mUsesCompression(false) - , mMaterialSet(nullptr) + , mpMaterialSet(nullptr) , mpGeneratorLayer(nullptr) , mpCollision(nullptr) { @@ -31,6 +31,32 @@ CGameArea::~CGameArea() delete mLightLayers[iLyr][iLight]; } +CDependencyTree* CGameArea::BuildDependencyTree() const +{ + // Base dependencies + CAreaDependencyTree *pTree = new CAreaDependencyTree(ResID()); + + for (u32 iMat = 0; iMat < mpMaterialSet->NumMaterials(); iMat++) + { + CMaterial *pMat = mpMaterialSet->MaterialByIndex(iMat); + pTree->AddDependency(pMat->IndTexture()); + + for (u32 iPass = 0; iPass < pMat->PassCount(); iPass++) + pTree->AddDependency(pMat->Pass(iPass)->Texture()); + } + + pTree->AddDependency(mpPoiToWorldMap); + Log::Warning("CGameArea::FindDependencies not handling PATH/PTLA"); + + // Layer dependencies + for (u32 iLayer = 0; iLayer < mScriptLayers.size(); iLayer++) + pTree->AddScriptLayer(mScriptLayers[iLayer]); + + pTree->AddScriptLayer(mpGeneratorLayer); + + return pTree; +} + void CGameArea::AddWorldModel(CModel *pModel) { mWorldModels.push_back(pModel); @@ -52,7 +78,7 @@ void CGameArea::MergeTerrain() for (u32 iSurf = 0; iSurf < SubmeshCount; iSurf++) { SSurface *pSurf = pMdl->GetSurface(iSurf); - CMaterial *pMat = mMaterialSet->MaterialByIndex(pSurf->MaterialID); + CMaterial *pMat = mpMaterialSet->MaterialByIndex(pSurf->MaterialID); bool NewMat = true; for (std::vector::iterator it = mStaticWorldModels.begin(); it != mStaticWorldModels.end(); it++) @@ -93,7 +119,7 @@ void CGameArea::ClearTerrain() delete mStaticWorldModels[iStatic]; mStaticWorldModels.clear(); - if (mMaterialSet) delete mMaterialSet; + if (mpMaterialSet) delete mpMaterialSet; mVertexCount = 0; mTriangleCount = 0; diff --git a/src/Core/Resource/Area/CGameArea.h b/src/Core/Resource/Area/CGameArea.h index ddb31453..7db83507 100644 --- a/src/Core/Resource/Area/CGameArea.h +++ b/src/Core/Resource/Area/CGameArea.h @@ -45,7 +45,7 @@ class CGameArea : public CResource std::vector mSectionNumbers; // Geometry - CMaterialSet *mMaterialSet; + CMaterialSet *mpMaterialSet; std::vector mWorldModels; // TerrainModels is the original version of each model; this is currently mainly used in the POI map editor std::vector mStaticWorldModels; // StaticTerrainModels is the merged terrain for faster rendering in the world editor // Script @@ -62,6 +62,7 @@ class CGameArea : public CResource public: CGameArea(CResourceEntry *pEntry = 0); ~CGameArea(); + CDependencyTree* BuildDependencyTree() const; void AddWorldModel(CModel *pModel); void MergeTerrain(); diff --git a/src/Core/Resource/CAnimationParameters.h b/src/Core/Resource/CAnimationParameters.h index 202290ce..daa88aaa 100644 --- a/src/Core/Resource/CAnimationParameters.h +++ b/src/Core/Resource/CAnimationParameters.h @@ -28,6 +28,7 @@ public: // Accessors inline EGame Version() const { return mGame; } + inline CUniqueID ID() const { return mCharacter.ID(); } inline CAnimSet* AnimSet() const { return (CAnimSet*) mCharacter.Load(); } inline u32 CharacterIndex() const { return mCharIndex; } inline u32 AnimIndex() const { return mAnimIndex; } diff --git a/src/Core/Resource/CDependencyGroup.h b/src/Core/Resource/CDependencyGroup.h new file mode 100644 index 00000000..5a646dbf --- /dev/null +++ b/src/Core/Resource/CDependencyGroup.h @@ -0,0 +1,33 @@ +#ifndef CDEPENDENCYGROUP +#define CDEPENDENCYGROUP + +#include "CResource.h" + +class CDependencyGroup : public CResource +{ + DECLARE_RESOURCE_TYPE(eDependencyGroup) + std::set mDependencies; + +public: + CDependencyGroup(CResourceEntry *pEntry = 0) : CResource(pEntry) {} + inline void AddDependency(const CUniqueID& rkID) { mDependencies.insert(rkID); } + inline void AddDependency(CResource *pRes) { if (pRes) mDependencies.insert(pRes->ResID()); } + inline void RemoveDependency(const CUniqueID& rkID) { mDependencies.erase(rkID); } + inline void Clear() { mDependencies.clear(); } + inline bool HasDependency(const CUniqueID& rkID) const { return mDependencies.find(rkID) != mDependencies.end(); } + inline u32 NumDependencies() const { return mDependencies.size(); } + inline CUniqueID DependencyByIndex(u32 Index) const { return *std::next(mDependencies.begin(), Index); } + + CDependencyTree* BuildDependencyTree() const + { + CDependencyTree *pTree = new CDependencyTree(ResID()); + + for (auto DepIt = mDependencies.begin(); DepIt != mDependencies.end(); DepIt++) + pTree->AddDependency(*DepIt); + + return pTree; + } +}; + +#endif // CDEPENDENCYGROUP + diff --git a/src/Core/Resource/CFont.cpp b/src/Core/Resource/CFont.cpp index 6add9989..7372fe92 100644 --- a/src/Core/Resource/CFont.cpp +++ b/src/Core/Resource/CFont.cpp @@ -22,6 +22,13 @@ inline float PtsToFloat(s32 Pt) return 0.00208333f * Pt; } +CDependencyTree* CFont::BuildDependencyTree() const +{ + CDependencyTree *pOut = new CDependencyTree(ResID()); + pOut->AddDependency(mpFontTexture); + return pOut; +} + CVector2f CFont::RenderString(const TString& rkString, CRenderer* /*pRenderer*/, float /*AspectRatio*/, CVector2f /*Position*/, CColor FillColor, CColor StrokeColor, u32 FontSize) { diff --git a/src/Core/Resource/CFont.h b/src/Core/Resource/CFont.h index 487b7605..d9060b5d 100644 --- a/src/Core/Resource/CFont.h +++ b/src/Core/Resource/CFont.h @@ -60,6 +60,7 @@ class CFont : public CResource public: CFont(CResourceEntry *pEntry = 0); ~CFont(); + CDependencyTree* BuildDependencyTree() const; CVector2f RenderString(const TString& rkString, CRenderer *pRenderer, float AspectRatio, CVector2f Position = CVector2f(0,0), CColor FillColor = CColor::skWhite, CColor StrokeColor = CColor::skBlack, diff --git a/src/Core/Resource/CResource.h b/src/Core/Resource/CResource.h index f5ac180d..78e7a254 100644 --- a/src/Core/Resource/CResource.h +++ b/src/Core/Resource/CResource.h @@ -2,6 +2,7 @@ #define CRESOURCE_H #include "EResType.h" +#include "Core/GameProject/CDependencyTree.h" #include "Core/GameProject/CResourceEntry.h" #include "Core/GameProject/CResourceStore.h" #include @@ -39,7 +40,8 @@ public: } virtual ~CResource() {} - + virtual CDependencyTree* BuildDependencyTree() const { return new CDependencyTree(ResID()); } + inline CResourceEntry* Entry() const { return mpEntry; } inline TString Source() const { return mpEntry ? mpEntry->CookedAssetPath(true).GetFileName() : ""; } inline TString FullSource() const { return mpEntry ? mpEntry->CookedAssetPath(true) : ""; } diff --git a/src/Core/Resource/CScan.h b/src/Core/Resource/CScan.h index 3c070910..391b9ab3 100644 --- a/src/Core/Resource/CScan.h +++ b/src/Core/Resource/CScan.h @@ -40,6 +40,17 @@ public: , mCategory(eNone) {} + CDependencyTree* BuildDependencyTree() const + { + if (Game() >= eEchoesDemo) + Log::Warning("CScan::BuildDependencyTree not handling Echoes/Corruption dependencies"); + + CDependencyTree *pTree = new CDependencyTree(ResID()); + pTree->AddDependency(mpFrame); + pTree->AddDependency(mpStringTable); + return pTree; + } + EGame Version() const { return mVersion; } CStringTable* ScanText() const { return mpStringTable; } bool IsImportant() const { return mIsImportant; } diff --git a/src/Core/Resource/CWorld.cpp b/src/Core/Resource/CWorld.cpp index a2e64efe..66a32aa5 100644 --- a/src/Core/Resource/CWorld.cpp +++ b/src/Core/Resource/CWorld.cpp @@ -17,6 +17,28 @@ CWorld::~CWorld() { } +CDependencyTree* CWorld::BuildDependencyTree() const +{ + CDependencyTree *pTree = new CDependencyTree(ResID()); + + for (u32 iArea = 0; iArea < mAreas.size(); iArea++) + { + pTree->AddDependency(mAreas[iArea].FileID); + pTree->AddDependency(mAreas[iArea].pAreaName); + } + + pTree->AddDependency(mpWorldName); + pTree->AddDependency(mpDarkWorldName); + pTree->AddDependency(mpSaveWorld); + pTree->AddDependency(mpDefaultSkybox); + pTree->AddDependency(mpMapWorld); + + if (Game() <= ePrime) + Log::Warning("CWorld::BuildDependencyTree not handling audio groups"); + + return pTree; +} + void CWorld::SetAreaLayerInfo(CGameArea *pArea) { // The AreaIndex parameter is a placeholder until an improved world loader is implemented. diff --git a/src/Core/Resource/CWorld.h b/src/Core/Resource/CWorld.h index 4590e032..57dcfb09 100644 --- a/src/Core/Resource/CWorld.h +++ b/src/Core/Resource/CWorld.h @@ -84,6 +84,7 @@ public: CWorld(CResourceEntry *pEntry = 0); ~CWorld(); + CDependencyTree* BuildDependencyTree() const; void SetAreaLayerInfo(CGameArea *pArea); // Accessors diff --git a/src/Core/Resource/Factory/CAreaLoader.cpp b/src/Core/Resource/Factory/CAreaLoader.cpp index baed7098..e9e8fe61 100644 --- a/src/Core/Resource/Factory/CAreaLoader.cpp +++ b/src/Core/Resource/Factory/CAreaLoader.cpp @@ -73,7 +73,7 @@ void CAreaLoader::ReadGeometryPrime() mpSectionMgr->ToSection(mGeometryBlockNum); // Materials - mpArea->mMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion); + mpArea->mpMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion); mpSectionMgr->ToNextSection(); // Geometry @@ -81,7 +81,7 @@ void CAreaLoader::ReadGeometryPrime() for (u32 iMesh = 0; iMesh < mNumMeshes; iMesh++) { - CModel *pModel = CModelLoader::LoadWorldModel(*mpMREA, *mpSectionMgr, *mpArea->mMaterialSet, mVersion); + CModel *pModel = CModelLoader::LoadWorldModel(*mpMREA, *mpSectionMgr, *mpArea->mpMaterialSet, mVersion); FileModels.push_back(pModel); if (mVersion <= ePrime) @@ -394,7 +394,7 @@ void CAreaLoader::ReadGeometryCorruption() mpSectionMgr->ToSection(mGeometryBlockNum); // Materials - mpArea->mMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion); + mpArea->mpMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion); mpSectionMgr->ToNextSection(); // Geometry @@ -404,7 +404,7 @@ void CAreaLoader::ReadGeometryCorruption() for (u32 iMesh = 0; iMesh < mNumMeshes; iMesh++) { - CModel *pWorldModel = CModelLoader::LoadCorruptionWorldModel(*mpMREA, *mpSectionMgr, *mpArea->mMaterialSet, CurWOBJSection, CurGPUSection, mVersion); + CModel *pWorldModel = CModelLoader::LoadCorruptionWorldModel(*mpMREA, *mpSectionMgr, *mpArea->mpMaterialSet, CurWOBJSection, CurGPUSection, mVersion); FileModels.push_back(pWorldModel); CurWOBJSection += 4; diff --git a/src/Core/Resource/Factory/CDependencyGroupLoader.cpp b/src/Core/Resource/Factory/CDependencyGroupLoader.cpp new file mode 100644 index 00000000..c94afa4e --- /dev/null +++ b/src/Core/Resource/Factory/CDependencyGroupLoader.cpp @@ -0,0 +1,48 @@ +#include "CDependencyGroupLoader.h" +#include + +EGame CDependencyGroupLoader::VersionTest(IInputStream& rDGRP, u32 DepCount) +{ + // Only difference between versions is asset ID length. Just check for EOF with 32-bit ID length. + u32 Start = rDGRP.Tell(); + rDGRP.Seek(DepCount * 4, SEEK_CUR); + u32 Remaining = rDGRP.Size() - Start; + + EGame Game = ePrimeDemo; + + if (Remaining < 32) + { + for (u32 iRem = 0; iRem < Remaining; iRem++) + { + if (rDGRP.ReadByte() != 0xFF) + { + Game = eCorruptionProto; + break; + } + } + } + + rDGRP.Seek(Start, SEEK_SET); + return Game; +} + +CDependencyGroup* CDependencyGroupLoader::LoadDGRP(IInputStream& rDGRP, CResourceEntry *pEntry) +{ + if (!rDGRP.IsValid()) return nullptr; + + u32 NumDependencies = rDGRP.ReadLong(); + EGame Game = VersionTest(rDGRP, NumDependencies); + EUIDLength IDLength = (Game < eCorruptionProto ? e32Bit : e64Bit); + + CDependencyGroup *pGroup = new CDependencyGroup(pEntry); + pGroup->SetGame(Game); + + for (u32 iDep = 0; iDep < NumDependencies; iDep++) + { + rDGRP.Seek(0x4, SEEK_CUR); // Skip dependency type + CUniqueID AssetID(rDGRP, IDLength); + pGroup->AddDependency(AssetID); + } + + return pGroup; +} diff --git a/src/Core/Resource/Factory/CDependencyGroupLoader.h b/src/Core/Resource/Factory/CDependencyGroupLoader.h new file mode 100644 index 00000000..f806b55c --- /dev/null +++ b/src/Core/Resource/Factory/CDependencyGroupLoader.h @@ -0,0 +1,16 @@ +#ifndef CDEPENDENCYGROUPLOADER_H +#define CDEPENDENCYGROUPLOADER_H + +#include "Core/Resource/CDependencyGroup.h" +#include "Core/Resource/EGame.h" + +class CDependencyGroupLoader +{ + CDependencyGroupLoader() {} + static EGame VersionTest(IInputStream& rDGRP, u32 DepCount); + +public: + static CDependencyGroup* LoadDGRP(IInputStream& rDGRP, CResourceEntry *pEntry); +}; + +#endif // CDEPENDENCYGROUPLOADER_H diff --git a/src/Core/Resource/Factory/CMaterialLoader.cpp b/src/Core/Resource/Factory/CMaterialLoader.cpp index f54e4148..eb940a53 100644 --- a/src/Core/Resource/Factory/CMaterialLoader.cpp +++ b/src/Core/Resource/Factory/CMaterialLoader.cpp @@ -65,7 +65,7 @@ void CMaterialLoader::ReadPrimeMatSet() { mpSet->mMaterials[iMat] = ReadPrimeMaterial(); mpSet->mMaterials[iMat]->mVersion = mVersion; - mpSet->mMaterials[iMat]->mName = TString("Material #") + std::to_string(iMat + 1); + mpSet->mMaterials[iMat]->mName = TString("Material #") + TString::FromInt32(iMat + 1, 0, 10); mpFile->Seek(MatsStart + Offsets[iMat], SEEK_SET); } } @@ -261,7 +261,7 @@ void CMaterialLoader::ReadCorruptionMatSet() u32 Next = mpFile->Tell() + Size; mpSet->mMaterials[iMat] = ReadCorruptionMaterial(); mpSet->mMaterials[iMat]->mVersion = mVersion; - mpSet->mMaterials[iMat]->mName = TString("Material #") + std::to_string(iMat + 1); + mpSet->mMaterials[iMat]->mName = TString("Material #") + TString::FromInt32(iMat + 1, 0, 10); mpFile->Seek(Next, SEEK_SET); } } diff --git a/src/Core/Resource/Model/CModel.cpp b/src/Core/Resource/Model/CModel.cpp index 11a0d54e..a8d29533 100644 --- a/src/Core/Resource/Model/CModel.cpp +++ b/src/Core/Resource/Model/CModel.cpp @@ -29,6 +29,31 @@ CModel::~CModel() delete mMaterialSets[iMat]; } + +CDependencyTree* CModel::BuildDependencyTree() const +{ + CDependencyTree *pTree = new CDependencyTree(ResID()); + + for (u32 iSet = 0; iSet < mMaterialSets.size(); iSet++) + { + CMaterialSet *pSet = mMaterialSets[iSet]; + + for (u32 iMat = 0; iMat < pSet->NumMaterials(); iMat++) + { + CMaterial *pMat = pSet->MaterialByIndex(iMat); + pTree->AddDependency(pMat->IndTexture()); + + for (u32 iPass = 0; iPass < pMat->PassCount(); iPass++) + { + CMaterialPass *pPass = pMat->Pass(iPass); + pTree->AddDependency(pPass->Texture()); + } + } + } + + return pTree; +} + void CModel::BufferGL() { if (!mBuffered) diff --git a/src/Core/Resource/Model/CModel.h b/src/Core/Resource/Model/CModel.h index fe171284..fae4a933 100644 --- a/src/Core/Resource/Model/CModel.h +++ b/src/Core/Resource/Model/CModel.h @@ -25,6 +25,7 @@ public: CModel(CMaterialSet *pSet, bool OwnsMatSet); ~CModel(); + CDependencyTree* BuildDependencyTree() const; void BufferGL(); void GenerateMaterialShaders(); void ClearGLBuffer(); diff --git a/src/Core/Resource/Script/CScriptObject.cpp b/src/Core/Resource/Script/CScriptObject.cpp index eeb86899..02f3bf9b 100644 --- a/src/Core/Resource/Script/CScriptObject.cpp +++ b/src/Core/Resource/Script/CScriptObject.cpp @@ -63,6 +63,7 @@ bool CScriptObject::IsEditorProperty(IProperty *pProp) (pProp == mpRotation) || (pProp == mpScale) || (pProp == mpActive) || + (pProp == mpLightParameters) || (pProp->Parent() == mpLightParameters) ); }