UE4 Tick机制

2022/7/31 23:39:21

本文主要是介绍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 )
{
    // 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();
    }
}

 

各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):

            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)  // 不阻塞等待
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);
        }
    }
};

 

总结

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
Tickable Tick 无序数组 O(1) O(n) O(n) C++类 不可以 每帧调用,不能设置Tick时间间隔

 

参考

Actor Ticking(UE官方文档)

Unreal TickFunc调度

硬核分析:Unreal Tick 实现

UE4中的三种Tick方式

UE4 Tick实现流程

 



这篇关于UE4 Tick机制的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程