UnigineScript
The Language
Core Library
Engine Library
Node-Related Classes
GUI-Related Classes
Plugins Library
High-Level Systems
Samples
C++ API
API Reference
Integration Samples
C++ Plugins
Content Creation
Materials
Unigine Material Library
Tutorials

Class Export

Unigine API supports class export from C++ into UnigineScript along with their:

  • Constructors
  • Member functions (up to eight arguments are supported)
Notice
Member variables cannot be exported. Since it is not possible to access them directly, you need to create accessor methods for them.

See also

An example can be found in the <UnigineSDK>/source/samples/Api/Scripts/Classes/ directory.

Class Export Example

In order to export a C++ class into UnigineScript you need to do the following in the main() function on the C++ side:

  1. Create an external class based on your C++ class via the MakeExternClass() function.
  2. Add constructors to the external class via the Unigine::ExternClass<Class>::addConstructor() function.
  3. Add methods to the external class via the Unigine::ExternClass<Class>::addFunction() function.

    If the method receives an array as an argument, you should specify the array declaration as the last argument of the addFunction() as follows:

    Source code (C++)
    my_object->addFunction("my_array",&MyExternObject::my_array,"[]");
    							
    If the target method has more than one argument, specify the array declaration on the corresponding position:
    Source code (C++)
    // the my_array() method receives an array as the second argument
    my_object->addFunction("my_array",&MyExternObject::my_array,",[],");
    							

    See also the Default Argument Values chapter of the Function Export article and the article on UnigineScript Containers for more details.

  4. Register the external class via Unigine::Interpreter::addExternClass().
  5. Export functions of the external class via the Interpreter::addExternFunction() function.

Note that static methods of classes are exported as pure functions.

Source code (C++)
#include <Unigine.h>
#include <UnigineInterpreter.h>

using namespace Unigine;

/******************************************************************************\
*
* Extern class
*
\******************************************************************************/

/*
*/
class MyExternObject : public Base {
		
	public:
		
		MyExternObject() : mass(0.0f) {
			Log::warning("MyExternObject::MyExternObject(): called\n");
		}
		MyExternObject(const vec3 &size,float mass) : size(size), mass(mass) {
			Log::warning("MyExternObject::MyExternObject((%g,%g,%g),%g): called\n",size.x,size.y,size.z,mass);
		}
		~MyExternObject() {
			Log::warning("MyExternObject::~MyExternObject(): called\n");
		}
		
		// size
		void setSize(const vec3 &s) {
			Log::warning("MyExternObject::setSize((%g,%g,%g)): called\n",s.x,s.y,s.z);
			size = s;
		}
		const vec3 &getSize() const {
			return size;
		}
		
		// mass
		void setMass(float m) {
			Log::warning("MyExternObject::setMass(%g): called\n",m);
			mass = m;
		}
		float getMass() const {
			return mass;
		}
		
	private:
		
		vec3 size;
		float mass;
};

/*
*/
MyExternObject *MakeMyExternObject(const vec3 &size,float mass) {
	return new MyExternObject(size,mass);
}

void DeleteMyExternObject(MyExternObject *object) {
	delete object;
}

/*
 */
void MyExternObjectSetSize(MyExternObject *object,const vec3 &size) {
	object->setSize(size);
}

const vec3 &MyExternObjectGetSize(MyExternObject *object) {
	return object->getSize();
}

/******************************************************************************\
*
* Main
*
\******************************************************************************/

/*
*/
int main(int argc,char **argv) {
	
	
	// export extern class
	ExternClass<MyExternObject> *my_object = MakeExternClass<MyExternObject>();
	my_object->addConstructor();
	my_object->addConstructor<const vec3&,float>();
	my_object->addFunction("setSize",&MyExternObject::setSize);
	my_object->addFunction("getSize",&MyExternObject::getSize);
	my_object->addFunction("setMass",&MyExternObject::setMass);
	my_object->addFunction("getMass",&MyExternObject::getMass);
	Interpreter::addExternClass("MyExternObject",my_object);
	
	// export extern class functions
	Interpreter::addExternFunction("DeleteMyExternObject",MakeExternFunction(&DeleteMyExternObject));
	Interpreter::addExternFunction("MakeMyExternObject",MakeExternFunction(&MakeMyExternObject));
	Interpreter::addExternFunction("MyExternObjectSetSize",MakeExternFunction(&MyExternObjectSetSize));
	Interpreter::addExternFunction("MyExternObjectGetSize",MakeExternFunction(&MyExternObjectGetSize));
	
	// init engine
	Engine *engine = Engine::init(UNIGINE_VERSION,argc,argv);
	
	// enter main loop
	engine->main();
	
	// shutdown engine
	Engine::shutdown();
	
	return 0;
}

Access from Scripts

After the registration, the exported class can be used in UnigineScript just like any other classes.

Source code (UnigineScript)
// my_world.cpp

/*
*/
void extern_object_info(MyExternObject object) {
	
	// call object methods to get its parameters
	vec3 size = object.getSize();
	float mass = object.getMass();
	
	log.message("size is: (%g,%g,%g), mass is: %g\n",size.x,size.y,size.z,mass);
}

/*
*/
int init() {

	/////////////////////////////////
	
	log.message("\n");
	
	// create an external object using a default constructor
	MyExternObject extern_object = new MyExternObject();
	exter_object_info(extern_object);
	
	// set parameters of the external object
	object.setSize(vec3(10.0f,20.0f,30.0f));
	object_info(extern_object);
	
	// delete the object
	delete extern_object;
	
	/////////////////////////////////
	
	log.message("\n");
	
	// create an object using another constructor
	extern_object = new MyExternObject(vec3(1.0f,2.0f,3.0f),10.0f);
	extern_object_info(extern_object);
	
	// set object parameters
	MyExternObjectSetSize(extern_object,vec3(10.0f,20.0f,30.0f));
	vec3 size = MyExternObjectGetSize(extern_object);
	log.message("size is: (%g,%g,%g)\n",size.x,size.y,size.z);
	
	// delete the object
	delete extern_object;
	
	/////////////////////////////////
	
	log.message("\n");
	
	// create an object using the external class function
	extern_object = MakeMyExternObject(vec3(4.0f,5.0f,6.0f),10.0f);
	extern_object_info(extern_object);
	
	// set object parameters.
	extern_object.setMass(100.0f);
	extern_object_info(extern_object);
	
	// delete the object using the external class function
	DeleteMyExternObject(extern_object);
	
	/////////////////////////////////
	
	// open the console to show the example output
	engine.console.setActivity(1);
	
	return 1;
}

Output

The following results will be printed into the console:

Output
MyExternObject::MyExternObject(): called
size is: (0,0,0), mass is: 0
MyExternObject::setSize((10,20,30)): called
size is: (10,20,30), mass is: 0
MyExternObject::~MyExternObject(): called

MyExternObject::MyExternObject((1,2,3),10): called
size is: (1,2,3), mass is: 10
MyExternObject::setSize((10,20,30)): called
size is: (10,20,30)
MyExternObject::~MyExternObject(): called

MyExternObject::MyExternObject((4,5,6),10): called
size is: (4,5,6), mass is: 10
MyExternObject::setMass(100): called
size is: (4,5,6), mass is: 100
MyExternObject::~MyExternObject(): called

Exporting a Class with a Protected Constructor

If necessary, you can make a protected constructor of a C++ class available from scripts. To export it, you need to declare Unigine::ExternClassConstructor<Class,List,Type> template as a class friend.

Notice
Up to 9 arguments are supported.
You can find the declaration of the template in the <UnigineSDK>/include/UnigineInterpreter.h header file.
Notice
Protected class members cannot be exported.
Source code (C++)
#include <Unigine.h>
#include <UnigineInterpreter.h>

using namespace Unigine;

/******************************************************************************\
*
* User defined class
*
\******************************************************************************/

/*
*/
class MyClass {
		
	protected:
	
		 // declare the template as the friend of the MyClass
		template <class,typename,typename> friend class Unigine::ExternClassConstructor;
		
		// define the first constructor (without arguments)
		MyClass() {
			Log::warning("MyClass::MyClass() is called\n");
		}
		
		// define the second constructor with one argument
		MyClass(int v) {
			Log::warning("MyClass::MyClass(%d) is called\n",v);
		}
};

/******************************************************************************\
*
* Main
*
\******************************************************************************/

/*
 */
int main(int argc,char **argv) {
	
	// export a class.
	ExternClass<MyClass> *my_object = MakeExternClass<MyClass>();
	// add a default constructor without arguments
	my_object->addConstructor();
	// add a constructor with one argument
	my_object->addConstructor<int>();
	// register the exported class.
	Interpreter::addExternClass("MyExternObject",my_object);
	
	// Initialize the engine.
	Engine *engine = Engine::init(UNIGINE_VERSION,argc,argv);
	
	// Enter the main loop.
	engine->main();
	
	// Shut down the engine.
	Engine::shutdown();
	
	return 0;
}

You can also declare the corresponding template as the class friend for each of the protected constructors as follows:

Source code (C++)
class MyClass {
		
	protected:
	
		// declare the templates as the friends of the MyClass
		// one to add the constructor without arguments
		friend class Unigine::ExternClassConstructor<MyClass,MakeTypeList<>::Type>;
		// and another to add the constructor with one argument
		friend class Unigine::ExternClassConstructor<MyClass,MakeTypeList<int>::Type>;
		
		// define the first constructor (without arguments)
		MyClass() {
			Log::warning("MyClass::MyClass() is called\n");
		}
		
		// define the second constructor with one argument
		MyClass(int v) {
			Log::warning("MyClass::MyClass(%d) is called\n",v);
		}
};
				

Access from Scripts

After that, you can use the exported class in UnigineScript.

Source code (UnigineScript)
// my_world.cpp

int init() {
		
	// create an instance of the exported class
	MyExternObject object_0 = new MyExternObject();
	MyExternObject object_1 = new MyExternObject(1);
	
	return 1;
}

Output

Output
MyClass::MyClass() is called
MyClass::MyClass(1) is called

Memory Management for External Classes

By both creating and deleting variables that refer to the external classes, the corresponding scope should be set (world / system / editor). You should use pointers to the corresponding interpreter that are obtained via the following functions in order to set the required scope:

Also you can use pointer to the current interpreter obtained via the Unigine::Interpreter::get() function:

Source code (C++)
Interpreter *interpreter = Unigine::Interpreter::get();
				
Notice
If a C++ function is called from the script (world, system and editor), it means the current scope is already set and there is no need to call functions listed above.

If the corresponding scope is not set, memory leaks can occur when creating or deleting a variable.

For example, if you have a function defined on the script side and want to call it from the C++ code with a variable of the external class as an argument, you should set the script runtime:

Source code (C++)
#include <Unigine.h>
#include <UnigineInterpreter.h>

#include <string>

using namespace Unigine;

class MyExternClass {

    public:	

        MyExternClass() {}
        MyExternClass(const std::string &m) { my_member = m; }
        MyExternClass(const MyExternClass &other) { my_member = other.my_member; }
        ~MyExternClass() {}

    private:

        std::string my_member;

};

void my_update() {

    MyExternClass mec("hello!!!\n");
    Engine *engine = Engine::get();
	// get a pointer to the world interpreter 
	Interpreter *world = (Interpreter*)engine->getWorldInterpreter();
	// create a variable of the external class
	Unigine::Variable v(world,TypeInfo(TypeID<MyExternClass*>()),new MyExternClass(mec),1,1);
	// specify the name of the function to call
	Unigine::Variable name("onMyUpdate");
	// run the world script function with the variable of the MyExternClass as the argument
   	engine->runWorldFunction(name,v);
	
}

int main(int argc,char **argv) {

    ExternClass<MyExternClass> *mec = MakeExternClass<MyExternClass>();
    Interpreter::addExternClass("MyExternClass",mec);
    
    Engine *engine = Engine::init(UNIGINE_VERSION,argc,argv);
    while(engine->isDone() == 0) {
		engine->update();
		engine->render();
		engine->swap();
       
        my_update();
    }

    Engine::shutdown();
    return 0;
}
			
Notice
In the example above Variable v is a static variable, that is why when leaving the scope of its visibility, it is necessary to reset the context.
Source code (UnigineScript)
// the world script function which receives a variable referring to the external class
void onMyUpdate(MyExternClass v) {
	// some code
}

int init() {
	// some code
	return 1;
}

int shutdown() {
	// some code
	return 1;
}

int update() {
	// some code
	return 1;
}
			
Last update: 2017-07-03