• 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. 设置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):

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

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

    总结

    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实现流程

  • 相关阅读:
    Sublime安装package control的操作
    Sublime的简单操作
    C# 泛型方法
    C# 数组的讲解(ArrayList 与List的区别)
    免费的天气API
    bootstrapValidator的验证
    sqlServer 多行合并为一行
    bootstrap的安装注意
    SQL数据库中把一个表中的数据复制到另一个表中
    JavaScript的误区
  • 原文地址:https://www.cnblogs.com/kekec/p/14781454.html
Copyright © 2020-2023  润新知