Fork me on GitHub

UE5 MPCook 流程

UE5 MPCook 流程

首先给出 MPCook 的时序图
请点击查看大图

MPCook 时序图

然后讲数据结构

1. 核心类

  • IWorkerRequests

    • 协调多进程/单进程任务调度的核心接口

    • 它的派生类:FWorkerRequestsLocalFWorkerRequestsRemote

      • 派生类中的成员变量 FExternalRequests, FCookWorkerClient
        • ExternalRequests 中的成员变量 FFilePlatformRequest
  • FCookWorkerClient, FCookWorkerServer

  • CookDirector

  • IMPCollector

1.1 IWorkerRequests

用于发送请求到该进程的 Cooker 的接口,以及用于将信息返回给其 Director 的接口。

在单进程模式下,这些函数会被直接传递到本地的 COTFS(CookOnTheFlyServer)。

在多进程模式下,这些函数则通过进程间通信的方式实现,与 CookDirector 进行消息交互。

抽象类,所有函数都是虚函数且 (virtual xxx = 0;),只作声明,实现由派生类 FWorkerRequestsLocalFWorkerRequestRemote 实现。

image-20250411204048247

1.1.1 任务队列管理

  • 外部请求处理

    作为 Worker 进程(执行具体 cook 任务的进程)与 Director 进程(任务调度中心)间的通信桥梁,管理来自不同源的 cook 请求和回调

    • 优先级处理
      • 回调 Callbacks 优先于普通烹饪请求(如 DequeueNextCluster)。
    • 无锁检查
      • 通过 HasExternalRequestsGetNumExternalRequests 快速判断待处理任务,优化性能。
    • 批量出队
      • 支持按类型(回调 / Cook) 或全部出队请求,适应不同场景(如取消任务时的清理)。

1.1.2 多进程协调

  • 跨进程通信抽象

    在单进程模式下直接在操作本地 CookOnTheFlyServer,多进程时封装 IPC (进程间通信) 细节。

    • 任务分发
      • 通过 AddCookOnTheFlyRequest, AddStartCookByTheBookRequest 等方法添加动态或预定义任务。
    • 事件等待
      • WaitForCookOnTheFlyEvents 用于同步多进程间的事件完成状态。

1.1.3 资源生成与状态跟踪

  • 包(Package)生命周期管理
    • 动态发现资源
      • QueueDiscoveredPackage 将新发现的资源加入处理队列。
    • 生成资源完成
      • EndQueueGeneratedPackages 标记生成结束阶段,可能触发后续处理。
    • 状态上报
      • ReportDemoteToldleReportPromoteToSaveComplete 通知任务状态变化(如空闲、保存完成),用于进度跟踪。

等等,此处更详细的信息不再说明。

1.2 CookWorkerRequestsLocal 和 CookWorkerRequestRemote

IWorkerRequests 的派生类,上面说的 任务队列管理多进程协调资源生成与状态跟踪在这里具体实现。

  • FCookWorkerRequestsLocal

比基类 IWorkerRequests 多了一个私有成员变量 FExternalRequests

image-20250411204431878

上面各种方法的实现也与这个类相关,如

image-20250411204508798

FCookWorkerRequestsLocal 是单进程 Cook 相关,CookOnTheFly 和 CookByTheBook 通用。

  • FCookWorkerRequestsRemote

FCookWorkerRequestsRemote 是多进程 Worker 用到的

比基类 IWorkerRequests 多两个私有成员变量 FExternalRequestsFCookWorkerClient 以及一些私有函数,主要是报错的 log 方法

image-20250411205143372

1.3 ExternalRequests

里面有成员变量 FFilePlatformRequest ,因此先讲它

1.3.1 FilePlatformRequest

结构体 FFilePlatformRequest 是 UE CookSystem 中用于表示 单个文件(资源)的 Cook 请求 的数据结构。

封装了 需要 Cook 的文件名、目标平台、回调函数等信息,是调度器 (Scheduler) 处理外部请求的核心单元。

且不使用被 scheluler 内部使用的 FPackageData

1. 核心功能与设计目的
  1. 封装 Cook 请求的元数据

    描述哪个文件需要被 Cook,针对哪些目标平台,以及请求处理完成的回调。

  2. 支持多平台 Cook

    允许一个文件同时为多个目标平台(如 Windows, PS5, XBox 等)生成资源。

  3. 异步任务管理

    通过回调机制(FCompletionCallback) 通知请求处理结果(成功、失败、取消等)。

  4. 动态调整请求属性

    支持运行时修改目标平台、紧急状态(Urgent)等参数。

2. 主要成员解析
  1. 关键数据成员

    成员 类型 作用
    Filename FName 需要Cook的文件名(如 /Game/AssetName),使用优化哈希性能
    Platforms TArray<const ITargetPlatform*> 目标平台列表(如 Windows, Android),决定生成哪些平台的资源
    CompletionCallback FCompletionCallback 请求完成时的回调函数,用于通知调用方结果。
    Instigator FInstigator 请求的发起者信息(如用户操作、系统事件),用于调试和日志追踪。
    bUrgent bool 紧急标志,若为 true 则请求会被优先处理。

    重点的说一下:

    • FCompletionCallback

      image-20250414115412609

      当请求的 package 完成 cook 的时候的回调函数

    • FInstigator

      • 用于追踪资源 (Package) 被发现并加入 cook 队列的原因和来源。(后面有具体讲解)。
  2. 构造函数

    • 多平台支持

      提供多个构造函数重载,支持从多个平台(InPlatform) 或平台数组(InPlatforms) 初始化请求。

    • 移动语义优化

      使用 TArray<...>&&FInstigator&& 移动语义减少数据拷贝开销(如大数组传递时)。

  3. 成员函数

    函数 作用
    SetUrgent / IsUrgent 设置或查询请求的紧急状态
    AddPlatform / RemovePlatform 动态增删目标平台
    RemapTargetPlatforms 替换目标平台引用(用于热重载或配置更新)
    IsValid 验证请求是否有效(如文件名非空、至少有一个平台)
    ToString 生成可读字符串

3. 设计亮点
  1. 使用 FName 而非 FString 存储文件名,优化哈希比较性能

    关于为什么使用 FName 能优化性能,主要是 FName 是存储在全局表中的,更多具体细节,见 《UE FName.md》

  2. 移动语义减少数据拷贝

    image-20250414111904261

1.3.2 FInstigator

FFilePlatformRequest 中的构造函数中用到了 FInstigator ,也就是记录了 策动者

  • instigator
    • n. 策动者;煽动者;教唆者
    • instigate v. 使(正式)开始,使发生;鼓动

image-20250414113926375

FInstigator 结构体用于 追踪资源(Package)被发现并加入 Cook 队列的原因和来源。它结合 EInstigator 枚举的类别(Category)和可选的引用者(Referencer),记录资源被 Cook 的上下文信息。


核心作用

  1. 记录资源被发现的来源
    • Category: 类型 EInstigator: 表示资源被发现的 “原因” 或 “触发途径”。例如:
      • StartupPackage: 引擎启动时必须加载的核心资源。
      • CommandLinePackage: 通过命令行参数显式指定的资源。
      • Dependency: 因其它资源依赖而被间接发现。
    • Referencer: 类型 FName: 可选的引用者名称,用于标识触发该资源发现的直接源头(如依赖他的父资源路径)。
  2. 调试与 log
    • 当资源 cook 出现问题时,通过 FInstigator::ToString() 生成的字符串可以快速定位触发该资源 cook 的代码路径或外部输入。
    • 例如:若资源因资源 B 的依赖被 cook,log 会显示类似 Dependency(Referencer=/Game/B)
  3. 依赖链追踪
    • 在复杂资源依赖场景中,通过 Referencer 字段可以构建依赖链,帮助解决循环依赖或优化不必要的依赖。
  4. 优先级与调度决策
    • 不同 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)

设计意义

  1. 透明化 cook 流程

    通过记录每个资源的触发来源,开发者可以清晰地了解哪些代码路径或配置项导致了 package 被 cook,避免黑盒操作。

  2. 优化性能

    分析高频 Category 可以针对性优化。例如:若大量资源因 SoftDependency 被加载,可能需要重构依赖关系。

  3. 错误隔离

    当某个资源 cook 失败时,通过 FInstigator 快速定位是配置错误(如 IniMapSection)、依赖问题(如 Dependency) 还是外部输入问题(如 CommandLinePackage)。

  4. 扩展性

    EInstigator 枚举通过宏扩展开定义,新增类别只需修改宏,无需改动 FInstigator 结构体或相关逻辑。

1.2.3 ExternalRequests

FExternalRequests 是 UE 中一个用于 管理外部 Cook 请求 和 回调任务的线程安全容器。以下是其核心功能解析:

1. 核心职责
  1. 请求管理
    • 回调请求 Callbacks: 通过 AddCallback 添加的即时任务(如事件响应),调度器会优先按 FIFO 顺序执行。
    • Cook 请求 CookRequests: 通过 EnqueueUnique 添加的文件处理任务(如打包资源到特定平台),支持 去重合并。同一文件的多次请求会合并为一个,记录所有目标平台。
  2. 线程安全
    • 使用 FCriticalSection 锁 (类中的成员变量名为 RequestLock) 保护内部数据结构(Queue, RequestMap, Callbacks)。
    • 通过原子变量 RequestCount 提供无锁的请求数量查询(GetNumRequestsHasRequests),用于快速判断是否需要加锁处理。
2. 关键操作
  • 添加请求

    • 回调请求:直接追加到 Callbacks 数组,触发事件通知调度器。
    • Cook 请求:通过 EnqueueUnique 添加时,如果文件已经存在,合并目标平台列表;否则插入队列(支持插队到队首)。
  • 取出请求

    • 优先级策略:回调请求优先于 Cook 请求(DequeueNextCluster)。
    • 批量处理:每次取出全部回调或 Cook 请求,减少锁竞争。
  • 平台管理

    • 移除平台OnRemoveSessionPlatform 清理指定平台相关的 Cook 请求。
    • 平台重映射RemapTargetPlatforms 更新请求中的平台指针(如热更新配置)。
3. 数据结构
  • Queue环形缓冲区 TRingBuffer):按顺序保存 cook 请求的文件名(FName),用于 FIFO 处理。
  • RequestMap (哈希表 TMap<FName, FFilePlatformRequest>): 以文件名为 key,存储完整的 cook 请求信息(FFilePlatformRequest),包含目标平台列表。
  • Callbacks (回调数组):存储所有待处理的回调任务。
4. 同步机制
  • CookRequestEvent 事件
    • 调度器在空闲时等待此事件,当新请求加入(如 AddCallbackEnqueueUnique 调用)时触发,唤醒 scheduler 处理。
5. 典型流程
  1. 添加请求
    • 用于调用 AddCallbackEnqueueUnique 添加任务,锁内更新数据结构并递增 RequestCount
    • 触发 CookRequestEvent 通知 Scheduler。
  2. 处理请求
    • Scheduler 通过 DequeueNextCluster 取出任务:先处理所有回调,再处理 cook 请求。
    • 批量取出减少锁争用,处理完成后更新 RequestCount
  3. 清理与调试
    • EmptyRequestsDequeueAll 用于取消任务或重置状态。
    • LogAllRequestedFiles 输出 log 帮助调试。

更多的细节可以看 《UE ExternalRequests.md》

1.4 FCookWorkerServer

FCookWorkerServer 是 UE 多进程资源 Cook 系统中负责 与单个 CookWorker 进程通信和任务管理 的核心类。

1.4.1 进程间通信管理

  • Socket 通信
    • 通过 FSocket 与远端 FCookWorkerClient 建立 TCP 连接,实现双向数据传输。
  • 消息队列
    • 使用 SendBufferReceiveBuffer 管理待发送/接收的数据包,支持异步消息处理。
  • 即时与队列发送
    • 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) 智能分配任务,减少子进程间的数据争用。
  • 任务回收
    • 通过 RemoveFromWorkerReassignAbortedPackages 处理异常(如进程崩溃),重新分配未完成任务。

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 的网络通信,减轻主线程负担。
  • 消息类型

    • 定义了多种消息类型 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 列表中。
  • 连接流程
    1. CookWorker 启动后,向 CookDirector监听 socket 发起 TCP 连接。
    2. CookDirector 接受连接后,将 socket 存储在 FPendingConnection 中,等待 CookWorker 发送 FWorkerConnectMessage 以完成身份验证。
    3. 一旦身份验证完成,连接会被转移到对应的 FCookWorkerServer 实例中,用于后续通信。
4.2.2 消息类型结构体

FCookDirector 通过消息机制与 CookWorker 进行通信,主要消息类型包括:

  • FWorkerConnectMessage

    • CookWorkerCookDirector 发送,表明自己已经准备好接收配置和任务。
    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 作为响应。
  • FRetractionRequestMessageFRetractionResultsMessage:

    • FRetractionRequestMessage:

      • CookDirectorCookWorker 发送,请求撤销部分已经分配的包。
    • FRetractionResultsMessage:

      • CookWorker 响应,告知哪些包被撤销。
    • 这些消息用于 负载动态均衡,当某个 CookWorker 超载时,CookDirector 会将任务重新分配给其他空闲的 CookWorker

  • FHeartbeatMessage

    • 用于检测 CookWorker 是否存活,防止连接中断。
    • TickHeartbeat 函数定期发送心跳消息,并检查 CookWorker 的响应。
    • 如果心跳超时,CookDirector 会将对应 CookWorker 标记为断开连接,并重新分配其任务。

消息的序列化和反序列化基于 CompactBinary 格式,通过 FCbWriterFCbObjectView 实现。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

image-20250801145117384

这里接收了三个参数 FSocket, FReceiveBufferTArray<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)
       {}
};
posted @ 2025-09-19 13:39  icewalnut  阅读(12)  评论(0)    收藏  举报