/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** 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 the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: Memory.h
//-----------------------------------------------------------------------------
//
// Westwood Studios Pacific.
//
// Confidential Information
// Copyright (C); 2001 - All Rights Reserved
//
//-----------------------------------------------------------------------------
//
// Project: RTS3
//
// File name: Memory.h
//
// Created: Steven Johnson, August 2001
//
// Desc: Memory manager
//
//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
#pragma once
#ifndef _GAME_MEMORY_H_
#define _GAME_MEMORY_H_
// Turn off memory pool checkpointing for now.
#define DISABLE_MEMORYPOOL_CHECKPOINTING 1
#if (defined(_DEBUG) || defined(_INTERNAL)) && !defined(MEMORYPOOL_DEBUG_CUSTOM_NEW) && !defined(DISABLE_MEMORYPOOL_DEBUG_CUSTOM_NEW)
#define MEMORYPOOL_DEBUG_CUSTOM_NEW
#endif
//#if (defined(_DEBUG) || defined(_INTERNAL)) && !defined(MEMORYPOOL_DEBUG) && !defined(DISABLE_MEMORYPOOL_DEBUG)
#if (defined(_DEBUG)) && !defined(MEMORYPOOL_DEBUG) && !defined(DISABLE_MEMORYPOOL_DEBUG)
#define MEMORYPOOL_DEBUG
#endif
// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
#include
#include
#ifdef MEMORYPOOL_OVERRIDE_MALLOC
#include
#endif
// USER INCLUDES //////////////////////////////////////////////////////////////
#include "Lib/BaseType.h"
#include "Common/Debug.h"
#include "Common/Errors.h"
// MACROS //////////////////////////////////////////////////////////////////
#ifdef MEMORYPOOL_DEBUG
// by default, enable free-block-retention for checkpointing in debug mode
#ifndef DISABLE_MEMORYPOOL_CHECKPOINTING
#define MEMORYPOOL_CHECKPOINTING
#endif
// by default, enable bounding walls in debug mode (unless we have specifically disabled them)
#ifndef DISABLE_MEMORYPOOL_BOUNDINGWALL
#define MEMORYPOOL_BOUNDINGWALL
#endif
#define DECLARE_LITERALSTRING_ARG1 const char * debugLiteralTagString
#define PASS_LITERALSTRING_ARG1 debugLiteralTagString
#define DECLARE_LITERALSTRING_ARG2 , const char * debugLiteralTagString
#define PASS_LITERALSTRING_ARG2 , debugLiteralTagString
#define MP_LOC_SUFFIX /*" [" DEBUG_FILENLINE "]"*/
#define allocateBlock(ARGLITERAL) allocateBlockImplementation(ARGLITERAL MP_LOC_SUFFIX)
#define allocateBlockDoNotZero(ARGLITERAL) allocateBlockDoNotZeroImplementation(ARGLITERAL MP_LOC_SUFFIX)
#define allocateBytes(ARGCOUNT,ARGLITERAL) allocateBytesImplementation(ARGCOUNT, ARGLITERAL MP_LOC_SUFFIX)
#define allocateBytesDoNotZero(ARGCOUNT,ARGLITERAL) allocateBytesDoNotZeroImplementation(ARGCOUNT, ARGLITERAL MP_LOC_SUFFIX)
#define newInstanceDesc(ARGCLASS,ARGLITERAL) new(ARGCLASS::ARGCLASS##_GLUE_NOT_IMPLEMENTED, ARGLITERAL MP_LOC_SUFFIX) ARGCLASS
#define newInstance(ARGCLASS) new(ARGCLASS::ARGCLASS##_GLUE_NOT_IMPLEMENTED, __FILE__) ARGCLASS
#if !defined(MEMORYPOOL_STACKTRACE) && !defined(DISABLE_MEMORYPOOL_STACKTRACE)
#define MEMORYPOOL_STACKTRACE
#endif
// flags for the memory-report options.
enum
{
#ifdef MEMORYPOOL_CHECKPOINTING
// ------------------------------------------------------
// you usually won't use the _REPORT bits directly; see below for more convenient combinations.
// you must set at least one of the 'allocate' bits.
_REPORT_CP_ALLOCATED_BEFORE = 0x0001,
_REPORT_CP_ALLOCATED_BETWEEN = 0x0002,
_REPORT_CP_ALLOCATED_DONTCARE = (_REPORT_CP_ALLOCATED_BEFORE|_REPORT_CP_ALLOCATED_BETWEEN),
// you must set at least one of the 'freed' bits.
_REPORT_CP_FREED_BEFORE = 0x0010,
_REPORT_CP_FREED_BETWEEN = 0x0020,
_REPORT_CP_FREED_NEVER = 0x0040, // ie, still in existence
_REPORT_CP_FREED_DONTCARE = (_REPORT_CP_FREED_BEFORE|_REPORT_CP_FREED_BETWEEN|_REPORT_CP_FREED_NEVER),
// ------------------------------------------------------
#endif // MEMORYPOOL_CHECKPOINTING
#ifdef MEMORYPOOL_STACKTRACE
/** display the stacktrace for allocation location for all blocks found.
this bit may be mixed-n-matched with any other flag.
*/
REPORT_CP_STACKTRACE = 0x0100,
#endif
/** display stats for each pool, in addition to each block.
(this is useful for finding suitable allocation counts for the pools.)
this bit may be mixed-n-matched with any other flag.
*/
REPORT_POOLINFO = 0x0200,
/** report on the overall memory situation (including all pools and dma's).
this bit may be mixed-n-matched with any other flag.
*/
REPORT_FACTORYINFO = 0x0400,
/** report on pools that have overflowed their initial allocation.
this bit may be mixed-n-matched with any other flag.
*/
REPORT_POOL_OVERFLOW = 0x0800,
/** simple-n-cheap leak checking */
REPORT_SIMPLE_LEAKS = 0x1000,
#ifdef MEMORYPOOL_CHECKPOINTING
/** report on blocks that were allocated between the checkpoints.
(don't care if they were freed or not.)
*/
REPORT_CP_ALLOCATES = (_REPORT_CP_ALLOCATED_BETWEEN | _REPORT_CP_FREED_DONTCARE),
/** report on blocks that were freed between the checkpoints.
(don't care when they were allocated.)
*/
REPORT_CP_FREES = (_REPORT_CP_ALLOCATED_DONTCARE | _REPORT_CP_FREED_BETWEEN),
/** report on blocks that were allocated between the checkpoints, and still exist
(note that this reports *potential* leaks -- some such blocks may be desired)
*/
REPORT_CP_LEAKS = (_REPORT_CP_ALLOCATED_BETWEEN | _REPORT_CP_FREED_NEVER),
/** report on blocks that existed before checkpoint #1 and still exist now.
*/
REPORT_CP_LONGTERM = (_REPORT_CP_ALLOCATED_BEFORE | _REPORT_CP_FREED_NEVER),
/** report on blocks that were allocated-and-freed between the checkpoints.
*/
REPORT_CP_TRANSIENT = (_REPORT_CP_ALLOCATED_BETWEEN | _REPORT_CP_FREED_BETWEEN),
/** report on all blocks that currently exist
*/
REPORT_CP_EXISTING = (_REPORT_CP_ALLOCATED_BEFORE | _REPORT_CP_ALLOCATED_BETWEEN | _REPORT_CP_FREED_NEVER),
/** report on all blocks that have ever existed (!) (or at least, since the last call
to debugResetCheckpoints)
*/
REPORT_CP_ALL = (_REPORT_CP_ALLOCATED_DONTCARE | _REPORT_CP_FREED_DONTCARE)
#endif // MEMORYPOOL_CHECKPOINTING
};
#else
#define DECLARE_LITERALSTRING_ARG1
#define PASS_LITERALSTRING_ARG1
#define DECLARE_LITERALSTRING_ARG2
#define PASS_LITERALSTRING_ARG2
#define allocateBlock(ARGLITERAL) allocateBlockImplementation()
#define allocateBlockDoNotZero(ARGLITERAL) allocateBlockDoNotZeroImplementation()
#define allocateBytes(ARGCOUNT,ARGLITERAL) allocateBytesImplementation(ARGCOUNT)
#define allocateBytesDoNotZero(ARGCOUNT,ARGLITERAL) allocateBytesDoNotZeroImplementation(ARGCOUNT)
#define newInstanceDesc(ARGCLASS,ARGLITERAL) new(ARGCLASS::ARGCLASS##_GLUE_NOT_IMPLEMENTED) ARGCLASS
#define newInstance(ARGCLASS) new(ARGCLASS::ARGCLASS##_GLUE_NOT_IMPLEMENTED) ARGCLASS
#endif
// FORWARD REFERENCES /////////////////////////////////////////////////////////
class MemoryPoolSingleBlock;
class MemoryPoolBlob;
class MemoryPool;
class MemoryPoolFactory;
class DynamicMemoryAllocator;
class BlockCheckpointInfo;
// TYPE DEFINES ///////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------
/**
This class is purely a convenience used to pass optional arguments to initMemoryManager(),
and by extension, to createDynamicMemoryAllocator(). You can specify how many sub-pools you
want, what size each is, what the allocation counts are to be, etc. Most apps will
construct an array of these to pass to initMemoryManager() and never use it elsewhere.
*/
struct PoolInitRec
{
const char *poolName; ///< name of the pool; by convention, "dmaPool_XXX" where XXX is allocationSize
Int allocationSize; ///< size, in bytes, of the pool.
Int initialAllocationCount; ///< initial number of blocks to allocate.
Int overflowAllocationCount; ///< when the pool runs out of space, allocate more blocks in this increment
};
enum
{
MAX_DYNAMICMEMORYALLOCATOR_SUBPOOLS = 8 ///< The max number of subpools allowed in a DynamicMemoryAllocator
};
#ifdef MEMORYPOOL_CHECKPOINTING
// ----------------------------------------------------------------------------
/**
This class exists purely for coding convenience, and should never be used by external code.
It simply allows MemoryPool and DynamicMemoryAllocator to share checkpoint-related
code in a seamless way.
*/
class Checkpointable
{
private:
BlockCheckpointInfo *m_firstCheckpointInfo; ///< head of the linked list of checkpoint infos for this pool/dma
Bool m_cpiEverFailed; ///< flag to detect if we ran out of memory accumulating checkpoint info.
protected:
Checkpointable();
~Checkpointable();
/// create a new checkpoint info and add it to the list.
BlockCheckpointInfo *debugAddCheckpointInfo(
const char *debugLiteralTagString,
Int allocCheckpoint,
Int blockSize
);
public:
/// dump a checkpoint report to logfile
void debugCheckpointReport(Int flags, Int startCheckpoint, Int endCheckpoint, const char *poolName);
/// reset all the checkpoints for this pool/dma
void debugResetCheckpoints();
};
#endif
// ----------------------------------------------------------------------------
/**
A MemoryPool provides a way to efficiently allocate objects of the same (or similar)
size. We allocate large a large chunk of memory (a "blob") and subdivide it into
even-size chunks, doling these out as needed. If the first blob gets full, we allocate
additional blobs as necessary. A given pool can allocate blocks of only one size;
if you need a different size, you should use a different pool.
*/
class MemoryPool
#ifdef MEMORYPOOL_CHECKPOINTING
: public Checkpointable
#endif
{
private:
MemoryPoolFactory *m_factory; ///< the factory that created us
MemoryPool *m_nextPoolInFactory; ///< linked list node, managed by factory
const char *m_poolName; ///< name of this pool. (literal string; must not be freed)
Int m_allocationSize; ///< size of the blocks allocated by this pool, in bytes
Int m_initialAllocationCount; ///< number of blocks to be allocated in initial blob
Int m_overflowAllocationCount; ///< number of blocks to be allocated in any subsequent blob(s)
Int m_usedBlocksInPool; ///< total number of blocks in use in the pool.
Int m_totalBlocksInPool; ///< total number of blocks in all blobs of this pool (used or not).
Int m_peakUsedBlocksInPool; ///< high-water mark of m_usedBlocksInPool
MemoryPoolBlob *m_firstBlob; ///< head of linked list: first blob for this pool.
MemoryPoolBlob *m_lastBlob; ///< tail of linked list: last blob for this pool. (needed for efficiency)
MemoryPoolBlob *m_firstBlobWithFreeBlocks; ///< first blob in this pool that has at least one unallocated block.
private:
/// create a new blob with the given number of blocks.
MemoryPoolBlob* createBlob(Int allocationCount);
/// destroy a blob.
Int freeBlob(MemoryPoolBlob *blob);
public:
// 'public' funcs that are really only for use by MemoryPoolFactory
MemoryPool *getNextPoolInList(); ///< return next pool in linked list
void addToList(MemoryPool **pHead); ///< add this pool to head of the linked list
void removeFromList(MemoryPool **pHead); ///< remove this pool from the linked list
#ifdef MEMORYPOOL_DEBUG
static void debugPoolInfoReport( MemoryPool *pool, FILE *fp = NULL ); ///< dump a report about this pool to the logfile
const char *debugGetBlockTagString(void *pBlock); ///< return the tagstring for the given block (assumed to belong to this pool)
void debugMemoryVerifyPool(); ///< perform internal consistency check on this pool.
Int debugPoolReportLeaks( const char* owner );
#endif
#ifdef MEMORYPOOL_CHECKPOINTING
void debugResetCheckpoints(); ///< throw away all checkpoint information for this pool.
#endif
public:
MemoryPool();
/// initialize the given memory pool.
void init(MemoryPoolFactory *factory, const char *poolName, Int allocationSize, Int initialAllocationCount, Int overflowAllocationCount);
~MemoryPool();
/// allocate a block from this pool. (don't call directly; use allocateBlock() macro)
void *allocateBlockImplementation(DECLARE_LITERALSTRING_ARG1);
/// same as allocateBlockImplementation, but memory returned is not zeroed
void *allocateBlockDoNotZeroImplementation(DECLARE_LITERALSTRING_ARG1);
/// free the block. it is OK to pass null.
void freeBlock(void *pMem);
/// return the factory that created (and thus owns) this pool.
MemoryPoolFactory *getOwningFactory();
/// return the name of this pool. the result is a literal string and must not be freed.
const char *getPoolName();
/// return the block allocation size of this pool.
Int getAllocationSize();
/// return the number of free (available) blocks in this pool.
Int getFreeBlockCount();
/// return the number of blocks in use in this pool.
Int getUsedBlockCount();
/// return the total number of blocks in this pool. [ == getFreeBlockCount() + getUsedBlockCount() ]
Int getTotalBlockCount();
/// return the high-water mark for getUsedBlockCount()
Int getPeakBlockCount();
/// return the initial allocation count for this pool
Int getInitialBlockCount();
Int countBlobsInPool();
/// if this pool has any empty blobs, return them to the system.
Int releaseEmpties();
/// destroy all blocks and blobs in this pool.
void reset();
#ifdef MEMORYPOOL_DEBUG
/// return true iff this block was allocated by this pool.
Bool debugIsBlockInPool(void *pBlock);
#endif
};
// ----------------------------------------------------------------------------
/**
The DynamicMemoryAllocator class is used to handle unpredictably-sized
allocation requests. It basically allocates a number of (private) MemoryPools,
then routes request to the smallest-size pool that will satisfy the request.
(Requests too large for any of the pool are routed to the system memory allocator.)
You should normally use this in place of malloc/free or (global) new/delete.
*/
class DynamicMemoryAllocator
#ifdef MEMORYPOOL_CHECKPOINTING
: public Checkpointable
#endif
{
private:
MemoryPoolFactory *m_factory; ///< the factory that created us
DynamicMemoryAllocator *m_nextDmaInFactory; ///< linked list node, managed by factory
Int m_numPools; ///< number of subpools (up to MAX_DYNAMICMEMORYALLOCATOR_SUBPOOLS)
Int m_usedBlocksInDma; ///< total number of blocks allocated, from subpools and "raw"
MemoryPool *m_pools[MAX_DYNAMICMEMORYALLOCATOR_SUBPOOLS]; ///< the subpools
MemoryPoolSingleBlock *m_rawBlocks; ///< linked list of "raw" blocks allocated directly from system
/// return the best pool for the given allocSize, or null if none are suitable
MemoryPool *findPoolForSize(Int allocSize);
public:
// 'public' funcs that are really only for use by MemoryPoolFactory
DynamicMemoryAllocator *getNextDmaInList(); ///< return next dma in linked list
void addToList(DynamicMemoryAllocator **pHead); ///< add this dma to the list
void removeFromList(DynamicMemoryAllocator **pHead); ///< remove this dma from the list
#ifdef MEMORYPOOL_DEBUG
Int debugCalcRawBlockBytes(Int *numBlocks); ///< calculate the number of bytes in "raw" (non-subpool) blocks
void debugMemoryVerifyDma(); ///< perform internal consistency check
const char *debugGetBlockTagString(void *pBlock); ///< return the tagstring for the given block (assumed to belong to this dma)
void debugDmaInfoReport( FILE *fp = NULL ); ///< dump a report about this pool to the logfile
Int debugDmaReportLeaks();
#endif
#ifdef MEMORYPOOL_CHECKPOINTING
void debugResetCheckpoints(); ///< toss all checkpoint information
#endif
public:
DynamicMemoryAllocator();
/// initialize the dma. pass 0/null for numSubPool/parms to get some reasonable default subpools.
void init(MemoryPoolFactory *factory, Int numSubPools, const PoolInitRec pParms[]);
~DynamicMemoryAllocator();
/// allocate bytes from this pool. (don't call directly; use allocateBytes() macro)
void *allocateBytesImplementation(Int numBytes DECLARE_LITERALSTRING_ARG2);
/// like allocateBytesImplementation, but zeroes the memory before returning
void *allocateBytesDoNotZeroImplementation(Int numBytes DECLARE_LITERALSTRING_ARG2);
#ifdef MEMORYPOOL_DEBUG
void debugIgnoreLeaksForThisBlock(void* pBlockPtr);
#endif
/// free the bytes. (assumes allocated by this dma.)
void freeBytes(void* pMem);
/**
return the actual number of bytes that would be allocated
if you tried to allocate the given size. (It will generally be slightly
larger than you request.) This lets you use extra space if you're gonna get it anyway...
The idea is that you will call this before doing a memory allocation, to see if
you got any extra "bonus" space.
*/
Int getActualAllocationSize(Int numBytes);
/// destroy all allocations performed by this DMA.
void reset();
Int getDmaMemoryPoolCount() const { return m_numPools; }
MemoryPool* getNthDmaMemoryPool(Int i) const { return m_pools[i]; }
#ifdef MEMORYPOOL_DEBUG
/// return true iff this block was allocated by this dma
Bool debugIsBlockInDma(void *pBlock);
/// return true iff the pool is a subpool of this dma
Bool debugIsPoolInDma(MemoryPool *pool);
#endif // MEMORYPOOL_DEBUG
};
// ----------------------------------------------------------------------------
#ifdef MEMORYPOOL_DEBUG
enum { MAX_SPECIAL_USED = 256 };
#endif
// ----------------------------------------------------------------------------
/**
The class that manages all the MemoryPools and DynamicMemoryAllocators.
Usually you will create exactly one of these (TheMemoryPoolFactory)
and use it for everything.
*/
class MemoryPoolFactory
{
private:
MemoryPool *m_firstPoolInFactory; ///< linked list of pools
DynamicMemoryAllocator *m_firstDmaInFactory; ///< linked list of dmas
#ifdef MEMORYPOOL_CHECKPOINTING
Int m_curCheckpoint; ///< most recent checkpoint value
#endif
#ifdef MEMORYPOOL_DEBUG
Int m_usedBytes; ///< total bytes in use
Int m_physBytes; ///< total bytes allocated to all pools (includes unused blocks)
Int m_peakUsedBytes; ///< high-water mark of m_usedBytes
Int m_peakPhysBytes; ///< high-water mark of m_physBytes
Int m_usedBytesSpecial[MAX_SPECIAL_USED];
Int m_usedBytesSpecialPeak[MAX_SPECIAL_USED];
Int m_physBytesSpecial[MAX_SPECIAL_USED];
Int m_physBytesSpecialPeak[MAX_SPECIAL_USED];
#endif
public:
// 'public' funcs that are really only for use by MemoryPool and friends
#ifdef MEMORYPOOL_DEBUG
/// adjust the usedBytes and physBytes variables by the given amoun ts.
void adjustTotals(const char* tagString, Int usedDelta, Int physDelta);
#endif
#ifdef MEMORYPOOL_CHECKPOINTING
/// return the current checkpoint value.
Int getCurCheckpoint() { return m_curCheckpoint; }
#endif
public:
MemoryPoolFactory();
void init();
~MemoryPoolFactory();
/// create a new memory pool with the given settings. if a pool with the given name already exists, return it.
MemoryPool *createMemoryPool(const PoolInitRec *parms);
/// overloaded version of createMemoryPool with explicit parms.
MemoryPool *createMemoryPool(const char *poolName, Int allocationSize, Int initialAllocationCount, Int overflowAllocationCount);
/// return the pool with the given name. if no such pool exists, return null.
MemoryPool *findMemoryPool(const char *poolName);
/// destroy the given pool.
void destroyMemoryPool(MemoryPool *pMemoryPool);
/// create a DynamicMemoryAllocator with subpools with the given parms.
DynamicMemoryAllocator *createDynamicMemoryAllocator(Int numSubPools, const PoolInitRec pParms[]);
/// destroy the given DynamicMemoryAllocator.
void destroyDynamicMemoryAllocator(DynamicMemoryAllocator *dma);
/// destroy the contents of all pools and dmas. (the pools and dma's are not destroyed, just reset)
void reset();
void memoryPoolUsageReport( const char* filename, FILE *appendToFileInstead = NULL );
#ifdef MEMORYPOOL_DEBUG
/// perform internal consistency checking
void debugMemoryVerify();
/// return true iff the block was allocated by any pool or dma owned by this factory.
Bool debugIsBlockInAnyPool(void *pBlock);
/// return the tag string for the block.
const char *debugGetBlockTagString(void *pBlock);
/// dump a report with the given options to the logfile.
void debugMemoryReport(Int flags, Int startCheckpoint, Int endCheckpoint, FILE *fp = NULL );
void debugSetInitFillerIndex(Int index);
#endif
#ifdef MEMORYPOOL_CHECKPOINTING
/// set a new checkpoint.
Int debugSetCheckpoint();
/// reset all checkpoint information.
void debugResetCheckpoints();
#endif
};
// how many bytes are we allowed to 'waste' per pool allocation before the debug code starts yelling at us...
#define MEMORY_POOL_OBJECT_ALLOCATION_SLOP 16
// ----------------------------------------------------------------------------
#define GCMP_FIND(ARGCLASS, ARGPOOLNAME) \
private: \
static MemoryPool *getClassMemoryPool() \
{ \
/* \
Note that this static variable will be initialized exactly once: the first time \
control flows over this section of code. This allows us to neatly resolve the \
order-of-execution problem for static variables, ensuring this is not executed \
prior to the initialization of TheMemoryPoolFactory. \
*/ \
DEBUG_ASSERTCRASH(TheMemoryPoolFactory, ("TheMemoryPoolFactory is NULL\n")); \
static MemoryPool *The##ARGCLASS##Pool = TheMemoryPoolFactory->findMemoryPool(ARGPOOLNAME); \
DEBUG_ASSERTCRASH(The##ARGCLASS##Pool, ("Pool \"%s\" not found (did you set it up in initMemoryPools?)\n", ARGPOOLNAME)); \
DEBUG_ASSERTCRASH(The##ARGCLASS##Pool->getAllocationSize() >= sizeof(ARGCLASS), ("Pool \"%s\" is too small for this class (currently %d, need %d)\n", ARGPOOLNAME, The##ARGCLASS##Pool->getAllocationSize(), sizeof(ARGCLASS))); \
DEBUG_ASSERTCRASH(The##ARGCLASS##Pool->getAllocationSize() <= sizeof(ARGCLASS)+MEMORY_POOL_OBJECT_ALLOCATION_SLOP, ("Pool \"%s\" is too large for this class (currently %d, need %d)\n", ARGPOOLNAME, The##ARGCLASS##Pool->getAllocationSize(), sizeof(ARGCLASS))); \
return The##ARGCLASS##Pool; \
}
// ----------------------------------------------------------------------------
#define GCMP_CREATE(ARGCLASS, ARGPOOLNAME, ARGINITIAL, ARGOVERFLOW) \
private: \
static MemoryPool *getClassMemoryPool() \
{ \
/* \
Note that this static variable will be initialized exactly once: the first time \
control flows over this section of code. This allows us to neatly resolve the \
order-of-execution problem for static variables, ensuring this is not executed \
prior to the initialization of TheMemoryPoolFactory. \
*/ \
DEBUG_ASSERTCRASH(TheMemoryPoolFactory, ("TheMemoryPoolFactory is NULL\n")); \
static MemoryPool *The##ARGCLASS##Pool = TheMemoryPoolFactory->createMemoryPool(ARGPOOLNAME, sizeof(ARGCLASS), ARGINITIAL, ARGOVERFLOW); \
DEBUG_ASSERTCRASH(The##ARGCLASS##Pool, ("Pool \"%s\" not found (did you set it up in initMemoryPools?)\n", ARGPOOLNAME)); \
DEBUG_ASSERTCRASH(The##ARGCLASS##Pool->getAllocationSize() >= sizeof(ARGCLASS), ("Pool \"%s\" is too small for this class (currently %d, need %d)\n", ARGPOOLNAME, The##ARGCLASS##Pool->getAllocationSize(), sizeof(ARGCLASS))); \
DEBUG_ASSERTCRASH(The##ARGCLASS##Pool->getAllocationSize() <= sizeof(ARGCLASS)+MEMORY_POOL_OBJECT_ALLOCATION_SLOP, ("Pool \"%s\" is too large for this class (currently %d, need %d)\n", ARGPOOLNAME, The##ARGCLASS##Pool->getAllocationSize(), sizeof(ARGCLASS))); \
return The##ARGCLASS##Pool; \
}
// ----------------------------------------------------------------------------
#define MEMORY_POOL_GLUE_WITHOUT_GCMP(ARGCLASS) \
protected: \
virtual ~ARGCLASS(); \
public: \
enum ARGCLASS##MagicEnum { ARGCLASS##_GLUE_NOT_IMPLEMENTED = 0 }; \
public: \
inline void *operator new(size_t s, ARGCLASS##MagicEnum e DECLARE_LITERALSTRING_ARG2) \
{ \
DEBUG_ASSERTCRASH(s == sizeof(ARGCLASS), ("The wrong operator new is being called; ensure all objects in the hierarchy have MemoryPoolGlue set up correctly")); \
return ARGCLASS::getClassMemoryPool()->allocateBlockImplementation(PASS_LITERALSTRING_ARG1); \
} \
public: \
/* \
Note that this delete operator can't be called directly; it is called \
only if the analogous new operator is called, AND the constructor \
throws an exception... \
*/ \
inline void operator delete(void *p, ARGCLASS##MagicEnum e DECLARE_LITERALSTRING_ARG2) \
{ \
ARGCLASS::getClassMemoryPool()->freeBlock(p); \
} \
protected: \
/* \
Make normal new and delete protected, so they can't be called by the outside world. \
Note that delete is funny, in that it can still be called by the class itself; \
this is safe but not recommended, for consistency purposes. More problematically, \
it can be called by another class that has declared itself 'friend' to us. \
In theory, this shouldn't work, since it may not use the right operator-delete, \
and thus the wrong memory pool; in practice, it seems the right delete IS called \
in MSVC -- it seems to make operator delete virtual if the destructor is also virtual. \
At any rate, this is undocumented behavior as far as I can tell, so we put a big old \
crash into operator delete telling people to do the right thing and call deleteInstance \
instead -- it'd be nice if we could catch this at compile time, but catching it at \
runtime seems to be the best we can do... \
*/ \
inline void *operator new(size_t s) \
{ \
DEBUG_CRASH(("This operator new should normally never be called... please use new(char*) instead.")); \
DEBUG_ASSERTCRASH(s == sizeof(ARGCLASS), ("The wrong operator new is being called; ensure all objects in the hierarchy have MemoryPoolGlue set up correctly")); \
throw ERROR_BUG; \
return 0; \
} \
inline void operator delete(void *p) \
{ \
DEBUG_CRASH(("Please call deleteInstance instead of delete.")); \
ARGCLASS::getClassMemoryPool()->freeBlock(p); \
} \
private: \
virtual MemoryPool *getObjectMemoryPool() \
{ \
return ARGCLASS::getClassMemoryPool(); \
} \
public: /* include this line at the end to reset visibility to 'public' */
// ----------------------------------------------------------------------------
#define MEMORY_POOL_GLUE(ARGCLASS, ARGPOOLNAME) \
MEMORY_POOL_GLUE_WITHOUT_GCMP(ARGCLASS) \
GCMP_FIND(ARGCLASS, ARGPOOLNAME)
// ----------------------------------------------------------------------------
#define MEMORY_POOL_GLUE_WITH_EXPLICIT_CREATE(ARGCLASS, ARGPOOLNAME, ARGINITIAL, ARGOVERFLOW) \
MEMORY_POOL_GLUE_WITHOUT_GCMP(ARGCLASS) \
GCMP_CREATE(ARGCLASS, ARGPOOLNAME, ARGINITIAL, ARGOVERFLOW)
// ----------------------------------------------------------------------------
#define MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ARGCLASS, ARGPOOLNAME) \
MEMORY_POOL_GLUE_WITHOUT_GCMP(ARGCLASS) \
GCMP_CREATE(ARGCLASS, ARGPOOLNAME, -1, -1)
// ----------------------------------------------------------------------------
// this is the version for an Abstract Base Class, which will never be instantiated...
#define MEMORY_POOL_GLUE_ABC(ARGCLASS) \
protected: \
virtual ~ARGCLASS(); \
public: \
enum ARGCLASS##MagicEnum { ARGCLASS##_GLUE_NOT_IMPLEMENTED = 0 }; \
protected: \
inline void *operator new(size_t s, ARGCLASS##MagicEnum e DECLARE_LITERALSTRING_ARG2) \
{ \
DEBUG_CRASH(("this should be impossible to call (abstract base class)")); \
DEBUG_ASSERTCRASH(s == sizeof(ARGCLASS), ("The wrong operator new is being called; ensure all objects in the hierarchy have MemoryPoolGlue set up correctly")); \
throw ERROR_BUG; \
return 0; \
} \
protected: \
inline void operator delete(void *p, ARGCLASS##MagicEnum e DECLARE_LITERALSTRING_ARG2) \
{ \
DEBUG_CRASH(("this should be impossible to call (abstract base class)")); \
} \
protected: \
inline void *operator new(size_t s) \
{ \
DEBUG_CRASH(("this should be impossible to call (abstract base class)")); \
DEBUG_ASSERTCRASH(s == sizeof(ARGCLASS), ("The wrong operator new is being called; ensure all objects in the hierarchy have MemoryPoolGlue set up correctly")); \
throw ERROR_BUG; \
return 0; \
} \
inline void operator delete(void *p) \
{ \
DEBUG_CRASH(("this should be impossible to call (abstract base class)")); \
} \
private: \
virtual MemoryPool *getObjectMemoryPool() \
{ \
throw ERROR_BUG; \
return 0; \
} \
public: /* include this line at the end to reset visibility to 'public' */
// ----------------------------------------------------------------------------
/**
Sometimes you want to make a class's destructor protected so that it can only
be destroyed under special circumstances. MemoryPoolObject short-circuits this
by making the destructor always be protected, and the true delete technique
(namely, deleteInstance) always public by default. You can simulate the behavior
you really want by including this macro
*/
#define MEMORY_POOL_DELETEINSTANCE_VISIBILITY(ARGVIS)\
ARGVIS: void deleteInstance() { MemoryPoolObject::deleteInstance(); } public:
// ----------------------------------------------------------------------------
/**
This class is provided as a simple and safe way to integrate C++ object allocation
into MemoryPool usage. To use it, you must have your class inherit from
MemoryPoolObject, then put the macro MEMORY_POOL_GLUE(MyClassName, "MyPoolName")
at the start of your class definition. (This does not create the pool itself -- you
must create that manually using MemoryPoolFactory::createMemoryPool)
*/
class MemoryPoolObject
{
protected:
/** ensure that all destructors are virtual */
virtual ~MemoryPoolObject() { }
protected:
inline void *operator new(size_t s) { DEBUG_CRASH(("This should be impossible")); return 0; }
inline void operator delete(void *p) { DEBUG_CRASH(("This should be impossible")); }
protected:
virtual MemoryPool *getObjectMemoryPool() = 0;
public:
void deleteInstance()
{
if (this)
{
MemoryPool *pool = this->getObjectMemoryPool(); // save this, since the dtor will nuke our vtbl
this->~MemoryPoolObject(); // it's virtual, so the right one will be called.
pool->freeBlock((void *)this);
}
}
};
// ----------------------------------------------------------------------------
/**
A simple utility class to ensure exception safety; this holds a MemoryPoolObject
and deletes it in its destructor. Especially useful for iterators!
*/
class MemoryPoolObjectHolder
{
private:
MemoryPoolObject *m_mpo;
public:
MemoryPoolObjectHolder(MemoryPoolObject *mpo = NULL) : m_mpo(mpo) { }
void hold(MemoryPoolObject *mpo) { DEBUG_ASSERTCRASH(!m_mpo, ("already holding")); m_mpo = mpo; }
void release() { m_mpo = NULL; }
~MemoryPoolObjectHolder() { m_mpo->deleteInstance(); }
};
// INLINING ///////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------
inline MemoryPoolFactory *MemoryPool::getOwningFactory() { return m_factory; }
inline MemoryPool *MemoryPool::getNextPoolInList() { return m_nextPoolInFactory; }
inline const char *MemoryPool::getPoolName() { return m_poolName; }
inline Int MemoryPool::getAllocationSize() { return m_allocationSize; }
inline Int MemoryPool::getFreeBlockCount() { return getTotalBlockCount() - getUsedBlockCount(); }
inline Int MemoryPool::getUsedBlockCount() { return m_usedBlocksInPool; }
inline Int MemoryPool::getTotalBlockCount() { return m_totalBlocksInPool; }
inline Int MemoryPool::getPeakBlockCount() { return m_peakUsedBlocksInPool; }
inline Int MemoryPool::getInitialBlockCount() { return m_initialAllocationCount; }
// ----------------------------------------------------------------------------
inline DynamicMemoryAllocator *DynamicMemoryAllocator::getNextDmaInList() { return m_nextDmaInFactory; }
// EXTERNALS //////////////////////////////////////////////////////////////////
/**
Initialize the memory manager. Construct a new MemoryPoolFactory and
DynamicMemoryAllocator and store 'em in the singletons of the relevant
names.
*/
extern void initMemoryManager();
/**
return true if initMemoryManager() has been called.
return false if only preMainInitMemoryManager() has been called.
*/
extern Bool isMemoryManagerOfficiallyInited();
/**
similar to initMemoryManager, but this should be used if the memory manager must be initialized
prior to main() (e.g., from a static constructor). If preMainInitMemoryManager() is called prior
to initMemoryManager(), then subsequent calls to either are quietly ignored, AS IS any subsequent
call to shutdownMemoryManager() [since there's no safe way to ensure that shutdownMemoryManager
will execute after all static destructors].
(Note: this function is actually not externally visible, but is documented here for clarity.)
*/
/* extern void preMainInitMemoryManager(); */
/**
Shut down the memory manager. Throw away TheMemoryPoolFactory and
TheDynamicMemoryAllocator.
*/
extern void shutdownMemoryManager();
extern MemoryPoolFactory *TheMemoryPoolFactory;
extern DynamicMemoryAllocator *TheDynamicMemoryAllocator;
/**
This function is declared in this header, but is not defined anywhere -- you must provide
it in your code. It is called by initMemoryManager() or preMainInitMemoryManager() in order
to get the specifics of the subpool for the dynamic memory allocator. (If you just want
some defaults, set both return arguments to zero.) The reason for this odd setup is that
we may need to init the memory manager prior to main() [due to static C++ ctors] and
this allows us a way to get the necessary parameters.
*/
extern void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms);
/**
This function is declared in this header, but is not defined anywhere -- you must provide
it in your code. It is called by initMemoryManager() or preMainInitMemoryManager() in order
to initialize the pools to be used. (You can define an empty function if you like.)
*/
extern void userMemoryManagerInitPools();
/**
This function is declared in this header, but is not defined anywhere -- you must provide
it in your code. It is called by createMemoryPool to adjust the allocation size(s) for a
given pool. Note that the counts are in-out parms!
*/
extern void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount);
#ifdef __cplusplus
#ifndef _OPERATOR_NEW_DEFINED_
#define _OPERATOR_NEW_DEFINED_
extern void * __cdecl operator new (size_t size);
extern void __cdecl operator delete (void *p);
extern void * __cdecl operator new[] (size_t size);
extern void __cdecl operator delete[] (void *p);
// additional overloads to account for VC/MFC funky versions
extern void* __cdecl operator new(size_t nSize, const char *, int);
extern void __cdecl operator delete(void *, const char *, int);
extern void* __cdecl operator new[](size_t nSize, const char *, int);
extern void __cdecl operator delete[](void *, const char *, int);
// additional overloads for 'placement new'
//inline void* __cdecl operator new (size_t s, void *p) { return p; }
//inline void __cdecl operator delete (void *, void *p) { }
inline void* __cdecl operator new[] (size_t s, void *p) { return p; }
inline void __cdecl operator delete[] (void *, void *p) { }
#endif
#ifdef MEMORYPOOL_DEBUG_CUSTOM_NEW
#define MSGNEW(MSG) new(MSG, 0)
#define NEW new(__FILE__, __LINE__)
#else
#define MSGNEW(MSG) new
#define NEW new
#endif
#endif
class STLSpecialAlloc
{
public:
static void* allocate(size_t __n);
static void deallocate(void* __p, size_t);
};
#define EMPTY_DTOR(CLASS) inline CLASS::~CLASS() { }
#endif // _GAME_MEMORY_H_