Summary of memory management for Cocos2d x

  • 2020-05-30 21:03:22
  • OfStack

The core of the Cocos2d-x engine is written in C++, and memory management is an impossible hurdle for any game developer who USES the engine.

There are a lot of reference materials on the Internet about Cocos2d-x memory management, some of which are quite detailed, because I don't want to spend too much time on memory management, but just describe the ideas more clearly.

1. Object memory reference counting

The basic principle of Cocos2d-x memory management is object memory reference counting. Cocos2d-x puts the implementation of memory reference counting in the top-level parent class CCObject. Here, the members and methods of CCObject involved in reference counting are extracted:


class CC_DLL CCObject : public CCCopying
{
public:
    ...   ... 
protected:
    // count of references
    unsigned int        m_uReference;
    // count of autorelease
    unsigned int        m_uAutoReleaseCount;
public:
    void release(void);
    void retain(void);
    CCObject* autorelease(void);
     ...   ... .
}
CCObject::CCObject(void)
: m_nLuaID(0)
, m_uReference(1) // when the object is created, the reference count of it is 1
, m_uAutoReleaseCount(0)
{
   ...   ... 
}
void CCObject::release(void)
{
    CCAssert(m_uReference > 0, "reference count should greater than 0");
     � m_uReference;
    if (m_uReference == 0)
    {
        delete this;
    }
}
void CCObject::retain(void)
{
    CCAssert(m_uReference > 0, "reference count should greater than 0");
    ++m_uReference;
}
CCObject* CCObject::autorelease(void)
{
    CCPoolManager::sharedPoolManager()->addObject(this);
    return this;
}

Forget autorelease and m_uAutoReleaseCount (more on that later). The core field of the count is m_uReference, as you can see:

* when 1 Object is initialized (by new), m_uReference = 1;
* when the retain method of Object is called, m_uReference++;
* when the release method of Object is called, m_uReference is used. If m_uReference is reduced to 0, delete is used.

2. Manual object memory management

Based on the above principle of object memory reference counting, we obtain the following basic mode of manual object memory management under Cocos2d-x:


CCObject *obj = new CCObject();
obj->init();
 ... .  ... 
obj->release();
 in Cocos2d-x In the CCDirector is 1 Typical of manual memory management: 
CCDirector* CCDirector::sharedDirector(void)
{
    if (!s_SharedDirector)
    {
        s_SharedDirector = new CCDisplayLinkDirector();
        s_SharedDirector->init();
    }
    return s_SharedDirector;
}
void CCDirector::purgeDirector()
{
     ...   ... 
    // delete CCDirector
    release();
}

3. Automatic object memory management

The so-called "automatic object memory management" means that any object that is no longer needed will be freed for you by the Cocos2d-x engine without you having to manually call the Release method again.

Automatic object memory management obviously follows the memory reference count rule as well, freeing the object's memory only when the object count goes to zero.

The typical patterns of automatic object memory management are as follows:


CCYourClass *CCYourClass::create()
{
    CCYourClass*pRet = new CCYourClass();
    if (pRet && pRet->init())
    {
        pRet->autorelease();
        return pRet;
    }
    else
    {
        CC_SAFE_DELETE(pRet);
        return NULL;
    }
}

We create objects using a singleton pattern, which differs from the manual pattern in that there is an extra autorelease call after init. Here again, the implementation of autorelease call is extracted once:


CCObject* CCObject::autorelease(void)
{
    CCPoolManager::sharedPoolManager()->addObject(this);
    return this;
}

Trace addObject method:


// cocoa/CCAutoreleasePool.cpp
void CCPoolManager::addObject(CCObject* pObject)
{
    getCurReleasePool()->addObject(pObject);
}
void CCAutoreleasePool::addObject(CCObject* pObject)
{
    m_pManagedObjectArray->addObject(pObject);
    CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");
    ++(pObject->m_uAutoReleaseCount);
    pObject->release(); // no ref count, in this case autorelease pool added.
}
// cocoa/CCArray.cpp
void CCArray::addObject(CCObject* object)                                                                                                   
{                                                                                                                                          
    ccArrayAppendObjectWithResize(data, object);                             
}  
// support/data_support/ccCArray.cpp
void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)                                                                          
{                                                                                                                   
    ccArrayEnsureExtraCapacity(arr, 1);                                                              
    ccArrayAppendObject(arr, object);                                         
}
void ccArrayAppendObject(ccArray *arr, CCObject* object)
{
    CCAssert(object != NULL, "Invalid parameter!");
    object->retain();
    arr->arr[arr->num] = object;
    arr->num++;
}

Call level is quite deep, the class involved is also numerous, here summed up 1.

Cocos2d-x's automatic object memory management is based on object reference counting and CCAutoreleasePool (automatic release pool). The reference count has been mentioned before, but the automatic release pool is the only one. The basic class hierarchy of Cocos2d-x for automatic object memory management is as follows:


    CCPoolManager class  ( Automatic release pool manager )
         �  CCArray*    m_pReleasePoolStack;  Automatic release pool stack, store CCAutoreleasePool The class instance) 

    CCAutoreleasePool class 
         �  CCArray*    m_pManagedObjectArray;  (array of managed objects) 

CCObject has two fields for memory counting and automatic management: m_uReference and m_uAutoReleaseCount. In the previous manual management mode, I only mentioned m_uReference, which is the time for m_uAutoReleaseCount to appear. Let's take a look at what the two important fields are and what they represent in the various stages following the creation of the auto-release object:

CCYourClass*pRet = new CCYourClass();    m_uReference = 1 ;  m_uAutoReleaseCount = 0 ; 
pRet->init() ;                            m_uReference = 1 ;  m_uAutoReleaseCount = 0 ; 
pRet->autorelease();                    
    m_pManagedObjectArray->addObject(pObject); m_uReference = 2 ;  m_uAutoReleaseCount = 0 ; 
    ++(pObject->m_uAutoReleaseCount);          m_uReference = 2 ;  m_uAutoReleaseCount = 1 ; 
    pObject->release();                        m_uReference = 1 ;  m_uAutoReleaseCount = 1 ; 

Before calling autorelease, the two values are the same as in manual mode. After autorelease, the m_uReference value does not change, but m_uAutoReleaseCount is added by 1.

m_uAutoReleaseCount this field name is easy to let a person misunderstanding, thought that is a counter, but in fact most of the time it is the role of a logo, with one in the previous version code Boolean fields m_bManaged, seems to be m_uAutoReleaseCount later replaced, therefore m_uAutoReleaseCount both m_bManaged meaning, that is to say whether the object pool under the control of the automatic release, if the pool under the control of the automatic release, The auto-release pool periodically calls the release method of object until the object memory count drops to 0 and is actually released. Otherwise, the object cannot be automatically released by the automatic release pool. release needs to be manually released. This is a very important understanding, and we can use it later.


4. Automatic release time

We've already put object into autoreleasePool via autorelease, so when exactly will the object be released? The answer is to perform an automatic memory object release operation once per frame.

In "Hello, Cocos2d-x" 1, we have explained that the entire Cocos2d-x engine is driven by the guardedRun function of GLThread, which calls the onDrawFrame method of Render in the form of "dead loop" (the actual frame drawing frequency is affected by the screen vertsym signal), and the final program enters the CCDirector::mainLoop method. That is to say, mainLoop is executed once per frame. Let's look at the implementation of mainLoop:


void CCDisplayLinkDirector::mainLoop(void)
{
    if (m_bPurgeDirecotorInNextLoop)
    {
        m_bPurgeDirecotorInNextLoop = false;
        purgeDirector();
    }
    else if (! m_bInvalid)
     {
         drawScene();
         // release the objects
         CCPoolManager::sharedPoolManager()->pop();
     }
}

This time we're not going to focus on drawScene, we're going to focus on CCPoolManager::sharedPoolManager()- > pop(), CCPoolManager's pop method is executed 1 time per frame, apparently without the game exiting (m_bPurgeDirecotorInNextLoop), which is the starting point for the automatic release pool execution.


void CCPoolManager::pop()
{
    if (! m_pCurReleasePool)
    {
        return;
    }
     int nCount = m_pReleasePoolStack->count();
    m_pCurReleasePool->clear();
      if(nCount > 1)
      {
        m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
        m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount  �  2);
    }
}

The way to actually release an object is m_pCurReleasePool- > clear ().


void CCAutoreleasePool::clear()
{
    if(m_pManagedObjectArray->count() > 0)
    {
        CCObject* pObj = NULL;
        CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)
        {
            if(!pObj)
                break;
             � (pObj->m_uAutoReleaseCount);
        }
        m_pManagedObjectArray->removeAllObjects();
    }
}
void CCArray::removeAllObjects()     
{   
    ccArrayRemoveAllObjects(data);                    
}
void ccArrayRemoveAllObjects(ccArray *arr)                    
{                       
    while( arr->num > 0 )                      
    {                    
        (arr->arr[--arr->num])->release();               
    }                    
}

As expected, the current auto-release pool traverses each "controlled" Object, with m_uAutoReleaseCount, and calls the release method of that object.

Let's follow the release process to see the change of m_uAutoReleaseCount and m_uReference values:


CCPoolManager::sharedPoolManager()->pop() ;   m_uReference = 0 ;  m_uAutoReleaseCount = 0 ; 

5. Initialization of automatic release pool

When did the automatic release pool itself appear? To review 1 the initialization process of Cocos2d-x engine (android version), the engine initialization is carried out in Render's onSurfaceCreated method. It is not difficult to trace the following code:


//hellocpp/jni/hellocpp/main.cpp
Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit {

    // Here, CCDirector The first 1 Time is created 
    if (!CCDirector::sharedDirector()->getOpenGLView())
    {
        CCEGLView *view = CCEGLView::sharedOpenGLView();
        view->setFrameSize(w, h);
        AppDelegate *pAppDelegate = new AppDelegate();
        CCApplication::sharedApplication()->run();
    }
}

CCDirector* CCDirector::sharedDirector(void)
{
    if (!s_SharedDirector)
    {
        s_SharedDirector = new CCDisplayLinkDirector();
        s_SharedDirector->init();  
    }
    return s_SharedDirector;
}
bool CCDirector::init(void)
{
    setDefaultValues();
     ...   ... 
    // create autorelease pool
    CCPoolManager::sharedPoolManager()->push();
    return true;
}

6. Explore the automatic memory release of Cocos2d-x kernel objects

Previously, we had a basic understanding of the principle of automatic memory release of Cocos2D-x. If you have looked through the kernel source code for Cocos2d-x, you will find that many kernel objects are managed in singleton mode create, which means that autorelease is used to put itself into the automatic memory release pool.

For example, we have seen this code in HelloCpp:


//HelloWorldScene.cpp
bool HelloWorld::init() {
      ... .  ... .
    // add "HelloWorld" splash screen"
    CCSprite* pSprite = CCSprite::create("HelloWorld.png");
    // position the sprite on the center of the screen
    pSprite->setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
    // add the sprite as a child to this layer
    this->addChild(pSprite, 0);
     ...   ... 
}

CCSprite USES the automatic memory management mode create object (cocos2dx/sprite_nodes/ CCSprite.cpp), and then adds itself to the HelloWorld instance of CCLayer. According to the above analysis, after the end of create, m_uReference of CCSprite object = 1; m_uAutoReleaseCount = 1. If so, the object will be released by CCPoolManager on the next frame. But we can still see the Sprite on the screen. What's going on?

The key is this- > addChild(pSprite, 0) in this line of code. The addChild method is implemented in CCLayer's parent class CCNode:


//  cocos2dx/base_nodes/CCNode.cpp
void CCNode::addChild(CCNode *child, int zOrder, int tag)
{
     ...   ... 
    if( ! m_pChildren )
    {
        this->childrenAlloc();
    }
    this->insertChild(child, zOrder);
     ...   ... 
}
void CCNode::insertChild(CCNode* child, int z)
{
    m_bReorderChildDirty = true;
    ccArrayAppendObjectWithResize(m_pChildren->data, child);
    child->_setZOrder(z);
}
void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)
{
    ccArrayEnsureExtraCapacity(arr, 1);
    ccArrayAppendObject(arr, object);
}
void ccArrayAppendObject(ccArray *arr, CCObject* object)
{
    CCAssert(object != NULL, "Invalid parameter!");
    object->retain();
    arr->arr[arr->num] = object;
    arr->num++;
}

It's another 1 series of method calls, and finally we come to the ccArrayAppendObject method and see the unfamiliar and familiar retain method calls.

When we introduced CCObject at the beginning of this article, we learned that retain is a method of CCObject to increase the m_uReference count. In fact, retain also implies the meaning of "reservation".

In the complete this - > m_uReference = 2 after addChild(pSprite, 0) is called, CSprite object m_uReference = 2; m_uAutoReleaseCount = 1, that's critical.

Let's go through the process of releasing object in the automatic release pool again in our mind: pool release m_uReference, pool release m_uAutoReleaseCount. After 1 frame, the two values become m_uReference = 1; m_uAutoReleaseCount = 0. Remember the other non-count meaning of m_uAutoReleaseCount mentioned earlier, that is, whether the object is "controlled" or not. Now the value is 0, it is obviously no longer under the control of the auto-release pool. Even if another 100 memory auto-releases are performed later, the survival of object will not be affected.

To release the Sprite later, we still need to manually call release or call its autorelease method.


Related articles: