This page has been translated automatically.
Programming
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
Bounds-Related Classes
Containers
Controls-Related Classes
Core Library
Engine-Related Classes
GUI-Related Classes
Node-Related Classes
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
Rendering-Related Classes
Utility Classes
注意! 这个版本的文档是过时的,因为它描述了一个较老的SDK版本!请切换到最新SDK版本的文档。
注意! 这个版本的文档描述了一个不再受支持的旧SDK版本!请升级到最新的SDK版本。

Memory Management(内存管理)

本文详述了对象管理的内容,以及Unigine脚本,C++外部类,Unigine引擎的C++部分(C++内部类和引擎编辑器(Engine Editor))这三部分之间的信息往来。

See Also

Unigine Ownership System(Unigine所有权系统)

Unigine引擎拥有经过优化的专属内存分配器,可实现更快更高效的内存管理。 它作用于一种特殊的预分配内存池中,所有Unigine模块共享该内存池。 自定义的C++模块可通过C++外部类扩展Unigine的功能,这些C++模块会调用一种由操作系统提供的底层分配器,该分配器拥有独立的内存池。

分配内存,追踪内存的分配以及在不需要内存时释放内存块是对象管理的主要任务。 Unigine由多个模块组成(包括上面提到的C++模块),每个模块又都可以建立对象的所有权,也就是说,对象的所有权是当不再需要对象的情况下用于解除内存的分配的。 通过这种组织形式,所有这些模块便都可以访问对象了。 所有权也能被释放(release),以及传递给其它模块来接管,还可在恰当的时机用于删除多余的对象。 如果不能将解除对象分配的所有者(owner)设置成一名,那离内存泄露乃至应用崩溃也就不远了。

这里所指的对象都是C++内部类的实例。 下面的示意图通过Node(节点)的例子展示了Unigine的所有权系统。 图中对引擎编辑器(Engine Editor)图块做了*标记,目的是为了标示在当前时刻节点(C++内部类的实例)归该模块所有。 请注意,这幅示意图只是作为例子来展示,在实际使用时任一模块都可以成为内部实例的所有者,也都能存储指向该实例的指针。

通过Node(节点)例子展示的Unigine所有权系统

Internal C++ classes(C++内部类)。 这些类的实例属于低级别的内置对象,当有某个脚本或C++模块对这些实例进行声明时,实际上它们会在C++一侧被创建;比如说这样的实例就有:node(节点)(如例子中所示),mesh(网格),body(实体),image(图像)等等。 这类Internal Instances(内部实例)可以为以下任一模块所拥有:

  • Scripts(脚本)。 包括有:
    • World script(世界脚本),包含world(世界)逻辑。
    • Editor script(编辑器脚本),负责封装编辑器的功能及其GUI。
    • System script(系统脚本),负责系统的内部管理以及控制系统菜单的GUI。
    这些脚本由UnigineScript语言编写,且可以使用它们来创建用户类(user classes)。
    注意
    内部实例无论归谁所有,我们都可以通过任一脚本(世界,系统或编辑器脚本)来对其进行处理。 只有当对它进行【删除操作】时才涉及所有权问题。
  • Engine Editor(引擎编辑器)。 它属于Unigine引擎的C++部分。 当初始化虚拟世界时,引擎编辑器会加载world文件中的节点,同时还会创建一个指向这些节点的内部实例的指针数组。 引擎编辑器的节点也以层级列表的形式出现在Nodes(节点)面板上,并且这些节点还能在虚拟世界中被实时调节。 默认情况下,所有这些节点都归引擎编辑器所有。 此外,每个节点的所有权也都可以从任一类型脚本或某一C++模块传递给引擎编辑器。
  • API C++ classes(C++ API类)属于封装类,用于隐藏C++内部类的实现以及存储指向这些内部类的实例(也就是内部实例)的指针。 C++ API类在如下情况中可成为内部实例的所有者:
    • 这些实例是在用户应用的C++一侧通过create()函数被创建的。
    • 这类实例的所有权是通过C++ API类的grab()函数手动设置的。
    想要释放所有权,须使用release()函数。

    由于C++ API类的实例只能存储指向C++内部类的实例的指针,因此不能使用标准的new / delete运算符来创建和删除它们。 而是应当将它们声明为Smart Pointers(智能指针)(Unigine::Ptr),以便您能对这些C++ API类的实例的生命周期实现自动管理。 如果您删除了某个智能指针,那它所对应的C++ API类的实例也将会被自动删除。 不仅如此,假如所对应实例的所有权是通过grab()函数设置的,那该实例的删除还会致使相应的C++内部对象也被删除。

还有一模块称之为Engine World(引擎世界),也就是Unigine引擎的C++部分(注意不要与world(世界)脚本搞混!)。 所有通过new运算符或create()函数创建的实例都会被自动添加到引擎世界。 引擎世界负责加载虚拟世界连同该世界的所有节点,负责管理空间树以及处理节点间的碰撞和相交。 不过,它不会取得内部实例的所有权。

Managing Ownership(所有权的管理)

管理内部实例的所有权的基本原则如下:

  • 无论是哪个模块创建了某一C++内部类的实例,其都将自动成为该实例的所有者。 此后,拥有内部实例的该模块便可对此实例进行删除操作。 例如:
    • 如果您是在应用的脚本一侧通过new运算符创建的某一实例,那该类型脚本将成为此实例的所有者,这种情况下您可通过delete运算符来删除该实例。
    • 如果您是在C++一侧通过create()函数创建的某一实例,那此C++模块将取得该实例的所有权,这种情况下您可通过destroy()函数来删除该实例。
  • 某一C++内部类的一个实例应当只归一个模块所有,这样可避免对内存做双重释放(double-freeing)。
  • 当传递某一实例的所有权时,必须将其从上一个所有者处释放(release),并为其分配新的所有者。 这些操作的先后顺序无关紧要。
  • 绝不能只产生孤立(Orphan)实例或已释放实例而不为它们分配所有者。

C++内部类中出现Orphan(孤立)(也就是没有所有者的)实例的情况皆是因调用如下函数所致:

  • 库中的所有clone()函数都返回了不归任何模块所有的新克隆实例。
  • engine.world.loadNode()函数加载了某个孤立节点。

注意
下述情况中所有权会被自动重新分配。 这也是为什么会要求使用class_remove()函数来对已创建的实体(bodies),形状(shapes)或连接器(joints)进行脚本所有权释放操作的原因。

Script Ownership(脚本的所有权)

默认情况下,在某一脚本中创建的C++内部类的任何实例(node(节点),mesh(网格),image(图像)等等)都归该脚本所有:

源代码(UnigineScript)
NodeDummy node_1 = new NodeDummy(); // 节点node_1归脚本所有
NodeDummy node_2 = new NodeDummy(); // 节点node_2归脚本所有

// 在将节点node_2作为子节点添加给节点node_1之后
// 节点node_1不会成为节点node_2的所有者
node_1.addChild(node_2);

// 只有节点node_1会被删除,节点node_2的层级会被更新
delete node_1;
不过,如果您将一实体(body)分配给了某一对象(object),并且还调用了该实体的class_remove()函数(如上文所述),那这个实体将归此对象所有。 也因如此,在删除对象时,实体也会被删除。 这种情况也同样适用实体(bodies)与形状(shapes),实体(bodies)与连接器(joints)的组合。
源代码(UnigineScript)
ObjectDummy object = new ObjectDummy(); // 对象归脚本所有
BodyRigid body = new BodyRigid(object); // 实体(body)归脚本和对象共同所有

// 移除实体的(body)的脚本所有权
class_remove(body);

// 对象(object)及其实体(body)都会被删除
delete object;

Handling Ownership of Internal Instances(处理内部实例的所有权)

脚本可通过如下系统函数来处理C++内部类的实例的所有权:

  • class_append()函数用于将内部实例的所有权分配给当前脚本模块。 所有附加的实例都会在脚本关停时被自动删除;或者它们也可以由delete运算符来删除。 例如:
    源代码(UnigineScript)
    // 克隆已有节点。 克隆所得节点将成为孤立节点
    Node clone = node.clone();
    // 设置克隆所得节点的脚本所有权
    class_append(clone);
    // 如有所需,可在之后删除克隆所得节点
    delete clone;
    在本例子中,clone()函数会返回新节点,也就是孤立节点。 而为了防止内存泄漏,脚本须取得该节点的所有权并对其进行安全删除。
  • class_manage() 函数表示要为内部实例执行Reference Counting(引用计数)功能。 当指向实例的引用数量达到0时,先前分配给该实例的内存就会被自动删除,这样一来也就避免了开发者需谨慎管理所指向实例的生命周期问题。 在调用该函数之前,内部实例应当先附加给脚本。
    源代码(UnigineScript)
    // 创建一图像(image)实例,该实例会自动归脚本所有
    Image image = new Image();
    // 启用该图像(image)实例的引用计数功能
    class_manage(image);
    // 当该图像(image)实例所剩引用数量为0时,实例本身将被删除
    image = 0;
    在本例中,图像(image)实例会自动归脚本所有,原因在于它是通过new运算符创建的。 该实例之所以会被删除,是因为它已经没有了引用。
  • class_release()函数用于移除所有C++内部类的实例的引用。 它的作用就是杜绝微乎其微的内存泄漏的可能性。
    源代码(UnigineScript)
    // 创建一新节点,该节点会自动归脚本所有
    NodeDummy node = new NodeDummy();
    // 删除该节点的引用
    node = class_release(node);
  • class_remove()函数用于释放实例的所有权。 为了避免出现孤立实例,所有权需重新分配给任意模块(既可在释放前分配,也可在释放后分配)。 例如,所有权可传递给位于Nodes(节点)层级之中,并能被实时调节的Engine Editor(引擎编辑器)。 源代码 (UnigineScript)
    源代码(UnigineScript)
    Body body = class_remove(new BodyRigid(object)); // 实体(bodies)会自动归它们被分配给的对象(objects)来管理
    ShapeSphere shape = class_remove(new ShapeSphere(body,radius)); // 形状(shapes)会自动归它们被分配给的实体(bodies)来管理
    JointFixed joint = class_remove(new JointFixed(body,body0)); // 连接器(joints)会自动归它们被分配给的实体(bodies)来管理
  • class_cast()函数用于将指向给定类型的内部实例的指针转换为其它类型。
    注意
    指针的转换不但危险,还允许指定成任意类型,其原因就在于无论是指针还是其类型都不能被检查。
    在进行类型转换时是不构造新的内部实例的,因此您也就不能使用delete运算符来执行删实例的操作了。
    源代码(UnigineScript)
    // 获取某一节点
    Node node = engine.world.getNode(id);
    // 将该节点强制转换为ObjectMeshStatic类型
    ObjectMeshStatic mesh = class_cast("ObjectMeshStatic",node);
    // 调用该节点被强制转换成的那个类的成员函数
    string name = mesh.getMeshName();
    如果您试图通过delete运算符来删除上例中声明的网格(mesh)实例,那将得到一条错误信息。 不过,如果您删除了节点,那网格也会被删除。

Handling Ownership of Hierarchical Nodes(处理层级节点的所有权)

引擎也提供有一组函数供您安全处理层级节点(Hierarchical Nodes)连同它们的所有子节点。 您可以在Unigine SDK存储目录下的data/core/unigine.h文件中找到这些函数。

  • Node node_append(Node node) 函数用于注册给定节点及其子节点的脚本所有权。
    源代码(UnigineScript)
    // 加载某一节点。 该节点会自动归脚本所有
    Node node = engine.world.loadNode("my.node");
    
    // ...
    // 释放该实例的脚本所有权
    
    // 将孤立子节点添加给已加载节点
    node.addChild(class_remove(new NodeDummy()));
    // 为父节点和子节点都设置脚本所有权
    node_append(node);
    在这里,脚本取得了my节点及其子节点(NodeDummy)的所有权。
    注意
    上述例子中给出的情形是故意创建的,目的是为了向您展示如何使用node_append()函数来为整个节点层级设置脚本所有权。 因此,举例来说,在您为自己应用中的某一节点添加子节点之前是无需调用class_remove()函数的。
  • 函数用于释放给定节点及其子节点的脚本所有权,同时对它们执行强制类型转换。 该函数应当被用于诸如设置归脚本所有的节点的引擎编辑器所有权。
    注意
    不要忘记为孤立节点(如果存在的话)设置其它的所有者。
    源代码(UnigineScript)
    // 创建一静态网格(static mesh),该网格会自动归脚本所有
    ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh");
    // 释放脚本所有权,并将引擎编辑器(Engine Editor )设置为新所有者
    engine.editor.addNode(node_remove(mesh));
  • void node_delete(Node node)函数用于删除父节点及其子节点。 在调用该函数之前,节点应当先附加给脚本来以此取得所有权。
    源代码(UnigineScript)
    // 加载某一节点。 该节点会自动归脚本所有
    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()函数一样,它所创建的实例也是孤立的,且该实例的所有权也是需要传递给某个模块的。 例如,该函数很适合创建带有实体(body)和连接器(joints)的节点对象。 不过一般的clone()函数不用来重新创建已连接的连接器(joints)。
    源代码(UnigineScript)
    // 创建一节点,该节点会自动归脚本所有
    ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh");
    // 为克隆所得节点设置脚本所有权
    node_append(node_clone(mesh));
    在本例中,第一个网格(mesh)对象会自动归脚本所有,原因在于它是通过new运算符创建的,而复制所得对象则应当被手动附加。

    引擎中用于非层级(non-hierarchical)节点的函数包括有:

  • Node node_cast(Node node) 函数允许您将给定的基本节点(base node)安全地转换为它的派生类型。
    注意
    在进行类型转换时是不构造新的内部实例的,因此您也就不能使用delete运算符来执行删实例的操作了。
    源代码(UnigineScript)
    // 获取某一节点,该节点会自动归脚本所有
    Node node = engine.world.getNode(id);
    // 将该节点强制转换为ObjectMeshStatic类型
    ObjectMeshStatic mesh = node_cast(node);
    // 调用ObjectMeshStatic类的成员函数
    int num_targets = mesh.getNumSurfaceTargets(0);
    在Downcasting(向下继承)的操作完成后,节点便可调用ObjectMeshStatic类的成员函数。 如果您试图通过delete运算符来删除上例中声明的网格(mesh)实例,那将得到一条错误信息,其原因就是根本不存在这样一个内部实例。 不过,如果您删除了节点,那网格也会被删除。

Pass Ownership Between Scripts(在不同的脚本间传递所有权)

内部实例的所有权可以在不同的脚本间传递:World(世界)脚本,System(系统)脚本,Editor(编辑器)脚本。 您可通过如下函数来进行脚本的交互:

例如,您可以在World(世界)脚本中创建某一内部实例,之后将它的所有权传递给System(系统)脚本:

  1. 在World(世界)脚本中创建一实例, 并释放该对象的World(世界)脚本所有权,其目的是可以让System(系统)脚本抓取其所有权。 之后再通过engine.system.call()函数将该对象传递给System(系统)脚本的函数。
    源代码(UnigineScript)
    int init() {
    	
    	// 1. 创建一新的伪对象(dummy object)。 该伪对象所指向的内部实例将会被自动创建
    	ObjectDummy dummy = new ObjectDummy();
    	// 2. 释放该对象的World(世界)脚本所有权
    	class_remove(dummy);
    	// 3. 将该对象传递给System(系统)脚本的函数
    	engine.system.call("receive_dummy_ownership",dummy);
    	
    	return 1;
    }
  2. 在System(系统)脚本的函数中设置所接收孤立实例的System(系统)脚本所有权。 现在该实例就可通过delete运算符来删除了;而至于其它的情况,实例会在System(系统)脚本关停时被自动删除。
    源代码(UnigineScript)
    void receive_dummy_ownership(ObjectDummy dummy) {
    	// 1. 为在World(世界)脚本中构造的伪对象设置System(系统)脚本所有权
    	class_append(dummy);
    	
    	// 2. System(系统)脚本可以删除伪对象所指向的内部实例。 或者该实例会在System(系统)脚本关停时被自动删除 
    	delete dummy;
    }

Engine Editor Ownership(引擎编辑器的所有权)

正如前面所说,从world文件中加载的节点归Engine Editor(引擎编辑器)所有。 当虚拟世界被卸载时,节点也会被自动删除,因而也就释放了所分配的内存。

  • engine.editor.addNode()函数用于建立节点的引擎编辑器所有权。 在此之前,实例应当先被其它所有者释放。
    源代码(UnigineScript)
    // 创建一静态网格(static mesh),该网格会自动归脚本所有
    ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh");
    // 释放脚本所有权,并将引擎编辑器(Engine Editor)设置为新所有者
    engine.editor.addNode(node_remove(mesh));
    待脚本释放了通过new运算符所建立的网格(mesh)所有权之后,该网格就可以由引擎编辑器来处理了。 如果您通过engine.editor.addNode()函数将由new运算符创建的节点添加给了引擎编辑器,但是并没释放该节点的脚本所有权,那么引擎就会在其自身关停时发生崩溃:
    源代码(UnigineScript)
    engine.editor.addNode(new ObjectDummy()); // 脚本和引擎编辑器同时拥有该对象时就会发生崩溃
    engine.editor.addNode(node_remove(new ObjectDummy())); // 这样处理代码才正确
  • engine.editor.removeNode()函数用于删除归引擎编辑器所有的节点。
    源代码(UnigineScript)
    // 创建一静态网格(static mesh),该网格会自动归脚本所有
    ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh");
    // 释放脚本所有权,并将引擎编辑器(Engine Editor)设置为新所有者
    engine.editor.addNode(node_remove(mesh));
    
    // 在此处实现一些功能
    
    // 删除该节点
    engine.editor.removeNode(mesh);
    在本例中,新建的网格(mesh)由new运算符创建,归脚本所有,然后被释放了脚本所有权。 此后,该节点成为了孤立节点,并且其所有权也被传递给了引擎编辑器,最后引擎编辑器对该节点进行了安全删除。
  • engine.editor.releaseNode()函数用于释放归引擎编辑器所有的节点的所有权,同时将其传递给某个其它模块(例如,脚本)。
    源代码(UnigineScript)
    // 创建一静态网格(static mesh),该网格会自动归脚本所有
    ObjectMeshStatic mesh = new ObjectMeshStatic("samples/common/meshes/statue.mesh");
    // 释放脚本所有权,并将引擎编辑器(Engine Editor)设置为新所有者
    engine.editor.addNode(node_remove(mesh));
    
    // 在此处实现一些功能
    
    // 释放引擎编辑器所有权
    engine.editor.releaseNode(mesh);
    // 设置对象的脚本所有权
    node_append(mesh);
    在本例中,节点的所有权先是被从脚本传递给了引擎编辑器,之后引擎编辑器又反过来做了一次所有权的释放。 此后,节点得以被释放,其所有权最终再次归脚本所有。

C++ Ownership(C++的所有权)

引擎中有多种不同的方法可以用来创建和处理内部实例的所有权:

  1. 通过脚本创建C++内部类的实例,并将其传递给在C++一侧定义的函数,由该函数接收实例:
  2. 创建指向C++ API类的实例的智能指针,并将其传递给脚本:

Receive Instance as Smart Pointer(将实例作为智能指针来接收)

第一个可供选用的方法:由World(世界)脚本创建和处理的节点被传递给用来接收Smart Pointer(智能指针)的C++函数。

  1. 在C++一侧创建自定义函数,该函数用来接收NodePtr智能指针。 之后需要使用Unigine Interpreter(Unigine解释器)来注册函数,为的就是使脚本可以在其自己的运行时中调用此自定义函数。
    源代码(C++)
    //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;
    }
  2. 在World(世界)脚本中创建节点,并调用已注册的C++函数。 节点的所有权将属于此脚本,因此也只有脚本可以调用delete运算符来销毁节点。
    源代码(UnigineScript)
    // world script (myworld.cpp)
    
    int init() {
    	// 1. 创建一新的伪节点(dummy node),以便可以将该节点传递给C++外部函数
    	Node node = new NodeDummy();
    	// 2. 调用已注册的C++函数
    	my_node_set(node);
    	// 3. 如果不再需要该节点,可以将其删除;否则,该节点会在脚本关停时被自动删除
    	delete node;
    
    	return 1;
    }

Receive Instance as Smart Pointer and Grab Its Ownership(将实例作为智能指针来接收,并抓取所接收指针的所有权)

对于这种情况,节点会被传递给C++函数,该函数用来将节点作为智能指针来接收,它也用来抓取该节点的所有权,为的就是让此函数来负责节点的删除。

  1. 在C++一侧创建自定义函数,该函数用来接收NodePtr智能指针以及抓取所接收指针的所有权。 之后需要使用Unigine Interpreter(Unigine解释器)来注册函数,为的是使脚本可以在其自己的运行时中调用此自定义函数。
    源代码(C++)
    #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;
    }
  2. 在World(世界)脚本中创建节点,释放其脚本所有权,并调用已注册的C++函数。 节点的所有权将属于外部函数,因此您无需调用delete运算符来销毁节点。
    注意
    脚本所有权必须在脚本一侧或是C++一侧被释放。 否则,节点就会拥有2个所有者,这将会导致引擎崩溃。
    源代码(UnigineScript)
    int init() {
    		
    	// 1. 创建一新的伪节点(dummy node),以便可以将该节点传递给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;
    }

Receive Instance of Specific Type(接收指定类型的实例)

该种方法与第一种相似:由World(世界)脚本创建和处理的节点被传递给用来将其作为指定类型的Smart Pointer(智能指针)来接收的C++函数。 这里的"指定类型"指的是用智能指针封装的C++ API类(例如, ObjectMeshStaticPtr, DecalDefferedMeshPtr等等)。

  1. 在C++一侧创建自定义函数,该函数用来接收某一种ObjectMeshDynamicPtr智能指针。 而外部函数则应当被注册成由脚本调用。
    源代码(C++)
    //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;
    }
  2. 在脚本一侧创建对象,并将其传递给外部函数。 此后,当脚本调用已注册的C++外部函数时,该函数就能接收此对象。 不过所有权仍然属于脚本,因此脚本就可以删除对象。
    源代码(UnigineScript)
    // world script (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. 脚本可以删除该对象,因为对象是脚本分配(allocate)的
    	delete object;
    
    	return 1;
    }

Receive Instance as Variable(将实例作为变量来接收)

第三种方法是在脚本中创建内部实例(例如,图像image),并将其传递给用来接收【变量】的C++函数。 此后,外部函数由脚本调用。

  1. 在C++一侧创建自定义函数,该函数用来接收变量。 通过专用函数getImage()将变量强制转换为ImagePtr智能指针类型。 此后,C++函数应当被注册成由脚本调用。
    注意
    您应当在调用getImage()函数时设置script runtime:通过Unigine::Interpreter::get()函数将指针传递给当前解释器。
    源代码(C++)
    //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调用暴露在外的图像(image)实例的成员函数
    	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;
    }

    引擎中也提供有另一种方法将变量强制转换为image指针类型,那就是使用VariableToType()函数:

    源代码(C++)
    //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();
    }
    ...
  2. 在脚本一侧创建图像(image)实例,并只简单地将其传递给C++函数即可。 该实例会被自动转换为ImagePtr智能指针。
    源代码(UnigineScript)
    // World(世界)脚本 (myworld.cpp)
    int init() {
    
    	// 1. 创建一新的图像(image)实例
    	Image image = new Image();
    	// 1.1. 指定该图像(image)实例的参数,并为其填充黑色
    	image.create2D(256,256,IMAGE_FORMAT_R8);
    	
    	// 2. 调用已注册的外部函数,并只简单地将图像(image)实例传递给该函数即可
    	my_image_get(image);
    	
    	// 3. 如有所需,可直接使用脚本来删除该图像(image)实例
    	delete image;
    	
    	return 1;
    }

Create and Pass Instance as Smart Pointer(将实例作为智能指针来创建和传递)

智能指针不仅允许C++函数接收脚本所创建的C++内部类的实例,还允许C++函数来创建这种实例。

注意
您不能将C++ API类的实例作为Row Pointer(行指针)来创建和传递,原因就是C++ API类的实例不能使用标准的new运算符和delete运算符来创建或删除:这种实例必须且只能通过智能指针来声明。

在这里,脚本会通过智能指针ImagePtr来调用用来创建新的图像(image)实例的的外部函数。

  1. 在C++外部函数中声明智能指针ImagePtr,调用API函数create()并将指针传递给脚本。 不过,如果您只是对指针做了简单传递,那它将成为Dangling Pointer(悬垂指针),也就是说,该指针不会指向有效的图像(image)对象,其原因就是在外部函数的作用域之外指针不可见。 为了避免这种情况的出现,您应当通过release()函数将指针的所有权传递给脚本。 之后,自定义函数将被注册成在脚本一侧调用。
    源代码(C++)
    //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. 声明一image智能指针
    	ImagePtr image;
    	// 1.2. 通过ImagePtr智能指针在Unigine的内存池中创建一图像(image)实例
    	image = Image::create();
    	// 1.3. 指定该图像(image)实例的参数,并为其填充黑色
    	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;
    }
  2. 调用已注册的C++函数。 该函数返回的图像(image)实例可以只简单地传递给脚本,原因就是ImagePtr智能指针到Image(图像)实例的转换会被自动完成。 由于图像(image)实例的所有权已被从外部函数传递给了脚本,所以可以在任何时间使用脚本来删除该实例(或是等引擎关停时自动删除该实例)。
    源代码(UnigineScript)
    // World(世界)脚本 (myworld.cpp)
    int init() {
    	
    	// 1. 在脚本中调用外部函数
    	Image image = my_image_create_0();
    	// 2. 为图像(image)实例设置脚本所有权
    	class_append(image);
    	
    	// 3. 脚本会自动将C++函数返回的ImagePtr智能指针处理为简单的图像(image)实例
    	log.message("%s\n",image.getFormatName());
    
    	// 4. 如有所需,可删除该图像(image)实例
    	delete image;
    	
    	return 1;
    }

Create and Pass Instance as Variable(将实例作为变量来创建和传递)

在此方法中,外部函数会将新的图像(image)实例作为【变量】来创建,之后脚本会调用此外部函数。

  1. 在C++函数中声明ImagePtr智能指针,调用API函数create()来创建智能指针,该指针会分配(allocate)一个内部的Unigine图像(image)实例,并通过专用函数setImage()将其设置给变量。
    注意
    您应当在调用setImage()函数时设置脚本运行时 :通过Unigine::Interpreter::get()函数将指针传递给当前解释器。
    源代码(C++)
    //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. 声明一image智能指针
    	ImagePtr image;
    	
    	// 1.2. 在Unigine的内存池中创建一图像(image)实例
    	image = Image::create();
    	// 1.3. 指定该图像(image)实例的参数,并为其填充黑色
    	image->create2D(128,128,Image::FORMAT_RG8);
    	
    	// 1.4. 定义一变量
    	Variable v;
    	// 1.5. 将image智能指针设置给该变量
    	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函数:

    源代码(C++)
    //main.cpp
    ...
    // 1. The same external function
    Variable my_image_create_1() {
    	
    	image = Image::create();
    	image->create2D(128,128,Image::FORMAT_RG8);
    
    	// 1.3. Another way of setting the image smart pointer to the variable:
    	Variable v = TypeToVariable<ImagePtr>(Interpreter::get(),image).value;
    	
    	return v;
    }
    ...
  2. A script calls the registered C++ function. After that, it handles the returned variable as a simple image, because conversion is done automatically. Remember, the script cannot delete the image, as the ownership belongs to the external function. The smart pointer will be automatically released when the reference count reaches zero.
    源代码(UnigineScript)
    //world script (myworld.cpp)
    int init() {
    	
    	// 1. Call the external function in the script
    	Image image = my_image_create_1();
    	
    	// 2. The script automatically handles the returned ImagePrt as a simple image
    	log.message("%s\n",image.getFormatName());
    	
    	return 1;
    }

Manage Script Ownership from C++ Side

If an instance has been created in the script and then passed as a variable to the C++ function, you can handle script ownership from this function by using the following methods of the Unigine::Variable class:

These functions receive a pointer to the required context, which allows you to correctly set ownership of the required script. To get the right context, use the following functions:

Setting the context is required as we perform conversion between external class variables and script ones.

注意
When calling the functions listed above, the corresponding script should already be loaded. Otherwise, NULL will be returned.

For example:

  1. Create a custom function on the C++ side, which will receive a variable. Release script ownership of the received variable.
    源代码(C++)
    #include <UnigineEngine.h>
    #include <UnigineInterpreter.h>
    #include <UnigineImage.h>
    #include <UnigineInterface.h>
    
    /*
     */
    using namespace Unigine;
    
    /*
     */
    // 1. Create an external function that receives a variable
    const char* change_owner(const Variable &v) {
    	
    	// 1.1 Release script ownership of the received variable
    	Engine *engine = Engine::get();
    	v.removeExternClass(engine->getWorldInterpreter()); // the world script context should be set, as removeExternClass() requires a script environment
    
    	// 1.2. Cast the received variable to the ImagePtr type
    	ImagePtr image = v.getImage(Interpreter::get());
    	
    	// 1.3. Set C++ ownership for the image
    	image->grab();
    	
    	// 1.4. Call the image member functions exposed through C++ API
    	return image->getFormatName();
    }
    
    /*
     */
    int main(int argc,char **argv) {
    	
    	// 2. Register the function for export into Unigine
    	Interpreter::addExternFunction("change_owner",MakeExternFunction(&change_owner));
    	
    	// 3. Initialize the engine. It will be shut down automatically
    	EnginePtr engine(UNIGINE_VERSION,argc,argv);
    	
    	// 4. Enter the engine main loop
    	engine->main();
    	
    	return 0;
    }
  2. On the script side, create an image and pass it to the external C++ function. As the script ownership is released in the external function, there is no need to delete the image here.
    源代码(UnigineScript)
    int init() {
    
    	// 1. Create a new image
    	Image image = new Image();
    	// 1.1. Specify parameters of the image and fill it with black color
    	image.create2D(256,256,IMAGE_FORMAT_R8);
    	
    	// 2. Call the registered external function and simply pass the image to it
    	log.message("%s\n",change_owner(image));
    }
最新更新: 2017-07-03
Build: ()