// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Algo/Find.h" #include "Containers/StringView.h" #include "IAssetRegistry.h" #include "Misc/PackageName.h" /* This class has marked all of its functions const and its variables mutable so that CookOnTheFlyServer can use its functions from const functions */ struct FPackageNameCache { bool HasCacheForPackageName(const FName& PackageName) const; FString GetCachedStandardFileNameString(const UPackage* Package) const; FName GetCachedStandardFileName(const FName& PackageName) const; FName GetCachedStandardFileName(const UPackage* Package) const; const FName* GetCachedPackageNameFromStandardFileName(const FName& NormalizedFileName, bool bExactMatchRequired = true, FName* FoundFileName = nullptr) const; void ClearPackageFileNameCache(IAssetRegistry* InAssetRegistry) const; bool ClearPackageFileNameCacheForPackage(const UPackage* Package) const; bool ClearPackageFileNameCacheForPackage(const FName& PackageName) const; void AppendCacheResults(TArray>&& PackageToStandardFileNames) const; bool CalculateCacheData(FName PackageName, FString& OutStandardFilename, FName& OutStandardFileFName) const; bool ContainsPackageName(FName PackageName) const; void SetAssetRegistry(IAssetRegistry* InAssetRegistry) const; IAssetRegistry* GetAssetRegistry() const; /** Normalize the given FileName for use in looking up the cached data associated with the FileName. This normalization is equivalent to FPaths::MakeStandardFilename */ static FName GetStandardFileName(const FName& FileName); static FName GetStandardFileName(const FStringView& FileName); private: struct FCachedPackageFilename { FCachedPackageFilename(FString&& InStandardFilename, FName InStandardFileFName) : StandardFileNameString(MoveTemp(InStandardFilename)), StandardFileName(InStandardFileFName) { } FCachedPackageFilename(const FCachedPackageFilename& In) = default; FCachedPackageFilename(FCachedPackageFilename&& In) : StandardFileNameString(MoveTemp(In.StandardFileNameString)), StandardFileName(In.StandardFileName) { } FString StandardFileNameString; FName StandardFileName; }; bool DoesPackageExist(const FName& PackageName, FString* OutFilename) const; const FCachedPackageFilename& Cache(const FName& PackageName) const; mutable IAssetRegistry* AssetRegistry = nullptr; mutable TMap PackageFilenameCache; // filename cache (only process the string operations once) mutable TMap PackageFilenameToPackageFNameCache; }; inline FName FPackageNameCache::GetCachedStandardFileName(const FName& PackageName) const { return Cache(PackageName).StandardFileName; } inline bool FPackageNameCache::HasCacheForPackageName(const FName& PackageName) const { return PackageFilenameCache.Find(PackageName) != nullptr; } inline FString FPackageNameCache::GetCachedStandardFileNameString(const UPackage* Package) const { // check( Package->GetName() == Package->GetFName().ToString() ); return Cache(Package->GetFName()).StandardFileNameString; } inline FName FPackageNameCache::GetCachedStandardFileName(const UPackage* Package) const { // check( Package->GetName() == Package->GetFName().ToString() ); return Cache(Package->GetFName()).StandardFileName; } inline bool FPackageNameCache::ClearPackageFileNameCacheForPackage(const UPackage* Package) const { return ClearPackageFileNameCacheForPackage(Package->GetFName()); } inline void FPackageNameCache::AppendCacheResults(TArray>&& PackageToStandardFileNames) const { check(IsInGameThread()); for (auto& Entry: PackageToStandardFileNames) { FName PackageName = Entry.Get<0>(); FString& StandardFilename = Entry.Get<1>(); FName StandardFileFName(*StandardFilename); PackageFilenameToPackageFNameCache.Add(StandardFileFName, PackageName); PackageFilenameCache.Emplace(PackageName, FCachedPackageFilename(MoveTemp(StandardFilename), StandardFileFName)); } } inline bool FPackageNameCache::ClearPackageFileNameCacheForPackage(const FName& PackageName) const { check(IsInGameThread()); return PackageFilenameCache.Remove(PackageName) >= 1; } inline bool FPackageNameCache::DoesPackageExist(const FName& PackageName, FString* OutFilename) const { FString PackageNameStr = PackageName.ToString(); // "/Extra/" packages are editor-generated in-memory packages which don't have a corresponding // asset file (yet). However, we still want to cook these packages out, producing cooked // asset files for packaged projects. if (FPackageName::IsExtraPackage(PackageNameStr)) { if (UPackage* ExtraPackage = FindPackage(/*Outer =*/nullptr, *PackageNameStr)) { if (OutFilename) { *OutFilename = FPackageName::LongPackageNameToFilename(PackageNameStr, FPackageName::GetAssetPackageExtension()); } return true; } // else, the cooker could be responding to a NotifyUObjectCreated() event, and the object hasn't // been fully constructed yet (missing from the FindObject() list) -- in this case, we've found // that the linker loader is creating a dummy object to fill a referencing import slot, not loading // the proper object (which means we want to ignore it). } if (!AssetRegistry) { return FPackageName::DoesPackageExist(PackageNameStr, NULL, OutFilename, false); } TArray Assets; AssetRegistry->GetAssetsByPackageName(PackageName, Assets, /*bIncludeOnlyDiskAssets =*/true); if (Assets.Num() <= 0) { return false; } if (OutFilename) { FName ClassRedirector = UObjectRedirector::StaticClass()->GetFName(); bool bContainsMap = false; bool bContainsRedirector = false; for (const FAssetData& Asset: Assets) { bContainsMap = bContainsMap | ((Asset.PackageFlags & PKG_ContainsMap) != 0); bContainsRedirector = bContainsRedirector | (Asset.AssetClass == ClassRedirector); } if (!bContainsMap && bContainsRedirector) { // presence of map -> .umap // But we can only assume lack of map -> .uasset if we know the type of every object in the package. // If we don't, because there was a redirector, we have to check the package on disk // TODO: Have the AssetRegistry store the extension of the package so that we don't have to look it up // Guessing the extension based on map vs non-map also does not support text assets and maps which have a different extension return FPackageName::DoesPackageExist(PackageNameStr, NULL, OutFilename, false); } const FString& PackageExtension = bContainsMap ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension(); *OutFilename = FPackageName::LongPackageNameToFilename(PackageNameStr, PackageExtension); } return true; } inline bool FPackageNameCache::CalculateCacheData(FName PackageName, FString& OutStandardFilename, FName& OutStandardFileFName) const { FString FilenameOnDisk; if (DoesPackageExist(PackageName, &FilenameOnDisk)) { OutStandardFilename = FPaths::ConvertRelativePathToFull(FilenameOnDisk); FPaths::MakeStandardFilename(OutStandardFilename); OutStandardFileFName = FName(*OutStandardFilename); return true; } return false; } inline bool FPackageNameCache::ContainsPackageName(FName PackageName) const { return PackageFilenameCache.Contains(PackageName); } inline IAssetRegistry* FPackageNameCache::GetAssetRegistry() const { return AssetRegistry; } inline const FPackageNameCache::FCachedPackageFilename& FPackageNameCache::Cache(const FName& PackageName) const { check(IsInGameThread()); FCachedPackageFilename* Cached = PackageFilenameCache.Find(PackageName); if (Cached != NULL) { return *Cached; } // cache all the things, like it's your birthday! FString FileNameString; FName FileName = NAME_None; CalculateCacheData(PackageName, FileNameString, FileName); PackageFilenameToPackageFNameCache.Add(FileName, PackageName); return PackageFilenameCache.Emplace(PackageName, FCachedPackageFilename(MoveTemp(FileNameString), FileName)); } inline const FName* FPackageNameCache::GetCachedPackageNameFromStandardFileName(const FName& NormalizedFileName, bool bExactMatchRequired, FName* FoundFileName) const { check(IsInGameThread()); const FName* Result = PackageFilenameToPackageFNameCache.Find(NormalizedFileName); if (Result) { if (FoundFileName) { *FoundFileName = NormalizedFileName; } return Result; } FName PackageName = NormalizedFileName; FString PotentialLongPackageName = NormalizedFileName.ToString(); if (!FPackageName::IsValidLongPackageName(PotentialLongPackageName)) { if (!FPackageName::TryConvertFilenameToLongPackageName(PotentialLongPackageName, PotentialLongPackageName)) { return nullptr; } PackageName = FName(*PotentialLongPackageName); } const FCachedPackageFilename& CachedFilename = Cache(PackageName); if (bExactMatchRequired) { if (FoundFileName) { *FoundFileName = NormalizedFileName; } return PackageFilenameToPackageFNameCache.Find(NormalizedFileName); } else { check(FoundFileName != nullptr); *FoundFileName = CachedFilename.StandardFileName; return PackageFilenameToPackageFNameCache.Find(CachedFilename.StandardFileName); } } inline void FPackageNameCache::ClearPackageFileNameCache(IAssetRegistry* InAssetRegistry) const { check(IsInGameThread()); PackageFilenameCache.Empty(); PackageFilenameToPackageFNameCache.Empty(); AssetRegistry = InAssetRegistry; } inline void FPackageNameCache::SetAssetRegistry(IAssetRegistry* InAssetRegistry) const { AssetRegistry = InAssetRegistry; } inline FName FPackageNameCache::GetStandardFileName(const FName& FileName) { return GetStandardFileName(FileName.ToString()); } inline FName FPackageNameCache::GetStandardFileName(const FStringView& InFileName) { FString FileName(InFileName); FPaths::MakeStandardFilename(FileName); return FName(FileName); }