## 相关类型
![[ETimerStatus]]
![[FTimerHandle]]
![[FTimerData]]
## 成员变量
```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;
//上次生成的ID
static uint64 LastAssignedSerialNumber;
/** The game instance that created this timer manager. May be null if this timer manager wasn't created by a game instance. */
UGameInstance* OwningGameInstance;
```
## 重要方法
### Tick
`FTimerManager`的`Tick`是在`UWorld::Tick`中被调用的,作为一个提供定时服务的模块,直接看`Tick`就能理解大部分逻辑了
#### 判断本帧是否已执行过
因为`Tick`会在一帧内多次调用,`Timer`不需要反复执行
```cpp
if (HasBeenTickedThisFrame())
{
return;
}
```
`HasBeenTickedThisFrame()`其实就是用了成员变量和`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();
}
```
### SetTimer
几个对应不同情况的`SetTimer`其实都是调用的`InternalSetTimer`,直接看`InternalSetTimer`做了什么
#### 添加已有Timer,清理原有Timer
```cpp
if (FindTimer(InOutHandle))
{
// if the timer is already set, just clear it and we'll re-add it, since
// there's no data to maintain.
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
若本帧已`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);
```
### SetTimerForNextTick
顾名思义,作用就是在下一帧执行,所以不需要循环,马上执行(`Rate = 0.f`),状态为已添加,过期时间是现在(也就是`InternalTime`)