本文使用的引擎版本為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階段才進行。後者的調用堆棧如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL1AjY0EmMmlTOwE2M5cDO4QjZxQDO0kDZwQGOzEGZ2czLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
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
- 在經過上述目錄優先級的粗略處理以後,如果檔案名以
結尾,則将其優先級提升到 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)_n_P