## 相关类型 ![[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`)