Files
UnrealEngine/Engine/Source/Runtime/PakFile/Private/PakFile.inl
2025-05-18 13:04:45 +08:00

271 lines
9.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
/**
* Search the given FDirectoryIndex for all files under the given Directory. Helper for FindFilesAtPath, called separately on the DirectoryIndex or Pruned DirectoryIndex. Does not use
* FScopedPakDirectoryIndexAccess internally; caller is responsible for calling from within a lock.
* Returned paths are full paths (include the mount point)
*/
template <typename ShouldVisitFunc, class ContainerType>
void FPakFile::FindFilesAtPathInIndex(const FDirectoryIndex& TargetIndex, const FDirectoryTreeIndex& TargetTreeIndex, ContainerType& OutFiles,
const FString& FullSearchPath, const FVisitFilter<ShouldVisitFunc>& VisitFilter) const
{
TRACE_CPUPROFILER_EVENT_SCOPE(FindFilesAtPathInIndex);
FStringView RelSearchPath;
if (FullSearchPath.StartsWith(MountPoint))
{
RelSearchPath = FStringView(FullSearchPath).RightChop(MountPoint.Len());
}
else
{
// Directory is unnormalized and might not end with /; MountPoint is guaranteed to end with /.
// Act as if we were called with a normalized directory if adding slash makes it match MountPoint.
if (FStringView(FullSearchPath).StartsWith(FStringView(MountPoint).LeftChop(1)))
{
RelSearchPath = FStringView();
}
else
{
// Early out; directory does not start with MountPoint and so will not match any of files in this pakfile.
return;
}
}
TArray<FString> DirectoriesInPak; // List of all unique directories at path
#if ENABLE_PAKFILE_USE_DIRECTORY_TREE
if (ShouldUseDirectoryTree())
{
FindFilesAtPathInTreeIndexInternal(RelSearchPath, TargetTreeIndex, OutFiles, DirectoriesInPak, FullSearchPath, VisitFilter);
#if !UE_BUILD_SHIPPING
if (GPak_ValidateDirectoryTreeSearchConsistency)
{
ContainerType OutFilesIndexed;
TArray<FString> OutDirectoriesInPakIndexed;
FindFilesAtPathInIndexInternal(RelSearchPath, TargetIndex, OutFilesIndexed, OutDirectoriesInPakIndexed, FullSearchPath, VisitFilter);
if (!ValidateDirectoryTreeSearchConsistency(OutFiles, DirectoriesInPak, OutFilesIndexed, OutDirectoriesInPakIndexed))
{
UE_LOG(LogPakFile, Fatal,
TEXT("Mismatch between directoryindex and directorytreeindex search when searching for [%s] in pak [%s]"),
*FString(RelSearchPath), *GetFilename());
}
}
#endif // !UE_BUILD_SHIPPING
}
else
#endif // ENABLE_PAKFILE_USE_DIRECTORY_TREE
{
FindFilesAtPathInIndexInternal(RelSearchPath, TargetIndex, OutFiles, DirectoriesInPak, FullSearchPath, VisitFilter);
}
OutFiles.Append(MoveTemp(DirectoriesInPak));
}
#if ENABLE_PAKFILE_USE_DIRECTORY_TREE
template <typename ShouldVisitFunc, class ContainerType>
void FPakFile::FindFilesAtPathInTreeIndexInternal(FStringView RelSearchPath, const FDirectoryTreeIndex& TargetTreeIndex,
ContainerType& OutFiles, TArray<FString>& OutDirectories, const FString& FullSearchPath,
const FVisitFilter<ShouldVisitFunc>& VisitFilter) const
{
if (RelSearchPath.IsEmpty())
{
for (TPair<FStringView, const FPakDirectory&> Pair : TargetTreeIndex)
{
FindFilesAtPathInPakDirectoryInternal(MountPoint, Pair.Key, Pair.Value, OutFiles, OutDirectories,
FullSearchPath, VisitFilter);
}
}
else
{
const FPakDirectory* PakDirectory = TargetTreeIndex.Find(RelSearchPath);
if (PakDirectory)
{
FindFilesAtPathInPakDirectoryInternal(MountPoint, RelSearchPath, *PakDirectory, OutFiles, OutDirectories,
FullSearchPath, VisitFilter);
if (VisitFilter.bRecursive || VisitFilter.bIncludeDirectories)
{
// TODO: Add TDirectoryTree::CreatePathIterator so we can avoid the copy of children and the redundant
// lookups of the children in the tree.
TArray<FString> OutChildDirectories;
TargetTreeIndex.TryGetChildren(RelSearchPath, OutChildDirectories,
VisitFilter.bRecursive ? EDirectoryTreeGetFlags::Recursive : EDirectoryTreeGetFlags::None);
for (const FString& ChildDirectoryPath : OutChildDirectories)
{
FString RelChildPath = PakPathCombine(RelSearchPath, ChildDirectoryPath);
MakeDirectoryFromPath(RelChildPath);
if (const FPakDirectory* PakDirectoryChild = TargetTreeIndex.Find(RelChildPath))
{
FindFilesAtPathInPakDirectoryInternal(MountPoint, RelChildPath,
*PakDirectoryChild, OutFiles, OutDirectories, FullSearchPath, VisitFilter);
}
}
}
}
}
}
#if !UE_BUILD_SHIPPING
template <class ContainerType>
bool FPakFile::ValidateDirectoryTreeSearchConsistency(const ContainerType& FilesTree, const TArray<FString>& DirectoriesInPakTree, const ContainerType& FilesIndexed, const TArray<FString>& DirectoriesInPakIndexed) const
{
if (FilesTree.Num() == FilesIndexed.Num())
{
for (const FString& File : FilesTree)
{
if (!FilesIndexed.Contains(File))
{
return false;
}
}
}
else
{
return false;
}
if (DirectoriesInPakTree.Num() == DirectoriesInPakIndexed.Num())
{
for (const FString& Dir : DirectoriesInPakTree)
{
if (!DirectoriesInPakIndexed.Contains(Dir))
{
return false;
}
}
}
else
{
return false;
}
return true;
}
#endif // UE_BUILD_SHIPPING
#endif // ENABLE_PAKFILE_USE_DIRECTORY_TREE
template <typename ShouldVisitFunc, class ContainerType>
void FPakFile::FindFilesAtPathInIndexInternal(const FStringView& RelSearchPath, const FDirectoryIndex& TargetIndex,
ContainerType& OutFiles, TArray<FString>& OutDirectories,
const FString& FullSearchPath, const FVisitFilter<ShouldVisitFunc>& VisitFilter) const
{
for (TMap<FString, FPakDirectory>::TConstIterator It(TargetIndex); It; ++It)
{
// Check if the file is under the specified path.
if (FStringView(It.Key()).StartsWith(RelSearchPath))
{
FindFilesAtPathInPakDirectoryInternal(MountPoint, It.Key(), It.Value(), OutFiles, OutDirectories,
FullSearchPath, VisitFilter);
}
}
}
template <typename ShouldVisitFunc, class ContainerType>
void FPakFile::FindFilesAtPathInPakDirectoryInternal(const FString& MountPoint, FStringView RelPathInIndex,
const FPakDirectory& PakDirectory, ContainerType& OutFiles, TArray<FString>& OutDirectoriesInPak,
const FString& FullSearchPath, const FVisitFilter<ShouldVisitFunc>& VisitFilter)
{
FString FullPathInIndex = PakPathCombine(MountPoint, RelPathInIndex);
if (VisitFilter.bRecursive == true)
{
// Add everything
if (VisitFilter.bIncludeFiles)
{
for (FPakDirectory::TConstIterator FileIt(PakDirectory); FileIt; ++FileIt)
{
const FString& FilePathUnderDirectory = FileIt.Key();
if (VisitFilter.ShouldVisit(FilePathUnderDirectory))
{
OutFiles.Add(PakPathCombine(FullPathInIndex, FilePathUnderDirectory));
}
}
}
if (VisitFilter.bIncludeDirectories)
{
if (FullSearchPath != FullPathInIndex)
{
if (VisitFilter.ShouldVisit(FullPathInIndex))
{
OutDirectoriesInPak.Add(MoveTemp(FullPathInIndex));
}
}
}
}
else
{
int32 SubDirIndex = FullPathInIndex.Len() > FullSearchPath.Len()
? FullPathInIndex.Find(TEXT("/"), ESearchCase::CaseSensitive, ESearchDir::FromStart, FullSearchPath.Len() + 1)
: INDEX_NONE;
// Add files in the specified folder only.
if (VisitFilter.bIncludeFiles && SubDirIndex == INDEX_NONE)
{
for (FPakDirectory::TConstIterator FileIt(PakDirectory); FileIt; ++FileIt)
{
const FString& FilePathUnderDirectory = FileIt.Key();
if (VisitFilter.ShouldVisit(FilePathUnderDirectory))
{
OutFiles.Add(PakPathCombine(FullPathInIndex, FilePathUnderDirectory));
}
}
}
// Add sub-folders in the specified folder only
if (VisitFilter.bIncludeDirectories && SubDirIndex >= 0)
{
FString SubDirPath = FullPathInIndex.Left(SubDirIndex + 1);
if (VisitFilter.ShouldVisit(SubDirPath))
{
OutDirectoriesInPak.AddUnique(MoveTemp(SubDirPath));
}
}
}
}
template <typename ShouldVisitFunc>
FPakFile::FVisitFilter<ShouldVisitFunc>::FVisitFilter(const ShouldVisitFunc& InShouldVisit, bool bInIncludeFiles, bool bInIncludeDirectories, bool bInRecursive)
: ShouldVisit(InShouldVisit)
, bIncludeFiles(bInIncludeFiles)
, bIncludeDirectories(bInIncludeDirectories)
, bRecursive(bInRecursive)
{
}
template <typename ShouldVisitFunc, class ContainerType>
void FPakFile::FindPrunedFilesAtPathInternal(const TCHAR* InPath, ContainerType& OutFiles, const FVisitFilter<ShouldVisitFunc>& VisitFilter) const
{
// Make sure all directory names end with '/'.
FString FullSearchPath(InPath);
MakeDirectoryFromPath(FullSearchPath);
// Check the specified path is under the mount point of this pak file.
// The reverse case (MountPoint StartsWith Directory) is needed to properly handle
// pak files that are a subdirectory of the actual directory.
if (!FullSearchPath.StartsWith(MountPoint) && !MountPoint.StartsWith(FullSearchPath))
{
return;
}
FScopedPakDirectoryIndexAccess ScopeAccess(*this);
#if ENABLE_PAKFILE_RUNTIME_PRUNING_VALIDATE
if (ShouldValidatePrunedDirectory())
{
TSet<FString> FullFoundFiles, PrunedFoundFiles;
FindFilesAtPathInIndex(DirectoryIndex, DirectoryTreeIndex, FullFoundFiles, FullSearchPath, VisitFilter);
FindFilesAtPathInIndex(PrunedDirectoryIndex, PrunedDirectoryTreeIndex, PrunedFoundFiles, FullSearchPath, VisitFilter);
ValidateDirectorySearch(FullFoundFiles, PrunedFoundFiles, InPath);
for (const FString& FoundFile : FullFoundFiles)
{
OutFiles.Add(FoundFile);
}
}
else
#endif
{
FindFilesAtPathInIndex(DirectoryIndex, DirectoryTreeIndex, OutFiles, FullSearchPath, VisitFilter);
}
}