脚本调试
Unigine调试程序让您可以在运行时检查UnigineScript。例如,可帮助您决定何时调用某种函数,何时调用某个值。此外逐步执行您脚本中的程序错误或逻辑问题,可以定位这些错误或问题。
使用debug builds.可获取Unigine脚本系统的额外信息。
引擎可能会造成两种类型的错误:编译期及运行期。
编译期错误#
错误消息#
发生编译期错误就意味着编译器无法编译并加载脚本。这种情况下,日志文件会出现包含下列信息的错误消息:
- 使用无效语句的源码字符串。
- 来自解译器对错误的描述消息。
花括号的错误配对是最常见的编译器错误。例如:
class Foo {
Foo(int a) {
this.a = a;
// 花括号结束符丢失
~Foo() {}
void print() {
log.message("a is %d\n",a);
}
};
Parser::check_braces(): some errors with count of '{' and '}' symbols
Parser::preprocessor(): wrong number of braces
具体的Unigine警告#
在众多明显的错误中例如不正确的语法,有一些不明显却更令人困惑的错误。最重要地是:
- 递归 includes不被追踪,因此在使用文件包时应当心,有怀疑时使用defines。
-
在用户类的定义中请确保在构造函数或方法中使用成员变量之前,这些成员变量得到声明。
变量a未被声明,因此会产生一个错误:
class Foo { Foo(int a) { this.a = a; } ~Foo() {} void print() { log.message("a is %d\n",a); } };
Interpreter::parse(): unknown "Foo" class member "a"
- 下列表达式及类似的复杂表达式会导致错误:
对于第一个表达式,会出现下列错误消息:
object.doSomething().doSomethingElse(); object.doSomething()[4];
对于第二个表达式,错误信息为:Parser::expectSymbol(): bad '.' symbol expecting end of string
关键在于因为动态输入方式,解译器不知道通过object.doSomething()将返回什么结果或者什么都不返回,这样可能会导致运行期间的错误。Parser::expectSymbol(): bad '[' symbol expecting end of string
运行期错误#
错误消息#
出现运行期错误通常意味着您试图操作一个已被损坏甚至不存在的对象。这种情况下,日志文件会出现包含下列内容的错误信息:
- 解译器对错误的描述。
- 函数调用的当前堆栈。
- 无效表达式的组装转储。
Interpreter::run(): "int: 0" is not an extern class
Call stack:
00: 0x0001a84c update()
01: 0x00016565 nodesUpdate()
02: 0x0001612c Nodes::update()
Disassemble:
0x00016134: callecf Node.getName
0x00016137: pop
0x00016138: pushv parameters_tb
0x0001613a: callecf WidgetTabBox.getCurrentTab
常见错误#
常见运行期错误:
- NULL objects.请记得初始化声明的对象并检查函数返回的值。
- 当为函数提供比需求更少(或更多)的参数时,堆栈下溢(或上溢)。
- 动态输入允许将某种类型的值分配给另一种类型的变量,例如:
然而人们似乎忘记他们为变量分配的内容并调用不合适的方法:
// 下列操作可行 Object object = new ObjectMesh("file.mesh"); object = new LightWorld(vec4_one);
这种情况下,错误信息是:// 下列操作不可行,因为对象不再具有ObjectMesh类型 Material m = object.getMaterial(0);
ExternClass::run_function(): can't find "class Object * __ptr64" base class in "class LightWorld * __ptr64" class
在创建矢量时,请确保留有足够的容量(非负)。如果指定地是负值,默认情况下将矢量尺寸设置为0。
当然在处理矢量内容时,请确保存在使用的索引。对于贴图及键也如此。但贴图的键不能为空。
这种情况下,错误信息为:int vector[2] = ( 1, 2, 3 ); // 定义3个元素的矢量 log.message("%d\n", vector[4]); // 处理不存在的第4个组件
UserArray::get(): bad array index 4
-
请确保使用具有合适对象的正确 swizzles。例如不能使用具有标量类型的swizzles。
此示例会生成下列错误信息:
int a = 10; // 标量类型值 log.message("%d\n", a.x); // swizzling
Variable::getSwizzle(): bad component 0 for int variable
- 如果某个用户类重载了一些运算符,记得在代码中保留运算对象的顺序:
此实例会产生一个错误:
class Foo { int f; Foo(int f = 0) { this.f = f; } int operator+(Foo arg1,int arg2) { return arg1.f + arg2; } }; //这样可行 int ret = new Foo() + 5; // 这样不可行 ret = 5 + new Foo();
Variable::add(): bad operands int and user class
- 请确保在类的方法中使用了wait控制结构,这种类的方法会作为静态方法得到调用。
错误信息为:
class Foo { void update() { while(true) wait 1; } }; Foo f = new Foo(); Foo::update(); // 此操作有效,因为此方法会被作为静态函数进行调用 f.update(); // 这样会造成冲突因为会传递一个类的实例
Interpreter::runFunction(): depth of stack is not zero
- 在使用class_cast()时应当心。请记住此函数会将用户类的实例在毫无警告的情况下转换成另一种类型,即使这些类之间毫无共同之处。
- 大量临时对象。这并不是一种错误,但如果在短期内创建了大量迅速成为未使用的用户类,由于垃圾回收器的原因,每播放32帧性能便会出现下降直到不存在未使用对象为止。这种情况下,大量的对象及昂贵的对象析构会造成性能下降。
调试程序#
通过Unigine调试程序您可以:
- 在脚本中直接设置断点
- 通过控制台设置并移除断点
- 查看内存堆栈
- 查看函数调用栈
- 查看当前变量值
- 通过指令步测
控制台调试程序窗口(Windows系统内):
设置断点#
要调用控制台调试程序,在当前使用的脚本中插入断点;指令。使用这种指令类型是为了精确地放置断点。可在脚本中插入一个以上的断点指令。
例如:
int a = 10;
breakpoint; // 断点指令
int b = 1;
forloop(int i = 0; a){
b += i;
log.message("Iteration: %d\n",i);
log.message("Value: %d\n",b);
}
如果遇到断点,引擎会停止执行动作,应用程序会停止对用户动作作出反应。相反地,外部控制台开始接收用户的输入。
在此控制台中,可使用下一个命令通过指令进行步测。当然也可在调试过程中通过break命令设置断点。 比如在调试循环时,断点就十分有用。或者在有必要的情况下,可以运行另一个调试程序命令。
如果控制台不可用,如在Windows内发布构建一样,这样看起来仿佛引擎意外停止一样。为避免这种情况出现,需使用在data/core/unigine.h文件中定义的“断点”宏指令。此指令可通过正确的方式将FPS 的值进行保存。
设置运行断点#
也可通过编辑器控制台使用指定数量的参数为每个函数设置运行编译器断点。由于所需脚本指令会被触发用于断点,因此引擎停止执行动作且外部控制台开始接收用户的输入。此外也可在调试程序内设置断点标记。
这样的断点共有3种类型:system_breakpoint, world_breakpoint 和 editor_breakpoint分别用于 系统,世界和编辑器脚本。
设置断点的语法如下: system_breakpoint/world_breakpoint/editor_breakpoint 设置/移除 function_name number_of_arguments。
例如,要在自定义函数上使用0参数设置printMyMessage(),需在编辑器控制台 中输入下列内容:
world_breakpoint set printMyMessage 0
特征#
命令#
调试程序支持下列所列出的数种控制台命令。
help#
所有有效命令列表。
短格式 | 长格式 |
---|---|
h | help |
run#
继续执行编译器直到遇到下一个断点或到达脚本的末端。
短格式 | 长格式 |
---|---|
r [N] | run [N] |
此处的N为可选参数,指定跳过的断点数。默认的N为 0。
next#
执行下一条指令。此指令被用来对从断点处开始的指令进行步测。
短格式 | 长格式 |
---|---|
n [N] | next [N] |
此处N 为可选参数,指定跳过的指令数。默认的N 为0。
例如对下列代码进行调试:
breakpoint; // 此处设置断点
int a = 10;
int b = 1;
int vector [3] = ( 1, 2, 3, 4);
forloop(int i = 0; a){
b += i;
log.message("Iteration: %d\n",i);
log.message("Value: %d\n",b);
}
Breakpoint at 0x00000455: setvc a = int: 10
# next
Breakpoint at 0x00000458: setvc b = int: 1
stack#
转储内存堆栈。
短格式 | 长格式 |
---|---|
s | stack |
calls#
转储函数调用堆栈。
短格式 | 长格式 |
---|---|
c | calls |
一些注意事项:
dasm#
将一定数量从当前指令开始的指令进行反汇编。
短格式 | 长格式 |
---|---|
d [N] | dasm [N] |
此处的N为可选参数,指定执行的指令数。默认的 N为8。
例如:
# dasm
Disassemble:
0x00000465: addvv b i
0x00000468: popv b
0x0000046a: pushv i
0x0000046c: pushc string: "Iteration: %d\n"
0x0000046e: callefr log.message
0x00000470: pushv b
0x00000472: pushc string: "Value: %d\n"
0x00000474: callefr log.message
也可参看: 汇编助记符。
info#
显示所给变量的内容。
短格式 | 长格式 |
---|---|
i var_list | info var_list |
此处的var_list为单独空间变量名的列表。如果变量并未在当前范围内,此变量名应包含一个命名空间前缀。
一些注意事项:
- 支持普通和数组变量
- 贴图值和矢量元素值可通过方括号([])内的常量键/指数而被访问。但仅支持整型, 浮点型及字符串型的键
- 可通过点号(.)访问用户类的成员。不支持对用户类成员的自动补齐。
用法示例:
# info a b vector
a: int: 3
b: int: 1
vector: Vector of 4 elements
0: int: 1
1: int: 2
2: int: 3
3: int: 4
# info vector[1]
vector[1]: int: 2
如果想检查哪一条指令更改了变量值,这时此命令就很有用。
也可参看:列表。
list#
fault#
冲突解译器。当引擎自身在调试程序中(例如gdb)得到运行时, 此选项就十分有用因为其允许查看C++函数调用的堆栈。
短格式 | 长格式 |
---|---|
f | fault |
break#
切换当前断点。在调试过程中可为每条脚本指令添加或移除断点。
短格式 | 长格式 |
---|---|
b | break |
例如调试下列代码:
int a = 10;
int b = 1;
forloop(int i = 0; a){
b += i;
log.message("Iteration: %d\n",i);
log.message("Value: %d\n",b);
}
Breakpoint at 0x00000455: setvc a = int: 10
# next
Breakpoint at 0x00000458: setvc b = int: 1
# break
Disassemble:
0x00000458: ! setvc b = int: 1
0x00000468: setvc i = int: 0
Breakpoint at 0x00000455: setvc a = int: 10
# next
Breakpoint at 0x00000458: setvc b = int: 1
# break
# break
汇编助记符#
下方为汇编助记符列表用于组装转储。
助记符 | 操作 |
---|---|
NOP | 无操作 |
设置操作:
助记符 | 操作 |
---|---|
SETX | 设置临时寄存器X |
SET - 通过……设置变量: 例如在组装转储中的SETVC助记符:
|
|
V | 堆栈 |
VV | 变量 |
VC | 常量 |
VEV | 外部变量 |
UAV | 用户数组变量 |
UAVV | 通过变量的用户数组变量 |
UAVC | 通过常量的用户数组变量 |
SET - 通过……设置用户数组变量 | |
UAVCV | 变量 |
UAVCC | 常量 |
Pop operations:
助记符 | 操作 |
---|---|
POP | Pop 堆栈 |
POP - Pop: 例如在组装转储中的POPV助记符:
|
|
Y | X |
Z | 临时寄存器Z |
V | 变量 |
VV | 通过变量的变量 |
VE | 变量元素 |
VS | 变量的混合 |
UAID | 用户数组ID |
UAV | 用户数组变量 |
UAVV | 通过变量的用户数组变量 |
UAVC | 通过常量的用户数组变量 |
UAVS | 用户数组变量混合 |
UAVE | 用户数组变量元素 |
UC | 用户类 |
UCX | Pop用户类及保存堆栈 |
UCR | Pop用户类及移除堆栈 |
推送操作:
助记符 | 操作 |
---|---|
PUSH - Push: 例如在组装转储中的PUSHC助记符:
|
|
X | 临时寄存器X |
Y | 临时寄存器 X |
Z | 临时寄存器 Z |
C | 常量 |
CC | 两个常量 |
V | 变量 |
VV | 通过变量的变量 |
VE | 变量元素 |
VS | 变量的混合 |
EV | 外部变量 |
EVE | 外部变量元素 |
EVS | 外部变量混合 |
UAID | 用户数组ID |
UAV | 用户数组变量 |
UAVV | 通过变量的用户数组变量 |
UAVC | 通过常量的用户数组变量 |
UAVS | 用户数组变量混合 |
UAVE | 用户数组变量元素 |
UC | 用户类 |
UCV | 用户类变量 |
UCVE | 用户类变量元素 |
UCVS | 用户类变量混合 |
UCAV | 用户类数组变量 |
UCC | 用户类当前对象 |
调用操作:
助记符 | 操作 |
---|---|
调用L | 调用地址 |
调用 - Call: 例如在组装转储中的CALLEFR 助记符:
|
|
F | 函数 |
FD | 函数动态 |
FT | 函数线程 |
EF | 外部函数 |
EFR | 外部堆栈移除函数 |
UAC | 用户数组构造函数 |
UAF | 用户数组函数 |
UAFR | 用户数组堆栈移除函数 |
UCC | 用户类构造函数 |
UCF | 用户类函数 |
UCFV | 用户类虚拟函数 |
UCFVC | 当前用户类虚拟函数 |
ECC | 外部类构造函数 |
ECF | 外部类函数地址 |
ECFR | 外部类堆栈移除函数 |
CCD | 类动态构造函数 |
CFD | 类动态函数 |
CD | 类析构函数 |
数学运算:
助记符 | 操作 |
---|---|
INV | 反选 |
ONE | 某个补码 |
NEG | 否定 |
|
|
INC | 增量 |
DEC | 衰减量 |
INCV | 变量的增量 |
DECV | 变量的减量 |
|
|
MUL | 乘法 |
DIV | 除法 |
MOD | 模数 |
ADD | 加法 |
SUB | 减法 |
MUL - 通过……相乘 例如在组装转储中的MULVC助记符:
例如在组装转储中的DIVVC 助记符:
ADD - 通过……相加 SUB - 通过……相减 |
|
V | 变量 |
C | 常量 |
VV | 通过变量的变量 |
VC | 通过常量的变量 |
CV | 通过变量的常量 |
|
|
SHL | 向左移 |
SHR | 向右移 |
SHL - 通过……向左移 SHR - 通过……向右移 例如在组装转储中的SHLC助记符:
|
|
V | 变量 |
C | 常量 |
|
|
BAND | 位和 |
BXOR | 位异 |
BOR | 位或 |
BAND - 通过……位和: BXOR - 通过……位异: BOR - 通过……位或: 例如在组装转储中的BORVV助记符:
|
|
V | 变量 |
C | 常量 |
VV | 通过变量的变量 |
VC | 通过常量的变量 |
|
|
EQ | 相等 |
NE | 不相等 |
LE | 小于等于 |
GE | 大于等于 |
LT | 小于 |
GT | 大于 |
EQ - 相等: NE - 不相等: LE - 小于等于: 例如在组装转储中的LEV助记符:
LT - 小于: GT - 大于: |
|
V | 变量 |
C | 常量 |
转移操作:
助记符 | 操作 |
---|---|
JMP | 跳转到地址 |
JZ | 如果为0则跳转 |
JNZ | 如果不为0则跳转 |
JEQ - 如果为0则跳转: JNE - 如果不为0则跳转: 例如在组装转储中的JNEVV助记符:
JGE - 如果大于或等于则跳转: JLT - 如果小于则跳转: JGT - 如果大于则跳转: |
|
V | 变量 |
C | 常量 |
VV | 通过变量的变量 |
VC | 通过常量的变量 |
|
|
GOTOD | 通过名称跳转到标签 |
YIELD | Yield返回语句 |
WAIT | Wait返回语句 |
RET | 返回语句 |
RETZ | 返回0 |
RETC | 返回常量 |
|
|
SWITCH | 切换 |
循环:
助记符 | 操作 |
---|---|
FORLOOP | For循环 |
FOREINIT | Foreach循环的初始化 |
FORESTEP | Foreach循环步骤 |
FOREKINIT | Foreachkey循环的初始化 |
FOREKSTEP | Foreachkey循环步骤 |