UE AIModule 源码解读之写法借鉴(一)

2023-09-17 14:24
一个有效的分配ID的方法: 首先声明一个空的结构体,然后继承自一个类似于FAIBasicCounter的模板结构体,这个结构体可能需要满足的一些条件: 实现累加功能: Type GetNextAvailableID() { return NextAvailableID ++;}上限判定,达到上限的判定方法强制分配一个ID的实现函数存储该ID的变量 如果不想支持泛型,可以按照上面的规则定义一个结构体来使用,接着要做的就是在需要的类里加上一个该结构体的成员变量和一个静态ID变量,用来标记当前新分配的ID,以后分配的ID都可以通过该ID进行累加在FAISightQuery里FPerceptionListenerID和FAISenseID就采用了这种方式FPerceptionListenerID里的ID生成思路(FAISenseID同理) template 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(ForcedIndex + 1, NextAvailableID); } }; NextAvailableID标识下一个要生成的有效ID值 GetNextAvailableID函数ID生成累加函数每次递增一位 OnIndexForced函数强制附加给一个当前的索引ID,这个ID必须保证要大于已经标识的NextAvailableID,否则会使用原先的标识变量 struct AIMODULE_API FPerceptionListenerCounter : FAIBasicCounter{}; typedef FAIGenericID 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& OnRemoveFunc auto RemoveQuery = [&ListenerId, &OnRemoveFunc](TArray SightQueries, const int32 QueryIndex)->EReverseForEachResult {...OnRemoveFunc(SightQuery);... } UAISense_Sight::RemoveAllQueriesByListener这个函数嵌套调用了多层函数指针,首先把传入的OnRemoveFunc作为捕捉传入RemoveQueryLambda函数,然后在该函数里被调用,而RemoveQuery在模板函数ReverseForEach里,而ReverseForEach里对RemoveQuery函数进行了循环调用,整体而言不太适合高频使用类似写法,因为可读性不太好。但在有些比较基础的通用功能里,显得特别秀,比如TArray里就使用了类似的函数指针调用 发现一个声明FRotator并赋值的有效方法 FRotator ViewRotation(ForceInitToZero) 同理FVector支持此操作ForceInitToZero是一个枚举定义成员另一个是ForceInitToZero 创建一个具有先决条件的委托任务 源码部分 FSimpleDelegateGraphTask::CreateAndDispatchWhenReady( FSimpleDelegateGraphTask::FDelegate::CreateUObject( const_cast>>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;