Class Export
Unigine API supports class export from C++ into UnigineScript along with their:
- Constructors
- Member functions (up to eight arguments are supported)
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:
- Create an external class based on your C++ class via the MakeExternClass() function.
- Add constructors to the external class via the Unigine::ExternClass<Class>::addConstructor() function.
- 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:
If the target method has more than one argument, specify the array declaration on the corresponding position:my_object->addFunction("my_array",&MyExternObject::my_array,"[]");
// 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.
- Register the external class via Unigine::Interpreter::addExternClass().
- Export functions of the external class via the Interpreter::addExternFunction() function.
#include <UnigineEngine.h>
#include <UnigineInterpreter.h>
#include <UnigineInterface.h>
#include "AppSystemLogic.h"
#include "AppWorldLogic.h"
#include "AppEditorLogic.h"
using namespace Unigine;
using namespace Math;
/******************************************************************************\
*
* 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
*
\******************************************************************************/
#ifdef _WIN32
int wmain(int argc,wchar_t *argv[]) {
#else
int main(int argc,char *argv[]) {
#endif
// 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));
AppSystemLogic system_logic;
AppWorldLogic world_logic;
AppEditorLogic editor_logic;
Unigine::EnginePtr engine(UNIGINE_VERSION,argc,argv);
// Enter main loop.
engine->main(&system_logic,&world_logic,&editor_logic);
return 0;
}
Access from Scripts
After the registration, the exported class can be used in UnigineScript just like any other classes.
// 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() {
/* ... code ... */
/////////////////////////////////
log.message("\n");
// create an external object using a default constructor
MyExternObject extern_object = new MyExternObject();
extern_object_info(extern_object);
// set parameters of the external object
extern_object.setSize(vec3(10.0f,20.0f,30.0f));
extern_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);
/////////////////////////////////
return 1;
}
Output
The following results will be printed to the console:
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.
#include <UnigineEngine.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:
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.
// 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
MyClass::MyClass() is called
MyClass::MyClass(1) is called
Exporting Inherited Classes
You can export C++ classes inherited from other C++ classes into UnigineScript and use them like other classes. Both base and derived classes are exported as it was described above. For each derived class you should add a base class using the Unigine::ExternClass<Class>::addBaseClass() function.
my_derived_class->addBaseClass(my_base_class);
- MyBase - a base class
- MyNode - a class inherited from the MyBase class
- MyObject - a class inherited from the MyNode class
#include <UnigineEngine.h>
#include <UnigineInterpreter.h>
#include <UnigineInterface.h>
#include "AppSystemLogic.h"
#include "AppWorldLogic.h"
#include "AppEditorLogic.h"
using namespace Unigine;
//////////////////////////////////////////////////////////////////////////
// User defined class
//////////////////////////////////////////////////////////////////////////
class MyBase
{
public:
MyBase()
{
Log::warning("MyBase::MyBase(): called\n");
}
virtual ~MyBase()
{
Log::warning("MyBase::~MyBase(): called\n");
}
void function()
{
Log::warning("MyBase::function(): called\n");
}
virtual const char *getName() = 0;
};
class MyNode : public MyBase
{
public:
MyNode()
{
Log::warning("MyNode::MyNode(): called\n");
}
virtual ~MyNode()
{
Log::warning("MyNode::~MyNode(): called\n");
}
void function()
{
Log::warning("MyNode::function(): called\n");
}
virtual const char *getName()
{
return "MyNode";
}
};
class MyObject : public MyNode
{
public:
MyObject()
{
Log::warning("MyObject::MyObject(): called\n");
}
virtual ~MyObject()
{
Log::warning("MyObject::~MyObject(): called\n");
}
void function()
{
Log::warning("MyObject::function(): called\n");
}
virtual const char *getName()
{
return "MyObject";
}
};
/******************************************************************************\
*
* Main
*
\******************************************************************************/
int main(int argc, char **argv)
{
#ifdef _WIN32
int wmain(int argc,wchar_t *argv[]) {
#else
int main(int argc,char *argv[]) {
#endif
// Make a base class
ExternClass<MyBase> *my_base = MakeExternClass<MyBase>();
// add functions
my_base->addFunction("function", &MyBase::function);
my_base->addFunction("getName", &MyBase::getName);
// register the MyBase class.
Interpreter::addExternClass("MyBase", my_base);
// Make a class inherited from the MyBase
ExternClass<MyNode> *my_node = MakeExternClass<MyNode>();
// add a default constructor without arguments
my_node->addConstructor();
my_node->addFunction("function", &MyNode::function);
// add base class for MyNode class
my_node->addBaseClass(my_base);
// register the MyNode class.
Interpreter::addExternClass("MyNode", my_node);
// Make a class inherited from the MyNode
ExternClass<MyObject> *my_object = MakeExternClass<MyObject>();
// add a default constructor without arguments
my_object->addConstructor();
// add a function
my_object->addFunction("function", &MyObject::function);
// add base class for MyObject class
my_object->addBaseClass(my_node);
// register the MyObject class.
Interpreter::addExternClass("MyObject", my_object);
AppSystemLogic system_logic;
AppWorldLogic world_logic;
AppEditorLogic editor_logic;
// init engine
Unigine::EnginePtr engine(UNIGINE_VERSION,argc,argv);
// Enter main loop.
engine->main(&system_logic,&world_logic,&editor_logic);
return 0;
}
Access from Scripts
After the registration, the exported classes can be used in UnigineScript just like any other classes.
// my_world.cpp
/*
*/
int init() {
/////////////////////////////////
log.message("\n");
// object class
MyObject object = new MyObject();
object.function();
log.message("%s\n", object.getName());
// node class
MyNode node = object;
node.function();
log.message("%s\n", node.getName());
// base class
MyBase base = node;
base.function();
log.message("%s\n", base.getName());
// delete object
delete object;
/////////////////////////////////
// show console
engine.console.setActivity(1);
}
Output
The following results will be printed to the console:
MyBase::MyBase(): called
MyNode::MyNode(): called
MyObject::MyObject(): called
MyObject::function(): called
MyObject
MyNode::function(): called
MyObject
MyBase::function(): called
MyObject
MyObject::~MyObject(): called
MyNode::~MyNode(): called
MyBase::~MyBase(): 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:
- Unigine::Engine::getWorldInterpreter()
- Unigine::Engine::getSystemInterpreter()
- Unigine::Engine::getEditorInterpreter()
Also you can use pointer to the current interpreter obtained via the Unigine::Interpreter::get() function. If this function is called by the world interpreter, the current interpreter will be the world interpreter.
Interpreter *interpreter = Unigine::Interpreter::get();
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:
#include <UnigineEngine.h>
#include <UnigineInterpreter.h>
#include <UnigineInterface.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;
}
// 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;
}