
source:[UE-CORE-002 TickTaskManager](https://descriptive-cemetery-9e2.notion.site/UE-CORE-002-TickTaskManager-20da28348a39808cb364f311819b06dc#20fa28348a39807596ddc5508ea7b477)

---


# 一、概述

## 1.1 Tick 任务调度的重要性

在游戏引擎中，**Tick**（如 UE 的 Tick 以及 Unity 的 Update）是驱动动态内容更新的核心机制。每一帧的时间内，引擎需要完成大量的计算和逻辑处理，例如物理模拟、动画更新、AI 决策、用户输入响应等。这些任务的执行顺序和时机直接决定了游戏世界的稳定性和一致性。因此，**Tick 任务调度**的设计和实现是游戏引擎架构中的关键部分。

Tick 是一个每帧调用一次的函数，用于更新对象的状态或执行特定的逻辑。在 UE 中，几乎所有动态行为都依赖于 Tick 系统。以下是一些常见的 Tick 使用场景：

- **输入处理** ：如根据玩家输入，更新角色的位置和方向。
- **物理模拟** ：计算刚体动力学、碰撞检测和约束求解。
- **动画更新** ：驱动骨骼动画系统，确保角色动作流畅且与物理状态一致。
- **网络同步** ：将本地状态同步到服务器或其他客户端。
- **UI 更新** ：刷新界面元素的状态（如血条、计时器等）。

由于这些任务之间存在复杂的依赖关系（例如，物理结算必须先于动画更新），如果 Tick 任务的调度不当，可能会导致数据竞争、状态不一致甚至崩溃。

另外，由于每帧的时间是有限的，尤其是在高帧率（如 60 FPS 或更高）的情况下，每一帧只有约 16 毫秒的时间来完成所有任务。如果 Tick 系统没有经过良好的设计和优化，可能会导致以下问题：

- **性能瓶颈** ：高频 Tick 任务会占用大量 CPU 时间，影响其他系统的运行效率。
- **资源浪费** ：某些对象可能并不需要每帧都更新（例如远处的静态物体），但仍然被频繁调用。

同时，现代游戏引擎通常需要处理大量的异步任务（如数据加载与解码），为了支持这些异步任务，Tick 系统需要提供专门的机制，确保它们能够与其他任务正确协作。

综上所述，Tick 调度系统不仅是一个简单的“每帧调用”机制，更是游戏引擎运行时的核心支柱。它承担着以下几个重要职责：

1. **确保任务按正确的顺序执行** ：通过分组和依赖管理，避免逻辑冲突和状态不一致。
2. **优化性能** ：通过按需更新、多线程支持和细粒度控制，最大限度地提高运行效率。
3. **支持复杂场景** ：通过异步任务和动态注册机制，适应现代游戏开发中的多样化需求。

正因为 Tick 调度如此重要，UE 提供了一个专门的组件——**`FTickTaskManager`**，来管理和调度所有的 Tick 任务。它并非一个简单的功能优化，而是维系整个游戏世界稳定、高效、可预测运行的**核心基石**。

![Tick 任务调度核心与挑战](attachment:7bd27db8-d583-45aa-a558-11c3669e4e86:Editor___Mermaid_Chart-2025-06-11-030235.png)

Tick 任务调度核心与挑战

## 1.2 FTickTaskManager 的角色与定位

`FTickTaskManager` 并非一个孤立的系统，而是紧密集成在 UWorld 的 Tick 流程中，扮演着一个至关重要的角色。

大致可以从以下三个层面来精确定位 `FTickTaskManager` 的角色：

1. **UWorld 的中央调度核心**：`UWorld::Tick` 函数本身并不直接执行任何 Actor 或 Component 的 Tick 逻辑。它将这一复杂任务完全委托给了 `FTickTaskManager`。在每一帧，UWorld 会首先命令 `FTickTaskManager` 进行准备 (StartFrame)，然后根据预设的 Tick Group 顺序，多次调用 `FTickTaskManager` 的 `RunTickGroup` 来执行相应阶段的任务，最后再通知其进行清理 (EndFrame)。可以说，FTickTaskManager 是 `UWorld::Tick` 的“大脑”和“执行引擎”，负责将 UWorld 的更新意图转化为具体的、有序的任务执行。
2. **依赖关系解析器**：`FTickTaskManager` 的核心能力之一，是理解并处理 `FTickFunction` 之间通过 `AddPrerequisite` 建立的依赖关系。在 StartFrame 阶段，它会收集所有需要更新的 Tick 任务，并分析它们之间的前后置依赖，最终构建出一个有向无环图。这个依赖图精确地描述了本帧所有任务的执行约束，是保证逻辑正确性的基础。
3. **并发任务分发器**：`FTickTaskManager` 自身不直接执行并行计算，但它扮演着连接上层游戏逻辑与底层 Task Graph 系统的关键桥梁。在 `RunTickGroup` 阶段，它会将依赖图中那些没有前置约束、可以并行执行的任务，打包成 `FGraphEvent` 并提交给 Task Graph 系统。由 Task Graph 系统负责将这些任务高效地分配到不同的 CPU 工作线程上。因此，`FTickTaskManager` 的角色更像一个“计划者”和“分包商”，它制定了详细的施工计划（依赖图），然后将具体的施工任务（可并行的 Tick）分发给专业的施工队（Task Graph）。

总结而言，**FTickTaskManager 的定位是：一个位于 UWorld 内部，负责将海量、无序、且相互依赖的 Tick 任务，转化为一个结构化、有序、且部分可并行的执行计划，并将其提交给底层系统来完成的高级调度器。** 它是 UE 解决 Tick 任务调度三大难题（顺序、依赖、性能）的核心技术实现。

![FTickTaskManager 分层架构](attachment:1b0f4837-c508-43f3-b708-132be492a28f:Editor___Mermaid_Chart-2025-06-11-031205.png)

FTickTaskManager 分层架构

# 二、源码剖析

## 2.1 核心数据结构

### 2.1.1 FTickFunction

> **源码文件**：`Source/Runtime/Engine/Classes/Engine/EngineBaseTypes.h`

`FTickFunction` 是一个 **USTRUCT**，这意味着它可以被 UE 的反射系统识别。它本身**不是一个可执行的函数**，而是一个**描述 Tick 任务所有属性和状态的“数据包”或“配置文件”**。当一个 Actor 或 Component 需要逐帧更新时，它内部就会包含一个 `FTickFunction` 的派生实例，并将其注册到 `FTickTaskManager` 中。

---

1. **公开配置属性**

这部分成员变量主要用于配置一个 Tick 任务的基本行为。

```cpp
// 定义了这个Tick任务所属的Tick组，决定了其大致的执行阶段
UPROPERTY(...)
TEnumAsByte<enum ETickingGroup> TickGroup;

// 定义了这个Tick任务必须在此Tick组之前完成
UPROPERTY(...)
TEnumAsByte<enum ETickingGroup> EndTickGroup;

// 该任务是否在游戏暂停时也执行
UPROPERTY(...)
uint8 bTickEvenWhenPaused:1;

// 【关键】这个Tick功能是否在代码层面被永久启用。如果为false，它永远不会被注册。
UPROPERTY()
uint8 bCanEverTick:1;

// 如果为true，该任务在创建时默认是启用的，但后续可以动态关闭。
UPROPERTY(...)
uint8 bStartWithTickEnabled:1;

// 是否允许在专用服务器上运行
UPROPERTY(...)
uint8 bAllowTickOnDedicatedServer:1;

// Tick的执行频率（秒）。如果大于0，引擎会尝试按这个间隔来调用，而不是每帧都调。
UPROPERTY(...)
float TickInterval;
```

**分析**：

这部分定义了 Tick 任务的**静态行为蓝图**。`bCanEverTick` 是最重要的一个开关，它在编译时就决定了这个 Tick 功能是否存在。`TickGroup` 和 `EndTickGroup` 则是向 `FTickTaskManager` 声明其执行顺序的“意图”。开发者通过调整这些参数，可以精细地控制一个 Tick 任务的基础属性。

---

1. **内部状态与控制标志**

这部分主要是一些非 UPROPERTY 的 bool 标志，用于更精细地控制 Tick 的行为。

```cpp
// 是否允许将多个类似的Tick任务打包在一起执行以提升性能
uint8 bAllowTickBatching:1;

// 是否在本Tick组内优先执行，用于需要尽早开始的异步任务
uint8 bHighPriority:1;

// 【关键】如果为true，该任务可以在任何工作线程上并行执行，否则只能在游戏主线程。
uint8 bRunOnAnyThread:1;

// ... 其他一些实验性或特殊用途的标志 ...

// Tick任务的当前状态（启用、禁用、冷却中）
ETickState TickState : 2;
```

**分析：**

`bRunOnAnyThread` 是实现并行优化的关键。如果一个 Tick 任务的逻辑不依赖于任何游戏线程特有的数据（比如 UI、某些 UObject 操作），就可以将此标志设为 true，`FTickTaskManager` 就会放心地将它交给任意一个空闲的 CPU 核心去执行，从而与游戏主线程并行工作，缩短帧时间。

---

1. **核心功能性成员**

这部分是 `FTickFunction` 实现其调度能力的核心数据。

```cpp
// 【关键】存储该Tick任务的所有前置依赖项。
// FTickTaskManager在构建任务图时会读取这个数组，来确定任务间的先后关系。
TArray<struct FTickPrerequisite> Prerequisites;

// 【关键】一个懒加载的、包含所有运行时状态的内部数据结构。
// 只有当Tick函数被注册后，这个指针才会被分配内存。
// 这样做可以节省未注册的Tick函数的内存开销。
TUniquePtr<FInternalData> InternalData;
```

**分析：**

`Prerequisites` 是依赖系统的**物理实现**。任何通过 `AddPrerequisite()` 添加的依赖，最终都会被存储在这个数组中。`InternalData` 的设计则体现了**性能优化的思想**。对于成千上万个可能永远不会Tick的组件，引擎不会为它们分配额外的运行时数据，只有当 `RegisterTickFunction` 被调用时，才会按需创建 `FInternalData`，这是一种非常高效的内存管理策略。

`FInternalData` 内部包含了诸如 `TaskPointer`（指向底层T ask Graph 任务的指针）、`ActualStartTickGroup`（因依赖延迟后，实际开始执行的 Tick 组）等**运行时动态变化的状态**。将这些动态状态与静态配置分离，使得整个结构更加清晰。

---

1. **核心功能性函数**

这部分是 `FTickFunction` 对外暴露的主要API，以及其虚函数。

```cpp
// 注册/注销Tick函数到FTickTaskManager
ENGINE_API void RegisterTickFunction(class ULevel* Level);
ENGINE_API void UnRegisterTickFunction();

// 动态启用/禁用Tick
ENGINE_API void SetTickFunctionEnable(bool bInEnabled);

// 【关键】添加/移除依赖项
ENGINE_API void AddPrerequisite(UObject* TargetObject, struct FTickFunction& TargetTickFunction);
ENGINE_API void RemovePrerequisite(UObject* TargetObject, struct FTickFunction& TargetTickFunction);

// 【关键】纯虚函数，必须由派生类实现。这里是真正执行Tick逻辑的地方。
ENGINE_API virtual void ExecuteTick(...) PURE_VIRTUAL(,);

// 【关键】纯虚函数，用于在产生循环依赖等错误时，提供可读的诊断信息。
ENGINE_API virtual FString DiagnosticMessage() PURE_VIRTUAL(, ...);
```

**分析：**

`RegisterTickFunction` 和 `UnRegisterTickFunction` 是 Tick 任务生命周期的起点和终点。`AddPrerequisite` 是构建复杂依赖关系网的API入口。

最重要的两个**纯虚函数（PURE_VIRTUAL）** `ExecuteTick` 和 `DiagnosticMessage`，揭示了 `FTickFunction` 的设计模式：它是一个**抽象基类**，定义了一个**契约**。任何想要成为可Tick任务的类（如 `FActorTickFunction`），都必须实现这两个函数。`ExecuteTick` 负责“做什么”，`DiagnosticMessage` 负责“我是谁”。`FTickTaskManager` 在调度时，并不关心具体是谁在Tick，它只知道调用 `ExecuteTick` 这个标准接口即可，这是一种典型的**多态**应用。

---

**总结**：

`FTickFunction` 是一个设计精巧、高度工程化的数据结构。它完美地体现了以下设计思想：

- **配置与状态分离:** 将静态配置（UPROPERTY）与运行时动态数据（InternalData）分离。
- **懒加载:** 通过 `TUniquePtr` 按需分配 `InternalData`，优化内存使用。
- **抽象与多态:** 通过纯虚函数定义了一个统一的执行契约，让上层调度代码可以与具体实现解耦。
- **数据驱动:** 整个结构就是一个数据容器，其行为完全由内部的成员变量配置来驱动。

![FTickFunction 结构类图](attachment:3dbe052f-4415-4baf-8e3f-c0145e11d5d3:Editor___Mermaid_Chart-2025-06-11-084044.png)

FTickFunction 结构类图

### 2.1.2 FActorTickFunction & FActorComponentTickFunction

> **源码文件**：`Source/Runtime/Engine/Classes/Engine/EngineBaseTypes.h`

```cpp
/** 
* Tick function that calls AActor::TickActor
**/
USTRUCT()
struct FActorTickFunction : public FTickFunction
{
	GENERATED_USTRUCT_BODY()

	/**  AActor  that is the target of this tick **/
#if UE_WITH_REMOTE_OBJECT_HANDLE
	TObjectPtr<AActor> Target;
#else
	class AActor*	Target;
#endif

	/** 
		* Abstract function actually execute the tick. 
		* @param DeltaTime - frame time to advance, in seconds
		* @param TickType - kind of tick for this frame
		* @param CurrentThread - thread we are executing on, useful to pass along as new tasks are created
		* @param MyCompletionGraphEvent - completion event for this task. Useful for holding the completetion of this task until certain child tasks are complete.
	**/
	ENGINE_API virtual void ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) override;
	/** Abstract function to describe this tick. Used to print messages about illegal cycles in the dependency graph **/
	ENGINE_API virtual FString DiagnosticMessage() override;
	ENGINE_API virtual FName DiagnosticContext(bool bDetailed) override;

#if UE_WITH_REMOTE_OBJECT_HANDLE
	/** Exposes references to GC system */
	ENGINE_API void AddStructReferencedObjects(FReferenceCollector& Collector);
#endif
};
```

```cpp
/** 
* Tick function that calls UActorComponent::ConditionalTick
**/
USTRUCT()
struct FActorComponentTickFunction : public FTickFunction
{
	GENERATED_USTRUCT_BODY()

#if UE_WITH_REMOTE_OBJECT_HANDLE
	TObjectPtr<class UActorComponent> Target;
#else
	/**  AActor  component that is the target of this tick **/
	class UActorComponent*	Target;
#endif

	/** 
		* Abstract function actually execute the tick. 
		* @param DeltaTime - frame time to advance, in seconds
		* @param TickType - kind of tick for this frame
		* @param CurrentThread - thread we are executing on, useful to pass along as new tasks are created
		* @param MyCompletionGraphEvent - completion event for this task. Useful for holding the completetion of this task until certain child tasks are complete.
	**/
	ENGINE_API virtual void ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) override;
	/** Abstract function to describe this tick. Used to print messages about illegal cycles in the dependency graph **/
	ENGINE_API virtual FString DiagnosticMessage() override;
	ENGINE_API virtual FName DiagnosticContext(bool bDetailed) override;

	/**
	 * Conditionally calls ExecuteTickFunc if registered and a bunch of other criteria are met
	 * @param Target - the actor component we are ticking
	 * @param bTickInEditor - whether the target wants to tick in the editor
	 * @param DeltaTime - The time since the last tick.
	 * @param TickType - Type of tick that we are running
	 * @param ExecuteTickFunc - the lambda that ultimately calls tick on the actor component
	 */

	//NOTE: This already creates a UObject stat so don't double count in your own functions

	template <typename ExecuteTickLambda>
	static void ExecuteTickHelper(UActorComponent* Target, bool bTickInEditor, float DeltaTime, ELevelTick TickType, const ExecuteTickLambda& ExecuteTickFunc);	

#if UE_WITH_REMOTE_OBJECT_HANDLE
	/** Exposes references to GC system */
	ENGINE_API void AddStructReferencedObjects(FReferenceCollector& Collector);
#endif
};
```

由上面的源码可知，`FActorTickFunction` & `FActorComponentTickFunction` 这两个结构体都直接继承自 `FTickFunction`。它们的核心作用是**将抽象的 Tick 任务具体化**，使其与一个特定的 `AActor` 或 `UActorComponent` 实例绑定，并负责在 `ExecuteTick` 被调用时，真正地去执行那个实例的 `Tick` 或 `TickComponent` 方法。

在分别解析之前，我们先看它们完全相同的设计模式：

- **共同的核心设计**：
    1. **继承 `FTickFunction`:** 它们都继承了基类所有的配置属性（`TickGroup`等）、状态管理（`InternalData`）和API（`RegisterTickFunction`等）。
    2. **实现纯虚函数:** 它们都用 `override` 关键字实现了基类中定义的两个纯虚函数 `ExecuteTick` 和 `DiagnosticMessage`，从而满足了基类定义的“契约”。
    3. **持有目标指针:** 它们都包含一个名为 `Target` 的成员变量，用于**指向**它们所服务的具体 `UObject` 实例。

---

1. **`struct FActorTickFunction` 解析**

- **`Target` 成员**
    - **职责:** 这个指针就是 `FActorTickFunction` 的“服务对象”。它明确地指出了这个 Tick 任务是属于哪一个 `AActor` 实例的。
    - **类型选择:**
        - `TObjectPtr<AActor>`: 在较新的 UE 版本中，使用 `TObjectPtr` 是一种更现代、更安全的指针类型，它能被 GC 系统更好地追踪。
        - `AActor*`: 在旧版本或特定编译配置下，使用裸指针。
- **`ExecuteTick()` 实现**
    - **职责:** 这是**连接调度与执行的最后一环**。当 `FTickTaskManager` 调度到这个任务时，就会调用这个函数。
    - **内部实现（在 `.cpp` 文件中）:** 这个函数的实现非常直接，其核心逻辑为：检查 `Target` 指针是否有效，然后调用 `AActor` 类中真正的 `TickActor` 方法（`TickActor` 内部会再调用我们熟悉的 `virtual void Tick(float DeltaTime)`）。

```cpp
void FActorTickFunction::ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
	if (IsValid(Target))
	{
		if (TickType != LEVELTICK_ViewportsOnly || Target->ShouldTickIfViewportsOnly())
		{
			FScopeCycleCounterUObject ActorScope(Target);
			Target->TickActor(DeltaTime*Target->CustomTimeDilation, TickType, *this);
		}
	}
}
```

- **`DiagnosticMessage()` 实现**
    - **职责:** 当 `FTickTaskManager` 检测到循环依赖等问题时，会调用这个函数来获取一段可读的错误信息，用于日志输出。
    - **内部实现:** 它通常会返回类似 `FString::Printf(TEXT("FActorTickFunction for %s"), *Target->GetFullName())` 的字符串，清晰地指明是哪个 Actor 的 Tick 出了问题。

---

1. **`struct FActorComponentTickFunction` 解析**

它与 `FActorTickFunction` 的结构和原理几乎完全相同，只是服务对象不同。

- **`Target` 成员**
    - **职责:** 指向其所服务的具体 `UActorComponent` 实例。
- **`ExecuteTick()` 实现**
    - **内部实现:** 其核心逻辑与 `FActorTickFunction` 类似，但它最终调用的是 `Target` 组件的 `TickComponent` 方法：

```cpp
void FActorComponentTickFunction::ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
	TRACE_CPUPROFILER_EVENT_SCOPE(FActorComponentTickFunction::ExecuteTick);

	ExecuteTickHelper(Target, Target->bTickInEditor, DeltaTime, TickType, [this, TickType](float DilatedTime)
	{
		Target->TickComponent(DilatedTime, TickType, this);
	});
}
```

---

**总结**：

`FActorTickFunction` 和 `FActorComponentTickFunction` 是 `FTickFunction` 抽象概念的**具体化实现**。它们扮演着“适配器”（Adapter）的角色，将 `FTickTaskManager` 的泛型调度指令，精准地“翻译”为对特定 `AActor` 或 `UActorComponent` 实例的成员函数调用。

它们的设计体现了面向对象中**多态**和**继承**的核心思想，使得上层调度系统无需关心 Tick 任务的具体类型，从而实现了高度的解耦和可扩展性。

### 2.1.3 FInternalData

> **源码文件**：`Source/Runtime/Engine/Classes/Engine/EngineBaseTypes.h`

`FInternalData` 是一个专门用于存储 **已注册** 的 `FTickFunction` 在**运行时动态变化的状态**的内部数据结构。

它的存在本身就是一个**性能优化设计**。`FTickFunction` 通过 `TUniquePtr<FInternalData> InternalData;` 来持有它。这意味着，只有当一个 Tick 任务通过 `RegisterTickFunction()` 被激活时，引擎才会为其分配 `FInternalData` 的内存。对于成千上万个可能永远不会被注册的 Tick 任务（例如，一个从未被使用的组件），引擎不会浪费任何内存来存储它们的运行时状态。这是一种典型的 **懒加载** 策略。

---

1. **核心状态与注册信息**

```cpp
// 标志位，表示该Tick函数是否已向FTickTaskManager注册。
bool bRegistered : 1;

// 内部状态，决定了 TaskPointer 指针的类型和含义。
// ETickTaskState 枚举的可能值：NotQueued, Pending, HasTask, HasCompletionEvent
ETickTaskState TaskState;

// 指向其所属的 FTickTaskLevel 的反向指针。
// 这使得FTickTaskManager可以快速地从一个FTickFunction找到它所在的Level。
class FTickTaskLevel* TickTaskLevel;
```

**分析:**

`bRegistered` 是生命周期的起点。`TaskState` 是整个 Tick 调度过程中最重要的状态机，`FTickTaskManager` 通过改变这个状态来管理一个 Tick 任务从“待处理”到“已排队”再到“已完成”的整个流程。`TickTaskLevel` 则提供了上下文信息。

---

1. **运行时 Tick Group 信息**

```cpp
// 由于依赖关系，一个Tick任务实际开始执行的Tick组可能晚于其配置的TickGroup。
// 这个变量记录了它在本帧实际开始的Tick组。
TEnumAsByte<enum ETickingGroup> ActualStartTickGroup;

// 类似地，记录了它在本帧保证会结束的Tick组。
TEnumAsByte<enum ETickingGroup> ActualEndTickGroup;
```

**分析:** 这组变量体现了依赖系统的动态性。一个配置为 `TG_PrePhysics` 的任务，如果它依赖的另一个任务直到 `TG_EndPhysics` 才完成，那么它自己的 `ActualStartTickGroup` 就会被动态调整为 `TG_EndPhysics` 之后的某个组。这是 `FTickTaskManager` 在 `StartFrame` 阶段进行依赖图解析后得出的**动态调度计划**。

---

1. **并发与任务图相关数据**

```cpp
// 【关键】一个void*指针，其具体指向的类型由 TaskState 决定。
// 它可能指向一个 FGraphEventRef（代表一个独立的Task Graph任务）或
// 其他内部任务结构。这是FTickFunction与底层Task Graph系统连接的纽带。
void* TaskPointer;

// 用于多线程同步的原子计数器，记录该Tick任务在哪一帧被访问过，以防止重复处理。
std::atomic<uint32> TickVisitedGFrameCounter;

// 记录该Tick任务在哪一帧被排入队列。
std::atomic<uint32> TickQueuedGFrameCounter;
```

**分析:**

`TaskPointer` 是与底层并行系统交互的核心。`FTickTaskManager` 将一个 `FTickFunction` 包装成一个 `Task Graph` 任务后，会将任务的句柄（Handle）存储在这里。`std::atomic` 的使用则表明这些变量会在多线程环境下被访问，用于保证在并行调度时，每个任务在一帧内只被处理一次，避免了竞态条件。

---

1. **Tick 间隔与冷却管理**

这部分成员专门用于实现 `TickInterval` 功能。

```cpp
// 标志位，缓存该函数是否因设置了TickInterval而被重新调度。
bool bWasInterval:1;

// 指向冷却列表中的下一个FTickFunction。
// FTickTaskManager内部维护了一个按冷却时间排序的链表来管理所有设置了TickInterval的任务。
FTickFunction* Next;

// 剩余的冷却时间（秒）。
// 它存储的是相对于链表中前一个元素的相对时间，这种设计可以高效地更新整个链表。
float RelativeTickCooldown;

// 上一次该函数被Tick时的游戏时间。
// 用于计算下一次应该在何时Tick。
float LastTickGameTimeSeconds;
```

**分析:** 这组数据揭示了 `TickInterval` 的实现原理。引擎并非为每个有间隔的 Tick 都创建一个独立的计时器，而是将它们组织成一个**高效的排序链表（冷却列表）**。每一帧，引擎只需要检查链表头部的任务，看看它的 `RelativeTickCooldown` 是否已经到期。这种设计避免了每帧遍历所有任务来检查时间，极大地提升了效率。

---

**总结**：

![FInternalData Mindmap](attachment:7a4ed083-c473-4b10-b01d-db2a3b05c91a:Editor___Mermaid_Chart-2025-06-11-080937.png)

FInternalData Mindmap

FInternalData 是一个纯粹的**运行时数据容器**，它的设计充满了对性能和效率的考量：

- **懒加载:** 只有在需要时才分配内存。
- **状态驱动:** 通过 ETickTaskState 状态机来管理复杂的任务生命周期。
- **动态调度:** 通过 ActualStartTickGroup 等变量记录依赖解析后的动态结果。
- **并发安全:** 使用原子变量来确保多线程环境下的数据一致性。
- **高效算法:** 通过精巧的链表和相对时间来管理 Tick 间隔，避免了不必要的计算。

FTickFunction 主体定义了“我是谁以及我想做什么”，而 FInternalData 则记录了“我现在正在做什么以及我接下来要去哪”。两者结合，构成了一个完整的、功能强大的 Tick 任务单元。

### 2.1.4 FTickPrerequisite

> **源码文件**：`Source/Runtime/Engine/Classes/Engine/EngineBaseTypes.h`

`FTickPrerequisite` 是一个专门用于**安全地存储和引用一个前置依赖 Tick 任务**的 `USTRUCT`。当我们在 `FTickFunction` 中调用 `AddPrerequisite` 时，实际上就是在其内部的 `Prerequisites` 数组中添加了一个 `FTickPrerequisite` 的实例。`FTickPrerequisite` 这个结构体虽然小，但却是实现 `FTickTaskManager` 依赖系统的关键“连接件”。它的核心设计目标是解决一个问题：**如何在保证内存安全的前提下，持有一个指向另一个 `FTickFunction` 的指针？**

---

```cpp
/**
 * This is small structure to hold prerequisite tick functions
 */
USTRUCT()
struct FTickPrerequisite
{
	GENERATED_USTRUCT_BODY()

	/** Tick functions live inside of UObjects, so we need a separate weak pointer to the UObject solely for the purpose of determining if PrerequisiteTickFunction is still valid. */
	TWeakObjectPtr<class UObject> PrerequisiteObject;

	/** Pointer to the actual tick function and must be completed prior to our tick running. */
	struct FTickFunction*		PrerequisiteTickFunction;

	/** Noop constructor. */
	FTickPrerequisite()
	: PrerequisiteTickFunction(nullptr)
	{
	}
	/** 
		* Constructor
		* @param TargetObject - UObject containing this tick function. Only used to verify that the other pointer is still usable
		* @param TargetTickFunction - Actual tick function to use as a prerequisite
	**/
	FTickPrerequisite(UObject* TargetObject, struct FTickFunction& TargetTickFunction)
	: PrerequisiteObject(TargetObject)
	, PrerequisiteTickFunction(&TargetTickFunction)
	{
		check(PrerequisiteTickFunction);
	}
	/** Equality operator, used to prevent duplicates and allow removal by value. */
	bool operator==(const FTickPrerequisite& Other) const
	{
		return PrerequisiteObject == Other.PrerequisiteObject &&
			PrerequisiteTickFunction == Other.PrerequisiteTickFunction;
	}
	/** Return the tick function, if it is still valid. Can be null if the tick function was null or the containing UObject has been garbage collected. */
	struct FTickFunction* Get()
	{
		if (PrerequisiteObject.IsValid(true))
		{
			return PrerequisiteTickFunction;
		}
		return nullptr;
	}
	
	const struct FTickFunction* Get() const
	{
		if (PrerequisiteObject.IsValid(true))
		{
			return PrerequisiteTickFunction;
		}
		return nullptr;
	}
};
```

---

1. **核心成员变量解析**

由上面的源码可知，这个结构体只有两个成员变量，但它们的设计配合得很巧妙。

```cpp
// 用于验证 PrerequisiteTickFunction 指针是否仍然有效的弱引用。
TWeakObjectPtr<class UObject> PrerequisiteObject;

// 指向实际的前置依赖Tick函数的裸指针。
struct FTickFunction* PrerequisiteTickFunction;
```

**分析：为什么定义两个指针？**

- `FTickFunction` 本身并不是一个 `UObject`，所以它不受 UE 的垃圾回收（GC）系统直接管理。它通常是作为 `UObject`（如 `AActor` 或 `UActorComponent`）的成员变量存在的。
- 如果直接持有一个 `FTickFunction*` 裸指针，我们无法知道它所依附的 `UObject` 是否已经被GC销毁。如果其宿主`UObject`被销毁，这个裸指针就会变成一个危险的**悬空指针**。
- **`TWeakObjectPtr` 的关键作用：**`TWeakObjectPtr`（弱对象指针）是 UE 提供的一种智能指针，它**不会**阻止其指向的 `UObject` 被 GC 回收。它的核心能力是提供了一个 `IsValid()` 方法。在访问裸指针之前，我们可以通过检查 `PrerequisiteObject.IsValid()` 来安全地判断那个 `UObject` 是否还存活。如果 `IsValid()` 返回 `false`，就意味着宿主对象已经被销毁，那么 `PrerequisiteTickFunction` 这个裸指针也必然失效了。
- **协同工作机制：**
    - `PrerequisiteObject` 扮演着“哨兵”的角色。
    - `PrerequisiteTickFunction` 存储着实际的目标地址。
    - 只有在“哨兵”确认安全（`IsValid()`）之后，我们才会去使用那个地址。

![FTickPrerequisite 安全访问机制](attachment:7c645043-e873-45c0-b3e2-f341eefbb72f:mermaid-diagram-2025-06-11-151136.png)

FTickPrerequisite 安全访问机制

---

1. **核心功能性函数解析**

- **构造函数**：

```cpp
FTickPrerequisite(UObject* TargetObject, struct FTickFunction& TargetTickFunction)
: PrerequisiteObject(TargetObject)
, PrerequisiteTickFunction(&TargetTickFunction)
{
    check(PrerequisiteTickFunction);
}
```

**分析：**

构造函数非常直白，它同时接收宿主 UObject 的指针和 FTickFunction 的引用，并将它们分别存入两个成员变量中。这确保了每次创建一个依赖关系时，安全验证所需的信息都被完整地记录下来。

---

- **Get() 方法**

```cpp
struct FTickFunction* Get()
{
    if (PrerequisiteObject.IsValid(true))
    {
        return PrerequisiteTickFunction;
    }
    return nullptr;
}
```

**分析：**

这是该结构体最核心的对外接口。它实践了我们上述提到的“先检查，后访问”的安全模式。

- 首先调用 PrerequisiteObject.IsValid(true)。参数 true 表示即使对象正在等待被清理（Pending Kill），也将其视为无效。
- 只有在弱指针验证通过，确认宿主 UObject 仍然存活的情况下，它才会返回那个可能有效的 PrerequisiteTickFunction 裸指针。
- 如果宿主对象已失效，它会安全地返回 nullptr，避免了任何访问悬空指针的风险。

FTickTaskManager 在构建依赖图时，就会通过调用这个 Get() 方法来安全地获取每一个依赖项。

---

**总结**：

FTickPrerequisite 虽然定义简单，但却是一个展示**如何在非 UObject 上下文中安全引用 UObject 成员**的优秀范例。它通过将一个**裸指针**与一个**用于生命周期验证的弱指针**巧妙地捆绑在一起，构建了一个既高效（直接访问裸指针）又安全（通过 TWeakObjectPtr 验证）的依赖引用机制。

---

### 2.1.5 ETickingGroup

> **源码文件**：`Source/Runtime/Engine/Classes/Engine/EngineBaseTypes.h`

`ETickingGroup` 是一个枚举类型，它定义了一系列离散的、有序的**“时间阶段”。`FTickTaskManager` 在一帧内，会严格按照这个枚举定义的顺序，一个接一个地执行属于每个阶段（Group）的 Tick 任务。**这套机制是 UE 用来解决 Tick 任务执行顺序和依赖关系问题的基石。

接下来，我将按照它们的实际执行顺序来逐一分析每个 Tick Group 的职责和设计目的。

（下面分析中提到的 ”注释描述“ 部分指的是源码中对于当前枚举值的注释。）

---

1. **`TG_PrePhysics` - 物理模拟前**

- **注释描述:** `Any item that needs to be executed before physics simulation starts.` (任何需要在物理模拟开始前执行的项)
- **职责:** 这是绝大多数游戏逻辑的默认执行阶段。所有决定物体“本帧想要如何运动”的逻辑都应该放在这里。
- **典型用例:**
    - **玩家输入处理:** 根据玩家的键盘/手柄输入，计算角色的移动向量。
    - **AI 决策:** AI Controller 在此决定本帧的移动目标或攻击行为。
    - **动画蓝图更新（非物理部分）:** 更新状态机，决定要播放哪个动画序列。
- **设计原因:** 物理引擎需要知道物体的“意图”（如目标速度、施加的力）才能开始计算。此阶段就是为了准备好所有这些输入数据。

---

1. **`TG_StartPhysics` - 物理模拟开始**

- **注释描述:** `Special tick group that starts physics simulation.` (启动物理模拟的特殊Tick组)
- **元数据:** `UMETA(Hidden)` - 表示这个选项通常不在编辑器中对用户暴露。
- **职责:** 一个内部阶段，用于触发物理引擎（如 Chaos）开始本帧的模拟计算。它更像是一个命令信号，而不是一个让用户注册任务的阶段。

---

1. **`TG_DuringPhysics` - 物理模拟中**

- **注释描述:** `Any item that can be run in parallel with our physics simulation work.` (任何可以与物理模拟并行运行的项)
- **职责:** 提供一个时间窗口，用于执行那些**不依赖**于本帧物理计算最终结果的、且可以并行化的任务。
- **典型用例:**
    - **异步场景查询:** 发起一些耗时的射线检测或形状检测，而不阻塞主线程。
    - 一些独立的视觉效果或系统更新。
- **设计原因:** 物理模拟是CPU密集型任务，通常在独立的线程上异步进行。`TG_DuringPhysics` 允许游戏主线程在等待物理计算的同时，去“插空”执行其他不相关的任务，从而提高CPU的利用率和整体性能。

---

1. **`TG_EndPhysics` - 物理模拟结束**

- **注释描述:** `Special tick group that ends physics simulation.` (结束物理模拟的特殊Tick组)
- **元数据:** `UMETA(Hidden)`
- **职责:** 这是一个关键的**同步点**。游戏主线程会在此等待，直到异步的物理模拟线程完成其所有计算。完成后，物理引擎会将计算出的最终结果（如新的位置、旋转、碰撞事件）写回到游戏世界中的对象上。

---

1. **`TG_PostPhysics` - 物理模拟后**

- **注释描述:** `Any item that needs rigid body and cloth simulation to be complete before being executed.` (任何需要刚体和布料模拟完成才能执行的项)
- **职责:** 执行所有**依赖于**本帧物理结算结果的逻辑。
- **典型用例:**
    - **骨骼动画更新:** 这是骨骼网格体组件的默认 Tick Group。它需要根据角色经过物理计算后的最终位置和速度来调整动画，例如使用 IK 将脚踩在正确的地面上，或根据速度混合不同的跑动动画。
    - **相机更新:** 相机需要跟随已经移动到最终位置的角色。
    - **碰撞事件响应:** 在这里处理 `OnComponentHit` 等事件是安全的，因为所有的碰撞信息此时都已生成。
- **设计原因:** 这是为了保证**“表现”基于“事实”**。物理结果就是本帧的“事实”，而动画、相机等视觉表现必须基于这个事实来进行调整，否则就会出现“脚滑”、穿模等视觉错误。

---

1. **`TG_PostUpdateWork` - 更新工作后**

- **注释描述:** `Any item that needs the update work to be done before being ticked.` (任何需要在更新工作完成后被Tick的项)
- **职责:** 在所有主要的游戏逻辑和视觉表现更新都完成后，执行一些收尾的更新任务。
- **典型用例:**
    - **布料模拟:** 布料通常需要附着在已经更新完动画的角色骨骼上，所以放在这里执行。
    - 一些最终的清理或同步逻辑。

---

1. **`TG_LastDemotable` - 可降级的最后任务**

- **注释描述:** `Catchall for anything demoted to the end.` (一个用于接收所有被降级到末尾任务的“捕集器”)
- **元数据:** `UMETA(Hidden)`
- **职责:** 优先级最低的阶段。用于执行那些即使在一帧内被跳过也不会造成严重问题的任务。

---

1. **`TG_NewlySpawned` - 特殊：新生成对象**

- **注释描述:** `...After every tick group this is repeatedly re-run until there are no more newly spawned items to run.` (在每个Tick组之后，这个组会重复运行，直到没有新生成的对象需要运行)
- **职责:** 这是一个非常特殊的“伪”组。它的目的是处理那些**在本帧的Tick过程中被动态生成（Spawn）出来的新Actor**。
- **工作机制:** 假设在 `TG_PrePhysics` 阶段，一个Actor A 的 Tick 函数 `Spawn` 了一个新的 Actor B。Actor B 错过了本帧的 `TG_PrePhysics` 调度。为了让 B 也能在**当前帧**就被更新（而不是等到下一帧），`FTickTaskManager` 在 `TG_PrePhysics` 执行完后，会立即检查并执行所有新生成的、属于`TG_PrePhysics` 的对象的Tick。这个过程会在每个主Tick组后都重复一次。
- **设计原因:** 解决了“帧内生成，帧内更新”的问题，使得动态生成的对象能够更及时地融入游戏世界，避免了一帧的延迟。

---

**总结**：

ETickingGroup 是 UE 对一帧游戏时间的**“时间切片”**。通过将 Tick 任务强制归入这些有序的“切片”中，FTickTaskManager 实现了对整个世界模拟流程的精确控制，从根本上保证了复杂系统中数据流的正确性和逻辑的稳定性。这是 UE 高性能和高保真模拟能力的架构基石。

![ETickingGroup 流程图](attachment:c79803ca-b0d3-4f5f-bbae-c6c9b68a267c:Editor___Mermaid_Chart-2025-06-11-074346.png)

**ETickingGroup 流程图**

## 2.2 核心类

### 2.2.1 FTickTaskManagerInterface

> **源码文件**：`Source\\Runtime\\Engine\\Public\\TickTaskManagerInterface.h`

```cpp
/** 
 * Interface for the tick task manager
 **/
class FTickTaskManagerInterface
{
public:
	virtual ~FTickTaskManagerInterface()
	{
	}

	/** Allocate a new ticking structure for a ULevel **/
	virtual FTickTaskLevel* AllocateTickTaskLevel() = 0;

	/** Free a ticking structure for a ULevel **/
	virtual void FreeTickTaskLevel(FTickTaskLevel* TickTaskLevel) = 0;

	/**
	 * Queue all of the ticks for a frame
	 *
	 * @param World	- World currently ticking
	 * @param DeltaSeconds - time in seconds since last tick
	 * @param TickType - type of tick (viewports only, time only, etc)
	 */
	virtual void StartFrame(UWorld* InWorld, float DeltaSeconds, ELevelTick TickType, const TArray<ULevel*>& LevelsToTick) = 0;

	/**
	 * Run all of the ticks for a pause frame synchronously on the game thread.
	 * The capability of pause ticks are very limited. There are no dependencies or ordering or tick groups.
	 * @param World	- World currently ticking
	 * @param DeltaSeconds - time in seconds since last tick
	 * @param TickType - type of tick (viewports only, time only, etc)
	 */
	virtual void RunPauseFrame(UWorld* InWorld, float DeltaSeconds, ELevelTick TickType, const TArray<ULevel*>& LevelsToTick) = 0;

	/**
		* Run a tick group, ticking all actors and components
		* @param Group - Ticking group to run
		* @param bBlockTillComplete - if true, do not return until all ticks are complete
	*/
	virtual void RunTickGroup(ETickingGroup Group, bool bBlockTillComplete ) = 0;

	/** Finish a frame of ticks **/
	virtual void EndFrame() = 0;

	/** Dumps all registered tick functions to output device. */
	virtual void DumpAllTickFunctions(FOutputDevice& Ar, UWorld* InWorld, bool bEnabled, bool bDisabled, bool bGrouped) = 0;

	/** Returns a map of enabled ticks, grouped by 'diagnostic context' string, along with count of enabled ticks */
	virtual void GetEnabledTickFunctionCounts(UWorld* InWorld, TSortedMap<FName, int32, FDefaultAllocator, FNameFastLess>& TickContextToCountMap, int32& EnabledCount, bool bDetailed, bool bFilterCoolingDown=false) = 0;

	/**
	 * Singleton to retrieve the GLOBAL tick task manager
	 *
	 * @return Reference to the global cache tick task manager
	 */
	static ENGINE_API FTickTaskManagerInterface& Get();

};
```

由以上源码可知，`FTickTaskManagerInterface` 是一个**纯抽象基类**。它定义了全局唯一的 Tick 任务管理器（`FTickTaskManager`）所必须提供的一系列公共服务和功能。

它的所有核心方法都被声明为**纯虚函数 (`= 0`)**，这意味着这个接口本身不能被实例化，任何想要成为一个有效的 Tick Task Manager 的类，都**必须**继承自这个接口并实现其所有纯虚函数。在UE中，这个唯一的实现者就是我们后面会分析的 `class FTickTaskManager`。

这种设计是**接口与实现分离**设计模式的典型应用，它允许引擎的其他部分（主要是`UWorld`）依赖于一个稳定的、抽象的接口，而无需关心其底层的具体实现可能会如何变化。

我们可以将 `FTickTaskManagerInterface` 中的接口函数按照其在 Tick 生命周期中的作用进行分组，下面我们分组进行分析。

---

1. **生命周期管理函数**

这组函数构成了 `FTickTaskManager` 在一帧内的主要工作流程，由 `UWorld::Tick` 严格按顺序调用。

```cpp
// 在一帧的Tick开始时被调用，用于收集和规划所有任务。
virtual void StartFrame(...) = 0;

// 在一帧的Tick结束时被调用，用于清理和重置状态。
virtual void EndFrame() = 0;

// 【关键】执行一个指定Tick Group中的所有任务。
// UWorld::Tick 会为每个ETickingGroup（如TG_PrePhysics）调用一次这个函数。
virtual void RunTickGroup(ETickingGroup Group, bool bBlockTillComplete) = 0;
```

**分析：**

`StartFrame`, `RunTickGroup`, `EndFrame` 共同定义了 Tick 管理器的**三段式工作模型**。这是 `UWorld` 与 `FTickTaskManager` 交互的主干道。`RunTickGroup` 中的 `bBlockTillComplete` 参数更是暴露了其支持同步/异步执行模式的能力，这是实现高性能并发调度的关键接口。

---

1. **特殊帧处理函数**

```cpp
// 在游戏暂停时，执行一个简化的、同步的Tick流程。
// 暂停时的Tick功能受限，没有依赖、排序或分组。
virtual void RunPauseFrame(...) = 0;
```

**分析：** 这个函数提供了一种处理“游戏暂停”这一特殊状态的专用路径。它将暂停时的 Tick 逻辑与正常的游戏 Tick 逻辑分离开来，使得两种情况的处理都更加清晰。

---

1. **Tick 任务结构管理函数**

这组函数负责管理与 `ULevel` 绑定的底层数据结构。

```cpp
// 为一个ULevel分配一个新的、用于存储其Tick任务的数据结构。
virtual FTickTaskLevel* AllocateTickTaskLevel() = 0;

// 释放一个ULevel不再使用的Tick任务数据结构。
virtual void FreeTickTaskLevel(FTickTaskLevel* TickTaskLevel) = 0;
```

**分析：** 这两个函数揭示了 `FTickTaskManager` 内部是**以 Level 为单位**来组织 Tick 任务的。当一个`ULevel`被加载到世界中时，`UWorld`会调用`AllocateTickTaskLevel`来为它创建一个“账本”（`FTickTaskLevel`），用于记录该关卡内所有 Actor 和 Component 的 Tick 任务。当 Level 被卸载时，则调用`FreeTickTaskLevel`来销毁这个“账本”。这与 UE 的关卡流送（Level Streaming）机制紧密相关。

---

1. **调试与分析函数**

这组函数主要用于开发和调试，提供了查询和输出 Tick 系统内部状态的能力。

```cpp
// 将所有已注册的Tick函数的信息（启用/禁用、分组等）转储到输出设备（如日志文件）。
virtual void DumpAllTickFunctions(...) = 0;

// 获取当前已启用的Tick函数的统计信息，按上下文（通常是类名）分组计数。
// 这是Unreal Insights等性能分析工具获取数据的重要来源。
virtual void GetEnabledTickFunctionCounts(...) = 0;
```

**分析：** 这些接口的存在表明，`FTickTaskManager` 在设计之初就充分考虑到了**可观测性**和**可调试性**。开发者可以通过这些工具，清晰地看到当前有哪些 Tick 在运行，它们的分组是什么，以及各类 Tick 的数量，这对于性能优化和问题排查至关重要。

---

1. **单例访问器**

```cpp
// 全局静态函数，用于获取唯一的、全局的FTickTaskManagerInterface实例。
static ENGINE_API FTickTaskManagerInterface& Get();
```

**分析：** 这是典型的**单例模式**。整个引擎中只有一个 `FTickTaskManager` 实例，任何需要与它交互的代码（如`UWorld`, `AActor`等）都通过这个静态 `Get()` 方法来获取对它的引用。

---

**总结**：

`FTickTaskManagerInterface` 如同一份清晰的“产品说明书”，它通过一系列纯虚函数，向整个引擎精确地定义了 Tick 调度系统所能提供的服务：

- **核心服务:** 提供了一套完整的 `StartFrame` -> `RunTickGroup` -> `EndFrame` 的生命周期管理。
- **组织方式:** 明确了其内部是以 `Level` 为单位来管理 Tick 任务的。
- **特殊处理:** 定义了处理暂停帧的专门逻辑。
- **可观测性:** 提供了强大的调试和性能分析接口。
- **访问模式:** 规定了其作为一个全局单例的存在形式。

通过分析这个接口，我们无需深入其复杂的实现细节，就能从一个很高的层面理解`FTickTaskManager`的**设计目标、功能边界和核心职责**。

![FTickTaskManagerInterface 类图](attachment:4183eddf-e8df-42fa-b083-8b06068e7bcf:image.png)

FTickTaskManagerInterface 类图

### 2.2.2 FTickTaskManager

> **源码文件**：`Source\\Runtime\\Engine\\Private\\TickTaskManager.cpp`

`FTickTaskManager` 是 `FTickTaskManagerInterface` 的**唯一实现**。它是一个全局单例，负责接收 `UWorld` 的指令，并将海量的、无序的 `FTickFunction` 组织成一个有序的、部分可并行的执行计划，然后提交给底层的 `Task Graph` 系统去执行。

---

1. **核心成员变量**

```cpp
// 对一个更底层的、专门负责任务排序和依赖解析的辅助类的引用。
FTickTaskSequencer& TickTaskSequencer;

// 本帧需要进行Tick的所有Level的FTickTaskLevel的列表。
TArray<FTickTaskLevel*> LevelList;

// 存储本帧Tick的上下文信息，如World, DeltaSeconds, TickType等。
FTickContext Context;

// 一个关键的状态标志。当为true时，表示正处于一帧的Tick流程中，
// 此时任何新注册的Tick函数都需要被特殊处理（加入到新生成队列）。
bool bTickNewlySpawned;
```

**分析：**

`TickTaskSequencer` 是一个非常重要的内部辅助类，`FTickTaskManager` 将大量的复杂排序和依赖解析工作都委托给了它。`LevelList` 表明了其以`Level`为单位的管理模式。`Context` 作为一个上下文结构体，方便地将本帧的所有共享信息传递给各个子系统。`bTickNewlySpawned` 则是控制“帧内生成，帧内更新”机制的总开关。

---

1. **生命周期函数**

这是 `UWorld::Tick` 驱动 `FTickTaskManager` 工作的主流程。

<aside> 1️⃣

**`StartFrame()` - 规划阶段**

```cpp
virtual void StartFrame(UWorld* InWorld, float InDeltaSeconds, ELevelTick InTickType, const TArray<ULevel*>& LevelsToTick) override
{
    // 1. 设置上下文和状态
    Context.World = InWorld;
    // ... 其他上下文设置 ...
    bTickNewlySpawned = true; // 打开“新生成”处理开关
    TickTaskSequencer.StartFrame(); // 通知Sequencer准备新的一帧

    // 2. 填充LevelList
    FillLevelList(LevelsToTick); // 根据传入的Levels，构建本帧要处理的FTickTaskLevel列表

    // 3. 【关键】选择串行或并行模式来收集和排队Tick任务
    if (!bConcurrentQueue) // 默认通常是串行模式
    {
        // 串行路径：
        // a. 遍历每个Level，调用其StartFrame，这会收集该Level的所有Tick函数并初步处理依赖
        for (FTickTaskLevel* Level : LevelList)
        {
            Level->StartFrame(Context);
        }
        // b. 再次遍历，将所有准备好的任务正式排入队列
        for (FTickTaskLevel* Level : LevelList)
        {
            Level->QueueAllTicks();
        }
    }
    else // 并行路径 (由CVar控制)
    {
        // a. 先将所有Level的所有Tick函数收集到一个大的总列表中
        for (FTickTaskLevel* Level : LevelList)
        {
            Level->StartFrameParallel(Context, AllTickFunctions);
        }
        // b. 使用ParallelFor，在多个工作线程上并行地为每个Tick函数排队
        ParallelFor(AllTickFunctions.Num(), ...);
    }
}
```

**分析：**

StartFrame 的核心职责是**“收集所有任务并完成初步排队”**。它首先设置好本帧的上下文环境，然后遍历所有需要 Tick 的 Level。有趣的是它提供了两种收集任务的路径：

- **串行路径（默认）:** 更稳定，按部就班地处理每个 Level。
    
- **并行路径（实验性/特定场景）:** 将任务收集过程本身也并行化，以期在拥有海量 Tick 任务的场景下提升 StartFrame 阶段的性能。
    
    无论哪种路径，最终目的都是将所有 FTickFunction 及其依赖关系交给底层的 TickTaskSequencer 去构建任务图。
    

</aside>

---

<aside> 2️⃣

**`RunTickGroup()` - 执行阶段**

```cpp
virtual void RunTickGroup(ETickingGroup Group, bool bBlockTillComplete) override
{
    check(Context.TickGroup == Group); // 检查UWorld的调用顺序是否正确

    // 【关键】将执行权完全委托给底层的Sequencer
    TickTaskSequencer.ReleaseTickGroup(Group, bBlockTillComplete, TicksToManualDispatch);

    Context.TickGroup = ETickingGroup(Context.TickGroup + 1); // 将上下文中的组推进到下一个

    if (bBlockTillComplete)
    {
        // 【关键】处理“新生成”对象的逻辑
        bool bFinished = false;
        for (int32 Iterations = 0; Iterations < 101; Iterations++) // 最多循环101次以防死循环
        {
            int32 Num = 0;
            // 遍历所有Level，看是否有新生成的、属于当前Tick组的Actor
            for (FTickTaskLevel* Level : LevelList)
            {
                Num += Level->QueueNewlySpawned(Context.TickGroup);
            }

            if (Num > 0) // 如果有
            {
                // 再次调用ReleaseTickGroup，但这次传入TG_NewlySpawned，
                // 来执行这些新任务
                TickTaskSequencer.ReleaseTickGroup(TG_NewlySpawned, true, TicksToManualDispatch);
            }
            else
            {
                bFinished = true; // 没有新任务了，退出循环
                break;
            }
        }
        // ... 处理 runaway (无限生成) 的异常情况 ...
    }
}
```

**分析：**

`RunTickGroup` 的实现揭示了两个核心秘密：

1. **委托执行:** 它自身不执行任务，而是直接调用 `TickTaskSequencer.ReleaseTickGroup`，让 Sequencer 去和 `Task Graph` 系统打交道。它扮演的是一个“发令员”的角色。
2. **`TG_NewlySpawned` 的实现:** 这里清晰地展示了我们之前讨论的“新生成对象处理”机制。在一个主 Tick Group (`Group`) 执行完毕后，它会**立即进入一个 `for` 循环**，反复检查并执行那些在本轮中新生成的、且其Tick组已经“错过”的任务。这个循环保证了“帧内生成，帧内更新”的效果。 </aside>

---

<aside> 3️⃣

**`EndFrame()` - 清理阶段**

```cpp
virtual void EndFrame() override
{
    TickTaskSequencer.EndFrame(); // 通知Sequencer清理
    bTickNewlySpawned = false; // 关闭“新生成”处理开关
    for (FTickTaskLevel* Level : LevelList)
    {
        Level->EndFrame(); // 通知每个Level进行清理
    }
    Context.World = nullptr; // 清空上下文
    LevelList.Reset(); // 清空本帧的Level列表
}
```

**分析：**

`EndFrame` 的逻辑非常清晰，就是一系列的**状态重置和资源清理**操作，将 `FTickTaskManager` 恢复到一个干净的状态，为下一帧的 `StartFrame` 调用做好准备。

</aside>

---

1. **Tick 函数注册/注销接口**

```cpp
void AddTickFunction(ULevel* InLevel, FTickFunction* TickFunction)
{
    // ...
    FTickTaskLevel* Level = TickTaskLevelForLevel(InLevel); // 找到或创建Level对应的TickTaskLevel
    Level->AddTickFunction(TickFunction); // 在Level的“账本”中添加这个Tick函数
    // ...
}

void RemoveTickFunction(FTickFunction* TickFunction)
{
    // ...
    FTickTaskLevel* Level = TickFunction->InternalData->TickTaskLevel; // 从TickFunction自身找到它的“账本”
    Level->RemoveTickFunction(TickFunction); // 从“账本”中移除
}
```

**分析：**

`AActor` 和 `UActorComponent` 在创建和销毁时调用的就是这两个函数。它们清晰地展示了 `FTickTaskManager` 是如何通过 `FTickTaskLevel` 来**按 Level 组织**所有 Tick 函数的。每个 `FTickFunction` 也通过其 `InternalData` 反向持有一个指向其所属 `FTickTaskLevel` 的指针，形成了一个双向链接，方便快速地添加和移除。

---

**总结**：

![FTickTaskManager 工作流程](attachment:6f9c6260-4c36-4e42-b495-449f11e15aef:Editor___Mermaid_Chart-2025-06-11-100647.png)

FTickTaskManager 工作流程

`FTickTaskManager` 的实现是一个典型的**分层委托**架构：

- 它作为**高层管理者**，定义了 `StartFrame` -> `RunTickGroup` -> `EndFrame` 的宏观流程。
- 它将**复杂的任务收集和排序逻辑**，委托给了内部的 `FTickTaskLevel` 和 `FTickTaskSequencer`。
- 它将**最终的并行执行**，委托给了 `TickTaskSequencer` 背后的 `Task Graph` 系统。

通过这种层层委托，`FTickTaskManager` 以一种相对清晰、高内聚的方式，精心安排了整个极其复杂的 Tick 调度过程。

### 2.2.3 FTickTaskSequencer

> **源码文件**：`Source\\Runtime\\Engine\\Private\\TickTaskManager.cpp`

`FTickTaskSequencer` 是一个全局单例，是`FTickTaskManager`的**核心工作委托对象**。它的名字“Sequencer”（序列器）精准地描述了其核心职责：将一堆无序的、带有依赖关系的 Tick 任务，组织成一个可以被`Task Graph`系统高效执行的**任务序列**。

---

1. **核心成员变量**

```cpp
// 存储用于批处理（Batching）的Tick任务信息
TArray< TPair<FTickGroupCondition, TUniquePtr<FTickBatchInfo> > > TickBatches;

// 【关键】为每个Tick组（TG_MAX个）存储一个FGraphEventRef数组。
// 当一个Tick任务被创建时，它的完成事件（Completion Event）会被添加到这个数组中。
TArrayWithThreadsafeAdd<FGraphEventRef, TInlineAllocator<4> > TickCompletionEvents[TG_MAX];

// 【关键】一个二维数组，用于存储所有Tick任务。
// 第一个维度是任务的开始Tick组，第二个维度是结束Tick组。
// 分为高优先级和普通优先级。
TArrayWithThreadsafeAdd<FTickGraphTask*> HiPriTickTasks[TG_MAX][TG_MAX];
TArrayWithThreadsafeAdd<FTickGraphTask*> TickTasks[TG_MAX][TG_MAX];

// 用于存储需要在帧末尾完成的清理任务。
FGraphEventArray CleanupTasks;

// 记录上一个被阻塞等待的Tick组，用于确定下一次需要等待哪些组。
ETickingGroup WaitForTickGroup;

// 一系列控制行为的布尔开关，由CVar控制。
bool bAllowConcurrentTicks;     // 是否允许并行Tick
bool bAllowBatchedTicksForFrame; // 是否允许Tick批处理
// ...
```

**分析：**

- **`TickCompletionEvents`** 和 **`TickTasks` / `HiPriTickTasks`** 是这个类的**核心数据容器**。
- `TickTasks[StartGroup][EndGroup]` 这种二维数组的设计非常精巧。它使得`Sequencer`可以快速地根据**开始组**来分发任务，并根据**结束组**来收集它们的完成事件。
- `TArrayWithThreadsafeAdd` 的使用，暗示了这些数组可以在**多个线程中被安全地添加元素**，这对于并行化的`StartFrame`流程至关重要。
- `TickBatches` 则是一种性能优化，它尝试将属性相同（比如都在同一个 Tick 组、都没有依赖）的多个小 Tick 任务，打包成一个大的`Task Graph`任务来执行，以减少任务调度的开销。

---

1. **核心函数**

<aside> 1️⃣

**`StartFrame()` - 准备工作台**

```cpp
void StartFrame()
{
    // 1. 从控制台变量（CVar）读取并设置本帧的行为开关
    bAllowConcurrentTicks = !!CVarAllowAsyncComponentTicks.GetValueOnGameThread();
    bAllowBatchedTicksForFrame = !!CVarAllowBatchedTicks.GetValueOnGameThread();
    // ...

    // 2. 等待上一帧的清理任务完成
    WaitForCleanup();

    // 3. 【关键】重置所有核心容器
    // 为新的一帧清空所有任务列表和完成事件列表
    for (int32 Index = 0; Index < TG_MAX; Index++)
    {
        TickCompletionEvents[Index].Reset();
        for (int32 IndexInner = 0; IndexInner < TG_MAX; IndexInner++)
        {
            TickTasks[Index][IndexInner].Reset();
            HiPriTickTasks[Index][IndexInner].Reset();
        }
    }
    WaitForTickGroup = (ETickingGroup)0;
}
```

**分析：**

`StartFrame` 的职责非常纯粹：**清理工作台，为新一轮的任务做准备**。它确保了上一帧的所有遗留任务（特别是异步清理任务）都已完成，然后将所有核心的数组容器重置为空。

</aside>

<aside> 2️⃣

**`QueueTickTask()` / `QueueOrBatchTickTask()` - 创建并注册任务**

```cpp
// QueueTickTask是基础版本
void QueueTickTask(const FGraphEventArray* Prerequisites, FTickFunction* TickFunction, const FTickContext& TickContext)
{
    // 1. 创建一个Task Graph任务
    FTickGraphTask* Task = TGraphTask<FTickFunctionTask>::CreateTask(Prerequisites, ...).ConstructAndHold(...);
    // 2. 将Task指针存回FTickFunction，方便后续查找
    TickFunction->SetTaskPointer(FTickFunction::ETickTaskState::HasTask, Task);
    // 3. 将任务的完成事件添加到对应的Tick组列表中
    AddTickTaskCompletion(..., Task, ...);
}
```

**分析：** 这组函数是 **`FTickTaskManager` 调用 `FTickTaskLevel` 后，最终被执行**的地方。它的核心工作是：

1. **实例化一个`Task Graph`任务** (`TGraphTask<FTickFunctionTask>`)，并将`Prerequisites`（前置依赖的完成事件）传递给它。
2. 将这个创建好的任务实例，通过`AddTickTaskCompletion`函数，**注册到** `TickTasks` 和 `TickCompletionEvents` 这两个核心二维数组中，分类存放。 `QueueOrBatchTickTask` 则是在此基础上增加了尝试**批处理**的逻辑，如果条件满足，它会把多个 Tick 任务打包到一个`FBatchTickFunctionTask`中，否则就退化为调用`QueueTickTask`。 </aside>

<aside> 3️⃣

**`ReleaseTickGroup()` - 发令执行**

```cpp
void ReleaseTickGroup(ETickingGroup WorldTickGroup, bool bBlockTillComplete)
{
    // 1. 【关键】分发任务
    {
        if (SingleThreadedMode() || ...)
        {
            // 单线程模式下，直接在游戏线程调用DispatchTickGroup
            DispatchTickGroup(ENamedThreads::GameThread, WorldTickGroup);
        }
        else
        {
            // 多线程模式下，将DispatchTickGroup本身也作为一个任务扔到Task Graph里去异步执行
            // 这样做可以进一步利用并行，让分发过程不阻塞主线程
            TGraphTask<FDipatchTickGroupTask>::CreateTask(...).ConstructAndDispatchWhenReady(...);
        }
    }

    // 2. 【关键】等待任务完成
    if (bBlockTillComplete || SingleThreadedMode())
    {
        // 遍历从上次等待点到当前组的所有Tick组
        for (ETickingGroup Block = WaitForTickGroup; Block <= WorldTickGroup; ...)
        {
            if (TickCompletionEvents[Block].Num())
            {
                // 使用Task Graph接口，等待该组所有的完成事件被触发
                FTaskGraphInterface::Get().WaitUntilTasksComplete(TickCompletionEvents[Block], ...);
                // 等待完成后，重置该组
                ResetTickGroup(Block);
            }
        }
        // 更新等待点
        WaitForTickGroup = ETickingGroup(WorldTickGroup + 1);
    }
}
```

**分析：**

`ReleaseTickGroup` 是**命令执行和同步**的核心。

1. 它首先通过 `DispatchTickGroup` 函数，**“解锁”**所有属于当前 `WorldTickGroup` 的任务，允许`Task Graph`系统开始执行它们。这个解锁过程甚至可以被异步化。
2. 如果 `bBlockTillComplete` 为`true`，它就会进入等待逻辑，使用 `FTaskGraphInterface::Get().WaitUntilTasksComplete` **阻塞游戏主线程**，直到 `TickCompletionEvents` 数组中记录的、属于当前及之前所有需要等待的组的`FGraphEvent`全部完成。这就是实现**同步点**的机制。 </aside>

<aside> 4️⃣

**`DispatchTickGroup()` - 底层分发**

```cpp
void DispatchTickGroup(ENamedThreads::Type CurrentThread, ETickingGroup WorldTickGroup)
{
    // 遍历 HiPriTickTasks[WorldTickGroup][...]
    for (...)
    {
        // 调用每个Task的Unlock，允许它开始执行
        TickArray[Index]->Unlock(CurrentThread);
    }
    // 遍历 TickTasks[WorldTickGroup][...]
    for (...)
    {
        TickArray[Index]->Unlock(CurrentThread);
    }
}
```

**分析：** 这是最底层的分发函数。它做的事情非常简单：遍历 `TickTasks` 二维数组中，所有**起始组**为当前 `WorldTickGroup` 的任务，并调用它们的 `Unlock` 方法。`Unlock` 会通知 `Task Graph` 系统：“这个任务的所有前置条件都已满足，可以开始执行了！”

</aside>

---

**总结**：

![FTickTaskSequencer 工作流程](attachment:5a98047a-d98f-4e09-b89f-a4d15db8ba90:Editor___Mermaid_Chart-2025-06-11-125137.png)

FTickTaskSequencer 工作流程

`FTickTaskSequencer` 是一个高度优化的、与底层 `Task Graph` 系统紧密耦合的精密调度器。

- 它使用**二维数组**来高效地组织和索引海量的 Tick 任务，实现了按“开始组”和“结束组”的快速访问。
- 它的 `Queue` 系列函数负责将上层的 `FTickFunction` **翻译**成底层的 `Task Graph` 任务。
- 它的 `ReleaseTickGroup` 函数通过“分发”（Dispatch）和“等待”（Wait）两个步骤，精确地控制了**任务的并发执行和必要的同步**。

可以说，`FTickTaskManager` 是“战略层”，而 `FTickTaskSequencer` 则是“战术层”。它处理了所有最棘手的并发、同步和性能优化细节，是 UE Tick 机制能够高性能运行的真正幕后功臣。

### 2.2.4 FTickTaskLevel

> **源码文件**：`Source\\Runtime\\Engine\\Private\\TickTaskManager.cpp`

`FTickTaskLevel` 可以被理解为 **一个 `ULevel` 中所有 `FTickFunction` 的“账本”或“花名册”**。它的核心职责是**管理隶属于单个 `ULevel` 的所有 Tick 任务**，并对它们进行分类、预处理，为上层的 `FTickTaskManager` 提供干净、有序的数据。

每一个`ULevel`对象内部都有一个指向`FTickTaskLevel`实例的指针（`Level->TickTaskLevel`），这构成了`FTickTaskManager`按 Level 组织 Tick 任务的基础。

---

1. **核心成员变量**

这是 `FTickTaskLevel` 用来分类和管理 `FTickFunction` 的内部容器。

```cpp
// 对全局Sequencer的引用，方便直接调用
FTickTaskSequencer& TickTaskSequencer;

// 【关键】存储所有明确启用的、需要每帧Tick的函数。TSet保证了不重复。
TSet<FTickFunction*> AllEnabledTickFunctions;

// 【关键】一个自定义的链表，用于存储所有设置了TickInterval且当前处于“冷却”状态的函数。
FCoolingDownTickFunctionList AllCoolingDownTickFunctions;

// 存储所有明确禁用的Tick函数。
TSet<FTickFunction*> AllDisabledTickFunctions;

// 临时数组，用于存放本帧结束后需要重新计算冷却时间的Tick函数。
TArrayWithThreadsafeAdd<FTickScheduleDetails> TickFunctionsToReschedule;

// 存储在本帧Tick期间新生成的Tick函数。
TSet<FTickFunction*> NewlySpawnedTickFunctions;

// 当前帧的Tick上下文
FTickContext Context;

// 是否处于Tick流程中的标志
bool bTickNewlySpawned;
```

**分析：**

- `FTickTaskLevel` 的核心就是这**四大容器**：`AllEnabledTickFunctions`, `AllCoolingDownTickFunctions`, `AllDisabledTickFunctions`, 和 `NewlySpawnedTickFunctions`。它将一个 Level 内的所有 Tick 函数分门别类地进行管理。
- **`AllCoolingDownTickFunctions`** 的链表结构设计非常巧妙。因为它需要处理基于`DeltaTime`的动态激活，链表结构比数组或集合更适合进行高效的遍历和中间节点的移除。
- `TickFunctionsToReschedule` 和 `NewlySpawnedTickFunctions` 是**临时性**的容器，用于处理帧内的状态变化，体现了其逻辑的严谨性。

---

1. **核心函数**：

<aside> 1️⃣

**`AddTickFunction()` / `RemoveTickFunction()`**

```cpp
void AddTickFunction(FTickFunction* TickFunction)
{
    // ...
    if (TickFunction->TickState == FTickFunction::ETickState::Enabled)
    {
        AllEnabledTickFunctions.Add(TickFunction); // 添加到启用集合
        if (bTickNewlySpawned)
        {
            NewlySpawnedTickFunctions.Add(TickFunction); // 如果在Tick中，额外添加到新生成集合
        }
    }
    else
    {
        AllDisabledTickFunctions.Add(TickFunction); // 添加到禁用集合
    }
}
```

**分析：**

`AddTickFunction` 的逻辑清晰地展示了其**分类管理**的职责。它会根据 `TickFunction` 的当前状态，将其放入不同的“抽屉”（`TSet`）中。特别地，它处理了“帧内生成”的特殊情况，确保这些新任务不会被错过。`RemoveTickFunction` 也是类似的反向操作。

</aside>

<aside> 2️⃣

**`StartFrame()` - 准备本 Level 的任务**

```cpp
int32 StartFrame(const FTickContext& InContext)
{
    // ... 设置上下文 ...

    // 1. 将待调度的函数放入冷却列表
    ScheduleTickFunctionCooldowns();

    // 2. 遍历冷却列表，激活到期的Tick函数
    FTickFunction* TickFunction = AllCoolingDownTickFunctions.Head;
    while (TickFunction)
    {
        // ... 计算是否应该激活 ...
        if (/* 冷却时间已到 */)
        {
            // 将其状态设为Enabled，这样在QueueAllTicks时就会被处理
            TickFunction->TickState = FTickFunction::ETickState::Enabled;
            // ...
        }
    }
    // ...
    return AllEnabledTickFunctions.Num() + CooldownTicksEnabled;
}
```

**分析：**

`StartFrame` 的核心职责是**处理带有`TickInterval`的函数**。它会检查 `AllCoolingDownTickFunctions` 链表，看哪些函数的冷却时间已经在本帧 `DeltaSeconds` 内结束。如果结束了，就将其状态标记为 `Enabled`，使其有资格在本帧被 Tick。这个过程确保了`TickInterval`功能的正确实现。

</aside>

<aside> 3️⃣

**`QueueAllTicks()` - 将任务提交给 Sequencer**

```cpp
void QueueAllTicks()
{
    FTickTaskSequencer& TTS = FTickTaskSequencer::Get();

    // 1. 遍历所有启用的Tick函数
    for (TSet<FTickFunction*>::TIterator It(AllEnabledTickFunctions); It; ++It)
    {
        FTickFunction* TickFunction = *It;
        // 【关键】调用TickFunction自己的排队方法
        TickFunction->QueueTickFunction(TTS, Context);

        // 2. 如果有TickInterval，则将其移出启用列表，并准备重新调度冷却
        if (TickFunction->TickInterval > 0.f)
        {
            It.RemoveCurrent();
            RescheduleForInterval(TickFunction, TickFunction->TickInterval);
        }
    }

    // 3. 遍历冷却列表中本帧被激活的函数
    while (FTickFunction* TickFunction = AllCoolingDownTickFunctions.Head)
    {
        if (TickFunction->TickState == FTickFunction::ETickState::Enabled)
        {
            TickFunction->QueueTickFunction(TTS, Context);
            RescheduleForInterval(...); // 重新调度冷却
            AllCoolingDownTickFunctions.Head = TickFunction->InternalData->Next; // 从冷却列表头部移除
        }
        else
        {
            break;
        }
    }
}
```

**分析：**

`QueueAllTicks` 是 **`FTickTaskLevel` 与 `FTickTaskSequencer` 之间的桥梁**。它遍历自己管理的所有“本帧需要执行”的函数（包括一直启用的和冷却结束的），然后调用每个 `FTickFunction` 自己的 `QueueTickFunction` 方法。`FTickFunction::QueueTickFunction` 内部会进一步调用 `FTickTaskSequencer` 的 `QueueTickTask`，从而将任务正式提交给调度器。

这个函数还负责**循环利用**带有`TickInterval`的函数：在它们被 Tick 后，立即将它们重新放入“待调度冷却”的列表（`TickFunctionsToReschedule`）中。

</aside>

<aside> 4️⃣

**`QueueNewlySpawned()` - 处理“插队”的任务**

```cpp
int32 QueueNewlySpawned(ETickingGroup CurrentTickGroup)
{
    // ...
    for (TSet<FTickFunction*>::TIterator It(NewlySpawnedTickFunctions); It; ++It)
    {
        // 同样调用QueueTickFunction将新任务提交给Sequencer
        (*It)->QueueTickFunction(TTS, Context);
        // ...
    }
    NewlySpawnedTickFunctions.Empty(); // 处理完后清空
    // ...
}
```

**分析：** 这个函数由 `FTickTaskManager::RunTickGroup` 调用，专门用于处理那些在帧中途新生成的Actor的Tick任务。它遍历 `NewlySpawnedTickFunctions` 集合，并将这些“插队”的任务也提交给`Sequencer`执行，从而实现了“帧内生成，帧内更新”的机制。

</aside>

---

**总结**：

![FTickTaskLevel 数据组织与工作流](attachment:7280ffcf-e8ba-4fb7-a182-3a9f9fd1b764:Editor___Mermaid_Chart-2025-06-11-132305.png)

FTickTaskLevel 数据组织与工作流

`FTickTaskLevel` 是一个**面向单个`ULevel`的、高度特化的 Tick 任务管理器**。它的存在体现了 UE 设计的**分而治之**思想。

- **数据组织者：** 它将一个`ULevel`中成百上千的 Tick 函数，通过`TSet`和自定义链表，分门别类地管理起来，极大地降低了上层`FTickTaskManager`的管理复杂度。
- **预处理器：** 它在`StartFrame`阶段就独立处理完了复杂的`TickInterval`逻辑，为主调度器提供了干净、明确的“待办事项列表”。
- **执行委托者：** 它通过调用`FTickFunction::QueueTickFunction`，将最终的排队任务委托出去，自身不直接与`FTickTaskSequencer`深度耦合。

通过引入`FTickTaskLevel`这一中间层，UE 的 Tick 系统架构变得更加清晰、模块化，并且能够与关卡流送系统完美协同工作。

## 2.3 调用上下文

### 2.3.1 UWorld::Tick

> **源码文件**：`Source\\Runtime\\Engine\\Private\\LevelTick.cpp`

`UWorld::Tick` 的源码虽然冗长，但我们只需要聚焦于它与 `FTickTaskManager` 直接交互的**三个关键阶段**，就能清晰地看到 `FTickTaskManager` 是如何被驱动的。

---

1. **第一阶段：`StartFrame` - 规划与准备**

在 `UWorld::Tick` 的核心循环中，当确定了本帧需要进行 Actor Tick (`bDoingActorTicks` 为 `true`) 之后，第一件重要的事情就是调用 `FTickTaskManager` 的 `StartFrame` 方法。

```cpp
// in UWorld::Tick, inside the main for-loop over LevelCollections

if (bDoingActorTicks)
{
    // ...
    TickGroup = TG_PrePhysics; // 重置世界的当前Tick组

    // 【关键调用 1】通知TickTaskManager开始新的一帧
    FTickTaskManagerInterface::Get().StartFrame(this, DeltaSeconds, TickType, LevelsToTick);

    // ... 后续的RunTickGroup调用 ...
}
```

**上下文剖析：**

1. **时机：** 这个调用发生在所有 `RunTickGroup` **之前**。
2. **传递的参数：**
    - `this` (`UWorld*`): 告诉 `FTickTaskManager` 当前工作的世界是哪一个。
    - `DeltaSeconds`, `TickType`: 传递本帧的时间和Tick类型上下文。
    - `LevelsToTick`: 一个 `TArray<ULevel*>`，明确告知 `FTickTaskManager` **本帧只需要考虑这些 `ULevel` 中的 Tick 任务**。这对于关卡流送至关重要，它避免了对已卸载或隐藏的 Level 进行不必要的工作。
3. **作用：** `UWorld` 在这里扮演了**“信息提供者”**和**“启动者”**的角色。它将所有必要的上下文信息打包好，然后按下 `FTickTaskManager` 的“启动按钮”。`StartFrame` 接收到指令后，会执行我们之前分析过的内部逻辑：收集所有相关Level的Tick函数，并构建本帧的任务依赖图。

---

1. **第二阶段：`RunTickGroup` - 按部就班地执行**

在 `StartFrame` 完成规划后，`UWorld::Tick` 进入了一个高度结构化的执行阶段，它像一个严谨的工序流程单，依次调用 `RunTickGroup`。

```cpp
// in UWorld::Tick, right after StartFrame

// 【关键调用 2】按严格顺序执行每个Tick组
{ SCOPE_CYCLE_COUNTER(STAT_TG_PrePhysics); RunTickGroup(TG_PrePhysics); }
EnsureCollisionTreeIsBuilt();
{ SCOPE_CYCLE_COUNTER(STAT_TG_StartPhysics); RunTickGroup(TG_StartPhysics); }
{ SCOPE_CYCLE_COUNTER(STAT_TG_DuringPhysics); RunTickGroup(TG_DuringPhysics, false); }
// ...
{ SCOPE_CYCLE_COUNTER(STAT_TG_EndPhysics); RunTickGroup(TG_EndPhysics); }
{ SCOPE_CYCLE_COUNTER(STAT_TG_PostPhysics); RunTickGroup(TG_PostPhysics); }
// ...
{ SCOPE_CYCLE_COUNTER(STAT_TG_PostUpdateWork); RunTickGroup(TG_PostUpdateWork); }
{ SCOPE_CYCLE_COUNTER(STAT_TG_LastDemotable); RunTickGroup(TG_LastDemotable); }
```

**上下文剖析：**

1. **严格的顺序：** `UWorld` **硬编码**了 `RunTickGroup` 的调用顺序，从 `TG_PrePhysics` 一直到 `TG_LastDemotable`。这从根本上保证了不同阶段任务的执行顺序，是整个 Tick 依赖系统能够正常工作的基础。
2. **阻塞与非阻塞：**
    - 大部分 `RunTickGroup` 调用（如 `TG_PrePhysics`, `TG_EndPhysics`）都使用了默认的 `bBlockTillComplete = true` 参数。这意味着 `UWorld` 的执行流会在这里**暂停**，直到该 Tick 组的所有任务完成。这是实现**同步点**的关键。
    - `RunTickGroup(TG_DuringPhysics, false)` 是一个特例。`false` 参数告诉 `UWorld` **不要等待**物理模拟完成。这使得游戏主线程可以在物理计算在后台线程进行的同时，继续执行其他工作，从而实现了**并发**，提升了性能。
3. **作用：** `UWorld` 在这里扮演了**“工序调度员”**的角色。它不关心每个`TickGroup`内部具体有哪些任务，只负责按照预设的蓝图，一步步地命令 `FTickTaskManager`去执行每一个工序。

---

1. **第三阶段：`EndFrame` - 清理与收尾**

在所有 `RunTickGroup` 都执行完毕后，`UWorld::Tick` 会调用 `FTickTaskManager` 的 `EndFrame` 来结束本帧的 Tick 调度。

```cpp
// in UWorld::Tick, after all RunTickGroup calls

if (bDoingActorTicks)
{
    // ...
    // 【关键调用 3】通知TickTaskManager本帧的Tick调度工作全部结束
    FTickTaskManagerInterface::Get().EndFrame();
}
```

**上下文剖析：**

1. **时机：** 这个调用发生在所有 `RunTickGroup` **之后**。
2. **作用：** `UWorld` 在这里发出一个**“收工”**的信号。`FTickTaskManager` 接收到后，会执行其内部的清理逻辑，如重置状态、清空临时容器等，为下一帧做好准备。

---

**总结**：

![UWorld::Tick 与 FTickTaskManager 交互时序图](attachment:de5ca54d-82ca-4c26-bf55-01f5e0c0f00c:Editor___Mermaid_Chart-2025-06-11-133444.png)

UWorld::Tick 与 FTickTaskManager 交互时序图

从 `UWorld::Tick` 这个调用上下文的视角来看，`FTickTaskManager` 并非一个自我驱动的系统，而是一个**被动响应的、服务于 `UWorld` 的高级工具**。

- `UWorld` 负责**定义“做什么”和“按什么顺序做”**（提供 LevelsToTick，按序调用 RunTickGroup）。
- `FTickTaskManager` 负责**解决“如何高效、正确地做”**（收集任务、解析依赖、并行执行）。

这种清晰的**职责分离**是 UE 架构设计的精髓。`UWorld` 掌握着宏观的、游戏逻辑驱动的流程控制，而 `FTickTaskManager` 则封装了所有复杂的、与具体游戏逻辑无关的底层调度技术。

### 2.3.2 AActor

> **源码文件**：`Source\\Runtime\\Engine\\Private\\Actor.cpp`

`AActor` 是整个 Tick 调度系统最主要的服务对象。引擎为`AActor`提供了一套封装良好、易于使用的 API，让游戏开发者可以在不了解`FTickTaskManager`复杂实现的情况下，方便地控制 Actor 的 Tick 行为。

---

1. **注册与注销：`RegisterAllActorTickFunctions`**
    - **函数:** `void AActor::RegisterAllActorTickFunctions(bool bRegister, bool bDoComponents)`
        
    - **调用时机:**
        
        - `bRegister = true`: 当Actor被创建并添加到世界时（如通过 `SpawnActor` 或关卡加载）。
        - `bRegister = false`: 当Actor被销毁时。
    - **源码剖析:**
        
        ```cpp
        void AActor::RegisterAllActorTickFunctions(bool bRegister, bool bDoComponents)
        {
            // ...
            // 防止重复注册/注销
            if (bTickFunctionsRegistered != bRegister)
            {
                // 【关键】调用RegisterActorTickFunctions来处理自身的PrimaryActorTick
                RegisterActorTickFunctions(bRegister);
                bTickFunctionsRegistered = bRegister;
                // ...
            }
        
            if (bDoComponents)
            {
                // 递归地为所有子组件也执行注册/注销
                for (UActorComponent* Component : GetComponents())
                {
                    if (Component)
                    {
                        Component->RegisterAllComponentTickFunctions(bRegister);
                    }
                }
            }
            // ...
        }
        ```
        
        ```cpp
        void AActor::RegisterActorTickFunctions(bool bRegister)
        {
            if(bRegister)
            {
                if(PrimaryActorTick.bCanEverTick)
                {
                    // 1. 设置Target，让FActorTickFunction知道要Tick哪个Actor
                    PrimaryActorTick.Target = this;
                    // 2. 设置初始启用状态
                    PrimaryActorTick.SetTickFunctionEnable(...);
                    // 3. 【核心调用】将自身的PrimaryActorTick注册到其所在Level的FTickTaskLevel中
                    PrimaryActorTick.RegisterTickFunction(GetLevel());
                }
            }
            else
            {
                if(PrimaryActorTick.IsTickFunctionRegistered())
                {
                    // 【核心调用】从管理器中注销
                    PrimaryActorTick.UnRegisterTickFunction();
                }
            }
        }
        ```
        
    - **上下文剖析:**
        
        - 这是`AActor`与`FTickTaskManager`的**第一次握手**。当一个 Actor 诞生时，`RegisterAllActorTickFunctions`会被调用。
        - 它首先通过调用`RegisterActorTickFunctions`，将自己的`PrimaryActorTick`（一个`FActorTickFunction`实例）注册到调度系统中。
        - `PrimaryActorTick.RegisterTickFunction(GetLevel())` 这个调用最终会触发 `FTickTaskManager::AddTickFunction`，将这个Tick任务添加到正确的`FTickTaskLevel`中。
        - 同时，它还会递归地为所有子组件执行相同的操作，确保整个Actor及其所有部分的Tick都能被正确管理。

---

1. **执行 Tick 逻辑：从`ExecuteTick`到`AActor::Tick`**
    - **函数:** `void FActorTickFunction::ExecuteTick(...)`
        
    - **调用时机:** 当`FTickTaskSequencer`通过`Task Graph`执行这个`FActorTickFunction`对应的任务时。
        
    - **源码剖析:**`AActor::TickActor`是一个内部函数，它会进一步调用我们熟悉的`AActor::Tick`。
        
        ```cpp
        void FActorTickFunction::ExecuteTick(float DeltaTime, ...)
        {
            if (IsValid(Target)) // Target就是之前注册时设置的AActor*
            {
                // ...
                // 【关键】调用Actor自身的TickActor方法
                Target->TickActor(DeltaTime*Target->CustomTimeDilation, TickType, *this);
            }
        }
        ```
        
        ```cpp
        void AActor::Tick( float DeltaSeconds )
        {
            // 如果是蓝图Actor，调用其蓝图事件图中的Tick事件
            if (/* 是蓝图 */)
            {
                ReceiveTick(DeltaSeconds);
            }
            // ...
            // 处理该Actor的Latent Actions
            LatentActionManager.ProcessLatentActions(this, ...);
        }
        ```
        
    - **上下文剖析:**
        
        - 这里展示了**从底层调度到上层逻辑的完整调用链**。
        - `FTickTaskSequencer` 并不知道`AActor`的存在，它只知道执行一个`FTickFunction`的`ExecuteTick`虚函数。
        - `FActorTickFunction` 作为派生类，在`ExecuteTick`的实现中，调用了其`Target`（即`AActor`实例）的`Tick`方法。
        - 最终，控制权传递到了我们游戏开发者所编写的C++ `Tick`函数或蓝图的`Event Tick`节点。这是一个典型的**策略模式**和**多态**的应用。

---

1. **动态控制 API：`SetActorTickEnabled`及其伙伴**
    - **函数:** `SetActorTickEnabled`, `SetActorTickInterval`, `SetTickGroup`, `AddTickPrerequisiteActor`等。
        
    - **调用时机:** 在游戏运行时的任何时候，由开发者根据逻辑需要调用。
        
    - **源码剖析:**
        
        ```cpp
        void AActor::SetActorTickEnabled(bool bEnabled)
        {
            // 直接调用其PrimaryActorTick的API
            PrimaryActorTick.SetTickFunctionEnable(bEnabled);
        }
        
        void AActor::AddTickPrerequisiteActor(AActor* PrerequisiteActor)
        {
            // 将对Actor的依赖，翻译成对该Actor的PrimaryActorTick的依赖
            PrimaryActorTick.AddPrerequisite(PrerequisiteActor, PrerequisiteActor->PrimaryActorTick);
        }
        ```
        
    - **上下文剖析:**
        
        - `AActor` 提供的这一整套API，本质上都是对其内部成员`PrimaryActorTick`的**简单封装**。
        - 这种设计非常优雅。它为上层开发者提供了简洁、易于理解的接口（`SetActorTickEnabled`），同时将所有与底层调度系统交互的复杂性都**隐藏**在了`FTickFunction`的实现内部。
        - 开发者只需要与`AActor`打交道，而无需关心`FTickFunction`、`FTickTaskManager`这些底层细节，这大大降低了使用的门槛。

---

**总结**：

![AActor 与 Tick 系统的交互](attachment:77dae94b-ae07-434b-8118-2e4366fb884a:Editor___Mermaid_Chart-2025-06-11-140839.png)

AActor 与 Tick 系统的交互

`AActor` 作为 Tick 系统的主要**“客户端”**，通过以下方式与调度系统交互：

1. **生命周期绑定:** 在创建和销毁时，通过`RegisterAllActorTickFunctions`来**订阅/退订** Tick 服务。
2. **执行回调:** 通过`FActorTickFunction`这个**适配器（Adapter）**，让底层的`ExecuteTick`调用能够最终触发到上层的`AActor::Tick`逻辑。
3. **接口封装:** 提供了一系列简洁的 API，将对`FTickFunction`属性的修改**封装**起来，为开发者提供了便利。

### 2.3.3 UActorComponent

> **源码文件**：`Source\\Runtime\\Engine\\Private\\Components\\ActorComponent.cpp`

`UActorComponent` 作为 Actor 功能的载体，同样需要与底层的 Tick 调度系统进行交互。它的实现方式与`AActor`如出一辙，都是通过**封装**其内部的`FTickFunction`派生实例（`FActorComponentTickFunction`）来提供 API。

---

1. **注册与注销：`RegisterAllComponentTickFunctions`**
    - **函数:** `void UActorComponent::RegisterAllComponentTickFunctions(bool bRegister)`
        
    - **调用时机:** 这个函数通常由其所属的`AActor`在`RegisterAllActorTickFunctions`中递归调用。
        
    - **源码剖析:**
        
        ```cpp
        void UActorComponent::RegisterAllComponentTickFunctions(bool bRegister)
        {
            // 只有当组件已经注册到世界后，才能注册其Tick函数
            if (bRegistered)
            {
                // ... 防止重复注册/注销 ...
                if (bTickFunctionsRegistered != bRegister)
                {
                    // 【关键】调用RegisterComponentTickFunctions来处理自身的PrimaryComponentTick
                    RegisterComponentTickFunctions(bRegister);
                    bTickFunctionsRegistered = bRegister;
                    // ...
                }
            }
        }
        ```
        
        ```cpp
        void UActorComponent::RegisterComponentTickFunctions(bool bRegister)
        {
            if(bRegister)
            {
                // 【核心调用】SetupActorComponentTickFunction是注册逻辑的核心
                if (SetupActorComponentTickFunction(&PrimaryComponentTick))
                {
                    // 设置Target，让FPrimaryComponentTickFunction知道要Tick哪个Component
                    PrimaryComponentTick.Target = this;
                }
            }
            else
            {
                // ... 注销逻辑 ...
            }
        }
        ```
        
        ```cpp
        bool UActorComponent::SetupActorComponentTickFunction(struct FTickFunction* TickFunction)
        {
            if(TickFunction->bCanEverTick && !IsTemplate())
            {
                // ...
                // 【核心调用】最终还是调用FTickFunction的RegisterTickFunction
                TickFunction->RegisterTickFunction(ComponentLevel);
                return true;
            }
            return false;
        }
        ```
        
    - **上下文剖析:**
        
        - 流程与`AActor`高度相似：`RegisterAll...` -> `Register...` -> `Setup...` -> `FTickFunction::RegisterTickFunction`。
        - 一个重要的区别是，Component 的 Tick 注册有一个前提条件：`if (bRegistered)`，即**组件必须先附加到 Actor 并注册到世界中**，然后才能注册它的 Tick。这体现了 Component 对 Actor 的从属关系。
        - 最终，它同样是通过调用`FTickFunction`的公共 API `RegisterTickFunction`，将自己注册到`FTickTaskManager`中。

---

1. **执行 Tick 逻辑：从`ExecuteTick`到`UActorComponent::TickComponent`**
    - **函数:** `void FActorComponentTickFunction::ExecuteTick(...)`
        
    - **调用时机:** 当`FTickTaskSequencer`执行这个`FPrimaryComponentTickFunction`对应的任务时。
        
    - **源码剖析:**
        
        ```cpp
        void FActorComponentTickFunction::ExecuteTick(float DeltaTime, ...)
        {
            // ...
            // 【关键】ExecuteTickHelper是一个辅助函数，最终会调用下面的Lambda
            ExecuteTickHelper(Target, ..., [this, TickType](float DilatedTime)
            {
                // 在Lambda中，调用Component自身的TickComponent方法
                Target->TickComponent(DilatedTime, TickType, this);
            });
        }
        ```
        
        ```cpp
        void UActorComponent::TickComponent(float DeltaTime, ...)
        {
            // ...
            // 如果是蓝图组件，调用其蓝图事件图中的Tick事件
            if (/* 是蓝图 */)
            {
                ReceiveTick(DeltaTime);
            }
            // ... 处理Latent Actions ...
        }
        ```
        
    - **上下文剖析:**
        
        - 这再次展示了从底层调度到上层逻辑的**回调机制**。
        - `FTickTaskSequencer` 调用 `FActorComponentTickFunction::ExecuteTick`。
        - `ExecuteTick` 内部通过其 `Target` 指针，最终调用到我们为组件编写的 `TickComponent` 函数或蓝图的 `Event Tick` 节点。
        - 这种设计模式的复用，使得 Actor 和 Component 在 Tick 执行层面遵循着完全一致的逻辑。

---

1. **动态控制 API：一系列`Set...`和`Add...`函数**
    - **函数:** `SetComponentTickEnabled`, `SetTickGroup`, `AddTickPrerequisiteComponent`等。
        
    - **调用时机:** 游戏运行时，由开发者调用。
        
    - **源码剖析:**
        
        ```cpp
        void UActorComponent::SetComponentTickEnabled(bool bEnabled)
        {
            // 直接调用其PrimaryComponentTick的API
            PrimaryComponentTick.SetTickFunctionEnable(bEnabled);
        }
        
        void UActorComponent::AddTickPrerequisiteComponent(UActorComponent* PrerequisiteComponent)
        {
            // 将对Component的依赖，翻译成对该Component的PrimaryComponentTick的依赖
            PrimaryComponentTick.AddPrerequisite(PrerequisiteComponent, PrerequisiteComponent->PrimaryComponentTick);
        }
        ```
        
    - **上下文剖析:**
        
        - 与`AActor`的设计**完全一致**。所有这些上层 API 都是对内部成员`PrimaryComponentTick`（一个`FPrimaryComponentTickFunction`实例）的**简单、直接的封装**。
        - 这种一致性极大地降低了开发者的学习成本。一旦你学会了如何控制 Actor 的 Tick，你就自然而然地学会了如何控制 Component 的 Tick。

---

**结论**：

![UActorComponent 与 Tick 系统的交互](attachment:f40fd509-be3a-4923-a41d-6cdaa7510b75:Editor___Mermaid_Chart-2025-06-11-142214.png)

UActorComponent 与 Tick 系统的交互

通过剖析`UActorComponent`的上下文，我们可以得出结论：

`UActorComponent`在与 Tick 系统交互方面，是**`AActor`设计模式的完美复刻**。它同样通过**生命周期绑定**、**执行回调**和**接口封装**这三大手段，实现了与底层`FTickTaskManager`的解耦和交互。

这种高度一致的设计，体现了 UE 在 API 设计上的**泛化和复用**思想。它将`Tick`的能力从一个宏观的`AActor`，下放到了更细粒度的、可组合的`UActorComponent`上，同时保持了接口的统一和简洁。这使得开发者可以像组装乐高积木一样，为 Actor 添加各种带有独立更新逻辑的功能模块，而无需关心它们底层的调度细节。

# 三、总结

经过对`FTickTaskManager`从宏观定位、核心数据结构，到内部实现，再到外部调用上下文的逐层剖析，我们已经相对完整地构建了 UE 核心 Tick 调度系统的全貌。

## 3.1 设计思想：FTickTaskManager 如何解决复杂性

`FTickTaskManager` 的核心设计，可以归结为对**“分层、委托与解耦”**这一软件工程黄金法则的极致应用。它通过以下三个层面的设计，成功地将一个极其复杂的调度问题分解为多个可控的子问题：

1. **宏观分层（`UWorld` vs. `FTickTaskManager`）:** `UWorld` 作为“战略层”，只负责定义“做什么”（提供Tick上下文）和“按什么顺序做”（按序调用`RunTickGroup`）。而`FTickTaskManager`作为“战术层”，则完全封装了“如何高效正确地做”的所有技术细节。这种职责分离，使得游戏逻辑与底层调度技术彻底解耦。
2. **中观委托（`FTickTaskManager` vs. `FTickTaskSequencer`）:** `FTickTaskManager` 自身也并非单体，它将更底层的、与`Task Graph`系统直接交互的任务排序、依赖解析和并发提交工作，进一步委托给了内部的`FTickTaskSequencer`。这使得`FTickTaskManager`可以更专注于高层的管理和接口封装，而`FTickTaskSequencer`则可以专注于性能极致的调度算法。
3. **微观组织（`FTickTaskManager` vs. `FTickTaskLevel`）:** `FTickTaskManager` 并没有采用一个巨大的全局列表来管理所有Tick任务，而是创造性地通过`FTickTaskLevel`，将任务**按`ULevel`进行分组**。这种“分而治之”的策略，不仅极大地提升了管理效率，更与 UE 的关卡流送（Level Streaming）机制完美契合，使得动态加载和卸载关卡时的 Tick 管理变得轻而易举。

## 3.2 性能考量：为何它比简单的循环更高效

`FTickTaskManager` 的高性能源于其对两大性能瓶颈的精准打击：

1. **依赖解析与拓扑排序：** 通过`FTickPrerequisite`和对依赖图的构建，`FTickTaskManager`解决了任务间的执行顺序问题。它能够生成一个保证逻辑正确的**拓扑排序**，这是高性能调度的前提。
2. **并发执行:** 它的真正威力在于，在保证了依赖关系的前提下，能够识别出依赖图中所有**可以并行执行**的任务（那些没有前置依赖的节点），并将它们打包成`FGraphEvent`，提交给底层的`Task Graph`系统。由`Task Graph`将这些任务分发到多个 CPU 核心上同时运行，从而将原本漫长的串行`for`循环，变成了一个高效的并行计算过程，极大地缩短了帧时间。

## 3.3 启示：如何更好地利用 Tick 系统

通过本次剖析，我们作为上层开发者可以得到以下重要启示：

1. **善用`TickGroup`：** 理解并善用`TickGroup`是编写健壮、无抖动逻辑的第一步。
2. **巧用`AddPrerequisite`：** 当 Actor 或 Component 之间存在明确的逻辑先后关系时，使用`AddTickPrerequisite`来明确声明这种依赖，而不是依赖于不确定的执行顺序或使用延迟等“魔法”手段。
3. **为性能优化着想：** 对于那些不依赖游戏线程数据、纯计算密集型的 Tick 逻辑，可以考虑将其封装并尝试开启`bRunOnAnyThread`（需谨慎评估线程安全），以充分利用引擎的并行调度能力。

![FTickTaskManager 机制总结](attachment:3ef28401-8182-47c8-8d55-700873bf47b9:Mermaid_Chart_-_Create_complex_visual_diagrams_with_text._A_smarter_way_of_creating_diagrams.-2025-06-11-144606.png)

FTickTaskManager 机制总结

总而言之，`FTickTaskManager` 是 UE 工程智慧的集中体现。它不仅仅是一个功能模块，更是一套解决大规模实时系统中任务调度问题的完整方案。