為了管理時間,Unreal將游戲運行時間片分隔為"Ticks"。一個Tick是關卡中所有Actors更新的最小時間單位。一個tick一般是10ms-100ms(CPU性能越好,游戲邏輯越簡單,tick的時間越短)。
Tick總流程
一共有8個TickGroup:
/** Determines which ticking group a tick function belongs to. */ UENUM(BlueprintType) enum ETickingGroup { /** Any item that needs to be executed before physics simulation starts. */ TG_PrePhysics UMETA(DisplayName="Pre Physics"), /** Special tick group that starts physics simulation. */ TG_StartPhysics UMETA(Hidden, DisplayName="Start Physics"), /** Any item that can be run in parallel with our physics simulation work. */ TG_DuringPhysics UMETA(DisplayName="During Physics"), /** Special tick group that ends physics simulation. */ TG_EndPhysics UMETA(Hidden, DisplayName="End Physics"), /** Any item that needs rigid body and cloth simulation to be complete before being executed. */ TG_PostPhysics UMETA(DisplayName="Post Physics"), /** Any item that needs the update work to be done before being ticked. */ TG_PostUpdateWork UMETA(DisplayName="Post Update Work"), /** Catchall for anything demoted to the end. */ TG_LastDemotable UMETA(Hidden, DisplayName = "Last Demotable"), /** Special tick group that is not actually a tick group. After every tick group this is repeatedly re-run until there are no more newly spawned items to run. */ TG_NewlySpawned UMETA(Hidden, DisplayName="Newly Spawned"), TG_MAX, };
各個TickGroup串行執行,保證有序。整個Tcik流程代碼:
void UWorld::Tick( ELevelTick TickType, float DeltaSeconds ) { FWorldDelegates::OnWorldTickStart.Broadcast(this, TickType, DeltaSeconds); // UNetworkPredictionWorldManager FWorldDelegates::OnWorldPreActorTick.Broadcast(this, TickType, DeltaSeconds); // FAnimationBudgetAllocator、UNetworkPredictionWorldManager就是在這個邏輯中Tick的 // TArray<FLevelCollection> LevelCollections成員變量 逐Level執行 for (int32 i = 0; i < LevelCollections.Num(); ++i) { SetupPhysicsTickFunctions(DeltaSeconds); TickGroup = TG_PrePhysics; // reset this to the start tick group 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); // 對Timer進行Tick FTickableGameObject::TickObjects(this, TickType, bIsPaused, DeltaSeconds); // 對Tickable對象進行Tick // 更新相機 RunTickGroup(TG_PostUpdateWork); RunTickGroup(TG_LastDemotable); FTickTaskManagerInterface::Get().EndFrame(); } FWorldDelegates::OnWorldPostActorTick.Broadcast(this, TickType, DeltaSeconds); // FNiagaraWorldManager、UNavigationSystemV1就是在這個邏輯中Tick的 }
各Tick步驟詳細說明:
Tick步驟 | 說明 |
SetupPhysicsTickFunctions(DeltaSeconds) | ① 沒有注冊,則注冊UWorld.StartPhysicsTickFunction到TG_StartPhysics組,注冊UWorld.EndPhysicsTickFunction到TG_EndPhysics組。 並將UWorld.StartPhysicsTickFunction設置為UWorld.EndPhysicsTickFunction依賴的前置TickFunction。 注:只會注冊一次 后續執行對應的TickGroup時,調用相應的ExecuteTick()函數 ② DeferredPhysResourceCleanup() // 清理物理引擎資源 ③ 設置PhysScene->SetUpForFrame()。如:Gravity(重力)、MaxPhysicsDeltaTime、MaxSubstepDeltaTime、MaxSubsteps、bSubstepping等 |
FTickTaskManagerInterface::Get().StartFrame (this, DeltaSeconds, LEVELTICK_All, LevelsToTick) |
① 調用TickTaskSequencer.StartFrame(),重置FTickTaskSequencer中的數據。 ② 設置當前幀的關卡數據到FTickTaskManager.LevelList數組中。 ③ 循環遍歷FTickTaskManager.LevelList數組,執行FTickTaskLevel.StartFrame(),返回為Enabled狀態FTickFunction的個數 a. 設置FTickTaskLevel的FTickContext信息 b. 執行FTickTaskLevel.ScheduleTickFunctionCooldowns()函數:將TickFunctionsToReschedule中的FTickFunction按照Cooldown進行升序排序 然后設置為CoolingDown狀態並放進AllCoolingDownTickFunctions列表中 c. 遍歷AllCoolingDownTickFunctions鏈表,將Cooldown小於等於0的FTickFunction設置為Enabled狀態 ④ 循環遍歷FTickTaskManager.LevelList數組,執行FTickTaskLevel.QueueAllTicks() a. 遍歷AllEnabledTickFunctions,逐個執行FTickFunction.QueueTickFunction()函數 ■ 先遍歷當前FTickFunction依賴的前置FTickFunction的Prerequisites列表,逐個調用QueueTickFunction()函數(遞歸),來創建Task任務(Hold不執行) ■ 然后將這些依賴的前置FTickFunction的Task的FGraphEventRef對象設為前置任務 調用FTickTaskSequencer.QueueTickTask() --》FTickTaskSequencer.StartTickTask()為當前FTickFunction創建Task任務(Hold不執行) 然后調用FTickTaskSequencer.AddTickTaskCompletion()將創建出來的Task任務添加到FTickTaskSequencer.HiPriTickTasks或FTickTaskSequencer.TickTasks數組中 以及將創建出來的Task的FGraphEventRef對象添加到FTickTaskSequencer.TickCompletionEvents數組中 b. 遍歷AllCoolingDownTickFunctions,對里面處於Enabled狀態的FTickFunction執行FTickFunction.QueueTickFunction()函數(具體細節如上) |
RunTickGroup(TG_PrePhysics) |
FTickTaskManagerInterface::Get().RunTickGroup(TG_PrePhysics, bBlockTillComplete=true) a. 調用TickTaskSequencer.ReleaseTickGroup(TG_PrePhysics, bBlockTillComplete=true) ■ 調用DispatchTickGroup(ENamedThreads::GameThread, TG_PrePhysics) Unlock來執行所有FTickTaskSequencer.HiPriTickTasks[TG_PrePhysics]或FTickTaskSequencer.TickTasks[TG_PrePhysics]數組中的Task ■ 阻塞等待FTickTaskSequencer.TickCompletionEvents[TG_PrePhysics],保證當前TG_PrePhysics組所有Task執行完成 ■ 調用FTickTaskSequencer.ResetTickGroup(TG_PrePhysics),重置FTickTaskSequencer.TickCompletionEvents[TG_PrePhysics]中的數據 |
RunTickGroup(TG_StartPhysics) | 執行FStartPhysicsTickFunction.ExecuteTick()函數,發起物理模擬過程 a. 調用UWorld.StartPhysicsSim() --》FPhysScene_PhysX.StartFrame() : FPhysScene_PhysX.TickPhysScene(FGraphEventRef& InOutCompletionEvent = PhysicsSubsceneCompletion): UpdateKinematicsOnDeferredSkelMeshes(); // Update any skeletal meshes that need their bone transforms sent to physics sim InOutCompletionEvent = FGraphEvent::CreateGraphEvent(); PhysXCompletionTask* Task = new PhysXCompletionTask(InOutCompletionEvent, ApexScene->getTaskManager()); ApexScene->simulate(AveragedFrameTime, true, Task, SimScratchBuffer.Buffer, SimScratchBuffer.BufferSize); // 發起物理模擬(異步並行) Task->removeReference(); FGraphEventArray MainScenePrerequisites; MainScenePrerequisites.Add(PhysicsSubsceneCompletion); FGraphEventArray FinishPrerequisites = FDelegateGraphTask::CreateAndDispatchWhenReady( FDelegateGraphTask::FDelegate::CreateRaw(this, &FPhysScene_PhysX::SceneCompletionTask), GET_STATID(STAT_FDelegateGraphTask_ProcessPhysScene_Sync), &MainScenePrerequisites, ENamedThreads::GameThread, ENamedThreads::GameThread); if (FinishPrerequisites.Num()) &FinishPrerequisites, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(); 注1:當ApexScene->simulate()物理模擬完成后,會回調執行PhysXCompletionTask,則PhysicsSubsceneCompletion事件就會完成 由於SceneCompletionTask的前置MainScenePrerequisites任務列表中,只有一個PhysicsSubsceneCompletion事件 此時就會開始執行FPhysScene_PhysX::SceneCompletionTask() --》FPhysScene_PhysX::ProcessPhysScene(): PxScene* PScene = GetPxScene(); PScene->lockWrite(); bool bSuccess = ApexScene->fetchResults(true, &OutErrorCode); // 取回物理模擬計算結果 PScene->unlockWrite(); 注2:FPhysScene_PhysX::SceneCompletionTask()執行完成后,PhysicsSceneCompletion事件就會完成 |
RunTickGroup(TG_DuringPhysics, false) | FTickTaskManagerInterface::Get().RunTickGroup(TG_DuringPhysics, bBlockTillComplete=false) // 不阻塞等待 a. 調用TickTaskSequencer.ReleaseTickGroup(TG_DuringPhysics, bBlockTillComplete=false) ■ 調用DispatchTickGroup(ENamedThreads::GameThread, TG_DuringPhysics) Unlock來執行所有FTickTaskSequencer.HiPriTickTasks[TG_DuringPhysics]或FTickTaskSequencer.TickTasks[TG_DuringPhysics]數組中的Task ■ 調用FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread),阻塞執行完GameThread中所有任務 |
RunTickGroup(TG_EndPhysics) | 執行FEndPhysicsTickFunction.ExecuteTick()函數 a. 調用FPhysScene_PhysX::GetCompletionEvents()獲取PhysicsSceneCompletion事件 b. 調用FPhysScene_PhysX::GetCompletionEvents()來判斷PhysicsSceneCompletion事件是否完成,未完成則阻塞等待 c. PhysicsSceneCompletion事件完成后,會觸發UWorld::FinishPhysicsSim() --》FPhysScene_PhysX::EndFrame(LineBatcher) |
RunTickGroup(TG_PostPhysics) | FTickTaskManagerInterface::Get().RunTickGroup(TG_PostPhysics, bBlockTillComplete=true) |
GetTimerManager().Tick(DeltaSeconds) | Timer Tick |
FTickableGameObject::TickObjects(this, TickType, bIsPaused, DeltaSeconds) | Tickable Tick |
RunTickGroup(TG_PostUpdateWork) | TG_PostUpdateWork階段在攝像機更新之后,如武器的鐳射特效必須知曉攝像機的准確朝向,就要將控制這些特效的Actor放到這個階段。 這個階段也可用於在邏輯幀中最靠后運行的游戲邏輯,如解決格斗游戲中兩個角色在同一幀中嘗試抓住對方的情況。 |
RunTickGroup(TG_LastDemotable) | FTickTaskManagerInterface::Get().RunTickGroup(TG_LastDemotable, bBlockTillComplete=true) |
FTickTaskManagerInterface::Get().EndFrame() | ① 執行FTickTaskSequencer.EndFrame()函數 ② 循環遍歷FTickTaskManager.LevelList數組,執行執行FTickTaskLevel.EndFrame() a.執行FTickTaskLevel.ScheduleTickFunctionCooldowns()函數:將TickFunctionsToReschedule中的FTickFunction按照Cooldown進行排序 然后設置為CoolingDown狀態並放進AllCoolingDownTickFunctions列表中 ③ 清空FTickTaskManager.LevelList數組 |
Actor / ActorComponent Tick
一個Actor對象可以包含多個ActorComponent對象。雖然它們之間是組合關系,但它們的Tick注冊和開啟都是互相獨立的,不存在依賴關系。
FTickFunction中的數據成員:
/** * Abstract Base class for all tick functions. **/ USTRUCT() struct ENGINE_API FTickFunction { GENERATED_USTRUCT_BODY() public: // The following UPROPERTYs are for configuration and inherited from the CDO/archetype/blueprint etc /** * Defines the minimum tick group for this tick function. These groups determine the relative order of when objects tick during a frame update. * Given prerequisites, the tick may be delayed. * * @see ETickingGroup * @see FTickFunction::AddPrerequisite() */ UPROPERTY(EditDefaultsOnly, Category="Tick", AdvancedDisplay) TEnumAsByte<enum ETickingGroup> TickGroup; /** * Defines the tick group that this tick function must finish in. These groups determine the relative order of when objects tick during a frame update. * * @see ETickingGroup */ UPROPERTY(EditDefaultsOnly, Category="Tick", AdvancedDisplay) TEnumAsByte<enum ETickingGroup> EndTickGroup; public: /** Bool indicating that this function should execute even if the game is paused. Pause ticks are very limited in capabilities. **/ UPROPERTY(EditDefaultsOnly, Category="Tick", AdvancedDisplay) uint8 bTickEvenWhenPaused:1; /** If false, this tick function will never be registered and will never tick. Only settable in defaults. */ UPROPERTY() uint8 bCanEverTick:1; /** If true, this tick function will start enabled, but can be disabled later on. */ UPROPERTY(EditDefaultsOnly, Category="Tick") uint8 bStartWithTickEnabled:1; /** If we allow this tick to run on a dedicated server */ UPROPERTY(EditDefaultsOnly, Category="Tick", AdvancedDisplay) uint8 bAllowTickOnDedicatedServer:1; /** Run this tick first within the tick group, presumably to start async tasks that must be completed with this tick group, hiding the latency. */ uint8 bHighPriority:1; /** If false, this tick will run on the game thread, otherwise it will run on any thread in parallel with the game thread and in parallel with other "async ticks" **/ uint8 bRunOnAnyThread:1; // ... ... }
注冊Tick
在Actor / ActorComponent的C++類型,才能在構造函數中設置PrimaryActorTick.bCanEverTick的值,來決定是否在BeginPlay時為其注冊FTickFunction。
而在Actor / ActorComponent的藍圖類型中是不能重新設置PrimaryActorTick.bCanEverTick的。
如果是藍圖類型缺省對象或CDO,不會注冊FTickFunction函數,詳見AActor::RegisterAllActorTickFunctions函數:
void AActor::RegisterAllActorTickFunctions(bool bRegister=true, bool bDoComponents=false) { if(!IsTemplate()) // IsTemplate缺省參數為RF_ArchetypeObject|RF_ClassDefaultObject { RegisterActorTickFunctions(bRegister); // 注冊FTickFunction } }
以AMyActor / UMyActorComponent為例來說明注冊FTickFunction函數過程
UMyActorComponent代碼:
/**************MyActorComponent.h****************/ #pragma once #include "Components/ActorComponent.h" #include "MyActorComponent.generated.h" UCLASS() class UMyActorComponent : public UActorComponent { GENERATED_BODY() public: // Sets default values for this component's properties UMyActorComponent(); protected: // Called when the game starts virtual void BeginPlay() override; public: // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; }; /***************MyActorComponent.cpp*******************/ #include "MyActorComponent.h" UMyActorComponent::UMyActorComponent() { PrimaryComponentTick.bCanEverTick = true; // 為該UMyActorComponent對象注冊FTickFunction } // Called when the game starts void UMyActorComponent::BeginPlay() { Super::BeginPlay(); } // Called every frame void UMyActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); }
AMyActor代碼:
/***************MyActor.h*******************/ #pragma once #include "GameFramework/Actor.h" #include "MyActorComponent.h" #include "MyActor.generated.h" UCLASS() class AMyActor : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties AMyActor(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; private: // 若不指定EditAnywhere,在AMyActor派生的藍圖類中,看不到其MyActorComponent* MyActorComponent組件的成員數據 UPROPERTY(EditAnywhere) class UMyActorComponent* MyActorComponent; }; /***************MyActor.cpp*******************/ #include "MyActor.h" // Sets default values AMyActor::AMyActor() { PrimaryActorTick.bCanEverTick = true; // 為該AMyActor對象注冊FTickFunction MyActorComponent = CreateDefaultSubobject<UMyActorComponent>(TEXT("MyActorComponent")); } // Called when the game starts or when spawned void AMyActor::BeginPlay() { Super::BeginPlay(); } // Called every frame void AMyActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); }
AAMyctor的FTickFunction注冊調用堆棧:
FTickTaskLevel::AddTickFunction(FTickFunction * TickFunction=0x00000253bd5c1938) FTickTaskManager::AddTickFunction(ULevel * InLevel=0x00000253b6370100, FTickFunction * TickFunction=0x00000253bd5c1938) FTickFunction::RegisterTickFunction(ULevel * Level=0x00000253b6370100) AActor::RegisterActorTickFunctions(bool bRegister=true) AActor::RegisterAllActorTickFunctions(bool bRegister=true, bool bDoComponents=false) AActor::BeginPlay() AMyActor::BeginPlay()
AAMyctor中UMyActorComponent的FTickFunction注冊調用堆棧:
FTickTaskLevel::AddTickFunction(FTickFunction * TickFunction=0x00000253bde00f80) FTickTaskManager::AddTickFunction(ULevel * InLevel=0x00000253b6370100, FTickFunction * TickFunction=0x00000253bde00f80) FTickFunction::RegisterTickFunction(ULevel * Level=0x00000253b6370100) UActorComponent::SetupActorComponentTickFunction(FTickFunction * TickFunction=0x00000253bde00f80) UActorComponent::RegisterComponentTickFunctions(bool bRegister=true) UActorComponent::RegisterAllComponentTickFunctions(bool bRegister=true) AActor::BeginPlay() AMyActor::BeginPlay()
AActor::BeginPlay函數邏輯:
void AActor::BeginPlay() { SetLifeSpan( InitialLifeSpan ); RegisterAllActorTickFunctions(true, false); // 注冊Actor自身的FTickFunction TInlineComponentArray<UActorComponent*> Components; GetComponents(Components); for (UActorComponent* Component : Components) { if (Component->IsRegistered() && !Component->HasBegunPlay()) { Component->RegisterAllComponentTickFunctions(true); // 注冊Actor中所有UActorComponent的FTickFunction Component->BeginPlay(); } } if (GetAutoDestroyWhenFinished()) { if (UWorld* MyWorld = GetWorld()) { if (UAutoDestroySubsystem* AutoDestroySys = MyWorld->GetSubsystem<UAutoDestroySubsystem>()) { AutoDestroySys->RegisterActor(this); } } } ReceiveBeginPlay(); ActorHasBegunPlay = EActorBeginPlayState::HasBegunPlay; }
啟用Tick
在Actor / ActorComponent的C++類型,在構造函數中設置PrimaryActorTick.bStartWithTickEnabled=true,就會在注冊FTickFunction前將ETickState TickState設為Enabled。
在Actor / ActorComponent的藍圖類型中,可以重新設置PrimaryActorTick.bStartWithTickEnabled的值。
在后續的邏輯中,可以調用void AActor::SetActorTickEnabled(bool bEnabled)和void UActorComponent::SetComponentTickEnabled(bool bEnabled)來Enabled/Disable Tick。
設置MyBlueprintActor(從AMyActor派生)藍圖類的bStartWithTickEnabled:
設置MyBlueprintActor(從AMyActor派生)藍圖類中的UMyActorComponent組件的bStartWithTickEnabled:
Tick變量缺省值
變量 | Actor | ActorComponent | 含義 |
PrimaryActorTick.bCanEverTick | false | false | 是否注冊Tick 注:只能在c++構造函數中設置 |
PrimaryActorTick.bStartWithTickEnabled | true | true | 是否在注冊時就啟用Tick |
PrimaryActorTick.TickGroup | TG_PrePhysics | TG_DuringPhysics | 每個TickFunction都可以指定TickGroup,但是這個TickGroup並不代表最終實際執行的TickGroup。 根據每個Tick注冊的Prerequisite不同,Tick的執行Group會被延遲。 例如,如果TickFunction要求的Prerequisite是在TG_PostPhysics中的,那么即便它自己是注冊為TG_PrePhyiscs, 也會被推遲到Post才能執行, TickFunction的ActualStartTickGroup會變成實際的Tick執行組。 |
PrimaryActorTick.EndTickGroup | TG_PrePhysics | TG_PrePhysics | 決定TickFunction必須在哪個TickGroup中或之前完成。 |
PrimaryActorTick.bAllowTickOnDedicatedServer | true | true | 是否在DS上執行 |
PrimaryActorTick.TickInterval | 小於等於0則每幀都更新 大於0則按照TickInterval時間間隔來更新 |
||
PrimaryActorTick.bTickEvenWhenPaused | false | false | PIE中點擊Pause按鈕后,是否仍然執行Tick |
PrimaryActorTick.bHighPriority | false | false | 為true,添加到FTickTaskSequencer.HiPriTickTasks數組中(作為高優先級的任務) 為false,添加到FTickTaskSequencer.TickTasks數組中(作為正常優先級的任務) 也可通過FTickFunction.SetPriorityIncludingPrerequisites(bool bInHighPriority)來修改Task的優先級 |
PrimaryActorTick.bRunOnAnyThread | false | false | 為ture,放在TaskGraph的AnyThread上跑 為false,放在GameThread上跑 |
Tick執行
Actor / ActorComponent的的Tick會被封裝成FTickFunctionTask任務,由TaskGraph系統來執行。
AMyActor執行Tick的調用堆棧:
AMyActor::Tick(float DeltaTime=0.0141042992) AActor::TickActor(float DeltaSeconds=0.0141042992, ELevelTick TickType=LEVELTICK_All, FActorTickFunction & ThisTickFunction={...}) FActorTickFunction::ExecuteTick(float DeltaTime=0.0141042992, ELevelTick TickType=LEVELTICK_All, ENamedThreads::Type CurrentThread=GameThread, const TRefCountPtr<FGraphEvent> & MyCompletionGraphEvent={...}) FTickFunctionTask::DoTask(ENamedThreads::Type CurrentThread=GameThread, const TRefCountPtr<FGraphEvent> & MyCompletionGraphEvent={...}) TGraphTask<FTickFunctionTask>::ExecuteTask(TArray<FBaseGraphTask *,TSizedDefaultAllocator<32>> & NewTasks={...}, ENamedThreads::Type CurrentThread=GameThread) FNamedTaskThread::ProcessTasksNamedThread(int QueueIndex=0, bool bAllowStall=false) FNamedTaskThread::ProcessTasksUntilIdle(int QueueIndex=0) FTaskGraphImplementation::ProcessThreadUntilIdle(ENamedThreads::Type CurrentThread=GameThread) FTickTaskSequencer::ReleaseTickGroup(ETickingGroup WorldTickGroup=TG_DuringPhysics, bool bBlockTillComplete=false) FTickTaskManager::RunTickGroup(ETickingGroup Group=TG_DuringPhysics, bool bBlockTillComplete=false) UWorld::RunTickGroup(ETickingGroup Group=TG_DuringPhysics, bool bBlockTillComplete=false) UWorld::Tick(ELevelTick TickType=LEVELTICK_All, float DeltaSeconds=0.0141042992)
UMyActorComponent執行Tick的調用堆棧:
UMyActorComponent::TickComponent(float DeltaTime=0.0141042992, ELevelTick TickType=LEVELTICK_All, FActorComponentTickFunction * ThisTickFunction=0x000001a26f19cf80) FActorComponentTickFunction::ExecuteTick::__l2::<lambda>(float DilatedTime=0.0141042992) FActorComponentTickFunction::ExecuteTickHelper<void <lambda>(float)>(UActorComponent * Target=0x000001a26f19cf40, bool bTickInEditor=false, float DeltaTime=0.0141042992, ELevelTick TickType=LEVELTICK_All, const FActorComponentTickFunction::ExecuteTick::__l2::void <lambda>(float) & ExecuteTickFunc=void <lambda>(float DilatedTime){...}) FActorComponentTickFunction::ExecuteTick(float DeltaTime=0.0141042992, ELevelTick TickType=LEVELTICK_All, ENamedThreads::Type CurrentThread=GameThread, const TRefCountPtr<FGraphEvent> & MyCompletionGraphEvent={...}) FTickFunctionTask::DoTask(ENamedThreads::Type CurrentThread=GameThread, const TRefCountPtr<FGraphEvent> & MyCompletionGraphEvent={...}) TGraphTask<FTickFunctionTask>::ExecuteTask(TArray<FBaseGraphTask *,TSizedDefaultAllocator<32>> & NewTasks={...}, ENamedThreads::Type CurrentThread=GameThread) FNamedTaskThread::ProcessTasksNamedThread(int QueueIndex=0, bool bAllowStall=false) FNamedTaskThread::ProcessTasksUntilIdle(int QueueIndex=0) FTaskGraphImplementation::ProcessThreadUntilIdle(ENamedThreads::Type CurrentThread=GameThread) FTickTaskSequencer::ReleaseTickGroup(ETickingGroup WorldTickGroup=TG_DuringPhysics, bool bBlockTillComplete=false) FTickTaskManager::RunTickGroup(ETickingGroup Group=TG_DuringPhysics, bool bBlockTillComplete=false) UWorld::RunTickGroup(ETickingGroup Group=TG_DuringPhysics, bool bBlockTillComplete=false) UWorld::Tick(ELevelTick TickType=LEVELTICK_All, float DeltaSeconds=0.0141042992)
Tick的依賴性
與其他單獨的Actor不同,AController和APawn有着關聯關系。以此為例說明Tick的依賴性。
每次AController執行AttachToPawn函數綁定一個Pawn對象時,就會調用AddPawnTickDependency函數來建立它們之間Tick依賴關系:
void AController::AddPawnTickDependency(APawn* NewPawn) { if (NewPawn != NULL) { bool bNeedsPawnPrereq = true; UPawnMovementComponent* PawnMovement = NewPawn->GetMovementComponent(); if (PawnMovement && PawnMovement->PrimaryComponentTick.bCanEverTick) { PawnMovement->PrimaryComponentTick.AddPrerequisite(this, this->PrimaryActorTick); // Don't need a prereq on the pawn if the movement component already sets up a prereq. if (PawnMovement->bTickBeforeOwner || NewPawn->PrimaryActorTick.GetPrerequisites().Contains(FTickPrerequisite(PawnMovement, PawnMovement->PrimaryComponentTick))) { bNeedsPawnPrereq = false; } } if (bNeedsPawnPrereq) { NewPawn->PrimaryActorTick.AddPrerequisite(this, this->PrimaryActorTick); } } }
總架構圖
控制台變量
變量 | 說明 |
CriticalPathStall.TickStartFrame | Sleep for the given time in start frame. Time is given in ms. This is a debug option used for critical path analysis and forcing a change in the critical path. 注:非shipping包可用。 |
tick.AddIndirectTestTickFunctions | Add no-op ticks to test performance of ticking infrastructure. |
tick.AddTestTickFunctions | Add no-op ticks to test performance of ticking infrastructure. |
tick.AllowAsyncComponentTicks | Used to control async component ticks. |
tick.AllowAsyncTickCleanup | If true, ticks are cleaned up in a task thread. |
tick.AllowAsyncTickDispatch | If true, ticks are dispatched in a task thread. |
tick.AllowConcurrentTickQueue | If true, queue ticks concurrently. 注:非windows、非android平台可用。 |
tick.AnimationDelaysEndGroup | If > 0, then skeletal meshes that do not rely on physics simulation will set their animation end tick group to TG_PostPhysics. |
tick.DoAsyncEndOfFrameTasks | Experimental option to run various things concurrently with the HUD render. |
tick.DoAsyncEndOfFrameTasks.Randomize | Used to add random sleeps to tick.DoAsyncEndOfFrameTasks to shake loose bugs on either thread. Also does random render thread flushes from the game thread. |
tick.DoAsyncEndOfFrameTasks.ValidateReplicatedProperties | If true, validates that replicated properties haven't changed during the Slate tick. Results will not be valid if demo.ClientRecordAsyncEndOfFrame is also enabled. |
tick.HiPriSkinnedMeshes | If > 0, then schedule the skinned component ticks in a tick group before other ticks. |
tick.LightweightTimeguardThresholdMS | Threshold in milliseconds for the tick timeguard |
tick.LogTicks | Spew ticks for debugging. |
tick.RemoveTestTickFunctions | Remove no-op ticks to test performance of ticking infrastructure. |
tick.SecondsBeforeEmbeddedAppSleeps | When built as embedded, how many ticks to perform before sleeping |
tick.ShowPrerequistes | When logging ticks, show the prerequistes; debugging. |
Timer Tick
通過傳入Delegate執行體和相關參數來調用TimerManager的SetTimer方法來設置一個定時器。
Timer的實現相關的邏輯:UnrealEngine\Engine\Source\Runtime\Engine\Public\TimerManager.h、UnrealEngine\Engine\Source\Runtime\Engine\Private\TimerManager.cpp
Automation System(自動化測試系統)測試代碼:UnrealEngine\Engine\Source\Runtime\Engine\Private\TimerManagerTests.cpp
/** * Sets a timer to call the given native function at a set interval. If a timer is already set * for this handle, it will replace the current timer. * * @param InOutHandle If the passed-in handle refers to an existing timer, it will be cleared before the new timer is added. A new handle to the new timer is returned in either case. * @param InObj Object to call the timer function on. * @param InTimerMethod Method to call when timer fires. * @param InRate The amount of time (in seconds) between set and firing. If <= 0.f, clears existing timers. * @param InbLoop true to keep firing at Rate intervals, false to fire only once. * @param InFirstDelay The time (in seconds) for the first iteration of a looping timer. If < 0.f InRate will be used. */ template< class UserClass > FORCEINLINE void SetTimer(FTimerHandle& InOutHandle, UserClass* InObj, typename FTimerDelegate::TUObjectMethodDelegate< UserClass >::FMethodPtr InTimerMethod, float InRate, bool InbLoop = false, float InFirstDelay = -1.f); // AActor對象中調用SetTimer GetWorldTimerManager().SetTimer(TimerHandle_UpdateNetSpeedsTimer, this, &AGameNetworkManager::UpdateNetSpeedsTimer, 1.0f); // UObject對象中調用SetTimer GetWorld()->GetTimerManager().SetTimer(TimerHandle, FTimerDelegate::CreateUObject(this, &UNavMeshRenderingComponent::TimerFunction), 1, true);
FTimerManager中其他一些重要函數:
FTimerHandle SetTimerForNextTick(...); // 設置下一幀調用的Timer
void ClearTimer(FTimerHandle& InHandle); // 清除名為InHandle的Timer
void ClearAllTimersForObject(void const* Object); // 清除與Object對象關聯的所有Timer
void PauseTimer(FTimerHandle InHandle); // 暫停名為InHandle的Timer
void UnPauseTimer(FTimerHandle InHandle); // 繼續運行名為InHandle的Timer
float GetTimerRate(FTimerHandle InHandle) const; // 獲取名為InHandle的Timer的間隔
bool IsTimerActive(FTimerHandle InHandle) const; // 名為InHandle的Timer是否處於激活狀態
bool IsTimerPaused(FTimerHandle InHandle) const // 名為InHandle的Timer是否處於暫停狀態
bool IsTimerPending(FTimerHandle InHandle) const; // 名為InHandle的Timer是否處於Pending狀態
bool TimerExists(FTimerHandle InHandle) const; // 名為InHandle的Timer是否存在
float GetTimerElapsed(FTimerHandle InHandle) const; // 名為InHandle的Timer已經運行了多少秒
float GetTimerRemaining(FTimerHandle InHandle) const; // 名為InHandle的Timer還有多少秒會被執行
bool HasBeenTickedThisFrame() const; // 當前幀是否已經執行了Timer的Tick邏輯
Timer Tick實現機制
Timer定時器是基於小根堆實現,TimerManager在每次Tick時從檢查堆頂元素,如果到了執行時間就取出並執行,直到條件不滿足停止本次Tick。
void FTimerManager::Tick(float DeltaTime) { // ... ... while (ActiveTimerHeap.Num() > 0) { // 堆定定時任務到達執行時間 if (InternalTime > Top->ExpireTime) { // 執行定時任務 ActiveTimerHeap.HeapPop(CurrentlyExecutingTimer, FTimerHeapOrder(Timers), /*bAllowShrinking=*/ false); Top->TimerDelegate.Execute(); } } // ... ... }
TArray<FTimerHandle> ActiveTimerHeap小根堆結構示例如下:
Timer示例
/******* 創建帶參數的Timer *******/ UCLASS() class UDelegatepTimerTest : public UObject { GENERATED_BODY() public: void DelegateProc1(FString MapName) { UE_LOG(LogTemp, Log, TEXT("DelegateProc1 : %s"), *MapName); } void Test() { FTimerDelegate MyDelegate; // DECLARE_DELEGATE(FTimerDelegate); 代理類型FTimerDelegate為單播代理,無參、無返回值 MyDelegate.BindUObject(this, &UDelegatepTimerTest::DelegateProc1, FString(TEXT("FarmLand"))); // 動態傳入Payload變量 GetWorld()->GetTimerManager().SetTimer(MyTimeHandle, MyDelegate, 5.0f, false); // 創建一個5.0s的一次性定時器 Payload示例 } void Clear() { GetWorld()->GetTimerManager().ClearTimer(MyTimeHandle); } private: FTimerHandle MyTimeHandle; };
Tickable Tick
Tickable Tick服務對象為C++ 類,繼承自FTickableGameObject 基類,需重載Tick和GetStatId虛函數。
Tickable的實現相關的邏輯:UnrealEngine\Engine\Source\Runtime\Engine\Public\Tickable.h、UnrealEngine\Engine\Source\Runtime\Engine\Private\Tickable.cpp
新Tickable 對象初始化后會添加到單例FTickableStatics 集合中,FTickableGameObject 單例在每次 Tick 后遍歷 FTickableStatics 集合中的所有 Tickable 對象並執行,因此Tickable 對象會在每一幀執行,不能設置 Tick 的時間間隔。
void FTickableGameObject::TickObjects(UWorld* World, const int32 InTickType, const bool bIsPaused, const float DeltaSeconds) { FTickableStatics& Statics = FTickableStatics::Get(); for (const FTickableObjectEntry& TickableEntry : Statics.TickableObjects) { TickableObject->Tick(DeltaSeconds); } }
Tickable示例
UCLASS() class UMyBPObject : public UObject, public FTickableGameObject { GENERATED_BODY() public: UMyBPObject(); ~UMyBPObject(); virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(MyBPObject, STATGROUP_Tickables); // 如果不希望被統計,直接返回return TStatId();即可 } virtual bool IsTickable() const override { return !this->IsDefaultSubobject(); } // CDO對象就不要Tick了 virtual void Tick(float DeltaTime) override { if (GFrameCounter % 300 == 0) { FPlatformProcess::SleepNoStats(0.03); } } };
FTicker Tick
FTicker::GetCoreTicker()單例通過AddTicker來添加FTickerDelegate代理對象到內部的TArray<FElement, TInlineAllocator<1>> 數組中 注:AddTicker時可指定間隔時間,為0表示逐幀
然后在void Tick(float DeltaTime)函數中,遍歷該數組來實現對綁定的代理函數的執行 注:FTicker::GetCoreTicker().Tick函數會在引擎的Tick循環的末尾處被調用
當代理函數返回false,添加FTickerDelegate代理對象會被立即刪除,從而達到只執行一次的目的。返回true,則不會刪除,除非顯示調用RemoveTicker來刪除FTickerDelegate代理對象
FTickerDelegate代理的類型定義為:DECLARE_DELEGATE_RetVal_OneParam(bool, FTickerDelegate, float);
FTicker的實現相關邏輯見:UnrealEngine\Engine\Source\Runtime\Core\Public\Containers\Ticker.h、UnrealEngine\Engine\Source\Runtime\Core\Private\Containers\Ticker.cpp
class FTest1 { public: void Init(); void Uninit(); private: FTickerDelegate TickerDelegate; bool OnTick(float deltaTime); }; // -------------------------------------------------------- void FTest1::Init() { TickerDelegate.BindRaw(this, &FTest1::OnTick); // 添加TickerDelegate代理到Ticker列表中 FTicker::GetCoreTicker().AddTicker(TickerDelegate); // 第二個參數float InDelay保持默認值0,表示逐幀檢測執行 } void FTest1::Uninit() { // 從Ticker列表中刪除TickerDelegate代理 FTicker::GetCoreTicker().RemoveTicker(TickerDelegate); } bool FTest1::OnTick(float deltaTime) { return true; // 返回true表示執行完函數后不移除 }
每幀執行某個邏輯
FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([](float Unused) { UE_LOG(LogTemp, Log, TEXT("Ticker test! Frame:%llu"), GFrameCounter); return true; }));
每隔3秒執行某個邏輯
FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([](float Unused) { UE_LOG(LogTemp, Log, TEXT("Ticker test! Frame:%llu"), GFrameCounter); return true; }), 3.0f);
下一幀執行一次某個邏輯
FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([](float Unused) { UE_LOG(LogTemp, Log, TEXT("Ticker test! Frame:%llu"), GFrameCounter); return false; }));
1秒后執行一次某個邏輯
FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([](float Unused) { UE_LOG(LogTemp, Log, TEXT("Ticker test! Frame:%llu"), GFrameCounter); return false; }), 1.0f);
FBackgroundableTicker
用於某些平台下(如:IOS)切后台之后執行Tick邏輯
FBackgroundableTicker的實現相關邏輯見:UnrealEngine\Engine\Source\Runtime\Core\Public\Containers\BackgroundableTicker.h、UnrealEngine\Engine\Source\Runtime\Core\Private\Containers\BackgroundableTicker.cpp
FTicker& Ticker = FBackgroundableTicker::GetCoreTicker();
FTickerObjectBase Tick
基於FTick實現的
FTickerObjectBase的實現相關邏輯見:UnrealEngine\Engine\Source\Runtime\Core\Public\Containers\Ticker.h、UnrealEngine\Engine\Source\Runtime\Core\Private\Containers\Ticker.cpp
class FTest2 : public FTickerObjectBase { public: FTest2(float InDelay, bool bInTickOnce); bool Tick(float DeltaSeconds) override; private: bool bTickOnce; }; // --------------------------------------------------------- FTest2::FTest2(float InDelay, bool bInTickOnce) : FTickerObjectBase(InDelay, FTicker::GetCoreTicker()), bTickOnce(bInTickOnce) { } bool FTest2::Tick(float DeltaSeconds) { UE_LOG(LogTemp, Log, TEXT("FTickerObjectBase test! Frame:%llu"), GFrameCounter); return bTickOnce; }
每幀執行某個邏輯
FTest2* pTest = new FTest2(0.0f, true);
每隔3秒執行某個邏輯
FTest2* pTest = new FTest2(3.0f, true);
下一幀執行一次某個邏輯
FTest2* pTest = new FTest2(0.0f, false);
1秒后執行一次某個邏輯
FTest2* pTest = new FTest2(1.0f, false);
FTickableObjectRenderThread Tick
FTickableObjectRenderThread Tick服務對象為C++ 類,C++類需要從FTickableObjectRenderThread繼承來獲取Tick能力,重載Tick、IsTickable和GetStatId虛函數。用於RenderThread上跑的對象的Tick。
這些C++類的對象的Register(在構造函數中創建)和Unregister(在析構函數中銷毀)邏輯必須在RenderThread中
這些對象可在RenderThread中直接構造並立即Register
也可以在其他線程(如:GameThread)中構造,然后向RenderThread發送COMMAND命令來延遲Register,在析構時,也需要向RenderThread發送COMMAND命令來延遲Unregister
// 在GameThread中構造FDefaultGameMoviePlayer對象,bRigisterImmediately傳入false不立即Register FDefaultGameMoviePlayer::FDefaultGameMoviePlayer() : FTickableObjectRenderThread(/*bool bRegisterImmediately=*/false, /*bool bInHighFrequency=*/true) { } // 后續在GameThread里面調用Initialize函數,通過ENQUEUE_RENDER_COMMAND宏向渲染線程發起Register void FDefaultGameMoviePlayer::Initialize(FSlateRenderer& InSlateRenderer, TSharedPtr<SWindow> TargetRenderWindow) { // ... ... FDefaultGameMoviePlayer* InMoviePlayer = this; ENQUEUE_RENDER_COMMAND(RegisterMoviePlayerTickable)( [InMoviePlayer](FRHICommandListImmediate& RHICmdList) { InMoviePlayer->Register(); }); // ... ... } // 析構時,通過ENQUEUE_RENDER_COMMAND宏向渲染線程發起Unregister FDefaultGameMoviePlayer::~FDefaultGameMoviePlayer() { if ( bInitialized ) { // This should not happen if initialize was called correctly. This is a fallback to ensure that the movie player rendering tickable gets unregistered on the rendering thread correctly Shutdown(); } else if (GIsRHIInitialized) { // Even when uninitialized we must safely unregister the movie player on the render thread FDefaultGameMoviePlayer* InMoviePlayer = this; ENQUEUE_RENDER_COMMAND(UnregisterMoviePlayerTickable)( [InMoviePlayer](FRHICommandListImmediate& RHICmdList) { InMoviePlayer->Unregister(); }); } FCoreDelegates::IsLoadingMovieCurrentlyPlaying.Unbind(); FlushRenderingCommands(); // 阻塞讓Unregister在渲染線程上執行完成 }
FTickableObjectRenderThread內部有兩個靜態數組,來存放這些Register進來的對象
struct FRenderingThreadTickableObjectsArray : public TArray<FTickableObjectRenderThread*> { ... ... }; static FRenderingThreadTickableObjectsArray RenderingThreadTickableObjects; // 低頻Tick對象,1.f/GRenderingThreadMaxIdleTickFrequency秒執行一次 注:GRenderingThreadMaxIdleTickFrequency缺省為40 static FRenderingThreadTickableObjectsArray RenderingThreadHighFrequencyTickableObjects; // 高頻Tick對象,1.f/(4.0f * GRenderingThreadMaxIdleTickFrequency)秒執行一次
FTickableObjectRenderThread的實現相關的邏輯:UnrealEngine\Engine\Source\Runtime\RenderCore\Public\TickableObjectRenderThread.h
FRenderingThreadTickHeartbeat線程中會通過ENQUEUE_RENDER_COMMAND宏向渲染線程中發送命令,在命令被RenderThread執行后,會調用TickRenderingTickables函數來執行各個FTickableObjectRenderThread的Tick函數
class FRenderingThreadTickHeartbeat : public FRunnable { public: // FRunnable interface. virtual bool Init(void) { GSuspendRenderingTickables = 0; OutstandingHeartbeats.Reset(); // ... ... return true; } virtual void Exit(void) { } virtual void Stop(void) { } virtual uint32 Run(void) { while(GRunRenderingThreadHeartbeat.Load(EMemoryOrder::Relaxed)) { FPlatformProcess::Sleep(1.f/(4.0f * GRenderingThreadMaxIdleTickFrequency)); if (!GIsRenderingThreadSuspended.Load(EMemoryOrder::Relaxed) && OutstandingHeartbeats.GetValue() < 4) { OutstandingHeartbeats.Increment(); ENQUEUE_RENDER_COMMAND(HeartbeatTickTickables)( [](FRHICommandList& RHICmdList) { OutstandingHeartbeats.Decrement(); // make sure that rendering thread tickables get a chance to tick, even if the render thread is starving // but if GSuspendRenderingTickables is != 0 a flush is happening so don't tick during it if (!GIsRenderingThreadSuspended.Load(EMemoryOrder::Relaxed) && !GSuspendRenderingTickables.Load(EMemoryOrder::Relaxed)) { TickRenderingTickables(); } }); } } return 0; } }; void TickRenderingTickables() { static double LastTickTime = FPlatformTime::Seconds(); // calc how long has passed since last tick double CurTime = FPlatformTime::Seconds(); float DeltaSeconds = CurTime - LastTickTime; TickHighFrequencyTickables(CurTime); if (DeltaSeconds < (1.f/GRenderingThreadMaxIdleTickFrequency)) { return; } // tick any rendering thread tickables for (int32 ObjectIndex = 0; ObjectIndex < FTickableObjectRenderThread::RenderingThreadTickableObjects.Num(); ObjectIndex++) { FTickableObjectRenderThread* TickableObject = FTickableObjectRenderThread::RenderingThreadTickableObjects[ObjectIndex]; // make sure it wants to be ticked and the rendering thread isn't suspended if (TickableObject->IsTickable()) { STAT(FScopeCycleCounter(TickableObject->GetStatId());) TickableObject->Tick(DeltaSeconds); } } // update the last time we ticked LastTickTime = CurTime; } void TickHighFrequencyTickables(double CurTime) { static double LastHighFreqTime = FPlatformTime::Seconds(); float DeltaSecondsHighFreq = CurTime - LastHighFreqTime; // tick any high frequency rendering thread tickables. for (int32 ObjectIndex = 0; ObjectIndex < FTickableObjectRenderThread::RenderingThreadHighFrequencyTickableObjects.Num(); ObjectIndex++) { FTickableObjectRenderThread* TickableObject = FTickableObjectRenderThread::RenderingThreadHighFrequencyTickableObjects[ObjectIndex]; // make sure it wants to be ticked and the rendering thread isn't suspended if (TickableObject->IsTickable()) { STAT(FScopeCycleCounter(TickableObject->GetStatId());) TickableObject->Tick(DeltaSecondsHighFreq); } } LastHighFreqTime = CurTime; }
FTickableObjectRenderThread派生類
FTickableObjectRenderThread TRenderResourcePool /* UnrealEngine\Engine\Source\Runtime\Engine\Public\ResourcePool.h */ FGlobalDynamicMeshIndexPool /* UnrealEngine\Engine\Source\Runtime\Engine\Private\DynamicMeshBuilder.cpp */ FGlobalDynamicMeshVertexPool /* UnrealEngine\Engine\Source\Runtime\Engine\Private\DynamicMeshBuilder.cpp */ FBoneBufferPool /* UnrealEngine\Engine\Source\Runtime\Engine\Public\GPUSkinVertexFactory.h */ FClothBufferPool /* UnrealEngine\Engine\Source\Runtime\Engine\Public\GPUSkinVertexFactory.h */ FDefaultGameMoviePlayer /* UnrealEngine\Engine\Source\Runtime\MoviePlayer\Private\DefaultGameMoviePlayer.h */ FShaderPipelineCache /* UnrealEngine\Engine\Source\Runtime\RenderCore\Public\ShaderPipelineCache.h */
FTickableObjectRenderThread示例
class FTest3 : public FTickableObjectRenderThread { public: FTest3(bool bRegisterImmediately, bool bInHighFrequency); ~FTest3(); void Init(); virtual void Tick(float DeltaTime) override; virtual TStatId GetStatId() const override; virtual bool IsTickable() const override; private: bool bRegisterObjectImmediately; }; // --------------------------------------------------------- FTest3::FTest3(bool bRegisterImmediately, bool bInHighFrequency) : FTickableObjectRenderThread(bRegisterImmediately, bInHighFrequency) , bRegisterObjectImmediately(bRegisterImmediately) { } FTest3::~FTest3() { if (!bRegisterObjectImmediately) { FTest3* InObject = this; ENQUEUE_RENDER_COMMAND(UnregisterTest3Tickable)( [InObject](FRHICommandListImmediate& RHICmdList) { InObject->Unregister(); }); FlushRenderingCommands(); // 阻塞讓Unregister在渲染線程上執行完成 } } void FTest3::Init() { FTest3* InObject = this; ENQUEUE_RENDER_COMMAND(UnregisterTest3Tickable)( [InObject](FRHICommandListImmediate& RHICmdList) { InObject->Register(); }); } void FTest3::Tick(float DeltaTime) { uint32 Tid = FPlatformTLS::GetCurrentThreadId(); if (Tid == GRenderThreadId) { UE_LOG(LogTemp, Log, TEXT("%p: It's RenderThread. Frame:%u"), this, GFrameNumberRenderThread); } } TStatId FTest3::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FTest3, STATGROUP_Tickables); } bool FTest3::IsTickable() const { return true; } ///////////////////////////////////////////////////////////////////////////////////////////// // 在GameThread創建一個延遲Register的高頻Tick的FTest3對象 FTest3* pObj1 = new FTest3(/*bool bRegisterImmediately=*/false, /*bool bInHighFrequency=*/true); pObj1->Init(); // 進行Reigster // 從GameThread向RenderThread投遞一個Task ENQUEUE_RENDER_COMMAND(UnregisterTest3RenderTickable)( [](FRHICommandListImmediate& RHICmdList) { // 在RenderThread創建一個立即Register的低頻Tick的FTest3 FTest3* pObj2 = new FTest3(/*bool bRegisterImmediately=*/true, /*bool bInHighFrequency=*/false); });
總結
Tick類型 | 實現機制 | 插入復雜度 | 刪除復雜度 | Tick復雜度 | 服務對象 | 設置Tick間隔時間 | 特點 |
Actor / ActorComponent Tick | Cooldown機制,以Tick間隔時間維護有序隊列 | O(1) | O(n) | O(nlogn) | Actor / ActorComponent | 可以 | ① 通過設置Tick Group, 確定Tick被調用的時機 |
Timer Tick | 以Tick間隔時間維護小根堆 | O(logn) | O(n) | O(logn) | Delegate | 可以 | 粒度細化為Delegate 注:需要獲取當前World |
Tickable Tick | 無序數組 | O(1) | O(n) | O(n) | C++類 | 不可以 | 每幀調用,不能設置Tick時間間隔 |
FTick Tick | 數組(按AddTicker的先后順序執行) | O(1) | O(n) | O(n) | Delegate | 可以 | 粒度細化為Delegate 注:不需要獲取當前World |
FTickerObjectBase Tick | 基於FTick | O(1) | O(1) | O(1) | C++類 | 可以 | 注:不需要獲取當前World |
FTickableObjectRenderThread Tick | 數組 | O(1) | O(n) | O(n) | C++類 | 不可以 | 注:用於RenderThread上對象 |
參考