浅析Unreal Engine 3中的FName
2014-06-03 21:16
681 查看
近来新到一个使用Unreal Engine 3的项目,本着熟悉代码的目的,看了一些代码,简单记录一下。
本文主要分析Unreal Engine 3中对于字符串封装后的结构FName,内容主要包含以下3点:
1.FName的作用;
2.FName的具体实现;
3.FName的一些特殊处理
1.FName的作用
FName利用hash table来存贮字符串。
对FName对象进行比较时,实际上只需对两个整数进行比较。
查找操作类似于在std::map中根据主键(整数)查找对应的值。效率上有了很明显的改善。
2.FName的具体实现
FName中的关键数据结构
FName就是今天要分析的重点,这里是几个关键的变量及存储字符串的容器。
Index是Names数组中的索引,用于快速的查找字符串的字符部分,Number是字符串中的数字部分。
接着,来看FName的Init()函数,Init()函数会在FName的构造函数中调用,一般情况下,使用FName只需传入第一个参数,其他参数在FName的构造函数中填入默认值。FName的Init()函数传入了4个参数,InName字符串中的字符部分,InNumber字符串中的数字部分,FindType表示操作类型,bSplitName分割标示符。传入的字符串在InName中,分割后会将字符保留在InName内,而将数字部分保存在InNumber中。
FName::Init()函数主要做了以下的工作:
1)做一些必要的初始化工作,初始化hashtable,注册关键字
2)分割传入字符串中的字符和数字
3)根据传入的字符串(InName)计算得到hash值,在hash
table中查找hash值,如果不存在对应的值,会将该hash值加入hash table。(重点)
其中NameHash的结构可以参照下图
根据FindType的不同,Fname::Init()函数有两个作用,第一个作用是作为FName的初始化函数,第二个作用是作为FName的查找函数。
Init()中具体的查找步骤为通过计算字符串的hash值,在hash table中定位到到对应链表的头结点,接着遍历链表依次比较当前元素与传入的InName。
当Init中的FindName变量值为FNAME_Find时,当没有找到对应的字符串会将Index = NAME_None,找到对应的字符串会将Index置为hash table中的hash值。
当FindName为FName_Add时,没有找到对应的字符串就会将该字符串经过hash加入到hash table对应的位置。
当FindName为FName_Replace时,会将查找到的字符串替换为传入的字符串。
FName重载==运算符用于比较操作
对FName对象的比较有两种方式,第一种是通过重载关系运算符==,首先比较索引(这个索引是保存在Names数组中的索引值),第二种实现了一个compare函数,不同之处在于两个FName对象不等时compare函数的返回值将根据字母表的升序来返回小于0或者大于0。
INT FName::Compare(const FName& other) const
{
if (GetIndex() == Other.GetIndex())
{
return GetNumber() – Other.GetNumber();
}
else
…
}
可以看到比较两个FName对象时,先比较二者的Index,如果不等则退化为调用传统的字符比较函数(正常情况下经由FName的构造函数调用FName::Init()都会生成对应的Index)。
3.FName的一些特殊处理
在前面FName的定义中,看到除了定义NameHash数组之外,还定义了一个Names数组,这是为什么?两个数组在功能上有什么不同之处?
其原因在于hash table可以快速查找,却不能随机存取一个元素,当需要根据Index来获取一个FNameEntry对象时,在Names数组中通过下标直接存取效率会更高,这样做弥补了hash table无法随机存取的缺点。
在FName::Init()中,有提到最后一个参数bSplitName是分割标示符,而在实际的使用中,假设我们将其置为TRUE,传入“test1”,发现调用分割函数FName::SplitNameWithCheck()并没有成功。查看FName::SplitNameWithCheck()的代码后,发现分割函数只是针对类似“test_1”这样格式的字符串进行分割,经过与同事的交流得知,Unreal编辑生成的资源文件多是以这样格式命名(或者在内部处理时将对象的命名统一格式)以加快处理速度。
本文主要分析Unreal Engine 3中对于字符串封装后的结构FName,内容主要包含以下3点:
1.FName的作用;
2.FName的具体实现;
3.FName的一些特殊处理
1.FName的作用
FName利用hash table来存贮字符串。
对FName对象进行比较时,实际上只需对两个整数进行比较。
查找操作类似于在std::map中根据主键(整数)查找对应的值。效率上有了很明显的改善。
2.FName的具体实现
FName中的关键数据结构
struct FNameEntry { INT Index; FNameEntry* HashNext; }FNameEntry是一个全局的结构,后面会说到的存储结构中都是以FNameEntry为元素存储的。
class FName { INT Index; INT Number; static TArrayNoInit<FNameEntry*> Names; static FNameEntry* NameHash[65536]; }
FName就是今天要分析的重点,这里是几个关键的变量及存储字符串的容器。
Index是Names数组中的索引,用于快速的查找字符串的字符部分,Number是字符串中的数字部分。
接着,来看FName的Init()函数,Init()函数会在FName的构造函数中调用,一般情况下,使用FName只需传入第一个参数,其他参数在FName的构造函数中填入默认值。FName的Init()函数传入了4个参数,InName字符串中的字符部分,InNumber字符串中的数字部分,FindType表示操作类型,bSplitName分割标示符。传入的字符串在InName中,分割后会将字符保留在InName内,而将数字部分保存在InNumber中。
void FName::Init(const TCHAR* InName, INT InNumber, EFindName FindType, UBOOL bSplitName) { StaticInit(); if (InNumber == NAME_NO_NUMBER_INTERNAL && bSplitName == TRUE) { if (SplitNameWithCheck(InName,...)) {} } INT iHash; iHash = appStrhash( InName ) & ( ARRAY_COUNT(NameHash)-1 ); for (FNameEntry* Hash = NameHash[iHash]; Hash; Hash=Hash->HashNext) { if( Hash->IsEqual(InName)) { Index = Hash->GetIndex(); ... } } Index = Names.Add(); Names(Index) = NameHash[iHash] = AllocateNameEntry( NewName, Index, NameHash[iHash], bIsPureAnsi ); }
FName::Init()函数主要做了以下的工作:
1)做一些必要的初始化工作,初始化hashtable,注册关键字
2)分割传入字符串中的字符和数字
3)根据传入的字符串(InName)计算得到hash值,在hash
table中查找hash值,如果不存在对应的值,会将该hash值加入hash table。(重点)
其中NameHash的结构可以参照下图
根据FindType的不同,Fname::Init()函数有两个作用,第一个作用是作为FName的初始化函数,第二个作用是作为FName的查找函数。
Init()中具体的查找步骤为通过计算字符串的hash值,在hash table中定位到到对应链表的头结点,接着遍历链表依次比较当前元素与传入的InName。
Enum EFindName { FNAME_Find, FNAME_Add, FNAME_Replace }
当Init中的FindName变量值为FNAME_Find时,当没有找到对应的字符串会将Index = NAME_None,找到对应的字符串会将Index置为hash table中的hash值。
当FindName为FName_Add时,没有找到对应的字符串就会将该字符串经过hash加入到hash table对应的位置。
当FindName为FName_Replace时,会将查找到的字符串替换为传入的字符串。
FName重载==运算符用于比较操作
FORCEINLINE UBOOL operator==(const FNAME& other) const { return Index == Other.Index && Number == Other.Number; }
对FName对象的比较有两种方式,第一种是通过重载关系运算符==,首先比较索引(这个索引是保存在Names数组中的索引值),第二种实现了一个compare函数,不同之处在于两个FName对象不等时compare函数的返回值将根据字母表的升序来返回小于0或者大于0。
INT FName::Compare(const FName& other) const
{
if (GetIndex() == Other.GetIndex())
{
return GetNumber() – Other.GetNumber();
}
else
…
}
可以看到比较两个FName对象时,先比较二者的Index,如果不等则退化为调用传统的字符比较函数(正常情况下经由FName的构造函数调用FName::Init()都会生成对应的Index)。
3.FName的一些特殊处理
static TArrayNoInit<FNameEntry*> Names; static FNameEntry* NameHash[65536];
在前面FName的定义中,看到除了定义NameHash数组之外,还定义了一个Names数组,这是为什么?两个数组在功能上有什么不同之处?
其原因在于hash table可以快速查找,却不能随机存取一个元素,当需要根据Index来获取一个FNameEntry对象时,在Names数组中通过下标直接存取效率会更高,这样做弥补了hash table无法随机存取的缺点。
在FName::Init()中,有提到最后一个参数bSplitName是分割标示符,而在实际的使用中,假设我们将其置为TRUE,传入“test1”,发现调用分割函数FName::SplitNameWithCheck()并没有成功。查看FName::SplitNameWithCheck()的代码后,发现分割函数只是针对类似“test_1”这样格式的字符串进行分割,经过与同事的交流得知,Unreal编辑生成的资源文件多是以这样格式命名(或者在内部处理时将对象的命名统一格式)以加快处理速度。
相关文章推荐
- 游戏音频技术备忘 (五)Wwise Unreal Engine 集成代码浅析 二
- 游戏音频技术备忘 (五)Wwise Unreal Engine 集成代码浅析 二
- UnrealEngine4.8.3 插件开发入门篇
- 【UNREAL ENGINE 游戏开发】开篇之为什么现在就要抱紧UE的大腿!(另附学习资料/交流群)
- 【UNREAL ENGINE 游戏开发】开篇之UE4的BLUEPRINT(蓝图)与C++(新童鞋必看)
- Unreal Engine 4 —— 可交互绳索的构建
- Unreal Engine 3技术细节
- Unreal Engine 4 Radiant UI 入门教程(一)
- Unreal Engine 2X
- Unreal Engine 4 —— Smear Frame效果的实现与分析
- 游戏制作之路:游戏引擎选择、Mac下和Windows下UnrealEngine 4体验对比、文档及其他 -- 前几天我说要学做游戏的流程...
- Unreal Engine 4 常用功能引导
- Unreal Engine 4 —— 版本兼容的工作原理以及一些可优化项
- Unreal Engine 4 笔记 2
- Mono for Unreal Engine发布,C#进入虚幻引擎(Unreal Engine)
- Unreal Engine 4 C++使用注意事项
- Mac下和Windows下UnrealEngine 4体验对比
- Unreal Engine 4 C++ AI 粗略探究
- 虚幻引擎3(Unreal Engine 3)概要
- Memory stomp allocator for Unreal Engine 4.