游戏架构其四:内存管理

这里实现的内存池的主要功能有:

该类实现了一个简单的内存管理池,内存池就是一个盛放内存的池子,这些内存被分为大小相等的内存快,每个内存块有四个字节的字节头。这个字节头被当做指针对待,它指向了下一个内存块,内存池是一条单向链接起来的多个内存块。

当内存池被第一次初始化时通过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中的重要性,绝对可以算得上一级必须慎重对待的模块。




你可能感兴趣的:(架构,内存管理,cocos2d-x)