【Cocos2d-x 3.x】内存管理机制与源码分析
2015-11-09 03:44
633 查看
侯捷先生说过这么一句话 : 源码之前,了无秘密。 要了解Cocos2d-x的内存管理机制,就得阅读源码。
接触Cocos2d-x时, Cocos2d-x的最新版本已经到了3.2的时代,在学习Cocos2d-x 3.x的时,经常会写点很小的例子,比如创建一个精灵Sprite, 然后设计精灵的动作Action等等,或者添加图层Layer并设置相应属性等等。在创建这些元素的时候,都会先进行这样的操作 :
在cocos2d-x 3.0之后加入了C++11的内容,于是m_sprite和m_layer的类型就可以这样写了 :
当然这些都不是重点, 重点的是这些直接或间接继承Node的类(Node继承自Ref)在创建的时候都使用create() 成员函数来创建, 看看create()的源码 :
在create一个对象时, 会调用一个autorelease()函数, 继续跟进 :
原来是将新创建好的这个添加到当前的管理池中,而addObject实际上是Ref类的函数,Ref中有个存放Ref* 类型的vector,将新创建的对象添加到vector之中。
PoolManager::getInstance()->getCurrentPool()->addObject(this)这行代码中,涉及到了三个类 : PoolManager, AutoreleasePool和Ref类。实际上, Cocos2d-x的内存管理机制就是和这三个类息息相关的,分别来看下源码 :
先看Ref类:
再看AutoreleasePool类:
最后看看PoolManager类:
源码都在这里了,重新分析开头说的那个例子,在创建一个Sprite或Layer时, 先调用autorelease函数 :
然后就会先获得一个管理自动管理内存池的类PoolManager的单例 :
第一次使用单例,会新建一个,会添加一个自动管理池 :
然后将该管理池push到PoolManager中的vector中,该vector是管理AutoreleasePool的 :
然后由该单例获得管理这个Sprite或Layer的AutoreleasePool,并将该Sprite或Layer添加到该自动管理池当中 :
然后如果将这个Sprite添加到图层时,会增加这个Sprite的引用计数 :
remove一个Sprite,会减少该对象的引用计数:
在AutoreleasePool类中,建议不要在堆上建立内存管理池,因为new出来的需要手动delete掉,而栈上的内存管理池则在程序结束后自动销毁:
当前池销毁后, 就从管理类中弹出该池。
自动管理池用clear()函数来释放 , 有Director类来控制自动管理池的释放操作:
_invalid来决定Director是否应该进行逻辑循环,_purgeDierctorInNextLoop在Director调用了end()函数后被设置为true,_restartDirector在restart()后被设置为true,mainloop函数是游戏的主逻辑循环处理函数,drawScene函数进行处理逻辑帧,在每帧尾结束都调用clear函数来清理这一帧所占用的资源,每帧都会回收,如果上一帧进行很多次autorelease而没有帧帧来清理,内存池的性能就会急剧下降,典型的例子是:魂斗罗里主角的“S”型子弹,一帧内产生了几十个子弹资源,如果在这一帧结束后不释放,内存池的性能就会急剧下降,游戏就会显得非常卡。
我们知道,游戏运行都是从Application开始的,在Application中的函数run来将游戏运行起来,在run里面,有个循环不断地执行director->mainloop()来进行逻辑操作。
以上就是我个人对内存管理的浅显的理解,如有不足,欢迎指出~
接触Cocos2d-x时, Cocos2d-x的最新版本已经到了3.2的时代,在学习Cocos2d-x 3.x的时,经常会写点很小的例子,比如创建一个精灵Sprite, 然后设计精灵的动作Action等等,或者添加图层Layer并设置相应属性等等。在创建这些元素的时候,都会先进行这样的操作 :
cocos2d::Sprite* m_sprite = cocos2d::Sprite::create("Picture_name.png"); cocos2d::Layer* m_layer = cocos2d::Layer::create();
在cocos2d-x 3.0之后加入了C++11的内容,于是m_sprite和m_layer的类型就可以这样写了 :
auto m_sprite = cocos2d::Sprite::create("Picture_name.png"); auto m_layer = cocos2d::Layer::create();
当然这些都不是重点, 重点的是这些直接或间接继承Node的类(Node继承自Ref)在创建的时候都使用create() 成员函数来创建, 看看create()的源码 :
Node * Node::create() { Node * ret = new (std::nothrow) Node(); if (ret && ret->init()) { ret->autorelease(); // 重点在这里 } else { CC_SAFE_DELETE(ret); } return ret; }
在create一个对象时, 会调用一个autorelease()函数, 继续跟进 :
Ref* Ref::autorelease() { PoolManager::getInstance()->getCurrentPool()->addObject(this); return this; }
原来是将新创建好的这个添加到当前的管理池中,而addObject实际上是Ref类的函数,Ref中有个存放Ref* 类型的vector,将新创建的对象添加到vector之中。
PoolManager::getInstance()->getCurrentPool()->addObject(this)这行代码中,涉及到了三个类 : PoolManager, AutoreleasePool和Ref类。实际上, Cocos2d-x的内存管理机制就是和这三个类息息相关的,分别来看下源码 :
先看Ref类:
// CCRef.cpp
Ref::Ref()
: _referenceCount(1) // 新创建一个Ref,将引用计数初始化为 1
{
#if CC_ENABLE_SCRIPT_BINDING
static unsigned int uObjectCount = 0;
_luaID = 0;
_ID = ++uObjectCount;
_scriptObject = nullptr;
#endif
#if CC_REF_LEAK_DETECTION
trackRef(this);
#endif
}
Ref::~Ref()
{
#if CC_ENABLE_SCRIPT_BINDING
// if the object is referenced by Lua engine, remove it
if (_luaID)
{
ScriptEngineManager::getInstance()->getScriptEngine()->removeScriptObjectByObject(this);
}
else
{
ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine();
if (pEngine != nullptr && pEngine->getScriptType() == kScriptTypeJavascript)
{
pEngine->removeScriptObjectByObject(this);
}
}
#endif
#if CC_REF_LEAK_DETECTION
if (_referenceCount != 0)
untrackRef(this);
#endif
}
// retain函数将引用计数值增加 1
void Ref::retain()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
++_referenceCount;
}
// release函数将引用计数值减少 1
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
--_referenceCount;
// 如果引用计数等于0, 如果此时自动管理池仍然在清理该对象,则直接报错(assert)
if (_referenceCount == 0)
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
auto poolManager = PoolManager::getInstance();
if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
{
CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
}
#endif
#if CC_REF_LEAK_DETECTION
untrackRef(this);
#endif
delete this;
}
}
// 将此对象交个自动管理池来管理
Ref* Ref::autorelease() { PoolManager::getInstance()->getCurrentPool()->addObject(this); return this; }
unsigned int Ref::getReferenceCount() const
{
return _referenceCount;
}
再看AutoreleasePool类:
// AutoreleasePool.cpp AutoreleasePool::AutoreleasePool() : _name("") #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) , _isClearing(false) #endif { // 初始化时将Ref* 的vector大小设置为150 _managedObjectArray.reserve(150); // 然后将这个自动管理池添加到管理类PoolManager中 PoolManager::getInstance()->push(this); } // 具有特定名字的管理池 方便调试 AutoreleasePool::AutoreleasePool(const std::string &name) : _name(name) #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) , _isClearing(false) #endif { _managedObjectArray.reserve(150); PoolManager::getInstance()->push(this); } // 当前管理池销毁时, 从管理类中弹出该管理池 AutoreleasePool::~AutoreleasePool() { CCLOGINFO("deallocing AutoreleasePool: %p", this); clear(); PoolManager::getInstance()->pop(); } // 添加对象到vector中 void AutoreleasePool::addObject(Ref* object) { _managedObjectArray.push_back(object); } void AutoreleasePool::clear() { #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) _isClearing = true; #endif for (const auto &obj : _managedObjectArray) { obj->release(); } _managedObjectArray.clear(); #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) _isClearing = false; #endif } bool AutoreleasePool::contains(Ref* object) const { for (const auto& obj : _managedObjectArray) { if (obj == object) return true; } return false; } void AutoreleasePool::dump() { CCLOG("autorelease pool: %s, number of managed object %d\n", _name.c_str(), static_cast<int>(_managedObjectArray.size())); CCLOG("%20s%20s%20s", "Object pointer", "Object id", "reference count"); for (const auto &obj : _managedObjectArray) { CC_UNUSED_PARAM(obj); CCLOG("%20p%20u\n", obj, obj->getReferenceCount()); } }
最后看看PoolManager类:
// CCPoolManager.cpp PoolManager* PoolManager::s_singleInstance = nullptr; PoolManager* PoolManager::getInstance() { if (s_singleInstance == nullptr) { s_singleInstance = new (std::nothrow) PoolManager(); //assert(nullptr != s_singleInstance); // 在创建单例时, 添加第一个管理池 new AutoreleasePool("cocos2d autorelease pool"); } return s_singleInstance; } // 销毁单例模式 void PoolManager::destroyInstance() { delete s_singleInstance; s_singleInstance = nullptr; } // 初始化时设置管理类的vector的大小为10 PoolManager::PoolManager() { _releasePoolStack.reserve(10); } // 一个管理类PoolManager销毁时,销毁它管理的所有管理池 PoolManager::~PoolManager() { CCLOGINFO("deallocing PoolManager: %p", this); while (!_releasePoolStack.empty()) { AutoreleasePool* pool = _releasePoolStack.back(); delete pool; } } // 获得当前管理池 AutoreleasePool* PoolManager::getCurrentPool() const { return _releasePoolStack.back(); } // 判断一个Ref或其子类对象是否在管理池中 bool PoolManager::isObjectInPools(Ref* obj) const { for (const auto& pool : _releasePoolStack) { if (pool->contains(obj)) return true; } return false; } // 添加一个管理池 void PoolManager::push(AutoreleasePool *pool) { _releasePoolStack.push_back(pool); } // 弹出最新的一个管理池 void PoolManager::pop() { CC_ASSERT(!_releasePoolStack.empty()); _releasePoolStack.pop_back(); }
源码都在这里了,重新分析开头说的那个例子,在创建一个Sprite或Layer时, 先调用autorelease函数 :
Ref* Ref::autorelease() { PoolManager::getInstance()->getCurrentPool()->addObject(this); return this; }
然后就会先获得一个管理自动管理内存池的类PoolManager的单例 :
PoolManager* PoolManager::getInstance() { if (s_singleInstance == nullptr) { s_singleInstance = new (std::nothrow) PoolManager(); //assert(nullptr != s_singleInstance); // Add the first auto release pool new AutoreleasePool("cocos2d autorelease pool"); } return s_singleInstance; }
第一次使用单例,会新建一个,会添加一个自动管理池 :
AutoreleasePool::AutoreleasePool(const std::string &name) : _name(name) #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0) , _isClearing(false) #endif { _managedObjectArray.reserve(150); PoolManager::getInstance()->push(this); }
然后将该管理池push到PoolManager中的vector中,该vector是管理AutoreleasePool的 :
void PoolManager::push(AutoreleasePool *pool) { _releasePoolStack.push_back(pool); // std::vector<AutoreleasePool*> _releasePoolStack; }
然后由该单例获得管理这个Sprite或Layer的AutoreleasePool,并将该Sprite或Layer添加到该自动管理池当中 :
void AutoreleasePool::addObject(Ref* object) { _managedObjectArray.push_back(object); // std::vector<Ref*> _managedObjectArray; }
然后如果将这个Sprite添加到图层时,会增加这个Sprite的引用计数 :
void pushBack(T object) { CCASSERT(object != nullptr, "The object should not be nullptr"); _data.push_back( object ); object->retain(); // 增加引用计数 }
remove一个Sprite,会减少该对象的引用计数:
void clear() { for( auto it = std::begin(_data); it != std::end(_data); ++it ) { (*it)->release(); } _data.clear(); }
在AutoreleasePool类中,建议不要在堆上建立内存管理池,因为new出来的需要手动delete掉,而栈上的内存管理池则在程序结束后自动销毁:
AutoreleasePool::~AutoreleasePool() { CCLOGINFO("deallocing AutoreleasePool: %p", this); clear(); PoolManager::getInstance()->pop(); }
当前池销毁后, 就从管理类中弹出该池。
自动管理池用clear()函数来释放 , 有Director类来控制自动管理池的释放操作:
void DisplayLinkDirector::mainLoop() { if (_purgeDirectorInNextLoop) { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (_restartDirectorInNextLoop) { _restartDirectorInNextLoop = false; restartDirector(); } else if (! _invalid) { drawScene(); // release the objects PoolManager::getInstance()->getCurrentPool()->clear(); } }
_invalid来决定Director是否应该进行逻辑循环,_purgeDierctorInNextLoop在Director调用了end()函数后被设置为true,_restartDirector在restart()后被设置为true,mainloop函数是游戏的主逻辑循环处理函数,drawScene函数进行处理逻辑帧,在每帧尾结束都调用clear函数来清理这一帧所占用的资源,每帧都会回收,如果上一帧进行很多次autorelease而没有帧帧来清理,内存池的性能就会急剧下降,典型的例子是:魂斗罗里主角的“S”型子弹,一帧内产生了几十个子弹资源,如果在这一帧结束后不释放,内存池的性能就会急剧下降,游戏就会显得非常卡。
我们知道,游戏运行都是从Application开始的,在Application中的函数run来将游戏运行起来,在run里面,有个循环不断地执行director->mainloop()来进行逻辑操作。
以上就是我个人对内存管理的浅显的理解,如有不足,欢迎指出~
相关文章推荐
- Chipmunk引擎在Cocos2d-js中的使用
- cocos code ide--js 中获取cocostudio中按钮以及函数回调
- cocos2d-x游戏循环和日程安排
- cocos2dx 3.x UI之Button的初使用
- cocospod国外镜像源不能用
- cocos3 singleton
- 【Cocos2dx】根据窗口大小进行拉伸
- quick-cocos2d-x(一)开发环境配置
- cocos2d-x 3.x 创造物理世界
- [Cocos2d塔防游戏开发]Cocos2dx-3.X完成塔防游戏《王国保卫战》--防御塔(七)之士兵
- [Cocos2d塔防游戏开发]Cocos2dx-3.X完成塔防游戏《王国保卫战》--防御塔(六)之兵营塔
- cocos2d-x项目打包Android apk(二)
- 由quick2升级到cocos2d-x3.8的一些体会
- 解决cocos2dx CCArmature动画在部分型号的安卓手机上播放不正常的问题
- 在公司3个月的一些积累
- 如何在你的cocos2dx中使用sqlite3
- cocos 怎么实现技能状态的cd
- cocos2d-x设计模式发掘之九:委托模式
- cocos2d-x设计模式发掘之八:中介者模式
- Cocosd-x设计模式之七:防御式编程模式