疑问
GUID (全局唯一标识符) 这个概念,直接使用UE4编辑器的话应该是接触不到的,甚至说只写游戏逻辑代码的话大概也是不需要接触的。但如果需要的一些代码是对UE4原有的设计有冲击的话,可能会碰到需要处理GUID的情况。之前在项目中有遇到过需要处理的情况,当时并不理解GUID,而现在我想给予它更多的关注,并尝试解答下之前的疑问:
- 它是怎么生成的?这样生成真的能保证“全局唯一”吗?而这里的“全局”又指什么?一个项目?一台机器?
- 谁存了GUID?他们为何要存?
- 何时,又被谁触发生成新的GUID?
- 那么,GUID在UE4究竟有什么用?
1.生成
首先看下GUID的定义:
/**
* Implements a globally unique identifier.
*/
struct FGuid
{
public:
/** Holds the first component. */
uint32 A;
/** Holds the second component. */
uint32 B;
/** Holds the third component. */
uint32 C;
/** Holds the fourth component. */
uint32 D;
它由4个uint32
组成,这样算来一共有2^128
个值,这是个很大的值了。(IPv6也是这么多的数目,其数量号称可以为全世界的每一粒沙子编上一个地址)。
观察GUID生成函数:
/**Returns a new GUID.
* @return A new GUID.
*/
FGuid FGuid::NewGuid()
{
FGuid Result(0, 0, 0, 0);
FPlatformMisc::CreateGuid(Result);
return Result;
}
其中调用了FPlatformMisc::CreateGuid
,FPlatformMisc
是平台抽象层,在Windows上这个函数的实现是:
void FWindowsPlatformMisc::CreateGuid(FGuid& Result)
{
verify( CoCreateGuid( (GUID*)&Result )==S_OK );
}
它最终调用了CoCreateGuid函数,而这个函数并不是UE4的函数,是微软的函数。这么看来,UE4中的GUID的概念和一般GUID的概念是一致的,而网上大多数关于GUID的讨论也都适用于UE4中的GUID。
这样,我的第一个问题有了答案:
GUID是全局唯一的,它有CoCreateGuid函数保证。而这里的“全局”,不单单指一个项目,一台机器这样小的范围,我想在我有生之年,任何机器上,任何项目上都不可能创造出两个完全一样的GUID。
2.谁存了GUID变量
以"UPROPERTY()\n FGuid"
为关键字,在\Engine\Source\Runtime
范围内进行搜索,可以得到大量的结果:
图中显示搜索到104个,实际上应该会更多(出于格式原因或者没有UPROPERTY宏的原因)。
也就是说,它的使用是非常的通用的,而且在各个方面都有出现。
例如:ULevel
中的:
/** Identifies map build data specific to this level, eg lighting volume samples. */
UPROPERTY()
FGuid LevelBuildDataId;
和UMaterialInterface
中的:
/** Unique ID for this material, used for caching during distributed lighting */
UPROPERTY()
FGuid LightingGuid;
从他们两个的注释看起来和Build关照有关。
而例如FGraphReference
中的:
// The graph GUID so we can refind it if it has been renamed
UPROPERTY()
FGuid GraphGuid;
显然和Build光照没关系,看注释是说想要在重命名后能再次找到。
目前我对代码的研究还比较少,上面GUID的意义我还并不能有更深入的分析或实践。
不过,有一处GUID的使用我有过实践,是ALandscapeProxy
的:
/** Guid for LandscapeEditorInfo **/
UPROPERTY()
FGuid LandscapeGuid;
ALandscapeStreamingProxy
(继承自ALandscapeProxy
)代表了一个完整的大地形被分割成的易于被单个Level动态加载的“小地形”。一个“小地形”可以被单独加载出来,但是当他和它相邻的被分割的“小地形”一起被编辑器加载出来时,笔刷期望对他们一起编辑(即笔触应该能从一个小地形划过到另一个小地形中)。这时,LandscapeGuid
就用上了:被同一个大地形分割出来的“小地形”,都拥有和大地形相同的GUID,这样编辑器通过看LandscapeGuid
就可以知道要把哪几个“小地形”放在一起编辑了。(详情见LandscapeEditor
模块)
3.生成新的GUID的时机
我加了一个断点在FGuid::NewGuid()
中:
期待能明白我做哪些操作能生成新GUID。可结果是——生成得太频繁了,一个操作甚至会导致数个GUID的生成。于是,我将断点设置成输出调用者的名字,期待观察一个操作后都会导致多少个GUID生成:
之后,我得到了如下的结果:
光是启动编辑器,这个断点就触发了2000+次
创建新材质时,触发了1次:
GUID生成了,调用者是[内联框架] UMaterialInterface::SetLightingGuid
然后保存它时,触发了两次:
GUID生成了,调用者是TextNamespaceUtilImpl::FindOrAddPackageNamespace
GUID生成了,调用者是UPackage::Save
创建新的关卡时,触发了6次
GUID生成了,调用者是UModel::Modify
GUID生成了,调用者是ULevel::PostInitProperties
GUID生成了,调用者是[内联框架] UMaterialInterface::SetLightingGuid
GUID生成了,调用者是UModel::Modify
GUID生成了,调用者是UBodySetup::PostInitProperties
GUID生成了,调用者是[内联框架] UMaterialInterface::SetLightingGuid
创建地形时,更是触发了600次左右,前几个大概是:
GUID生成了,调用者是FTransaction::FTransaction
GUID生成了,调用者是FTransaction::BeginOperation
GUID生成了,调用者是UModel::Modify
GUID生成了,调用者是FLandscapeEditorDetailCustomization_NewLandscape::OnCreateButtonClicked
GUID生成了,调用者是ULandscapeComponent::PostInitProperties
GUID生成了,调用者是ULandscapeComponent::PostInitProperties
。。。
而且首次执行这些操作时,也触发了很多次。
我想,面对如此多的数目,试图做出“在XXX情况下生成GUID”的结论是很困难的。
4.暂时的结论
通过对UE4的GUID的生成函数的观察让我明白,UE4的GUID其实和普通的网上讨论的GUID并无本质区别,因此关于他们的生成的具体算法,还有“何时会重复”这种问题,网上都有很多讨论可以参阅。
而对UE4中GUID的使用情况的观察,让我明白,GUID在UE4中并不是专门服务于“某个问题”,而是服务于“多个问题,而且他们各自并没关联”。因此与其从GUID出发去问“GUID在UE4有什么用?”不如从UE4中具体的问题出发去问 “UE4中的某个问题中GUID发挥了怎样的作用” 更有意义。