UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Landscape Tool
Using Editor Tools for Specific Tasks
FAQ
编程
Fundamentals
Setting Up Development Environment
Usage Examples
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine and Tools
GUI
Double Precision Coordinates
应用程序接口
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
CIGI Client Plugin
Rendering-Related Classes

脚本调试

Unigine调试程序让您可以在运行时检查UnigineScript。例如,可帮助您决定何时调用某种函数,何时调用某个值。此外逐步执行您脚本中的程序错误或逻辑问题,可以定位这些错误或问题。

使用debug builds.可获取Unigine脚本系统的额外信息。

引擎可能会造成两种类型的错误:编译期及运行期。

编译期错误#

错误消息#

发生编译期错误就意味着编译器无法编译并加载脚本。这种情况下,日志文件会出现包含下列信息的错误消息:

  1. 使用无效语句的源码字符串。
  2. 来自解译器对错误的描述消息。

花括号的错误配对是最常见的编译器错误。例如:

源代码 (UnigineScript)
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
  • 用户类的定义中请确保在构造函数或方法中使用成员变量之前,这些成员变量得到声明。
    源代码 (UnigineScript)
    class Foo {		
    	Foo(int a) {
    		this.a = a;
    	}	
    	
    	~Foo() {}	
    	
    	void print() {
    		log.message("a is %d\n",a);
    	}
    };
    变量a未被声明,因此会产生一个错误:
    输出
    Interpreter::parse(): unknown "Foo" class member "a"
  • 下列表达式及类似的复杂表达式会导致错误:
    源代码 (UnigineScript)
    object.doSomething().doSomethingElse();
    object.doSomething()[4];
    对于第一个表达式,会出现下列错误消息:
    输出
    Parser::expectSymbol(): bad '.' symbol expecting end of string
    对于第二个表达式,错误信息为:
    输出
    Parser::expectSymbol(): bad '[' symbol expecting end of string
    关键在于因为动态输入方式,解译器不知道通过object.doSomething()将返回什么结果或者什么都不返回,这样可能会导致运行期间的错误。

运行期错误#

错误消息#

出现运行期错误通常意味着您试图操作一个已被损坏甚至不存在的对象。这种情况下,日志文件会出现包含下列内容的错误信息:

  1. 解译器对错误的描述。
  2. 函数调用的当前堆栈。
  3. 无效表达式的组装转储。
例如:
输出
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.请记得初始化声明的对象并检查函数返回的值。
  • 当为函数提供比需求更少(或更多)的参数时,堆栈下溢(或上溢)。
  • 动态输入允许将某种类型的值分配给另一种类型的变量,例如:
    源代码 (UnigineScript)
    // 下列操作可行
    Object object = new ObjectMesh("file.mesh");
    object = new LightWorld(vec4_one);
    然而人们似乎忘记他们为变量分配的内容并调用不合适的方法:
    源代码 (UnigineScript)
    // 下列操作不可行,因为对象不再具有ObjectMesh类型
    Material m = object.getMaterial(0);
    这种情况下,错误信息是:
    输出
    ExternClass::run_function(): can't find "class Object * __ptr64" base class in "class LightWorld * __ptr64" class
  • 在创建矢量时,请确保留有足够的容量(非负)。如果指定地是负值,默认情况下将矢量尺寸设置为0。

    当然在处理矢量内容时,请确保存在使用的索引。对于贴图及键也如此。但贴图的键不能为空。

    源代码 (UnigineScript)
    int vector[2] =	( 1, 2, 3 );	// 定义3个元素的矢量
    log.message("%d\n", vector[4]);	// 处理不存在的第4个组件
    这种情况下,错误信息为:
    输出
    UserArray::get(): bad array index 4
  • 请确保使用具有合适对象的正确 swizzles。例如不能使用具有标量类型的swizzles。
    源代码 (UnigineScript)
    int a = 10;	// 标量类型值
    log.message("%d\n", a.x); // swizzling
    此示例会生成下列错误信息:
    输出
    Variable::getSwizzle(): bad component 0 for int variable
  • 如果某个用户类重载了一些运算符,记得在代码中保留运算对象的顺序:
    源代码 (UnigineScript)
    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控制结构,这种类的方法会作为静态方法得到调用。
    源代码 (UnigineScript)
    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系统内):

设置断点#

要调用控制台调试程序,在当前使用的脚本中插入断点;指令。使用这种指令类型是为了精确地放置断点。可在脚本中插入一个以上的断点指令。

例如:

源代码 (UnigineScript)
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
在遇到断点时,便会调用控制台调试程序可运行调试程序命令
注意
最后一个参数number_of_arguments为可选选项。

特征#

调试程序也支持有限的自动补齐及历史命令。

有了调试程序,就不能

命令#

调试程序支持下列所列出的数种控制台命令。

help#

所有有效命令列表。

短格式 长格式
h help

run#

继续执行编译器直到遇到下一个断点或到达脚本的末端。

短格式 长格式
r [N] run [N]

此处的N为可选参数,指定跳过的断点数。默认的N0

next#

执行下一条指令。此指令被用来对从断点处开始的指令进行步测。

短格式 长格式
n [N] next [N]

此处N 为可选参数,指定跳过的指令数。默认的N0

例如对下列代码进行调试:

源代码 (UnigineScript)
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);
}
要执行断点后的下一条指令,需输入 next 命令:
输出
Breakpoint at 0x00000455: setvc a = int: 10
# next
Breakpoint at 0x00000458: setvc b = int: 1

stack#

转储内存堆栈。

短格式 长格式
s stack

calls#

转储函数调用堆栈。

短格式 长格式
c calls

一些注意事项:

  • 列出函数的调用,从C++函数调用开始,而C++函数未被包含在堆栈中。例如如果将某些脚本函数作为回调函数进行调用,将不会看见调用的C++函数。
  • 如果某个函数地址完整无损,则将显示此函数名否则将会看见乱码而不会看见名称。如果在函数的body部分有 yieldwait 指令,函数地址也会发生变化。

dasm#

将一定数量从当前指令开始的指令进行反汇编。

短格式 长格式
d [N] dasm [N]

此处的N为可选参数,指定执行的指令数。默认的 N8

例如:

输出
# 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#

显示当前作用域内的所有变量。

短格式 长格式
l list

也可参看: 信息

fault#

冲突解译器。当引擎自身在调试程序中(例如gdb)得到运行时, 此选项就十分有用因为其允许查看C++函数调用的堆栈。

短格式 长格式
f fault

break#

切换当前断点。在调试过程中可为每条脚本指令添加或移除断点。

短格式 长格式
b break

例如调试下列代码:

源代码 (UnigineScript)
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);
}
在调试过程中添加一个断点,在调试程序中输入bbreak
输出
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
要移除断点,再次输入breakb
输出
Breakpoint at 0x00000455: setvc a = int: 10
# next
Breakpoint at 0x00000458: setvc b = int: 1
# break
# break

注意
使用此命令,无法移除在脚本中设置的断点(breakpoint;指令) 。

汇编助记符#

下方为汇编助记符列表用于组装转储。

助记符 操作
NOP 无操作

设置操作:

助记符 操作
SETX 设置临时寄存器X
SET - 通过……设置变量:

例如在组装转储中的SETVC助记符:

输出
0x00000458: setvc b = int: 1
V 堆栈
VV 变量
VC 常量
VEV 外部变量
UAV 用户数组变量
UAVV 通过变量的用户数组变量
UAVC 通过常量的用户数组变量
SET - 通过……设置用户数组变量
UAVCV 变量
UAVCC 常量

Pop operations:

助记符 操作
POP Pop 堆栈
POP - Pop:

例如在组装转储中的POPV助记符:

输出
0x00000468: popv	b
Y X
Z 临时寄存器Z
V 变量
VV 通过变量的变量
VE 变量元素
VS 变量的混合
UAID 用户数组ID
UAV 用户数组变量
UAVV 通过变量的用户数组变量
UAVC 通过常量的用户数组变量
UAVS 用户数组变量混合
UAVE 用户数组变量元素
UC 用户类
UCX Pop用户类及保存堆栈
UCR Pop用户类及移除堆栈

推送操作:

助记符 操作
PUSH - Push:

例如在组装转储中的PUSHC助记符:

输出
0x0000046c: pushc	string: "Iteration: %d\n"
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 助记符:

输出
0x0000046e: callefr	log.message
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助记符:

输出
0x0000044c: mulvc	a int: 1
DIV - 通过……相除

例如在组装转储中的DIVVC 助记符:

输出
0x0000044c: mulvc	a int: 2
MOD - 通过……取模数
ADD - 通过……相加
SUB - 通过……相减
V 变量
C 常量
VV 通过变量的变量
VC 通过常量的变量
CV 通过变量的常量

SHL 向左移
SHR 向右移
SHL - 通过……向左移
SHR - 通过……向右移

例如在组装转储中的SHLC助记符:

输出
0x0000046b: shlc	int: 1
V 变量
C 常量

BAND 位和
BXOR 位异
BOR 位或
BAND - 通过……位和:
BXOR - 通过……位异:
BOR - 通过……位或:

例如在组装转储中的BORVV助记符:

输出
0x00000462: borvv	a b
V 变量
C 常量
VV 通过变量的变量
VC 通过常量的变量

EQ 相等
NE 不相等
LE 小于等于
GE 大于等于
LT 小于
GT 大于
EQ - 相等:
NE - 不相等:
LE - 小于等于:

例如在组装转储中的LEV助记符:

输出
0x00000461: lev	b
GE - 大于或等于:
LT - 小于:
GT - 大于:
V 变量
C 常量

转移操作:

助记符 操作
JMP 跳转到地址
JZ 如果为0则跳转
JNZ 如果不为0则跳转
JEQ - 如果为0则跳转:
JNE - 如果不为0则跳转:

例如在组装转储中的JNEVV助记符:

输出
0x00000456: jnevv	a b 0x0000045f
JLE - 如果小于或等于则跳转:
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循环步骤
最新更新: 2017-07-03