The core of the Cocos2d-x engine is written in C++, so memory management is an unavoidable hurdle for all game developers using the engine.
Regarding Cocos2d-x memory management, there are already many reference materials on the Internet, and some of them are written in detail, because I don’t want to spend more money on memory management, but I just describe the ideas more clearly.
1. Object memory reference count
The basic principle of Cocos2d-x memory management is object memory reference counting. Cocos2d-x places the implementation of memory reference counting in the top-level parent class CCObject. Here the members and methods of the CCObject involving reference counting are excerpted:
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;
}
No consideration is given to autorelease and m_uAutoReleaseCount (more on the follow-up). The core field of the count is m_uReference, you can see:
* When an Object is initialized (when it is new), m_uReference = 1;
* When the retain method of this Object is called, m_uReference++;
* When the release method of this Object is called, m_uReference--, if m_uReference is reduced to 0, delete the Object.
2. Manual object memory management
Under the above principle of object memory reference counting, we obtain the following basic pattern of manual object memory management under Cocos2d-x:
CCObject *obj = new CCObject();
obj->init();
…. …
obj->release();
In Cocos2d-x CCDirector is a typical example 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" refers to the no longer needed objects that will be released by the Cocos2d-x engine for you without you manually calling the Release method.
Automatic object memory management obviously also follows the memory reference counting rules. Only when the object's count becomes 0 will the object's memory be released.
Typical modes 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;
}
}
Generally, we create objects through a singleton pattern. The difference from the manual pattern is that there is an additional autorelease call after init. Here we will extract the implementation of the autorelease call:
CCObject* CCObject::autorelease(void)
{
CCPoolManager::sharedPoolManager()->addObject(this);
return this;
}
Trace back to addObject method:
// cocoa/
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/
void CCArray::addObject(CCObject* object)
{
ccArrayAppendObjectWithResize(data, object);
}
// support/data_support/
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++;
}
The call level is quite deep and there are many classes involved. Here is a summary.
Cocos2d-x's automatic object memory management is based on object reference counting and CCAutoreleasePool (automatically release the pool). Quote counting has been mentioned before, here we just talk about the automatic release of the pool. The basic class hierarchy of Cocos2d-x about automatic object memory management is as follows:
CCPoolManager class (automatically release the pool manager)
– CCArray* m_pReleasePoolStack; (automatically release the pool stack and store CCAutoreleasePool class instance)
CCAutoreleasePool class
– CCArray* m_pManagedObjectArray; (Managed object array)
CCObject has two fields regarding memory counting and automatic management: m_uReference and m_uAutoReleaseCount. In the manual management mode, I only mentioned m_uReference, which is the time for m_uAutoReleaseCount to debut. Let’s take a look at the different stages of the creation step of automatically releasing the object. What are the values of these two important fields and what does it mean:
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 no different from the manual mode. After autorelease, the m_uReference value does not change, but m_uAutoReleaseCount is added by 1.
The name of the field m_uAutoReleaseCount is easy to misunderstand, thinking it is a counter, but in fact it is an identifier most of the time. There was a Boolean field m_bManaged in the previous version of the code, which seemed to be replaced by m_uAutoReleaseCount. Therefore, m_uAutoReleaseCount also has the meaning of m_bManaged, that is, whether the object is under the control of the automatic release pool. If under the control of the automatic release pool, the automatic release pool will call the object's release method regularly until the object's memory count drops to 0 and is actually released. Otherwise, the object cannot be automatically released by the automatic release pool, and it needs to be released manually. This understanding is very important, and we can use this understanding later.
4. Automatic release time
Through autorelease we have put the object into the autoreleasePool, so when exactly will the object be released? The answer is to perform an automatic memory object release operation every frame.
In the article "Hello, Cocos2d-x", we talked about the driving mechanism of the entire Cocos2d-x engine lies in the guardedRun function of GLThread, which will call the Render's onDrawFrame method to implement the "dead loop" (the actual frame drawing frequency is affected by the screen vertsym signal). In the end, the program will enter the CCDirector::mainLoop method, which means that the execution frequency of mainLoop is once per frame. Let’s take a 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 are not focusing on drawScene, but CCPoolManager::sharedPoolManager()->pop(). Obviously, under the condition that the game has not exited (m_bPurgeDirecotorInNextLoop decision), CCPoolManager's pop method is executed once per frame, 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 really release the 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 automatic release pool traverses each "controlled" Object, -m_uAutoReleaseCount, and calls the release method of that object.
Let’s follow the release process to see the changes in the values of m_uAutoReleaseCount and m_uReference:
CCPoolManager::sharedPoolManager()->pop(); m_uReference = 0; m_uAutoReleaseCount = 0;
5. Initialization of the automatic release pool
When did the automatic release pool itself appear? Looking back at the initialization process of the Cocos2d-x engine (android version), the engine initialization is carried out in the onSurfaceCreated method of Render. It is not difficult for us to trace the following code:
//hellocpp/jni/hellocpp/
Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit {
//The CCDirector is created for the first time
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 automated memory release of Cocos2d-x kernel objects
Previously, we have basically understood the principle of automated memory release of Cocos2D-x. If you have looked through some of the kernel source codes of Cocos2d-x before, you will find that many kernel objects are created through singleton mode, which means that they all use autorelease to put themselves into the automated memory release pool and are managed.
For example, we have seen code like this in HelloCpp:
//
bool HelloWorld::init() {
…. ….
// add "HelloWorld" splash screen"
CCSprite* pSprite = CCSprite::create("");
// position the sprite on the center of the screen
pSprite->setPosition(ccp(/2 + , /2 + ));
// add the sprite as a child to this layer
this->addChild(pSprite, 0);
… …
}
CCSprite adopts the automated memory management mode create object (cocos2dx/sprite_nodes/), and then adds itself to the CCLayer instance of HelloWorld. According to the above analysis, after the create is completed, m_uReference = 1 of the CCSprite object; m_uAutoReleaseCount = 1. Once this is the case, then in the next frame, the object will be released by the CCPoolManager. But we can still see the existence of this Sprite on the screen. What's going on?
The key to the problem lies in the line of code this->addChild(pSprite, 0). The addChild method is implemented in the parent class CCNode of CCLayer:
// cocos2dx/base_nodes/
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++;
}
Another series of method calls, and finally we came to the ccArrayAppendObject method and saw the unfamiliar and familiar retain method calls.
When we began this article, we knew retain is a method of CCObject to increase the m_uReference count. In fact, retain also implies the meaning of "reservation".
After completing this->addChild(pSprite, 0) call, m_uReference = 2 of the CSprite object; m_uAutoReleaseCount = 1, which is very critical.
Let's go through the process of releasing objects in our minds: –m_uReference, –m_uAutoReleaseCount. After one frame, the two values become m_uReference = 1; m_uAutoReleaseCount = 0. Do you still remember another non-counting meaning of m_uAutoReleaseCount mentioned earlier? It means whether the object is "controlled". Now the value is 0, and it is obviously no longer controlled by the automatic release pool. Even if 100 memory automatic releases are performed in the future, it will not affect the survival of the object.
In order to release this "sprite", we still need to manually call release, or call its autorelease method.