Galoisplusplus

A fan of science, technology and Classical music.

检测quick-cocos2d-x游戏的lua内存泄漏

| Comments

虽然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引擎的SceneLayer中间引入了一层Window,并用一个全局singleton的WindowManager对所有Window对象进行管理,对窗口的创建前、创建后、销毁前、销毁后等等行为做公共处理。这样的设计给我加入内存泄露检测带来了不少便利:我只需在创建窗口实例之前记录下当时的快照snapshot1,打开窗口后根据这个窗口的功能做一些操作——通常和测试功能一起进行,也和WindowManager的公共逻辑无关,所以不在代码做处理——在窗口销毁前记下snapshot2,在销毁后记下snapshot3,比较这三个快照,如果一个实例不在snapshot1中而存在于snapshot2和snapshot3中,则该实例属于这个窗口操作后造成的内存泄露。

参考资料

Comments