您的位置:首页 > 移动开发 > Cocos引擎

6------cocos2dx-------3.3<LUA>官网英文版

2016-06-24 17:21 591 查看


LUA

version: Cocos2d-x v3.3 and higher

Lua

Call
custom c++ from Lua

Call
the class member function

Call
global functions

Call
OpenGL functions

Bind
a c++ class to lua by bindings-generator automatically

Create
a custom class

Add
a new cocos2dx_custom.ini file

Subclassing

Memory
Management

Simple
Test Case

Memory
Management for Class Object

The
Class Members of Ref Class for Memory Management

Create
a Ref object from Lua

The
Release of the Userdata

Memory
Management for Lua Callback Function

Add
a reference for Lua Callback Function

Remove
a reference for Lua Callback Function

References


Call custom c++ from Lua

Cocos2d-x lua binds c++ classes, class functions, enums and some global functions to lua by using the bindings-generator (tools/bindings-generator) and by manual bindings. This allows you to call custom c++ from lua conveniently.


Call the class member function

Open
lua-empty-test/src/hello.lua
and
you will see many function calls like
cc.***
.
They are actually calling the class member functions. This is the
initGLView
function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[/code]
local function initGLView()
local director = cc.Director:getInstance()
local glView = director:getOpenGLView()
if nil == glView then
glView = cc.GLViewImpl:create("Lua Empty Test")
director:setOpenGLView(glView)
end

director:setOpenGLView(glView)

glView:setDesignResolutionSize(480, 320, cc.ResolutionPolicy.NO_BORDER)

director:setDisplayStats(true)

director:setAnimationInterval(1.0 / 60)
end


The relationship between lua function call and the c++ function call as follow:
LuaC++
cc.Director:getInstance()cocos2d::Director::getInstance()
director:getOpenGLView()director->getOpenGLView
cc.GLViewImpl:create("Lua Empty Test")cocos2d::GLViewImpl::create("Lua Empty Test")
glView:setDesignResolutionSize(480, 320, cc.ResolutionPolicy.NO_BORDER)glview->glView:setDesignResolutionSize(480, 320, ResolutionPolicy::NO_BORDER)
From this table, we can see that the functions called in lua are very similar with the functions called in c++. These are some key points that we need to pay attention to:

cc
is
a module name like namespace name in c++,it is cocos2d-x 3.0 new features. The relation between lua modules and c++ namespaces is as follow:

Lua module namec++ namespace
cccocos2d, cocos2d::extension, CocosDenshion, cocosbuilder
ccuicocos2d::ui
ccscocostudio, cocostudio::timeline
spspine
ccexpcocos2d::experimental, cocos2d::experimental::ui
static and non-static c++ functions are called in lua using
:


cc.ResolutionPolicy.NO_BORDER
corresponds
to
ResolutionPolicy::NO_BORDER
which
is enum value in the c++.
enum
values
are bound to lua by manual.

Different modules use different lua files to keep the bindings value:
moudle nameconst value files
ccCocos2dConstants.lua, ExtensionConstants.lua, NetworkConstants.lua
ccuiGuiConstants.lua
ccsStudioConstants.lua
ccexpexperimentalUIConstants.lua
For some functions, their parameters include
cocos2d::Vec2
,
cocos2d::Vec3
we
should do a conversion to call c++ function.For example:

1
[/code]
void Node::setPosition(const Vec2& position)


In c++, we should call this function like this:
1
[/code]
nodeObj->setPosition(Vec2(0.0f, 0.0f))


In lua, we should call the function like this:
1
[/code]
nodeObj:setPosition(cc.p(0.0, 0.0))


cc.p(0.0,
0.0)
is to construct an anonymous table like this {x = 0, y =0}

The other parametric types that should be converted are:
parametric typeslua conversional format
cocos2d::Point{x = numValue, y = numValue}
cocos2d::Vec3{x = numValue, y = numValue, z = numValue}
cocos2d::Vec4{x = numValue, y = numValue, z = numValue, w = numValue}
cocos2d::Rect{x = numValue, y = numValue, width = numValue, height = numValue}
cocos2d::Size{width = numValue, height = numValue}
cocos2d::Color4B{r = numValue, g = numValue, b = numValue, a = numValue}
cocos2d::Color4F{r = numValue, g = numValue, b = numValue, a = numValue}
cocos2d::Color3B{r = numValue, g = numValue, b = numValue}
cocos2d::PhysicsMaterial{density = numValue, restitution = numValue, friction = numValue}
cocos2d::AffineTransform{a = numValue, b = numValue, c = numValue, d = numValue, tx = numValue, ty = numValue}
cocos2d::FontDefinition{fontName = stringValue, fontSize = numValue, fontAlignmentH = numValue, fontAlignmentV = numValue, fontFillColor = {r = numValue, g = numValue, b = numValue}, fontDimensions = {width = numValue, height = numValue}, shadowEnabled = boolValue[,shadowOffset =
{width = numValue, height = numValue}, shadowBlur = numValue, shadowOpacity = numValue], strokeEnabled = boolValue[,strokeColor = {r = numValue, g = numValue, b = numValue}, strokeSize = numValue]}
cocos2d::Vector{objValue1,objValue2,...,objValuen,...}
cocos2d::Map<std::string, T>{key1 = objValue1, key2 = objValue2,..., keyn = objValuen,...}
cocos2d::Value{objValue1,objValue2,...,objValuen,...} or key1 = objValue1, key2 = objValue2,..., keyn = objValuen,...} or stringValue or boolValue or numValue
cocos2d::ValueMap{key1 = Value1, key2 = Value2,..., keyn = Valuen,...}
cocos2d::ValueMapIntKey{numKey1 = Value1, intKey2 = Value2, ...,intKeyn = Valuen,...}
cocos2d::ValueVector{Value1, Value2, ..., Valuen, ...}
std::vector<string>{stringValue1, stringValue2, ..., stringValuen, ...}
std::vector<int>{numValue1, numValue2, ..., numValuen, ...}
std::vector<float>{numValue1, numValue2, ..., numValuen, ...}
std::vector<unsigned short>{numValue1, numValue2, ..., numValuen, ...}
cocos2d::Mat4{numValue1,numValue2,..., numValue16}
cocos2d::TTFConfig{fontFilePath = stringValue, fontSize = numValue, glyphs = numValue, customGlyphs = stringValue, distanceFieldEnabled = boolValue, outlineSize = numValue}
cocos2d::MeshVertexAttrib{size = numValue, type = numValue, vertexAttrib = numValue, vertexAttrib =numValue}
cocos2d::BlendFunc{ src = numValue, dst = numValue}


Call global functions

cocos2d-x v3 also binds some global functions to lua manually, such as
kmGLPushMatrix
,
kmGLTranslatef
and
kmGLLoadMatrix
.
We can call these global functions in lua as follows:
1
[/code]
kmGLPopMatrix()



Call OpenGL functions

cocos2d-x v3 binds some OpenGL functions to lua. All the OpenGL functions are in the
gl
module
and can be called as follows:
1
2
3
4
5
[/code]
local glNode  = gl.glNodeCreate()
glNode:setContentSize(cc.size(256, 256))
glNode:setAnchorPoint(cc.p(0.5, 0.5))
uniformCenter = gl.getUniformLocation(program,"center")
uniformResolution  = gl.getUniformLocation( program, "resolution")


You can refer to
lua-tests/DrawPrimitiveTest
and
lua-tests/OpenGLTest
for
more information.


Bind a c++ class to lua by bindings-generator automatically

Since cocos2d-x v3.0, there is a tools called bindings-generator to
bind c++ class to lua automatically.

The bindings-generator is based on tolua++.
There is an ini file in the
tools/tolua
directory
and then run the genbindings.py script to generate the binding code.


Create a custom class

Consider this code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[/code]
// CustomClass.h

#ifndef __CUSTOM__CLASS

#define __CUSTOM__CLASS

#include "cocos2d.h"

namespace cocos2d {
class CustomClass : public cocos2d::Ref
{
public:

CustomClass();

~CustomClass();

static cocos2d::CustomClass* create();

bool init();

CREATE_FUNC(CustomClass);
};
} //namespace cocos2d

#endif // __CUSTOM__CLASS


Note:

the cpp file was omitted because the bindings-generator only scan the header files

The custom class should be inherited from the
Ref
class,
this is mainly due to the destructor of
Ref
calling
removeScriptObjectByObject
to
reduce the reference count of userdata which gets created in the c++ automatically to avoid memory
leak.


Add a new cocos2dx_custom.ini file

In tools/lua folder create a new file named cocos2dx_custom.ini as:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
[/code]
[cocos2dx_custom]

# the prefix to be added to the generated functions. You might or might
not use this in your own

# templates

prefix = cocos2dx_custom

# create a target namespace (in javascript, this would create some code
like the equiv. to `ns = ns

# all classes will be embedded in that namespace

target_namespace = cc

android_headers =  -I%(androidndkdir)s/platforms/android-14/arch-arm/usr/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.7/libs/armeabi-v7a/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.7/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.8/libs/armeabi-v7a/include -I%(androidndkdir)s/sources/cxx-stl/gnu-libstdc++/4.8/include

android_flags = -D_SIZE_T_DEFINED_

clang_headers = -I%(clangllvmdir)s/lib/clang/3.3/include

clang_flags = -nostdinc -x c++ -std=c++11 -U __SSE__

cocos_headers = -I%(cocosdir)s/cocos -I%(cocosdir)s/my -I%(cocosdir)s/cocos/2d -I%(cocosdir)s/cocos/base -I%(cocosdir)s/cocos/ui -I%(cocosdir)s/cocos/physics -I%(cocosdir)s/cocos/2d/platform -I%(cocosdir)s/cocos/2d/platform/android -I%(cocosdir)s/cocos/math/kazmath -I%(cocosdir)s/extensions -I%(cocosdir)s/external -I%(cocosdir)s/cocos/editor-support -I%(cocosdir)s

cocos_flags = -DANDROID -DCOCOS2D_JAVASCRIPT

cxxgenerator_headers =

# extra arguments for clang

extra_arguments =  %(android_headers)s %(clang_headers)s %(cxxgenerator_headers)s %(cocos_headers)s %(android_flags)s %(clang_flags)s %(cocos_flags)s %(extra_flags)s

# what headers to parse

headers = %(cocosdir)s/cocos/my/CustomClass.h

# what classes to produce code for. You can use regular expressions here.
# When testing the regular expression, it will be enclosed in "^$", like
# this: "^Menu*$".

classes = CustomClass.*

# what should we skip? in the format ClassName::[function function]
# ClassName is a regular expression, but will be used like this:
# "^ClassName$" functions are also regular expressions, they will not be
# surrounded by "^$". If you want to skip a whole class, just add a single
# "*" as functions. See bellow for several examples. A special class name
# is "*", which will apply to all class names. This is a convenience
# wildcard to be able to skip similar named functions from all classes.

skip =

rename_functions =

rename_classes =

# for all class names, should we remove something when registering in the
# target VM?

remove_prefix =

# classes for which there will be no "parent" lookup

classes_have_no_parents =

# base classes which will be skipped when their sub-classes found them.

base_classes_to_skip =

# classes that create no constructor

# Set is special and we will use a hand-written constructor

abstract_classes =

# Determining whether to use script object(js object) to control the
# lifecycle of native(cpp) object or the other way around. Supported
# values are 'yes' or 'no'.

script_control_cpp = no


All of the config files under tools/tolua use the same format. Here is a list which you should consult
when writing your own ini file:

[title]: To config the title which will by used by the tools/tolua/gengindings.py scripts. Generally,
the title could be the file name.

prefix: To config the prefix of a function name, generally, we also use the file name as the prefix.

target_namespace: To config the module name in lua. Here we use the
cc
as
the module name, when you want to use
CustomClass
in
lua, you must put a prefix named
cc
in
front of the name. For example, the
CustomClass
could
be reference as
cc.CustomClass
.

headers: To config all the header files needed for parsing and the %(cocosdir)s is the engine root path of cocos2d-x.

classes: To config all the classes needed to bind. Here it supports regular expression. So we could set MyCustomClass.* here. For looking more specified usage, you could ref to
tools/tolua/cocos2dx.ini
.

skip: To config the functions needed to be omit. Now the bindings-generator can't parse
void*
type
and also the delegate type, so these types needed to be bind manually. And at this circumstance, you should omit all these types first and then to bind them manually. You could ref to the config files under path
cocos/scripting/lua-bindings/auto
.

rename_functions: To config the functions need to be renamed in the scripting layer. Due to some reasons, developers want more scripting friendly API, so the config option is for this purpose.

rename_classes: Not used any more.

remove_prefix: Not used any more.

classes_have_no_parents: To config the parent class needed to be filter. This option is seldom modified.

abstract_classes: To config the classes whose public constructor don't need to be exported.

script_control_cpp:yes. To config whether the scripting layer manage the object life time or not. If no, then the c++ layer cares about their life time. Now, it is imperfect to control native object's life time in scripting layer. So you could simply leave
it to no.


Subclassing

Sometimes we want to add some new functions to extend the bindings, think inheritance in c++. Through
class(classname,
super)
function in the
cocos/scripting/lua-bindings/script/cocos2d/extern.lua
,
we can realize this requirement easily. The details function are as follow:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
[/code]
function class(classname, super)
local superType = type(super)
local cls

if superType ~= "function" and superType ~= "table" then
superType = nil
super = nil
end

if superType == "function" or (super and super.__ctype == 1) then
-- inherited from native C++ Object
cls = {}

if superType == "table" then
-- copy fields from super
for k,v in pairs(super) do cls[k] = v end
cls.__create = super.__create
cls.super    = super
else
cls.__create = super
end

cls.ctor    = function() end
cls.__cname = classname
cls.__ctype = 1

function cls.new(...)
local instance = cls.__create(...)
-- copy fields from class to native object
for k,v in pairs(cls) do instance[k] = v end
instance.class = cls
instance:ctor(...)
return instance
end

else
-- inherited from Lua Object
if super then
cls = clone(super)
cls.super = super
else
cls = {ctor = function() end}
end

cls.__cname = classname
cls.__ctype = 2 -- lua
cls.__index = cls

function cls.new(...)
local instance = setmetatable({}, cls)
instance.class = cls
instance:ctor(...)
return instance
end
end

return cls
end


Through this function, we can see inheritance easily. Example, if we want to derive from
cc.Node
:

Define a subclass by
class
function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[/code]
local SubNode = class("SubNode",function()
return cc.Node:create()
end

--This function like the construtor of c++ class
function SubNode:ctor()
-- do initialized
end

function SubNode:addSprite(filePath)
local sprite = cc.Sprite:create(filePath)
sprite:setPosition(cc.p(0, 0))
self:addChild(sprite)
end


Create a object of subclass and use it:

1
2
[/code]
local node = SubNode.new()
node:addSprite( "xxx.jpg" )


Note:
new
is
implemented by default in the
class
function.
Since the type of the second parameter is
function
,
when we call
new
,
this is what happens:
1
2
3
4
5
6
7
8
[/code]
function SubNode.new(...)
local instance = cc.Node:create()
-- copy fields from SubNode to native object
for k,v in pairs(SubNode) do instance[k] = v end
instance.class = SubNode
instance:ctor(...)
return instance
end


The object created by
new
have
all the properties and behaviors of the
cc.Node
object.
It also has the properties of the
SubNode
as
it is derived from
cc.Node
:
1
2
3
[/code]
function SubNode:setPostion(x,y)
print(string.format("x = %0.2f, y = %0.2f"), x, y)
end


If we still need to call the function of the same name of super class:
1
[/code]
getmetatable(SubNode):setPosition(x, y)


The override functions of inherited class in lua can't be called in the c++.


Memory Management

Cocos2d-x v3.x uses the memory management and garbage collection of lua itself except the release of
userdata
.
If the corresponding classes are derived from
Ref
the
release of
userdata
is
managed in c++ by the register table named

toluafix_refid_ptr_mapping
and
tolua_value_root
.


Simple Test Case

Create a
Sprite
in
the head of
createDog
function
1
[/code]
local testSprite = cc.Sprite:create("res/land.png")


Then call the
Sprite
in
the
tick
function
as follow:
1
[/code]
testSprite:getPosition()


After a period of time, we will see the error message as follows:
cocos2d: [LUA-print] stack traceback:
[string "src/hello.lua"]:13: in function <[string "src/hello.lua"]:10>
[C]: in function 'getPosition'
[string "src/hello.lua"]:98: in function <[string "src/hello.lua"]:88>
cocos2d: [LUA-print] ----------------------------------------
cocos2d: [LUA-print] ----------------------------------------
cocos2d: [LUA-print] LUA ERROR: [string "src/hello.lua"]:98: invalid 'self' in function 'tolua_cocos2d_Node_getPosition'


This error is triggered because the testsprite didn't add any other node as a child after creation.
The corresponding c++ object was released at the end of the frame.


Memory Management for Class Object


The Class Members of Ref Class for Memory Management

In
CCRef.h
we
see the usage of
CC_ENABLE_SCRIPT_BINDING
:
1
2
3
4
5
6
7
8
9
[/code]
#if CC_ENABLE_SCRIPT_BINDING
public:
/// object id, ScriptSupport need public _ID
unsigned int        _ID;
/// Lua reference id
int                 _luaID;
/// scriptObject, support for swift
void* _scriptObject;
#endif


Notice
_ID
and
_luaID
,
are very important when you push a
Ref
object
to lua by calling
toluafix_pushusertype_ccobject
to
store a key-value table named
toluafix_refid_ptr_mapping
in
the registry. The
_ID
is
key and the related c++ object pointer is value. The related code fragment in the
toluafix_pushusertype_ccobject
is:
1
2
3
4
5
6
7
8
[/code]
//Extract from `toluafix_pushusertype_ccobject` in the tolua_fix.cpp
lua_pushstring(L, TOLUA_REFID_PTR_MAPPING);
lua_rawget(L, LUA_REGISTRYINDEX);                           /* stack: refid_ptr */
lua_pushinteger(L, refid);                                  /* stack: refid_ptr refid */
lua_pushlightuserdata(L, vPtr);                             /* stack: refid_ptr refid ptr */

lua_rawset(L, -3);                                          /* refid_ptr[refid] = ptr, stack: refid_ptr */
lua_pop(L, 1);                                              /* stack: - */


Notes:

TOLUA_REFID_PTR_MAPPING
is
macro definition represent for "toluafix_refid_ptr_mapping"

LUA_REGISTRYINDEX
is
definition of
Pseudo-Index
for
registry of Lua

refid
is
value of
_ID


vPtr
is
value of related c++ object pointer


Create a Ref object from Lua

Call the
cocos2d::Sprite::create("res/land.png")
by
lua bindings to create a Sprite object and push it into lua stack:
1
2
3
4
5
6
7
[/code]
//Extract from `lua_cocos2dx_Sprite_create` in lua_cocos2dx_auto.cpp
std::string arg0;
ok &= luaval_to_std_string(tolua_S, 2,&arg0, "cc.Sprite:create");
if (!ok) { break; }
cocos2d::Sprite* ret = cocos2d::Sprite::create(arg0);
object_to_luaval<cocos2d::Sprite>(tolua_S, "cc.Sprite",(cocos2d::Sprite*)ret);
return 1;


Call
toluafix_pushusertype_ccobject
when
push created object to lua stack

1
2
3
4
5
6
7
8
9
[/code]
//Extract from `object_to_luaval` in luaBasicConversions.h
if (std::is_base_of<cocos2d::Ref, T>::value)
{
// use c style cast, T may not polymorphic
cocos2d::Ref* dynObject = (cocos2d::Ref*)(ret);
int ID = (int)(dynObject->_ID) ;
int* luaID = &(dynObject->_luaID);
toluafix_pushusertype_ccobject(L,ID, luaID, (void*)ret,type);
}


In the
toluafix_pushusertype_ccobject
,
we will use two tables named "toluafix_refid_ptr_mapping" and "toluafix_refid_type_mapping" in lua's registry to store the two key-value pairs about
_ID
-
object
pointer
and
_ID
-
object
type name
.The details are as follow:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[/code]
//Extract from `toluafix_pushusertype_ccobject` in the tolua_fix.cpp
if (*p_refid == 0)
{
*p_refid = refid;

lua_pushstring(L, TOLUA_REFID_PTR_MAPPING);
lua_rawget(L, LUA_REGISTRYINDEX); /* stack: refid_ptr */
lua_pushinteger(L, refid); /* stack: refid_ptr refid */
lua_pushlightuserdata(L, vPtr); /* stack: refid_ptr refid ptr */

lua_rawset(L, -3); /* refid_ptr[refid] = ptr, stack: refid_ptr */
lua_pop(L, 1); /* stack: - */

lua_pushstring(L, TOLUA_REFID_TYPE_MAPPING);
lua_rawget(L, LUA_REGISTRYINDEX);  /* stack: refid_type */
lua_pushinteger(L, refid); /* stack: refid_type refid */
lua_pushstring(L, vType); /* stack: refid_type refid type */
lua_rawset(L, -3); /* refid_type[refid] = type, stack: refid_type */
lua_pop(L, 1); /* stack: - */

//printf("[LUA] push CCObject OK - refid: %d, ptr: %x, type: %s\n",
//*p_refid, (int)ptr, type);
}


Call
tolua_pushusertype_internal
to
determine whether to create a new userdata, or just update the userdata.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
[/code]
void tolua_pushusertype_internal (lua_State* L, void* value, const char* type, int addToRoot)
{
if (value == NULL)
lua_pushnil(L);
else
{
luaL_getmetatable(L, type); /* stack: mt */
if (lua_isnil(L, -1)) { /* NOT FOUND metatable */
lua_pop(L, 1);
return;
}
lua_pushstring(L,"tolua_ubox");
lua_rawget(L,-2); /* stack: mt ubox */
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
lua_pushstring(L, "tolua_ubox");
lua_rawget(L, LUA_REGISTRYINDEX);
};

lua_pushlightuserdata(L,value); /* stack: mt ubox key<value> */
lua_rawget(L,-2); /* stack: mt ubox ubox[value] */

if (lua_isnil(L,-1))
{
lua_pop(L,1); /* stack: mt ubox */
lua_pushlightuserdata(L,value);
*(void**)lua_newuserdata(L,sizeof(void *)) = value; /* stack: mt ubox value newud */
lua_pushvalue(L,-1); /* stack: mt ubox value newud newud */
lua_insert(L,-4); /* stack: mt newud ubox value newud */
lua_rawset(L,-3); /* ubox[value] = newud, stack: mt newud ubox */
lua_pop(L,1); /* stack: mt newud */
/*luaL_getmetatable(L,type);*/
lua_pushvalue(L, -2); /* stack: mt newud mt */
lua_setmetatable(L,-2); /* update mt, stack: mt newud */

#ifdef LUA_VERSION_NUM
lua_pushvalue(L, TOLUA_NOPEER); /* stack: mt newud peer */
lua_setfenv(L, -2); /* stack: mt newud */
#endif
}
else
{
/* check the need of updating the metatable to a more specialized class */
lua_insert(L,-2); /* stack: mt ubox[u] ubox */
lua_pop(L,1); /* stack: mt ubox[u] */
lua_pushstring(L,"tolua_super");
lua_rawget(L,LUA_REGISTRYINDEX); /* stack: mt ubox[u] super */
lua_getmetatable(L,-2); /* stack: mt ubox[u] super mt */
lua_rawget(L,-2); /* stack: mt ubox[u] super super[mt] */
if (lua_istable(L,-1))
{
lua_pushstring(L,type); /* stack: mt ubox[u] super super[mt] type */
lua_rawget(L,-2); /* stack: mt ubox[u] super super[mt] flag */
if (lua_toboolean(L,-1) == 1) /* if true */
{
lua_pop(L,3); /* mt ubox[u]*/
lua_remove(L, -2);
return;
}
}
/* type represents a more specilized type */
/*luaL_getmetatable(L,type);// stack: mt ubox[u] super super[mt] flag mt */
lua_pushvalue(L, -5); /* stack: mt ubox[u] super super[mt] flag mt */
lua_setmetatable(L,-5); /* stack: mt ubox[u] super super[mt] flag */
lua_pop(L,3); /* stack: mt ubox[u] */
}
lua_remove(L, -2);    /* stack: ubox[u]*/

if (0 != addToRoot)
{
lua_pushvalue(L, -1);
tolua_add_value_to_root(L, value);
}
}
}


We use a table named
ubox
to
store key-value pairs about
userdata
and
object
pointer
. This table would be used in the destruction of the object.

Call
tolua_add_value_to_root
to
add a reference count for
userdata
in
lua by the
tolua_value_root
table
in lua registry. The mechanism will make the object in lua wouldn't collected by lua gc. Example:
lua
local node = cc.Node:create() node.extendValue = 10000 nodeParent:addChild(node, 0 , 9999)
This code creates a
node
object
and extends the attributes of the node object dynamically by lua's feature. When we want to get this node and its extended attribute somewhere, we can do as follows:
lua
local child = lnodeParent:getChildByTag(9999) print(child.extendValue)


If we don't call the
tolua_add_value_to_root
,
the result of
print(child.extendValue)
would
be uncertain. Sometimes the result would be 10000 and sometimes it would be
nil
.
This is because we wouldn't control lua's automatic gc effectively. When lua gc thinks there are no other references for this userdata it will collect this userdata. When we call
getChildByTag
to
get a node object, it would create a new userdata and the extended attributes would disapper. We add a reference count for the userdata
tolua_value_root
table
in lua registry in the c++ to avoid generating this error.


The Release of the Userdata

When calling the desturctor of
Ref
,
it will trigger the release of the userdata.

In the destructor of Ref, we can see:
1
2
3
4
5
6
7
8
[/code]
#if CC_ENABLE_SCRIPT_BINDING
// if the object is referenced by Lua engine, remove it
if (_luaID)
{
ScriptEngineManager::getInstance()->getScriptEngine()->removeScriptObjectByObject(this);
}
...
#endif


After we push a c++ object to lua, the related _luaID would be not 0. We now can call
removeScriptObjectByObject


The
removeScriptObjectByObject
called
would trigger the call of
toluafix_remove_ccobject_by_refid
,
and this function would call some lua c APIs to operate the table like
toluafix_refid_ptr_mapping
,
toluafix_refid_type_mapping
and
tolua_value_root
table
in the registry.

The specific implementation of
toluafix_remove_ccobject_by_refid
is
as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
[/code]
TOLUA_API int toluafix_remove_ccobject_by_refid(lua_State* L, int refid)
{
void* ptr = NULL;
const char* type = NULL;
void** ud = NULL;
if (refid == 0) return -1;

// get ptr from tolua_refid_ptr_mapping
lua_pushstring(L, TOLUA_REFID_PTR_MAPPING);
lua_rawget(L, LUA_REGISTRYINDEX); /* stack: refid_ptr */
lua_pushinteger(L, refid); /* stack: refid_ptr refid */
lua_rawget(L, -2); /* stack: refid_ptr ptr */
ptr = lua_touserdata(L, -1);
lua_pop(L, 1); /* stack: refid_ptr */
if (ptr == NULL)
{
lua_pop(L, 1);
// Lua stack has closed, C++ object not in Lua.
// printf("[LUA ERROR] remove CCObject with NULL ptr, refid: %d\n", refid);
return -2;
}

// remove ptr from tolua_refid_ptr_mapping
lua_pushinteger(L, refid); /* stack: refid_ptr refid */
lua_pushnil(L); /* stack: refid_ptr refid nil */
lua_rawset(L, -3); /* delete refid_ptr[refid], stack: refid_ptr */
lua_pop(L, 1); /* stack: - */

// get type from tolua_refid_type_mapping
lua_pushstring(L, TOLUA_REFID_TYPE_MAPPING);
lua_rawget(L, LUA_REGISTRYINDEX); /* stack: refid_type */
lua_pushinteger(L, refid); /* stack: refid_type refid */
lua_rawget(L, -2); /* stack: refid_type type */
if (lua_isnil(L, -1))
{
lua_pop(L, 2);
printf("[LUA ERROR] remove CCObject with NULL type, refid: %d, ptr: %p\n", refid, ptr);
return -1;
}

type = lua_tostring(L, -1);
lua_pop(L, 1); /* stack: refid_type */

// remove type from tolua_refid_type_mapping
lua_pushinteger(L, refid); /* stack: refid_type refid */
lua_pushnil(L); /* stack: refid_type refid nil */
lua_rawset(L, -3); /* delete refid_type[refid], stack: refid_type */
lua_pop(L, 1); /* stack: - */

// get ubox
luaL_getmetatable(L, type); /* stack: mt */
lua_pushstring(L, "tolua_ubox"); /* stack: mt key */
lua_rawget(L, -2); /* stack: mt ubox */
if (lua_isnil(L, -1))
{
// use global ubox
lua_pop(L, 1); /* stack: mt */
lua_pushstring(L, "tolua_ubox"); /* stack: mt key */
lua_rawget(L, LUA_REGISTRYINDEX); /* stack: mt ubox */
};

// cleanup root
tolua_remove_value_from_root(L, ptr);

lua_pushlightuserdata(L, ptr); /* stack: mt ubox ptr */
lua_rawget(L,-2); /* stack: mt ubox ud */
if (lua_isnil(L, -1))
{
// Lua object has released (GC), C++ object not in ubox.
//printf("[LUA ERROR] remove CCObject with NULL ubox, refid: %d, ptr: %x, type: %s\n", refid, (int)ptr, type);
lua_pop(L, 3);
return -3;
}

// cleanup peertable
lua_pushvalue(L, LUA_REGISTRYINDEX);
lua_setfenv(L, -2);

ud = (void**)lua_touserdata(L, -1);
lua_pop(L, 1); /* stack: mt ubox */
if (ud == NULL)
{
printf("[LUA ERROR] remove CCObject with NULL userdata, refid: %d, ptr: %p, type: %s\n", refid, ptr, type);
lua_pop(L, 2);
return -1;
}

// clean userdata
*ud = NULL;

lua_pushlightuserdata(L, ptr); /* stack: mt ubox ptr */
lua_pushnil(L); /* stack: mt ubox ptr nil */
lua_rawset(L, -3); /* ubox[ptr] = nil, stack: mt ubox */

lua_pop(L, 2);
//printf("[LUA] remove CCObject, refid: %d, ptr: %x, type: %s\n", refid, (int)ptr, type);
return 0;
}


The steps are as follows:

Get related object pointer stored in the
toluafix_refid_ptr_mapping
table
by the value of
_luaID
.
Store it.

Remove reference relationship of the object pointer from
toluafix_refid_ptr_mapping
table
by
_luID


Get related type name stored in the
tolua_refid_type_mapping
table
by the value of
_luaID
,then
store it

Remove reference relationship of type name from
tolua_refid_type_mapping
table
by
_luID


Get the related metatable by the type name

Get the
ubox
table

Remove reference relationship of userdata from
tolua_value_root
table
by the object pointer got in the upper step

Clean userdata and remove reference relationship of uesrdata from
ubox
by
the object pointer got in the upper step.Note:To destroy a object cited by lua, we only called '*ud = NULL;'

Through the above steps,the refernce relationships in the
toluafix_refid_ptr_mapping
tolua_refid_type_mapping
and
tolua_refid_type_mapping
table
in the registry would be removed, release the
userdata
which
is created when push c++ object to lua stack, and when lua gc trigger, the related object would be collected if there is no other place refer

to it.


Memory Management for Lua Callback Function

Cocos2dx have been used
toluafix_refid_function_mapping
table
in the registry to manage the gc of lua callback function


Add a reference for Lua Callback Function

When we define a lua function which would be called throuch c++ codes, we whould store the pointer of this function in the
toluafix_refid_function_mapping
table
by calling
toluafix_ref_function
function
in the
tolua_fix.cpp
.Cocos2d-x
bound

a series of functions like
registerScriptHandler
and
addEventListener
to
finish this work.

Let's use
registerScriptHandler
of
Node
as
a sample,we could use it as follows in lua:
1
2
3
4
5
6
7
[/code]
local function onNodeEvent(event)
if "enter" == event then
--do something
end
end

nodeObject:registerScriptHandler(onNodeEvent)


The related bindings function is named
tolua_cocos2d_Node_registerScriptHandler
in
the
lua_cocos2dx_manual.cpp
,the
most important sections are as follows:
1
2
[/code]
LUA_FUNCTION handler = toluafix_ref_function(tolua_S,2,0);
ScriptHandlerMgr::getInstance()->addObjectHandler((void*)self, handler, ScriptHandlerMgr::HandlerType::NODE);


toluafix_ref_function
is
implemented to store the related function pointer into
toluafix_refid_function_mapping
table
in the registry with a static variable named
s_function_ref_id
.This
operation makes lua function avoid being collected by lua gc because that
toluafix_refid_function_mapping
table
have a reference of this function. The details are as follow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[/code]
TOLUA_API int toluafix_ref_function(lua_State* L, int lo, int def)
{
// function at lo
if (!lua_isfunction(L, lo)) return 0;

s_function_ref_id++;

lua_pushstring(L, TOLUA_REFID_FUNCTION_MAPPING);
lua_rawget(L, LUA_REGISTRYINDEX); /* stack: fun ... refid_fun */
lua_pushinteger(L, s_function_ref_id); /* stack: fun ... refid_fun refid */
lua_pushvalue(L, lo); /* stack: fun ... refid_fun refid fun */

lua_rawset(L, -3); /* refid_fun[refid] = fun, stack: fun ... refid_ptr */
lua_pop(L, 1); /* stack: fun ... */

return s_function_ref_id;
}


addObjectHandler
is
used to stored the map of object pointer and pair of
s_function_ref_id
and
handler type.


Remove a reference for Lua Callback Function

If lua callback function become useless, we should remove the reference in the
toluafix_refid_function_mapping
table
in the registry. Cocos2d-x provided the
toluafix_remove_function_by_refid
function
to realize it. This function could be called by
removeScriptHandler
of
LuaStack
removeScriptHandler
of
LuaEngine
or
directly. The details are as follows:
1
2
3
4
5
6
7
8
9
10
[/code]
TOLUA_API void toluafix_remove_function_by_refid(lua_State* L, int refid)
{
lua_pushstring(L, TOLUA_REFID_FUNCTION_MAPPING);
lua_rawget(L, LUA_REGISTRYINDEX); /* stack: ... refid_fun */
lua_pushinteger(L, refid); /* stack: ... refid_fun refid */
lua_pushnil(L); /* stack: ... refid_fun refid nil */
lua_rawset(L, -3); /* refid_fun[refid] = nil, stack: ... refid_fun */
lua_pop(L, 1); /* stack: ... */

}


Note:

refid
is
the corresponding value of
s_function_ref_id
.

For Ref object,we would call
ScriptHandlerMgr::getInstance()->removeObjectAllHandlers
to
remove all the reference function relationship which added by the
ScriptHandlerMgr::getInstance()->addObjectHandler
automatically

Because Cocos2d-x v3.x support the features of c++ 11, we can call the related remove function through the lambda function. For example:

1
2
3
4
5
6
7
8
9
10
[/code]
//Extract from `lua_cocos2dx_TextureCache_addImageAsync` in lua_cocos2dx_manual.cpp
LUA_FUNCTION handler = (  toluafix_ref_function(tolua_S, 3, 0));

self->addImageAsync(configFilePath, [=](Texture2D* tex){
int ID = (tex) ? (int)tex->_ID : -1;
int* luaID = (tex) ? &tex->_luaID : nullptr;
toluafix_pushusertype_ccobject(tolua_S, ID, luaID, (void*)tex, "cc.Texture2D");
LuaEngine::getInstance()->getLuaStack()->executeFunctionByHandler(handler,1);
LuaEngine::getInstance()->removeScriptHandler(handler);
});


By the mechanism of the lambda, we could get the value of handler which represents the corresponding value of
s_function_ref_id
.
When we finish calling lua callback function,we could call
LuaEngine::getInstance()->removeScriptHandler(handler)
directly
to remove the reference of lua callback function.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

官网的文档,拿过来看看。

原文链接:http://www.cocos2d-x.org/wiki/Lua
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: