zoukankan      html  css  js  c++  java
  • 【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
    ", _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
    ", 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()来进行逻辑操作。


    以上就是我个人对内存管理的浅显的理解,如有不足,欢迎指出~


  • 相关阅读:
    夜半随笔
    VC6.0 工程转到VS2008一些问题
    没有找到MSVCR90.dll,因此这个应用程序未能启动,重新安装应用程序可解决
    开源中国
    保存桌面图标的次序吧
    2008下,错误:fatal error C1853
    生成成功,调试时出现错误导致中断Unhandled exception at 0x41cd7fb0 in webcam.exe: 0xC0000005: Access violatio
    opencv安装可能没注意的细节
    error PRJ0003 : 生成“cl.exe”时出错
    wince对话框添加菜单
  • 原文地址:https://www.cnblogs.com/averson/p/5096042.html
Copyright © 2011-2022 走看看