【UE】pak的mount(带源码解析)

本文使用的引擎版本为UE4.27
为了方便理解,文中选取的代码均为部分截取,只截取与小节相关的部分

概述

正常的散文件加载是使用FFileHelper::LoadFileToArray等接口来读取文件内容。但pak作为一个类似于压缩包的格式,其中的文件无法直接使用这种方式读取。故需要使用mount来挂载。

mount操作告诉系统有哪些文件可以从pak中读到,并提供虚拟路径使系统可以通过FPakPlatformFile::CopyFileFFileHelper::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   (n1)

// 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   (n1)

猜你喜欢

转载自blog.csdn.net/weixin_44559752/article/details/128695492
今日推荐