개요.
먼저 제가 만든 poolingManger는 아래 두 책에 나오는 내용을 기반으로 만들어 졌다는 것을 미리 밝힙니다.
앞에서도 이야기 했듯이
별 게임은 별, 총알, 아이템 등이 매우 많이 만들어 지고 삭제됩니다..
때문에 게임 개발을 하는 것에 있어서 가장 먼저 고려했던 것이..
1. 생성과 삭제의 리스크
2. 메모리 단편화
위 두 가지 이유로 인한 게임 성능 저하 였습니다.
게임이 렉이 걸리고 느리면 게임을 하는 입장에서는 매우 답답하며 화가나는 것은 당연한 사실이니까요.
그래서 고민을 하다가 PoolingManager를 만들게 된 것입니다.
아래는 PoolingManager의 코드 및 간단한 설명입니다.
(그리고 MemoryPooling에 대해 자세한 설명을 보고싶으시면 아래 포스트들을 참조해 주세요. )
[operator new / operator delete]
먼저 완성된 코드는 아래와 같습니다.
(가독성을 위해 적당히 편집하였습니다.)
코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | /*-----------------------CPoolingManager------------------------ * * singleton pattern * 동작 사이클은 아래와 같다. * * 1. CreateBulletList() - 원하는 만큼의 메모리 블럭 생성 및 리스트에 추가 * 2. BulletNew() - 생성하고자 하는 오브젝트로 초기화(operator new, 생성자 호출) * 3. 사용 * 4. ReturnToFreeMemory() - 메모리 반환 * 5. 사용할 때 마다 2번부터 반복 * 6. 게임 종료시 메모리 해제 * ----------------------------------------------------------------*/ /* 메모리 블럭 타입 정의 */ typedef char* MEMORYBLOCK; /* size만큼의 메모리 블럭을 생성한다. */ MEMORYBLOCK NewMemoryBlock(size_t size) const { /* memoryAlive flag를 위한 1바이트 추가 생성 */ MEMORYBLOCK block = new char[size + 1]; /* memory 초기화 및 memoryAlive = false */ memset(block, 0, size + 1); return block; } /* size만큼의 char형 포인터를 count만큼 Bullet리스트에 add */ void CreateBulletList(size_t count, size_t size) { m_BulletSize = size; m_BulletList.reserve(m_BulletSize); while (count--) { /* 하나의 크기가 size만큼의 메모리 블럭을 count만큼 생성한다. */ MEMORYBLOCK memBlock = NewMemoryBlock(m_BulletSize); m_BulletList.emplace_back(memBlock); /* 오브젝트 매니저 리스트에 추가한다. */ CObjectManager::Instance()->AddBullet(memBlock); } } /* pool이 보유한 메모리 리스트가 생성하려는 것보다 적으면 새로 생성 */ void* BulletNew() { for (auto bullet : m_BulletList) { /* 메모리가 Free(false)상태면 메모리를 사용 중 상태(true)로 전환 후 반환 */ if (false == bullet[m_BulletSize]) { bullet[m_BulletSize] = true; return bullet; } } /* 모든 메모리가 사용 중 상태라면 새롭게 하나 생성 */ CCLOG("BULLET LIST OVERFLOWED"); MEMORYBLOCK memBlock = NewMemoryBlock(m_BulletSize); m_BulletList.emplace_back(memBlock); /* 오브젝트 매니저 리스트에 추가한다. */ CObjectManager::Instance()->AddBullet(memBlock); /* 메모리를 사용 중 상태로 전환 */ memBlock[m_BulletSize] = true; return memBlock; } /* Bullet을 메모리블럭으로 전환 (alive off) */ void Bullet_ReturnToFreeMemory(void* bullet) { static_cast<char*>(bullet)[m_BulletSize] = false; } /* 모든 메모리를 해제한다. (게임이 종료되거나 Scene이 변경될때 호출) */ void DeleteAllMemory() { for (auto bullet : m_BulletList) { delete[] bullet; bullet = nullptr; } m_BulletList.clear(); } /* 메모리 블럭 리스트 */ std::vector<MEMORYBLOCK> m_BulletList; /* 메모리 블럭 하나 당 크기 */ int m_BulletSize; | cs |
CPoolingManager 클래스의 주요 함수 및 동작원리 설명
1. memoryPooling기능을 수행하는 singleton 클래스 입니다.
2. 게임을 시작할 때 원하는 사이즈의 메모리 블럭을 원하는 개수만큼 미리 생성해 놓습니다.
3. 사용자는 필요할 때 해당 메모리들을 사용할 수 있습니다.
4. MemoryPooling의 관리를 받고 싶다면 해당 클래스의 operator new에서 PoolingManager의 New를 호출해주면 됩니다.
6. 그러면 미리 생성된 메모리 중 Free상태의 메모리를 할당 받을 수 있습니다.
사용 예
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /* 사용 예 1 객체 초기화 */ /* operator new */ void* CNormalBullet::operator new(size_t size, const std::nothrow_t) { // PoolingManager에서 메모리를 할당 받는다. return CPoolingManager::Instance()->BulletNew(); } CNormalBullet* CNormalBullet::create( std::string textureName, //bullet 이미지 float boundingRadius, //bullet 충돌 범위 float angle, //bullet 초기 각도 float speed) //bullet 초기 속도 { // operator new 호출 후 CNormalBullet 생성자로 초기화 CNormalBullet* pRet = (CNormalBullet*)new(std::nothrow)CNormalBullet( textureName, boundingRadius, angle, speed); // nothrow로 인해 초기화 되지 않으면 nullptr반환됨 if (pRet && pRet->init()) { // ObjectManager의 Auto_ReturnToMemoryBlock()에 등록 pRet->Auto_ReturnToMemoryBlock(); return pRet; } else { delete pRet; pRet = NULL; return NULL; } } | cs |
사용 예 (객체 초기화)
1. NormalBullet의 create에서 호출하는 new는 operator new 로 poolingManager의 New함수를 호출합니다.
2. PoolingManager의 New함수는 미리 생성된 Free메모리를 반환합니다.
3. 반환된 MemoryBlock은 CNormalBullet 생성자로 초기화 되어 사용합니다.
1 2 3 4 5 6 7 8 9 10 11 | /* 사용 예 2 메모리 반환 */ void CNormalBullet::ReturnToMemoryBlock() { this->removeFromParent(); this->removeAllChildren(); this->setVisible(false); this->setAlive(false); } | cs |
사용 예 (메모리 반환)
1. 사용한 메모리를 Free메모리로 반환하려면 setAlive(false) 하면 됩니다.
2. alive가 false인 오브젝트는 다음 프레임에서 Free메모리로 전환됩니다.
3. this->removeFromParent();
this->removeAllChildren();
this->setVisible(false);
this->setAlive(false);
4. 위 일련의 작업들은 현재 오브젝트에 AddChild되어 있는 메모리들을 해제해주는 것과 부모로부터 현재 메모리를 단절시키는 작업을 진행합니다.
EXTRA :
처음엔 addChild되어야만 object로써 제대로된 구실을 하는 cocos2dx의 특성을 고려하여
createBulletList할 때 모든 NormalBullet 등의 최상위 클래스인 CBullet으로 생성한 후 addChild까지 했었습니다.
게임 중 addChild하는 비용까지 없애자는 야심찬 계획이었지만 다음과 같은 이유로 뻘짓이 되었습니다.
1. bullet으로 생성자를 호출하여 초기화 하면 이후 normalBullet으로써 사용하지 못한다.
(normalBullet의 생성자를 호출한 것이 아니기 때문에 다형성 불가.)
2. 그래서 처음에 Bullet으로 초기화 후 사용할 때 NormalBullet으로 다시 초기화 해보았는데
이때는 Ref생성자 호출 과정에서 RefCount가 다시 1이 되기 때문에 이전의 addChild 한 것이 소용없어졌습니다.
(그리고 생성자를 두 번 호출하는 말도 안되는 문제가 있었죠.)
3. Bullet으로 생성한 메모리를 NormalBullet으로 사용하려면 두 클래스의 크기가 같아야 했습니다.
위와 같은 일이 있었기에 지금의 모습인 Char* 형으로 모든 Bullet보다 적당히 큰 메모리 블럭을 만들어 놓고
사용할 때 operator new를 이용하여 NormalBullet이든 SpecificBullet이든 초기화하여 사용할 수 있도록 변경하였습니다.
다형성을 이용할 수 있게된 것이죠.
현재 구조를 보면 사용할 때마다 addChild및 RemoveFromParent를 하고 있습니다.
같은 메모리를 반복해서 사용하니까 당연하게 해주어야 할 일이긴 한데.. 뭔가찜찜한 상태라 차후에 리펙토링을 고려하고 있습니다.
귀차니즘을 위한 3줄 요약
1. PoolingManager구현
2. 별게임에 적용
3. 게임성능 UP
'All > Project' 카테고리의 다른 글
Shooter / Bullet (0) | 2016.04.12 |
---|---|
ObjectManager (0) | 2016.04.12 |
게임 전체 클래스 UML 및 간단설명 (0) | 2016.04.12 |
게임 개요 (0) | 2016.04.11 |
MemoryPooling_2 (0) | 2016.03.15 |