cocos2dx 3.x内存管理源代码追踪

cocos2dx 3.x内存管理源代码追踪,第1张

概述cocos2dx的内存管理,我有空追踪一下源代码 理解一下。 我们都知道 cocos2dx是靠引用计数实现的,顺便加入了对象池的管理。 涉及到一下几个类,AutorealsePool,PoolManager,Ref,DisplayLinkDirector. 函数: mainLoop,retain,release,autoRealse. 1.首先跳转到Application::run() ,,, i

cocos2dx的内存管理,我有空追踪一下源代码 理解一下。
我们都知道 cocos2dx是靠引用计数实现的,顺便加入了对象池的管理。

涉及到一下几个类,autorealsePool,PoolManager,Ref,displaylinkDirector.
函数:
mainLoop,retain,release,autoRealse.

1.首先跳转到Application::run()

,
int Application::run()
{
PVRFrameEnableControlWindow(false);

// Main message loop:LARGE_INTEGER nLast;LARGE_INTEGER nNow;queryPerformanceCounter(&nLast);initGLContextAttrs();// Initialize instance and cocos2d.if (!applicationDIDFinishLaunching()){    return 0;}auto director = Director::getInstance();auto glvIEw = director->getopenGLVIEw();// Retain glvIEw to avoID glvIEw being released in the while loopglvIEw->retain();//如果窗口未关闭  就是一个while死循环while(!glvIEw->windowshouldClose()){    queryPerformanceCounter(&nNow);    if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)    {        nLast.QuadPart = nNow.QuadPart - (nNow.QuadPart % _animationInterval.QuadPart);        director->mainLoop();        glvIEw->pollEvents();    }    else    {        Sleep(1);    }}// Director should still do a cleanup if the window was closed manually.if (glvIEw->isOpenglready()){    director->end();    director->mainLoop();    director = nullptr;}glvIEw->release();return true;

}
,

2.跳转到mainLoop函数中
voID displaylinkDirector::mainLoop(){    if (_purgeDirectorInNextLoop)    {        _purgeDirectorInNextLoop = false;        purgeDirector();    }    else if (_restartDirectorInNextLoop)    {        _restartDirectorInNextLoop = false;        restartDirector();    }    else if (! _invalID)    {        drawScene();        // release the objects        auto count = PoolManager::getInstance()->getPoolCount();//这个函数我自己加的,方便打印        cclOG("$$$$$$$$$$=========== %d",count);        cclOG("&&&&&&&&&&=========== %s",PoolManager::getInstance()->getCurrentPool()->getname());//这个函数我自己加的,方便打印 auto pool = PoolManager::getInstance()->getCurrentPool();        cclOG("前面pool count is %d",pool->getCount());        PoolManager::getInstance()->getCurrentPool()->clear();        cclOG("后面pool count is %d",pool->getCount());    }}

其他的先不管 ,就看PoolManager::getInstance()->getCurrentPool()->clear();这行代码。

PoolManager这个类,从字面上的意思它是个管理类,所以它被写成单例模式。单例的代码:

PoolManager* PoolManager::getInstance(){    if (s_singleInstance == nullptr)    {        s_singleInstance = new (std::nothrow) PoolManager();        // Add the first auto release pool        new autoreleasePool("cocos2d autorelease pool");    }    return s_singleInstance;}

以上代码是个典型的单例模式写法,但是有行代码
new autoreleasePool(“cocos2d autorelease pool”);我从没看过生成一个对象 却不用其他的引用或者指针指向它,写的好奇怪。

3.查看autoreleasePool这个类

查看他的构造函数:

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类生成对象的时候,在构造函数中调用
PoolManager::getInstance()->push(this)。

voID PoolManager::push(autoreleasePool *pool){    _releasePoolStack.push_back(pool);}

看懂了吧,原来push函数原来在这里调用了,但是这种写法很奇怪,可能是我c++功力太浅了,要是我直接在单例getInstance函数中push了。

PoolManager类成员变量:
std::vector 《autoreleasePool*》 _releasePoolStack
其实这个_releasePoolStack断点的时候,只有在生成单例模式的push了一个autoreleasePool对象,_releasePoolStack的size() = 1。
所以在mainLoop中:

auto count = PoolManager::getInstance()->getPoolCount();cclOG("$$$$$$$$$$=========== %d",count);定义如下int PoolManager::getPoolCount(){    return _releasePoolStack.size();}

打印始终都是size等于1

autoreleasePool* PoolManager::getCurrentPool() const{    return _releasePoolStack.back();}

这个函数返回的是_releasePoolStack的末尾引用
而_releasePoolStack的数据size其实只有一条,既然是一条,那干嘛用vector呢?这点没弄明白 难道是因为喜欢用vector存贮数据?

接下来看autoreleasePool::clear()函数

voID autoreleasePool::clear(){#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)    _isClearing = true;#endif    std::vector<Ref*> releasings;    releasings.swap(_managedobjectArray);    for (const auto &obj : releasings)    {        obj->release();    }#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)    _isClearing = false;#endif}

_managedobjectArray数据被清空,这个函数中有个重要的知识点 那就是vector的swap的用法,swap可以清空vector申请的内存,而clear却不一定。详情可参照:

简单的程序诠释C++ STL算法系列之十五:swap

追踪release代码:

voID Ref::release(){    CCASSERT(_referenceCount > 0,"reference count should be greater than 0");    --_referenceCount;    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; } }

release函数 就是将计数自减一,将0的Ref析构掉。

每个集成了Ref的对象 都有属性_referenceCount,那它在哪里自增呢?

举个例子,去看看Sprite的create函数

Sprite* Sprite::create(const std::string& filename){    Sprite *sprite = new (std::nothrow) Sprite();    if (sprite && sprite->initWithfile(filename))    {        sprite->autorelease();        return sprite;    }    CC_SAFE_DELETE(sprite);    return nullptr;}

autorelease函数的定义

Ref* Ref::autorelease(){    PoolManager::getInstance()->getCurrentPool()->addobject(this);    return this;}

再追踪addobject函数

voID autoreleasePool::addobject(Ref* object){    _managedobjectArray.push_back(object);}

奇怪 不是说好 一个精灵创建的时候 引用计数就加1吗?但是我们找了半天没发现,原来是Ref构造的时候就默认设置成1了,如下代码:

Ref::Ref(): _referenceCount(1) // when the Ref is created,the reference count of it is 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}

我们知道 cocos2dx 中node sprite都是继承Ref,C++中对象生成的时候,先调用父类的构造函数 ,再调用自己的构造函数。

4.引用计数 自增和自减 在哪些地方出现? addChild()函数
Node下面好几个addChild函数,都是函数重载,最终都用到这个函数:
addChildHelper()
voID Node::addChildHelper(Node* child,int localZOrder,int tag,const std::string &name,bool setTag){    if (_children.empty())    {        this->childrenAlloc();    }    this->insertChild(child,localZOrder);    if (setTag)        child->setTag(tag);    else        child->setname(name);    child->setParent(this);    child->setorderOfArrival(s_globalOrderOfArrival++);#if CC_USE_PHYSICS    // Recursive add children with which have physics body.    auto scene = this->getScene();    if (scene && scene->getPhysicsWorld())    {        scene->addChildtophysicsWorld(child);    }#endif    if( _running )    {        child->onEnter();        // prevent onEnterTransitionDIDFinish to be called twice when a node is added in onEnter        if (_isTransitionFinished)        {            child->onEnterTransitionDIDFinish();        }    }    if (_cascadecolorEnabled)    {        updateCascadecolor();    }    if (_cascadeOpacityEnabled)    {        updateCascadeOpacity();    }}

找到insertChild函数:

voID Node::insertChild(Node* child,int z){    _transformUpdated = true;    _reorderChildDirty = true;    _children.pushBack(child);    child->_localZOrder = z;}

接着找到pushBack函数,开始以为就是vector中的函数呢,其实不然:

voID pushBack(T object)    {        CCASSERT(object != nullptr,"The object should not be nullptr");        _data.push_back( object );        object->retain();    }

终于找到retain函数了啊!

setParent函数:
voID Node::setParent(Node * parent){    _parent = parent;    _transformUpdated = _transformDirty = _inverseDirty = true;}

发现这个函数根本没有使引用计数增加,而我在项目中使用setParent的时候 根本一个节点没有显示出来,所以这个函数只是一个指向关系,并未将孩子真正加入到父节点中,所以我基本不使用它,我都是使用addChild函数。

所以当你生成一个Sprite并把它加入到layer之后,他的引用计数是2。

removeFromParent函数:
voID Node::removeFromParent(){    this->removeFromParentAndCleanup(true);}voID Node::removeFromParentAndCleanup(bool cleanup){    if (_parent != nullptr)    {        _parent->removeChild(this,cleanup);    } }

我们可以看到这两个函数几乎等同,接着removeChild:

voID Node::removeChild(Node* child,bool cleanup /* = true */){    // explicit nil handling    if (_children.empty())    {        return;    }    ssize_t index = _children.getIndex(child);    if( index != CC_INVALID_INDEX )        this->detachChild( child,index,cleanup );}

接着detachChild:

voID Node::detachChild(Node *child,ssize_t childindex,bool doCleanup){    // important:    // -1st do onExit    // -2nd cleanup    if (_running)    {        child->onExitTransitionDIDStart();        child->onExit();    }#if CC_USE_PHYSICS    child->removeFromPhysicsWorld();#endif    // If you don't do cleanup,the child's actions will not get removed and the    // its scheduledSelectors_ dict will not get released!    if (doCleanup)    {        child->cleanup();    }    // set parent nil at the end    child->setParent(nullptr);    _children.erase(childindex);}

接着cleanup函数:

voID Node::cleanup(){    // actions    this->stopAllActions();    this->unscheduleAllCallbacks();#if CC_ENABLE_SCRIPT_BINDING    if ( _scriptType != kScriptTypeNone)    {        int action = kNodeOnCleanup;        BasicScriptData data(this,(voID*)&action);        ScriptEvent scriptEvent(kNodeEvent,(voID*)&data);        ScriptEngineManager::getInstance()->getScriptEngine()->sendEvent(&scriptEvent);    }#endif // #if CC_ENABLE_SCRIPT_BINDING    // timers    for( const auto &child: _children)        child->cleanup();}

可以看到 递归调用了cleanup函数 子节点都是处理一些事情,detachChild函数最后将子节点的父节点设置为空,最后看erase函数:

iterator erase(ssize_t index)    {        CCASSERT(!_data.empty() && index >=0 && index < size(),"InvalID index!");        auto it = std::next( begin(),index );        (*it)->release();        return _data.erase(it);    }

终于找到了release函数了。

有时候我容易将 std::vector和cocos2dx封装的Vector搞混淆,真是眼睛不好啊!现在内存管理的基本思路搞清楚了。

总结

以上是内存溢出为你收集整理的cocos2dx 3.x内存管理源代码追踪全部内容,希望文章能够帮你解决cocos2dx 3.x内存管理源代码追踪所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/web/1081535.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-27
下一篇 2022-05-27

发表评论

登录后才能评论

评论列表(0条)

保存