系列文章目录
UE蓝图 Get节点和源码
UE蓝图 Set节点和源码
UE蓝图 Cast节点和源码
UE蓝图 分支(Branch)节点和源码
UE蓝图 入口(FunctionEntry)节点和源码
UE蓝图 返回结果(FunctionResult)节点和源码
UE蓝图 函数调用(CallFunction)节点和源码
UE蓝图 序列(Sequence)节点和源码
UE蓝图 宏(Macro)节点和源码
UE蓝图 编译过程详解
文章目录
- 系列文章目录
- 一、蓝图编译器
- 二、术语
- 三、编译过程
- 1、清理类
- 2、创建类属性
- 3、创建函数列表
- 3.1 处理事件图表
- 3.2 处理函数图表
- 3.3 预编译函数
- 4、绑定和链接类
- 5、编译函数
- 6、后编译函数
- 7、完成编译类
- 8、复制类默认对象属性
- 9、重新实例化
- 总结
一、蓝图编译器
蓝图编译器是一种用于编译UE蓝图的可视化脚本系统的编译器。 其主要作用是将蓝图资源的属性和图转换成类,以便在运行时使用。
当在蓝图编辑器中按下编译按钮时,编译器会将蓝图资源的属性和图转换成类。这个过程中,会生成一个新的实例,存储需要编译类的引用和蓝图等信息。同时,编译器还会处理函数信息的编译,包括相关图的引用、属性以及生成的UFunction。
在编译过程中,编译器会将节点转换成一系列的语句,然后编译器后端会将这些节点翻译成字节码操作。例如,变量赋值、无条件跳转(goto)和调用(call)等操作。
二、术语
FKismetCompilerContext
执行编译工作的类。系统为每次编译生成一个新实例。存储对正在编译的类、蓝图等的引用。
FKismetFunctionContext
保存用于编译单个函数的信息,例如对关联图表、属性和生成的UFunction的引用。
FNodeHandlingFunctor
一个辅助工具类,用于处理编译器中的一个节点类(单件)。包含用于注册引脚连接和生成编译语句的函数。
FKismetCompiledStatement
编译器中的工作单元。编译器将节点转换为一组已编译语句,后端将这些语句转换为字节码操作。
FKismetTerm
图中的终端(文字、常量或变量引用)。每个数据引脚连接都与其中一个终端关联!您还可以在"NodeHandlingFunctor"中为Scratch变量、中间结果等创建自己的术语。
三、编译过程
1、清理类
类是实时编译的,这意味着相同的 UBlueprintGeneratedClass 会被一次又一次地清理和重用, 因此指向类的指针不必固定。CleanAndSanitizeClass() 将属性和函数从类中移到临时包中的垃圾类中, 然后清除类中的任何数据。
2、创建类属性
编译器在蓝图的 新变量(NewVariables) 阵列以及其他一些地方(构造脚本等)上进行迭代, 以查找类所需的所有UProperty,然后在 函数 CreateClassVariablesFromBlueprint() 中创建UClass作用域上的UProperty。
3、创建函数列表
编译器通过处理事件图表,处理函数图表和_预编译_函数(即为每个上下文调用 PrecompileFunction()) 来为类创建函数列表。
3.1 处理事件图表
事件图表的处理由 CreateAndProcessUberGraph() 函数执行。此 函数将所有事件图表复制到一个大图表中,在此之后,节点将获得机会而展开。然后, 此函数为图表中的每个事件节点创建一个函数存根,并为每个事件图表创建一个 FKismetFunctionContext。
3.2 处理函数图表
常规函数图表的处理是通过ProcessOneFunctionGraph()函数来完成的,它会把图中的每一个节点拷贝到另外一个节点中去,这个时候调用expansionStep展开每个节点(expandNode),最后会为每一个函数创建一个FKismetFunctionContext,添加到functionList中。
3.3 预编译函数
函数的预编译由每个上下文的 PrecompileFunction() 处理。此函数执行 以下操作:
-
计划执行并计算数据依赖性。
-
删除任何计划外的或不是数据依赖项的节点。
-
在每个剩余节点上运行节点处理器的 RegisterNets()。
-
此操作将为函数内的值创建 FKismetTerms。
-
创建 UFunction 和关联属性。
4、绑定和链接类
现在编译器已经了解类的所有UProperty和UFunction,因此它可以绑定和链接该类, 这包括填充属性链、属性大小、函数图等。此时,从本质上看,它具有一个类标头 -减去最终的标记和元数据 - 以及一个类默认对象(CDO)。
5、编译函数
下一步是为剩余的节点生成 FKismetCompiledStatment 对象, 此操作使用 AppendStatementForNode() 通过节点处理器的 Compile() 函数完成。此 函数可以在编译函数中创建 FKismetTerm 对象,但前提是这些对象仅在本地使用。
6、后编译函数
PostCompileFunction()是编译函数的最后一个阶段,在所有函数调用了CompileFunction()之后调用,蓝图编译器会对Statements进行简单的优化,由FKismetFunctionContext::ResolveStatements()函数实现。该函数对linearExecutionList进行排序,链接goto,并合并相邻的语句。
- finalSortLinearExecList
按照可能的执行顺序再次对“线性执行列表”进行排序;列表中应该只包含执行节点。 - ResolveGotoFixups
链接goto,如果执行Flow Stack不是必需的,那么使用GotoReturn代替EndOfThread。 EndOfThread弹出Flow Stack,GotoReturn无需处理。 - mergeAdjacentStates
合并相邻的语句
7、完成编译类
为了完成编译类,编译器将确定类标记,并从父类传播标记和元数据, 最后执行一些最终检查,以确保编译过程中一切正常。
后端发出生成的代码
后端将每个函数上下文中的语句集合转换为代码。有两个后端 在使用:
FKismetCompilerVMBackend - 将FKCS转换为UnrealScript VM字节码,然后将其序列化为函数的脚本阵列。引擎运行时会读取字节码,并交由蓝图虚拟机动态解释执行。
FKismetCppBackend - 发出_类似C++_的代码,仅用于调试用途。自动生成的C++代码与我们自己写的有些差异,其行数会特别多,执行流程都用switch-case进行实现,因此执行效率会稍低些,而且可读性不友好。
8、复制类默认对象属性
编译器使用一个特殊的函数 CopyPropertiesForUnrelatedObjects() 将类的旧CDO中的值 复制到新CDO中。属性通过标记序列化复制, 因此只要名称一致,它们就应当会被正确地传输。在此阶段, CDO的组件将被重新实例化并进行适当的修复。操作时以GeneratedClass CDO为准。
9、重新实例化
由于类可能已经更改了大小,且属性可能已经过添加或删除, 因此编译器需要用刚编译的类重新实例化所有对象。这个过程使用 TObjectIterator 查找类的所有实例, 生成一个新实例,然后使用 CopyPropertiesForUnrelatedObjects() 函数 将旧实例复制到新实例。
总结
更详细信息请下载UE蓝图编译过程详解,基于UE4源码