这里实现的内存池的主要功能有:
该类实现了一个简单的内存管理池,内存池就是一个盛放内存的池子,这些内存被分为大小相等的内存快,每个内存块有四个字节的字节头。这个字节头被当做指针对待,它指向了下一个内存块,内存池是一条单向链接起来的多个内存块。
当内存池被第一次初始化时通过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中的重要性,绝对可以算得上一级必须慎重对待的模块。