这里实现的内存池的主要功能有:
该类实现了一个简单的内存管理池,内存池就是一个盛放内存的池子,这些内存被分为大小相等的内存快,每个内存块有四个字节的字节头。这个字节头被当做指针对待,它指向了下一个内存块,内存池是一条单向链接起来的多个内存块。
当内存池被第一次初始化时通过Init()方法,你必须同时传入两个参数:每个内存快的大小,和多少个内存块。这两个值不会变化,除非你销毁或者重新初始化真个内存池。实际用来存储的空间时每个内存块减去字节头。
具体实现和注释如下:
I. 内存池声名:
#pragma once //---------------------------------------------------------------------------------------------------------- // MemoryPool.h : // // This class represents实现了 a single memory pool. A memory pool is pool of memory that's split into // chunks of equal size相等的块大小, each with a 4-byte header. The header is treated as a pointer that // points to the next chunk, making the pool a singly-linked单向链接的 list of memory chunks. // // When the pool is first initialized (via the Init() function), you must pass in a chunk size and the // number of chunks you want created. These two values are immutable一层不变的 unless you destroy and // reinitialize重新初始化 the entire pool. The chunk size is the size of each chunk, minus the header, // in bytes. The memory pool will allocate the appropriate适当的 amount of memory and set up the data // structure in the Init() call. Thus, total memory usage will be N * (S + 4) + O, where N is the // number of chunks, S is the size of each chunk, and O is the overhead for the class (currently // 18 + (number of reallocations * 4). // // Call the Alloc() function to retrieve恢复 a chunk from the memory pool. The Alloc() function removes // the head of the linked list, sets the new head to the next chunk, and returns a pointer to the data // section of the old head. If there aren't anymore chunks left当调用Alloc方法但是分配的内存块已经被全部使用时 // when Alloc() is called, it 那么它会重新分配一个 和你调用Init方法时分配内存大小一样的多个内存块 will allocate // another block of N chunks, where N is the number of chunks you passed into Init(). // While Alloc() is typically a very fast function, this reallocation will certainly cost you so // choose your initial sizes carefully. // // Call the Free() function to release a chunk of memory back into the memory pool for reuse. This // will cause the chunk to the inserted to the front of the list, ready for the next bit. //-------------------------------------------------------------------------------------------------- class MemoryPool { // 第一级指针指向分配的内存块 第二级指针指向第一级所在的Address并且这些第二级指针通过链表相连 unsigned char** m_ppRawMemoryArray; // an array of memory blocks, each split up into chunks and connected // 指向二级指针链表的头 unsigned char* m_pHead; // the front of the memory chunk linked list // 每个被分配的内存块大小和内存块的数量 unsigned int m_chunkSize, m_numChunks; // the size of each chunk and number of chunks per array, respectively // 二级指针链表中的现有的内存块数量 unsigned int m_memArraySize; // the number elements in the memory array // 当首次初始化时的内存块数量使用完后 则需重新申请该值为真 bool m_toAllowResize; // true if we resize the memory pool when it fills up // tracking variables we only care about for debug 测试 #ifdef _DEBUG std::string m_debugName; unsigned long m_allocPeak, m_numAllocs; #endif public: // construction MemoryPool(void); ~MemoryPool(void); bool Init(unsigned int chunkSize, unsigned int numChunks); // 首次必须制定大小 void Destroy(void); // allocation functions void* Alloc(void); //分配内存函数 void Free(void* pMem); // 释放内存函数 unsigned int GetChunkSize(void) const { return m_chunkSize; } // settings void SetAllowResize(bool toAllowResize) { m_toAllowResize = toAllowResize; } // debug functions #ifdef _DEBUG void SetDebugName(const char* debugName) { m_debugName = debugName; } std::string GetDebugName(void) const { return m_debugName; } #else void SetDebugName(const char* debugName) { } //std::string GetDebugName(void) const { return (std::string("<No Name>")); } const char* GetDebugName(void) const { return "<No Name>"; } #endif private: // resets internal vars 将内部数据重新全部重置 void Reset(void); // internal memory allocation helpers bool GrowMemoryArray(void); unsigned char* AllocateNewMemoryBlock(void); // internal linked list management 内部链表管理 unsigned char* GetNext(unsigned char* pBlock); void SetNext(unsigned char* pBlockToChange, unsigned char* pNewNext); // don't allow copy constructor MemoryPool(const MemoryPool& memPool) { assert(false,"don't allow copy constructor!"); } };II. 内存池实现:
//======================================================================== // MemoryPool.cpp : // // Part of the GameCode4 Application // // GameCode4 is the sample application that encapsulates much of the source code // discussed in "Game Coding Complete - 4th Edition" by Mike McShaffry and David // "Rez" Graham, published by Charles River Media. // ISBN-10: 1133776574 | ISBN-13: 978-1133776574 // // If this source code has found it's way to you, and you think it has helped you // in any way, do the authors a favor and buy a new copy of the book - there are // detailed explanations in it that compliment this code well. Buy a copy at Amazon.com // by clicking here: // http://www.amazon.com/gp/product/1133776574/ref=olp_product_details?ie=UTF8&me=&seller= // // There's a companion web site at http://www.mcshaffry.com/GameCode/ // // The source code is managed and maintained through Google Code: // http://code.google.com/p/gamecode4/ // // (c) Copyright 2012 Michael L. McShaffry and David Graham // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser GPL v3 // as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See // http://www.gnu.org/licenses/lgpl-3.0.txt for more details. // // You should have received a copy of the GNU Lesser GPL v3 // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //======================================================================== #include "GameCodeStd.h" #include "MemoryPool.h" #ifdef _DEBUG #include "../Utilities/String.h" #include <stdlib.h> #endif const static size_t CHUNK_HEADER_SIZE = (sizeof(unsigned char*)); MemoryPool::MemoryPool(void) { Reset(); } MemoryPool::~MemoryPool(void) { Destroy(); } bool MemoryPool::Init(unsigned int chunkSize, unsigned int numChunks) { // it's safe to call Init() without calling Destroy() if (m_ppRawMemoryArray) Destroy(); // fill out our size & number members m_chunkSize = chunkSize; m_numChunks = numChunks; // attempt to grow the memory array if (GrowMemoryArray()) return true; return false; } void MemoryPool::Destroy(void) { // dump the state of the memory pool #ifdef _DEBUG std::string str; if (m_numAllocs != 0) str = "***(" + ToStr(m_numAllocs) + ") "; unsigned long totalNumChunks = m_numChunks * m_memArraySize; unsigned long wastedMem = (totalNumChunks - m_allocPeak) * m_chunkSize; str += "Destroying memory pool: [" + GetDebugName() + ":" + ToStr((unsigned long)m_chunkSize) + "] = " + ToStr(m_allocPeak) + "/" + ToStr((unsigned long)totalNumChunks) + " (" + ToStr(wastedMem) + " bytes wasted)\n"; ::OutputDebugStringA(str.c_str()); // the logger is not initialized during many of the initial memory pool growths, so let's just use the OS version #endif // free all memory for (unsigned int i = 0; i < m_memArraySize; ++i) { free(m_ppRawMemoryArray[i]); } free(m_ppRawMemoryArray); // update member variables Reset(); } void* MemoryPool::Alloc(void) { // If we're out of memory chunks, grow the pool. This is very expensive. if (!m_pHead) { // if we don't allow resizes, return NULL if (!m_toAllowResize) return NULL; // attempt to grow the pool if (!GrowMemoryArray()) return NULL; // couldn't allocate anymore memory } #ifdef _DEBUG // update allocation reports ++m_numAllocs; if (m_numAllocs > m_allocPeak) m_allocPeak = m_numAllocs; #endif // grab the first chunk from the list and move to the next chunks unsigned char* pRet = m_pHead; m_pHead = GetNext(m_pHead); return (pRet + CHUNK_HEADER_SIZE); // make sure we return a pointer to the data section only } void MemoryPool::Free(void* pMem) { if (pMem != NULL) // calling Free() on a NULL pointer is perfectly valid { // The pointer we get back is just to the data section of the chunk. This gets us the full chunk. unsigned char* pBlock = ((unsigned char*)pMem) - CHUNK_HEADER_SIZE; // push the chunk to the front of the list SetNext(pBlock, m_pHead); m_pHead = pBlock; #ifdef _DEBUG // update allocation reports --m_numAllocs; GCC_ASSERT(m_numAllocs >= 0); #endif } } void MemoryPool::Reset(void) { m_ppRawMemoryArray = NULL; m_pHead = NULL; m_chunkSize = 0; m_numChunks = 0; m_memArraySize = 0; m_toAllowResize = true; #ifdef _DEBUG m_allocPeak = 0; m_numAllocs = 0; #endif } bool MemoryPool::GrowMemoryArray(void) { #ifdef _DEBUG std::string str("Growing memory pool: [" + GetDebugName() + ":" + ToStr((unsigned long)m_chunkSize) + "] = " + ToStr((unsigned long)m_memArraySize + 1) + "\n"); ::OutputDebugStringA(str.c_str()); // the logger is not initialized during many of the initial memory pool growths, so let's just use the OS version #endif // allocate a new array size_t allocationSize = sizeof(unsigned char*) * (m_memArraySize + 1); unsigned char** ppNewMemArray = (unsigned char**)malloc(allocationSize); // make sure the allocation succeeded if (!ppNewMemArray) return false; // copy any existing memory pointers over for (unsigned int i = 0; i < m_memArraySize; ++i) { ppNewMemArray[i] = m_ppRawMemoryArray[i]; } // allocate a new block of memory ppNewMemArray[m_memArraySize] = AllocateNewMemoryBlock(); // indexing m_memArraySize here is safe because we haven't incremented it yet to reflect the new size // attach the block to the end of the current memory list if (m_pHead) { unsigned char* pCurr = m_pHead; unsigned char* pNext = GetNext(m_pHead); while (pNext) { pCurr = pNext; pNext = GetNext(pNext); } SetNext(pCurr, ppNewMemArray[m_memArraySize]); } else { m_pHead = ppNewMemArray[m_memArraySize]; } // destroy the old memory array if (m_ppRawMemoryArray) free(m_ppRawMemoryArray); // assign the new memory array and increment the size count m_ppRawMemoryArray = ppNewMemArray; ++m_memArraySize; return true; } unsigned char* MemoryPool::AllocateNewMemoryBlock(void) { // calculate the size of each block and the size of the actual memory allocation size_t blockSize = m_chunkSize + CHUNK_HEADER_SIZE; // chunk + linked list overhead size_t trueSize = blockSize * m_numChunks; // allocate the memory unsigned char* pNewMem = (unsigned char*)malloc(trueSize); if (!pNewMem) return NULL; // turn the memory into a linked list of chunks unsigned char* pEnd = pNewMem + trueSize; unsigned char* pCurr = pNewMem; while (pCurr < pEnd) { // calculate the next pointer position unsigned char* pNext = pCurr + blockSize; // set the next & prev pointers unsigned char** ppChunkHeader = (unsigned char**)pCurr; ppChunkHeader[0] = (pNext < pEnd ? pNext : NULL); // move to the next block pCurr += blockSize; } return pNewMem; } unsigned char* MemoryPool::GetNext(unsigned char* pBlock) { unsigned char** ppChunkHeader = (unsigned char**)pBlock; return ppChunkHeader[0]; } void MemoryPool::SetNext(unsigned char* pBlockToChange, unsigned char* pNewNext) { unsigned char** ppChunkHeader = (unsigned char**)pBlockToChange; ppChunkHeader[0] = pNewNext; }III. 内存池的再封装如下:它为AI中的路径寻找提供支持
#pragma once //--------------------------------------------------------------------------------------------------------------------- // These macros are designed to allow classes to easily take advantage of memory pools. To use, follow this steps: // 1) Call GCC_MEMORYPOOL_DECLARATION() in the class declaration // 2) Call GCC_MEMORYPOOL_DEFINITION() in the cpp file // 3) Call GCC_MEMORYPOOL_AUTOINIT() or GCC_MEMORYPOOL_AUTOINIT_DEBUGNAME() in the cpp file // // That's it! Objects of your class will now be allocated through the memory pool! You can see an example of its // usage in Pathing.h and Pathing.cpp. Check out the PathingNode class. //--------------------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------------------- // This macro is placed inside the body of the class that you want to use a memory pool with. It declares the // overloaded new and delete operators as well as the static MemoryPool object. // // IMPORTANT: InitMemoryPool() and DestroyMemoryPool() must be called manually unless you use the GCC_MEMORYPOOL_AUTOINIT() // macro below. //--------------------------------------------------------------------------------------------------------------------- #define GCC_MEMORYPOOL_DECLARATION(__defaultNumChunks__) \ public: \ static MemoryPool* s_pMemoryPool; \ static void InitMemoryPool(unsigned int numChunks = __defaultNumChunks__, const char* debugName = 0); \ static void DestroyMemoryPool(void); \ static void* operator new(size_t size); \ static void operator delete(void* pPtr); \ static void* operator new[](size_t size); \ static void operator delete[](void* pPtr); \ private: \ //--------------------------------------------------------------------------------------------------------------------- // This macro defines the definition for the overloaded new & delete operators on a class meant to be pooled with a // memory pool. It is meant to work specifically with the MemoryPool class. To use it, call this macro from the cpp // file where your class function definitions are. // - _className_: The name of this class. //--------------------------------------------------------------------------------------------------------------------- #define GCC_MEMORYPOOL_DEFINITION(_className_) \ MemoryPool* _className_::s_pMemoryPool = NULL;\ void _className_::InitMemoryPool(unsigned int numChunks, const char* debugName) \ { \ if (s_pMemoryPool != NULL) \ { \ GCC_ERROR("s_pMemoryPool is not NULL. All data will be destroyed. (Ignorable)"); \ SAFE_DELETE(s_pMemoryPool); \ } \ s_pMemoryPool = GCC_NEW MemoryPool; \ if (debugName) \ s_pMemoryPool->SetDebugName(debugName); \ else \ s_pMemoryPool->SetDebugName(#_className_); \ s_pMemoryPool->Init(sizeof(_className_), numChunks); \ } \ void _className_::DestroyMemoryPool(void) \ { \ GCC_ASSERT(s_pMemoryPool != NULL); \ SAFE_DELETE(s_pMemoryPool); \ } \ void* _className_::operator new(size_t size) \ { \ GCC_ASSERT(s_pMemoryPool); \ void* pMem = s_pMemoryPool->Alloc(); \ return pMem; \ } \ void _className_::operator delete(void* pPtr) \ { \ GCC_ASSERT(s_pMemoryPool); \ s_pMemoryPool->Free(pPtr); \ } \ void* _className_::operator new[](size_t size) \ { \ GCC_ASSERT(s_pMemoryPool); \ void* pMem = s_pMemoryPool->Alloc(); \ return pMem; \ } \ void _className_::operator delete[](void* pPtr) \ { \ GCC_ASSERT(s_pMemoryPool); \ s_pMemoryPool->Free(pPtr); \ } \ //--------------------------------------------------------------------------------------------------------------------- // This macro defines a static class that automatically initializes a memory pool at global startup and destroys it at // global destruction time. Using this gets around the requirement of manually initializing and destroying the memory // pool yourself. //--------------------------------------------------------------------------------------------------------------------- #define GCC_MEMORYPOOL_AUTOINIT_DEBUGNAME(_className_, _numChunks_, _debugName_) \ class _className_ ## _AutoInitializedMemoryPool \ { \ public: \ _className_ ## _AutoInitializedMemoryPool(void); \ ~_className_ ## _AutoInitializedMemoryPool(void); \ }; \ _className_ ## _AutoInitializedMemoryPool::_className_ ## _AutoInitializedMemoryPool(void) \ { \ _className_::InitMemoryPool(_numChunks_, _debugName_); \ } \ _className_ ## _AutoInitializedMemoryPool::~_className_ ## _AutoInitializedMemoryPool(void) \ { \ _className_::DestroyMemoryPool(); \ } \ static _className_ ## _AutoInitializedMemoryPool s_ ## _className_ ## _AutoInitializedMemoryPool; \ #define GCC_MEMORYPOOL_AUTOINIT(_className_, _numChunks_) GCC_MEMORYPOOL_AUTOINIT_DEBUGNAME(_className_, _numChunks_, #_className_)以上就是内存池实现的相关代码,下一节中介绍AI中的路径寻找问题。
AI路径寻找在RPG中的重要性,绝对可以算得上一级必须慎重对待的模块。