天天看點

【UE】pak的mount(帶源碼解析)概述幾個涉及到的結構Mount時機pak讀取優先級

本文使用的引擎版本為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階段才進行。後者的調用堆棧如下:

【UE】pak的mount(帶源碼解析)概述幾個涉及到的結構Mount時機pak讀取優先級

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優先級的确立方法:

  1. 确立pak讀取優先級的基本政策是:Project/Content/Paks/ProjectName-*.pak > Project/Content/Paks/*.pak > Engine/Content/Paks/*.pak > Project/Saved/Paks/*.pak
  2. 在經過上述目錄優先級的粗略處理以後,如果檔案名以

    _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)

繼續閱讀