## UWorld::Tick 从`UWorld::Tick`入手,剔除无关主题的内容和一些判断后,主要逻辑如下 ```cpp for (int32 i = 0; i < LevelCollections.Num(); ++i) { SetupPhysicsTickFunctions(DeltaSeconds); FTickTaskManagerInterface::Get().StartFrame(this, DeltaSeconds, TickType, LevelsToTick); RunTickGroup(TG_PrePhysics); RunTickGroup(TG_StartPhysics); RunTickGroup(TG_DuringPhysics, false); RunTickGroup(TG_EndPhysics); RunTickGroup(TG_PostPhysics); GetTimerManager().Tick(DeltaSeconds); FTickableGameObject::TickObjects(this, TickType, bIsPaused, DeltaSeconds); RunTickGroup(TG_PostUpdateWork); FTickTaskManagerInterface::Get().EndFrame(); } ``` 其中调用最多的`RunTickGroup`只是个简单封装 ```cpp void UWorld::RunTickGroup(ETickingGroup Group, bool bBlockTillComplete = true) { FTickTaskManagerInterface::Get().RunTickGroup(Group, bBlockTillComplete); TickGroup = ETickingGroup(TickGroup + 1); } ``` 可以得出由`World::Tick`分成的三种`Tick`机制,分别是[[#TickFunction]]、[[#TimerManager]]和[[#TickableGameObject]] ## TickFunction `AActor`所用的`Tick`就是此类,通过`AActor::PrimaryActorTick`注册并提供`Tick`方法 `RrimaryActorTick`的类型是`FActorTickFunction`,其实就是继承了`FTickFunction`并重写了相关虚函数 以`AActor::RegisterActorTickFunctions`为例查看`TickFunction`的注册方法 ```cpp PrimaryActorTick.Target = this; PrimaryActorTick.SetTickFunctionEnable(PrimaryActorTick.bStartWithTickEnabled || PrimaryActorTick.IsTickFunctionEnabled()); PrimaryActorTick.RegisterTickFunction(GetLevel()); ``` 先是将`Target`指向本`Actor`,再设置`TickFunction`的启用状态,跟着`RegisterTickFunction`即可找到注册流程 重要的类关系是这样的 ![[TickFunction结构图|1080]] - `FTickTaskManager`是单例,负责提供所有的对外接口,管理所有的`FTickTaskLevel` - `FTickTaskSequencer`也是单例,所有需要执行的`FTickFunction`都会被添加到`Sequencer`,`Sequencer`根据优先级和`TickGroup`维护并有序执行 - `FTickTaskLevel`按照`TickState`管理本`Level`的`FTickFunction`,并提供管理接口,将满足条件和状态的`FTickFunction`放入`Sequencer`处理 ### FTickFunction 重要的变量 ```cpp //状态 ETickState TickState; //执行频率,单位为秒,为0时每帧都执行 float TickInterval; //前置条件,要执行所有先决条件数组的FTickFunction后才会执行本TickFunction TArray<struct FTickPrerequisite> Prerequisites; //最早可以开始执行的TickGroup TEnumAsByte<enum ETickingGroup> TickGroup //必须在该TickGroup中完成 TEnumAsByte<enum ETickingGroup> EndTickGroup; ``` 状态有三种,`CoolingDown`可以转换到`Enable`,但`Disable`不行 ```cpp enum class ETickState : uint8 { Disabled, Enabled, CoolingDown }; ``` `TickInterval`大于`0`时会被设置为`CoolingDown`状态,直到满足执行时间再切换到`Enable`,`Disable`状态的`TickFunction`什么都不会做,除非手动调整状态 `Prerequisites`会优先自身执行,可以通过`FTickFunction::AddPrerequisite`添加前置条件,封装了一个函数`FTickFunction::QueueTickFunction`将所有前置条件送入`TickTaskSequencer` `TickGroup`一个有八个,根据名字就能知道大致作用,决定了`TickFunction`的相对顺序 ```cpp enum ETickingGroup { TG_PrePhysics UMETA(DisplayName="Pre Physics"), TG_StartPhysics UMETA(Hidden, DisplayName="Start Physics"), TG_DuringPhysics UMETA(DisplayName="During Physics"), TG_EndPhysics UMETA(Hidden, DisplayName="End Physics"), TG_PostPhysics UMETA(DisplayName="Post Physics"), TG_PostUpdateWork UMETA(DisplayName="Post Update Work"), TG_LastDemotable UMETA(Hidden, DisplayName = "Last Demotable"), TG_NewlySpawned UMETA(Hidden, DisplayName="Newly Spawned"), TG_MAX, }; ``` 其中`TG_NewlySpawned`比较特殊,不是一个真正的`TickGroup`,在每个`TickGroup`运行后都会执行,处理所有在`Tick`中新增的`TickFunction` `TG_StartPhysics`和`TG_EndPhysics`是专用于物理的 `TickFunction`中`TickGroup`和`EndTickGroup`可以视为一种“建议”,在`QueueTickFunction`时会根据`CanDemoteIntoTickGroup`函数返回值和几个规则对`TickGroup`和`EndTickGroup`进行处理,并将结果赋值给`ActualStartTickGroup`和`ActualEndTickGroup` 1. 如果`TickFunction`有依赖的`TickFunction`,`ActualTickGroup`取所有依赖的最大值。如果所有依赖的最大值不等于自身指定的`TickGroup`,则会选定`>=ActualTickGroup`且可降级到的第一个`TickGroup`。比如`TG_StartPhysics`是专用于物理的,如果计算出的`ActualTickGroup`是`TG_StartPhysics`,则会取下一个`TickGroup`即`TG_DuringPhysics` 2. `TickGroup` <= `ActualStartTickGroup` <= `ActualEndTickGroup` <= `EndTickGroup` ### RegisterTickFunction ```cpp //省略了不重要的内容 void FTickFunction::RegisterTickFunction(ULevel* Level) { if (!IsTickFunctionRegistered()) { FTickTaskManager::Get().AddTickFunction(Level, this); InternalData->bRegistered = true; } } void FTickTaskManager::AddTickFunction(ULevel* InLevel, FTickFunction* TickFunction) { FTickTaskLevel* Level = TickTaskLevelForLevel(InLevel); Level->AddTickFunction(TickFunction); TickFunction->InternalData->TickTaskLevel = Level; } void FTickTaskLevel::AddTickFunction(FTickFunction* TickFunction) { if (TickFunction->TickState == FTickFunction::ETickState::Enabled) { AllEnabledTickFunctions.Add(TickFunction); if (bTickNewlySpawned) { NewlySpawnedTickFunctions.Add(TickFunction); } } else { AllDisabledTickFunctions.Add(TickFunction); } } ``` ![[RegisterTickFunction流程|1080]] `TickTaskManager`通过`ULevel`获取对应的`TickTaskLevel`,并根据`ETickState`将`TickFunction`加入到不同的管理数据结构中 `ETickState`的三种状态对应`TaskLevel`中的三个管理结构`AllEnabledTickFunctions`、`AllCoolingDownTickFunctions`和`AllDisabledTickFunctions` ### SetTickFunctionEnable 前面`RrimaryActorTick`注册时还用了`SetTickFunctionEnable`修改`FActorTickFunction`的状态,已经在`TaskLevel`管理数据结构中处理如下 - 检查是否已在`TaskLevel`中注册,若已注册则移除后再添加 - 若未注册,则简单修改自身状态即可 ### StartFrame & EndFrame 一对方法,相关处理在之间调用,比如`UWorld::Tick`中,所有的`RunTickGroup`都在`StartFrame`和`EndFrame`之间 `StartFrame`负责设置相关初始条件及预处理 `FTickTaskManager::StartFrame`会调用每个`FTickTaskLevel::StartFrame`和`FTickTaskSequencer::StartFrame`,比如每个类的`bTickNewlySpawned`在此时置为`true`,在处理过程中添加的`TickFunction`会单独组织起来后续处理 #### FTickTaskLevel::StartFrame 以`FTickTaskLevel::StartFrame`为例,函数设置了上下文并修改所有符合条件的`TickFunction`状态 ```cpp int32 StartFrame(const FTickContext& InContext) { //设置了一堆上下文 Context.TickGroup = ETickingGroup(0); // reset this to the start tick group Context.DeltaSeconds = InContext.DeltaSeconds; Context.TickType = InContext.TickType; Context.Thread = ENamedThreads::GameThread; Context.World = InContext.World; bTickNewlySpawned = true; int32 CooldownTicksEnabled = 0; { //有Interval的TickFunction会被设置为CoolingDown状态串成一个链表 ScheduleTickFunctionCooldowns(); //将CooldownTime也就是Interval符合要求的TickFunction状态设为Enabled float CumulativeCooldown = 0.f; FTickFunction* TickFunction = AllCoolingDownTickFunctions.Head; while (TickFunction) { if (CumulativeCooldown + TickFunction->InternalData->RelativeTickCooldown >= Context.DeltaSeconds) { TickFunction->InternalData->RelativeTickCooldown -= (Context.DeltaSeconds - CumulativeCooldown); break; } CumulativeCooldown += TickFunction->InternalData->RelativeTickCooldown; TickFunction->TickState = FTickFunction::ETickState::Enabled; TickFunction = TickFunction->InternalData->Next; ++CooldownTicksEnabled; } } return AllEnabledTickFunctions.Num() + CooldownTicksEnabled; } ``` `EndFrame`代表本帧结束,清理无关数据并置`bTickNewlySpawned`为`false` #### FTickTaskManager::StartFrame `StartFrame`调用所有`Level`的`FTickTaskLevel::StartFrame`之后代表相关数据结构已经就绪 再调用`FTickTaskLevel::QueueAllTicks`处理每个`Level`中每个`TickFunction`的前置条件 ### RunTickGroup `RunTickGroup`的工作分为两部分 1. 调用`FTickTaskSequencer::ReleaseTickGroup`阻塞调用所有已添加的`TickFunction` 2. 将所有`Tick`中也就是`StartFrame`和`EndFrame`期间添加的`TickFunction`进行处理,包括处理前置任务和`Interval`等,并加入到下一个`TickGroup`执行,如果当前`TickGroup`已经是最后一个,解锁所有`TG_NewlySpawned`的任务阻塞执行 #### FTickTaskSequencer::ReleaseTickGroup 参数`TickGroup`指的是该`TickGroup`起始,表示执行所有`StartTickGroup`等于该`TickGroup`的`TickTask` 1. 阻塞执行该`TickGroup`起始的所有高优先级的`TickFunction`,执行完毕清空该`TickGroup`所有`Task` 2. 阻塞执行该`TickGroup`起始的所有低优先级的`TickFunction`,执行完毕清空该`TickGroup`所有`Task` 3. 清空该`TickGroup`起始的数据结构,也就是清理了该`TickGroup`所有`TickFunction` ```cpp //高优先级 TArrayWithThreadsafeAdd<TGraphTask<FTickFunctionTask>*> HiPriTickTasks[TG_MAX][TG_MAX] //低优先级 TArrayWithThreadsafeAdd<TGraphTask<FTickFunctionTask>*> TickTasks[TG_MAX][TG_MAX] ``` `Tasks`二维数组的含义是`Tasks[StartTickGroup][EndTickGroup]`,对应`FTickFunction::QueueTickFunction`时决定的`FTickFunction::FInternalData::ActualStartTick和ActualEndTick` 执行时先执行`Tasks[StartTickGroup][StartTickGroup]`,然后是`[StartTickGroup][StartTickGroup + 1]`直到`EndTickGroup` #### NewlySpawned 在执行完本组`TickTask`后,会处理期间新增的`TickTask`,并在下一个`TickGroup`处理依赖的`TickFunction`,如果下一个`TickGroup`是`TG_NewlySpawned`,就阻塞执行所有`TickTask` ```cpp TickTaskSequencer.ReleaseTickGroup(Group, bBlockTillComplete); Context.TickGroup = ETickingGroup(Context.TickGroup + 1); for (int32 Iterations = 0;Iterations < 101; Iterations++) { int32 Num = 0; for( int32 LevelIndex = 0; LevelIndex < LevelList.Num(); LevelIndex++ ) { Num += LevelList[LevelIndex]->QueueNewlySpawned(Context.TickGroup); } if (Num && Context.TickGroup == TG_NewlySpawned) { SCOPE_CYCLE_COUNTER(STAT_TG_NewlySpawned); TickTaskSequencer.ReleaseTickGroup(TG_NewlySpawned, true); } } ``` ### 使用 `FTickFunction`有三个虚函数,其中两个是纯虚函数 ```cpp virtual void ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) PURE_VIRTUAL(,); /** Abstract function to describe this tick. Used to print messages about illegal cycles in the dependency graph **/ virtual FString DiagnosticMessage() PURE_VIRTUAL(, return TEXT("DiagnosticMessage() not implemented");); /** Function to give a 'context' for this tick, used for grouped active tick reporting */ virtual FName DiagnosticContext(bool bDetailed) { return NAME_None; } ``` 还有`Type Traits`描述为 ```cpp template<> struct TStructOpsTypeTraits<FTickFunction> : public TStructOpsTypeTraitsBase2<FTickFunction> { enum { WithCopy = false, WithPureVirtual = true }; }; ``` 使用时我们只需要继承`FTickFunction`,重写虚函数,并去掉`Type Traits`的`WithPureVirtual`描述即可,保留`WithCopy = false`,因为`FTickFunction`不能复制 #### 引擎的例子 以给`AActor`提供`Tick`的`PrimaryActorTick`为例,它的类型为`FActorTickFunction`,重写的虚函数和`Type Traits`如下 ```cpp void FActorTickFunction::ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) { if (Target && !Target->IsPendingKillOrUnreachable()) { if (TickType != LEVELTICK_ViewportsOnly || Target->ShouldTickIfViewportsOnly()) { FScopeCycleCounterUObject ActorScope(Target); Target->TickActor(DeltaTime*Target->CustomTimeDilation, TickType, *this); } } } FString FActorTickFunction::DiagnosticMessage() { return Target->GetFullName() + TEXT("[TickActor]"); } FName FActorTickFunction::DiagnosticContext(bool bDetailed) { if (bDetailed) { FString ContextString = FString::Printf(TEXT("%s/%s"), *GetParentNativeClass(Target->GetClass())->GetName(), *Target->GetClass()->GetName()); return FName(*ContextString); } else { return GetParentNativeClass(Target->GetClass())->GetFName(); } } template<> struct TStructOpsTypeTraits<FActorTickFunction> : public TStructOpsTypeTraitsBase2<FActorTickFunction> { enum { WithCopy = false }; }; ``` ## TimerManager 日常使用姿势之一 ```cpp World->GetTimerManager().SetTimer(TimerHandle,Callback,Rate,bLoop,FirstDelay); ``` 对应的声明 ```cpp void SetTimer(FTimerHandle& InOutHandle, TFunction<void(void)>&& Callback, float InRate, bool InbLoop, float InFirstDelay = -1.f) ``` - `InOutHandle`:如果传入的句柄引用了现有的定时器,则在添加新定时器之前将其清除。在任何情况下,新定时器的新句柄都将返回。 - `Callback`:要调用的定时器函数。 - `InRate`:设置和触发之间的时间量(以秒为单位)。如果`<= 0.f`,则清除现有的定时器。 - `InbLoop`:`true`表示保持以`Rate`间隔触发,`false`表示仅触发一次。 - `InFirstDelay`:循环定时器的第一次迭代的时间(以秒为单位)。如果`<0.f`,则使用`InRate` ### 数据结构 在具体逻辑之前先看下数据是如何组织的 ```cpp //所有的Timer,FTimerHandle的Handle就是该数组的下标 TSparseArray<FTimerData> Timers; //所有已添加的Timer的大顶堆,ExpireTime最大也就是过期最久未激活的Timer在最上面 TArray<FTimerHandle> ActiveTimerHeap; //已暂停的Timer TSet<FTimerHandle> PausedTimerSet; //已创建待添加的Timer TSet<FTimerHandle> PendingTimerSet; //用于从BoundUObject快速查找对应的Handle,FTimerData.TimerIndicesByObjectKey -> FTimerHandle TMap<const void*, TSet<FTimerHandle>> ObjectToTimers; //内置时钟,独立于world double InternalTime; //索引指向当前正在执行的定时器委托,如果没有正在执行,则指向INDEX_NONE。用于处理“定时器委托操作定时器”的情况 FTimerHandle CurrentlyExecutingTimer; //Tick后设置为GFrameCounter,用于确认本帧已Tick过或不是这一帧 uint64 LastTickedFrame; ``` `TimerManager`的管理单位,包含`Timer`的属性、对应`FTimerManager`中`Timers`的`Handle`以及`Timer`激活时需要执行的`Delegate` ```cpp struct FTimerData{ //是否循环 uint8 bLoop : 1; //该Timer是用委托创建的,如果委托已经失效,那么定时器也应无效 uint8 bRequiresDelegate : 1; //Timer的状态 ETimerStatus Status; //创建到下次执行的时间 or 如果循环的话就是执行的间隔 float Rate; //Timer的过期时间,当前时间大于过期时间即执行委托 double ExpireTime; //激活时需要执行的委托or函数 FTimerUnifiedDelegate TimerDelegate; //该Timer对应的Handle FTimerHandle Handle; //缓存委托绑定的对象地址,方便查找Object对应的Handle,尤其是对象已销毁的时候 const void* TimerIndicesByObjectKey; //关卡类型用于查找关卡设置正确的上下文 ELevelCollectionType LevelCollection; } ``` 其中`Status`一共有五个状态 ```cpp enum class ETimerStatus : uint8 { //已创建,未添加 Pending, //已添加,可在Tick中被执行 Active, //暂停 Paused, //执行中 Executing, //已添加,待移除 ActivePendingRemoval }; ``` `FTimerHandle`可以视作一个`Key` ```cpp struct FTimerHandle{ //对应FTimerManager::Timers的下标 uint64 Handle; } ``` ### SetTimer `SetTimer`的几种回调情况,包括传递函数、成员函数、委托等都通过重载最终调用了同一个内部函数`InternalSetTimer` ```cpp void FTimerManager::InternalSetTimer(FTimerHandle& InOutHandle, FTimerUnifiedDelegate&& InDelegate, float InRate, bool InbLoop, float InFirstDelay); ``` `FTimerUnifiedDelegate`是一个包装类,可以将委托、动态委托、函数包起来统一传给`InternalSetTimer` #### 添加已有Timer,清理原有Timer 因为`SetTimer`永远返回新的`Handle`,如果定时器已存在则清理掉,重新添加 ```cpp if (FindTimer(InOutHandle)) { InternalClearTimer(InOutHandle); } ``` #### 初始化新Timer ```cpp FTimerData NewTimerData; NewTimerData.TimerDelegate = MoveTemp(InDelegate); NewTimerData.Rate = InRate; NewTimerData.bLoop = InbLoop; NewTimerData.bRequiresDelegate = NewTimerData.TimerDelegate.IsBound(); ``` #### 设置关卡信息 ```cpp // Set level collection const UWorld* const OwningWorld = OwningGameInstance ? OwningGameInstance->GetWorld() : nullptr; if (OwningWorld && OwningWorld->GetActiveLevelCollection()) { NewTimerData.LevelCollection = OwningWorld->GetActiveLevelCollection()->GetType(); } ``` #### 计算FirstDelay的值 ```cpp const float FirstDelay = (InFirstDelay >= 0.f) ? InFirstDelay : InRate; ``` #### 设置ExpireTime和Status 通过`LastTickedFrame`判断是否已`Tick`,若本帧已`Tick`过,直接添加 ```cpp NewTimerData.ExpireTime = InternalTime + FirstDelay; NewTimerData.Status = ETimerStatus::Active; NewTimerHandle = AddTimer(MoveTemp(NewTimerData)); ActiveTimerHeap.HeapPush(NewTimerHandle, FTimerHeapOrder(Timers)); ``` 若本帧未`Tick`过设置为待添加状态,`ExpireTime`直接设置为`FirstDelay`是因为在`Tick`时`Pending`状态的`Timer`转为`Active`状态时会加上`TimerManager`的`InternalTime` ```cpp NewTimerData.ExpireTime = FirstDelay; NewTimerData.Status = ETimerStatus::Pending; NewTimerHandle = AddTimer(MoveTemp(NewTimerData)); PendingTimerSet.Add(NewTimerHandle); ``` ### Tick `FTimerManager`的`Tick`是在`UWorld::Tick`中被调用的,作为一个提供定时的模块,`Tick`包含主要逻辑,看下`Tick`都干了些什么 #### 判断本帧是否已执行过 因为`Tick`会在一帧内多次调用,`Timer`不需要反复执行 ```cpp if (HasBeenTickedThisFrame()) { return; } ``` `HasBeenTickedThisFrame()`其实就是用了成员变量`LastTickedFrame`和`GFrameCounter`来判断是否已`Tick`过 ```cpp bool FORCEINLINE HasBeenTickedThisFrame() const { return (LastTickedFrame == GFrameCounter); } ``` #### 取堆顶Timer 当已添加的`Timer`堆不为空时取顶部的`Handle`,也就是过期最久的`Timer`,并根据`Handle`的`Index`从`Times`获取到对应的`TimerData` ```cpp FTimerHandle TopHandle = ActiveTimerHeap.HeapTop(); int32 TopIndex = TopHandle.GetIndex(); FTimerData* Top = &Timers[TopIndex]; ``` #### 移除待删除Timer 当`Timer`的状态为已添加待删除也就是`ActivePendingRemoval`时从Times数组中移除该`Timer`,从堆中也移除该`Timer`,并按照`FTimerHeapOrder`重整堆,开始下一次循环 ```cpp if (Top->Status == ETimerStatus::ActivePendingRemoval) { ActiveTimerHeap.HeapPop(TopHandle, FTimerHeapOrder(Timers), /*bAllowShrinking=*/ false); RemoveTimer(TopHandle); continue; } ``` `InternalTime`是`TimerManager`的成员变量,代表内部的时间,当 `InternalTime > FTimerData::ExpireTime`时表示该`Timer`需要激活代理并执行后续操作 #### 切换Level到Timer相关的上下文 通过`TimerData`存储的`LevelCollection`切换关卡上下文 ```cpp const int32 LevelCollectionIndex = OwningWorld ? OwningWorld->FindCollectionIndexByType(Top->LevelCollection) : INDEX_NONE; FScopedLevelCollectionContextSwitch LevelContext(LevelCollectionIndex, LevelCollectionWorld); ``` #### 切换到执行中状态 从堆中移除要执行的Timer并赋值给`CurrentlyExecutingTimer`,切换状态到执行中 ```cpp ActiveTimerHeap.HeapPop(CurrentlyExecutingTimer, FTimerHeapOrder(Timers), /*bAllowShrinking=*/ false); Top->Status = ETimerStatus::Executing; ``` #### 计算执行次数 分成循环和不循环两种情况 ```cpp int32 const CallCount = Top->bLoop ? FMath::TruncToInt( (InternalTime - Top->ExpireTime) / Top->Rate ) + 1 : 1; ``` #### 执行 在`Execute`前后均会检查`Execute`过程中有没有删除自身,除了`Execute`语句,其他行都是在做相关检查和处理 ```cpp for (int32 CallIdx=0; CallIdx<CallCount; ++CallIdx) { checkf(!WillRemoveTimerAssert(CurrentlyExecutingTimer), TEXT("RemoveTimer(CurrentlyExecutingTimer) - due to fail before Execute()")); Top->TimerDelegate.Execute(); // Update Top pointer, in case it has been invalidated by the Execute call Top = FindTimer(CurrentlyExecutingTimer); checkf(!Top || !WillRemoveTimerAssert(CurrentlyExecutingTimer), TEXT("RemoveTimer(CurrentlyExecutingTimer) - due to fail after Execute()")); if (!Top || Top->Status != ETimerStatus::Executing) { break; } } ``` #### 处理循环Timer 对于循环`Timer`,检查委托是否合法,更新过期时间到下次,恢复状态到已添加并入堆 非循环`Timer`直接删除 置`CurrentlyExecutingTimer`为`INDEX_NONE` ```cpp // if timer requires a delegate, make sure it's still validly bound (i.e. the delegate's object didn't get deleted or something) if (Top->bLoop && (!Top->bRequiresDelegate || Top->TimerDelegate.IsBound())) { // Put this timer back on the heap Top->ExpireTime += CallCount * Top->Rate; Top->Status = ETimerStatus::Active; ActiveTimerHeap.HeapPush(CurrentlyExecutingTimer, FTimerHeapOrder(Timers)); } else { RemoveTimer(CurrentlyExecutingTimer); } CurrentlyExecutingTimer.Invalidate(); ``` #### 其他处理 更新`Frame` ```cpp LastTickedFrame = GFrameCounter; ``` 待添加Timer加入已添加队列 ```cpp if( PendingTimerSet.Num() > 0 ) { for (FTimerHandle Handle : PendingTimerSet) { FTimerData& TimerToActivate = GetTimer(Handle); TimerToActivate.ExpireTime += InternalTime; TimerToActivate.Status = ETimerStatus::Active; ActiveTimerHeap.HeapPush( Handle, FTimerHeapOrder(Timers) ); } PendingTimerSet.Reset(); } ``` ## TickableGameObject 有时候我们可能并不需要`Actor`庞大的功能,比如复制反射等等,仅仅需要在一个类上加一个简单的`Tick`,那就用上`TickableGameObject`了 ### FTickableObjectBase `FTickableGameObject`继承了`FTickableObjectBase`,基类提供了供管理类使用的静态方法并声明了一组虚函数 #### 静态方法 用于操作`FTickableStatics`中的`TickableObjects` ```cpp static void AddTickableObject(TArray<FTickableObjectEntry>& TickableObjects, FTickableObjectBase* TickableObject); static void RemoveTickableObject(TArray<FTickableObjectEntry>& TickableObjects, FTickableObjectBase* TickableObject, const bool bIsTickingObjects); ``` #### 虚函数 ```cpp virtual void Tick( float DeltaTime ) = 0;//纯虚函数用于子类Tick操作 //指明自己的TickType virtual ETickableTickType GetTickableTickType() const { return ETickableTickType::Conditional; } //可以Tick返回True virtual bool IsTickable() const { return true; } virtual TStatId GetStatId() const = 0; ``` ### FTickableGameObject 在`FTickableGameObject`的构造函数中获取管理类`FTickableStatics`的单例并注册自身,析构时移除自身 #### FTickableGameObject() ```cpp FTickableGameObject::FTickableGameObject() { FTickableStatics& Statics = FTickableStatics::Get(); //如果UObject模块已初始化,就将自身添加到Statics的待加入Tick的队列 if (UObjectInitialized()) { Statics.QueueTickableObjectForAdd(this); } //直接将自身添加到Statics的Tick对象数组 else { AddTickableObject(Statics.TickableObjects, this); } } ``` #### ~FTickableGameObject() ```cpp FTickableGameObject::~FTickableGameObject() { //从Statics删除自己 FTickableStatics& Statics = FTickableStatics::Get(); Statics.RemoveTickableObjectFromNewObjectsQueue(this); FScopeLock LockTickableObjects(&Statics.TickableObjectsCritical); RemoveTickableObject(Statics.TickableObjects, this, Statics.bIsTickingObjects); } ``` #### TickObjects ```cpp static TickObjects(UWorld* World, const int32 InTickType, const bool bIsPaused, const float DeltaSeconds) ``` 加锁避免`GC`破坏`UObject` ```cpp FScopeLock LockTickableObjects(&Statics.TickableObjectsCritical); ``` 将`Statics`中`NewTickableObjects`也就是待添加的对象添加到`Tick`数组中 ```cpp for (FTickableGameObject* NewTickableObject : Statics.NewTickableObjects) { AddTickableObject(Statics.TickableObjects, NewTickableObject); } Statics.NewTickableObjects.Empty(); ``` 遍历所有`TickableObject`, 根据`TickType`、`LEVELTICK`枚举和相关虚函数判断`TickableObject`是否满足以下条件,是则调用`TickableObject->Tick(DeltaSeconds)` ```cpp for (const FTickableObjectEntry& TickableEntry : Statics.TickableObjects) { if (FTickableGameObject* TickableObject = static_cast<FTickableGameObject*>(TickableEntry.TickableObject)) { //可Tick且属于该World if (((TickableEntry.TickType == ETickableTickType::Always) || TickableObject->IsTickable()) && (TickableObject->GetTickableGameObjectWorld() == World) && TickableObject->IsAllowedToTick()) { const bool bIsGameWorld = InTickType == LEVELTICK_All || (World && World->IsGameWorld()); //编辑器环境且编辑器环境可Tick //未暂停且关卡为实时 //已暂停且暂停时可Tick if ((GIsEditor && TickableObject->IsTickableInEditor()) || (bIsGameWorld && ((!bIsPaused && TickType != LEVELTICK_TimeOnly) || (bIsPaused && TickableObject->IsTickableWhenPaused())))) { FScopeCycleCounter Context(TickableObject->GetStatId()); //执行Tick逻辑 TickableObject->Tick(DeltaSeconds); } } } } ``` ### 使用 继承`FTickableGameObject`,重写虚函数即可给自定义的类增加`Tick`功能,引擎在很多场景使用了这种方式来提供`Tick`,比如行为树等等