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()来进行逻辑操作。


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


  • 相关阅读:
    stenciljs 学习四 组件装饰器
    stenciljs 学习三 组件生命周期
    stenciljs 学习二 pwa 简单应用开发
    stenciljs ionic 团队开发的方便web 组件框架
    stenciljs 学习一 web 组件开发
    使用npm init快速创建web 应用
    adnanh webhook 框架 hook rule
    adnanh webhook 框架 hook 定义
    adnanh webhook 框架request values 说明
    adnanh webhook 框架execute-command 以及参数传递处理
  • 原文地址:https://www.cnblogs.com/averson/p/5096042.html
Copyright © 2011-2022 走看看