标签:内存管理 cocos2d-x 2.2.3 3.2 ref
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()); //实际操作了+1-1.
结果:
After new: 1
After init: 1
After retain: 2
After release: 1
After autorelease: 1
“HelloCpp.exe”(Win32): 已加载“C:\WINDOWS\SysWOW64\SogouTSF.ime”。无法查找或打开 PDB 文件。
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; };
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; }
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。实现了+1的,随着3.2使用C++11语法后使用vector去管理就没有这么纠结了。 CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1"); ++(pObject->m_uAutoReleaseCount); //同时在当前的pool中增加管理 pObject->release(); // no ref count, in this case autorelease pool added. //必须-1,否则泄露。 }
void CCPoolManager::purgePoolManager() { CC_SAFE_DELETE(s_pPoolManager); }
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(); } }
回顾一下CCDiretor的初始化源码段:
// scheduler m_pScheduler = new CCScheduler(); // 计时器 // action manager m_pActionManager = new CCActionManager(); //动作管理器 m_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false); // touchDispatcher m_pTouchDispatcher = new CCTouchDispatcher(); //触摸信号管理器 m_pTouchDispatcher->init(); // KeypadDispatcher m_pKeypadDispatcher = new CCKeypadDispatcher(); //键盘信号管理器 // Accelerometer m_pAccelerometer = new CCAccelerometer(); // 加速器管理器 // create autorelease pool CCPoolManager::sharedPoolManager()->push(); //创建一个当前的pool并加入PoolManager中,还是一个CCArray,名为:m_pReleasePoolStack,实际上触控更换为vector是从2.0的版本左右就计划好了。2.x版本是个过渡,也就是说,了解了2.x,更具版本更新说明,3.x不存在代沟。通告层NotificationNode使用说明
下面有个NotificationNode,实际上和Scene属于UI种类,因为是后绘制,会遮盖当前scene,用途在于loading或者提示这种弹窗,记得触摸屏蔽的相关设置
// Draw the Scene void 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(); } }
回归正题:
在经过CCDisplayLinkDirector继承CCDirector,在mainLoop中调用poolManager,这个才是真正的cocos程序循环,程序入口的是win32或者ios的主程序循环:
简化版:
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) /*虽然cocos2d允许多个director,准备兼容多窗口程序(实际上到目前3.2为止,cocos2d应用多为是单窗口,只有一个窗口句柄,支持多场景、层,支持C++11多线程的引擎 */ {//导演类清理自己 m_bPurgeDirecotorInNextLoop = false; purgeDirector(); } else if (! m_bInvalid) { drawScene(); // release the objects CCPoolManager::sharedPoolManager()->pop(); } }
实际上终于回到了我们要密切关注的CCObject内存回收机制了:
每次pop()都去调用当前CCAutoreleasePool的clear操作,因为原理在于Director将游戏世界切片为每一帧,并为每一帧设定绘制时间长度,当GPU和CPU在设备上完成上一帧的制作,会计算一个时间长度,长度不够时会占用当前帧的可用时长,当前帧绘制的必要时长于剩余帧时长时跳过当前帧的CPU执行和GPU绘制,这就是为什么掉帧和碰撞穿透的原因,而在clear操作中,代码如下:
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(); } }
3.2 ref对比待续
ref 内部变化:变得清爽多了,除了增加了 CC_USE_MEM_LEAK_DETECTION 预编译模式下的内存跟踪:(这两个不是ref内部函数)
static void trackRef(Ref* ref);
static void untrackRef(Ref* ref);
外,减少了原先CCObject中的各种引用,只剩下:_referenceCount,无论lua还是js、c++,公用一个引用计数,C++分支真的往库或者大型3D多线程发展啦。只要坚持了过渡期,学好python、Linux的相关编译,手游纯3D的可行性很高。
下面才是新增的内存追踪显示函数:
#if CC_USE_MEM_LEAK_DETECTION public: static void printLeaks(); #endif
void AutoreleasePool::addObject(Ref* object) { _managedObjectArray.push_back(object); }
void DisplayLinkDirector::mainLoop() { if (_purgeDirectorInNextLoop) { _purgeDirectorInNextLoop = false; purgeDirector(); } else if (! _invalid) { drawScene(); // release the objects PoolManager::getInstance()->getCurrentPool()->clear(); } }
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(); } }通过调用setNextScene();来在每次绘制之前增加scene的引用计数,这样子在drawscene之后调用clear就能清楚了无用的内存ref对象:
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(); } }
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(); } }这里要注意了,addChild中用到的vector不是std的vector,是CCVector,cocos2d重写的,
void pushBack(T object) { CCASSERT(object != nullptr, "The object should not be nullptr"); _data.push_back( object ); object->retain(); }
渲染树的清理:
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();调用的是Node的cleanup,递归形式完成渲染树的清理。
提示: //利用静态函数实现每个pool创建自动添加至Manager,用的真妙!!C++只会越用越熟啊。
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); }
至于ref继承了clone接口,这个人尽皆知,就不分析啦。可惜目前我仍然无法搭建git环境,我最怕搭建环境了,比如花了4天去配置cocos2dx 2.x 和quick的win安卓环境,后来放弃了之后发现3.x完全是方便至极。虚拟机mac环境早搞定了,只剩sdk下载。
下面是源码推荐的用法:
// 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.
CCObject的分析:release、retain 基于2.2.3,增加3.2 ref对比
标签:内存管理 cocos2d-x 2.2.3 3.2 ref
原文地址:http://blog.csdn.net/jingzhewangzi/article/details/39810275