UE5-MultiThread
Thread
FRunnable与FRunnableThread
FRunnable
定义的是“接口函数”,FRunableThread
是“线程实体”。在FRunnableThread
的执行周期中会调用到FRunnable
中的虚函数,因此通常情况下我们都会派生FRunnable
FRunnableThread
可以类比标准库中的std::thread
。它是FRunnableThread
的子类,在Windows平台上使用,FRunnableThread
有一个类成员:/** The thread handle for the thread. */ HANDLE Thread = 0;
FRunnableThread
中有一个类成员为/** The runnable object to execute on this thread. */ FRunnable* Runnable;
线程的创建以及任务的的分配为
class FTestRunnable : public FRunnable {
public:
FTestRunnable();
virtual ~FTestRunnable() override;
protected:
TObjectPtr<FRunnableThread> RunnableThread;
};
// 传入自身供作为线程生命周期执行任务调用
FTestRunnable::FTestRunnable()
: RunnableThread(FRunnableThread::Create(this, TEXT("FTestRunnableThread"))) {}
FTestRunnable::~FTestRunnable() { delete RunnableThread; }
FRunnableThread::Create
这一步中包含了许多操作,例如
- 向操作系统申请线程,同时绑定申请成功的回调
- 回调中会将线程注册到全局
FThreadManager
中,然后调用FRunnable
中定义的各种虚方法
实际创建线程的代码如下所示,_ThreadProc
作为创建成功的回调函数
// virtual FRunnableThreadWin::CreateInternal
Thread = CreateThread(NULL, InStackSize, _ThreadProc, this,
STACK_SIZE_PARAM_IS_A_RESERVATION | CREATE_SUSPENDED, (::DWORD*)&ThreadID);
/**
* The thread entry point. Simply forwards the call on to the right
* thread main function
*/
static ::DWORD STDCALL _ThreadProc(LPVOID pThis)
{
check(pThis);
auto* ThisThread = (FRunnableThreadWin*)pThis;
// 添加到线程管理类中
FThreadManager::Get().AddThread(ThisThread->GetThreadID(), ThisThread);
return ThisThread->GuardedRun();
}
_ThreadProc
中会依次执行FRunnable
中的接口函数
uint32 FRunnableThreadWin::Run() {
uint32 ExitCode = 1;
if (Runnable->Init() == true) {
// 执行对应的工作
ExitCode = Runnable->Run();
Runnable->Exit();
// ...
}
return ExitCode;
}
Fake Thread
当系统不支持多线程时,UE将会在主线程上分时间片去Tick一个"Fake Thread"
FRunnableThread* FRunnableThread::Create(
class FRunnable* InRunnable,
const TCHAR* ThreadName,
uint32 InStackSize,
EThreadPriority InThreadPri,
uint64 InThreadAffinityMask,
EThreadCreateFlags InCreateFlags)
{
bool bCreateRealThread = FPlatformProcess::SupportsMultithreading();
FRunnableThread* NewThread = nullptr;
if (bCreateRealThread) {
// ...
}
else if (InRunnable->GetSingleThreadInterface()) {
// Create a fake thread when multithreading is disabled.
NewThread = new FFakeThread();
}
// Init... Request to OS to create new thread
return NewThread;
}
FFakeThread
与FRunableThreadWin
都继承自FRunableThread
,与FRunableThreadWin
不同的时,在调用FRunnableThread::Create
时,FFakeThread
不会向操作系统申请线程资源,而是将自己注册到FThreadManager
中,由FThreadManager
来调用自身的Tick
class FFakeThread : public FRunnableThread {
/** Runnable object associated with this thread. */
FSingleThreadRunnable* SingleThreadRunnable;
/** Tick one time per frame. */
virtual void Tick() override
{
if (SingleThreadRunnable && !bIsSuspended)
{
SingleThreadRunnable->Tick();
}
}
};
为了确保程序能正常在支持多线程和只支持单线程的机器上运行,通常会同时继承FRunnable
以及FSingleThreadRunnable
可以视情况只继承
FRunnable
;但若要使用FSingleThreadRunnable
,则必须和FRunnable
同时使用
class FUdpSocketSender : public FRunnable, private FSingleThreadRunnable
{
/**
* Gets single thread interface pointer used for ticking this runnable when multi-threading is disabled.
* If the interface is not implemented, this runnable will not be ticked when FPlatformProcess::SupportsMultithreading() is false.
*
* @return Pointer to the single thread interface or nullptr if not implemented.
*/
virtual FSingleThreadRunnable* GetSingleThreadInterface() override { return this; }
virtual uint32 Run() override
{
while (!Stopping)
{
// Loop...
}
return 0;
}
virtual void Tick() override
{
// Execute Per Tick
}
};
FThreadManager
全局单例
class CORE_API FThreadManager {
static FThreadManager& Get() {
static FThreadManager Singleton;
return Singleton;
}
};
FThreadManager
的Tick将在Gameplay主线程Tick之后调用,且只会执行FakeThread或ForkableThread
/** List of thread objects to be ticked. */
TMap<uint32, class FRunnableThread*, TInlineSetAllocator<256>> Threads;
void FThreadManager::Tick()
{
const bool bIsSingleThreadEnvironment = FPlatformProcess::SupportsMultithreading() == false;
if (bIsSingleThreadEnvironment)
{
FScopeLock ThreadsLock(&ThreadsCritical);
// Tick all registered fake threads.
for (TPair<uint32, FRunnableThread*>& ThreadPair : Threads)
{
// Only fake and forkable threads are ticked by the ThreadManager
if( ThreadPair.Value->GetThreadType() != FRunnableThread::ThreadType::Real )
{
ThreadPair.Value->Tick();
}
}
}
}
void FPreLoadScreenManager::GameLogicFrameTick() {
//We have to manually tick everything as we are looping the main thread here
FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread);
FTSTicker::GetCoreTicker().Tick(DeltaTime);
FThreadManager::Get().Tick();
// ...
}
Terminate FRunnableThread
在FRunnableThread
的执行逻辑中,会依次执行FRunnable::Init
,FRunnable::Run
,FRunnable::Exit
。对于FRunnable::Stop
一般情况下会在FRunnableThread
的析构函数中调用
FRunnable::Stop
的调用和线程被销毁没有必然联系,可以通过主动调用来执行任务的收尾操作,然后更改标志位让FRunnable::Run
中的while循环主动退出(此操作会导致线程对象被操作系统回收吗,是否涉及到TaskGraph)
~FRunnableThreadWin()
{
if (Thread)
{
Kill(true);
}
}
/**
* Tells the thread to exit. If the caller needs to know when the thread has exited, it should use the bShouldWait value.
* It's highly recommended not to kill the thread without waiting for it.
* Having a thread forcibly destroyed can cause leaks and deadlocks.
*
* The kill method is calling Stop() on the runnable to kill the thread gracefully.
*
* @param bShouldWait If true, the call will wait infinitely for the thread to exit.
* @return Always true
*/
virtual bool Kill(bool bShouldWait = false) override
{
check(Thread && "Did you forget to call Create()?");
bool bDidExitOK = true;
// Let the runnable have a chance to stop without brute force killing
if (Runnable)
{
Runnable->Stop();
}
if (bShouldWait == true)
{
// Wait indefinitely for the thread to finish. IMPORTANT: It's not safe to just go and
// kill the thread with TerminateThread() as it could have a mutex lock that's shared
// with a thread that's continuing to run, which would cause that other thread to
// dead-lock.
//
// This can manifest itself in code as simple as the synchronization
// object that is used by our logging output classes
// 通常情况是在Gameplay Thread中析构FRunnable对象 进而析构FRunnableThread对象
// 最后Gameplay Thread线程阻塞等待
WaitForSingleObject(Thread, INFINITE);
}
CloseHandle(Thread);
Thread = NULL;
// ...
return bDidExitOK;
}
Synchronization
FCriticalSection/FScopeLock
std::mutex
/std::lock_guard
/** * This is the Windows version of a critical section. It uses an aggregate * CRITICAL_SECTION to implement its locking. */
FRWLock/FRWScopeLock
class ThreadSafeArray
{
public:
int32 Get(int32 Index)
{
FRWScopeLock ScopeLock(ValuesLock, SLT_ReadOnly);
return Values[Index];
}
void Add(int32 Value)
{
FRWScopeLock ScopeLock(ValuesLock, SLT_Write);
Values.Add(Value);
}
private:
FRWLock ValuesLock;
TArray<int32> Values;
};
/** * FWindowsRWLock - Read/Write Mutex * - Provides non-recursive Read/Write (or shared-exclusive) access. * - Windows specific lock structures/calls Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa904937(v=vs.85).aspx */
FEvent
std::condition_variable
// FGenericPlatformProcess
UE_DEPRECATED(5.0, "Please use GetSynchEventFromPool to create a new event, and ReturnSynchEventToPool to release the event.")
static class FEvent* CreateSynchEvent(bool bIsManualReset = false);
FEvent
作为操作系统资源,一般从池子中获取,再归还到池子中
FEvent* FGenericPlatformProcess::GetSynchEventFromPool(bool bIsManualReset)
{
return bIsManualReset
? TLazySingleton<TEventPool<EEventMode::ManualReset>>::Get().GetEventFromPool()
: TLazySingleton<TEventPool<EEventMode::AutoReset>>::Get().GetEventFromPool();
}
void FGenericPlatformProcess::ReturnSynchEventToPool(FEvent* Event)
{
if( !Event )
{
return;
}
if (Event->IsManualReset())
{
TLazySingleton<TEventPool<EEventMode::ManualReset>>::Get().ReturnToPool(Event);
}
else
{
TLazySingleton<TEventPool<EEventMode::AutoReset>>::Get().ReturnToPool(Event);
}
}
// true means the event requires manual reseting
// reset means resets the event to an untriggered (waitable) state
FEvent* SyncEvent = FPlatformProcess::GetSynchEventFromPool(true);
// somewhere wait
SyncEvent->Wait();
// somewhere trigger
SyncEvent->Trigger();
FPlatformProcess::ReturnSynchEventToPool(SyncEvent);
由于FEvent
通常需要手动创建和手动释放,那么也因此有了RAII的做法
// 类似unique的FEvent 但non-copyable, non-movable
class CORE_API FEventRef final
{
public:
// 在构造和析构函数中申请和释放
explicit FEventRef(EEventMode Mode = EEventMode::AutoReset);
~FEventRef();
// Delete special member functions...
private:
FEvent* Event;
};
class CORE_API FSharedEventRef final
{
public:
explicit FSharedEventRef(EEventMode Mode = EEventMode::AutoReset) :
Ptr(TLazySingleton<TEventPool<EEventMode::AutoReset>>::Get().GetRawEvent(),
[](FEvent* Event) {
TLazySingleton<TEventPool<EEventMode::AutoReset>>::Get().ReturnRawEvent(Event);
}) {}
FEvent* operator->() const { return Ptr.Get(); }
private:
TSharedPtr<FEvent> Ptr;
};
TAtomic
std::atomic
LockFree Programing
AsyncTask
本质上还是使用FRunnableThread
以及FRunnable
,只是进行了一定程度的封装
Init ThreadPool
在初始化线程池的时候其实还分了是否处在编辑器中,以下代码是WITH_EDITOR 0
的情况
// FEngineLoop::PreInitPreStartupScreen
// 全局线程池指针
GThreadPool = FQueuedThreadPool::Allocate();
int32 NumThreadsInThreadPool = FPlatformMisc::NumberOfWorkerThreadsToSpawn();
// we are only going to give dedicated servers one pool thread
if (FPlatformProperties::IsServerOnly()) {
NumThreadsInThreadPool = 1;
}
verify(GThreadPool->Create(NumThreadsInThreadPool, StackSize, TPri_SlightlyBelowNormal, TEXT("ThreadPool")));
在GThreadPool->Create
中会根据线程池中创建的线程的数量,创建对应的FQueuedThread
以及FRunnableThread
由于FQueuedThread
携带了条件变量FEvent
,因此在线程被创建准备执行任务时,将会被阻塞,直至有任务被分配
uint32 FQueuedThread::Run()
{
while (!TimeToDie.Load(EMemoryOrder::Relaxed))
{
// We need to wait for shorter amount of time
bool bContinueWaiting = true;
if (bContinueWaiting)
{
DoWorkEvent->Wait();
}
IQueuedWork* LocalQueuedWork = QueuedWork;
QueuedWork = nullptr;
FPlatformMisc::MemoryBarrier();
// well you woke me up, where is the job or termination request?
check(LocalQueuedWork || TimeToDie.Load(EMemoryOrder::Relaxed));
while (LocalQueuedWork)
{
// Tell the object to do the work
LocalQueuedWork->DoThreadedWork();
// Let the object cleanup before we remove our ref to it
LocalQueuedWork = OwningThreadPool->ReturnToPoolOrGetNextJob(this);
}
}
return 0;
}
Add Task
// FAutoDeleteAsyncTask - template task for jobs that delete themselves when complete
// Sample code:
class ExampleAutoDeleteAsyncTask : public FNonAbandonableTask
{
friend class FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>;
int32 ExampleData;
ExampleAutoDeleteAsyncTask(int32 InExampleData) : ExampleData(InExampleData) {}
void DoWork() { /* ... do the work here */ }
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAutoDeleteAsyncTask, STATGROUP_ThreadPoolAsyncTasks);
}
};
void Example()
{
// start an example job
new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5)->StartBackgroundTask();
// do an example job now, on this thread
new FAutoDeleteAsyncTask<ExampleAutoDeleteAsyncTask>(5)->StartSynchronousTask();
}
// FAsyncTask - template task for jobs queued to thread pools
// Sample code:
class ExampleAsyncTask : public FNonAbandonableTask
{
friend class FAsyncTask<ExampleAsyncTask>;
int32 ExampleData;
ExampleAsyncTask(int32 InExampleData) : ExampleData(InExampleData) {}
void DoWork() { /* ... do the work here */ }
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAsyncTask, STATGROUP_ThreadPoolAsyncTasks);
}
};
void Example()
{
// start an example job
FAsyncTask<ExampleAsyncTask>* MyTask = new FAsyncTask<ExampleAsyncTask>(5);
MyTask->StartBackgroundTask();
// --or --
MyTask->StartSynchronousTask();
// to just do it now on this thread Check if the task is done
// 阻塞等待
if (MyTask->IsDone()) {}
// Spinning on IsDone is not acceptable( see EnsureCompletion ), but it is ok to check once a frame.
// Ensure the task is done, doing the task on the current thread if it has not been started, waiting until completion in all cases.
MyTask->EnsureCompletion();
delete Task;
}
FAutoDeleteAsyncTask
在执行任务流程后通过delete this
自动完成销毁操作;FAsyncTask
则需要手动销毁StartBackgroundTask
会调用QueuedPool->AddQueuedWork
来将任务注册到优先级任务队列中,以此来分配线程工作- 线程池中的
FQueuedThread
对象在完成当前分配的任务后,会调用OwningThreadPool->ReturnToPoolOrGetNextJob
来将任务从任务队列中移除,然后寻找是否有排队的任务需要完成
如果要自定义Task,需要完成以下几件事情
-
确保类中具有
void DoWork()
,TStatId GetStatId()
,bool CanAbandon()
,void Abandon()
四个成员函数。FAsyncTask<Task>
或FAutoDeleteAsyncTask<Task>
作为模板类,已经将Task
作为类成员并这四个函数进行了调用,如果不定义将会导致编译出错 -
将
FAsyncTask
或FAutoDeleteAsyncTask
设为友元类 -
可以继承
FNonAbandonableTask
,它为你预先定义好了两个函数,同时代表该Task是不可被抛弃的/** * Stub class to use a base class for tasks that cannot be abandoned */ class FNonAbandonableTask { public: bool CanAbandon() { return false; } void Abandon() {} };
TaskGraph
ENamedThreads
TaskGraph支持两种类型的线程,一种为AnyThread
,一种为NamedThread
。AnyThread
是TaskGraph创建出来的后台线程(BackgroundThread),会持续的从优先级任务队列中那任务出来执行,类似于线程池;NamedThread
例如GameThread
,RHIThread
需要在初始化的时候Attach到TaskGraph系统中。
namespace ENamedThreads
{
enum Type : int32
{
UnusedAnchor = -1,
StatsThread,
RHIThread, /* 对应FRHIThread 渲染硬件接口层线程 */
AudioThread,
GameThread, /* 程序主线程 */
ActualRenderingThread = GameThread + 1, /* FRenderingThread */
/** not actually a thread index. Means "Unknown Thread" or "Any Unnamed Thread" **/
AnyThread = 0xff,
}
}
在UE5中,
AudioThread
和StatsThread
已经被从TaskGraph中移除// StatsThread has been removed. Stats system should be used by Stats public API // AudioThread has been removed. Please use FAudioThread API
ESubsequentsMode
namespace ESubsequentsMode
{
enum Type
{
/** Necessary when another task will depend on this task. */
TrackSubsequents,
/** Can be used to save task graph overhead when firing off a task that will not be a dependency of other tasks. */
FireAndForget
};
}
CreateTask
自定义Task任务与上文中创建线程池任务颇为类似,同样需要手动加成成员函数依赖
class FMyTask
{
public:
// 使用自带的STATGROUP_TaskGraphTasks分组
FORCEINLINE TStatId GetStatId() const {
RETURN_QUICK_DECLARE_CYCLE_STAT(FMyTask, STATGROUP_TaskGraphTasks)
}
static ENamedThreads::Type GetDesiredThread() { return ENamedThreads::AnyThread; }
// 代表该任务不存在后续任务
static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::FireAndForget; }
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& CompletionGraphEvent)
{
const FString CurrentThreadName =
FThreadManager::GetThreadName(FPlatformTLS::GetCurrentThreadId());
// May Log Like This: CurrentThread: Foreground Worker #1
UE_LOG(LogTemp, Log, TEXT("CurrentThread: %s"), *CurrentThreadName);
}
};
调用接口函数创建出使用TRefCountPtr
智能指针包裹住的FGraphEvent
对象
// Somewhere Like Beginplay
FGraphEventRef NewTask = TGraphTask<FMyTask>::CreateTask().ConstructAndDispatchWhenReady();
/** Passthrough internal task constructor and dispatch. Note! Generally speaking references will not pass through; use pointers */
template<typename...T>
FGraphEventRef ConstructAndDispatchWhenReady(T&&... Args)
{
new ((void *)&Owner->TaskStorage) TTask(Forward<T>(Args)...);
// Launch Immediately
return Owner->Setup(Prerequisites, CurrentThreadIfKnown);
}
/** Passthrough internal task constructor and hold. */
template<typename...T>
TGraphTask* ConstructAndHold(T&&... Args)
{
new ((void *)&Owner->TaskStorage) TTask(Forward<T>(Args)...);
// 需要对返回值进行UnLock操作才能开始执行任务
return Owner->Hold(Prerequisites, CurrentThreadIfKnown);
}
当然也可以使用官方封装过的全局函数AsyncTask
,只需要传入函数对象
void AsyncTask(ENamedThreads::Type Thread, TUniqueFunction<void()> Function)
{
TGraphTask<FAsyncGraphTask>::CreateTask().ConstructAndDispatchWhenReady
(Thread, MoveTemp(Function));
}
Startup TaskGraph
在引擎的初始化流程中,会初始化全局单例FTaskGraphInterface
UE中单例的实现方式很多,有使用TLazySingleton<T>
的,有使用传统Meyers' Singleton的,也有使用单文件static指针的。这里TaskGraph使用的是单文件static指针
if (bCreateTaskGraphAndThreadPools)
{
// initialize task graph sub-system with potential multiple threads
SCOPED_BOOT_TIMING("FTaskGraphInterface::Startup");
// 将单例指针new出来
FTaskGraphInterface::Startup(FPlatformMisc::NumberOfWorkerThreadsToSpawn());
FTaskGraphInterface::Get().AttachToThread(ENamedThreads::GameThread);
}
UE调用FPlatformMisc::NumberOfWorkerThreadsToSpawn()
为线程池和TaskGraph计算需要创建的线程数量,在非DS的情况下,计算方式可以简化为
int32 FWindowsPlatformMisc::NumberOfWorkerThreadsToSpawn()
{
int32 NumberOfCores = FWindowsPlatformMisc::NumberOfCores();
int32 NumberOfCoresIncludingHyperthreads = FWindowsPlatformMisc::NumberOfCoresIncludingHyperthreads();
int32 NumberOfThreads = NumberOfCoresIncludingHyperthreads > NumberOfCores ?
NumberOfCoresIncludingHyperthreads - 2 : NumberOfCores - 1;
// need to spawn at least one worker thread (see FTaskGraphImplementation)
return FMath::Max(NumberOfThreads, 2);
}
UE5添加了一种轻量化的TaskGraph,为FTaskGraphCompatibilityImplementation
。UE5默认创建FTaskGraphCompatibilityImplementation
void FTaskGraphInterface::Startup(int32 NumThreads)
{
if (FParse::Param(FCommandLine::Get(), TEXT("TaskGraphForceOldBackend")))
{
GUseNewTaskBackend = 0;
}
else if (FParse::Param(FCommandLine::Get(), TEXT("TaskGraphForceNewBackend")))
{
GUseNewTaskBackend = 1;
}
if (GUseNewTaskBackend)
{
//We want to reduce the number of overall threads that UE uses so that there is are some
//free cores available for other things like the Browser or other Applications.
//Therefore we increase the number of Foreground workers, which are mostly unused.
//But when HighPrio work comes in the Foreground workers will be available and get the job done.
// ...
TaskGraphImplementationSingleton = new FTaskGraphCompatibilityImplementation(NumThreads);
}
else
{
// TaskGraphImplementationSingleton is actually set in the constructor because find work will be called before this returns.
new FTaskGraphImplementation(NumThreads);
}
}
如果创建的是FTaskGraphImplementation
,那么在TaskGraph创建的过程中就会将所有的NamedThread和AnyThread都创建出来,并赋值进WorkerThreads数组中
如果创建的是FTaskGraphCompatibilityImplementation
,那么在TaskGraph创建的时候,它首先会将AnyThread的创建交给FScheduler
去处理;且它并不负责创建实际的NamedThread实体,而是会留在引擎的后续初始化流程中,由各个对应的模块创建。也就是说TaskGraph本身并不持有指向各种线程的指针(真的是这样吗?Gameplay ActorTick的是在哪个部分添加的?物理线程的循环Tick是在哪里添加的?对于FTaskGraphCompatibilityImplementation
而言任务是如何分配到AnyThread的)
AActor::Tick
Delegate And Function Task
Other Task
UE Stats
UE的一种基于埋点的性能统计机制,通过Stat StartFile
和Stat StopFile
两个控制台指令来开始和结束录制性能分析文件
性能分析文件的保存目录为,Saved\Profiling\UnrealStats\,格式为uestats
在UE5中通过Tools\Session Frontend打开分析窗口
在控制台中启动监视的时候通常以组为单位,即使用
Stat GroupDesc
SCOPE_CYCLE_COUNTER
通过函数作用域实现开始和停止性能统计,一个基础的COUNTER有三部曲
// GroupDescription GroupID GroupCategory
DECLARE_STATS_GROUP(GroupDesc, GroupId, GroupCat)
DECLARE_CYCLE_STAT(CounterName,StatId,GroupId)
// StatID
SCOPE_CYCLE_COUNTER(Stat)
通常使用中需要定义一个组,定义一个埋点变量名,最后根据变量名创建埋点对象
// xx.h
// 通常使用STATCAT_Advanced
DECLARE_STATS_GROUP(TEXT("SharedMemory"), STATGROUP_SharedMemory, STATCAT_Advanced)
// xx.cpp
DECLARE_CYCLE_STAT(TEXT("SharedMemory_Tick"), STAT_SharedMemory_Tick, STATGROUP_SharedMemory)
// 可以为组声明更多的埋点...
void ASharedMemoryActor::Tick(float DeltaTime)
{
// 在当前作用域创建FScopeCycleCounter对象
SCOPE_CYCLE_COUNTER(STAT_SharedMemory_Tick)
// Do Something...
}
组宏的命名方式通常为
STATGROUP_XXX
,埋点变量名的命名方式通常为STAT_XXX
。但这并不是固定的
DECLARE_SCOPE_CYCLE_COUNTER
这个宏相当于上一个条目的一个整合,他将结构体的声明以及静态对象的创建安排在了作用域中,本质上是一样的
#define DECLARE_SCOPE_CYCLE_COUNTER(CounterName,Stat,GroupId) \
DECLARE_STAT(CounterName,Stat,GroupId,EStatDataType::ST_int64, EStatFlags::ClearEveryFrame | EStatFlags::CycleStat, FPlatformMemory::MCR_Invalid); \
static DEFINE_STAT(Stat) \
FScopeCycleCounter CycleCount_##Stat(GET_STATID(Stat), GET_STATFLAGS(Stat));
// 同样首先需要有一个组的定义
DECLARE_STATS_GROUP(GroupDesc, GroupId, GroupCat)
DECLARE_SCOPE_CYCLE_COUNTER(CounterName,Stat,GroupId)
使用的时候StatID会动态创建,即在首次调用Tick的时候创建
// xx.h
// 通常使用STATCAT_Advanced
DECLARE_STATS_GROUP(TEXT("SharedMemory"), STATGROUP_SharedMemory, STATCAT_Advanced)
void ASharedMemoryActor::Tick(float DeltaTime)
{
// 在当前作用域创建FScopeCycleCounter对象
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("SharedMemory_Tick"), STAT_SharedMemory_Tick, STATGROUP_SharedMemory)
// Do Something...
}
QUICK_SCOPE_CYCLE_COUNTER
很多时候都会把GroupID与GroupDescription,StatID与CounterName起成相同的名字,因此这个宏就是一层更简便的封装,它归属于STATGROUP_Quick
组
#define QUICK_SCOPE_CYCLE_COUNTER(Stat) \
DECLARE_SCOPE_CYCLE_COUNTER(TEXT(#Stat),Stat,STATGROUP_Quick)
衍生宏,常用于返回TStatId
时使用
#define RETURN_QUICK_DECLARE_CYCLE_STAT(StatId,GroupId) \
DECLARE_STAT(TEXT(#StatId),StatId,GroupId,EStatDataType::ST_int64, EStatFlags::ClearEveryFrame | EStatFlags::CycleStat, FPlatformMemory::MCR_Invalid); \
static DEFINE_STAT(StatId) \
return GET_STATID(StatId);
FScopeCycleCounterUObject
专门为UObject使用的埋点