Script Debugging
The Unigine Debugger allows you to inspect your UnigineScript code at run-time. For example, it can help you to determine when a function is called and with which values. Furthermore, you can locate bugs or logic problems in your scripts by executing them step by step.
An additional information from the scripting system of Unigine is obtained if you use debug builds.
The engine may cause the errors of two types: compile-time and run-time.
Compile-Time Errors#
Error Message#
When a compile-time error occurs, it means that the interpreter could not parse and load the script. In this case, the log file will contain an error message with:
- A source code string with an invalid statement.
- An error description from the interpreter.
Mismatched curly braces are the most frequent compile-time error. For example:
class Foo {
Foo(int a) {
this.a = a;
// missing closing curly brace
~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-Specific Warnings#
Among the obvious errors like incorrect syntax, there are some not so evident and more confusing. The most important are:
- Recursive includes are not tracked, so be careful with file inclusions and use defines when in doubt.
- In user class definitions make sure that all member variables are declared before they are used in a constructor or a method.
Variable a is not declared, so an error occurs:
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"
- The following and alike complex expressions lead to errors:
For the first expression, the following error message occurs:
object.doSomething().doSomethingElse(); object.doSomething()[4];
For the second expression, the error message is:Parser::expectSymbol(): bad '.' symbol expecting end of string
The point is that due to a dynamic typing the interpreter does not know what will be returned by object.doSomething(), maybe, even nothing, and that may lead to a run-time error.Parser::expectSymbol(): bad '[' symbol expecting end of string
Run-Time Errors#
Error Message#
When a run-time error occurs, it usually means that you are trying to manipulate a corrupt or even a non-existent object. In this case, the log file will contain an error message with:
- An error description from the interpreter.
- Current stack of function calls.
- An assembly dump of an invalid statement.
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
Common Errors#
Common run-time errors:
- NULL objects. Please, don't forget to initialize declared objects and check the values returned from functions.
- Stack underflow (or overflow) when less (or more) arguments than required are provided to a function.
- Dynamic typing allows assigning a value of one type to a variable of another, for example:
However, people tend to forget what they've assigned to a variable and call inappropriate methods:
// the following is ok Object object = new ObjectMesh("file.mesh"); object = new LightWorld(vec4_one);
In this case, the error message is:// the following is NOT ok, as object is not of type ObjectMesh any more Material m = object.getMaterial(0);
ExternClass::run_function(): can't find "class Object * __ptr64" base class in "class LightWorld * __ptr64" class
-
When creating a vector, make sure you provide a correct capacity (non-negative). If the negative capacity is specified, a vector size is set to 0 by default.
Also, when addressing vector contents, make sure an index being used exists; the same is true for maps and their keys. Also, a map key cannot be NULL.
In this case, the error message is:int vector[2] = ( 1, 2, 3 ); // vector of 3 elements is defined log.message("%d\n", vector[4]); // addressing the 4th component, which doesn't exist
UserArray::get(): bad array index 4
- Make sure you use correct swizzles with proper objects. For example, you cannot use swizzles with scalar types.
The exapmle produces the following error message:
int a = 10; // value of the scalar type log.message("%d\n", a.x); // swizzling
Variable::getSwizzle(): bad component 0 for int variable
- If a user class overloads some operator, do not forget to preserve the order of operands in the code:
The example produces an error:
class Foo { int f; Foo(int f = 0) { this.f = f; } int operator+(Foo arg1,int arg2) { return arg1.f + arg2; } }; // this is ok int ret = new Foo() + 5; // this is NOT ok ret = 5 + new Foo();
Variable::add(): bad operands int and user class
- Make sure that if you use wait control structure in the class method, this class method is called as a static one. The error message is:
class Foo { void update() { while(true) wait 1; } }; Foo f = new Foo(); Foo::update(); // this is valid, because the method is called as a static function f.update(); // this would cause a crash, because a class instance is passed
Interpreter::runFunction(): depth of stack is not zero
- Use class_cast() with caution. Remember that it converts an instance of one user class to another without warnings, even if those classes have nothing in common.
- Lots of transient objects. This is not exactly an error, but if you create in a short period plenty of user class objects that soon become unused, the performance will drop every 32 frames because of garbage collector clean-ups, until all unused objects are gone. In this case, the performance drop will be caused by both a multitude of objects and expensive object destruction.
Debugger#
The Unigine debugger allows you to:
- Set the breakpoints directly in the script
- Set and remove the breakpoints via the console
- View memory stack
- View function call stack
- View current variables values
- Step through instructions
To run the debug process, you can insert the breakpoint; instruction in your code or set the run-time breakpoint.
The console debugger window (in Windows):
Set a Breakpoint#
To invoke the console debugger, insert a breakpoint; instruction in the script you are working on. This type of instructions used for the precise breakpoints placing. You can insert more than one breakpoint; instruction in your script.
For example:
int a = 10;
breakpoint; // the breakpoint instruction
int b = 1;
forloop(int i = 0; a){
b += i;
log.message("Iteration: %d\n",i);
log.message("Value: %d\n",b);
}
When a breakpoint is encountered, the engine execution stops, and the application stops responding to user actions. Instead, the external console starts receiving user input.
In this console, you can step through instructions using the next command. Also it is possible to set the breakpoint during the debug process by using the break command. It is useful, for example, when you debug a loop. Or you can run the other debugger commands, if necessary.
If the console is unavailable, as in Windows release builds, this will seem as a hang-up of the engine. To avoid that, use a "breakpoint" macro defined in the file data/core/unigine.h. This macro also correctly preserves FPS values.
Set a Run-Time Breakpoint#
There is also a way to set a run-time per-function interpreter breakpoints with the specified number of arguments via the editor console. A required script instruction is triggered for breakpoint, so the engine execution stops and the external console starts to receive the user input. Moreover, it is also possible to set the breakpoint flag inside the debugger.
There are 3 types of such breakpoints: system_breakpoint, world_breakpoint and editor_breakpoint, used for system, world, and editor scripts respectively.
The syntax to set the breakpoint is the following: system_breakpoint/world_breakpoint/editor_breakpoint set/remove function_name number_of_arguments.
For example, to set the breakpoint on the custom function with zero arguments printMyMessage(), type in the editor console the following:
world_breakpoint set printMyMessage 0
Features#
The debugger also supports limited autocompletion and history of commands.
With the debugger, you cannot:
Commands#
The debugger supports several console commands listed below.
help#
Lists all available commands.
Short form | Long form |
---|---|
h | help |
run#
Continues interpreter execution until the next breakpoint or the end of the script is reached.
Short form | Long form |
---|---|
r [N] | run [N] |
Here N is an optional argument specifying the number of breakpoints to skip. By default, N equals to 0.
next#
Executes the next instruction. This command is used to step through instructions starting from the breakpoint.
Short form | Long form |
---|---|
n [N] | next [N] |
Here N is an optional argument specifying the number of instructions to skip. By default, N equals to 0.
For example, you debug the following code:
breakpoint; // here the breakpoint is set
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#
Dumps the memory stack.
Short form | Long form |
---|---|
s | stack |
calls#
Dumps the function call stack.
Short form | Long form |
---|---|
c | calls |
Some notes:
- Function calls are listed starting from a C++ function call, which is not included in the stack. For example, if some script function is invoked as a callback, you will not see the invoking C++ function.
- If a function address is intact, a function name will be displayed, otherwise, you will see gibberish instead of the name. The address changes, if there are yield or wait instructions in the function body.
dasm#
Disassembles a certain number of instructions starting from the current instruction.
Short form | Long form |
---|---|
d [N] | dasm [N] |
Here N is an optional argument specifying the number of instructions to process. By default, N equals to 8.
For example:
# 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
See also: Assembly Mnemonics.
info#
Displays contents of given variables.
Short form | Long form |
---|---|
i var_list | info var_list |
Here var_list is a list of space-separated variable names. If a variable is not local to the current scope, its name should contain a namespace prefix.
Some notes:
- Both ordinary and array variables are supported
- Values of map and vector elements can be accessed via constant keys/indices in brackets ([]). Only int, float, and string keys are supported
- User class members can be accessed via a dot (.). Autocompletion of user class members is not supported
Usage example:
# 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
This command is useful, when you want to check, for example, which instruction changes variable values.
See also: list.
list#
fault#
Crashes interpreter. Useful when the enigne itself is run in a debugger (for example, gdb), as it allows seeing the stack of C++ function calls.
Short form | Long form |
---|---|
f | fault |
break#
Toggles the current breakpoint. You can add or remove the breakpoint for each script instruction during the debug process.
Short form | Long form |
---|---|
b | break |
For example, you debug the following code:
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
Assembly Mnemonics#
Here is a list of assembly mnemonics for the assembly dump.
Mnemonic | Operation |
---|---|
NOP | No operation |
Set operations:
Mnemonic | Operation |
---|---|
SETX | Set temporary register X |
SET - Set variable by:
For example, the SETVC mnemonic in the assembly dump:
|
|
V | Stack |
VV | Variable |
VC | Constant |
VEV | Extern variable |
UAV | User array variable |
UAVV | User array variable by variable |
UAVC | User array variable by constant |
SET - Set user array variable by: | |
UAVCV | Variable |
UAVCC | Constant |
Pop operations:
Mnemonic | Operation |
---|---|
POP | Pop stack |
POP - Pop:
For example, the POPV mnemonic in the assembly dump:
|
|
Y | Temporary register X |
Z | Temporary register Z |
V | Variable |
VV | Variable by variable |
VE | Variable element |
VS | Variable swizzle |
UAID | User array ID |
UAV | User array variable |
UAVV | User array variable by variable |
UAVC | User array variable by constant |
UAVS | User array variable swizzle |
UAVE | User array variable element |
UC | User class |
UCX | Pop user class and save stack |
UCR | Pop user class and stack remove |
Push operations:
Mnemonic | Operation |
---|---|
PUSH - Push:
For example, the PUSHC mnemonic in the assembly dump:
|
|
X | Temporary register X |
Y | Temporary register X |
Z | Temporary register Z |
C | Constant |
CC | Two constants |
V | Variable |
VV | Variable by variable |
VE | Variable element |
VS | Variable swizzle |
EV | Extern variable |
EVE | Extern variable element |
EVS | Extern variable swizzle |
UAID | User array ID |
UAV | User array variable |
UAVV | User array variable by variable |
UAVC | User array variable by constant |
UAVS | User array variable swizzle |
UAVE | User array variable element |
UC | User class |
UCV | User class variable |
UCVE | User class variable element |
UCVS | User class variable swizzle |
UCAV | User class array variable |
UCC | User class current object |
Call operations:
Mnemonic | Operation |
---|---|
CALL | Call address |
CALL - Call:
For example, the CALLEFR mnemonic in the assembly dump:
|
|
F | Function |
FD | Function dynamic |
FT | Function thread |
EF | Extern function |
EFR | Extern stack remove function |
UAC | User array constructor |
UAF | User array function |
UAFR | User array stack remove function |
UCC | User class constructor |
UCF | User class function |
UCFV | User class virtual function |
UCFVC | User class virtual current function |
ECC | External class constructor |
ECF | External class function address |
ECFR | External class stack remove function |
CCD | Class dynamic constructor |
CFD | Class dynamic function |
CD | Class destructor |
Math operations:
Mnemonic | Operation |
---|---|
INV | Inverse |
ONE | One's complement |
NEG | Negate |
|
|
INC | Increment |
DEC | Decrement |
INCV | Variable increment |
DECV | Variable decrement |
|
|
MUL | Multiplication |
DIV | Division |
MOD | Modulus |
ADD | Addition |
SUB | Subtraction |
MUL - Multiplication by:
For example, the MULVC mnemonic in the assembly dump:
For example, the DIVVC mnemonic in the assembly dump:
ADD - Addition by: SUB - Subtraction by: |
|
V | Variable |
C | Constant |
VV | Variable by variable |
VC | Variable by constant |
CV | Constant by variable |
|
|
SHL | Shift left |
SHR | Shift right |
SHL - Shift left by:
SHR - Shift right by: For example, the SHLC mnemonic in the assembly dump:
|
|
V | Variable |
C | Constant |
|
|
BAND | Bit and |
BXOR | Bit xor |
BOR | Bit or |
BAND - Bit and by:
BXOR - Bit xor by: BOR - Bit or by: For example, the BORVV mnemonic in the assembly dump:
|
|
V | Variable |
C | Constant |
VV | Variable by variable |
VC | Variable by constant |
|
|
EQ | Equal |
NE | Not equal |
LE | Less than or equal |
GE | Greater than or equal |
LT | Less than |
GT | Greater than |
EQ - Equal:
NE - Not equal: LE - Less or equal: For example, the LEV mnemonic in the assembly dump:
LT - Less than: GT - Greater than: |
|
V | Variable |
C | Constant |
Branch operations:
Mnemonic | Operation |
---|---|
JMP | Jump to address |
JZ | Jump if zero |
JNZ | Jump if not zero |
JEQ - Jump if equal to:
JNE - Jump if not equal to: For example, the JNEVV mnemonic in the assembly dump:
JGE - Jump if greater or equal to: JLT - Jump if less than to: JGT - Jump if greater than to: |
|
V | Variable |
C | Constant |
VV | Variable by variable |
VC | Variable by constant |
|
|
GOTOD | Go to label by name |
YIELD | Yield return |
WAIT | Wait return |
RET | Return |
RETZ | Return zero |
RETC | Return constant |
|
|
SWITCH | Switch |
Loops:
Mnemonic | Operation |
---|---|
FORLOOP | For loop |
FOREINIT | Foreach loop initialization |
FORESTEP | Foreach loop step |
FOREKINIT | Foreachkey loop initialization |
FOREKSTEP | Foreachkey loop step |