This page has been translated automatically.
Video Tutorials
Interface
Essentials
Advanced
How To
Basics
Rendering
Professional (SIM)
UnigineEditor
Interface Overview
Assets Workflow
Version Control
Settings and Preferences
Working With Projects
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Lighting
Sandworm
Using Editor Tools for Specific Tasks
Extending Editor Functionality
Built-in Node Types
Nodes
Objects
Effects
Decals
Light Sources
Geodetics
World Nodes
Sound Objects
Pathfinding Objects
Players
Programming
Fundamentals
Setting Up Development Environment
Usage Examples
C++
C#
UUSL (Unified UNIGINE Shader Language)
Plugins
File Formats
Materials and Shaders
Rebuilding the Engine Tools
GUI
Double Precision Coordinates
API
Animations-Related Classes
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
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
VR-Related Classes
Content Creation
Content Optimization
Materials
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Tutorials

Script Debugging

Warning
The scope of applications for UnigineScript is limited to implementing materials-related logic (material expressions, scriptable materials, brush materials). Do not use UnigineScript as a language for application logic, please consider C#/C++ instead, as these APIs are the preferred ones. Availability of new Engine features in UnigineScript (beyond its scope of applications) is not guaranteed, as the current level of support assumes only fixing critical issues.

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

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

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

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 form Long 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 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:

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.

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:

Output
0x00000458: setvc b = int: 1
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:

Output
0x00000468: popv	b
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:

Output
0x0000046c: pushc	string: "Iteration: %d\n"
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:

Output
0x0000046e: callefr	log.message
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:

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

Output
0x0000046b: shlc	int: 1
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:

Output
0x00000462: borvv	a b
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:

Output
0x00000461: lev	b
GE - Greater or equal:
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:

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:
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
Last update: 2024-08-16
Build: ()