## 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`,比如行为树等等