Cocos2d-x-Lua对象生命周期管理

  最近研究了Cocos2d-x的Lua binding。在Cocos2d-x中,使用了tolua++来完成C++绑定到Lua的工作。以下便是我对tolua++绑定Cocos2d-x的一些理解和改进。

一、对象的生命周期管理

  C++做Lua binding最重要的工作就是管理对象在不同系统中的生命周期。在Cocos2d-x和tolua++中是这样做的,C++中的对象都由引用计数管理,当通过Lua访问它们时,会将C++对象包装在Userdata类型的Lua对象中,然后这个Lua对象交由Lua的GC系统管理。

不同系统中管理对象生命周期

不同系统中管理对象生命周期

  但是这样有个问题,就是当Lua系统通过Lua Userdata使用C++对象的时候,这个C++对象就被Lua系统所引用,在C++系统的管理下不经Lua系统同意就可能将这个对象销毁,导致Lua系统引用的变成了无效的C++对象。
  Cocos2d-x的做法是当C++对象销毁的时候,检查有没有被Lua引用,如果被Lua引用了就把对应的Lua Userdata中携带的C++对象置为空,然后Lua引用的只是一个空对象。简单说就是实现了Lua对C++对象做弱引用。
  这样做有一个麻烦之处就是当在Lua中需要强引用一个C++对象长期使用时,我们就只好在获得C++对象时在Lua里调用retain()方法,手动增加对C++对象的引用,到这个对象使用结束时,还要手动调用release()方法去除引用。我觉得在一个有完整的GC系统的Lua语言里不应该还要手动进行对象生命周期的管理。
  不过要做改进也很简单,这个的问题在于Lua获取C++对象时需要做强引用做retain(),在Lua停用这个对象时需要release()。我们就只要找到在Lua里对C++对象的获取和停用的时机。
  Lua里获取C++对象的时机就是创建Lua Userdata包装C++对象指针,让C++对象进入Lua系统的时候;Lua里停用C++对象的时机一般和程序逻辑有关,不过还有一个最终确定的时机就是当Lua Userdata对象不再被引用而被Lua GC销毁的时候。于是,我们只要在创建Userdata时,对C++对象做一个retain(),当Userdata被Lua销毁,产生GC Event的时候,再对C++对象做一个release()。这样C++对象真正的生命周期就完整了,在所有使用它的地方它都应该被强引用而存活,当再没有任何地方引用后,因为被去除所有引用而销毁。

Lua系统引用C++对象

Lua系统引用C++对象

二、避免重复创建对象

  在实际里C++和Lua的交互非常复杂,同一个C++对象可能在Lua系统的多个不同地方被使用。我们没有必要在每次Lua系统引用C++对象时都创建新的Lua Userdata对象。于是tolua++中设计了一个叫tolua_ubox的表来保存每一个为C++对象创建的Lua Usedata对象,这个tolua_ubox是一个弱表,不会对Userdata产生强引用。当新的C++对象进入Lua系统时,就可以先在tolua_ubox中查询一下有没有对应创建过的Userdata对象,有就利用原有的对象,没有就创建新对象并做保存。
  原来的tolua++实现是在tolua_ubox中以C++对象指针为键,Lua Userdata为值保存它们的对应关系,以哈希表的形式存储和查询。哈希表是很快,但我觉得还可以更高效一点,如当对象被Lua引用时就给被引用的对象按整数序列分配一个id,在tolua_ubox中以这个id和Userdata对应保存,这样就可以以一个C数组的形式存储和查询,提高效率。不过带来一个问题就是id肯定是递增的整数,每有一个新的C++对象进入Lua就增加一,id无限增加肯定不行,对象销毁的时候会将id返还成为空闲id,有新对象时就先分配这些空闲id,没有空闲id就递增一个新的id使用。

用tolua_ubox缓存Userdata对象

用tolua_ubox缓存Userdata对象

三、特殊对象的生命周期管理

  实际上在Cocos2d-x中还有两种特殊的C++对象,这两种对象并不被引用计数所管理。一种是单例对象,一般在初次使用时创建,程序结束时释放;另一种是值对象,如Point,Rect,Color4B等等,一般都是直接通过值的拷贝来进行传递使用,而不是对这些对象进行多处引用。对于单例对象只要在Lua引用时创建对应的Userdata直接使用,当Userdata销毁时不用做任何其它处理。值对象比较特殊,在被Lua引用时就会在在堆上创建一份拷贝给Lua所有,当Lua停止使用这个拷贝时就将该对象销毁,让值对象生命周期完全由Lua管理。

Lua托管单例对象

Lua托管单例对象

Lua托管单例对象

Lua托管值对象

  在tolua++并没有做C++引用对象、值对象和单利对象的区分。所有的对象在tolua++中只是可以自定义一个由tolua++调用的pushXXX方法,和GC销毁时回调的collectXXX方法,交给用户来自定义管理方法。所有创建的Userdata对象都会被保存在ubox表中进行缓存,以供C++对象进入lua时进行查询。事实上值对象和单例对象都不需要缓存用于查询,值对象每次进入lua系统的一定是不被原C++系统引用的新对象,而单例对象可以只进入lua系统一次,然后在lua系统中保存在一些全局环境中来被重复使用。所以我的lua binding版本去除了这些特殊对象的缓存,减少了资源浪费。

标题目录