UE5 MPCook 流程
UE5 MPCook 流程
首先给出 MPCook 的时序图
请点击查看大图
然后讲数据结构
1. 核心类
-
IWorkerRequests
-
协调多进程/单进程任务调度的核心接口
-
它的派生类:
FWorkerRequestsLocal
和FWorkerRequestsRemote
- 派生类中的成员变量
FExternalRequests
,FCookWorkerClient
- ExternalRequests 中的成员变量
FFilePlatformRequest
- ExternalRequests 中的成员变量
- 派生类中的成员变量
-
-
FCookWorkerClient
,FCookWorkerServer
-
CookDirector
-
IMPCollector
1.1 IWorkerRequests
用于发送请求到该进程的 Cooker 的接口,以及用于将信息返回给其 Director 的接口。
在单进程模式下,这些函数会被直接传递到本地的 COTFS(CookOnTheFlyServer)。
在多进程模式下,这些函数则通过进程间通信的方式实现,与 CookDirector 进行消息交互。
抽象类,所有函数都是虚函数且 (virtual xxx = 0;
),只作声明,实现由派生类 FWorkerRequestsLocal
和 FWorkerRequestRemote
实现。
1.1.1 任务队列管理
-
外部请求处理:
作为 Worker 进程(执行具体 cook 任务的进程)与 Director 进程(任务调度中心)间的通信桥梁,管理来自不同源的 cook 请求和回调。
- 优先级处理:
- 回调 Callbacks 优先于普通烹饪请求(如
DequeueNextCluster
)。
- 回调 Callbacks 优先于普通烹饪请求(如
- 无锁检查:
- 通过
HasExternalRequests
和GetNumExternalRequests
快速判断待处理任务,优化性能。
- 通过
- 批量出队:
- 支持按类型(回调 / Cook) 或全部出队请求,适应不同场景(如取消任务时的清理)。
- 优先级处理:
1.1.2 多进程协调
-
跨进程通信抽象:
在单进程模式下直接在操作本地
CookOnTheFlyServer
,多进程时封装 IPC (进程间通信) 细节。- 任务分发:
- 通过
AddCookOnTheFlyRequest
,AddStartCookByTheBookRequest
等方法添加动态或预定义任务。
- 通过
- 事件等待:
WaitForCookOnTheFlyEvents
用于同步多进程间的事件完成状态。
- 任务分发:
1.1.3 资源生成与状态跟踪
- 包(Package)生命周期管理
- 动态发现资源:
QueueDiscoveredPackage
将新发现的资源加入处理队列。
- 生成资源完成:
EndQueueGeneratedPackages
标记生成结束阶段,可能触发后续处理。
- 状态上报:
ReportDemoteToldle
和ReportPromoteToSaveComplete
通知任务状态变化(如空闲、保存完成),用于进度跟踪。
- 动态发现资源:
等等,此处更详细的信息不再说明。
1.2 CookWorkerRequestsLocal 和 CookWorkerRequestRemote
是 IWorkerRequests
的派生类,上面说的 任务队列管理,多进程协调,资源生成与状态跟踪在这里具体实现。
FCookWorkerRequestsLocal
比基类 IWorkerRequests
多了一个私有成员变量 FExternalRequests
上面各种方法的实现也与这个类相关,如
FCookWorkerRequestsLocal
是单进程 Cook 相关,CookOnTheFly 和 CookByTheBook 通用。
FCookWorkerRequestsRemote
FCookWorkerRequestsRemote
是多进程 Worker 用到的
比基类 IWorkerRequests
多两个私有成员变量 FExternalRequests
和 FCookWorkerClient
以及一些私有函数,主要是报错的 log 方法
1.3 ExternalRequests
里面有成员变量 FFilePlatformRequest
,因此先讲它
1.3.1 FilePlatformRequest
结构体 FFilePlatformRequest
是 UE CookSystem 中用于表示 单个文件(资源)的 Cook 请求 的数据结构。
封装了 需要 Cook 的文件名、目标平台、回调函数等信息,是调度器 (Scheduler) 处理外部请求的核心单元。
且不使用被 scheluler 内部使用的 FPackageData
。
1. 核心功能与设计目的
-
封装 Cook 请求的元数据
描述哪个文件需要被 Cook,针对哪些目标平台,以及请求处理完成的回调。
-
支持多平台 Cook
允许一个文件同时为多个目标平台(如 Windows, PS5, XBox 等)生成资源。
-
异步任务管理
通过回调机制(
FCompletionCallback
) 通知请求处理结果(成功、失败、取消等)。 -
动态调整请求属性
支持运行时修改目标平台、紧急状态(Urgent)等参数。
2. 主要成员解析
-
关键数据成员
成员 类型 作用 Filename
FName
需要Cook的文件名(如 /Game/AssetName
),使用优化哈希性能Platforms
TArray<const ITargetPlatform*>
目标平台列表(如 Windows, Android),决定生成哪些平台的资源 CompletionCallback
FCompletionCallback
请求完成时的回调函数,用于通知调用方结果。 Instigator
FInstigator
请求的发起者信息(如用户操作、系统事件),用于调试和日志追踪。 bUrgent
bool
紧急标志,若为 true
则请求会被优先处理。重点的说一下:
-
FCompletionCallback
当请求的 package 完成 cook 的时候的回调函数
-
FInstigator
- 用于追踪资源 (Package) 被发现并加入 cook 队列的原因和来源。(后面有具体讲解)。
-
-
构造函数
-
多平台支持
提供多个构造函数重载,支持从多个平台(
InPlatform
) 或平台数组(InPlatforms
) 初始化请求。 -
移动语义优化
使用
TArray<...>&&
和FInstigator&&
移动语义减少数据拷贝开销(如大数组传递时)。
-
-
成员函数
函数 作用 SetUrgent
/IsUrgent
设置或查询请求的紧急状态 AddPlatform
/RemovePlatform
动态增删目标平台 RemapTargetPlatforms
替换目标平台引用(用于热重载或配置更新) IsValid
验证请求是否有效(如文件名非空、至少有一个平台) ToString
生成可读字符串
3. 设计亮点
-
使用
FName
而非FString
存储文件名,优化哈希比较性能关于为什么使用 FName 能优化性能,主要是
FName
是存储在全局表中的,更多具体细节,见 《UE FName.md》 -
移动语义减少数据拷贝
1.3.2 FInstigator
FFilePlatformRequest
中的构造函数中用到了 FInstigator
,也就是记录了 策动者
- instigator
- n. 策动者;煽动者;教唆者
- instigate v. 使(正式)开始,使发生;鼓动
FInstigator
结构体用于 追踪资源(Package)被发现并加入 Cook 队列的原因和来源。它结合 EInstigator
枚举的类别(Category)和可选的引用者(Referencer),记录资源被 Cook 的上下文信息。
核心作用
- 记录资源被发现的来源
Category
: 类型EInstigator
: 表示资源被发现的 “原因” 或 “触发途径”。例如:StartupPackage
: 引擎启动时必须加载的核心资源。CommandLinePackage
: 通过命令行参数显式指定的资源。Dependency
: 因其它资源依赖而被间接发现。
Referencer
: 类型FName
: 可选的引用者名称,用于标识触发该资源发现的直接源头(如依赖他的父资源路径)。
- 调试与 log
- 当资源 cook 出现问题时,通过
FInstigator::ToString()
生成的字符串可以快速定位触发该资源 cook 的代码路径或外部输入。 - 例如:若资源因资源 B 的依赖被 cook,log 会显示类似
Dependency(Referencer=/Game/B)
。
- 当资源 cook 出现问题时,通过
- 依赖链追踪
- 在复杂资源依赖场景中,通过
Referencer
字段可以构建依赖链,帮助解决循环依赖或优化不必要的依赖。
- 在复杂资源依赖场景中,通过
- 优先级与调度决策
- 不同
Category
可能影响资源处理的优先级。例如:AlwaysCookMap
类别的资源可能被优先处理。Unsolicited
(未请求的资源)可能需要额外验证。
- 不同
结合 EInstigator
枚举的具体场景
EInstigator 值 | 场景 |
---|---|
StartupPackage |
引擎启动时自动加载的核心资源(如默认材质类、基础蓝图类) |
CommandLinePackage |
通过命令行参数 -cookpackage=/Game/Asset 显式指定需要 Cook 的资源。 |
Dependency / HardDependency |
资源因其它资源的硬依赖被引用(如静态引用的材质或网格)。Referencer 记录父资源路径。 |
CookOnTheFly |
动态运行时按需 Cook 触发的资源请求。 |
AssetManagerModifyCook |
资源管理器(AssetManager)在 cook 过程中动态添加的资源(如主资产列表中的条目)。IniMapSection |
IterativeCook |
增量 cook 模式下,仅处理已修改的资源。 |
代码示例
// 1. 因依赖被发现的资源
FInstigator DependencyInstigator(
EInstigator::Dependency,
FName("/Game/Characters/Hero/BP_Hero") // 引用者:副资源路径
);
// 2. 通过命令行指定的资源
FInstigator CommandLineInstigator(EInstigator::CommandLinePackage);
// 3. 动态按需 cook 请求
FInstigator CookOnTheFlyInstigator(EInstigator::CookOnTheFly);
// 输出调试信息
UE_LOG(LogCook, Display, TEXT("Instigator:: %s"), *DependencyInstigator.ToString());
// 输出:Dependency(Referencer=/Game/Characters/Hero/BP_Hero)
设计意义
-
透明化 cook 流程
通过记录每个资源的触发来源,开发者可以清晰地了解哪些代码路径或配置项导致了 package 被 cook,避免黑盒操作。
-
优化性能
分析高频
Category
可以针对性优化。例如:若大量资源因SoftDependency
被加载,可能需要重构依赖关系。 -
错误隔离
当某个资源 cook 失败时,通过
FInstigator
快速定位是配置错误(如IniMapSection
)、依赖问题(如Dependency
) 还是外部输入问题(如CommandLinePackage
)。 -
扩展性
EInstigator
枚举通过宏扩展开定义,新增类别只需修改宏,无需改动FInstigator
结构体或相关逻辑。
1.2.3 ExternalRequests
FExternalRequests
是 UE 中一个用于 管理外部 Cook 请求 和 回调任务的线程安全容器。以下是其核心功能解析:
1. 核心职责
- 请求管理
- 回调请求 Callbacks: 通过
AddCallback
添加的即时任务(如事件响应),调度器会优先按 FIFO 顺序执行。 - Cook 请求 CookRequests: 通过
EnqueueUnique
添加的文件处理任务(如打包资源到特定平台),支持 去重合并。同一文件的多次请求会合并为一个,记录所有目标平台。
- 回调请求 Callbacks: 通过
- 线程安全
- 使用
FCriticalSection
锁 (类中的成员变量名为RequestLock
) 保护内部数据结构(Queue
,RequestMap
,Callbacks
)。 - 通过原子变量
RequestCount
提供无锁的请求数量查询(GetNumRequests
和HasRequests
),用于快速判断是否需要加锁处理。
- 使用
2. 关键操作
-
添加请求
- 回调请求:直接追加到
Callbacks
数组,触发事件通知调度器。 - Cook 请求:通过
EnqueueUnique
添加时,如果文件已经存在,合并目标平台列表;否则插入队列(支持插队到队首)。
- 回调请求:直接追加到
-
取出请求:
- 优先级策略:回调请求优先于 Cook 请求(
DequeueNextCluster
)。 - 批量处理:每次取出全部回调或 Cook 请求,减少锁竞争。
- 优先级策略:回调请求优先于 Cook 请求(
-
平台管理
- 移除平台:
OnRemoveSessionPlatform
清理指定平台相关的 Cook 请求。 - 平台重映射:
RemapTargetPlatforms
更新请求中的平台指针(如热更新配置)。
- 移除平台:
3. 数据结构
Queue
(环形缓冲区TRingBuffer
):按顺序保存 cook 请求的文件名(FName
),用于 FIFO 处理。RequestMap
(哈希表TMap<FName, FFilePlatformRequest>
): 以文件名为 key,存储完整的 cook 请求信息(FFilePlatformRequest
),包含目标平台列表。Callbacks
(回调数组):存储所有待处理的回调任务。
4. 同步机制
- CookRequestEvent 事件
- 调度器在空闲时等待此事件,当新请求加入(如
AddCallback
或EnqueueUnique
调用)时触发,唤醒 scheduler 处理。
- 调度器在空闲时等待此事件,当新请求加入(如
5. 典型流程
- 添加请求
- 用于调用
AddCallback
或EnqueueUnique
添加任务,锁内更新数据结构并递增RequestCount
。 - 触发
CookRequestEvent
通知 Scheduler。
- 用于调用
- 处理请求
- Scheduler 通过
DequeueNextCluster
取出任务:先处理所有回调,再处理 cook 请求。 - 批量取出减少锁争用,处理完成后更新
RequestCount
。
- Scheduler 通过
- 清理与调试
EmptyRequests
和DequeueAll
用于取消任务或重置状态。LogAllRequestedFiles
输出 log 帮助调试。
更多的细节可以看 《UE ExternalRequests.md》
1.4 FCookWorkerServer
FCookWorkerServer
是 UE 多进程资源 Cook 系统中负责 与单个 CookWorker 进程通信和任务管理 的核心类。
1.4.1 进程间通信管理
- Socket 通信:
- 通过
FSocket
与远端FCookWorkerClient
建立 TCP 连接,实现双向数据传输。
- 通过
- 消息队列:
- 使用
SendBuffer
和ReceiveBuffer
管理待发送/接收的数据包,支持异步消息处理。
- 使用
- 即时与队列发送:
SendMessage
: 立即发送关键消息(如心跳、终止指令)。AppendMessage
: 将非紧急消息(如任务结果)加入队列,通过TickCommunication
周期发送。
1.4.2 任务分配与监控
- 任务分发:
- 通过
AppendAssignments
将资源烹饪任务FPackageData
分配给 CookWorker,并附带依赖信息(ExtraDatas
)。
- 通过
- 任务状态跟踪:
- 维护
PackageToAssign
(待处理) 和PendingPackages
(处理中)列表,确保任务进度可控。
- 维护
- 异常处理:
AbortAssignment
:- 主动取消单个任务(如依赖失败)。
AbortAllAssignments
:- 强制回收所有任务(如进程崩溃),通过
OutPendingPackages
通知 Director 重新分配。
- 强制回收所有任务(如进程崩溃),通过
1.4.3 进程生命周期控制
- 进程启动:
LaunchProcess
调用系统 API 启动 CookWorker 子进程,传递命令行参数(如日志路径、ProfileId
)。
- 连接管理:
- 处理连接握手(
TryHandleConnectMessage
), 超时检测(TickWaitForConnect
),维护状态机EConnectStatus
。
- 处理连接握手(
1.5 FCookDirector
FCookDirector
是 UE 中用于 多进程 Cook 协调 的核心类,主要职责是管理和协调多个 CookWorker
进程,实现高效的分布式资源处理。
1. 多进程任务分配与负载均衡
1.1 分布式 Cook
将资源 Cook 任务(如材质、贴图等)分配给多个 CookWorker
子进程,利用多核 / 多机资源加速整体流程。
1.2 动态分配策略
- 算法支持:
- 提供
Striped
(简单分片) 和CookBurden
(基于任务复杂度)两种负载均衡算法,优化资源利用。
- 提供
- 请求图分析:
- 根据资源依赖关系(
RequestGraph
) 智能分配任务,减少子进程间的数据争用。
- 根据资源依赖关系(
- 任务回收:
- 通过
RemoveFromWorker
和ReassignAbortedPackages
处理异常(如进程崩溃),重新分配未完成任务。
- 通过
2. 进程生命周期管理
- 进程启动:
- 生成并配置
CookWorker
进程,传递命令行参数、日志路径等(GetLaunchInfo
)。
- 生成并配置
- 状态监控:
- 通过心跳机制(
TickHeartbeat
) 检测子进程活性,处理超时或僵死。
- 通过心跳机制(
- 优雅关闭:
ShutdownCookSession
确保所有子进程完成工作后安全退出,避免资源泄露。
3. 跨进程通信
- 双向通信:
- 消息广播:
BroadcastGeneratorMessage
向所有子进程发送全局事件(如资源生成完成)。
- 消息广播:
以下是网络相关的主要组件和机制:
-
通信协议
-
使用
CompactBinaryTCP
(在CompactBinaryTCP.h
中定义)进行序列化和数据传输。这种协议基于 紧凑二进制格式(Compact Binary,简称 Cb),适合高效传输结构化数据。CompactBinaryTCP 的讲解见
UE5 MPCook Cb.md
文档。
-
-
通信模式
FCookDirector
作为服务器端,通过监听 socket(WorkerConnectSocket
)接受来自CookWorker
的连接请求。每个CookWorker
是一个客户端,通过 TCP 连接与CookDirector
通信。
-
双线程模型:
- SchedulerThread:
- 通常是 UE 的主线程(GameThread),负责任务分配、状态更新等逻辑。
- CommunicateThread:
- 一个专门的线程(通过
FRunnableThread
实现),用于处理与CookWorker
的网络通信,减轻主线程负担。
- 一个专门的线程(通过
- SchedulerThread:
-
消息类型
- 定义了多种消息类型
FWorkerConnectMessage
,FRetractionRequestMessage
,FRetractionResultsMessage
,FHeartbeatMessage
等,用于不同场景的通信,如 连接建立、任务分配、任务撤销、心跳检测等。
- 定义了多种消息类型
4. 关键网络相关功能
4.1 监听 socket 的创建
TryCreateWorkerConnectSocket
负责创建 监听 Socket,用于接受 CookWorker
的连接请求:
bool FCookDirector::TryCreateWorkerConnectSocket()
{
ISocketSubsystem* SocketSybsystem = ISocketSubsystem::Get();
// 创建一个 Stream 式 Socket(NAME_Stream 表示 TCP 流)
WorkerConnectSocket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("CookDirector"), false);
if (!WorkerConnectSocket) return false;
TSharedPtr<FInternetAddr> LocalAddr = SocketSubsystem->CreateInternetAddr();
// 绑定到本地地址 SetAnyAddress
LocalAddr->SetAnyAddress();
// 绑定到指定的端口 WorkerConnectPort
LocalAddr->SetPort(WorkerConnectPort);
// 绑定
bool bSuccess = WorkerConnectSocket->Bind(*LocalAddr);
if (!bSuccess)
{
Sockets::CloseSocket(WorkerConnectSocket);
WorkerConnectSocket = nullptr;
return false;
}
// 设置监听队列长度为 8,允许最多 8 个未处理的连接请求
bSuccess = WorkerConnectSocket->Listen(8);
if (!bSuccess)
{
Sockets::CloseSocket(WorkerConnectSocket);
WorkerConnectSocket = nullptr;
return false;
}
// 记录连接的 URI(如 hostname:port),用于 CookWorker 连接
WorkerConnectAuthority = FString::Printf(
TEXT("%s:%d"),
*SocketSubsystem->GetLocalHostAddr(*GLog, false).ToString(),
WorkerConnectPort);
return true;
}
4.2 CookWorker 连接管理
4.2.1 PendingConnection
结构体
CookDirector
使用 FPendingConnection
结构体管理尚未完成初始化的 CookWorker
连接:
struct FPendingConnection
{
explicit FPendingConnection(FSocket* InSocket = nullptr)
: Socket(InSocket)
{}
FPendingConnection(FPendingConnection&& Other);
FPendingConnection(const FPendingConnection& Other) = delete;
~FPendingConnection();
FSocket* DetachSocket();
FSocket* Socket = nullptr;
// FReceiveBuffer 详见 CompactBinaryTCP.h
UE::CompactBinaryTCP::FReceiveBuffer Buffer;
};
- 功能:
- 每个
FPendingConnection
包含一个FSocket
和一个FReceiveBuffer
,用于接收来自CookServer
的初始消息(通常是FWorkerConnectMessage
)。 FCookDirector::TickWorkerConnects
函数(在后面会讲到)定期检查 监听 socket,接受新的连接请求,并将新连接添加到PendingConnections
列表中。
- 每个
- 连接流程:
CookWorker
启动后,向CookDirector
的 监听 socket 发起 TCP 连接。CookDirector
接受连接后,将 socket 存储在FPendingConnection
中,等待CookWorker
发送FWorkerConnectMessage
以完成身份验证。- 一旦身份验证完成,连接会被转移到对应的
FCookWorkerServer
实例中,用于后续通信。
4.2.2 消息类型结构体
FCookDirector
通过消息机制与 CookWorker
进行通信,主要消息类型包括:
-
FWorkerConnectMessage
CookWorker
向CookDirector
发送,表明自己已经准备好接收配置和任务。
struct FWorkerConnnectMessage : public IMPCollectorMessage { virtual void Write(FCbWriter& Writer) const override; virtual bool TryRead(FCbObjectView Object) override; int32 RemoteIndex = 0; static FGuid MessageType; };
IMPCollectorMessage
是一个被 IMPCollectors 使用的解析为 C++ 结构体的信息的基类。具体见UE5_MPCook_Cb 与 MPCollector.md
。- 包含
RemoteIndex
,标识CookWorker
的唯一索引。 - 用于初始化连接,
CookDirector
收到后会发送FInitialConfigMessage
作为响应。
-
FRetractionRequestMessage
和FRetractionResultsMessage
:-
FRetractionRequestMessage
:CookDirector
向CookWorker
发送,请求撤销部分已经分配的包。
-
FRetractionResultsMessage
:CookWorker
响应,告知哪些包被撤销。
-
这些消息用于 负载动态均衡,当某个
CookWorker
超载时,CookDirector
会将任务重新分配给其他空闲的CookWorker
。
-
-
FHeartbeatMessage
- 用于检测
CookWorker
是否存活,防止连接中断。 TickHeartbeat
函数定期发送心跳消息,并检查CookWorker
的响应。- 如果心跳超时,
CookDirector
会将对应CookWorker
标记为断开连接,并重新分配其任务。
- 用于检测
消息的序列化和反序列化基于 CompactBinary
格式,通过 FCbWriter
和 FCbObjectView
实现。FMPCollectorServerMessageContext
提供上下文,用于在消息处理时传递相关信息。
4.2.3 CookWorker 连接处理 TickWorkerConnects
TickWorkerConnects
函数负责处理新连接的建立和初始消息的接收,它被 TickCommunication
调用
void FCookDirector::TickWorkerConnects(ECookDirectorThread TickThread)
{
using namespace UE::CompactBinaryTCP;
if (!WorkerConnectSocket)
{
return;
}
bool bReadReady;
while (WorkerConnectSocket->HasPendingConnection(bReadReady) && bReadReady)
{
// Accept 是创建了一个新的 Socket?
FSocket* WorkerSocket = WorkerConnectSocket->Accept(TEXT("Client Connection"));
if (!WorkerSocket)
{
UE_LOG(LogCook, Warning, TEXT("Pending connection failed to create a ClientSocket."));
}
else
{
// 设置非阻塞
WorkerSocket->SetNonBlocking(true);
// PendingConnection 上面讲过了,用来管理尚未完成初始化的连接
// PendingConnections 是一个数组
PendingConnections.Add(FPendingConnection(WorkerSocket));
}
}
// TIterator 是一个模板类, 可以点进去看
for (TArray<FPendingConnection>::TIterator Iter(PendingConnections); Iter; ++Iter)
{
FPendingConnection& Conn = *Iter;
TArray<FMarshalledMessage> Messages;
// ConnectionStatus 有几种状态:Okay, Terminated, FormatError, Failed, Incomplete
EConnectionStatus Status;
// 核心函数 UE::CompactBinaryTCP::TryReadPacket,尝试读包,把数据存到 Conn.Buffer 这个缓存中,返回连接的状态
Status = TryReadPacket(Conn.Socket, Conn.Buffer, Messages);
if (Status != EConnectionStatus::Okay)
{
UE_LOG(LogCook, Warning,
TEXT("Pending connection failed before sending a WorkerPacket: %s"), DescribeStatus(Status));
// RemoveCurrent 是在 Array 中移除当前元素
// 也就是说连接状态不是 Okay 的直接从 PendingConnections 中移除了
Iter.RemoveCurrent();
}
}
}
核心函数:TryReadPacket
这里接收了三个参数 FSocket
, FReceiveBuffer
和 TArray<FMarshalledMessage>& Messages
(FMarshalledMessage
是封送消息,还没有序列化的消息)。走到了 FCompactBinaryTCPImpl::TryReadPacket
里面,具体看 "CbTCP_Grok.md" 文档
4.3 通信线程 CommunicationThread 的持续处理
4.3.1 通信线程创建
FCookDirector
使用一个独立的通信线程 CommunicationThread
来处理与 CookWorker
的网络通信,减少主线程的阻塞。
void FCookDirector::LaunchCommunicationThread()
{
if (!CommunicationThread && FPlatformProcess::SupportsMultithreading())
{
CommunicationThread = FRunnableThread::Create(&RunnableShunt, TEXT("FCookDirector"), 0, TPri_Normal);
}
}
uint32 FCookDirector::RunCommunicationThread()
{
// constexpr 是编译期确定值,和 const 仅此区别
constexpr float TickPeriod = 1.f;
constexpr float MinSleepTime = 0.001f;
for (;;)
{
double StartTime = FPlatformTime::Seconds();
// 这个函数在下面展开
TickCommunication(ECookDirectorThread::CommunicationThread);
double CurrentTime = FPlatformTime::Seconds();
// 如果通信的这一帧时长小于 1s,等够 1s
float RemainingDuration = StartTime + TickPeriod - CurrentTime;
if (RemainingDuration > .001f)
{
uint32 WaitTimeMilliseconds = static_cast<uint32>(RemainingDuration * 1000);
if (ShutdownEvent->Wait(WaitTimeMilliseconds))
{
break;
}
}
}
return 0;
}
- 功能:
- 通信线程以固定周期
TickPeriod = 1秒
调用TickCommunication
- 通信线程以固定周期
4.3.2 TickCommunication
TickCommunication
是网络通信的核心函数,负责处理所有 CookWorker
的消息:
void FCookDirector::TickCommunication(ECookDirectorThread TickThread)
{
bool bHasShutdownWorkers = false;
TickWorkerConnects(TickThread);
}
1.6 ECookAction
enum class ECookAction
{
Done, // The cook is complete; no requests remain in any non-idle state
Request, // Process the RequestQueue
Load, // Process the LoadQueue
LoadLimited, // Process the LoadQueue, stopping when loadqueuelength reaches the desired population level
Save, // Process the SaveQueue
SaveLimited, // Process the SaveQueue, stopping when savequeuelength reaches the desired population level
Poll, // Execute pollables which have exceeded their period
WaitForAsync, // Sleep for a time slice while we wait for async tasks to complete
YieldTick, // Progress is blocked by an async result. Temporarily exit TickMainCookLoop.
};
记录 Cook Action 状态的 Enum
FTickStackData
// 关于 Cooker 的当前帧的临时生命周期数据
struct FTickStackData
{
double LoopStartTime = 0.;
// 掩码,和 ECookOnTheSideResult 做逻辑运算
uint32 ResultFlags = 0;
FCookTimer Timer;
ECookTickFlags TickFlags;
bool bCookComplete = false;
bool bCookCancelled = false;
explicit FTickStackData(float TimeSlice, ECookTickFlags InTickFlags)
:Timer(TimeSlice), TickFlags(InTickFlags)
{}
};