无用的前言
很久前我观察过DDC的一些代码了解了些浅显的知识。
最近我遇到个DDC相关的问题,于是将之前写的东西又复习了一遍。同时我也将记录下我最近研究这个问题时,一些重要的部分以作备忘。
目标
观察一个StaticMesh加载其对应DDC文件的流程,记录其中的一些要点。
(注意:此处引擎版本是4.26,UE5里DDC相关代码已有较大调整)
0. 代码入手处
DDC接口的GetSynchronous
和GetAsynchronous
函数是获得DDC数据的统一接口,断点在这里一定能触发到。
不过,我现在只想观察一个特定StaticMesh的DDC,那么如果我一开始就将断点打在GetSynchronous
和GetAsynchronous
内部,则会断到很多其他资源。
所以,我选择首先将断点打在StaticMesh调用DDC接口的地方,然后,双击加载一个我想观察的StaticMesh资源比如这个:
然后断点就能触发:(可以通过Owner的值确认下触发的StaticMesh资源是自己想要观察的那个)
现在,即是这个StaticMesh准备获取DDC数据的时刻。
随后,再在 GetSynchronous
函数内部下断点来触发:(可以通过CacheKey值确认是这个资源)
这里调用的函数是 GetSynchronous,即同步的方式,所以接下来会立即执行这个任务(而不是放在另一个线程中执行)。
GetSynchronous函数核心是创建一个 FAsyncTask<FBuildAsyncWorker>
来执行任务。
1. FBuildAsyncWorker
FAsyncTask
安排了一个可异步执行的任务(也可同步执行),其基础用法可见上一篇。
而任务的具体内容则由FBuildAsyncWorker
定义,它本身有些成员变量,可以参考下注释对他们的解释:
/** true in the case of a cache hit, otherwise the result of the deriver build call **/
bool bSuccess;
/** true if we should record the timing **/
bool bSynchronousForStats;
/** true if we had to build the data */
bool bDataWasBuilt;
/** Data dervier we are operating on **/
FDerivedDataPluginInterface* DataDeriver;
/** Cache key associated with this build **/
FString CacheKey;
/** Data to return to caller, later **/
TArray<uint8> Data;
而执行的逻辑,则在 DoWork()
函数中,其中最重要的是通过FDerivedDataBackend
获得DDC数据。
- 如果成功则局部变量
bGetResult
会记为真,那么FBuildAsyncWorker
本身的成员bSuccess
会变为真,且成员Data
也是有值的。 - 如果失败,那么
bSuccess
为假,Data
也是空的。
这样,当任务结束后,就可以通过FBuildAsyncWorker
本身的成员变量bSuccess
和Data
的值,来明白DDC是否获取成功和DDC数据是什么。
接下来,需要看FDerivedDataBackend::Get().GetRoot().GetCachedData
2. 众多的 DerivedDataBackend?
正如 《观察DerivedDataBackendInterface各个子类的关系》 所看到,DerivedDataBackend 由多个子类,且之间并非并列,而是嵌套的关系。
所以,尽管这里作为 Root 的 DerivedDataBackend 是 DerivedDataLimitKeyLengthWrapper:
但随后它又会调用其他的 DerivedDataBackend 来尝试获得DDC数据。
UE设计成这样众多Backend的结构肯定是有理由的,不过我还没理解清楚,所以不做讨论。
但有一点可以肯定:如果最终通过文件获得DDC数据,则一定是通过FFileSystemDerivedDataBackend
。
3. 在FFileSystemDerivedDataBackend::GetCachedData 下断点可以获得对应文件的路径
随后,在 FFileSystemDerivedDataBackend::GetCachedData
下断点,可以通过Filename
的值知道对应的DDC文件路径:
可以直接在磁盘上找到这个文件:
4*. 缓存缺失重新构建的情况
也可以在之后手动删除这个文件,来模拟DDC文件没有找到的情况。
此时可以看到FBuildAsyncWorker
的DoWork()
会走到这里,结束后bSuccess
为假,Data
也是空的。
GetSynchronous
由于返回的是PendingTask.GetTask().bSuccess
,所以返回的是假。
那么StaticMesh调用后就会走向另一个分支
随后,DDC的数据会被重建。
最后,在GetDerivedDataCacheRef().Put
调用后:
DDC文件会被重新保存(由于这次调用应该是异步的,所以这个函数返回后不会立即看到文件的产生)
总结
- StaticMesh 调用DDC的通用接口
GetSynchronous
获得DDC数据 GetSynchronous
内部创建一个FAsyncTask<FBuildAsyncWorker>
来执行任务。FBuildAsyncWorker
的DoWork()
是其核心,可以看到它通过DerivedDataBackend获得DDC数据。- 众多的 DerivedDataBackend 相互连接,定义了找DDC数据的逻辑。
- 可在
FFileSystemDerivedDataBackend::GetCachedData
下断点知道资源加载的DDC文件路径。
小记最近的问题
最近遇到的问题从堆栈上看是,StaticMesh加载的DDC数据中一个数组的元素大小不匹配现在的定义。一般来说,DDC的问题首先要尝试的肯定是清除一遍DDC让它重新生成一遍。但是我(自以为)清除了之后,还是同样的错误。
最后,通过找到那个StaticMesh对应的DDC文件的具体路径才发现——其获得数据的文件并没有清除,而是一个共享的位置,不在本地。而且这个位置是很久前已经弃用的,之所以找到这个位置是因为之前配置了一个环境变量。