文章目录

  • 一、蓝图编译器
  • 二、术语
  • 三、编译过程
  • 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变量、中间结果等创建自己的术语。

三、编译过程

unreal游戏引擎新建蓝图为蓝图添加游戏组件_UE

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() 函数 将旧实例复制到新实例。