虽然lua有垃圾回收机制,但在使用quick-cocos2d-x和lua开发游戏还是会有不恰当的实现方式所导致的lua内存泄露(例如对cocos2d-x的Node
对象做了retain
却没有release
、把local
变量定义成全局变量、没有根据instance的lifecycle去释放它所占有的资源等等)。最近看到云风大神写的lua内存泄露检查工具lua-snapshot,便萌发了将它集成到我们游戏开发中,作为quantity assurance中一环。
配置
lua-snapshot
是用C实现的。我在网上看到不少人是把lua-snapshot
编译成动态链接库、用package.loadlib
来调用的,其实lua的require
本身就能加载C库的。
首先在cocos2d-x/external/lua/
目录下将lua-snapshot代码clone下来:
1
| git clone https://github.com/cloudwu/lua-snapshot.git
|
在cocos2d-x/cocos/scripting/lua-bindings/manual/lua-snapshot/
目录中添加如下文件:
(lua_snapshot_extensions.h) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| #ifndef __LUA_SNAPSHOT_EXTRA_H_
#define __LUA_SNAPSHOT_EXTRA_H_
#if defined(_USRDLL)
#define LUA_EXTENSIONS_DLL __declspec(dllexport)
#else /* use a DLL library */
#define LUA_EXTENSIONS_DLL
#endif
#if __cplusplus
extern "C" {
#endif
#include "lauxlib.h"
void LUA_EXTENSIONS_DLL luaopen_lua_snapshot_extensions(lua_State *L);
#if __cplusplus
}
#endif
#endif /* __LUA_SNAPSHOT_EXTRA_H_ */
|
(lua_snapshot_extensions.c) download
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
| #include "lua_snapshot_extensions.h"
#if __cplusplus
extern "C" {
#endif
// snapshot
#include "lua-snapshot/snapshot.h"
static luaL_Reg luax_exts[] = {
{"snapshot", luaopen_snapshot},
{NULL, NULL}
};
void luaopen_lua_snapshot_extensions(lua_State *L)
{
// load extensions
luaL_Reg* lib = luax_exts;
lua_getglobal(L, "package");
lua_getfield(L, -1, "preload");
for (; lib->func; lib++)
{
lua_pushcfunction(L, lib->func);
lua_setfield(L, -2, lib->name);
}
lua_pop(L, 2);
}
#if __cplusplus
} // extern "C"
#endif
|
(lua_cocos2dx_snapshot_manual.h) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| #ifndef __COCOS_SCRIPTING_LUA_BINDINGS_MANUAL_SNAPSHOT_LUA_COCOS2DX_SNAPSHOT_MANUAL_H__
#define __COCOS_SCRIPTING_LUA_BINDINGS_MANUAL_SNAPSHOT_LUA_COCOS2DX_SNAPSHOT_MANUAL_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "tolua++.h"
#ifdef __cplusplus
}
#endif
TOLUA_API int register_snapshot_module(lua_State* L);
#endif //#ifndef __COCOS_SCRIPTING_LUA_BINDINGS_MANUAL_SNAPSHOT_LUA_COCOS2DX_SNAPSHOT_MANUAL_H__
|
(lua_cocos2dx_snapshot_manual.cpp) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| #include "lua_cocos2dx_snapshot_manual.h"
extern "C" {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
#include "lua_snapshot_extensions.h"
#endif
}
#include "CCLuaEngine.h"
int register_snapshot_module(lua_State* L)
{
lua_getglobal(L, "_G");
if (lua_istable(L,-1))//stack:...,_G,
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
luaopen_lua_snapshot_extensions(L);
#endif
}
lua_pop(L, 1);
return 1;
}
|
然后找到lua_module_register.h
文件,在lua_module_register
函数中添加
1
| register_snapshot_module(L);
|
和头文件
1
| #include "lua-snapshot/lua_cocos2dx_snapshot_manual.h"
|
(lua_module_register.h) download
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
| #ifndef __LUA_TEMPLATE_RUNTIME_FRAMEWORKS_RUNTIME_SRC_CLASSES_LUA_MODULE_REGISTER_H__
#define __LUA_TEMPLATE_RUNTIME_FRAMEWORKS_RUNTIME_SRC_CLASSES_LUA_MODULE_REGISTER_H__
#include "cocosdenshion/lua_cocos2dx_cocosdenshion_manual.h"
#include "network/lua_cocos2dx_network_manual.h"
#include "lua-snapshot/lua_cocos2dx_snapshot_manual.h"
#include "cocosbuilder/lua_cocos2dx_cocosbuilder_manual.h"
#include "cocostudio/lua_cocos2dx_coco_studio_manual.hpp"
#include "extension/lua_cocos2dx_extension_manual.h"
#include "ui/lua_cocos2dx_ui_manual.hpp"
#include "spine/lua_cocos2dx_spine_manual.hpp"
#include "3d/lua_cocos2dx_3d_manual.h"
#include "audioengine/lua_cocos2dx_audioengine_manual.h"
#include "lua/quick/lua_cocos2dx_quick_manual.hpp"
int lua_module_register(lua_State* L)
{
//Dont' change the module register order unless you know what your are doing
register_cocosdenshion_module(L);
register_network_module(L);
#if CC_USE_CCBUILDER
register_cocosbuilder_module(L);
#endif
#if CC_USE_CCSTUDIO
register_cocostudio_module(L);
#endif
register_ui_moudle(L);
register_extension_module(L);
#if CC_USE_SPINE
register_spine_module(L);
#endif
#if CC_USE_3D
register_cocos3d_module(L);
#endif
register_audioengine_module(L);
register_snapshot_module(L);
return 1;
}
#endif // __LUA_TEMPLATE_RUNTIME_FRAMEWORKS_RUNTIME_SRC_CLASSES_LUA_MODULE_REGISTER_H__
|
使用
由于我们游戏的界面主要是窗口,所以之前实现时便在cocos2d-x引擎的Scene
和Layer
中间引入了一层Window
,并用一个全局singleton的WindowManager
对所有Window
对象进行管理,对窗口的创建前、创建后、销毁前、销毁后等等行为做公共处理。这样的设计给我加入内存泄露检测带来了不少便利:我只需在创建窗口实例之前记录下当时的快照snapshot1,打开窗口后根据这个窗口的功能做一些操作——通常和测试功能一起进行,也和WindowManager
的公共逻辑无关,所以不在代码做处理——在窗口销毁前记下snapshot2,在销毁后记下snapshot3,比较这三个快照,如果一个实例不在snapshot1中而存在于snapshot2和snapshot3中,则该实例属于这个窗口操作后造成的内存泄露。
参考资料