【Cocos2d-x 3.x】内存管理机制与源码分析
侯捷先生说过这么一句话 : 源码之前,了无秘密。 要了解Cocos2d-x的内存管理机制,就得阅读源码。
接触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()来进行逻辑操作。
以上就是我个人对内存管理的浅显的理解,如有不足,欢迎指出~
【Cocos2d-x 3.x】内存管理机制与源码分析的更多相关文章
- Python内存管理机制-《源码解析》
Python内存管理机制 Python 内存管理分层架构 /* An object allocator for Python. Here is an introduction to the layer ...
- Guava cacha 机制及源码分析
1.ehcahce 什么时候用比较好:2.问题:当有个消息的key不在guava里面的话,如果大量的消息过来,会同时请求数据库吗?还是只有一个请求数据库,其他的等待第一个把数据从DB加载到Guava中 ...
- spark内存管理器--MemoryManager源码解析
MemoryManager内存管理器 内存管理器可以说是spark内核中最重要的基础模块之一,shuffle时的排序,rdd缓存,展开内存,广播变量,Task运行结果的存储等等,凡是需要使用内存的地方 ...
- Cocos2d-X3.0 刨根问底(七)----- 事件机制Event源码分析
这一章,我们来分析Cocos2d-x 事件机制相关的源码, 根据Cocos2d-x的工程目录,我们可以找到所有关于事件的源码都存在放在下图所示的目录中. 从这个event_dispatcher目录中的 ...
- android的消息处理机制(图文+源码分析)—Looper/Handler/Message[转]
from:http://www.jb51.net/article/33514.htm 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想.andro ...
- Android中Handler的消息处理机制以及源码分析
在实际项目当中,一个很常见的需求场景就是在根据子线程当中的数据去更新ui.我们知道,android中ui是单线程模型的,就是只能在UI线程(也称为主线程)中更新ui.而一些耗时操作,比如数据库,网络请 ...
- 内存管理pbuf.c源码解析——LwIP学习
声明:个人所写所有博客均为自己在学习中的记录与感想,或为在学习中总结他人学习成果,但因本人才疏学浅,如果大家在阅读过程中发现错误,欢迎大家指正. 本文自己尚有认为写的不完整的地方,源代码没有完全理清, ...
- storm事件管理器EventManager源码分析-event.clj
storm事件管理器定义在event.clj中,主要功能就是通过独立线程执行"事件处理函数".我们可以将"事件处理函数"添加到EventManager的阻塞队列 ...
- Redis 内存管理 源码分析
要想了解redis底层的内存管理是如何进行的,直接看源码绝对是一个很好的选择 下面是我添加了详细注释的源码,需要注意的是,为了便于源码分析,我把redis为了弥补平台差异的那部分代码删了,只需要知道有 ...
随机推荐
- MRPT笔记——MRPT在VS2013中的配置
Mobile Robot Programming Toolkit (MRPT)是一个跨平台的.开源的C++库,旨在帮助机器人研究员设计和实现SLAM.机器视觉和运动规划(避障)的算法. MRPT为移动 ...
- OC基础--Xcode 模板修改和文档安装
修改项目模板 项目模板就是创建工程的时候选择的某一个条目, Xcode会根据选择的条目生成固定格式的项目 如何修改项目模板 找到Xcode, 右键"显示包内容" 打开"/ ...
- linux内核学习之七 可执行程序的装载和运行
一 程序的装载和运行的基本知识补充 1 当进程开始执行一个新的程序时,从父进程继承的所有页被释放,以便在新的用户地址空间开始执行新的计算,甚至进程的特权都可能发生改变,但是,进程的PID不会改变 ...
- 把本地代码同步到github
2016-05-03 12:52:00 把代码同步到远程github,还算比较顺利.主要参考了以下两个博客,谢谢 http://blog.csdn.net/duxinfeng2010/article/ ...
- 原生js实现放大镜效果
今天做任务的时候,有一个任务就是让实现电商网站常用的放大镜效果,类似于这样的效果,之前并没有做过这种放大镜效果,刚开始的思路是对图片进行裁剪,但是后来发现实在是难以实现,于是求助了万能的谷歌,发现一个 ...
- 006-Map、Tuple、Zip实战解析
006-Map.Tuple.Zip实战解析 实战代码 Map实战 本身是映射,映射关系在实际中应用非常广泛,比如:配置信息都是Key-Value形式 键值对不存在下边关系 是一个immutable(不 ...
- Android ListView加载更多
先看效果: ListView的footer布局: <?xml version="1.0" encoding="utf-8"?> <Relati ...
- Oracle计算时间函数(对时间的加减numtodsinterval、numtoyminterval) (转)
原文来自:http://blog.itpub.net/756652/viewspace-697256/ 11g interval分区,按天分区,需要用到函数numtodsinterval. cre ...
- 学习Excel 十大函数
云课堂视频教程 笔记总结: URL:http://study.163.com/course/courseLearn.htm?courseId=1009026#/learn/video?lessonId ...
- js②
操作符 ECMA-262描述了一组用于操作数据值的操作符,包括算术操作符(如加号和减号).位操作符.关系操作符和相等操作符. 一元操作符 递增和递减操作符(++ --) 一元加和减操作符 对非数值应用 ...