CCObject analysis: release and retain are based on 2.2.3, and 3.2 ref comparison is added, ccobjectretain
CCSprite * fish = new CCSprite; CCLOG ("After new: % d", fish-> retainCount (); fish-> init (); CCLOG ("After init: % d ", fish-> retainCount (); fish-> retain (); CCLOG (" After retain: % d ", fish-> retainCount ()); fish-> release (); CCLOG ("After release: % d", fish-> retainCount (); fish-> autorelease (); CCLOG ("After autorelease: % d ", fish-> retainCount (); // the actual operation is + 1-1.
Result:
After new: 1
After init: 1
After retain: 2
After release: 1
After autorelease: 1
Export hellocpp.exe "(Win32):" C: \ WINDOWS \ SysWOW64 \ SogouTSF. ime "has been loaded ". Unable to find or open the PDB file.
class CC_DLL CCCopying{public: virtual CCObject* copyWithZone(CCZone* pZone);};/** * @js NA */class CC_DLL CCObject : public CCCopying{public: // object id, CCScriptSupport need public m_uID unsigned int m_uID; // Lua reference id int m_nLuaID;protected: // count of references unsigned int m_uReference; // count of autorelease unsigned int m_uAutoReleaseCount;public: CCObject(void); /** * @lua NA */ virtual ~CCObject(void); void release(void); void retain(void); CCObject* autorelease(void); CCObject* copy(void); bool isSingleReference(void) const; unsigned int retainCount(void) const; virtual bool isEqual(const CCObject* pObject); virtual void acceptVisitor(CCDataVisitor &visitor); virtual void update(float dt) {CC_UNUSED_PARAM(dt);}; friend class CCAutoreleasePool;};
The default value of reference is 1,
CCObject::CCObject(void): m_nLuaID(0), m_uReference(1) // when the object is created, the reference count of it is 1, m_uAutoReleaseCount(0) // { static unsigned int uObjectCount = 0; m_uID = ++uObjectCount;}
Look at the source code of autorelease and you will know that the operations of + 1 and-1 are implemented:
CCObject* CCObject::autorelease(void){ CCPoolManager::sharedPoolManager()->addObject(this); return this;}
//<span style="font-family: Arial, Helvetica, sans-serif;">CCAutoreleasePool.cpp</span>
void CCPoolManager::addObject(CCObject* pObject){ getCurReleasePool()->addObject(pObject);}
Void CCAutoreleasePool: addObject (CCObject * pObject) {m_pManagedObjectArray-> addObject (pObject); // CCArray is used unexpectedly. With the implementation of + 1, with 3.2 using the C ++ 11 syntax and then using vector for management, it is not so tangled. CCAssert (pObject-> m_uReference> 1, "reference count shocould be greater than 1"); ++ (pObject-> m_uAutoReleaseCount ); // Add manage pObject-> release (); // no ref count, in this case autorelease pool added. // It Must Be-1; otherwise, it will be leaked .}
For each class that implements the Singleton, clear operations should be provided: as shown below, starting with purge.
void CCPoolManager::purgePoolManager(){ CC_SAFE_DELETE(s_pPoolManager);}
In the 2.2 series, each CCAutoreleasePool is actually a CCArray that stores a series of CCObject * and counts through m_uAutoReleaseCount.
void CCAutoreleasePool::clear(){ if(m_pManagedObjectArray->count() > 0) { //CCAutoreleasePool* pReleasePool;#ifdef _DEBUG int nIndex = m_pManagedObjectArray->count() - 1;#endif CCObject* pObj = NULL; CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj) { if(!pObj) break; --(pObj->m_uAutoReleaseCount); //(*it)->release(); //delete (*it);#ifdef _DEBUG nIndex--;#endif } m_pManagedObjectArray->removeAllObjects(); }}
Review the CCDiretor initialization source code segment:
// Scheduler m_pScheduler = new CCScheduler (); // timer // action manager m_pActionManager = new CCActionManager (); // action manager m_pScheduler-> scheduleUpdateForTarget (m_pActionManager, success, false ); // touchDispatcher identifier = new CCTouchDispatcher (); // touch signal manager Identifier-> init (); // KeypadDispatcher m_pKeypadDispatcher = new CCKeypadDispatcher (); // keyboard signal manager // Acceleromete R m_pAccelerometer = new CCAccelerometer (); // accelerator manager // create autorelease pool CCPoolManager: sharedPoolManager ()-> push (); // create a current pool and add it to the PoolManager. It is still a CCArray named: m_pReleasePoolStack. In fact, the change of touch to vector is planned from around 2.0. 2. Version x is a transitional version. That is to say, if you understand version 2.xand later, there is no generation gap in version 3.x.
Notice-layer icationicationnode instructions
The following NotificationNode actually belongs to the UI type with Scene. Because it is drawn later, the current scene will be overwritten. It is used for loading or prompting such a pop-up window. Remember the settings related to touch shielding.
// Draw the Scenevoid CCDirector::drawScene(void){ // calculate "global" dt calculateDeltaTime(); //tick before glClear: issue #533 if (! m_bPaused) { m_pScheduler->update(m_fDeltaTime); } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* to avoid flickr, nextScene MUST be here: after tick and before draw. XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */ if (m_pNextScene) { setNextScene(); } kmGLPushMatrix(); // draw the scene if (m_pRunningScene) { m_pRunningScene->visit(); } // draw the notifications node if (m_pNotificationNode) { m_pNotificationNode->visit(); } if (m_bDisplayStats) { showStats(); } kmGLPopMatrix(); m_uTotalFrames++; // swap buffers if (m_pobOpenGLView) { m_pobOpenGLView->swapBuffers(); } if (m_bDisplayStats) { calculateMPF(); }}
I thought it would be called in drawscene. Who knows. This is irrelevant.
Back to question:
After CCDisplayLinkDirector inherits CCDirector and calls poolManager in mainLoop, this is the real cocos program loop. The program entry is the win32 or ios main program loop:
Simplified Version:
int CCApplication::run(){ <span style="white-space:pre">// Initialize instance and cocos2d. if (!applicationDidFinishLaunching()) { return 0; } CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView(); pMainWnd->centerWindow(); ShowWindow(pMainWnd->getHWnd(), SW_SHOW); while (1) { if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // Get current time tick. QueryPerformanceCounter(&nNow); // If it's the time to draw next frame, draw it, else sleep a while. if (nNow.QuadPart - nLast.QuadPart > m_nAnimationInterval.QuadPart) { nLast.QuadPart = nNow.QuadPart; CCDirector::sharedDirector()->mainLoop(); } else { Sleep(0); } continue; } if (WM_QUIT == msg.message) { // Quit message loop. break; } // Deal with windows message. if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd, m_hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }</span> }
Void CCDisplayLinkDirector: mainLoop (void) {if (m_bPurgeDirecotorInNextLoop)/* although cocos2d allows multiple ctor, prepare compatible multi-window programs (in fact, up to 3.2, most cocos2d applications are single windows, with only one window handle. They support multiple scenarios and layers, and support C ++ 11 multi-threaded engines */{// The director class cleans itself m_bPurgeDirecotorInNextLoop = false; purgedire();} else if (! M_bInvalid) {drawScene (); // release the objects CCPoolManager: sharedPoolManager ()-> pop ();}}
In fact, we finally returned to the CCObject memory recycling mechanism that we should pay close attention:
Each pop () calls the clear operation of the current CCAutoreleasePool, because the principle is that ctor slices the game world into every frame and sets the draw time length for each frame, when the GPU and CPU finish making the last frame on the device, it will calculate a time length. If the length is not enough, it will take up the duration of the current frame, the CPU execution and GPU painting of the current frame are skipped when the current frame is drawn longer than the remaining frame duration. This is why frame dropping and collision penetration occur. In the clear operation, the Code is as follows:
void CCAutoreleasePool::clear(){ if(m_pManagedObjectArray->count() > 0) { //CCAutoreleasePool* pReleasePool;#ifdef _DEBUG int nIndex = m_pManagedObjectArray->count() - 1;#endif CCObject* pObj = NULL; CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj) { if(!pObj) break; --(pObj->m_uAutoReleaseCount); //(*it)->release(); //delete (*it);#ifdef _DEBUG nIndex--;#endif } m_pManagedObjectArray->removeAllObjects(); }}
Clear the data of the current frame. m_uAutoReleaseCount is changed to 0, but the memory is not actually released. At this time, the memory management mechanism relies on m_uReference to identify whether cleaning is required. All those that only use addChild and do not retain will be cleared with the parent node. On the one hand, we can use our own pushPool, and on the other hand, we can mount it to the current scene in m_pobScenesStack. PoolManager does not manage nodes of m_pobScenesStack Main Line. It only manages CCObject that is dropped to the pool through autorelease in the current pool.
3.2 ref comparison to be continued
Internal ref changes: it becomes much more refreshing, except for memory tracking in CC_USE_MEM_LEAK_DETECTION pre-compilation mode: (these two are not internal ref functions)
Static void trackRef (Ref * ref );
Static void untrackRef (Ref * ref );
In addition, the various references in the original CCObject are reduced, and only: _ referenceCount is left. No matter lua, js, c ++, a reference count is used, the C ++ branch is actually evolving to a library or a large 3D multithreading. As long as you stick to the transition period and learn how to compile python and Linux, it is highly feasible to use 3D mobile games.
The following is the new memory tracing display function:
#if CC_USE_MEM_LEAK_DETECTIONpublic: static void printLeaks();#endif
The new AutoreleasePool has used: std: vector <Ref *> _ managedObjectArray; Standard C ++ as the container, using ref. the Code called by autorelease () is not in the +-1 format,
void AutoreleasePool::addObject(Ref* object){ _managedObjectArray.push_back(object);}
At the same time, the mainLopp becomes the clear () operation that directly calls Autorelease:
void DisplayLinkDirector::mainLoop(){ if (_purgeDirectorInNextLoop) { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (! _invalid) { drawScene(); // release the objects PoolManager::getInstance()->getCurrentPool()->clear(); }}
Medium 3.2
void Director::drawScene(){ // calculate "global" dt calculateDeltaTime(); // skip one flame when _deltaTime equal to zero. if(_deltaTime < FLT_EPSILON) { return; } if (_openGLView) { _openGLView->pollInputEvents(); } //tick before glClear: issue #533 if (! _paused) { _scheduler->update(_deltaTime); _eventDispatcher->dispatchEvent(_eventAfterUpdate); } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* to avoid flickr, nextScene MUST be here: after tick and before draw. XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */ if (_nextScene) { setNextScene(); } pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); // draw the scene if (_runningScene) { _runningScene->visit(_renderer, Mat4::IDENTITY, false); _eventDispatcher->dispatchEvent(_eventAfterVisit); } // draw the notifications node if (_notificationNode) { _notificationNode->visit(_renderer, Mat4::IDENTITY, false); } if (_displayStats) { showStats(); } _renderer->render(); _eventDispatcher->dispatchEvent(_eventAfterDraw); popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); _totalFrames++; // swap buffers if (_openGLView) { _openGLView->swapBuffers(); } if (_displayStats) { calculateMPF(); }}Call setNextScene (); To increase the reference count of scene before each draw, so that clear can be called after drawscene to clear useless memory ref objects:
void Director::setNextScene(){ bool runningIsTransition = dynamic_cast<TransitionScene*>(_runningScene) != nullptr; bool newIsTransition = dynamic_cast<TransitionScene*>(_nextScene) != nullptr; // If it is not a transition, call onExit/cleanup if (! newIsTransition) { if (_runningScene) { _runningScene->onExitTransitionDidStart(); _runningScene->onExit(); } // issue #709. the root node (scene) should receive the cleanup message too // otherwise it might be leaked. if (_sendCleanupToScene && _runningScene) { _runningScene->cleanup(); } } if (_runningScene) { _runningScene->release(); } _runningScene = _nextScene; _nextScene->retain(); _nextScene = nullptr; if ((! runningIsTransition) && _runningScene) { _runningScene->onEnter(); _runningScene->onEnterTransitionDidFinish(); }}
At the same time, let's take a look at the Node: addChild operation, which can solve our doubts. The difference between addChild and Autorelease memory management is used:
void Node::addChild(Node* child, int localZOrder, const std::string &name){ CCASSERT(child != nullptr, "Argument must be non-nil"); CCASSERT(child->_parent == nullptr, "child already added. It can't be added again"); addChildHelper(child, localZOrder, INVALID_TAG, name, false);}
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. Scene* scene = this->getScene(); if (scene != nullptr && scene->getPhysicsWorld() != nullptr) { child->updatePhysicsBodyTransform(scene); 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(); }}Note that the vector used in addChild is not a std vector, It is a CCVector, and cocos2d rewrite,
void pushBack(T object) { CCASSERT(object != nullptr, "The object should not be nullptr"); _data.push_back( object ); object->retain(); }
Therefore, we actually seem to use the rendering tree that AddChild handed over to cocos2d to help us manage the memory and enable the AutoreleasePool form to manage the memory is irrelevant. Actually, it is equivalent to setting up a batch of objects in the region where the pool is the carrier, retian is used as needed, or a rendering tree is mounted (each rendering tree starts with scene and addChild executes a retian operation). The memory pool is cleared once after each rendering. After new, if Autorelease is not called, we need to manually manage the memory in the C ++ standard mode. If Autorelease is enabled, because Autorelease is called, drawscene (cpu game main line and GPU rendering) then, there is no possibility that the application may be cleared immediately, so you do not need +-1, you only need to judge whether it is 0 when you clear. The difference between AddChild is that the node _ referenceCount attached to the rendering tree is initially 2. After a clear operation is executed, it does not belong to the AutoReleasePool in the PoolManager, but only belongs to the rendering tree.
Clear the rendering tree:
void Director::replaceScene(Scene *scene){ CCASSERT(_runningScene, "Use runWithScene: instead to start the director"); CCASSERT(scene != nullptr, "the scene should not be null"); if (scene == _nextScene) return; if (_nextScene) { if (_nextScene->isRunning()) { _nextScene->onExit(); } _nextScene->cleanup(); _nextScene = nullptr; } ssize_t index = _scenesStack.size(); _sendCleanupToScene = true; _scenesStack.replace(index - 1, scene); _nextScene = scene;}
_nextScene->cleanup();
Call the cleanup of Node to clear the rendering tree recursively.
Tip: // use a static function to automatically add a pool to the Manager !! C ++ only gets familiar with usage.
PoolManager* PoolManager::getInstance(){ if (s_singleInstance == nullptr) { s_singleInstance = new PoolManager(); // 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);}
Therefore, after the clear operation is completed, the Manager will be cleared except the rendering tree and _ referenceCount> 1; however, after the pool is cleared, the memory of _ referenceCount> = 1 is leaked like a rendering tree. In this case, I need to use my own release (delete. Why is this actually caused by the management mechanism inherited from code compatible with ObjectC earlier? The Pool and Autorelease mechanisms may be cleaned up and called later, only save the rendering tree and manual retian and release. This is related to enabling C ++ 11 shared_ptr <typename> later. Because 3.2 of the pool is actually dispensable, it allows the program to implement memory management.
As for CCCopying being extracted into CloneAble, the ref does not inherit the clone interface and will not be analyzed. Unfortunately, I still cannot set up the git environment. I am most afraid of setting up the environment. For example, I spent four days configuring the cocos2dx 2.x and quick win Android environment and installing the mac Virtual Machine in three days, later found 3. x is completely convenient.
The following is the recommended source code usage:
// Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool. // This happens when 'autorelease/release' were not used in pairs with 'new/retain'. // // Wrong usage (1): // // auto obj = Node::create(); // Ref = 1, but it's an autorelease Ref which means it was in the autorelease pool. // obj->autorelease(); // Wrong: If you wish to invoke autorelease several times, you should retain `obj` first. // // Wrong usage (2): // // auto obj = Node::create(); // obj->release(); // Wrong: obj is an autorelease Ref, it will be released when clearing current pool. // // Correct usage (1): // // auto obj = Node::create(); // |- new Node(); // `new` is the pair of the `autorelease` of next line // |- autorelease(); // The pair of `new Node`. // // obj->retain(); // obj->autorelease(); // This `autorelease` is the pair of `retain` of previous line. // // Correct usage (2): // // auto obj = Node::create(); // obj->retain(); // obj->release(); // This `release` is the pair of `retain` of previous line.