【UE4源代码观察】观察DerivedDataBackendInterface各个子类的关系

前言

《【UE4源代码观察】观察DDC(DerivedDataCache)》中,我观察了DDC的基础部分,但是仍旧留下很多疑问,其中一个最令我迷惑的疑问是为什么FDerivedDataBackendInterface有如此多的子类,而FDerivedDataBackendGraph中又有很多FDerivedDataBackendInterface的成员,更复杂的是,他们还存在着嵌套的关系。因此我想要观察它们,试图对他们有更进一步的了解。步骤是

  1. 观察FDerivedDataBackendInterface本身接口的内容。
  2. 查看这个子类以及相关部分的注释,尝试搞明白每个子类的职责区别。
  3. 观察嵌套关系
  4. 实际运行,查看运行时一些变量的值。

FDerivedDataBackendInterface的接口

/** 
 * Interface for cache server backends. 
 * The entire API should be callable from any thread (except the singleton can be assumed to be called at least once before concurrent access).
**/
class FDerivedDataBackendInterface

【注释】:它是缓存服务器后端的接口。整个API应该能在任何线程上被调用(除了单例,假定在并发的访问之前已经至少被调用了一次)。
它的接口如下:

IsWritable 是否可写
/** return true if this cache is writable **/
virtual bool IsWritable()=0;

【注释】:返回缓存是否可写。

BackfillLowerCacheLevels 是否传递给更低一层级别
/** 
 * return true if hits on this cache should propagate to lower cache level. Typically false for a PAK file. 
 * Caution! This generally isn't propagated, so the thing that returns false must be a direct child of the heirarchical cache.
 **/
virtual bool BackfillLowerCacheLevels()
{
	return true;
}

【注释】:返回是否在缓存命中时传递给更低一层级的缓存级别。典型的情况是:PAK文件会返回false。注意!这个通常下是会传递的(原文注释语意前后矛盾,推测 is’nt 是笔误,正确应该是 is),因此这里返回false的应该都是heirarchical缓存的直接子对象。

CachedDataProbablyExists 缓存是否大概率存在
/**
 * Synchronous test for the existence of a cache item
 *
 * @param	CacheKey	Alphanumeric+underscore key of this cache item
 * @return				true if the data probably will be found, this can't be guaranteed because of concurrency in the backends, corruption, etc
 */
virtual bool CachedDataProbablyExists(const TCHAR* CacheKey)=0;

【注释】:
同步地检测缓存是否存在。
参数CacheKey:缓存的键值,应为数字加下划线的结构。
返回值:返回true表示缓存大概率能被找到,这一点不能被保证,因为后端的并发性、corruption等原因。(corruption指什么我不理解)。

GetCachedData 获得缓存
/**
 * Synchronous retrieve of a cache item
 *
 * @param	CacheKey	Alphanumeric+underscore key of this cache item
 * @param	OutData		Buffer to receive the results, if any were found
 * @return				true if any data was found, and in this case OutData is non-empty
 */
virtual bool GetCachedData(const TCHAR* CacheKey, TArray<uint8>& OutData)=0;

【注释】:
同步地取得缓存
参数OutData:缓存的buffer,前提是缓存被找到。
返回:如果找到数据的话就返回true,这种情况下OutData不为空。

PutCachedData 安放缓存
/**
 * Asynchronous, fire-and-forget placement of a cache item
 *
 * @param	CacheKey			Alphanumeric+underscore key of this cache item
 * @param	InData				Buffer containing the data to cache, can be destroyed after the call returns, immediately
 * @param	bPutEvenIfExists	If true, then do not attempt skip the put even if CachedDataProbablyExists returns true
 */
virtual void PutCachedData(const TCHAR* CacheKey, TArray<uint8>& InData, bool bPutEvenIfExists) = 0;

【注释】:
同步地,即发即忘地(fire-and-forget)放置一个缓存
参数InData:缓存的buffer,可以调用后立即被销毁。
参数bPutEvenIfExists:如果这里为true,在CachedDataProbablyExists返回true的情况下也不要跳过安放步骤。

RemoveCachedData 移除缓存
/**
 * Remove data from cache (used in the event that corruption is detected at a higher level and possibly house keeping)
 *
 * @param	CacheKey	Alphanumeric+underscore key of this cache item
 * @param	bTransient	true if the data is transient and it is up to the backend to decide when and if to remove cached data.
 */
virtual void RemoveCachedData(const TCHAR* CacheKey, bool bTransient)=0;

【注释】:
移除一个缓存数据,使用情况是:当corruption is detected at a higher level and possibly house keeping(这一段目前不理解)。
参数bTransient:如果是true则表示数据是暂时的,此时将由后端来决定“何时”以及“是否”移除缓存数据。

GatherUsageStats 收集使用统计数据
/**
 * Retrieve usage stats for this backend. If the backend holds inner backends, this is expected to be passed down recursively.
 * @param	UsageStatsMap		The map of usages. Each backend instance should give itself a unique name if possible (ie, use the filename associated).
 * @param	GraphPath			Path to the node in the graph. If you have inner nodes, add their index to the current path as ". <n>".
 *								This will create a path such as "0. 1. 0. 2", which can uniquely identify this node.
 */
virtual void GatherUsageStats(TMap<FString, FDerivedDataCacheUsageStats>& UsageStatsMap, FString&& GraphPath) = 0;

【注释】:
获得后端的使用统计数据。如果后端持有内部后端,则这里被期望递归地传递下去。
参数UsageStatsMap:使用情况的map。如果可以的话,每个后端instance都应该给自己一个独一无二的名字,例如所关联的文件名。
参数GraphPath:Graph中节点的路径,如果有内部节点,就在当前的路径上增加编号例如“.<n>”。这会创建出像"0. 1. 0. 2"这样的路径,这样就可以唯一地识别出节点。

子类以及部分相关内容的注释

FDerivedDataBackendAsyncPutWrapper

A backend wrapper that coordinates async puts. This means that a Get will hit an in-memory cache while the async put is still in flight.
一个用于协调异步“Put”的后端封装。这意味着一个“Get”会命中一个在内存中的缓存,但此时“Put”还仍旧在运行(in flight)。


成员变量:

/** Backend to use for storage, my responsibilities are about async puts **/
FDerivedDataBackendInterface*					InnerBackend;

内部用来贮存的后端,我的职责是关于异步“put”的。


cpp中定义了一种任务:

/** 
 * Async task to handle the fire and forget async put
 */
class FCachePutAsyncWorker

【注释】:异步任务来处理即发既往( fire and forget)的异步“put”


FDerivedDataBackendCorruptionWrapper

A backend wrapper that adds a footer to the data to check the CRC, length, etc.
一个后端封装,用来添加一个“footer”给数据,以便可以检查CRC、长度等等。(其中CRC应该是指一种哈希算法)


成员:

/** Backend to use for storage, my responsibilities are about corruption **/
FDerivedDataBackendInterface* InnerBackend;

内部用来贮存的后端,我的职责是关于corruption的。

FDerivedDataBackendVerifyWrapper

a wrapper for derived data that verifies the cache is bit-wise identical by failing all gets and verifying the puts

FDerivedDataLimitKeyLengthWrapper

A backend wrapper that limits the key size and uses hashing…in this case it wraps the payload and the payload contains the full key to verify the integrity of the hash
一个限制键的尺寸并且使用哈希的后端封装。在此情况下,它封装了payload(这是什么意思?),而payload包含了全部的键来确认哈希的完整性(integrity)。


成员:

/** Backend to use for storage, my responsibilities are about key length **/
FDerivedDataBackendInterface* InnerBackend;

内部用来贮存的后端,我的职责是关于键值长度的。


成员:

/** Maximum size of the backend key length **/
int32 MaxKeyLength;

最大键值长度

FFileSystemDerivedDataBackend

Cache server that uses the OS filesystem
使用操作系统的文件系统的缓存服务器。


成员:

/** Base path we are storing the cache files in. **/
FString	CachePath;

用来存储文件的基础路径


成员函数:

/**
 * Threadsafe method to compute the filename from the cachekey, currently just adds a path and an extension.
 *
 * @param	CacheKey	Alphanumeric+underscore key of this cache item
 * @return				filename built from the cache key
 */
FString BuildFilename(const TCHAR* CacheKey)

线程安全地从缓存键值计算文件名,目前只是加一个路径和扩展名。

FHierarchicalDerivedDataBackend

A backend wrapper that implements a cache hierarchy of backends
一个实现了层次结构(heirarchical)缓存的后端封装。


成员:

/** Array of backends forming the hierarchical cache...the first element is the fastest cache. **/
TArray<FDerivedDataBackendInterface*> InnerBackends;

层级结构缓存的列表,第一个是最快的缓存。


成员:

/** Each of the backends wrapped with an async put **/
TArray<TUniquePtr<FDerivedDataBackendInterface> > AsyncPutInnerBackends;

每一个后端都有一个异步“put”封装。

FMemoryDerivedDataBackend

A simple thread safe, memory based backend. This is used for Async puts and the boot cache.
一个线程安全型的、基于内存的后端。它被用作异步“put”和boot缓存。


公开接口:

/**
 * Save the cache to disk
 * @param	Filename	Filename to save
 * @return	true if file was saved sucessfully
 */
bool SaveCache(const TCHAR* Filename);

存储缓存到磁盘上。


公开接口:

/**
 * Load the cache from disk
 * @param	Filename	Filename to load
 * @return	true if file was loaded sucessfully
 */
bool LoadCache(const TCHAR* Filename);

将缓存从磁盘上读进来。


成员:

/** Name of the cache file loaded (if any). */
FString CacheFilename;

缓存文件的名字


成员:

/** Set of files that are being written to disk asynchronously. */
TMap<FString, FCacheValue*> CacheItems;

一系列被异步写到磁盘上的文件。

FPakFileDerivedDataBackend

A simple thread safe, pak file based backend.
一个简单的、线程安全型的、基于pak文件的后端。


嵌套关系

\Engine\Config\BaseEngine.ini中:

[DerivedDataBackendGraph]
MinimumDaysToKeepFile=7
Root=(Type=KeyLength, Length=120, Inner=AsyncPut)
AsyncPut=(Type=AsyncPut, Inner=Hierarchy)
Hierarchy=(Type=Hierarchical, Inner=Boot, Inner=Pak, Inner=EnginePak, Inner=Local, Inner=Shared)
Boot=(Type=Boot, Filename="%GAMEDIR%DerivedDataCache/Boot.ddc", MaxCacheSize=512)
Local=(Type=FileSystem, ReadOnly=false, Clean=false, Flush=false, PurgeTransient=true, DeleteUnused=true, UnusedFileAge=34, FoldersToClean=-1, Path=%ENGINEDIR%DerivedDataCache, EnvPathOverride=UE-LocalDataCachePath, EditorOverrideSetting=LocalDerivedDataCache)
Shared=(Type=FileSystem, ReadOnly=false, Clean=false, Flush=false, DeleteUnused=true, UnusedFileAge=10, FoldersToClean=10, MaxFileChecksPerSec=1, Path=?EpicDDC, EnvPathOverride=UE-SharedDataCachePath, EditorOverrideSetting=SharedDerivedDataCache, CommandLineOverride=SharedDataCachePath)
AltShared=(Type=FileSystem, ReadOnly=true, Clean=false, Flush=false, DeleteUnused=true, UnusedFileAge=23, FoldersToClean=10, MaxFileChecksPerSec=1, Path=?EpicDDC2, EnvPathOverride=UE-SharedDataCachePath2)
Pak=(Type=ReadPak, Filename="%GAMEDIR%DerivedDataCache/DDC.ddp")
EnginePak=(Type=ReadPak, Filename=%ENGINEDIR%DerivedDataCache/DDC.ddp)

它指定了FDerivedDataBackendGraph将包含哪些FDerivedDataBackendInterface以及它们的嵌套关系是什么。这一步骤是由ParseNode函数完成的:

/**
* Parses backend graph node from ini settings
 *
 * @param NodeName Name of the node to parse
 * @param IniFilename Ini filename
 * @param IniSection Section in the ini file containing the graph definition
 * @param InParsedNodes Map of parsed nodes and their names to be able to find already parsed nodes
 * @return Derived data backend interface instance created from ini settings
 */
FDerivedDataBackendInterface* ParseNode( const TCHAR* NodeName, const FString& IniFilename, const TCHAR* IniSection, TMap<FString, FDerivedDataBackendInterface*>& InParsedNodes  )

它在FDerivedDataBackendGraph的构造函数被调用:

RootCache = ParseNode( TEXT("Root"), GEngineIni, *GraphName, ParsedNodes );	

随后,根据Type的值,来跳转到对应的解析节点的函数中,而随后又递归地调用ParseNode

if( FParse::Value( *Entry, TEXT("Type="), NodeType ) )
{
	if( NodeType == TEXT("FileSystem") )
	{
		ParsedNode = ParseDataCache( NodeName, *Entry );
	}
	else if( NodeType == TEXT("Boot") )
	{
		if( BootCache == NULL )
		{
			BootCache = ParseBootCache( NodeName, *Entry, BootCacheFilename );
			ParsedNode = BootCache;
		}
		else
		{
			UE_LOG( LogDerivedDataCache, Warning, TEXT("FDerivedDataBackendGraph:  Unable to create %s Boot cache because only one Boot cache node is supported."), *NodeName );
		}
	}
	else if( NodeType == TEXT("Memory") )
	{
		ParsedNode = ParseMemoryCache( NodeName, *Entry );
	}
	else if( NodeType == TEXT("Hierarchical") )
	{
		ParsedNode = ParseHierarchicalCache( NodeName, *Entry, IniFilename, IniSection, InParsedNodes );
	}
	...

最终,他们的嵌套关系如下:
(其中“【】”表示的是FDerivedDataBackendGraph成员变量的名,而“《》”表示的是ini中的节点名)

InnerBackend
InnerBackend
InnerBackends[0]
InnerBackend
InnerBackends[1]
AsyncPutInnerBackends[0]
AsyncPutInnerBackends[1]
InnerBackend
InnerBackend
InflightCache
_LimitKeyLength【RootCache】【KeyLengthWrapper】《Root》
_AsyncPut【AsyncPutWrapper】《AsyncPut》
_Hierarchica【HierarchicalWrapper】《Hierarchy》
_Memory【BootCache】《Boot》
_Corruption《Local》
_FileSystem
_AsyncPut
_AsyncPut
_Memory

部分运行时的变量名

FMemoryDerivedDataBackend::LoadCache文件的对象是Boot.ddc
在这里插入图片描述
在这个函数中,CacheItems被添加,每一项都对应了一个.udd文件,可被查到
在这里插入图片描述


InMaxKeyLength的值是120,这和我这里.udd文件名的长度一致。
在这里插入图片描述


BuildFilename决定了.udd文件的路径:
在这里插入图片描述

总结

看起来,FDerivedDataBackendInterface的众多相互嵌套的子类,应该是希望实现一个 Decorator模式 ,他们各自都有各自的职责。不过具体为何要这样设计,以及有没有其他的方案,我并不知道答案。

虽然,现在对FDerivedDataBackendInterface的职责以及其子类的关系有了更进一步了解,但是DDC系统还是留给我很多疑问:

  • Boot.ddc文件体积也很大,而分散的.udd文件也有很多,那么他们之间是什么样的关系呢?
  • DDC整个系统是怎样运行的?
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值