本文使用的引擎版本为UE4.27
为了方便理解,文中选取的代码均为部分截取,只截取与小节相关的部分
文章目录
- 概述
- 几个涉及到的结构
- Mount时机
- pak读取优先级
- 目录优先级
- 根据文件名定优先级
- 综上所述
概述
正常的散文件加载是使用FFileHelper::LoadFileToArray
等接口来读取文件内容。但pak作为一个类似于压缩包的格式,其中的文件无法直接使用这种方式读取。故需要使用mount来挂载。
mount操作告诉系统有哪些文件可以从pak中读到,并提供虚拟路径使系统可以通过FPakPlatformFile::CopyFile
、FFileHelper::LoadFileToString
等操作普通文件的方法操作pak中的文件
几个涉及到的结构
IPlatformFile:文件IO的接口,是整个文件系统的基类。该类及其子类以链式组织,每个实例存有其下层的引用,每层访问的时候都先查找自身,找不到才向下层查找。
FPakPlatformFile:继承于IPlatformFile,负责pak的mount。它的私有成员ExcludedNonPakExtensions记录了几个只应存在pak里的文件拓展:uasset、umap、ubulk、uexp、uptnl和ushaderbytecode。这些文件如果在pak里没有找到,就不再查找它的下层了。
FPakListEntry:定义在FPakPlatformFile内的结构体,有ReadOrder和PakFile两个属性。FPakPlatformFile中用几个TArray来记录mount到的pak
FPakFile:Pak在C++中存储的形式。有PakFilename、LastUseTime、Info等成员
FPakInfo:Pak的提纲信息,存有版本号、哈希值、魔数、是否加密等
FPakEntry:Pak中单个文件的信息,存储文件大小、在pak中的偏移量、是否加密等
Mount时机
很多地方都会调到FPakPlatformFile::Mount
进行pak的Mount,这里只分析引擎启动时进行的mount
引擎启动时进行的初始化mount位于 FPakPlatformFile::Initialize
。如果使用命令行指定了PakFile单例,会在引擎PreInit的阶段通过ConditionallyCreateFileWrapper函数创建单例并调用 FPakPlatformFile::Initialize。如果没有在命令行中指定,则该初始化函数在EditorInit阶段才进行。后者的调用堆栈如下:
pak读取优先级
概述中提到,mount的作用是为了提供一种读取pak文件的方法,使pak中的文件也能使用操作散文件的操作方法。即:mount并不是读取pak中的文件。故mount顺序其实对pak的读取优先级没有影响
那么pak读取的优先级到底取决于什么呢? 做热更需求的时候如何保证自己新添加的pak可以覆盖掉原来pak中的资源?答案是取决于FPakListEntry的ReadOrder:ReadOrder大的pak会覆盖小的pak的资源内容
// IPlatformFilePak.h
struct FPakListEntry
{
FPakListEntry(): ReadOrder(0), PakFile(nullptr){}
uint32 ReadOrder;
TRefCountPtr<FPakFile> PakFile;
// ReadOrder越大,优先级越高。故热更的时候只需要保证新pak的ReadOrder大于原来的pak即可
FORCEINLINE bool operator < (const FPakListEntry& RHS) const
{
return ReadOrder > RHS.ReadOrder;
}
};
这个ReadOrder是在FPakPlatformFile::Mount(const TCHAR* InPakFilename, uint32 PakOrder, ...)
指定的,未指定的话使用最低的优先级0
在进入mount之前,Initialize函数中会按照pak的路径和名字,给这个pak赋一个ReadOrder(即下文中的PakOrder)。
目录优先级
// IPlatformFilePak.cpp
bool FPakPlatformFile::Initialize(IPlatformFile* Inner, const TCHAR* CmdLine)
{
……
#if EXCLUDE_NONPAK_UE_EXTENSIONS && !WITH_EDITOR
// Extensions for file types that should only ever be in a pak file. Used to stop unnecessary access to the lower level platform file
ExcludedNonPakExtensions.Add(TEXT("uasset"));
ExcludedNonPakExtensions.Add(TEXT("umap"));
ExcludedNonPakExtensions.Add(TEXT("ubulk"));
ExcludedNonPakExtensions.Add(TEXT("uexp"));
ExcludedNonPakExtensions.Add(TEXT("uptnl"));
ExcludedNonPakExtensions.Add(TEXT("ushaderbytecode"));
#endif
……
// Find and mount pak files from the specified directories.
TArray<FString> PakFolders;
GetPakFolders(FCommandLine::Get(), PakFolders);
MountAllPakFiles(PakFolders, *StartupPaksWildcard);
……
}
FPakPlatformFile初始化的时候除了对一些成员变量进行初始化以外,还会调用MountAllPakFiles进行pak的mount。
int32 FPakPlatformFile::MountAllPakFiles(const TArray<FString>& PakFolders, const FString& WildCard)
{
……
if (bMountPaks)
{
TArray<FString> FoundPakFiles;
FindAllPakFiles(LowerLevel, PakFolders, WildCard, FoundPakFiles);
……
for (int32 PakFileIndex = 0; PakFileIndex < FoundPakFiles.Num(); PakFileIndex++)
{
const FString& PakFilename = FoundPakFiles[PakFileIndex];
……
uint32 PakOrder = GetPakOrderFromPakFilePath(PakFilename);
UE_LOG(LogPakFile, Display, TEXT("Mounting pak file %s."), *PakFilename);
if (Mount(*PakFilename, PakOrder))
{
++NumPakFilesMounted;
}
}
}
return NumPakFilesMounted;
}
MountAllPakFiles其实就是读取需要mount的pak,遍历进行挂载。
其中GetPakOrderFromPakFilePath依据pak所属目录获取其优先级:
int32 FPakPlatformFile::GetPakOrderFromPakFilePath(const FString& PakFilePath)
{
if (PakFilePath.StartsWith(FString::Printf(TEXT("%sPaks/%s-"), *FPaths::ProjectContentDir(), FApp::GetProjectName())))
{
return 4;
}
else if (PakFilePath.StartsWith(FPaths::ProjectContentDir()))
{
return 3;
}
else if (PakFilePath.StartsWith(FPaths::EngineContentDir()))
{
return 2;
}
else if (PakFilePath.StartsWith(FPaths::ProjectSavedDir()))
{
return 1;
}
return 0;
}
可以看到,确立pak读取优先级的基本策略是:Project/Content/Paks/ProjectName-*.pak > Project/Content/Paks/*.pak > Engine/Content/Paks/*.pak > Project/Saved/Paks/*.pak
根据文件名定优先级
在经过上述目录优先级的处理以后,如果文件名以_n_P
结尾,则将其优先级提升到
P
a
k
O
r
d
e
r
+
(
n
+
1
)
×
100
(
n
≥
1
)
PakOrder + (n + 1)×100 \space\space\space(n \geq 1)
PakOrder+(n+1)×100 (n≥1):
// IPlatformFilePak.cpp
bool FPakPlatformFile::Mount(const TCHAR* InPakFilename, uint32 PakOrder, const TCHAR* InPath /*= NULL*/, bool bLoadIndex /*= true*/)
{
……
if (PakFilename.EndsWith(TEXT("_P.pak")))
{
// Prioritize based on the chunk version number
// Default to version 1 for single patch system
uint32 ChunkVersionNumber = 1;
FString StrippedPakFilename = PakFilename.LeftChop(6);
int32 VersionEndIndex = PakFilename.Find("_", ESearchCase::CaseSensitive, ESearchDir::FromEnd);
if (VersionEndIndex != INDEX_NONE && VersionEndIndex > 0)
{
int32 VersionStartIndex = PakFilename.Find("_", ESearchCase::CaseSensitive, ESearchDir::FromEnd, VersionEndIndex - 1);
if (VersionStartIndex != INDEX_NONE)
{
VersionStartIndex++;
FString VersionString = PakFilename.Mid(VersionStartIndex, VersionEndIndex - VersionStartIndex);
if (VersionString.IsNumeric())
{
int32 ChunkVersionSigned = FCString::Atoi(*VersionString);
if (ChunkVersionSigned >= 1)
{
// Increment by one so that the first patch file still gets more priority than the base pak file
ChunkVersionNumber = (uint32)ChunkVersionSigned + 1;
}
}
}
}
PakOrder += 100 * ChunkVersionNumber;
}
……
}
综上所述
pak优先级的确立方法:
-
确立pak读取优先级的基本策略是:Project/Content/Paks/ProjectName-*.pak > Project/Content/Paks/*.pak > Engine/Content/Paks/*.pak > Project/Saved/Paks/*.pak
-
在经过上述目录优先级的粗略处理以后,如果文件名以
_n_P
结尾,则将其优先级提升到 P a k O r d e r + ( n + 1 ) × 100 ( n ≥ 1 ) PakOrder + (n + 1)×100 \space\space\space(n \geq 1) PakOrder+(n+1)×100 (n≥1)