version: Cocos2d-x v3.3 and higher
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.
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:
12345678910111213141516
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:
Lua | C++ |
---|---|
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 name | c++ namespace |
---|---|
cc | cocos2d, cocos2d::extension, CocosDenshion, cocosbuilder |
ccui | cocos2d::ui |
ccs | cocostudio, cocostudio::timeline |
sp | spine |
ccexp | cocos2d::experimental, cocos2d::experimental::ui |
:
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 name | const value files |
---|---|
cc | Cocos2dConstants.lua, ExtensionConstants.lua, NetworkConstants.lua |
ccui | GuiConstants.lua |
ccs | StudioConstants.lua |
ccexp | experimentalUIConstants.lua |
cocos2d::Vec2
, cocos2d::Vec3
we should do a conversion to call c++ function.For example: 1
void Node::setPosition(const Vec2& position)
In c++, we should call this function like this:
1
nodeObj->setPosition(Vec2(0.0f, 0.0f))
In lua, we should call the function like this:
1
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 types | lua 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} |
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
kmGLPopMatrix()
cocos2d-x v3 binds some OpenGL functions to lua. All the OpenGL functions are in the gl
module and can be called as follows:
12345
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.
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.
Consider this code:
1234567891011121314151617181920212223242526
// 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:
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.In tools/lua folder create a new file named cocos2dx_custom.ini as:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
[cocos2dx_custom]# the prefix to be added to the generated functions. You might or mightnot use this in your own# templatesprefix = cocos2dx_custom# create a target namespace (in javascript, this would create some codelike the equiv. to `ns = ns# all classes will be embedded in that namespacetarget_namespace = ccandroid_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/includeandroid_flags = -D_SIZE_T_DEFINED_clang_headers = -I%(clangllvmdir)s/lib/clang/3.3/includeclang_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)scocos_flags = -DANDROID -DCOCOS2D_JAVASCRIPTcxxgenerator_headers =# extra arguments for clangextra_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 parseheaders = %(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" lookupclasses_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 constructorabstract_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:
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
.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.
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:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
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 clsend
Through this function, we can see inheritance easily. Example, if we want to derive from cc.Node
:
class
function 1234567891011121314
local SubNode = class("SubNode",function() return cc.Node:create()end--This function like the construtor of c++ classfunction SubNode:ctor() -- do initializedendfunction SubNode:addSprite(filePath) local sprite = cc.Sprite:create(filePath) sprite:setPosition(cc.p(0, 0)) self:addChild(sprite)end
12
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:
12345678
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 instanceend
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
:
123
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
getmetatable(SubNode):setPosition(x, y)
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 namedtoluafix_refid_ptr_mapping
and tolua_value_root
.
Create a Sprite
in the head of createDog
function
1
local testSprite = cc.Sprite:create("res/land.png")
Then call the Sprite
in the tick
function as follow:
1
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.
In CCRef.h
we see the usage of CC_ENABLE_SCRIPT_BINDING
:
123456789
#if CC_ENABLE_SCRIPT_BINDINGpublic: /// 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:
12345678
//Extract from `toluafix_pushusertype_ccobject` in the tolua_fix.cpplua_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 Luarefid
is value of _ID
vPtr
is value of related c++ object pointerCall the cocos2d::Sprite::create("res/land.png")
by lua bindings to create a Sprite object and push it into lua stack:
1234567
//Extract from `lua_cocos2dx_Sprite_create` in lua_cocos2dx_auto.cppstd::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
123456789
//Extract from `object_to_luaval` in luaBasicConversions.hif (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:
1234567891011121314151617181920212223
//Extract from `toluafix_pushusertype_ccobject` in the tolua_fix.cppif (*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);}
tolua_pushusertype_internal
to determine whether to create a new userdata, or just update the userdata. 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
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.
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:lualocal node = cc.Node:create()node.extendValue = 10000nodeParent: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:lualocal 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.
When calling the desturctor of Ref
, it will trigger the release of the userdata.
In the destructor of Ref, we can see:
12345678
#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:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
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.
Cocos2dx have been used toluafix_refid_function_mapping
table in the registry to manage the gc of 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:
1234567
local function onNodeEvent(event) if "enter" == event then --do something endendnodeObject: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:
12
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: 1234567891011121314151617
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.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:
12345678910
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
.ScriptHandlerMgr::getInstance()->removeObjectAllHandlers
to remove all the reference function relationship which added by the ScriptHandlerMgr::getInstance()->addObjectHandler
automatically 12345678910
//Extract from `lua_cocos2dx_TextureCache_addImageAsync` in lua_cocos2dx_manual.cppLUA_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.
For advance contents, please read
联系客服