Programming
Fundamentials
Setting Up Development Environment
UnigineScript
High-Level Systems
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine and Tools
GUI
Double Precision Coordinates
API
Core Library
Containers
Engine Classes
Node-Related Classes
Rendering-Related Classes
Physics-Related Classes
Bounds-Related Classes
GUI-Related Classes
Controls-Related Classes
Pathfinding-Related Classes
Utility Classes

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:

  1. A source code string with an invalid statement.
  2. An error description from the interpreter.

Mismatched curly braces are the most frequent compile-time error. For example:

Source code(UnigineScript)
class Foo {		
	Foo(int a) {
		this.a = a;
	// missing closing curly brace
	~Foo() {}	
	
	void print() {
		log.message("a is %d\n",a);
	}
};
Output
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.
    Source code(UnigineScript)
    class Foo {		
    	Foo(int a) {
    		this.a = a;
    	}	
    	
    	~Foo() {}	
    	
    	void print() {
    		log.message("a is %d\n",a);
    	}
    };
    Variable a is not declared, so an error occurs:
    Output
    Interpreter::parse(): unknown "Foo" class member "a"
  • The following and alike complex expressions lead to errors:
    Source code(UnigineScript)
    object.doSomething().doSomethingElse();
    object.doSomething()[4];
    For the first expression, the following error message occurs:
    Output
    Parser::expectSymbol(): bad '.' symbol expecting end of string
    For the second expression, the error message is:
    Output
    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.

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:

  1. An error description from the interpreter.
  2. Current stack of function calls.
  3. An assembly dump of an invalid statement.
For example:
Output
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
Notice
You can debug run-time errors with a console debugger.

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:
    Source code(UnigineScript)
    // the following is ok
    Object object = new ObjectMesh("file.mesh");
    object = new LightWorld(vec4_one);
    However, people tend to forget what they've assigned to a variable and call inappropriate methods:
    Source code(UnigineScript)
    // the following is NOT ok, as object is not of type ObjectMesh any more
    Material m = object.getMaterial(0);
    In this case, the error message is:
    Output
    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.

    Source code(UnigineScript)
    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
    In this case, the error message is:
    Output
    UserArray::get(): bad array index 4
  • Make sure you use correct swizzles with proper objects. For example, you cannot use swizzles with scalar types.
    Source code(UnigineScript)
    int a = 10;	// value of the scalar type	
    log.message("%d\n", a.x); // swizzling
    The exapmle produces the following error message:
    Output
    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:
    Source code(UnigineScript)
    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();
    The example produces an error:
    Output
    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.
    Source code(UnigineScript)
    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
    The error message is:
    Output
    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.

Notice
Note that the debugger opens in the main (external) console, which is available only in the debug builds.

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:

Source code(UnigineScript)
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.

Notice
This type of breakpoints does not require the script recompilation.

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:

Source code
world_breakpoint set printMyMessage 0
When the breakpoint is encountered, the console debugger is invoked, and you can run debugger commands.
Notice
The last parameter number_of_arguments is optional.

Features

The debugger also supports limited autocompletion and history of commands.

With the debugger, you cannot:

  • Call functions.
  • Evaluate arbitrary expressions.
  • Retrieve map values by arbitrary keys. Only int , float ,and string keys are supported.

Commands

The debugger supports several console commands listed below.

help

Lists all available commands.

Short formLong form
h help

run

Continues interpreter execution until the next breakpoint or the end of the script is reached.

Short formLong 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 formLong 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:

Source code(UnigineScript)
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);
}
To execute the next instruction after the breakpoint, type the next command:
Output
Breakpoint at 0x00000455: setvc a = int: 10
# next
Breakpoint at 0x00000458: setvc b = int: 1

stack

Dumps the memory stack.

Short formLong form
s stack

calls

Dumps the function call stack.

Short formLong 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 formLong 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:

Output
# 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 formLong 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:

Output
# 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

Displays all variables in the current scope.

Short formLong form
l list

See also: info.

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 formLong form
f fault

break

Toggles the current breakpoint. You can add or remove the breakpoint for each script instruction during the debug process.

Short formLong form
b break

For example, you debug the following code:

Source code(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);
}
To add a breakpoint during the debug process, type b or break in the debugger:
Output
Breakpoint at 0x00000455: setvc a = int: 10
# next
Breakpoint at 0x00000458: setvc b = int: 1
# break
If you disassemble instructions, the instruction with the new breakpoint is marked with !:
Output
Disassemble:
0x00000458: ! setvc	b = int: 1
0x00000468:	  setvc	i = int: 0
To remove the breakpoint, type break or b again:
Output
Breakpoint at 0x00000455: setvc a = int: 10
# next
Breakpoint at 0x00000458: setvc b = int: 1
# break
# break

Notice
By using this command, you cannot remove the breakpoint (breakpoint; instruction) you set in the script.

Assembly Mnemonics

Here is a list of assembly mnemonics for the assembly dump.

MnemonicOperation
NOPNo operation

Set operations:

MnemonicOperation
SETXSet temporary register X
SET - Set variable by:

For example, the SETVC mnemonic in the assembly dump:

Output
0x00000458: setvc b = int: 1
VStack
VVVariable
VCConstant
VEVExtern variable
UAVUser array variable
UAVVUser array variable by variable
UAVCUser array variable by constant
SET - Set user array variable by:
UAVCVVariable
UAVCCConstant

Pop operations:

MnemonicOperation
POPPop stack
POP - Pop:

For example, the POPV mnemonic in the assembly dump:

Output
0x00000468: popv	b
YTemporary register X
ZTemporary register Z
VVariable
VVVariable by variable
VEVariable element
VSVariable swizzle
UAIDUser array ID
UAVUser array variable
UAVVUser array variable by variable
UAVCUser array variable by constant
UAVSUser array variable swizzle
UAVEUser array variable element
UCUser class
UCXPop user class and save stack
UCRPop user class and stack remove

Push operations:

MnemonicOperation
PUSH - Push:

For example, the PUSHC mnemonic in the assembly dump:

Output
0x0000046c: pushc	string: "Iteration: %d\n"
XTemporary register X
YTemporary register X
ZTemporary register Z
CConstant
CCTwo constants
VVariable
VVVariable by variable
VEVariable element
VSVariable swizzle
EVExtern variable
EVEExtern variable element
EVSExtern variable swizzle
UAIDUser array ID
UAVUser array variable
UAVVUser array variable by variable
UAVCUser array variable by constant
UAVSUser array variable swizzle
UAVEUser array variable element
UCUser class
UCVUser class variable
UCVEUser class variable element
UCVSUser class variable swizzle
UCAVUser class array variable
UCCUser class current object

Call operations:

MnemonicOperation
CALLCall address
CALL - Call:

For example, the CALLEFR mnemonic in the assembly dump:

Output
0x0000046e: callefr	log.message
FFunction
FDFunction dynamic
FTFunction thread
EFExtern function
EFRExtern stack remove function
UACUser array constructor
UAFUser array function
UAFRUser array stack remove function
UCCUser class constructor
UCFUser class function
UCFVUser class virtual function
UCFVCUser class virtual current function
ECCExternal class constructor
ECFExternal class function address
ECFRExternal class stack remove function
CCDClass dynamic constructor
CFDClass dynamic function
CDClass destructor

Math operations:

MnemonicOperation
INVInverse
ONEOne's complement
NEGNegate

INCIncrement
DECDecrement
INCVVariable increment
DECVVariable decrement

MULMultiplication
DIVDivision
MODModulus
ADDAddition
SUBSubtraction
MUL - Multiplication by:

For example, the MULVC mnemonic in the assembly dump:

Output
0x0000044c: mulvc	a int: 1
DIV - Division by:

For example, the DIVVC mnemonic in the assembly dump:

Output
0x0000044c: mulvc	a int: 2
MOD - Modulus by:
ADD - Addition by:
SUB - Subtraction by:
VVariable
CConstant
VVVariable by variable
VCVariable by constant
CVConstant by variable

SHLShift left
SHRShift right
SHL - Shift left by:
SHR - Shift right by:

For example, the SHLC mnemonic in the assembly dump:

Output
0x0000046b: shlc	int: 1
VVariable
CConstant

BANDBit and
BXORBit xor
BORBit or
BAND - Bit and by:
BXOR - Bit xor by:
BOR - Bit or by:

For example, the BORVV mnemonic in the assembly dump:

Output
0x00000462: borvv	a b
VVariable
CConstant
VVVariable by variable
VCVariable by constant

EQEqual
NENot equal
LELess than or equal
GEGreater than or equal
LTLess than
GTGreater than
EQ - Equal:
NE - Not equal:
LE - Less or equal:

For example, the LEV mnemonic in the assembly dump:

Output
0x00000461: lev	b
GE - Greater or equal:
LT - Less than:
GT - Greater than:
VVariable
CConstant

Branch operations:

MnemonicOperation
JMPJump to address
JZJump if zero
JNZJump if not zero
JEQ - Jump if equal to:
JNE - Jump if not equal to:

For example, the JNEVV mnemonic in the assembly dump:

Output
0x00000456: jnevv	a b 0x0000045f
JLE - Jump if less or equal to:
JGE - Jump if greater or equal to:
JLT - Jump if less than to:
JGT - Jump if greater than to:
VVariable
CConstant
VVVariable by variable
VCVariable by constant

GOTODGo to label by name
YIELDYield return
WAITWait return
RETReturn
RETZReturn zero
RETCReturn constant

SWITCHSwitch

Loops:

MnemonicOperation
FORLOOPFor loop
FOREINITForeach loop initialization
FORESTEPForeach loop step
FOREKINITForeachkey loop initialization
FOREKSTEPForeachkey loop step
Last update: 2017-07-03