You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This change consists of multiple changes: Core: - Deprecation of ANY_PACKAGE macro. Added ANY_PACKAGE_DEPRECATED macro which can still be used for backwards compatibility purposes (only used in CoreUObject) - Deprecation of StaticFindObjectFast* functions that take bAnyPackage parameter - Added UStruct::GetStructPathName function that returns FTopLevelAssetPath representing the path name (package + object FName, super quick compared to UObject::GetPathName) + wrapper UClass::GetClassPathName to make it look better when used with UClasses - Added (Static)FindFirstObject* functions that find a first object given its Name (no Outer). These functions are used in places I consider valid to do global UObject (UClass) lookups like parsing command line parameters / checking for unique object names - Added static UClass::TryFindType function which serves a similar purpose as FindFirstObject however it's going to throw a warning (with a callstack / maybe ensure in the future?) if short class name is provided. This function is used in places that used to use short class names but now should have been converted to use path names to catch any potential regressions and or edge cases I missed. - Added static UClass::TryConvertShortNameToPathName utility function - Added static UClass::TryFixShortClassNameExportPath utility function - Object text export paths will now also include class path (Texture2D'/Game/Textures/Grass.Grass' -> /Script/Engine.Texture2D'/Game/Textures/Grass.Grass') - All places that manually generated object export paths for objects will now use FObjectPropertyBase::GetExportPath - Added a new startup test that checks for short type names in UClass/FProperty MetaData values AssetRegistry: - Deprecated any member variables (FAssetData / FARFilter) or functions that use FNames to represent class names and replaced them with FTopLevelAssetPath - Added new member variables and new function overloads that use FTopLevelAssetPath to represent class names - This also applies to a few other modules' APIs to match AssetRegistry changes Everything else: - Updated code that used ANY_PACKAGE (depending on the use case) to use FindObject(nullptr, PathToObject), UClass::TryFindType (used when path name is expected, warns if it's a short name) or FindFirstObject (usually for finding types based on user input but there's been a few legitimate use cases not related to user input) - Updated code that used AssetRegistry API to use FTopLevelAssetPaths and USomeClass::StaticClass()->GetClassPathName() instead of GetFName() - Updated meta data and hardcoded FindObject(ANY_PACKAGE, "EEnumNameOrClassName") calls to use path names #jira UE-99463 #rb many.people [FYI] Marcus.Wassmer #preflight 629248ec2256738f75de9b32 #codereviewnumbers 20320742, 20320791, 20320799, 20320756, 20320809, 20320830, 20320840, 20320846, 20320851, 20320863, 20320780, 20320765, 20320876, 20320786 #ROBOMERGE-OWNER: robert.manuszewski #ROBOMERGE-AUTHOR: robert.manuszewski #ROBOMERGE-SOURCE: CL 20430220 via CL 20433854 via CL 20435474 via CL 20435484 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246) [CL 20448496 by robert manuszewski in ue5-main branch]
2483 lines
77 KiB
C++
2483 lines
77 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AssetRegistry/AssetRegistryState.h"
|
|
|
|
#include "Algo/Sort.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/PathViews.h"
|
|
#include "Serialization/ArrayReader.h"
|
|
#include "Serialization/LargeMemoryReader.h"
|
|
#include "Serialization/MemoryWriter.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "UObject/MetaData.h"
|
|
#include "AssetRegistryArchive.h"
|
|
#include "AssetRegistryImpl.h"
|
|
#include "AssetRegistryPrivate.h"
|
|
#include "AssetRegistry/ARFilter.h"
|
|
#include "DependsNode.h"
|
|
#include "PackageReader.h"
|
|
#include "NameTableArchive.h"
|
|
#include "GenericPlatform/GenericPlatformChunkInstall.h"
|
|
#include "Blueprint/BlueprintSupport.h"
|
|
#include "UObject/NameBatchSerialization.h"
|
|
#include "UObject/PrimaryAssetId.h"
|
|
|
|
|
|
FAssetRegistryState& FAssetRegistryState::operator=(FAssetRegistryState&& Rhs)
|
|
{
|
|
Reset();
|
|
|
|
CachedAssetsByObjectPath = MoveTemp(Rhs.CachedAssetsByObjectPath);
|
|
CachedAssetsByPackageName = MoveTemp(Rhs.CachedAssetsByPackageName);
|
|
CachedAssetsByPath = MoveTemp(Rhs.CachedAssetsByPath);
|
|
CachedAssetsByClass = MoveTemp(Rhs.CachedAssetsByClass);
|
|
CachedAssetsByTag = MoveTemp(Rhs.CachedAssetsByTag);
|
|
CachedDependsNodes = MoveTemp(Rhs.CachedDependsNodes);
|
|
CachedPackageData = MoveTemp(Rhs.CachedPackageData);
|
|
PreallocatedAssetDataBuffers = MoveTemp(Rhs.PreallocatedAssetDataBuffers);
|
|
PreallocatedDependsNodeDataBuffers = MoveTemp(Rhs.PreallocatedDependsNodeDataBuffers);
|
|
PreallocatedPackageDataBuffers = MoveTemp(Rhs.PreallocatedPackageDataBuffers);
|
|
Swap(NumAssets, Rhs.NumAssets);
|
|
Swap(NumDependsNodes, Rhs.NumDependsNodes);
|
|
Swap(NumPackageData, Rhs.NumPackageData);
|
|
|
|
return *this;
|
|
}
|
|
|
|
FAssetRegistryState::~FAssetRegistryState()
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
void FAssetRegistryState::Reset()
|
|
{
|
|
// if we have preallocated all the FAssetData's in a single block, free it now, instead of one at a time
|
|
if (PreallocatedAssetDataBuffers.Num())
|
|
{
|
|
for (FAssetData* Buffer : PreallocatedAssetDataBuffers)
|
|
{
|
|
delete[] Buffer;
|
|
}
|
|
PreallocatedAssetDataBuffers.Reset();
|
|
|
|
NumAssets = 0;
|
|
}
|
|
else
|
|
{
|
|
// Delete all assets in the cache
|
|
for (TMap<FName, FAssetData*>::TConstIterator AssetDataIt(CachedAssetsByObjectPath); AssetDataIt; ++AssetDataIt)
|
|
{
|
|
if (AssetDataIt.Value())
|
|
{
|
|
delete AssetDataIt.Value();
|
|
NumAssets--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure we have deleted all our allocated FAssetData objects
|
|
// Mar-06: Temporarily remove this ensure to allow passing builds while we find and fix the cause
|
|
// TODO: Restore the ensure
|
|
// ensure(NumAssets == 0);
|
|
UE_CLOG(NumAssets != 0, LogAssetRegistry, Display,
|
|
TEXT("AssetRegistryState::Reset: NumAssets does not match the number of CachedAssetsByObjectPaths entries. Leaking some allocations."));
|
|
|
|
if (PreallocatedDependsNodeDataBuffers.Num())
|
|
{
|
|
for (FDependsNode* Buffer : PreallocatedDependsNodeDataBuffers)
|
|
{
|
|
delete[] Buffer;
|
|
}
|
|
PreallocatedDependsNodeDataBuffers.Reset();
|
|
NumDependsNodes = 0;
|
|
}
|
|
else
|
|
{
|
|
// Delete all depends nodes in the cache
|
|
for (TMap<FAssetIdentifier, FDependsNode*>::TConstIterator DependsIt(CachedDependsNodes); DependsIt; ++DependsIt)
|
|
{
|
|
if (DependsIt.Value())
|
|
{
|
|
delete DependsIt.Value();
|
|
NumDependsNodes--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure we have deleted all our allocated FDependsNode objects
|
|
ensure(NumDependsNodes == 0);
|
|
|
|
if (PreallocatedPackageDataBuffers.Num())
|
|
{
|
|
for (FAssetPackageData* Buffer : PreallocatedPackageDataBuffers)
|
|
{
|
|
delete[] Buffer;
|
|
}
|
|
PreallocatedPackageDataBuffers.Reset();
|
|
NumPackageData = 0;
|
|
}
|
|
else
|
|
{
|
|
// Delete all depends nodes in the cache
|
|
for (TMap<FName, FAssetPackageData*>::TConstIterator PackageDataIt(CachedPackageData); PackageDataIt; ++PackageDataIt)
|
|
{
|
|
if (PackageDataIt.Value())
|
|
{
|
|
delete PackageDataIt.Value();
|
|
NumPackageData--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure we have deleted all our allocated package data objects
|
|
ensure(NumPackageData == 0);
|
|
|
|
// Clear cache
|
|
CachedAssetsByObjectPath.Empty();
|
|
CachedAssetsByPackageName.Empty();
|
|
CachedAssetsByPath.Empty();
|
|
CachedAssetsByClass.Empty();
|
|
CachedAssetsByTag.Empty();
|
|
CachedDependsNodes.Empty();
|
|
CachedPackageData.Empty();
|
|
}
|
|
|
|
void FAssetRegistryState::FilterTags(const FAssetDataTagMapSharedView& InTagsAndValues, FAssetDataTagMap& OutTagsAndValues, const TSet<FName>* ClassSpecificFilterList, const FAssetRegistrySerializationOptions& Options)
|
|
{
|
|
const TSet<FName>* AllClassesFilterList = Options.CookFilterlistTagsByClass.Find(UE::AssetRegistry::WildcardPathName);
|
|
|
|
// Exclude denied tags or include only allowed tags, based on how we were configured in ini
|
|
for (const auto& TagPair : InTagsAndValues)
|
|
{
|
|
const bool bInAllClassesList = AllClassesFilterList && (AllClassesFilterList->Contains(TagPair.Key) || AllClassesFilterList->Contains(UE::AssetRegistry::WildcardFName));
|
|
const bool bInClassSpecificList = ClassSpecificFilterList && (ClassSpecificFilterList->Contains(TagPair.Key) || ClassSpecificFilterList->Contains(UE::AssetRegistry::WildcardFName));
|
|
if (Options.bUseAssetRegistryTagsAllowListInsteadOfDenyList)
|
|
{
|
|
// It's an allow list, only include it if it is in the all classes list or in the class specific list
|
|
if (bInAllClassesList || bInClassSpecificList)
|
|
{
|
|
// It is in the allow list. Keep it.
|
|
OutTagsAndValues.Add(TagPair.Key, TagPair.Value.ToLoose());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// It's a deny list, include it unless it is in the all classes list or in the class specific list
|
|
if (!bInAllClassesList && !bInClassSpecificList)
|
|
{
|
|
// It isn't in the deny list. Keep it.
|
|
OutTagsAndValues.Add(TagPair.Key, TagPair.Value.ToLoose());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAssetRegistryState::InitializeFromExistingAndPrune(const FAssetRegistryState & ExistingState, const TSet<FName>& RequiredPackages, const TSet<FName>& RemovePackages,
|
|
const TSet<int32> ChunksToKeep, const FAssetRegistrySerializationOptions& Options)
|
|
{
|
|
LLM_SCOPE(ELLMTag::AssetRegistry);
|
|
const bool bIsFilteredByChunkId = ChunksToKeep.Num() != 0;
|
|
const bool bIsFilteredByRequiredPackages = RequiredPackages.Num() != 0;
|
|
const bool bIsFilteredByRemovedPackages = RemovePackages.Num() != 0;
|
|
|
|
TSet<FName> RequiredDependNodePackages;
|
|
|
|
// Duplicate asset data entries
|
|
for (const TPair<FName, FAssetData*>& AssetPair : ExistingState.CachedAssetsByObjectPath)
|
|
{
|
|
const FAssetData* AssetData = AssetPair.Value;
|
|
|
|
bool bRemoveAssetData = false;
|
|
bool bRemoveDependencyData = true;
|
|
|
|
if (bIsFilteredByChunkId &&
|
|
!AssetData->ChunkIDs.ContainsByPredicate([&](int32 ChunkId) { return ChunksToKeep.Contains(ChunkId); }))
|
|
{
|
|
bRemoveAssetData = true;
|
|
}
|
|
else if (bIsFilteredByRequiredPackages && !RequiredPackages.Contains(AssetData->PackageName))
|
|
{
|
|
bRemoveAssetData = true;
|
|
}
|
|
else if (bIsFilteredByRemovedPackages && RemovePackages.Contains(AssetData->PackageName))
|
|
{
|
|
bRemoveAssetData = true;
|
|
}
|
|
else if (Options.bFilterAssetDataWithNoTags && AssetData->TagsAndValues.Num() == 0 &&
|
|
!FPackageName::IsLocalizedPackage(AssetData->PackageName.ToString()))
|
|
{
|
|
bRemoveAssetData = true;
|
|
bRemoveDependencyData = Options.bFilterDependenciesWithNoTags;
|
|
}
|
|
|
|
if (bRemoveAssetData)
|
|
{
|
|
if (!bRemoveDependencyData)
|
|
{
|
|
RequiredDependNodePackages.Add(AssetData->PackageName);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
FAssetDataTagMap NewTagsAndValues;
|
|
FAssetRegistryState::FilterTags(AssetData->TagsAndValues, NewTagsAndValues, Options.CookFilterlistTagsByClass.Find(AssetData->AssetClassPath), Options);
|
|
|
|
FAssetData* NewAssetData = new FAssetData(AssetData->PackageName, AssetData->PackagePath, AssetData->AssetName,
|
|
AssetData->AssetClassPath, NewTagsAndValues, AssetData->ChunkIDs, AssetData->PackageFlags);
|
|
|
|
NewAssetData->TaggedAssetBundles = AssetData->TaggedAssetBundles;
|
|
|
|
// Add asset to new state
|
|
AddAssetData(NewAssetData);
|
|
}
|
|
|
|
// Create package data for all script and required packages
|
|
for (const TPair<FName, FAssetPackageData*>& Pair : ExistingState.CachedPackageData)
|
|
{
|
|
if (Pair.Value)
|
|
{
|
|
// Only add if also in asset data map, or script package
|
|
if (CachedAssetsByPackageName.Find(Pair.Key) ||
|
|
FPackageName::IsScriptPackage(Pair.Key.ToString()))
|
|
{
|
|
FAssetPackageData* NewData = CreateOrGetAssetPackageData(Pair.Key);
|
|
*NewData = *Pair.Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find valid dependency nodes for all script and required packages
|
|
TSet<FDependsNode*> ValidDependsNodes;
|
|
ValidDependsNodes.Reserve(ExistingState.CachedDependsNodes.Num());
|
|
for (const TPair<FAssetIdentifier, FDependsNode*>& Pair : ExistingState.CachedDependsNodes)
|
|
{
|
|
FDependsNode* Node = Pair.Value;
|
|
const FAssetIdentifier& Id = Node->GetIdentifier();
|
|
bool bRemoveDependsNode = false;
|
|
|
|
if (Options.bFilterSearchableNames && Id.IsValue())
|
|
{
|
|
bRemoveDependsNode = true;
|
|
}
|
|
else if (Id.IsPackage() &&
|
|
!CachedAssetsByPackageName.Contains(Id.PackageName) &&
|
|
!RequiredDependNodePackages.Contains(Id.PackageName) &&
|
|
!FPackageName::IsScriptPackage(Id.PackageName.ToString()))
|
|
{
|
|
bRemoveDependsNode = true;
|
|
}
|
|
|
|
if (!bRemoveDependsNode)
|
|
{
|
|
ValidDependsNodes.Add(Node);
|
|
}
|
|
}
|
|
|
|
// Duplicate dependency nodes
|
|
for (FDependsNode* OldNode : ValidDependsNodes)
|
|
{
|
|
FDependsNode* NewNode = CreateOrFindDependsNode(OldNode->GetIdentifier());
|
|
NewNode->Reserve(OldNode);
|
|
}
|
|
|
|
for (FDependsNode* OldNode : ValidDependsNodes)
|
|
{
|
|
FDependsNode* NewNode = CreateOrFindDependsNode(OldNode->GetIdentifier());
|
|
OldNode->IterateOverDependencies([&, OldNode, NewNode](FDependsNode* InDependency, UE::AssetRegistry::EDependencyCategory InCategory, UE::AssetRegistry::EDependencyProperty InFlags, bool bDuplicate) {
|
|
if (ValidDependsNodes.Contains(InDependency))
|
|
{
|
|
// Only add link if it's part of the filtered asset set
|
|
FDependsNode* NewDependency = CreateOrFindDependsNode(InDependency->GetIdentifier());
|
|
NewNode->SetIsDependencyListSorted(InCategory, false);
|
|
NewNode->AddDependency(NewDependency, InCategory, InFlags);
|
|
NewDependency->SetIsReferencersSorted(false);
|
|
NewDependency->AddReferencer(NewNode);
|
|
}
|
|
});
|
|
NewNode->SetIsDependenciesInitialized(true);
|
|
}
|
|
|
|
// Remove any orphaned depends nodes. This will leave cycles in but those might represent useful data
|
|
TArray<FDependsNode*> AllDependsNodes;
|
|
CachedDependsNodes.GenerateValueArray(AllDependsNodes);
|
|
for (FDependsNode* DependsNode : AllDependsNodes)
|
|
{
|
|
if (DependsNode->GetConnectionCount() == 0)
|
|
{
|
|
RemoveDependsNode(DependsNode->GetIdentifier());
|
|
}
|
|
}
|
|
|
|
// Restore the sortedness that we turned off for performance when creating each DependsNode
|
|
for (TPair<FAssetIdentifier, FDependsNode*> Pair : CachedDependsNodes)
|
|
{
|
|
FDependsNode* DependsNode = Pair.Value;
|
|
DependsNode->SetIsDependencyListSorted(UE::AssetRegistry::EDependencyCategory::All, true);
|
|
DependsNode->SetIsReferencersSorted(true);
|
|
}
|
|
}
|
|
|
|
void FAssetRegistryState::InitializeFromExisting(const TMap<FName, FAssetData*>& AssetDataMap, const TMap<FAssetIdentifier, FDependsNode*>& DependsNodeMap,
|
|
const TMap<FName, FAssetPackageData*>& AssetPackageDataMap, const FAssetRegistrySerializationOptions& Options, EInitializationMode InInitializationMode)
|
|
{
|
|
LLM_SCOPE(ELLMTag::AssetRegistry);
|
|
if (InInitializationMode == EInitializationMode::Rebuild)
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
for (const TPair<FName, FAssetData*>& Pair : AssetDataMap)
|
|
{
|
|
if (Pair.Value == nullptr)
|
|
{
|
|
// don't do anything
|
|
continue;
|
|
}
|
|
|
|
FAssetData* ExistingData = nullptr;
|
|
if (InInitializationMode != EInitializationMode::Rebuild) // minor optimization to avoid lookup in rebuild mode
|
|
{
|
|
ExistingData = CachedAssetsByObjectPath.FindRef(Pair.Key);
|
|
}
|
|
if (InInitializationMode == EInitializationMode::OnlyUpdateExisting && ExistingData == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
if (InInitializationMode == EInitializationMode::OnlyUpdateNew && ExistingData != nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Filter asset registry tags now
|
|
const FAssetData& AssetData = *Pair.Value;
|
|
|
|
FAssetDataTagMap LocalTagsAndValues;
|
|
FAssetRegistryState::FilterTags(AssetData.TagsAndValues, LocalTagsAndValues, Options.CookFilterlistTagsByClass.Find(AssetData.AssetClassPath), Options);
|
|
|
|
if (ExistingData)
|
|
{
|
|
// Bundle tags might have changed even if other tags haven't
|
|
ExistingData->TaggedAssetBundles = AssetData.TaggedAssetBundles;
|
|
|
|
// If tags have changed we need to update CachedAssetsByTag
|
|
if (LocalTagsAndValues != ExistingData->TagsAndValues)
|
|
{
|
|
FAssetData TempData = *ExistingData;
|
|
TempData.TagsAndValues = FAssetDataTagMapSharedView(MoveTemp(LocalTagsAndValues));
|
|
UpdateAssetData(ExistingData, TempData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FAssetData* NewData = new FAssetData(AssetData.PackageName, AssetData.PackagePath, AssetData.AssetName,
|
|
AssetData.AssetClassPath, LocalTagsAndValues, AssetData.ChunkIDs, AssetData.PackageFlags);
|
|
NewData->TaggedAssetBundles = AssetData.TaggedAssetBundles;
|
|
|
|
AddAssetData(NewData);
|
|
}
|
|
}
|
|
|
|
TSet<FAssetIdentifier> ScriptPackages;
|
|
|
|
if (InInitializationMode != EInitializationMode::OnlyUpdateExisting)
|
|
{
|
|
for (const TPair<FName, FAssetPackageData*>& Pair : AssetPackageDataMap)
|
|
{
|
|
bool bIsScriptPackage = FPackageName::IsScriptPackage(Pair.Key.ToString());
|
|
if (InInitializationMode == EInitializationMode::OnlyUpdateNew && CachedPackageData.Find(Pair.Key))
|
|
{
|
|
continue;
|
|
}
|
|
if (Pair.Value)
|
|
{
|
|
// Only add if also in asset data map, or script package
|
|
if (bIsScriptPackage)
|
|
{
|
|
ScriptPackages.Add(Pair.Key);
|
|
|
|
FAssetPackageData* NewData = CreateOrGetAssetPackageData(Pair.Key);
|
|
*NewData = *Pair.Value;
|
|
}
|
|
else if (CachedAssetsByPackageName.Find(Pair.Key))
|
|
{
|
|
FAssetPackageData* NewData = CreateOrGetAssetPackageData(Pair.Key);
|
|
*NewData = *Pair.Value;
|
|
}
|
|
}
|
|
}
|
|
|
|
TMap<FAssetIdentifier, FDependsNode*> FilteredDependsNodeMap;
|
|
const TMap<FAssetIdentifier, FDependsNode*>* DependsNodesToAdd = &DependsNodeMap;
|
|
if (InInitializationMode == EInitializationMode::OnlyUpdateNew)
|
|
{
|
|
// Keep the original DependsNodeMap for reference, but remove from NodesToAdd all nodes that already have dependency data
|
|
// Also reserve up-front all (unfiltered) nodes we are adding, to avoid reallocating the Referencers array.
|
|
FilteredDependsNodeMap.Reserve(DependsNodeMap.Num());
|
|
DependsNodesToAdd = &FilteredDependsNodeMap;
|
|
for (const TPair<FAssetIdentifier, FDependsNode*>& Pair : DependsNodeMap)
|
|
{
|
|
FDependsNode* SourceNode = Pair.Value;
|
|
FDependsNode* TargetNode = CreateOrFindDependsNode(Pair.Key);
|
|
if (!TargetNode->IsDependenciesInitialized())
|
|
{
|
|
FilteredDependsNodeMap.Add(Pair.Key, SourceNode);
|
|
}
|
|
TargetNode->Reserve(SourceNode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Reserve up-front all the nodes that we are adding, so we do not reallocate
|
|
// the Referencers array multiple times on a node as we add nodes that refer to it
|
|
for (const TPair<FAssetIdentifier, FDependsNode*>& Pair : DependsNodeMap)
|
|
{
|
|
FDependsNode* SourceNode = Pair.Value;
|
|
FDependsNode* TargetNode = CreateOrFindDependsNode(Pair.Key);
|
|
TargetNode->Reserve(SourceNode);
|
|
}
|
|
}
|
|
|
|
for (const TPair<FAssetIdentifier, FDependsNode*>& Pair : *DependsNodesToAdd)
|
|
{
|
|
FDependsNode* SourceNode = Pair.Value;
|
|
FDependsNode* TargetNode = CreateOrFindDependsNode(Pair.Key);
|
|
SourceNode->IterateOverDependencies([this, &DependsNodeMap, &ScriptPackages, TargetNode]
|
|
(FDependsNode* InDependency, UE::AssetRegistry::EDependencyCategory InCategory, UE::AssetRegistry::EDependencyProperty InFlags, bool bDuplicate) {
|
|
const FAssetIdentifier& Identifier = InDependency->GetIdentifier();
|
|
if (DependsNodeMap.Find(Identifier) || ScriptPackages.Contains(Identifier))
|
|
{
|
|
// Only add if this node is in the incoming map
|
|
FDependsNode* TargetDependency = CreateOrFindDependsNode(Identifier);
|
|
TargetNode->SetIsDependencyListSorted(InCategory, false);
|
|
TargetNode->AddDependency(TargetDependency, InCategory, InFlags);
|
|
TargetDependency->SetIsReferencersSorted(false);
|
|
TargetDependency->AddReferencer(TargetDependency);
|
|
}
|
|
});
|
|
TargetNode->SetIsDependenciesInitialized(true);
|
|
}
|
|
|
|
// Restore the sortedness that we turned off for performance when creating each DependsNode
|
|
for (TPair<FAssetIdentifier, FDependsNode*> Pair : CachedDependsNodes)
|
|
{
|
|
FDependsNode* DependsNode = Pair.Value;
|
|
DependsNode->SetIsDependencyListSorted(UE::AssetRegistry::EDependencyCategory::All, true);
|
|
DependsNode->SetIsReferencersSorted(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAssetRegistryState::PruneAssetData(const TSet<FName>& RequiredPackages, const TSet<FName>& RemovePackages, const FAssetRegistrySerializationOptions& Options)
|
|
{
|
|
PruneAssetData(RequiredPackages, RemovePackages, TSet<int32>(), Options);
|
|
}
|
|
|
|
void FAssetRegistryState::PruneAssetData(const TSet<FName>& RequiredPackages, const TSet<FName>& RemovePackages, const TSet<int32> ChunksToKeep, const FAssetRegistrySerializationOptions& Options)
|
|
{
|
|
const bool bIsFilteredByChunkId = ChunksToKeep.Num() != 0;
|
|
const bool bIsFilteredByRequiredPackages = RequiredPackages.Num() != 0;
|
|
const bool bIsFilteredByRemovedPackages = RemovePackages.Num() != 0;
|
|
|
|
TSet<FName> RequiredDependNodePackages;
|
|
|
|
// Generate list up front as the maps will get cleaned up
|
|
TArray<FAssetData*> AllAssetData;
|
|
CachedAssetsByObjectPath.GenerateValueArray(AllAssetData);
|
|
TSet<FDependsNode*> RemoveDependsNodes;
|
|
|
|
// Remove assets and mark-for-removal any dependencynodes for assets removed due to having no tags
|
|
for (FAssetData* AssetData : AllAssetData)
|
|
{
|
|
bool bRemoveAssetData = false;
|
|
bool bRemoveDependencyData = true;
|
|
|
|
if (bIsFilteredByChunkId &&
|
|
!AssetData->ChunkIDs.ContainsByPredicate([&](int32 ChunkId) { return ChunksToKeep.Contains(ChunkId); }))
|
|
{
|
|
bRemoveAssetData = true;
|
|
}
|
|
else if (bIsFilteredByRequiredPackages && !RequiredPackages.Contains(AssetData->PackageName))
|
|
{
|
|
bRemoveAssetData = true;
|
|
}
|
|
else if (bIsFilteredByRemovedPackages && RemovePackages.Contains(AssetData->PackageName))
|
|
{
|
|
bRemoveAssetData = true;
|
|
}
|
|
else if (Options.bFilterAssetDataWithNoTags && AssetData->TagsAndValues.Num() == 0 &&
|
|
!FPackageName::IsLocalizedPackage(AssetData->PackageName.ToString()))
|
|
{
|
|
bRemoveAssetData = true;
|
|
bRemoveDependencyData = Options.bFilterDependenciesWithNoTags;
|
|
}
|
|
|
|
if (bRemoveAssetData)
|
|
{
|
|
bool bRemovedAssetData, bRemovedPackageData;
|
|
FName AssetPackageName = AssetData->PackageName;
|
|
// AssetData might be deleted after this call
|
|
RemoveAssetData(AssetData, false /* bRemoveDependencyData */, bRemovedAssetData, bRemovedPackageData);
|
|
if (!bRemoveDependencyData)
|
|
{
|
|
RequiredDependNodePackages.Add(AssetPackageName);
|
|
}
|
|
else if (bRemovedPackageData)
|
|
{
|
|
FDependsNode** RemovedNode = CachedDependsNodes.Find(AssetPackageName);
|
|
if (RemovedNode)
|
|
{
|
|
RemoveDependsNodes.Add(*RemovedNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<FDependsNode*> AllDependsNodes;
|
|
CachedDependsNodes.GenerateValueArray(AllDependsNodes);
|
|
|
|
// Mark-for-removal all other dependsnodes that are filtered out by our settings
|
|
for (FDependsNode* DependsNode : AllDependsNodes)
|
|
{
|
|
const FAssetIdentifier& Id = DependsNode->GetIdentifier();
|
|
bool bRemoveDependsNode = false;
|
|
if (RemoveDependsNodes.Contains(DependsNode))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (Options.bFilterSearchableNames && Id.IsValue())
|
|
{
|
|
bRemoveDependsNode = true;
|
|
}
|
|
else if (Id.IsPackage() &&
|
|
!CachedAssetsByPackageName.Contains(Id.PackageName) &&
|
|
!RequiredDependNodePackages.Contains(Id.PackageName) &&
|
|
!FPackageName::IsScriptPackage(Id.PackageName.ToString()))
|
|
{
|
|
bRemoveDependsNode = true;
|
|
}
|
|
|
|
if (bRemoveDependsNode)
|
|
{
|
|
RemoveDependsNodes.Add(DependsNode);
|
|
}
|
|
}
|
|
|
|
// Batch-remove all of the marked-for-removal dependsnodes
|
|
for (FDependsNode* DependsNode : AllDependsNodes)
|
|
{
|
|
check(DependsNode != nullptr);
|
|
if (RemoveDependsNodes.Contains(DependsNode))
|
|
{
|
|
CachedDependsNodes.Remove(DependsNode->GetIdentifier());
|
|
NumDependsNodes--;
|
|
// if the depends nodes were preallocated in a block, we can't delete them one at a time, only the whole chunk in the destructor
|
|
if (PreallocatedDependsNodeDataBuffers.Num() == 0)
|
|
{
|
|
delete DependsNode;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DependsNode->RemoveLinks([&RemoveDependsNodes](const FDependsNode* ExistingDependsNode) { return RemoveDependsNodes.Contains(ExistingDependsNode); });
|
|
}
|
|
}
|
|
|
|
// Remove any orphaned depends nodes. This will leave cycles in but those might represent useful data
|
|
CachedDependsNodes.GenerateValueArray(AllDependsNodes);
|
|
for (FDependsNode* DependsNode : AllDependsNodes)
|
|
{
|
|
if (DependsNode->GetConnectionCount() == 0)
|
|
{
|
|
RemoveDependsNode(DependsNode->GetIdentifier());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FAssetRegistryState::HasAssets(const FName PackagePath, bool bARFiltering) const
|
|
{
|
|
const TArray<FAssetData*>* FoundAssetArray = CachedAssetsByPath.Find(PackagePath);
|
|
if (FoundAssetArray)
|
|
{
|
|
if (bARFiltering)
|
|
{
|
|
return FoundAssetArray->ContainsByPredicate([](FAssetData* AssetData)
|
|
{
|
|
return AssetData && !UE::AssetRegistry::FFiltering::ShouldSkipAsset(AssetData->AssetClassPath, AssetData->PackageFlags);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
return FoundAssetArray->Num() > 0;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FAssetRegistryState::GetAssets(const FARCompiledFilter& Filter, const TSet<FName>& PackageNamesToSkip, TArray<FAssetData>& OutAssetData, bool bARFiltering) const
|
|
{
|
|
return EnumerateAssets(Filter, PackageNamesToSkip, [&OutAssetData](const FAssetData& AssetData)
|
|
{
|
|
OutAssetData.Emplace(AssetData);
|
|
return true;
|
|
},
|
|
bARFiltering);
|
|
}
|
|
|
|
template<class ArrayType, typename KeyType>
|
|
TArray<FAssetData*> FindAssets(const TMap<KeyType, ArrayType>& Map, const TSet<KeyType>& Keys)
|
|
{
|
|
TArray<TArrayView<FAssetData* const>> Matches;
|
|
Matches.Reserve(Keys.Num());
|
|
uint32 TotalMatches = 0;
|
|
|
|
for (const KeyType& Key : Keys)
|
|
{
|
|
if (const ArrayType* Assets = Map.Find(Key))
|
|
{
|
|
Matches.Add(MakeArrayView(*Assets));
|
|
TotalMatches += Assets->Num();
|
|
}
|
|
}
|
|
|
|
TArray<FAssetData*> Out;
|
|
Out.Reserve(TotalMatches);
|
|
for (TArrayView<FAssetData* const> Assets : Matches)
|
|
{
|
|
Out.Append(Assets.GetData(), Assets.Num());
|
|
}
|
|
|
|
return Out;
|
|
}
|
|
|
|
bool FAssetRegistryState::EnumerateAssets(const FARCompiledFilter& Filter, const TSet<FName>& PackageNamesToSkip, TFunctionRef<bool(const FAssetData&)> Callback, bool bARFiltering) const
|
|
{
|
|
// Verify filter input. If all assets are needed, use EnumerateAllAssets() instead.
|
|
if (Filter.IsEmpty() || !IsFilterValid(Filter))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const uint32 FilterWithoutPackageFlags = Filter.WithoutPackageFlags;
|
|
const uint32 FilterWithPackageFlags = Filter.WithPackageFlags;
|
|
|
|
// The assets that match each filter
|
|
TArray<TArray<FAssetData*>, TInlineAllocator<5>> FilterResults;
|
|
|
|
// On disk package names
|
|
if (Filter.PackageNames.Num() > 0)
|
|
{
|
|
FilterResults.Emplace(FindAssets(CachedAssetsByPackageName, Filter.PackageNames));
|
|
}
|
|
|
|
// On disk package paths
|
|
if (Filter.PackagePaths.Num() > 0)
|
|
{
|
|
FilterResults.Emplace(FindAssets(CachedAssetsByPath, Filter.PackagePaths));
|
|
}
|
|
|
|
// On disk classes
|
|
if (Filter.ClassPaths.Num() > 0)
|
|
{
|
|
FilterResults.Emplace(FindAssets(CachedAssetsByClass, Filter.ClassPaths));
|
|
}
|
|
|
|
// On disk object paths
|
|
if (Filter.ObjectPaths.Num() > 0)
|
|
{
|
|
TArray<FAssetData*>& ObjectPathsFilter = FilterResults.Emplace_GetRef();
|
|
ObjectPathsFilter.Reserve(Filter.ObjectPaths.Num());
|
|
|
|
for (FName ObjectPath : Filter.ObjectPaths)
|
|
{
|
|
if (FAssetData* AssetDataPtr = CachedAssetsByObjectPath.FindRef(ObjectPath))
|
|
{
|
|
ObjectPathsFilter.Add(AssetDataPtr);
|
|
}
|
|
}
|
|
}
|
|
|
|
// On disk tags and values
|
|
if (Filter.TagsAndValues.Num() > 0)
|
|
{
|
|
TArray<FAssetData*>& TagAndValuesFilter = FilterResults.Emplace_GetRef();
|
|
// Sometimes number of assets matching this filter is correlated to number of assets matching previous filters
|
|
if (FilterResults.Num())
|
|
{
|
|
TagAndValuesFilter.Reserve(FilterResults[0].Num());
|
|
}
|
|
|
|
for (auto FilterTagIt = Filter.TagsAndValues.CreateConstIterator(); FilterTagIt; ++FilterTagIt)
|
|
{
|
|
const FName Tag = FilterTagIt.Key();
|
|
const TOptional<FString>& Value = FilterTagIt.Value();
|
|
|
|
if (const TArray<FAssetData*>* TagAssets = CachedAssetsByTag.Find(Tag))
|
|
{
|
|
for (FAssetData* AssetData : *TagAssets)
|
|
{
|
|
if (AssetData != nullptr)
|
|
{
|
|
bool bAccept;
|
|
if (!Value.IsSet())
|
|
{
|
|
bAccept = AssetData->TagsAndValues.Contains(Tag);
|
|
}
|
|
else
|
|
{
|
|
bAccept = AssetData->TagsAndValues.ContainsKeyValue(Tag, Value.GetValue());
|
|
}
|
|
if (bAccept)
|
|
{
|
|
TagAndValuesFilter.Add(AssetData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Perform callback for assets that match all filters
|
|
if (FilterResults.Num() > 0)
|
|
{
|
|
auto SkipAssetData = [&](const FAssetData* AssetData)
|
|
{
|
|
if (PackageNamesToSkip.Contains(AssetData->PackageName) | //-V792
|
|
AssetData->HasAnyPackageFlags(FilterWithoutPackageFlags) | //-V792
|
|
!AssetData->HasAllPackageFlags(FilterWithPackageFlags)) //-V792
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return bARFiltering && UE::AssetRegistry::FFiltering::ShouldSkipAsset(AssetData->AssetClassPath, AssetData->PackageFlags);
|
|
};
|
|
|
|
int32 NumFilterResults = FilterResults.Num();
|
|
if (NumFilterResults > 1)
|
|
{
|
|
// Mark which filters each asset passes
|
|
uint32 PassAllFiltersValue = (1 << NumFilterResults) - 1; // 1 in every bit for the lowest n bits
|
|
TMap<FAssetData*, uint32> PassBits;
|
|
for (int32 FilterIndex = 0; FilterIndex < NumFilterResults; ++FilterIndex)
|
|
{
|
|
const TArray<FAssetData*>& FilterEvaluation = FilterResults[FilterIndex];
|
|
PassBits.Reserve(FilterEvaluation.Num());
|
|
|
|
for (FAssetData* AssetData : FilterEvaluation)
|
|
{
|
|
PassBits.FindOrAdd(AssetData) |= (1 << FilterIndex);
|
|
}
|
|
}
|
|
|
|
// Include assets that pass all filters
|
|
for (TPair<FAssetData*, uint32> PassPair : PassBits)
|
|
{
|
|
const FAssetData* AssetData = PassPair.Key;
|
|
if (PassPair.Value != PassAllFiltersValue || SkipAssetData(AssetData))
|
|
{
|
|
continue;
|
|
}
|
|
else if (!Callback(*AssetData))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// All matched assets passed the single filter
|
|
for (const FAssetData* AssetData : FilterResults[0])
|
|
{
|
|
if (SkipAssetData(AssetData))
|
|
{
|
|
continue;
|
|
}
|
|
else if (!Callback(*AssetData))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FAssetRegistryState::GetAllAssets(const TSet<FName>& PackageNamesToSkip, TArray<FAssetData>& OutAssetData, bool bARFiltering) const
|
|
{
|
|
return EnumerateAllAssets(PackageNamesToSkip, [&OutAssetData](const FAssetData& AssetData)
|
|
{
|
|
OutAssetData.Emplace(AssetData);
|
|
return true;
|
|
},
|
|
bARFiltering);
|
|
}
|
|
|
|
bool FAssetRegistryState::EnumerateAllAssets(const TSet<FName>& PackageNamesToSkip, TFunctionRef<bool(const FAssetData&)> Callback, bool bARFiltering) const
|
|
{
|
|
// All unloaded disk assets
|
|
for (const TPair<FName, FAssetData*>& AssetDataPair : CachedAssetsByObjectPath)
|
|
{
|
|
const FAssetData* AssetData = AssetDataPair.Value;
|
|
|
|
if (AssetData &&
|
|
!PackageNamesToSkip.Contains(AssetData->PackageName) &&
|
|
(!bARFiltering || !UE::AssetRegistry::FFiltering::ShouldSkipAsset(AssetData->AssetClassPath, AssetData->PackageFlags)))
|
|
{
|
|
if (!Callback(*AssetData))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void FAssetRegistryState::GetPackagesByName(FStringView PackageName, TArray<FName>& OutPackageNames) const
|
|
{
|
|
// Note that we use CachedAssetsByPackageName rather than CachedPackageData because CachedPackageData
|
|
// is often stripped out of the runtime AssetRegistry
|
|
if (!FPackageName::IsShortPackageName(PackageName))
|
|
{
|
|
FName PackageFName(PackageName);
|
|
if (CachedAssetsByPackageName.Contains(PackageFName))
|
|
{
|
|
OutPackageNames.Add(PackageFName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TStringBuilder<128> PackageNameStr;
|
|
for (const auto& It : CachedAssetsByPackageName)
|
|
{
|
|
It.Key.ToString(PackageNameStr);
|
|
FStringView ExistingBaseName = FPathViews::GetBaseFilename(PackageNameStr);
|
|
if (ExistingBaseName.Equals(PackageName, ESearchCase::IgnoreCase))
|
|
{
|
|
OutPackageNames.Add(It.Key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FName FAssetRegistryState::GetFirstPackageByName(FStringView PackageName) const
|
|
{
|
|
TArray<FName> LongPackageNames;
|
|
GetPackagesByName(PackageName, LongPackageNames);
|
|
if (LongPackageNames.Num() == 0)
|
|
{
|
|
return NAME_None;
|
|
}
|
|
if (LongPackageNames.Num() > 1)
|
|
{
|
|
LongPackageNames.Sort(FNameLexicalLess());
|
|
UE_LOG(LogAssetRegistry, Warning, TEXT("GetFirstPackageByName('%.*s') is returning '%s', but it also found '%s'%s."),
|
|
PackageName.Len(), PackageName.GetData(), *LongPackageNames[0].ToString(), *LongPackageNames[1].ToString(),
|
|
(LongPackageNames.Num() > 2 ? *FString::Printf(TEXT(" and %d others"), LongPackageNames.Num() - 2) : TEXT("")));
|
|
}
|
|
return LongPackageNames[0];
|
|
}
|
|
|
|
bool FAssetRegistryState::GetDependencies(const FAssetIdentifier& AssetIdentifier,
|
|
TArray<FAssetIdentifier>& OutDependencies,
|
|
EAssetRegistryDependencyType::Type InDependencyType) const
|
|
{
|
|
bool bResult = false;
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
UE::AssetRegistry::FDependencyQuery Flags(InDependencyType);
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
if (!!(InDependencyType & EAssetRegistryDependencyType::Packages))
|
|
{
|
|
bResult = GetDependencies(AssetIdentifier, OutDependencies, UE::AssetRegistry::EDependencyCategory::Package, Flags) || bResult;
|
|
}
|
|
if (!!(InDependencyType & EAssetRegistryDependencyType::SearchableName))
|
|
{
|
|
bResult = GetDependencies(AssetIdentifier, OutDependencies, UE::AssetRegistry::EDependencyCategory::SearchableName) || bResult;
|
|
}
|
|
if (!!(InDependencyType & EAssetRegistryDependencyType::Manage))
|
|
{
|
|
bResult = GetDependencies(AssetIdentifier, OutDependencies, UE::AssetRegistry::EDependencyCategory::Manage, Flags) || bResult;
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
bool FAssetRegistryState::GetDependencies(const FAssetIdentifier& AssetIdentifier,
|
|
TArray<FAssetIdentifier>& OutDependencies,
|
|
UE::AssetRegistry::EDependencyCategory Category, const UE::AssetRegistry::FDependencyQuery& Flags) const
|
|
{
|
|
const FDependsNode* const* NodePtr = CachedDependsNodes.Find(AssetIdentifier);
|
|
const FDependsNode* Node = nullptr;
|
|
if (NodePtr != nullptr)
|
|
{
|
|
Node = *NodePtr;
|
|
}
|
|
|
|
if (Node != nullptr)
|
|
{
|
|
Node->GetDependencies(OutDependencies, Category, Flags);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FAssetRegistryState::GetDependencies(const FAssetIdentifier& AssetIdentifier,
|
|
TArray<FAssetDependency>& OutDependencies,
|
|
UE::AssetRegistry::EDependencyCategory Category, const UE::AssetRegistry::FDependencyQuery& Flags) const
|
|
{
|
|
const FDependsNode* const* NodePtr = CachedDependsNodes.Find(AssetIdentifier);
|
|
const FDependsNode* Node = nullptr;
|
|
if (NodePtr != nullptr)
|
|
{
|
|
Node = *NodePtr;
|
|
}
|
|
|
|
if (Node != nullptr)
|
|
{
|
|
Node->GetDependencies(OutDependencies, Category, Flags);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FAssetRegistryState::GetReferencers(const FAssetIdentifier& AssetIdentifier,
|
|
TArray<FAssetIdentifier>& OutReferencers,
|
|
EAssetRegistryDependencyType::Type InReferenceType) const
|
|
{
|
|
bool bResult = false;
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
UE::AssetRegistry::FDependencyQuery Flags(InReferenceType);
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
if (!!(InReferenceType & EAssetRegistryDependencyType::Packages))
|
|
{
|
|
bResult = GetReferencers(AssetIdentifier, OutReferencers, UE::AssetRegistry::EDependencyCategory::Package, Flags) || bResult;
|
|
}
|
|
if (!!(InReferenceType & EAssetRegistryDependencyType::SearchableName))
|
|
{
|
|
bResult = GetReferencers(AssetIdentifier, OutReferencers, UE::AssetRegistry::EDependencyCategory::SearchableName) || bResult;
|
|
}
|
|
if (!!(InReferenceType & EAssetRegistryDependencyType::Manage))
|
|
{
|
|
bResult = GetReferencers(AssetIdentifier, OutReferencers, UE::AssetRegistry::EDependencyCategory::Manage, Flags) || bResult;
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
bool FAssetRegistryState::GetReferencers(const FAssetIdentifier& AssetIdentifier,
|
|
TArray<FAssetIdentifier>& OutReferencers,
|
|
UE::AssetRegistry::EDependencyCategory Category, const UE::AssetRegistry::FDependencyQuery& Flags) const
|
|
{
|
|
const FDependsNode* const* NodePtr = CachedDependsNodes.Find(AssetIdentifier);
|
|
const FDependsNode* Node = nullptr;
|
|
|
|
if (NodePtr != nullptr)
|
|
{
|
|
Node = *NodePtr;
|
|
}
|
|
|
|
if (Node != nullptr)
|
|
{
|
|
TArray<FDependsNode*> DependencyNodes;
|
|
Node->GetReferencers(DependencyNodes, Category, Flags);
|
|
|
|
OutReferencers.Reserve(DependencyNodes.Num());
|
|
for (FDependsNode* DependencyNode : DependencyNodes)
|
|
{
|
|
OutReferencers.Add(DependencyNode->GetIdentifier());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FAssetRegistryState::GetReferencers(const FAssetIdentifier& AssetIdentifier,
|
|
TArray<FAssetDependency>& OutReferencers,
|
|
UE::AssetRegistry::EDependencyCategory Category, const UE::AssetRegistry::FDependencyQuery& Flags) const
|
|
{
|
|
const FDependsNode* const* NodePtr = CachedDependsNodes.Find(AssetIdentifier);
|
|
const FDependsNode* Node = nullptr;
|
|
|
|
if (NodePtr != nullptr)
|
|
{
|
|
Node = *NodePtr;
|
|
}
|
|
|
|
if (Node != nullptr)
|
|
{
|
|
Node->GetReferencers(OutReferencers, Category, Flags);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FAssetRegistryState::Serialize(FArchive& Ar, const FAssetRegistrySerializationOptions& Options)
|
|
{
|
|
return Ar.IsSaving() ? Save(Ar, Options) : Load(Ar, FAssetRegistryLoadOptions(Options));
|
|
}
|
|
|
|
bool FAssetRegistryState::Save(FArchive& OriginalAr, const FAssetRegistrySerializationOptions& Options)
|
|
{
|
|
SCOPED_BOOT_TIMING("FAssetRegistryState::Save");
|
|
|
|
check(!OriginalAr.IsLoading());
|
|
|
|
#if !ALLOW_NAME_BATCH_SAVING
|
|
checkf(false, TEXT("Cannot save cooked AssetRegistryState in this configuration"));
|
|
#else
|
|
check(CachedAssetsByObjectPath.Num() == NumAssets);
|
|
|
|
FAssetRegistryVersion::Type Version = FAssetRegistryVersion::LatestVersion;
|
|
FAssetRegistryVersion::SerializeVersion(OriginalAr, Version);
|
|
|
|
// Set up fixed asset registry writer
|
|
FAssetRegistryWriter Ar(FAssetRegistryWriterOptions(Options), OriginalAr);
|
|
|
|
// serialize number of objects
|
|
int32 AssetCount = CachedAssetsByObjectPath.Num();
|
|
Ar << AssetCount;
|
|
|
|
// Write asset data first
|
|
TArray<TPair<FName, FAssetData*>> SortedAssetsByObjectPath = CachedAssetsByObjectPath.Array();
|
|
Algo::Sort(SortedAssetsByObjectPath, [](TPair<FName, FAssetData*>& A, TPair<FName, FAssetData*>& B) { return A.Key.LexicalLess(B.Key); });
|
|
for (const TPair<FName, FAssetData*>& Pair : SortedAssetsByObjectPath)
|
|
{
|
|
// Hardcoding FAssetRegistryVersion::LatestVersion here so that branches can get optimized out in the forceinlined SerializeForCache
|
|
Pair.Value->SerializeForCache(Ar, FAssetRegistryVersion::LatestVersion);
|
|
}
|
|
|
|
// Serialize Dependencies
|
|
// Write placeholder data for the size
|
|
int64 OffsetToDependencySectionSize = Ar.Tell();
|
|
int64 DependencySectionSize = 0;
|
|
Ar << DependencySectionSize;
|
|
int64 DependencySectionStart = Ar.Tell();
|
|
if (!Options.bSerializeDependencies)
|
|
{
|
|
int32 NumDependencies = 0;
|
|
Ar << NumDependencies;
|
|
}
|
|
else
|
|
{
|
|
TMap<FDependsNode*, FDependsNode*> RedirectCache;
|
|
TArray<FDependsNode*> Dependencies;
|
|
|
|
// Scan dependency nodes, we won't save all of them if we filter out certain types
|
|
for (TPair<FAssetIdentifier, FDependsNode*>& Pair : CachedDependsNodes)
|
|
{
|
|
FDependsNode* Node = Pair.Value;
|
|
|
|
if (Node->GetIdentifier().IsPackage()
|
|
|| (Options.bSerializeSearchableNameDependencies && Node->GetIdentifier().IsValue())
|
|
|| (Options.bSerializeManageDependencies && Node->GetIdentifier().GetPrimaryAssetId().IsValid()))
|
|
{
|
|
Dependencies.Add(Node);
|
|
}
|
|
}
|
|
Algo::Sort(Dependencies, [](FDependsNode* A, FDependsNode* B) { return A->GetIdentifier().LexicalLess(B->GetIdentifier()); });
|
|
int32 NumDependencies = Dependencies.Num();
|
|
|
|
TMap<FDependsNode*, int32> DependsIndexMap;
|
|
DependsIndexMap.Reserve(NumDependencies);
|
|
int32 Index = 0;
|
|
for (FDependsNode* Node : Dependencies)
|
|
{
|
|
DependsIndexMap.Add(Node, Index++);
|
|
}
|
|
|
|
TUniqueFunction<int32(FDependsNode*, bool bAsReferencer)> GetSerializeIndexFromNode = [this, &RedirectCache, &DependsIndexMap](FDependsNode* InDependency, bool bAsReferencer)
|
|
{
|
|
if (!bAsReferencer)
|
|
{
|
|
InDependency = ResolveRedirector(InDependency, CachedAssetsByObjectPath, RedirectCache);
|
|
}
|
|
if (!InDependency)
|
|
{
|
|
return -1;
|
|
}
|
|
int32* DependencyIndex = DependsIndexMap.Find(InDependency);
|
|
if (!DependencyIndex)
|
|
{
|
|
return -1;
|
|
}
|
|
return *DependencyIndex;
|
|
};
|
|
|
|
FDependsNode::FSaveScratch Scratch;
|
|
Ar << NumDependencies;
|
|
for (FDependsNode* DependentNode : Dependencies)
|
|
{
|
|
DependentNode->SerializeSave(Ar, GetSerializeIndexFromNode, Scratch, Options);
|
|
}
|
|
}
|
|
// Write the real value to the placeholder data for the DependencySectionSize
|
|
int64 DependencySectionEnd = Ar.Tell();
|
|
DependencySectionSize = DependencySectionEnd - DependencySectionStart;
|
|
Ar.Seek(OffsetToDependencySectionSize);
|
|
Ar << DependencySectionSize;
|
|
check(Ar.Tell() == DependencySectionStart);
|
|
Ar.Seek(DependencySectionEnd);
|
|
|
|
|
|
// Serialize the PackageData
|
|
int32 PackageDataCount = 0;
|
|
if (Options.bSerializePackageData)
|
|
{
|
|
PackageDataCount = CachedPackageData.Num();
|
|
Ar << PackageDataCount;
|
|
|
|
TArray<TPair<FName, FAssetPackageData*>> SortedPackageData = CachedPackageData.Array();
|
|
Algo::Sort(SortedPackageData, [](TPair<FName, FAssetPackageData*>& A, TPair<FName, FAssetPackageData*>& B) { return A.Key.LexicalLess(B.Key); });
|
|
for (TPair<FName, FAssetPackageData*>& Pair : SortedPackageData)
|
|
{
|
|
Ar << Pair.Key;
|
|
Pair.Value->SerializeForCache(Ar);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Ar << PackageDataCount;
|
|
}
|
|
#endif // ALLOW_NAME_BATCH_SAVING
|
|
|
|
return !OriginalAr.IsError();
|
|
}
|
|
|
|
bool FAssetRegistryState::Load(FArchive& OriginalAr, const FAssetRegistryLoadOptions& Options, FAssetRegistryVersion::Type* OutVersion)
|
|
{
|
|
LLM_SCOPE(ELLMTag::AssetRegistry);
|
|
|
|
FAssetRegistryVersion::Type Version = FAssetRegistryVersion::LatestVersion;
|
|
FAssetRegistryVersion::SerializeVersion(OriginalAr, Version);
|
|
if (OutVersion != nullptr)
|
|
{
|
|
*OutVersion = Version;
|
|
}
|
|
|
|
FSoftObjectPathSerializationScope SerializationScope(NAME_None, NAME_None, ESoftObjectPathCollectType::NonPackage, ESoftObjectPathSerializeType::AlwaysSerialize);
|
|
|
|
if (Version < FAssetRegistryVersion::RemovedMD5Hash)
|
|
{
|
|
// Cannot read states before this version
|
|
return false;
|
|
}
|
|
else if (Version < FAssetRegistryVersion::FixedTags)
|
|
{
|
|
FNameTableArchiveReader NameTableReader(OriginalAr);
|
|
Load(NameTableReader, Version, Options);
|
|
}
|
|
else
|
|
{
|
|
FAssetRegistryReader Reader(OriginalAr, Options.ParallelWorkers, Version);
|
|
|
|
if (Reader.IsError())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Load won't resolve asset registry tag values loaded in parallel
|
|
// and can run before WaitForTasks
|
|
Load(Reader, Version, Options);
|
|
|
|
Reader.WaitForTasks();
|
|
}
|
|
|
|
return !OriginalAr.IsError();
|
|
}
|
|
|
|
/* static */ bool FAssetRegistryState::LoadFromDisk(const TCHAR* InPath, const FAssetRegistryLoadOptions& InOptions, FAssetRegistryState& OutState, FAssetRegistryVersion::Type* OutVersion)
|
|
{
|
|
check(InPath);
|
|
|
|
TUniquePtr<FArchive> FileReader(IFileManager::Get().CreateFileReader(InPath));
|
|
if (FileReader)
|
|
{
|
|
// It's faster to load the whole file into memory on a Gen5 console
|
|
TArray64<uint8> Data;
|
|
Data.SetNumUninitialized(FileReader->TotalSize());
|
|
FileReader->Serialize(Data.GetData(), Data.Num());
|
|
check(!FileReader->IsError());
|
|
|
|
FLargeMemoryReader MemoryReader(Data.GetData(), Data.Num());
|
|
return OutState.Load(MemoryReader, InOptions, OutVersion);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template<class Archive>
|
|
void FAssetRegistryState::Load(Archive&& Ar, FAssetRegistryVersion::Type Version, const FAssetRegistryLoadOptions& Options)
|
|
{
|
|
// serialize number of objects
|
|
int32 LocalNumAssets = 0;
|
|
Ar << LocalNumAssets;
|
|
|
|
// allocate one single block for all asset data structs (to reduce tens of thousands of heap allocations)
|
|
TArrayView<FAssetData> PreallocatedAssetDataBuffer(new FAssetData[LocalNumAssets], LocalNumAssets);
|
|
PreallocatedAssetDataBuffers.Add(PreallocatedAssetDataBuffer.GetData());
|
|
|
|
for (FAssetData& NewAssetData : PreallocatedAssetDataBuffer)
|
|
{
|
|
NewAssetData.SerializeForCache(Ar, Version);
|
|
}
|
|
|
|
SetAssetDatas(PreallocatedAssetDataBuffer, Options);
|
|
|
|
if (Version < FAssetRegistryVersion::AddedDependencyFlags)
|
|
{
|
|
LoadDependencies_BeforeFlags(Ar, Options.bLoadDependencies, Version);
|
|
}
|
|
else
|
|
{
|
|
int64 DependencySectionSize;
|
|
Ar << DependencySectionSize;
|
|
int64 DependencySectionEnd = Ar.Tell() + DependencySectionSize;
|
|
|
|
if (Options.bLoadDependencies)
|
|
{
|
|
LoadDependencies(Ar);
|
|
}
|
|
|
|
if (!Options.bLoadDependencies || Ar.IsError())
|
|
{
|
|
Ar.Seek(DependencySectionEnd);
|
|
}
|
|
}
|
|
|
|
int32 LocalNumPackageData = 0;
|
|
Ar << LocalNumPackageData;
|
|
|
|
if (LocalNumPackageData > 0)
|
|
{
|
|
FAssetPackageData SerializedElement;
|
|
TArrayView<FAssetPackageData> PreallocatedPackageDataBuffer;
|
|
if (Options.bLoadPackageData)
|
|
{
|
|
PreallocatedPackageDataBuffer = TArrayView<FAssetPackageData>(new FAssetPackageData[LocalNumPackageData], LocalNumPackageData);
|
|
PreallocatedPackageDataBuffers.Add(PreallocatedPackageDataBuffer.GetData());
|
|
CachedPackageData.Reserve(LocalNumPackageData);
|
|
}
|
|
for (int32 PackageDataIndex = 0; PackageDataIndex < LocalNumPackageData; PackageDataIndex++)
|
|
{
|
|
FName PackageName;
|
|
Ar << PackageName;
|
|
FAssetPackageData* NewPackageData;
|
|
if (Options.bLoadPackageData)
|
|
{
|
|
NewPackageData = &PreallocatedPackageDataBuffer[PackageDataIndex];
|
|
CachedPackageData.Add(PackageName, NewPackageData);
|
|
}
|
|
else
|
|
{
|
|
NewPackageData = &SerializedElement;
|
|
}
|
|
if (Version >= FAssetRegistryVersion::LatestVersion)
|
|
{
|
|
NewPackageData->SerializeForCache(Ar);
|
|
}
|
|
else
|
|
{
|
|
NewPackageData->SerializeForCacheOldVersion(Ar, Version);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAssetRegistryState::LoadDependencies(FArchive& Ar)
|
|
{
|
|
int32 LocalNumDependsNodes = 0;
|
|
Ar << LocalNumDependsNodes;
|
|
|
|
if (LocalNumDependsNodes <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FDependsNode* PreallocatedDependsNodeDataBuffer = new FDependsNode[LocalNumDependsNodes];
|
|
PreallocatedDependsNodeDataBuffers.Add(PreallocatedDependsNodeDataBuffer);
|
|
CachedDependsNodes.Reserve(LocalNumDependsNodes);
|
|
|
|
TUniqueFunction<FDependsNode*(int32)> GetNodeFromSerializeIndex = [&PreallocatedDependsNodeDataBuffer, LocalNumDependsNodes](int32 Index) -> FDependsNode*
|
|
{
|
|
if (Index < 0 || LocalNumDependsNodes <= Index)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return &PreallocatedDependsNodeDataBuffer[Index];
|
|
};
|
|
|
|
FDependsNode::FLoadScratch Scratch;
|
|
for (int32 DependsNodeIndex = 0; DependsNodeIndex < LocalNumDependsNodes; DependsNodeIndex++)
|
|
{
|
|
FDependsNode* DependsNode = &PreallocatedDependsNodeDataBuffer[DependsNodeIndex];
|
|
DependsNode->SerializeLoad(Ar, GetNodeFromSerializeIndex, Scratch);
|
|
CachedDependsNodes.Add(DependsNode->GetIdentifier(), DependsNode);
|
|
}
|
|
}
|
|
|
|
void FAssetRegistryState::LoadDependencies_BeforeFlags(FArchive& Ar, bool bSerializeDependencies, FAssetRegistryVersion::Type Version)
|
|
{
|
|
int32 LocalNumDependsNodes = 0;
|
|
Ar << LocalNumDependsNodes;
|
|
|
|
FDependsNode Placeholder;
|
|
FDependsNode* PreallocatedDependsNodeDataBuffer = nullptr;
|
|
if (bSerializeDependencies && LocalNumDependsNodes > 0)
|
|
{
|
|
PreallocatedDependsNodeDataBuffer = new FDependsNode[LocalNumDependsNodes];
|
|
PreallocatedDependsNodeDataBuffers.Add(PreallocatedDependsNodeDataBuffer);
|
|
CachedDependsNodes.Reserve(LocalNumDependsNodes);
|
|
}
|
|
TUniqueFunction<FDependsNode* (int32)> GetNodeFromSerializeIndex = [&PreallocatedDependsNodeDataBuffer, LocalNumDependsNodes](int32 Index)->FDependsNode *
|
|
{
|
|
if (Index < 0 || LocalNumDependsNodes <= Index)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return &PreallocatedDependsNodeDataBuffer[Index];
|
|
};
|
|
|
|
uint32 HardBits, SoftBits, HardManageBits, SoftManageBits;
|
|
FDependsNode::GetPropertySetBits_BeforeFlags(HardBits, SoftBits, HardManageBits, SoftManageBits);
|
|
|
|
TArray<FDependsNode*> DependsNodes;
|
|
for (int32 DependsNodeIndex = 0; DependsNodeIndex < LocalNumDependsNodes; DependsNodeIndex++)
|
|
{
|
|
// Create the node if we're actually saving dependencies, otherwise just fake serialize
|
|
FDependsNode* DependsNode = nullptr;
|
|
if (bSerializeDependencies)
|
|
{
|
|
DependsNode = &PreallocatedDependsNodeDataBuffer[DependsNodeIndex];
|
|
}
|
|
else
|
|
{
|
|
DependsNode = &Placeholder;
|
|
}
|
|
|
|
// Call the DependsNode legacy serialization function
|
|
DependsNode->SerializeLoad_BeforeFlags(Ar, Version, PreallocatedDependsNodeDataBuffer, LocalNumDependsNodes, bSerializeDependencies, HardBits, SoftBits, HardManageBits, SoftManageBits);
|
|
|
|
// Register the DependsNode with its AssetIdentifier
|
|
if (bSerializeDependencies)
|
|
{
|
|
CachedDependsNodes.Add(DependsNode->GetIdentifier(), DependsNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
SIZE_T FAssetRegistryState::GetAllocatedSize(bool bLogDetailed) const
|
|
{
|
|
SIZE_T MapMemory = CachedAssetsByObjectPath.GetAllocatedSize();
|
|
MapMemory += CachedAssetsByPackageName.GetAllocatedSize();
|
|
MapMemory += CachedAssetsByPath.GetAllocatedSize();
|
|
MapMemory += CachedAssetsByClass.GetAllocatedSize();
|
|
MapMemory += CachedAssetsByTag.GetAllocatedSize();
|
|
MapMemory += CachedDependsNodes.GetAllocatedSize();
|
|
MapMemory += CachedPackageData.GetAllocatedSize();
|
|
MapMemory += PreallocatedAssetDataBuffers.GetAllocatedSize();
|
|
MapMemory += PreallocatedDependsNodeDataBuffers.GetAllocatedSize();
|
|
MapMemory += PreallocatedPackageDataBuffers.GetAllocatedSize();
|
|
|
|
SIZE_T MapArrayMemory = 0;
|
|
auto SubArray =
|
|
[&MapArrayMemory](const auto& A)
|
|
{
|
|
for (auto& Pair : A)
|
|
{
|
|
MapArrayMemory += Pair.Value.GetAllocatedSize();
|
|
}
|
|
};
|
|
SubArray(CachedAssetsByPackageName);
|
|
SubArray(CachedAssetsByPath);
|
|
|
|
for (auto& Pair : CachedAssetsByClass)
|
|
{
|
|
MapArrayMemory += Pair.Value.GetAllocatedSize();
|
|
}
|
|
|
|
SubArray(CachedAssetsByTag);
|
|
|
|
if (bLogDetailed)
|
|
{
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("Index Size: %" SIZE_T_FMT "k"), MapMemory / 1024);
|
|
}
|
|
|
|
SIZE_T AssetDataSize = 0;
|
|
FAssetDataTagMapSharedView::FMemoryCounter TagMemoryUsage;
|
|
|
|
for (const TPair<FName, FAssetData*>& AssetDataPair : CachedAssetsByObjectPath)
|
|
{
|
|
const FAssetData& AssetData = *AssetDataPair.Value;
|
|
|
|
AssetDataSize += sizeof(AssetData);
|
|
AssetDataSize += AssetData.ChunkIDs.GetAllocatedSize();
|
|
TagMemoryUsage.Include(AssetData.TagsAndValues);
|
|
}
|
|
|
|
if (bLogDetailed)
|
|
{
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("AssetData Count: %d"), CachedAssetsByObjectPath.Num());
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("AssetData Static Size: %" SIZE_T_FMT "k"), AssetDataSize / 1024);
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("Loose Tags: %" SIZE_T_FMT "k"), TagMemoryUsage.GetLooseSize() / 1024);
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("Fixed Tags: %" SIZE_T_FMT "k"), TagMemoryUsage.GetFixedSize() / 1024);
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("TArray<FAssetData*>: %" SIZE_T_FMT "k"), MapArrayMemory / 1024);
|
|
}
|
|
|
|
SIZE_T DependNodesSize = 0, DependenciesSize = 0;
|
|
|
|
for (const TPair<FAssetIdentifier, FDependsNode*>& DependsNodePair : CachedDependsNodes)
|
|
{
|
|
const FDependsNode& DependsNode = *DependsNodePair.Value;
|
|
DependNodesSize += sizeof(DependsNode);
|
|
|
|
DependenciesSize += DependsNode.GetAllocatedSize();
|
|
}
|
|
|
|
if (bLogDetailed)
|
|
{
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("Dependency Node Count: %d"), CachedDependsNodes.Num());
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("Dependency Node Static Size: %" SIZE_T_FMT "k"), DependNodesSize / 1024);
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("Dependency Arrays Size: %" SIZE_T_FMT "k"), DependenciesSize / 1024);
|
|
}
|
|
|
|
SIZE_T PackageDataSize = CachedPackageData.Num() * (sizeof(FAssetPackageData) + sizeof(FAssetPackageData*));
|
|
for (const TPair<FName, FAssetPackageData*>& PackageDataPair : CachedPackageData)
|
|
{
|
|
PackageDataSize += PackageDataPair.Value->GetAllocatedSize();
|
|
}
|
|
|
|
SIZE_T TotalBytes = MapMemory + AssetDataSize + TagMemoryUsage.GetFixedSize() + TagMemoryUsage.GetLooseSize() + DependNodesSize + DependenciesSize + PackageDataSize + MapArrayMemory;
|
|
|
|
if (bLogDetailed)
|
|
{
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("PackageData Count: %d"), CachedPackageData.Num());
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("PackageData Static Size: %" SIZE_T_FMT "k"), PackageDataSize / 1024);
|
|
UE_LOG(LogAssetRegistry, Log, TEXT("Total State Size: %" SIZE_T_FMT "k"), TotalBytes / 1024);
|
|
}
|
|
|
|
return TotalBytes;
|
|
}
|
|
|
|
FDependsNode* FAssetRegistryState::ResolveRedirector(FDependsNode* InDependency,
|
|
TMap<FName, FAssetData*>& InAllowedAssets,
|
|
TMap<FDependsNode*, FDependsNode*>& InCache)
|
|
{
|
|
if (InCache.Contains(InDependency))
|
|
{
|
|
return InCache[InDependency];
|
|
}
|
|
|
|
FDependsNode* CurrentDependency = InDependency;
|
|
FDependsNode* Result = nullptr;
|
|
|
|
TSet<FName> EncounteredDependencies;
|
|
|
|
while (Result == nullptr)
|
|
{
|
|
checkSlow(CurrentDependency);
|
|
|
|
if (EncounteredDependencies.Contains(CurrentDependency->GetPackageName()))
|
|
{
|
|
break;
|
|
}
|
|
|
|
EncounteredDependencies.Add(CurrentDependency->GetPackageName());
|
|
|
|
if (CachedAssetsByPackageName.Contains(CurrentDependency->GetPackageName()))
|
|
{
|
|
// Get the list of assets contained in this package
|
|
TArray<FAssetData*, TInlineAllocator<1>>& Assets = CachedAssetsByPackageName[CurrentDependency->GetPackageName()];
|
|
|
|
for (FAssetData* Asset : Assets)
|
|
{
|
|
if (Asset->IsRedirector())
|
|
{
|
|
FDependsNode* ChainedRedirector = nullptr;
|
|
// This asset is a redirector, so we want to look at its dependencies and find the asset that it is redirecting to
|
|
CurrentDependency->IterateOverDependencies([&](FDependsNode* InDepends, UE::AssetRegistry::EDependencyCategory Category, UE::AssetRegistry::EDependencyProperty Property, bool bDuplicate) {
|
|
if (bDuplicate)
|
|
{
|
|
return; // Already looked at this dependency node
|
|
}
|
|
if (InAllowedAssets.Contains(InDepends->GetPackageName()))
|
|
{
|
|
// This asset is in the allowed asset list, so take this as the redirect target
|
|
Result = InDepends;
|
|
}
|
|
else if (CachedAssetsByPackageName.Contains(InDepends->GetPackageName()))
|
|
{
|
|
// This dependency isn't in the allowed list, but it is a valid asset in the registry.
|
|
// Because this is a redirector, this should mean that the redirector is pointing at ANOTHER
|
|
// redirector (or itself in some horrible situations) so we'll move to that node and try again
|
|
ChainedRedirector = InDepends;
|
|
}
|
|
}, UE::AssetRegistry::EDependencyCategory::Package);
|
|
|
|
if (ChainedRedirector)
|
|
{
|
|
// Found a redirector, break for loop
|
|
CurrentDependency = ChainedRedirector;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Result = CurrentDependency;
|
|
}
|
|
|
|
if (Result)
|
|
{
|
|
// We found an allowed asset from the original dependency node. We're finished!
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Result = CurrentDependency;
|
|
}
|
|
}
|
|
|
|
InCache.Add(InDependency, Result);
|
|
return Result;
|
|
}
|
|
|
|
template <typename KeyType>
|
|
void ShrinkMultimap(TMap<KeyType, TArray<FAssetData*> >& Map)
|
|
{
|
|
Map.Shrink();
|
|
for (auto& Pair : Map)
|
|
{
|
|
Pair.Value.Shrink();
|
|
}
|
|
};
|
|
|
|
void FAssetRegistryState::SetAssetDatas(TArrayView<FAssetData> AssetDatas, const FAssetRegistryLoadOptions& Options)
|
|
{
|
|
UE_CLOG(NumAssets != 0, LogAssetRegistry, Fatal, TEXT("Can only load into empty asset registry states. Load into temporary and append using InitializeFromExisting() instead."));
|
|
|
|
NumAssets = AssetDatas.Num();
|
|
|
|
auto SetPathCache = [&]()
|
|
{
|
|
CachedAssetsByObjectPath.Empty(AssetDatas.Num());
|
|
for (FAssetData& AssetData : AssetDatas)
|
|
{
|
|
CachedAssetsByObjectPath.Add(AssetData.ObjectPath, &AssetData);
|
|
}
|
|
ensure(NumAssets == CachedAssetsByObjectPath.Num());
|
|
};
|
|
|
|
// FAssetDatas sharing package name are very rare.
|
|
// Reserve up front and don't bother shrinking.
|
|
auto SetPackageNameCache = [&]()
|
|
{
|
|
CachedAssetsByPackageName.Empty(AssetDatas.Num());
|
|
for (FAssetData& AssetData : AssetDatas)
|
|
{
|
|
TArray<FAssetData*, TInlineAllocator<1>>& PackageAssets = CachedAssetsByPackageName.FindOrAdd(AssetData.PackageName);
|
|
PackageAssets.Add(&AssetData);
|
|
}
|
|
};
|
|
|
|
auto SetOtherCaches = [&]()
|
|
{
|
|
CachedAssetsByPath.Empty();
|
|
for (FAssetData& AssetData : AssetDatas)
|
|
{
|
|
TArray<FAssetData*>& PathAssets = CachedAssetsByPath.FindOrAdd(AssetData.PackagePath);
|
|
PathAssets.Add(&AssetData);
|
|
}
|
|
ShrinkMultimap(CachedAssetsByPath);
|
|
|
|
CachedAssetsByClass.Empty();
|
|
for (FAssetData& AssetData : AssetDatas)
|
|
{
|
|
TArray<FAssetData*>& ClassAssets = CachedAssetsByClass.FindOrAdd(AssetData.AssetClassPath);
|
|
ClassAssets.Add(&AssetData);
|
|
}
|
|
ShrinkMultimap(CachedAssetsByClass);
|
|
|
|
CachedAssetsByTag.Empty();
|
|
for (FAssetData& AssetData : AssetDatas)
|
|
{
|
|
for (const TPair<FName, FAssetTagValueRef>& Pair : AssetData.TagsAndValues)
|
|
{
|
|
TArray<FAssetData*>& TagAssets = CachedAssetsByTag.FindOrAdd(Pair.Key);
|
|
TagAssets.Add(&AssetData);
|
|
}
|
|
}
|
|
ShrinkMultimap(CachedAssetsByTag);
|
|
};
|
|
|
|
if (Options.ParallelWorkers <= 1)
|
|
{
|
|
SetPathCache();
|
|
SetPackageNameCache();
|
|
SetOtherCaches();
|
|
}
|
|
else
|
|
{
|
|
TFuture<void> Task1 = Async(EAsyncExecution::TaskGraph, [=](){ SetPathCache(); });
|
|
TFuture<void> Task2 = Async(EAsyncExecution::TaskGraph, [=](){ SetPackageNameCache(); });
|
|
SetOtherCaches();
|
|
Task1.Wait();
|
|
Task2.Wait();
|
|
}
|
|
}
|
|
|
|
void FAssetRegistryState::AddAssetData(FAssetData* AssetData)
|
|
{
|
|
|
|
FAssetData*& ExistingByObjectPath = CachedAssetsByObjectPath.FindOrAdd(AssetData->ObjectPath);
|
|
if (ExistingByObjectPath)
|
|
{
|
|
UE_LOG(LogAssetRegistry, Error, TEXT("AddAssetData called with ObjectPath %s which already exists. ")
|
|
TEXT("This will overwrite and leak the existing AssetData."), *AssetData->ObjectPath.ToString());
|
|
}
|
|
else
|
|
{
|
|
++NumAssets;
|
|
}
|
|
ExistingByObjectPath = AssetData;
|
|
|
|
TArray<FAssetData*, TInlineAllocator<1>>& PackageAssets = CachedAssetsByPackageName.FindOrAdd(AssetData->PackageName);
|
|
TArray<FAssetData*>& PathAssets = CachedAssetsByPath.FindOrAdd(AssetData->PackagePath);
|
|
TArray<FAssetData*>& ClassAssets = CachedAssetsByClass.FindOrAdd(AssetData->AssetClassPath);
|
|
|
|
PackageAssets.Add(AssetData);
|
|
PathAssets.Add(AssetData);
|
|
ClassAssets.Add(AssetData);
|
|
|
|
for (auto TagIt = AssetData->TagsAndValues.CreateConstIterator(); TagIt; ++TagIt)
|
|
{
|
|
FName Key = TagIt.Key();
|
|
|
|
TArray<FAssetData*>& TagAssets = CachedAssetsByTag.FindOrAdd(Key);
|
|
TagAssets.Add(AssetData);
|
|
}
|
|
}
|
|
|
|
void FAssetRegistryState::AddTagsToAssetData(const FSoftObjectPath& InObjectPath, FAssetDataTagMap&& InTagsAndValues)
|
|
{
|
|
if (InTagsAndValues.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FAssetData* AssetData = CachedAssetsByObjectPath.FindRef(InObjectPath.GetAssetPathName());
|
|
if (AssetData == nullptr)
|
|
{
|
|
UE_LOG(LogAssetRegistry, Error, TEXT("AddTagsToAssetData called with asset data that doesn't exist! Tags not added. ObjectPath: %s"), *InObjectPath.GetAssetPathName().ToString());
|
|
return;
|
|
}
|
|
|
|
// Update the tag cache map with the new tags.
|
|
for (auto TagIt : InTagsAndValues)
|
|
{
|
|
const FName FNameKey = TagIt.Key;
|
|
|
|
if (!AssetData->TagsAndValues.Contains(FNameKey))
|
|
{
|
|
TArray<FAssetData*>& NewTagAssets = CachedAssetsByTag.FindOrAdd(FNameKey);
|
|
NewTagAssets.Add(AssetData);
|
|
}
|
|
}
|
|
|
|
FAssetDataTagMap OldTags = AssetData->TagsAndValues.CopyMap();
|
|
OldTags.Append(MoveTemp(InTagsAndValues));
|
|
AssetData->TagsAndValues = FAssetDataTagMapSharedView(MoveTemp(OldTags));
|
|
}
|
|
|
|
|
|
void FAssetRegistryState::UpdateAssetData(const FAssetData& NewAssetData)
|
|
{
|
|
if (FAssetData* AssetData = CachedAssetsByObjectPath.FindRef(NewAssetData.ObjectPath))
|
|
{
|
|
UpdateAssetData(AssetData, NewAssetData);
|
|
}
|
|
}
|
|
|
|
void FAssetRegistryState::UpdateAssetData(FAssetData* AssetData, const FAssetData& NewAssetData)
|
|
{
|
|
// Determine if tags need to be remapped
|
|
bool bTagsChanged = AssetData->TagsAndValues.Num() != NewAssetData.TagsAndValues.Num();
|
|
|
|
// If the old and new asset data has the same number of tags, see if any are different (its ok if values are different)
|
|
if (!bTagsChanged)
|
|
{
|
|
for (auto TagIt = AssetData->TagsAndValues.CreateConstIterator(); TagIt; ++TagIt)
|
|
{
|
|
if (!NewAssetData.TagsAndValues.Contains(TagIt.Key()))
|
|
{
|
|
bTagsChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update ObjectPath
|
|
if (AssetData->ObjectPath != NewAssetData.ObjectPath)
|
|
{
|
|
int32 NumRemoved = CachedAssetsByObjectPath.Remove(AssetData->ObjectPath);
|
|
check(NumRemoved <= 1);
|
|
if (NumRemoved == 0)
|
|
{
|
|
UE_LOG(LogAssetRegistry, Error, TEXT("UpdateAssetData called on AssetData %s that is not present in the AssetRegistry."),
|
|
*AssetData->ObjectPath.ToString());
|
|
}
|
|
NumAssets -= NumRemoved;
|
|
FAssetData*& Existing = CachedAssetsByObjectPath.FindOrAdd(NewAssetData.ObjectPath, AssetData);
|
|
if (Existing)
|
|
{
|
|
UE_LOG(LogAssetRegistry, Error, TEXT("UpdateAssetData called with a change in ObjectPath from Old=\"%s\" to New=\"%s\", ")
|
|
TEXT("but the new ObjectPath is already present with another AssetData. This will overwrite and leak the existing AssetData."),
|
|
*AssetData->ObjectPath.ToString(), *NewAssetData.ObjectPath.ToString());
|
|
}
|
|
else
|
|
{
|
|
++NumAssets;
|
|
}
|
|
Existing = AssetData;
|
|
}
|
|
|
|
// Update PackageName
|
|
if (AssetData->PackageName != NewAssetData.PackageName)
|
|
{
|
|
TArray<FAssetData*, TInlineAllocator<1>>& NewPackageAssets = CachedAssetsByPackageName.FindOrAdd(NewAssetData.PackageName);
|
|
TArray<FAssetData*, TInlineAllocator<1>>* OldPackageAssets = CachedAssetsByPackageName.Find(AssetData->PackageName);
|
|
|
|
OldPackageAssets->Remove(AssetData);
|
|
NewPackageAssets.Add(AssetData);
|
|
}
|
|
|
|
// Update PackagePath
|
|
if (AssetData->PackagePath != NewAssetData.PackagePath)
|
|
{
|
|
TArray<FAssetData*>& NewPathAssets = CachedAssetsByPath.FindOrAdd(NewAssetData.PackagePath);
|
|
TArray<FAssetData*>* OldPathAssets = CachedAssetsByPath.Find(AssetData->PackagePath);
|
|
|
|
OldPathAssets->Remove(AssetData);
|
|
NewPathAssets.Add(AssetData);
|
|
}
|
|
|
|
// Update AssetClass
|
|
if (AssetData->AssetClassPath != NewAssetData.AssetClassPath)
|
|
{
|
|
TArray<FAssetData*>& NewClassAssets = CachedAssetsByClass.FindOrAdd(NewAssetData.AssetClassPath);
|
|
TArray<FAssetData*>* OldClassAssets = CachedAssetsByClass.Find(AssetData->AssetClassPath);
|
|
|
|
OldClassAssets->Remove(AssetData);
|
|
NewClassAssets.Add(AssetData);
|
|
}
|
|
|
|
// Update Tags
|
|
if (bTagsChanged)
|
|
{
|
|
for (auto TagIt = AssetData->TagsAndValues.CreateConstIterator(); TagIt; ++TagIt)
|
|
{
|
|
const FName FNameKey = TagIt.Key();
|
|
|
|
if (!NewAssetData.TagsAndValues.Contains(FNameKey))
|
|
{
|
|
TArray<FAssetData*>* OldTagAssets = CachedAssetsByTag.Find(FNameKey);
|
|
OldTagAssets->RemoveSingleSwap(AssetData);
|
|
}
|
|
}
|
|
|
|
for (auto TagIt = NewAssetData.TagsAndValues.CreateConstIterator(); TagIt; ++TagIt)
|
|
{
|
|
const FName FNameKey = TagIt.Key();
|
|
|
|
if (!AssetData->TagsAndValues.Contains(FNameKey))
|
|
{
|
|
TArray<FAssetData*>& NewTagAssets = CachedAssetsByTag.FindOrAdd(FNameKey);
|
|
NewTagAssets.Add(AssetData);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy in new values
|
|
*AssetData = NewAssetData;
|
|
}
|
|
|
|
bool FAssetRegistryState::UpdateAssetDataPackageFlags(FName PackageName, uint32 PackageFlags)
|
|
{
|
|
if (const TArray<FAssetData*, TInlineAllocator<1>>* Assets = CachedAssetsByPackageName.Find(PackageName))
|
|
{
|
|
for (FAssetData* Asset : *Assets)
|
|
{
|
|
Asset->PackageFlags = PackageFlags;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FAssetRegistryState::RemoveAssetData(FAssetData* AssetData, bool bRemoveDependencyData, bool& bOutRemovedAssetData, bool& bOutRemovedPackageData)
|
|
{
|
|
bOutRemovedAssetData = false;
|
|
bOutRemovedPackageData = false;
|
|
|
|
if (!ensure(AssetData))
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 NumRemoved = CachedAssetsByObjectPath.Remove(AssetData->ObjectPath);
|
|
check(NumRemoved <= 1);
|
|
if (NumRemoved == 0)
|
|
{
|
|
UE_LOG(LogAssetRegistry, Error, TEXT("RemoveAssetData called on AssetData %s that is not present in the AssetRegistry."),
|
|
*AssetData->ObjectPath.ToString());
|
|
return;
|
|
}
|
|
|
|
TArray<FAssetData*, TInlineAllocator<1>>* OldPackageAssets = CachedAssetsByPackageName.Find(AssetData->PackageName);
|
|
TArray<FAssetData*>* OldPathAssets = CachedAssetsByPath.Find(AssetData->PackagePath);
|
|
TArray<FAssetData*>* OldClassAssets = CachedAssetsByClass.Find(AssetData->AssetClassPath);
|
|
OldPackageAssets->RemoveSingleSwap(AssetData);
|
|
OldPathAssets->RemoveSingleSwap(AssetData);
|
|
OldClassAssets->RemoveSingleSwap(AssetData);
|
|
|
|
for (auto TagIt = AssetData->TagsAndValues.CreateConstIterator(); TagIt; ++TagIt)
|
|
{
|
|
TArray<FAssetData*>* OldTagAssets = CachedAssetsByTag.Find(TagIt.Key());
|
|
OldTagAssets->RemoveSingleSwap(AssetData);
|
|
}
|
|
|
|
// Only remove dependencies and package data if there are no other known assets in the package
|
|
if (OldPackageAssets->Num() == 0)
|
|
{
|
|
CachedAssetsByPackageName.Remove(AssetData->PackageName);
|
|
|
|
// We need to update the cached dependencies references cache so that they know we no
|
|
// longer exist and so don't reference them.
|
|
if (bRemoveDependencyData)
|
|
{
|
|
RemoveDependsNode(AssetData->PackageName);
|
|
}
|
|
|
|
// Remove the package data as well
|
|
RemovePackageData(AssetData->PackageName);
|
|
bOutRemovedPackageData = true;
|
|
}
|
|
|
|
// if the assets were preallocated in a block, we can't delete them one at a time, only the whole chunk in the destructor
|
|
if (PreallocatedAssetDataBuffers.Num() == 0)
|
|
{
|
|
delete AssetData;
|
|
}
|
|
NumAssets--;
|
|
bOutRemovedAssetData = true;
|
|
}
|
|
|
|
FDependsNode* FAssetRegistryState::FindDependsNode(const FAssetIdentifier& Identifier) const
|
|
{
|
|
FDependsNode*const* FoundNode = CachedDependsNodes.Find(Identifier);
|
|
if (FoundNode)
|
|
{
|
|
return *FoundNode;
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
FDependsNode* FAssetRegistryState::CreateOrFindDependsNode(const FAssetIdentifier& Identifier)
|
|
{
|
|
FDependsNode* FoundNode = FindDependsNode(Identifier);
|
|
if (FoundNode)
|
|
{
|
|
return FoundNode;
|
|
}
|
|
|
|
FDependsNode* NewNode = new FDependsNode(Identifier);
|
|
NumDependsNodes++;
|
|
CachedDependsNodes.Add(Identifier, NewNode);
|
|
|
|
return NewNode;
|
|
}
|
|
|
|
bool FAssetRegistryState::RemoveDependsNode(const FAssetIdentifier& Identifier)
|
|
{
|
|
FDependsNode** NodePtr = CachedDependsNodes.Find(Identifier);
|
|
|
|
if (NodePtr != nullptr)
|
|
{
|
|
FDependsNode* Node = *NodePtr;
|
|
if (Node != nullptr)
|
|
{
|
|
TArray<FDependsNode*> DependencyNodes;
|
|
Node->GetDependencies(DependencyNodes);
|
|
|
|
// Remove the reference to this node from all dependencies
|
|
for (FDependsNode* DependencyNode : DependencyNodes)
|
|
{
|
|
DependencyNode->RemoveReferencer(Node);
|
|
}
|
|
|
|
TArray<FDependsNode*> ReferencerNodes;
|
|
Node->GetReferencers(ReferencerNodes);
|
|
|
|
// Remove the reference to this node from all referencers
|
|
for (FDependsNode* ReferencerNode : ReferencerNodes)
|
|
{
|
|
ReferencerNode->RemoveDependency(Node);
|
|
}
|
|
|
|
// Remove the node and delete it
|
|
CachedDependsNodes.Remove(Identifier);
|
|
NumDependsNodes--;
|
|
|
|
// if the depends nodes were preallocated in a block, we can't delete them one at a time, only the whole chunk in the destructor
|
|
if (PreallocatedDependsNodeDataBuffers.Num() == 0)
|
|
{
|
|
delete Node;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FAssetRegistryState::GetPrimaryAssetsIds(TSet<FPrimaryAssetId>& OutPrimaryAssets) const
|
|
{
|
|
for (TMap<FName, FAssetData*>::ElementType Element : CachedAssetsByObjectPath)
|
|
{
|
|
if (Element.Value)
|
|
{
|
|
FPrimaryAssetId PrimaryAssetId = Element.Value->GetPrimaryAssetId();
|
|
if (PrimaryAssetId.IsValid())
|
|
{
|
|
OutPrimaryAssets.Add(PrimaryAssetId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const FAssetPackageData* FAssetRegistryState::GetAssetPackageData(FName PackageName) const
|
|
{
|
|
FAssetPackageData* const* FoundData = CachedPackageData.Find(PackageName);
|
|
if (FoundData)
|
|
{
|
|
return *FoundData;
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
FAssetPackageData* FAssetRegistryState::CreateOrGetAssetPackageData(FName PackageName)
|
|
{
|
|
FAssetPackageData** FoundData = CachedPackageData.Find(PackageName);
|
|
if (FoundData)
|
|
{
|
|
return *FoundData;
|
|
}
|
|
|
|
FAssetPackageData* NewData = new FAssetPackageData();
|
|
NumPackageData++;
|
|
CachedPackageData.Add(PackageName, NewData);
|
|
|
|
return NewData;
|
|
}
|
|
|
|
bool FAssetRegistryState::RemovePackageData(FName PackageName)
|
|
{
|
|
FAssetPackageData** DataPtr = CachedPackageData.Find(PackageName);
|
|
|
|
if (DataPtr != nullptr)
|
|
{
|
|
FAssetPackageData* Data = *DataPtr;
|
|
if (Data != nullptr)
|
|
{
|
|
CachedPackageData.Remove(PackageName);
|
|
NumPackageData--;
|
|
|
|
// if the package data was preallocated in a block, we can't delete them one at a time, only the whole chunk in the destructor
|
|
if (PreallocatedPackageDataBuffers.Num() == 0)
|
|
{
|
|
delete Data;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FAssetRegistryState::IsFilterValid(const FARCompiledFilter& Filter)
|
|
{
|
|
return UE::AssetRegistry::Utils::IsFilterValid(Filter);
|
|
}
|
|
|
|
const TArray<const FAssetData*>& FAssetRegistryState::GetAssetsByClassName(const FName ClassName) const
|
|
{
|
|
static TArray<const FAssetData*> InvalidArray;
|
|
for (const TPair< FTopLevelAssetPath, TArray<FAssetData*> >& Pair : CachedAssetsByClass)
|
|
{
|
|
if (Pair.Key.GetAssetName() == ClassName)
|
|
{
|
|
return reinterpret_cast<const TArray<const FAssetData*>&>(Pair.Value);
|
|
}
|
|
}
|
|
return InvalidArray;
|
|
}
|
|
|
|
namespace UE::AssetRegistry::Utils
|
|
{
|
|
|
|
bool IsFilterValid(const FARCompiledFilter& Filter)
|
|
{
|
|
if (Filter.PackageNames.Contains(NAME_None) ||
|
|
Filter.PackagePaths.Contains(NAME_None) ||
|
|
Filter.ObjectPaths.Contains(NAME_None) ||
|
|
Filter.ClassPaths.Contains(FTopLevelAssetPath()) ||
|
|
Filter.TagsAndValues.Contains(NAME_None)
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
#if ASSET_REGISTRY_STATE_DUMPING_ENABLED
|
|
namespace UE
|
|
{
|
|
namespace AssetRegistry
|
|
{
|
|
void PropertiesToString(EDependencyProperty Properties, FStringBuilderBase& Builder, EDependencyCategory CategoryFilter)
|
|
{
|
|
bool bFirst = true;
|
|
auto AppendPropertyName = [&Properties, &Builder, &bFirst](EDependencyProperty TestProperty, const TCHAR* NameWith, const TCHAR* NameWithout)
|
|
{
|
|
if (!bFirst)
|
|
{
|
|
Builder.Append(TEXT(","));
|
|
}
|
|
if (!!(Properties & TestProperty))
|
|
{
|
|
Builder.Append(NameWith);
|
|
}
|
|
else
|
|
{
|
|
Builder.Append(NameWithout);
|
|
}
|
|
bFirst = false;
|
|
};
|
|
if (!!(CategoryFilter & EDependencyCategory::Package))
|
|
{
|
|
AppendPropertyName(EDependencyProperty::Hard, TEXT("Hard"), TEXT("Soft"));
|
|
AppendPropertyName(EDependencyProperty::Game, TEXT("Game"), TEXT("EditorOnly"));
|
|
AppendPropertyName(EDependencyProperty::Build, TEXT("Build"), TEXT("NotBuild"));
|
|
}
|
|
if (!!(CategoryFilter & EDependencyCategory::Manage))
|
|
{
|
|
AppendPropertyName(EDependencyProperty::Direct, TEXT("Direct"), TEXT("Indirect"));
|
|
}
|
|
static_assert((EDependencyProperty::PackageMask | EDependencyProperty::SearchableNameMask | EDependencyProperty::ManageMask) == EDependencyProperty::AllMask,
|
|
"Need to handle new flags in this function");
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename MapType>
|
|
static void PrintAssetDataMap(FString Name, const MapType& AssetMap, TStringBuilder<16>& PageBuffer, const TFunctionRef<void()>& AddLine)
|
|
{
|
|
PageBuffer.Appendf(TEXT("--- Begin %s ---"), *Name);
|
|
AddLine();
|
|
|
|
TArray<typename MapType::KeyType> Keys;
|
|
AssetMap.GenerateKeyArray(Keys);
|
|
|
|
struct FKeyTypeCompare
|
|
{
|
|
FORCEINLINE bool operator()(const typename MapType::KeyType& A, const typename MapType::KeyType& B) const
|
|
{
|
|
return A.Compare(B) < 0;
|
|
}
|
|
};
|
|
Keys.Sort(FKeyTypeCompare());
|
|
|
|
TArray<FAssetData*> Items;
|
|
Items.Reserve(1024);
|
|
|
|
int32 ValidCount = 0;
|
|
for (const typename MapType::KeyType& Key : Keys)
|
|
{
|
|
const auto& AssetArray = AssetMap.FindChecked(Key);
|
|
if (AssetArray.Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
++ValidCount;
|
|
|
|
Items.Reset();
|
|
Items.Append(AssetArray);
|
|
Items.Sort([](const FAssetData& A, const FAssetData& B)
|
|
{ return A.ObjectPath.ToString() < B.ObjectPath.ToString(); }
|
|
);
|
|
|
|
PageBuffer.Append(TEXT(" "));
|
|
Key.AppendString(PageBuffer);
|
|
PageBuffer.Appendf(TEXT(" : %d item(s)"), Items.Num());
|
|
AddLine();
|
|
for (const FAssetData* Data : Items)
|
|
{
|
|
PageBuffer.Append(TEXT(" "));
|
|
Data->ObjectPath.AppendString(PageBuffer);
|
|
AddLine();
|
|
}
|
|
}
|
|
|
|
PageBuffer.Appendf(TEXT("--- End %s : %d entries ---"), *Name, ValidCount);
|
|
AddLine();
|
|
};
|
|
|
|
void FAssetRegistryState::Dump(const TArray<FString>& Arguments, TArray<FString>& OutPages, int32 LinesPerPage) const
|
|
{
|
|
int32 ExpectedNumLines = 14 + CachedAssetsByObjectPath.Num() * 5 + CachedDependsNodes.Num() + CachedPackageData.Num();
|
|
const int32 EstimatedLinksPerNode = 10*2; // Each dependency shows up once as a dependency and once as a reference
|
|
const int32 EstimatedCharactersPerLine = 100;
|
|
const bool bDumpDependencyDetails = Arguments.Contains(TEXT("DependencyDetails"));
|
|
if (bDumpDependencyDetails)
|
|
{
|
|
ExpectedNumLines += CachedDependsNodes.Num() * (3 + EstimatedLinksPerNode);
|
|
}
|
|
LinesPerPage = FMath::Max(LinesPerPage, 1);
|
|
const int32 ExpectedNumPages = ExpectedNumLines / LinesPerPage;
|
|
const int32 PageEndSearchLength = LinesPerPage / 20;
|
|
const uint32 HashStartValue = MAX_uint32 - 49979693; // Pick a large starting value to bias against picking empty string
|
|
const uint32 HashMultiplier = 67867967;
|
|
TStringBuilder<16> PageBuffer;
|
|
TStringBuilder<16> OverflowText;
|
|
|
|
OutPages.Reserve(ExpectedNumPages);
|
|
PageBuffer.AddUninitialized(LinesPerPage * EstimatedCharactersPerLine);// TODO: Add Reserve function to TStringBuilder
|
|
PageBuffer.Reset();
|
|
OverflowText.AddUninitialized(PageEndSearchLength * EstimatedCharactersPerLine);
|
|
OverflowText.Reset();
|
|
int32 NumLinesInPage = 0;
|
|
const int32 LineTerminatorLen = TCString<TCHAR>::Strlen(LINE_TERMINATOR);
|
|
|
|
auto FinishPage = [&PageBuffer, &NumLinesInPage, HashStartValue, HashMultiplier, PageEndSearchLength, &OutPages, &OverflowText, LineTerminatorLen](bool bLastPage)
|
|
{
|
|
int32 PageEndIndex = PageBuffer.Len();
|
|
const TCHAR* BufferEnd = PageBuffer.GetData() + PageEndIndex;
|
|
int32 NumOverflowLines = 0;
|
|
// We want to facilitate diffing dumps between two different versions that should be similar, but naively breaking up the dump into pages makes this difficult
|
|
// because after one missing or added line, every page from that point on will be offset and therefore different, making false positive differences
|
|
// To make pages after one missing or added line the same, we look for a good page ending based on the text of all the lines near the end of the current page
|
|
// By choosing specific-valued texts as page breaks, we will usually randomly get lucky and have the two diffs pick the same line for the end of the page
|
|
if (!bLastPage && NumLinesInPage > PageEndSearchLength)
|
|
{
|
|
const TCHAR* WinningLineEnd = BufferEnd;
|
|
uint32 WinningLineValue = 0;
|
|
int32 WinningSearchIndex = 0;
|
|
const TCHAR* LineEnd = BufferEnd;
|
|
for (int32 SearchIndex = 0; SearchIndex < PageEndSearchLength; ++SearchIndex)
|
|
{
|
|
uint32 LineValue = HashStartValue;
|
|
const TCHAR* LineStart = LineEnd;
|
|
while (LineStart[-LineTerminatorLen] != LINE_TERMINATOR[0] || TCString<TCHAR>::Strncmp(LINE_TERMINATOR, LineStart - LineTerminatorLen, LineTerminatorLen) != 0)
|
|
{
|
|
--LineStart;
|
|
LineValue = LineValue * HashMultiplier + static_cast<uint32>(TChar<TCHAR>::ToLower(*LineStart));
|
|
}
|
|
if (SearchIndex == 0 || LineValue < WinningLineValue) // We arbitrarily choose the smallest hash as the winning value
|
|
{
|
|
WinningLineValue = LineValue;
|
|
WinningLineEnd = LineEnd;
|
|
WinningSearchIndex = SearchIndex;
|
|
}
|
|
LineEnd = LineStart - LineTerminatorLen;
|
|
}
|
|
if (WinningLineEnd != BufferEnd)
|
|
{
|
|
PageEndIndex = UE_PTRDIFF_TO_INT32(WinningLineEnd - PageBuffer.GetData());
|
|
NumOverflowLines = WinningSearchIndex;
|
|
}
|
|
}
|
|
|
|
OutPages.Emplace(PageEndIndex, PageBuffer.GetData());
|
|
if (PageEndIndex != PageBuffer.Len())
|
|
{
|
|
PageEndIndex += LineTerminatorLen; // Skip the newline
|
|
OverflowText.Reset();
|
|
OverflowText.Append(PageBuffer.GetData() + PageEndIndex, PageBuffer.Len() - PageEndIndex);
|
|
PageBuffer.Reset();
|
|
PageBuffer.Append(OverflowText);
|
|
PageBuffer.Append(LINE_TERMINATOR);
|
|
NumLinesInPage = NumOverflowLines;
|
|
}
|
|
else
|
|
{
|
|
PageBuffer.Reset();
|
|
NumLinesInPage = 0;
|
|
}
|
|
};
|
|
auto AddLine = [&PageBuffer, LinesPerPage, &NumLinesInPage, &FinishPage, &OutPages]()
|
|
{
|
|
if (LinesPerPage == 1)
|
|
{
|
|
OutPages.Emplace(PageBuffer.Len(), PageBuffer.GetData());
|
|
PageBuffer.Reset();
|
|
}
|
|
else
|
|
{
|
|
++NumLinesInPage;
|
|
if (NumLinesInPage != LinesPerPage)
|
|
{
|
|
PageBuffer.Append(LINE_TERMINATOR);
|
|
}
|
|
else
|
|
{
|
|
FinishPage(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (Arguments.Contains(TEXT("ObjectPath")))
|
|
{
|
|
PageBuffer.Append(TEXT("--- Begin CachedAssetsByObjectPath ---"));
|
|
AddLine();
|
|
|
|
TArray<FName> Keys;
|
|
CachedAssetsByObjectPath.GenerateKeyArray(Keys);
|
|
Keys.Sort(FNameLexicalLess());
|
|
|
|
for (FName ObjectPath : Keys)
|
|
{
|
|
PageBuffer.Append(TEXT(" "));
|
|
ObjectPath.AppendString(PageBuffer);
|
|
AddLine();
|
|
}
|
|
|
|
PageBuffer.Appendf(TEXT("--- End CachedAssetsByObjectPath : %d entries ---"), CachedAssetsByObjectPath.Num());
|
|
AddLine();
|
|
}
|
|
|
|
if (Arguments.Contains(TEXT("PackageName")))
|
|
{
|
|
PrintAssetDataMap(TEXT("CachedAssetsByPackageName"), CachedAssetsByPackageName, PageBuffer, AddLine);
|
|
}
|
|
|
|
if (Arguments.Contains(TEXT("Path")))
|
|
{
|
|
PrintAssetDataMap(TEXT("CachedAssetsByPath"), CachedAssetsByPath, PageBuffer, AddLine);
|
|
}
|
|
|
|
if (Arguments.Contains(TEXT("Class")))
|
|
{
|
|
PrintAssetDataMap(TEXT("CachedAssetsByClass"), CachedAssetsByClass, PageBuffer, AddLine);
|
|
}
|
|
|
|
if (Arguments.Contains(TEXT("Tag")))
|
|
{
|
|
PrintAssetDataMap(TEXT("CachedAssetsByTag"), CachedAssetsByTag, PageBuffer, AddLine);
|
|
}
|
|
|
|
if (Arguments.Contains(TEXT("Dependencies")) && !bDumpDependencyDetails)
|
|
{
|
|
PageBuffer.Appendf(TEXT("--- Begin CachedDependsNodes ---"));
|
|
AddLine();
|
|
|
|
TArray<FDependsNode*> Nodes;
|
|
CachedDependsNodes.GenerateValueArray(Nodes);
|
|
Nodes.Sort([](const FDependsNode& A, const FDependsNode& B)
|
|
{ return A.GetIdentifier().ToString() < B.GetIdentifier().ToString(); }
|
|
);
|
|
|
|
for (const FDependsNode* Node : Nodes)
|
|
{
|
|
PageBuffer.Append(TEXT(" "));
|
|
Node->GetIdentifier().AppendString(PageBuffer);
|
|
PageBuffer.Appendf(TEXT(" : %d connection(s)"), Node->GetConnectionCount());
|
|
AddLine();
|
|
}
|
|
|
|
PageBuffer.Appendf(TEXT("--- End CachedDependsNodes : %d entries ---"), CachedDependsNodes.Num());
|
|
AddLine();
|
|
}
|
|
|
|
if (bDumpDependencyDetails)
|
|
{
|
|
using namespace UE::AssetRegistry;
|
|
PageBuffer.Append(TEXT("--- Begin CachedDependsNodes ---"));
|
|
AddLine();
|
|
|
|
auto SortByAssetID = [](const FDependsNode& A, const FDependsNode& B) { return A.GetIdentifier().ToString() < B.GetIdentifier().ToString(); };
|
|
TArray<FDependsNode*> Nodes;
|
|
CachedDependsNodes.GenerateValueArray(Nodes);
|
|
Nodes.Sort(SortByAssetID);
|
|
|
|
if (Arguments.Contains(TEXT("LegacyDependencies")))
|
|
{
|
|
EDependencyCategory CategoryTypes[] = { EDependencyCategory::Package, EDependencyCategory::Package,EDependencyCategory::SearchableName,EDependencyCategory::Manage, EDependencyCategory::Manage, EDependencyCategory::None };
|
|
EDependencyQuery CategoryQueries[] = { EDependencyQuery::Hard, EDependencyQuery::Soft, EDependencyQuery::NoRequirements, EDependencyQuery::Direct, EDependencyQuery::Indirect, EDependencyQuery::NoRequirements };
|
|
const TCHAR* CategoryNames[] = { TEXT("Hard"), TEXT("Soft"), TEXT("SearchableName"), TEXT("HardManage"), TEXT("SoftManage"), TEXT("References") };
|
|
const int NumCategories = UE_ARRAY_COUNT(CategoryTypes);
|
|
check(NumCategories == UE_ARRAY_COUNT(CategoryNames) && NumCategories == UE_ARRAY_COUNT(CategoryQueries));
|
|
|
|
TArray<FDependsNode*> Links;
|
|
for (const FDependsNode* Node : Nodes)
|
|
{
|
|
PageBuffer.Append(TEXT(" "));
|
|
Node->GetIdentifier().AppendString(PageBuffer);
|
|
AddLine();
|
|
for (int CategoryIndex = 0; CategoryIndex < NumCategories; ++CategoryIndex)
|
|
{
|
|
EDependencyCategory CategoryType = CategoryTypes[CategoryIndex];
|
|
EDependencyQuery CategoryQuery = CategoryQueries[CategoryIndex];
|
|
const TCHAR* CategoryName = CategoryNames[CategoryIndex];
|
|
Links.Reset();
|
|
if (CategoryType != EDependencyCategory::None)
|
|
{
|
|
Node->GetDependencies(Links, CategoryType, CategoryQuery);
|
|
}
|
|
else
|
|
{
|
|
Node->GetReferencers(Links);
|
|
}
|
|
if (Links.Num() > 0)
|
|
{
|
|
PageBuffer.Appendf(TEXT(" %s"), CategoryName);
|
|
AddLine();
|
|
Links.Sort(SortByAssetID);
|
|
for (FDependsNode* LinkNode : Links)
|
|
{
|
|
PageBuffer.Append(TEXT(" "));
|
|
LinkNode->GetIdentifier().AppendString(PageBuffer);
|
|
AddLine();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EDependencyCategory CategoryTypes[] = { EDependencyCategory::Package, EDependencyCategory::SearchableName,EDependencyCategory::Manage, EDependencyCategory::None };
|
|
const TCHAR* CategoryNames[] = { TEXT("Package"), TEXT("SearchableName"), TEXT("Manage"), TEXT("References") };
|
|
const int NumCategories = UE_ARRAY_COUNT(CategoryTypes);
|
|
check(NumCategories == UE_ARRAY_COUNT(CategoryNames));
|
|
|
|
TArray<FAssetDependency> Dependencies;
|
|
TArray<FDependsNode*> References;
|
|
for (const FDependsNode* Node : Nodes)
|
|
{
|
|
PageBuffer.Append(TEXT(" "));
|
|
Node->GetIdentifier().AppendString(PageBuffer);
|
|
AddLine();
|
|
for (int CategoryIndex = 0; CategoryIndex < NumCategories; ++CategoryIndex)
|
|
{
|
|
EDependencyCategory CategoryType = CategoryTypes[CategoryIndex];
|
|
const TCHAR* CategoryName = CategoryNames[CategoryIndex];
|
|
if (CategoryType != EDependencyCategory::None)
|
|
{
|
|
Dependencies.Reset();
|
|
Node->GetDependencies(Dependencies, CategoryType);
|
|
if (Dependencies.Num() > 0)
|
|
{
|
|
PageBuffer.Appendf(TEXT(" %s"), CategoryName);
|
|
AddLine();
|
|
Dependencies.Sort([](const FAssetDependency& A, const FAssetDependency& B) { return A.AssetId.ToString() < B.AssetId.ToString(); });
|
|
for (const FAssetDependency& AssetDependency : Dependencies)
|
|
{
|
|
PageBuffer.Append(TEXT(" "));
|
|
AssetDependency.AssetId.AppendString(PageBuffer);
|
|
PageBuffer.Append(TEXT("\t\t{"));
|
|
PropertiesToString(AssetDependency.Properties, PageBuffer, AssetDependency.Category);
|
|
PageBuffer.Append(TEXT("}"));
|
|
AddLine();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
References.Reset();
|
|
Node->GetReferencers(References);
|
|
if (References.Num() > 0)
|
|
{
|
|
PageBuffer.Appendf(TEXT(" %s"), CategoryName);
|
|
AddLine();
|
|
References.Sort(SortByAssetID);
|
|
for (const FDependsNode* Reference : References)
|
|
{
|
|
PageBuffer.Append(TEXT(" "));
|
|
Reference->GetIdentifier().AppendString(PageBuffer);
|
|
AddLine();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PageBuffer.Appendf(TEXT("--- End CachedDependsNodes : %d entries ---"), CachedDependsNodes.Num());
|
|
AddLine();
|
|
}
|
|
if (Arguments.Contains(TEXT("PackageData")))
|
|
{
|
|
PageBuffer.Append(TEXT("--- Begin CachedPackageData ---"));
|
|
AddLine();
|
|
|
|
TArray<FName> Keys;
|
|
CachedPackageData.GenerateKeyArray(Keys);
|
|
Keys.Sort(FNameLexicalLess());
|
|
|
|
for (const FName& Key : Keys)
|
|
{
|
|
const FAssetPackageData* PackageData = CachedPackageData.FindChecked(Key);
|
|
PageBuffer.Append(TEXT(" "));
|
|
Key.AppendString(PageBuffer);
|
|
PageBuffer.Append(TEXT(" : "));
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
PageBuffer.Append(PackageData->PackageGuid.ToString()); // TODO: Add AppendString for Guid
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
PageBuffer.Appendf(TEXT(" : %d bytes"), PackageData->DiskSize);
|
|
AddLine();
|
|
}
|
|
|
|
PageBuffer.Appendf(TEXT("--- End CachedPackageData : %d entries ---"), CachedPackageData.Num());
|
|
AddLine();
|
|
}
|
|
|
|
if (PageBuffer.Len() > 0)
|
|
{
|
|
if (LinesPerPage == 1)
|
|
{
|
|
AddLine();
|
|
}
|
|
else
|
|
{
|
|
FinishPage(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // ASSET_REGISTRY_STATE_DUMPING_ENABLED
|