一个有效的分配ID的方法:
- 首先声明一个空的结构体,然后继承自一个类似于
FAIBasicCounter
的模板结构体,这个结构体可能需要满足的一些条件:
- 实现累加功能:
Type GetNextAvailableID() { return NextAvailableID ++;}
- 上限判定,达到上限的判定方法
- 强制分配一个ID的实现函数
- 存储该ID的变量
- 如果不想支持泛型,可以按照上面的规则定义一个结构体来使用,接着要做的就是在需要的类里加上一个该结构体的成员变量和一个静态ID变量,用来标记当前新分配的ID,以后分配的ID都可以通过该ID进行累加
- 在
FAISightQuery
里FPerceptionListenerID
和FAISenseID
就采用了这种方式FPerceptionListenerID
里的ID生成思路(FAISenseID
同理)template<typename TCounterType> struct FAIBasicCounter { typedef TCounterType Type; protected: Type NextAvailableID; public: FAIBasicCounter() : NextAvailableID(Type(0)) {} Type GetNextAvailableID() { return NextAvailableID++; } uint32 GetSize() const { return uint32(NextAvailableID); } void OnIndexForced(Type ForcedIndex) { NextAvailableID = FMath::Max<Type>(ForcedIndex + 1, NextAvailableID); } };
NextAvailableID
标识下一个要生成的有效ID值
GetNextAvailableID
函数ID生成累加函数每次递增一位
OnIndexForced
函数强制附加给一个当前的索引ID,这个ID必须保证要大于已经标识的NextAvailableID
,否则会使用原先的标识变量struct AIMODULE_API FPerceptionListenerCounter : FAIBasicCounter<uint32>{}; typedef FAIGenericID<FPerceptionListenerCounter> FPerceptionListenerID;
FAIGenericID
是一个模板类,里面维护的核心成员变量分别是static AIMoudle_API TCounter Counter
它标识了生成的数量;const typename TCounter::Type Index
当前使用的ID,这是个常量
标识当前实例化的ID
FAIGenericID
里的GetNextID
函数使用了FAIBasicCounter
里的GetNextAvailableID
函数而FAIGenericID
是靠Counter
这个静态成员变量累加生成的
const FPerceptionListenerID NewListenerId = FPerceptionListenerID::GetNextID();
这是在感知系统UpdateListener
里调用写法,意思是如果当前传入的Listener是一个新数据,则生成一个新的ListenID存储到感知系统的Listener数据结构里
FAISenseID
与FPerceptionListenerID
的ID分配区别在哪?
本质上都是一样的,唯一不一致的是用法上所表达的逻辑含义不同,前者是针对Sense的类型数量分配索引,而后者是针对感知组件的持有者本身一个函数指针的有效写法
const TFunction<void(const FAISightQuery&)>& OnRemoveFunc auto RemoveQuery = [&ListenerId, &OnRemoveFunc](TArray<FAISightQuery&> SightQueries, const int32 QueryIndex)->EReverseForEachResult { ... OnRemoveFunc(SightQuery); ... }
UAISense_Sight::RemoveAllQueriesByListener
这个函数嵌套调用了多层函数指针,首先把传入的OnRemoveFunc
作为捕捉传入RemoveQuery
Lambda函数,然后在该函数里被调用,而RemoveQuery
在模板函数ReverseForEach
里,而ReverseForEach
里对RemoveQuery
函数进行了循环调用,整体而言不太适合高频使用类似写法,因为可读性不太好。但在有些比较基础的通用功能里,显得特别秀,比如TArray
里就使用了类似的函数指针调用发现一个声明
FRotator
并赋值的有效方法
FRotator ViewRotation(ForceInitToZero)
同理FVector支持此操作ForceInitToZero
是一个枚举定义成员另一个是ForceInitToZero
创建一个具有先决条件的委托任务
源码部分
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady( FSimpleDelegateGraphTask::FDelegate::CreateUObject( const_cast<UAIPerceptionComponent* (this),&UAIPerceptionComponent::RemoveDeadData), >>>GET_STATID(STAT_FSimpleDelegateGraphTask_RequestingRemovalOfDeadPerceptionData), NULL, ENamedThreads::GameThread);
创建一个委托在发现感知数据中有无效数据的时候及时删除无效的感知数据,该委托在游戏线程中调用
该委托使用了Stat机制做性能统计分析,具体的实现如下:DECLARE_CYCLE_STAT(TEXT("Requesting UAIPerceptionComponent::RemoveDeadData call from within a const function"), STAT_FSimpleDelegateGraphTask_RequestingRemovalOfDeadPerceptionData, STATGROUP_TaskGraphTasks);
声明一个Stat,从属于
STATGROUP_TaskGraphTasks
group分组,通过GET_STATID
宏可以获取STAT_FSimpleDelegateGraphTask_RequestingRemovalOfDeadPerceptionData
标识的StatId#define DECLARE_CYCLE_STAT(CounterName,StatId,GroupId) \ DECLARE_STAT(CounterName,StatId,GroupId,EStatDataType::ST_int64, EStatFlags::ClearEveryFrame | EStatFlags::CycleStat, >>>FPlatformMemory::MCR_Invalid); \ static DEFINE_STAT(StatId)
DECLARE_STAT是一个结构声明宏,里面包含了StatName,StatType,IsClearEveryFrame等的获取函数
第一个传入的参数CounterName
会在宏声明的GetDescription
函数中作为描述文本返回
第二个传入的参数StatId
在GetStatName中作为字符串返回,也在构建结构中作为尾缀名使用
GroupId
是一个全局唯一标识的ID
后几个参数分别用于设置Stat的数据类型,拥有的状态标签,分析内存区域等DECLARE_STATS_GROUP(TEXT("AI"),STATGROUP_AI, STATCAT_Advanced)
声明在Stats2.h头文件里,源码注释中说并不是所有的这些GroupID的必须定义在这个文件,可以根据需要定义在自己的Cpp文件里,但必须保证全局唯一
推一篇更详细的文章: Stat获取反射里的函数并调用
const UFunction* Function = Object.GetClass()->FindFunctionByName(FuncName); return Function != nullptr;
UE AIModule 源码解读之写法借鉴(一)
猜你喜欢
转载自blog.csdn.net/u011047958/article/details/125631046
今日推荐
周排行