虚幻4蓝图编译剖析(三)

编译

上面基本的术语已经介绍完了,下面我们进入来进入蓝图编译过程分析。蓝图的编译过程都在FKismetCompilerContext::Compile()函数中。它根据编译的类型不同(上文编译选项中提到的只编译Skeleton、只生成字节码、只生成cpp代码等等)会走不同的分支。我们这里以完全编译来讲解。此处为大概的流程,若想看详细的流程,请参照流程图以及代码。

清除类

类是就地编译的,这意味着同一个UBlueprintGeneratedClass在每次编译的时候都会被清理,并且会重复利用,这样指向这个类的指针就不用修复了。CleanAndSanitizeClass()把属性和函数放到临时包的一个垃圾(trash)类里面,然后清除类中的所有数据。

创建类的属性

CreateClassVariablesFromBlueprint()函数遍历蓝图的NewVariables数组,也包括构造脚本、Timeline等地方来找到该类需要的所有属性,然后创建UProperties。

创建函数列表

通过处理事件图表(Event graph)、常规的函数图表、代理、以及接口中来创建函数。

处理事件图表

CreateAndProcessUberGraph()函数用来处理事件图表。它把所有的事件图表拷贝到一个大的图中,这个时候节点有机会去做展开操作(expand,如果它需要的话)。然后会为图中的每一个事件节点创建一个函数桩(stub),最终会为当前的事件图表创建一个FKismetFunctionContext,用于把整个事件图表当作一个函数来处理。

处理函数图表

常规函数图表的处理是通过ProcessOneFunctionGraph()函数来完成的,它会把图中的每一个节点拷贝到另外一个节点中去,这个时候每个节点有机会展开(expand),最后会为每一个函数创建一个FKismetFunctionContext,用来后面对该函数的编译工作。

预编译函数

函数的预编译是通过为每一个FKismetFunctionContext调用PrecompileFunction()来实现的,这个函数主要做以下操作:

  1. 确定执行顺序并计算数据依赖。
  2. 去除那些没有连接的或者无数据依赖的节点。
  3. 为每一个剩下的运行每一个节点处理器(FNodeHandlingFunctor)的RegisterNets()函数,它会为函数中的值创建FBPTerminal。
  4. 创建UFunction对象和相关的属性。

绑定和链接类

现在类已经拥有了UFunctions和UProperties,现在可以绑定和链接类了,它包含填充属性链,属性大小 以及函数的列表。这个时候它相关于有了一个类头文件,不包括最终的标记、元数据以及CDO对象。

编译函数

接下来就需要通过每一个节点处理器(FNodeHanlingFunctor)的Compile()函数使用AppendStatementForNode()函数来为该节点添加FKismetBlueprintStatement。这个函数也可能会创建FBPTerminal对象只要它们只是局部使用的。

后编译函数

PostCompileFunction()是编译函数的最后一个阶段,在所有函数调用了CompileFunction()之后调用,主要是修复交叉引用。

完成编译类

为了完成编译该类,编译器会最终设置类标记,并从父类继承标记和元数据,最后会确定所有的事情在编译过程中是正确的。

后端生成代码

编译器后端会把函数中的所有语句转换成代码,目前有两个使用的后端:

  1. FKismetCompilerVMBackend 把FKismetCompilerStatement转成字节码,并把它序列化到脚本数组中。

  1. FKismetCppBackend 生成C++代码,只用于调试目的。

字节码

字节码的定义在Script.h中的EExprToken枚举中,定义如下,可以看到它有一些比较通用的指令,如EX_Jump EX_JumpIfNot等,也有一些专用的指令比如EX_DynamicCast EX_SetArray。

    

  1 //
  2
  3 // Evaluatable expression item types.
  4
  5 //
  6
  7 enum EExprToken
  8
  9 {
 10
 11     // Variable references.
 12
 13     EX_LocalVariable        = 0x00,    // A local variable.
 14
 15     EX_InstanceVariable        = 0x01,    // An object variable.
 16
 17     EX_DefaultVariable        = 0x02, // Default variable for a class context.
 18
 19     //                        = 0x03,
 20
 21     EX_Return                = 0x04,    // Return from function.
 22
 23     //                        = 0x05,
 24
 25     EX_Jump                    = 0x06,    // Goto a local address in code.
 26
 27     EX_JumpIfNot            = 0x07,    // Goto if not expression.
 28
 29     //                        = 0x08,
 30
 31     EX_Assert                = 0x09,    // Assertion.
 32
 33     //                        = 0x0A,
 34
 35     EX_Nothing                = 0x0B,    // No operation.
 36
 37     //                        = 0x0C,
 38
 39     //                        = 0x0D,
 40
 41     //                        = 0x0E,
 42
 43     EX_Let                    = 0x0F,    // Assign an arbitrary size value to a variable.
 44
 45     //                        = 0x10,
 46
 47     //                        = 0x11,
 48
 49     EX_ClassContext            = 0x12,    // Class default object context.
 50
 51     EX_MetaCast = 0x13, // Metaclass cast.
 52
 53     EX_LetBool                = 0x14, // Let boolean variable.
 54
 55     EX_EndParmValue            = 0x15,    // end of default value for optional function parameter
 56
 57     EX_EndFunctionParms        = 0x16,    // End of function call parameters.
 58
 59     EX_Self                    = 0x17,    // Self object.
 60
 61     EX_Skip                    = 0x18,    // Skippable expression.
 62
 63     EX_Context                = 0x19,    // Call a function through an object context.
 64
 65     EX_Context_FailSilent    = 0x1A, // Call a function through an object context (can fail silently if the context is NULL; only generated for functions that don‘t have output or return values).
 66
 67     EX_VirtualFunction        = 0x1B,    // A function call with parameters.
 68
 69     EX_FinalFunction        = 0x1C,    // A prebound function call with parameters.
 70
 71     EX_IntConst                = 0x1D,    // Int constant.
 72
 73     EX_FloatConst            = 0x1E,    // Floating point constant.
 74
 75     EX_StringConst            = 0x1F,    // String constant.
 76
 77     EX_ObjectConst         = 0x20,    // An object constant.
 78
 79     EX_NameConst            = 0x21,    // A name constant.
 80
 81     EX_RotationConst        = 0x22,    // A rotation constant.
 82
 83     EX_VectorConst            = 0x23,    // A vector constant.
 84
 85     EX_ByteConst            = 0x24,    // A byte constant.
 86
 87     EX_IntZero                = 0x25,    // Zero.
 88
 89     EX_IntOne                = 0x26,    // One.
 90
 91     EX_True                    = 0x27,    // Bool True.
 92
 93     EX_False                = 0x28,    // Bool False.
 94
 95     EX_TextConst            = 0x29, // FText constant
 96
 97     EX_NoObject                = 0x2A,    // NoObject.
 98
 99     EX_TransformConst        = 0x2B, // A transform constant
100
101     EX_IntConstByte            = 0x2C,    // Int constant that requires 1 byte.
102
103     EX_NoInterface            = 0x2D, // A null interface (similar to EX_NoObject, but for interfaces)
104
105     EX_DynamicCast            = 0x2E,    // Safe dynamic class casting.
106
107     EX_StructConst            = 0x2F, // An arbitrary UStruct constant
108
109     EX_EndStructConst        = 0x30, // End of UStruct constant
110
111     EX_SetArray                = 0x31, // Set the value of arbitrary array
112
113     EX_EndArray                = 0x32,
114
115     //                        = 0x33,
116
117     EX_UnicodeStringConst = 0x34, // Unicode string constant.
118
119     EX_Int64Const            = 0x35,    // 64-bit integer constant.
120
121     EX_UInt64Const            = 0x36,    // 64-bit unsigned integer constant.
122
123     //                        = 0x37,
124
125     EX_PrimitiveCast        = 0x38,    // A casting operator for primitives which reads the type as the subsequent byte
126
127     //                        = 0x39,
128
129     //                        = 0x3A,
130
131     //                        = 0x3B,
132
133     //                        = 0x3C,
134
135     //                        = 0x3D,
136
137     //                        = 0x3E,
138
139     //                        = 0x3F,
140
141     //                        = 0x40,
142
143     //                        = 0x41,
144
145     EX_StructMemberContext    = 0x42, // Context expression to address a property within a struct
146
147     EX_LetMulticastDelegate    = 0x43, // Assignment to a multi-cast delegate
148
149     EX_LetDelegate            = 0x44, // Assignment to a delegate
150
151     //                        = 0x45,
152
153     //                        = 0x46, // CST_ObjectToInterface
154
155     //                        = 0x47, // CST_ObjectToBool
156
157     EX_LocalOutVariable        = 0x48, // local out (pass by reference) function parameter
158
159     //                        = 0x49, // CST_InterfaceToBool
160
161     EX_DeprecatedOp4A        = 0x4A,
162
163     EX_InstanceDelegate        = 0x4B,    // const reference to a delegate or normal function object
164
165     EX_PushExecutionFlow    = 0x4C, // push an address on to the execution flow stack for future execution when a EX_PopExecutionFlow is executed. Execution continues on normally and doesn‘t change to the pushed address.
166
167     EX_PopExecutionFlow        = 0x4D, // continue execution at the last address previously pushed onto the execution flow stack.
168
169     EX_ComputedJump            = 0x4E,    // Goto a local address in code, specified by an integer value.
170
171     EX_PopExecutionFlowIfNot = 0x4F, // continue execution at the last address previously pushed onto the execution flow stack, if the condition is not true.
172
173     EX_Breakpoint            = 0x50, // Breakpoint. Only observed in the editor, otherwise it behaves like EX_Nothing.
174
175     EX_InterfaceContext        = 0x51,    // Call a function through a native interface variable
176
177     EX_ObjToInterfaceCast = 0x52,    // Converting an object reference to native interface variable
178
179     EX_EndOfScript            = 0x53, // Last byte in script code
180
181     EX_CrossInterfaceCast    = 0x54, // Converting an interface variable reference to native interface variable
182
183     EX_InterfaceToObjCast = 0x55, // Converting an interface variable reference to an object
184
185     //                        = 0x56,
186
187     //                        = 0x57,
188
189     //                        = 0x58,
190
191     //                        = 0x59,
192
193     EX_WireTracepoint        = 0x5A, // Trace point. Only observed in the editor, otherwise it behaves like EX_Nothing.
194
195     EX_SkipOffsetConst        = 0x5B, // A CodeSizeSkipOffset constant
196
197     EX_AddMulticastDelegate = 0x5C, // Adds a delegate to a multicast delegate‘s targets
198
199     EX_ClearMulticastDelegate = 0x5D, // Clears all delegates in a multicast target
200
201     EX_Tracepoint            = 0x5E, // Trace point. Only observed in the editor, otherwise it behaves like EX_Nothing.
202
203     EX_LetObj                = 0x5F,    // assign to any object ref pointer
204
205     EX_LetWeakObjPtr        = 0x60, // assign to a weak object pointer
206
207     EX_BindDelegate            = 0x61, // bind object and name to delegate
208
209     EX_RemoveMulticastDelegate = 0x62, // Remove a delegate from a multicast delegate‘s targets
210
211     EX_CallMulticastDelegate = 0x63, // Call multicast delegate
212
213     EX_LetValueOnPersistentFrame = 0x64,
214
215     EX_ArrayConst            = 0x65,
216
217     EX_EndArrayConst        = 0x66,
218
219     EX_AssetConst            = 0x67,
220
221     EX_CallMath                = 0x68, // static pure function from on local call space
222
223     EX_SwitchValue            = 0x69,
224
225     EX_InstrumentationEvent    = 0x6A, // Instrumentation event
226
227     EX_ArrayGetByRef        = 0x6B,
228
229     EX_Max                    = 0x100,
230
231 };

拷贝类默认对象(CDO)属性

使用一个特殊的函数CopyPropertiesForUnrelatedObjects(),编译器把类旧CDO中的值拷贝到新的CDO中。属性是通过带标记的序列化来拷贝的,只要名字是一致的,它们就应该被正确的复制。CDO中的组件会重新实例化。

重新实例化

由于类可能已经改变大小或者属性已经添加或删除了,编译器需要重新实例化该类实例化的所有对象。它使用TOjbectIterator来找到该类的实例,创建一个新的,并且使用CopyPropertiesForUnrelatedObjects()函数来把旧实例的数据拷贝到新的实例。详细信息参照FBlueprintCompileReinstancer类。

编译实例学习

我在蓝图里面新建了一个继承自Actor的NewBlueprint,它有一个变量StringTest并且实现了一个BeginPlay 事件和一个FunctionTest()函数,这个函数有一个局部变量LocalStringTest。

下面分别是BeginPlay和FunctionTest的定义:

为了看到编译的结果,我们需要修改BaseEngine.ini中的设置把CompileDisplaysBinaryBackend设置为true,如果要显示生成的cpp文件,也可以把CompileDisplaysTextBackend设置为true。注意需要重新启动编辑器。点击编译后在OutputLog中得到的结果如下代码所示:

    

  1 BlueprintLog: New page: Compile NewBlueprint
  2
  3 LogK2Compiler: [function ExecuteUbergraph_NewBlueprint]:
  4
  5 Label_0x0:
  6
  7 $4E: Computed Jump, offset specified by expression:
  8
  9 $0: Local variable named EntryPoint
 10
 11 Label_0xA:
 12
 13 $5E: .. debug site ..
 14
 15 Label_0xB:
 16
 17 $5A: .. wire debug site ..
 18
 19 Label_0xC:
 20
 21 $5E: .. debug site ..
 22
 23 Label_0xD:
 24
 25 $1B: Virtual Function named FunctionTest
 26
 27 $0: Local variable named CallFunc_FunctionTest_OutNewString
 28
 29 $16: EX_EndFunctionParms
 30
 31 Label_0x24:
 32
 33 $5A: .. wire debug site ..
 34
 35 Label_0x25:
 36
 37 $5E: .. debug site ..
 38
 39 Label_0x26:
 40
 41 $19: Context
 42
 43 ObjectExpression:
 44
 45 $20: EX_ObjectConst (000000003022A100:KismetSystemLibrary /Script/Engine.Default__KismetSystemLibrary)
 46
 47 Skip Bytes: 0x3D
 48
 49 R-Value Property: (null)
 50
 51 ContextExpression:
 52
 53 $1C: Final Function (stack node KismetSystemLibrary::PrintString)
 54
 55 $17: EX_Self
 56
 57 $0: Local variable named CallFunc_FunctionTest_OutNewString
 58
 59 $28: EX_False
 60
 61 $27: EX_True
 62
 63 $2F: literal struct LinearColor (serialized size: 16)
 64
 65 $1E: literal float 0.000000
 66
 67 $1E: literal float 0.660000
 68
 69 $1E: literal float 1.000000
 70
 71 $1E: literal float 1.000000
 72
 73 $30: EX_EndStructConst
 74
 75 $1E: literal float 2.000000
 76
 77 $16: EX_EndFunctionParms
 78
 79 Label_0x79:
 80
 81 $5A: .. wire debug site ..
 82
 83 Label_0x7A:
 84
 85 $4: Return expression
 86
 87 $B: EX_Nothing
 88
 89 Label_0x7C:
 90
 91 $53: EX_EndOfScript
 92
 93 LogK2Compiler: [function ReceiveBeginPlay]:
 94
 95 Label_0x0:
 96
 97 $1B: Virtual Function named ExecuteUbergraph_NewBlueprint
 98
 99 $1D: literal int32 10
100
101 $16: EX_EndFunctionParms
102
103 Label_0x13:
104
105 $4: Return expression
106
107 $B: EX_Nothing
108
109 Label_0x15:
110
111 $53: EX_EndOfScript
112
113 LogK2Compiler: [function UserConstructionScript]:
114
115 Label_0x0:
116
117 $5E: .. debug site ..
118
119 Label_0x1:
120
121 $5A: .. wire debug site ..
122
123 Label_0x2:
124
125 $4: Return expression
126
127 $B: EX_Nothing
128
129 Label_0x4:
130
131 $53: EX_EndOfScript
132
133 LogK2Compiler: [function FunctionTest]:
134
135 Label_0x0:
136
137 $5E: .. debug site ..
138
139 Label_0x1:
140
141 $5A: .. wire debug site ..
142
143 Label_0x2:
144
145 $5E: .. debug site ..
146
147 Label_0x3:
148
149 $F: Let (Variable = Expression)
150
151 Variable:
152
153 $0: Local variable named LocalStringTest
154
155 Expression:
156
157 $1F: literal ansi string "Bluepirnt Test: "
158
159 Label_0x27:
160
161 $5A: .. wire debug site ..
162
163 Label_0x28:
164
165 $F: Let (Variable = Expression)
166
167 Variable:
168
169 $0: Local variable named CallFunc_Concat_StrStr_ReturnValue
170
171 Expression:
172
173 $19: Context
174
175 ObjectExpression:
176
177 $20: EX_ObjectConst (0000000030229B00:KismetStringLibrary /Script/Engine.Default__KismetStringLibrary)
178
179 Skip Bytes: 0x1C
180
181 R-Value Property: CallFunc_Concat_StrStr_ReturnValue
182
183 ContextExpression:
184
185 $1C: Final Function (stack node KismetStringLibrary::Concat_StrStr)
186
187 $0: Local variable named LocalStringTest
188
189 $1: Instance variable named StringTest
190
191 $16: EX_EndFunctionParms
192
193 Label_0x6C:
194
195 $5E: .. debug site ..
196
197 Label_0x6D:
198
199 $F: Let (Variable = Expression)
200
201 Variable:
202
203 $48: Local out variable named OutNewString
204
205 Expression:
206
207 $0: Local variable named CallFunc_Concat_StrStr_ReturnValue
208
209 Label_0x88:
210
211 $5A: .. wire debug site ..
212
213 Label_0x89:
214
215 $4: Return expression
216
217 $B: EX_Nothing
218
219 Label_0x8B:
220
221 $53: EX_EndOfScript

有个需要说明的地方就是,我们可以看到事件ReceiveBeginPlay,如上面我们所说,它具体并没有做什么事情,整个函数的指令被放到了function ExecuteUbergraph_NewBlueprint中,而它所做的事情是调用了 Virtual Function named ExecuteUbergraph_NewBlueprint 并传递了一个int32的值,这个值是在ExecuteUbergraph_NewBlueprint处的偏移值,而在ExecuteUbergraph_NewBlueprint一开始就根据传进来的偏移值进行了无条件jump跳转到相应的位置进行程序的执行。

总结

至此,我们我们对蓝图的编译过程有了一个基本的了解,粗略地讲解了蓝图是如何从我们编辑的结果最终编译的过程,接下来的文章我们将介绍虚幻4中蓝图虚拟机的实现,敬请期待。当然由于本人理解能力有限,里面难免有错误的地方,如读者发现还请指正。

参考文档

  1. https://docs.unrealengine.com/latest/INT/Engine/Blueprints/TechnicalGuide/Compiler/index.html
  2. http://www.cnblogs.com/ghl_carmack/p/5804737.html
  3. 脚本语言入门书籍 Game Scripting Mastery
  4. http://blog.csdn.net/tangl_99/article/details/5600
时间: 2024-10-04 23:37:11

虚幻4蓝图编译剖析(三)的相关文章

虚幻4蓝图编译剖析(二)

虚幻4编译相关术语和类图 虚幻引擎中的蓝图编译跟常规的程序编译多少是有一些不同的地方,但是基本原理是相通的.我们以普通的类蓝图为例,一个类中包含多个图,每个图中又可以包含一些子图.一个图会包含很多的节点(UEdGraphNode),每个节点可以包含若干引脚(UEdGraphPin)用来连接两个节点.节点又分为执行节点和纯节点(Pure node,上面没有执行引脚).还有一个模式类(UEdGraphSchema)用于验证语法是否正确等.类图如下所示: 图(UEdGraph) 虚幻4中许多其它的也是

虚幻4蓝图编译剖析(一)

前言 虚幻引擎中的 蓝图 - 可视化脚本系统 是一个完整的游戏脚本系统, 其理念是,在虚幻编辑器中,使用基于节点的界面创建游戏可玩性元素. 和其他一些常见的脚本语言一样,蓝图的用法也是通过定义在引擎中的面向对象的类或者对象. 在使用虚幻 4 的过程中,常常会遇到在蓝图中定义的对象,并且这类对象常常也会被直接称为"蓝图(Blueprint)".本文从代码的层面讲解虚幻引擎中蓝图的编译(包括编辑部分的代码,使用的版本是UE4 4.13.0),本文假设已经对蓝图已经有一个比较清楚的了解. 编

虚幻4蓝图虚拟机剖析

前言 这里,我们打算对虚幻4 中蓝图虚拟机的实现做一个大概的讲解,如果对其它的脚本语言的实现有比较清楚的认识,理解起来会容易很多,我们先会对相关术语进行一个简单的介绍,然后会对蓝图虚拟机的实现做一个讲解. 术语 编程语言一般分为编译语言和解释型语言. 编译型语言 程序在执行之前需要一个专门的编译过程,把程序编译成 为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了.程序执行效率高,依赖编译器,跨平台性差些.如C.C++.Delphi等. 解释性语言 编写的程序不进行预先编译,以文本

libevent源码深度剖析三

libevent源码深度剖析三 --libevent基本使用场景和事件流程张亮 1 前言 学习源代码该从哪里入手?我觉得从程序的基本使用场景和代码的整体处理流程入手是个不错的方法,至少从个人的经验上讲,用此方法分析libevent是比较有效的. 2 基本应用场景 基本应用场景也是使用libevnet的基本流程,下面来考虑一个最简单的场景,使用livevent设置定时器,应用程序只需要执行下面几个简单的步骤即可. 1)首先初始化libevent库,并保存返回的指针struct event_base

虚幻4蓝图跑酷游戏制作公开课

#虚幻4# 蓝图跑酷游戏制作公开课,持续更新中. 油管地址:http://t.cn/Rczx3Jv 国内地址:http://t.cn/Rczx3JP 本课程是免费课程,赞助随意,一杯咖啡钱即可,几杯也行,慢慢喝--

boost.asio源码剖析(三) ---- 流程分析

* 常见流程分析之一(Tcp异步连接) 我们用一个简单的demo分析Tcp异步连接的流程: 1 #include <iostream> 2 #include <boost/asio.hpp> 3 4 // 异步连接回调函数 5 void on_connect(boost::system::error_code ec) 6 { 7 if (ec) // 连接失败, 输出错误码 8 std::cout << "async connect error:"

简单程序的编译链接三种方法(编译多个源文件,静态链接库、动态链接库)

一个程序简单的程序如下: 1 hello.h #ifndef HELLO_H#define HELLO_H void hello(const char *name); #endif 2 hello.c #include <stdio.h>#include <stdlib.h> void hello(const char *name){ printf("hello %s\n",name);} 3 main.c #include <stdio.h>#in

虚幻4蓝图快速入门(三)

数学表达式节点 概述 要想创建一个数学表达式节点,请右击图表并从关联菜单中选择 Add Math Expression(添加数学表达式)... . 数学表达式节点就像一个合并的图表.它是一个独立的节点,您可以双击它来打开构成其功能的子图表. 最初,该名称/表达式是空的.任何时候,当您重命名该节点时,都将会解析新表达式并生成新的子图表. 变量 变量命名非常灵活,但是记住以下几点非常重要: 变量名称本身可以包含数字,但是不能以数字开头. 变量名称不能和隐藏的蓝图变量名称一样. 确保您正在使用正确的变

虚幻4蓝图快速入门(四)

蓝图跟C++交互 概述 蓝图可以继承C++类,从而使得程序员可以在代码中创建新的游戏性类,而关卡设计人员可以使用蓝图来继承该类并对其进行修改. 有很多种修饰符可以改变C++类和蓝图系统间交互方式,其中某些修饰符会在本示例中突出介绍. 可以通过查看以下内容来快速了解: 虚幻引擎快速入门视频教程第五章,见引用[1] 官方文档 类设置 在类设置的第一部分中,使用C++类向导创建一个名称为LightSwitchBoth 的类. LightSwitchBoth类中的大部分代码设置都和 仅使用C++的Lig