UE4 Tick機制


為了管理時間,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. 設置FTickTaskLevelFTickContext信息

    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())
       {
           if (FinishPrerequisites.Num() > 1)
          {
               PhysicsSceneCompletion = TGraphTask<FNullGraphTask>::CreateTask(

                                              &FinishPrerequisites, ENamedThreads::GameThread).ConstructAndDispatchWhenReady();
          }
          else
          {
               PhysicsSceneCompletion = FinishPrerequisites[0];
          }
       }

   注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);
}

 

AAMyctorFTickFunction注冊調用堆棧:

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()

 

AAMyctorUMyActorComponentFTickFunction注冊調用堆棧:

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不同,AControllerAPawn有着關聯關系。以此為例說明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.hUnrealEngine\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 基類,需重載TickGetStatId虛函數。

Tickable的實現相關的邏輯:UnrealEngine\Engine\Source\Runtime\Engine\Public\Tickable.hUnrealEngine\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被調用的時機
② 可以設置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上對象

 

參考

Actor Ticking(UE官方文檔)

Unreal TickFunc調度

硬核分析:Unreal Tick 實現

UE4中的三種Tick方式

UE4 Tick實現流程

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM