蓝图编译器概述(翻译)
发表于2016-08-25
本文依据官方文档翻译而来,注意官方版本的文档较旧(UE 4 4.9),新版本的类名已经改变。本文是直接翻译而来,并没有做相应改动。
蓝图,像常规的C++类一样,需要编译后才能在运行的时候使用。当你在蓝图编辑器中按下编译按钮时,那么编译器就会把蓝图资源的属性和图转换成类。
术语
FkismetCompilerContext
执行编译工作的类。每一次编译都会生成一个新的实例。存储需要编译类的引用,蓝图等。
FKismetFunctionContext
持有编译一个函数的信息,比如相关图的引用,属性以及生成的UFunction。
FNodeHandlingFunctor
一个用来处理一类节点的帮助类。包含用于注册引脚连接的函数,并且生成编译语句。
FKismetCompiledStatement
编译中的工作单元。编译器会把节点转换成一系列的语句,然后编译器后端会把这些节点翻译成字节码操作。
例子:变量赋值、无条件跳转(goto),调用(call)
FKismetTerm
图中的一个末端(字面量、常量、或者变量引用)。每一个数据引用接连都会跟它们中的一个连接。为了抓取变量或者中间值等你也可以在NodeHandlingFunctor中来创建你自己的项。
编译过程
编译蓝图的一个基本过程如下图所示:
*仅适用于完全编译
清理类
类是就地编译的,这意味着同一个UBlueprintGeneratedClass被清除并且会重复得使用,因此指向这个类的指针不必修正。CleanAndSanitizeClass()把属性和函数从类中移动到一个临时包中的垃圾类中,然后清除类中的所有数据。
创建类的属性
编译器遍历蓝图的NewVariables数组以及其它地方(比如构建脚本等)来找到类需要的所有属性,然后通过函数CreateClassVariablesFromBlueprint()在UClass内创建UProperties。
创建函数列表
编译器通过处理事件图表、常规函数图表来创建函数列表,并且预编译这些函数。例如为每一个上下文调用PrecompileFunction()函数。
处理事件图表
事件图表的处理通过调用CreateAndProcessUberGraph()函数而进行。它把所有的事件图表拷贝到一个大的图表中去,这之后节点会被展开。然后为图表中的每一个事件节点创建一个函数的存根(stub),并且第一个事件节点都会生成一个对应 的FKismetFunctionContext。
处理函数图表
处理常规函数图表的过程是通过调用ProcessOneFunctionGraph()函数来完成的,它会复制每一个图表到一个临时的图表里面,在这个图表里节点被会展开。每一个函数图表都会创建一个FKismetFunctionContext。
预编译函数
函数的预编译是通过调用每一个上下文中的PrecompileFunction()。这个函数会做以下几步的操作:
1、安排执行并且计算数据依赖。
2、去除那此没有使用的或者不是一个数据依赖的节点。
3、在每一个存在的节点上运行RegisterNets()节点处理器。
4、为函数中的值创建FKismetTerms。
5、创建UFunction和相关的属性。
绑定和链接类
现在编译器已经知道类的所有UFunction和UProperty了,现在它可以调用绑定和链接类了,这个过程包括创建属性链,设置属性大小,函数映射等。在这个时候,它实际上只有一个类头,不包含最终的标记和元数据,也没有一个类默认对象(CDO)。
编译函数
接下来需要为剩下的节点生成FKismetCompiledState对象,通过节点处理器的Compile()函数调用AppendStatementForNode()完成。这个函数会为当前编译的函数创建FKismetTerm对象只要它们只用来局部使用。
完成编译类
为了完成编译这个类,在通过一些最终检查来确保所有事情都做好之前,编译器会定下来类的标记并且从父类继承标记和元数据。
后端发出生成的代码
后端把每个函数上下文中的语句集合转换成代码。有两个可以使用的后端。
1、FKismetCompilerVMBackend 把FKCS转换成UnrealScript 虚拟机的字节码,然后就把这些字节码序列化到函数的脚本数组中。
2、FKismetCppBackend 产生用于调试的像C++的代码。
拷贝类默认对象属性
使用一个特别的函数CopyPropertiesForUnrelatedObjects(),编译器会把类原来的CDO中的值拷贝到新的CDO。属性通过标记的序列化拷贝,因此只要名字一致,它们应该会被正确的转移。CDO的组件会被重新实际化并且在这个阶段修正。这个生成类的CDO是权威的(authoritative)。
重新实际化
由于类可能会改变了大小 或者属性被添加或移除,编译器需要重新实例化刚编译的类的所有对应。这个过程使用TObjectIterator来找到这个类的所有实例,生成一个新的然后使用CopyPropertitiesForUnrelatedObjects()函数来把旧的实例拷贝到新的实例里面去。
要想了解更多的细节,查看FBlueprintCompileReinstancer类。
作者: 风恋残雪