内存管理
Unigine所有制
Unigine具有自身经优化的内存分配器用于实现更快效率更高的内存管理。其在预先分配的内存池中工作,此内存池被所有Unigine模块共用。使用外部C++类对Unigine函数进行扩展的自定义C++模块使用系统提供的基础性分配器,此分配器具有单独的内存池。
当不再需要分配时,对内存进行分配,对分配进行追踪及释放内存块是对象管理的主要任务。 Unigine由数个模块组成(包括上述提到的C++模块),每个模块都可建立物体对象的所有权,这意味着在不需要物体对象时,其应当负责接触对内存的分配。这样此物体对象便可为所有模块所访问。可对所有权进行释放并转配给另一个模块用来在合适的时间负责删除不需要的对象。如果未能成功设置一个能解除对象的所有权,内存泄露或甚至是应用程序崩溃可能会发生。
此处,物体对象是内部C++类的实例。下列方案通过节点示例演示Unigine的所有制。引擎编辑器板块标有*,这样做是为了显示当前时刻此模块拥有此节点(内部C++类的实例)。请注意本方案仅作为一个示例,任何一个模块都可以成为内部实例的所有者并为此模块保存一个指针。
内部C++类。这些类的实例时低级嵌入对象,当脚本或C++模块对这些对象进行申明时,实际上它们会在C++上得到创建,比如:节点 (如示例中一样), 网格, 实体, 图像 等等。这样的 内部实例可为下列某种模块所拥有:
- 脚本。它们是:
- 包括世界对象逻辑的世界脚本 。
- 围包编辑器函数和编辑器GUI的编辑器脚本。
- 进行系统内务处理和控制系统菜单GUI的系统脚本 。
无论此内部实例属于谁,可使用任意一种脚本(世界对象脚本,系统脚本或编辑器脚本)来处理内部实例。在删除此实例时,所有权会起到作用。 - 引擎编辑器。其为Unigine引擎的C++部分。在完成世界对象的初始化后,引擎编辑器会从世界对象文件中加载节点并创建一个指针数组给内部实例。引擎编辑器作为等级列表显示在节点面板上并可在虚拟世界中对其进行实时的调整。默认情况下,这些节点为引擎编辑器所拥有。当然,节点的所有权可转换给来自脚本的引擎编辑器或来自C++模块的编辑器。
- API C++类 是包装类,这种类隐藏了内部C++类的实施并保存指针到这些内部类的实例中(换句话说即内部实例)。在下列情况下API C++类可为内部实例的所有者:
- 通过 create() 函数,这些实例在用户应用程序的C++上得到创建。
- 通过API C++类的grab()函数可手动设置这种实例的所有者。
由于C++ API类的实例仅将指针保存到内部C++类的实例上,因此这些指针无法通过标准的new / delete运算符进行创建并删除。因此这些指针应被声明为智能指针 (Unigine::Ptr),这种指针可以让您自动对其生命周期进行管理。如果删除一个智能指针,API C++类的实例也将自动被删除。此外,此实例也将删除内部C++对象,如果通过grab()函数设置了对应的所有权。
当然还有引擎世界对象,此对象也是Unigine引擎的C++部分(不要与世界对象脚本相混淆!)。所有通过new 运算符或create()函数创建的实例都会被自动添加到引擎世界中。引擎世界负责加载世界对象及其所有节点,用于管理空间树,处理碰撞及节点交叉。然而其并不会夺取内部实例的所有权。
管理所有权
对内部实例的所有权进行管理的基本原则如下:
- 无论是谁创建了内部C++ class内的实例,其都将自动成为此实例的所有者。然后拥有此内部实例的模块可将其删除。例如:
- 如果通过new运算符在应用程序的脚本上创建一个实例,那么脚本即为此实例的所有者并且可使用 delete运算符来删除此实例。
- 如果通过create()函数在C++上创建一个实例,C++模块将拥有此实例的所有权,可通过destroy()函数来删除此实例。
- 一个内部C++类的实例仅能被一个模块所拥有,这样才能避免双重释放。
- 在转移某个实例的所有权时,其必须从上一个所有者手中得到释放并为其分配一个新的所有者。这些操作的顺序无不具有重要性。
- 如果没有分配的所有者,永远不要遗留孤行或释放实例。
内部C++类的孤行 (无所有者关系)实例是调用下列函数的结果:
- 库中的所有clone()函数会返回新的克隆实例,这些克隆出的实例并不为任何模块所有。
- engine.world.loadNode() 加载一个孤行节点。
- 通过
- body.setObject()
- object.setBody()如果一个新的实体 被分配到一个对象上
- 构造函数内会指定一个物体对象,例如new BodyRigid(object)
- 通过
- body.addShape()
- shape.setBody()如果分配了一种新形状给实体
- 构造函数内会指定一个实体,例如new ShapeSphere(body,radius)
- 通过
- body.addJoint()
- joint.setBody0()或 joint.setBody1()如果一个新的结合点 被分配到一个 实体上
- 构造函数内会指定一个实体,例如new JointFixed(body0,body1)
脚本所有权
默认情况下,脚本内创建的内部C++类的实例(节点,网格,图像等)都为此脚本所有:
NodeDummy node_1 = new NodeDummy(); // the script owns node_1
NodeDummy node_2 = new NodeDummy(); // the script owns node_2
// 将node_2 作为node_1的子节点进行添加之后
// node_1 并不会成为 node_2的所有者
node_1.addChild(node_2);
// 仅node_1 会被删除, node_2 的层级将得到更新
delete node_1;
ObjectDummy object = new ObjectDummy(); // 脚本拥有此对象的所有权
BodyRigid body = new BodyRigid(object); // 脚本和对象都拥有实体的所有权
// 移除脚本对实体的所有权
class_remove(body);
// 物体对象及其实体都将被删除
delete object;
处理内部实例的所有权
使用下列系统函数,脚本可对内部C++类示例的所有权进行处理:
- class_append()将内部实例的所有权分配给当前脚本模块。所有附加的实例将在脚本关机中被自动删除或者可使用delete运算符将其删除。例如:
此时clone()会返回一个孤行的新节点。为了防止内存泄露的发生,脚本会承担所有权关系并以安全的方式将节点删除。
// 克隆一个现存节点。克隆的节点将被进行孤行处理 Node clone = node.clone(); // 为节点设置脚本所有权 class_append(clone); // 在有必要的情况下稍后删除克隆节点 delete clone;
- class_manage()表明应为内部实例执行引用计数。当指向实例的引用数达到0时,先前为实例分配的内存会被自动删除,这样开发者不必再对指向实例的生命期进行细致的管理。在调用此函数之前,应将内部实例附加在脚本上。
此时图像会自动为脚本所拥有,因为使用了new运算符创建出此图像。由于无用于图像的引用,因此此图像将被删除。
// 创建一副将被脚本自动拥有的图像 Image image = new Image(); // 为图像启用引用计数 class_manage(image); // 当无用于图像的引用时,图像将被删除 image = 0;
- class_release()会将所有引用移至内部C++类的实例中。此方法可移除最小的内存泄露。
// 创建将自动被脚本所拥有的新节点 NodeDummy node = new NodeDummy(); // 删除节点的引用 node = class_release(node);
- class_remove() 释放实例的所有权。需要将所有权重新分配给任意的模块(在发布所有权之前或之后进行操作)使所有权不会成为孤行状态。例如可将其传递给节点 层级中的引擎编辑器以便将来能实时调整。
Body body = class_remove(new BodyRigid(object)); // 被分配到的对象会自动对实体进行管理 ShapeSphere shape = class_remove(new ShapeSphere(body,radius)); // 被分配到的实体会自动对形状进行管理 JointFixed joint = class_remove(new JointFixed(body,body0)); // 被分配到的实体会自动对结合点进行管理
- class_cast()将用于所给类型的内部实例的指针转换成另一种类型 。
在转化为另一种类型时,并不会构建一种新的内部实例,因此无法使用delete运算符对其进行删除。指针转换这一操作并不安全且可对指定任意的转出类型,因为不会对指针以及指针类型进行检查。如果要通过delete运算符将上述实例中阐述的网格删除,会发生错误。然而如果删除此节点,此网格也同样会被删除。
// 获取一个节点 Node node = engine.world.getNode(id); // 将节点强制转换成ObjectMeshStatic类型 ObjectMeshStatic mesh = class_cast("ObjectMeshStatic",node); // 调用节点被强制转换成的类成员函数 string name = mesh.getMeshName();
处理分层节点的所有权
存在一组函数让您能以安全的方式对分层节点 及所有子节点进行处理。可在Unigine SDK的data/core/unigine.h文件中找到这些函数。
- Node node_append(Node node) 对所给节点及节点后代的脚本所有权进行注册。
此处,脚本会拥有my节点及其子节点(NodeDummy)的所有权。
// 加载一个节点。脚本会自动拥有此节点。 Node node = engine.world.loadNode("my.node"); // ... // 释放实例的脚本所有权 // 将一个孤行子节点添加到加载的节点上 node.addChild(class_remove(new NodeDummy())); // 为父节点及子节点设置脚本所有权 node_append(node);
专门创建示例中呈现的场景这样才能显示出node_append()为整个节点层设置脚本的所有权。因此在添加一个子节点到应用程序的某个节点上之前,不必调用class_remove()。 - Node node_remove(Node node)释放所给节点及子节点的脚本所有权并将这些节点强制转换为相应类型。例如使用此函数来设置为脚本所拥有的节点引擎编辑器所有权。
如果存在地话,记得为孤行节点设置另一个所有者。
// 创建自动被脚本所拥有的静态网格 ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh"); // 释放脚本所有权并将引擎编辑器设置为一个新的所有者 engine.editor.addNode(node_remove(mesh));
- void node_delete(Node node)删除父节点及子节点。在调用此函数之前,应为脚本附加此节点以此获得所有权。
此处删除节点及子节点。
// 加载节点。此节点会自动为脚本所拥有。 Node node = engine.world.loadNode("my.node"); // ... // 释放节点的脚本所有权 // 将孤行子节点添加到加载的节点上 node.addChild(class_remove(new NodeDummy())); // 为节点及子节点设置脚本所有权 node_append(node); // 在此处执行某些操作 // 删除节点及子节点 node_delete(node);
专门创建示例中呈现的场景这样才能显示出node_delete()删除了整个节点层。因此在应用程序内添加一个子节点之前不必调用class_remove()。 - Node node_clone(Node node)克隆此节点。因此创建一个新的内部C++类实例。如其它所有clone()函数,创建的实例会被进行孤行处理且其所有权会被传给某个模块。例如当节点为拥有实体和结合点的对象时,此函数就很有用。通常使用的clone() 函数并不能创建连接的结合点。
此处由于使用new运算符来创建第一幅网格对象,因此其会自动为脚本所拥有,而拷贝的网格对象应进行手动附加的操作。
// 创建一个自动为脚本所有的节点 ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh"); // 为克隆节点设置脚本所有权 node_append(node_clone(mesh));
用于非层节点的函数:
- Node node_cast(Node node) 让您可以安装安全的方式将所给的基础节点转换为派生类型节点。
在转化为另一种类型时,并不会构建一种新的内部实例,因此无法使用delete运算符对其进行删除。向下转换之后,节点可调用ObjectMeshStatic 类成员函数。 如果要通过delete运算符将上述实例中阐述的网格删除,会发生错误因为并无这样额内部实例。然而如果删除此节点,此网格也同样会被删除。
// 获取一个节点,此节点会自动为脚本所有 Node node = engine.world.getNode(id); // 将节点强制转换成ObjectMeshStatic类型 ObjectMeshStatic mesh = node_cast(node); //调用ObjectMeshStatic类成员函数 int num_targets = mesh.getNumSurfaceTargets(0);
在脚本之间传递所有权
内部实例的所有权可在脚本之间进行传递:世界对象脚本,系统脚本和编辑器脚本。对于将要交互的脚本,使用下列函数:
- engine.world.call()中的一个函数用来调用世界脚本的函数。
- engine.system.call() 中的一个函数用来调用系统脚本的函数。
- engine.editor.call()中的一个函数用来调用编辑器脚本的函数。
例如您可以在世界脚本中创建一个内部实例接下来将此实例的所有权传递给系统脚本:
- 在世界脚本中创建一个实例并释放此对象的世界脚本所有权,这样系统脚本才能抓取此对象的所有权。接下来通过engine.system.call()函数将此对象传递给系统脚本函数。
int init() { // 1. 创建一个新的虚拟对象。此虚拟对象所指向的内部实例将自动得以创建 ObjectDummy dummy = new ObjectDummy(); // 2. 释放此对象的世界脚本所有权 class_remove(dummy); // 3. 将此对象传递给系统脚本函数 engine.system.call("receive_dummy_ownership",dummy); return 1; }
- 在系统脚本函数中,为所接收的孤行实例设置系统脚本所有权。现在可使用delete运算符删除此实例;否则此实例将在系统脚本关机时被自动删除。
void receive_dummy_ownership(ObjectDummy dummy) { // 1. 设置在世界脚本中构建的虚拟对象系统脚本所有权 class_append(dummy); // 2. 系统脚本可删除虚拟对象指向的内部实例,或者此实例会在系统脚本关机时被删除 delete dummy; }
引擎编辑器所有权
如上所说,引擎编辑器拥有来自加载world文件的节点。在卸载世界时,会自动删除此节点,这样释放所分配的内存。
- engine.editor.addNode()建立节点的引擎编辑器所属关系。在此之前,此实例应释放其它所有者。
当脚本释放通过new运算符建立的网格的所有权之后,可使用引擎编辑器对其进行处理。 如果通过engine.editor.addNode()在不释放此脚本所有权节点的情况下, 添加一个通过new运算符创建的节点到引擎便器上,此引擎会在关闭时损坏:
// 创建自动被脚本所拥有的静态网格 ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh"); // 释放脚本所有权并将引擎编辑器设置为一个新的所有者 engine.editor.addNode(node_remove(mesh));
engine.editor.addNode(new ObjectDummy()); // 损坏将会发生因为脚本和引擎编辑器都拥有此对象 engine.editor.addNode(node_remove(new ObjectDummy())); // 这样的代码会正常工作
- engine.editor.removeNode()删除为引擎编辑器所有的节点 。
此处通过new运算符得以创建并为脚本所拥有的新网格被脚本所有权所释放。在此之后,此网格成为孤行状态,其所有权被传递给引擎编辑器,这样能以安全的方式将此网格删除。
// 创建自动被脚本所拥有的静态网格 ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh"); // 释放脚本所有权并将引擎编辑器设置为一个新的所有者 engine.editor.addNode(node_remove(mesh)); //在此处执行某些操作 // 删除此节点 engine.editor.removeNode(mesh);
- engine.editor.releaseNode()可以释放由引擎编辑器所持有的所有权,并将此所有权传给某个其它模块(例如脚本)。
此处节点的所有权来自脚本传递给引擎编辑器,反过来引擎编辑器会释放所有权。在此之后,节点得到释放可再一次为脚本所有。
// 创建自动被脚本所拥有的静态网格 ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh"); // 释放脚本所有权并将引擎编辑器设置为一个新的所有者 engine.editor.addNode(node_remove(mesh)); // 在此处执行某些操作 // 释放引擎编辑器的所有权 engine.editor.releaseNode(mesh); // 设置对象的脚本所有权 node_append(mesh);
C++ Ownership
可使用不同的变体来创建并处理内部实例的所有权:
- 使用脚本创建一个内部C++类的实例并将此实例进行传递,使用在C++上定义的函数进行接收:
- 创建一个智能指针用于C++API类的实例并将其传递给脚本:
作为智能指针接收实例
首选如下:使用世界对象脚本创建并处理的节点被传递给接收智能指针的C++函数。
- 在C++上创建一个自定义函数用来接收NodePtr智能指针。接下来随同Unigine解析器一同对此函数进行注册,这样脚本才能在运行期间调用此函数。
//main.cpp #include <UnigineEngine.h> #include <UnigineInterpreter.h> #include <UnigineInterface.h> /* */ using namespace Unigine; /* */ // 1. 创建一个用来接收智能指针NodePtr的外部函数 void my_node_set(NodePtr node) { // 1.1. 通过C++API调用节点成员函数 node->setTransform(translate(vec3(1.0f,2.0f,3.0f))); } /* */ int main(int argc,char **argv) { // 2. 将函数注册以便能导出到Unigine Interpreter::addExternFunction("my_node_set",MakeExternFunction(&my_node_set)); // 3. 将引擎初始化,会自动将引擎关闭 EnginePtr engine(UNIGINE_VERSION,argc,argv); // 4. 进入引擎主循环 engine->main(); return 0; }
- 在世界对象脚本张创建一个节点并调用C++函数。节点的所有权属于脚本,因此仅脚本能调用delete运算符来销毁此节点。
// 世界对象脚本 (myworld.cpp) int init() { // 1.创建一个新的虚拟对象,这样才能将此对象传递给外部C++函数 Node node = new NodeDummy(); // 2. 调用注册的C++函数 my_node_set(node); // 3. 在不需要此节点时删除此节点,否则在关闭脚本时会自动删除此节点 delete node; return 1; }
作为智能指针接收一个实例并获取其所有权
这种情况下,节点会被传递给一个C++函数,此函数接收此节点并将其作为一个智能指针并且此函数会获取此节点的所有权,这样函数才会对节点的删除负责。
- 在C++上创建一个自定义函数用来接收NodePtr智能指针并获取其所有权。接下来随同Unigine解析器一同对此函数进行注册,这样脚本才能在运行期间调用此函数。
#include <UnigineEngine.h> #include <UnigineInterpreter.h> #include <UnigineInterface.h> /* */ using namespace Unigine; /* */ // 1. 创建用来接收脚本节点并将此节点作为智能指针NodePtr的外部函数 void my_node_set(NodePtr node) { // 1.1. 获取此接收指针的所有权 node->grab(); // 1.2. 通过C++API调用节点成员函数 node->setTransform(translate(dvec3(1.0f,2.0f,3.0f))); // 1.3. 在有必要的情况下删除此节点 node->destroy(); } /* */ int main(int argc,char **argv) { // 2. 注册此函数以便能导入到Unigine中 Interpreter::addExternFunction("my_node_set",MakeExternFunction(&my_node_set)); // 3. 初始化引擎。此引擎会被自动关闭 EnginePtr engine(UNIGINE_VERSION,argc,argv); // 4. 进入引擎主循环 engine->main(); return 0; }
- 在世界对象脚本张创建一个节点,释放脚本所有权并调用注册的C++函数。节点的所有权属于外部函数,因此不必调用delete运算符来销毁此节点。
Script ownership must be released either on the script or C++ side. Otherwise, there will be 2 owners of the node and it will cause engine crash.
int init() { // 1. 创建一个新的虚拟对象,这样才能将此对象传递给C++函数 Node node = new NodeDummy(); // 2. 释放脚本所有权这样此所有权才能被C++函数获取 class_remove(node); // 3. 调用注册的C++函数 my_node_set(node); // 4. 检查函数执行结果 log.message("%s\n",typeinfo(node.getTransform())); return 1; }
接收某种特定类型的实例
此变体与第一种类型相似:世界对象坐标创建并处理的节点会被传递给C++函数,此函数接收此节点并将其作为某种特定类型的智能指针。此处的“某种特定类型”指C++API类,此类被智能指针所包住(例如ObjectMeshStaticPtr, DecalDefferedMeshPtr等等)。
- 在C++上创建一个自定义函数,用来接收ObjectMeshDynamicPtr智能指针。应对外部函数进行注册以供脚本调用。
//main.cpp #include <UnigineEngine.h> #include <UnigineInterpreter.h> #include <UnigineObjectMeshDynamic.h> #include <UnigineInterface.h> #include <UnigineLog.h> /* */ using namespace Unigine; /* */ // 1.0. 创建用于接收智能指针ObjectMeshDynamicPtr的外部函数 void my_object_update(ObjectMeshDynamicPtr object,float time) { // 1.1. 调用ObjectMeshDynamic的成员函数 object->updateSurfaceBegin(0); object->updateSurfaceEnd(0); Log::message("Surface indices was updated\n"); } /* */ int main(int argc,char **argv) { // 2. 将函数注册以便能导出到Unigine Interpreter::addExternFunction("my_object_update",MakeExternFunction(&my_object_update)); // 3. 初始化引擎。此引擎会被自动关闭 EnginePtr engine(UNIGINE_VERSION,argc,argv); // 4. 进入引擎主循环 engine->main(); return 0; }
- 在脚本上创建一个对象并将此对象传递给外部函数。此后在使用脚本调用时,注册的外部C++函数调用会对其进行接收。通过脚本所有权得以保留,因此脚本可删除此对象。
// 世界对象脚本(myworld.cpp) int init() { // 1. 创建一个新的ObjectMeshDynamic Object object = new ObjectMeshDynamic(); // 1.1. 调用成员函数 object.setMaterial("mesh_base","*"); // 3. 调用注册的外部C++函数 my_object_update(object,engine.game.getTime()); // 4. 脚本可删除此对象,因为脚本对此对象进行分配 delete object; return 1; }
作为变量接收实例
第三种变体便是在脚本中创建内部实例(例如图像)并将此实例传递给C++函数,此函数用来接收一个变量。其后使用脚本调用外部函数。
- 在C++上创建一个自定义函数用来就收一个变量。使用专用函数getImage()将此变量强制转换成ImagePtr智能指针类型。此后应将C++函数注册以便脚本能够进行调用。
在调用 getImage()函数时,应设置脚本运行时间:通过Unigine::Interpreter::get()函数将指针传递给当前解译器。
//main.cpp #include <UnigineEngine.h> #include <UnigineInterpreter.h> #include <UnigineImage.h> #include <UnigineInterface.h> /* */ using namespace Unigine; /* */ // 1. 创建接收变量的外部函数 const char* my_image_get(const Variable &v) { // 1.1. 将接收的变量强制转换为ImagePtr类型 ImagePtr image = v.getImage(Interpreter::get()); // 1.2. 通过C++ API调用图像成员函数 return image->getFormatName(); } /* */ int main(int argc,char **argv) { // 2. 将函数注册以便能导出到Unigine Interpreter::addExternFunction("my_image_get",MakeExternFunction(&my_image_get)); // 3. 初始化引擎。此引擎会被自动关闭 EnginePtr engine(UNIGINE_VERSION,argc,argv); // 4. 进入引擎主循环 engine->main(); return 0; }
使用VariableToType() 函数,还有另一种方法将此变量强制转换为图像指针类型
//main.cpp ... // 1. 相同的外部函数 const char* my_image_get(const Variable &v) { // 1.1. 将变量强制转换为ImagePtr类型的另一种方法: ImagePtr image = VariableToType<ImagePtr>(Interpreter::get(),v).value; return image->getFormatName(); } ...
- 在脚本内,需创建图像并将此图像传递给C++函数。图像将自动被转换为ImagePtr智能指针类型。
//世界对象脚本(myworld.cpp) int init() { // 1. 创建一副新图像 Image image = new Image(); // 1.1. 指定图像参数并使用黑色进行填充 image.create2D(256,256,IMAGE_FORMAT_R8); // 2. 调用注册的外部函数并将此图像传递给函数 my_image_get(image); // 3. 在有必要的情况下,脚本可以外显的方式删除此图像 delete image; return 1; }
创建并传递作为智能指针的实例
智能指针不仅能让C++函数接收由脚本创建的内部C++类的实例还可以创建这样的实例。
此处脚本调用一种外部函数,使用ImagePtr创建一副新的图像。
- 在外部C++函数中,阐明ImagePtr,调用API函数的create()并将此指针传递给脚本。然而如果仅进行了指针的传递,此指针会一直处于悬挂摇摆状态,即其不会指向有效图像对象因为此对象在外部函数范围外不可见。为避免这种情况,应通过release() 函数将此指针的所有权传递给脚本。接着将自定义的函数注册以供脚本调用。
//main.cpp #include <UnigineEngine.h> #include <UnigineInterpreter.h> #include <UnigineImage.h> #include <UnigineInterface.h> /* */ using namespace Unigine; /* */ // 1. 创建会返回ImagePtr智能指针的外部函数 ImagePtr my_image_create_0() { // 1.1. 声明一个图像智能指针 ImagePtr image; // 1.2. 通过ImagePtr智能指针在Unigine内存池中创建一副图像 image = Image::create(); // 1.3. 指定图像参数并使用黑色进行填充 image->create2D(128,128,Image::FORMAT_RG8); // 1.4. 将指针的所有权传递给脚本 image->release(); return image; } /* */ int main(int argc,char **argv) { // 2. 将函数注册以便能导出到Unigine Interpreter::addExternFunction("my_image_create_0",MakeExternFunction(&my_image_create_0)); // 3. 初始化引擎。此引擎会被自动关闭 EnginePtr engine(UNIGINE_VERSION,argc,argv); // 4. 进入引擎主循环 engine->main(); return 0; }
- 调用注册的C++函数。此函数返回的图像可被简单地传递给脚本,因为将自动完成将ImagePtr转换成Image的过程。由于图像的所有权已从外部函数传递给脚本,因此可随时使用脚本删除此图像(或者在引擎关闭时自动删除此图像)。
//world script (myworld.cpp) int init() { // 1. 调用脚本中的外部函数 Image image = my_image_create_0(); // 2. 为图像设置脚本的所有权 class_append(image); // 3. 脚本会自动处理由C++函数作为一副简单图像返回的ImagePtr log.message("%s\n",image.getFormatName()); // 4. 在有必要的情况下删除此图像 delete image; return 1; }
创建并传递作为变量的实例
在此变体中,外部函数创建一副新的图像作为一个变量接下来脚本会调用此外部函数。
- 在C++函数内,声明一个ImagePtr,调用API函数的create() 来创建一个智能指针,此指针会分配一副Unigine内部图像并使用专用函数setImage()将此图像设置到变量中。
在调用setImage()函数时,应设置脚本运行时间,通过Unigine::Interpreter::get() 函数将此指针传递给当前解译器。 。
//main.cpp #include <UnigineEngine.h> #include <UnigineInterpreter.h> #include <UnigineImage.h> #include <UnigineInterface.h> /* */ using namespace Unigine; /* */ // 1. 创建一个返回变量的外部函数 Variable my_image_create_1() { // 1.1. Declare an image smart pointer ImagePtr image; // 1.2. 在Unigine内存池中创建一副图像 image = Image::create(); // 1.3. 指定图像参数并使用黑色进行填充 image->create2D(128,128,Image::FORMAT_RG8); // 1.4. 定义变量 Variable v; // 1.5. 将图像智能指针设置到变量上 v.setImage(Interpreter::get(),image); return v; } /* */ int main(int argc,char **argv) { // 2. 注册此函数以便能导入到Unigine中 Interpreter::addExternFunction("my_image_create_1",MakeExternFunction(&my_image_create_1)); // 3. 初始化引擎。此引擎会被自动关闭 EnginePtr engine(UNIGINE_VERSION,argc,argv); // 4. 进入引擎主循环 engine->main(); return 0; }
另一种将ImagePtr智能指针设置到变量上的方法会使用到 TypeToVariable函数:
//main.cpp ... // 1. 相同的外部函数 Variable my_image_create_1() { image = Image::create(); image->create2D(128,128,Image::FORMAT_RG8); // 1.3. 将图像智能指针设置到变量上的另一种方法: Variable v = TypeToVariable<ImagePtr>(Interpreter::get(),image).value; return v; } ...
- 脚本调用注册的C++函数。此后由于自动完成了转换过程,因此脚本会将返回的变量作为简单图像进行处理。记住脚本无法删除图像,因为图像的所有权属于外部函数。当参考计数达到0时,智能指针会自动得到释放。
//world script (myworld.cpp) int init() { // 1. 调用脚本中的外部函数 Image image = my_image_create_1(); // 2. 脚本会自动处理作为简单图像而得以返回的ImagePrt log.message("%s\n",image.getFormatName()); return 1; }
对来自C++方面的脚本所有权进行管理
如果在脚本中已创建一个实例并将其作为变量传递给C++函数,可通用下列Unigine::Variable类中的方法对脚本所有权进行处理:
- class_append()
- class_manage()
- class_remove()
- class_release()
这些函数会接收到一个用于实际情况中的指针,在此指针的帮助下您可以对所需脚本的所有权进行正确的设置。为了获取正确的配置命令,使用下列函数:
- Unigine::Engine::getWorldInterpreter() 管理指针的世界对象脚本所有权
- Unigine::Engine::getSystemInterpreter() 管理指针的系统脚本所有权
- Unigine::Engine::getEditorInterpreter() 管理指针的编辑器脚本所有权
在执行介于外部类的变量和脚本标量之间的转换时,需要试着配置命令。
例如:
- 在C++上创建自定义函数,这样会接收到一个变量。将接收到的变量的脚本所有权进行释放。
#include <UnigineEngine.h> #include <UnigineInterpreter.h> #include <UnigineImage.h> #include <UnigineInterface.h> /* */ using namespace Unigine; /* */ // 1. 创建接收变量的外部函数 const char* change_owner(const Variable &v) { // 1.1 释放接收到的变量的脚本所有权 Engine *engine = Engine::get(); v.removeExternClass(engine->getWorldInterpreter()); // 应设置世界对象脚本配置命令,因为removeExternClass()需要脚本环境 // 1.2. 将接收到的变量转换为ImagePtr类型 ImagePtr image = v.getImage(Interpreter::get()); // 1.3. 为图像设置 C++的所有权 image->grab(); // 1.4.通过C++API调用图像成员函数 return image->getFormatName(); } /* */ int main(int argc,char **argv) { // 2. 注册此函数以便能导入到Unigine中 Interpreter::addExternFunction("change_owner",MakeExternFunction(&change_owner)); // 3. 初始化引擎。此引擎会被自动关闭 EnginePtr engine(UNIGINE_VERSION,argc,argv); // 4. 进入引擎主循环 engine->main(); return 0; }
- 在脚本上,创建一副图像并将此图像传递给外部C++函数。由于在外部函数中释放此脚本的所有权,因此此处没必要删除图像。
int init() { // 1. 创建一副新图像 Image image = new Image(); // 1.1. 指定图像参数并使用黑色进行填充 image.create2D(256,256,IMAGE_FORMAT_R8); // 2. 调用注册的外部函数并将此图像传递给函数 log.message("%s\n",change_owner(image)); }