501 lines
11 KiB
C++
501 lines
11 KiB
C++
|
//Anything above this #include will be ignored by the compiler
|
||
|
#include "../qcommon/exe_headers.h"
|
||
|
|
||
|
#ifdef _DONETPROFILE_
|
||
|
#include <assert.h>
|
||
|
#include "hstring.h"
|
||
|
|
||
|
using namespace std;
|
||
|
|
||
|
// mapPoolBlockCount is defined differently in the executable (sv_main.cpp) and the game dll (g_main.cpp) cuz
|
||
|
//we likely don't need as many blocks in the executable as we do in the game
|
||
|
extern int mapPoolBlockCount;
|
||
|
|
||
|
// Used to fool optimizer during compilation of mem touch routines.
|
||
|
int HaHaOptimizer2=0;
|
||
|
|
||
|
CMapPoolLow &GetMapPool()
|
||
|
{
|
||
|
// this may need to be ifdefed to be different for different modules
|
||
|
static CMapPoolLow thePool;
|
||
|
return thePool;
|
||
|
}
|
||
|
|
||
|
#define MAPBLOCK_SIZE_NODES (1024)
|
||
|
#define MAPNODE_FREE (0xa1)
|
||
|
#define MAPNODE_INUSE (0x94)
|
||
|
|
||
|
struct SMapNode
|
||
|
{
|
||
|
unsigned char mData[MAP_NODE_SIZE-2];
|
||
|
unsigned char mMapBlockNum;
|
||
|
unsigned char mTag;
|
||
|
};
|
||
|
|
||
|
class CMapBlock
|
||
|
{
|
||
|
int mId;
|
||
|
char mRaw[(MAPBLOCK_SIZE_NODES+1)*MAP_NODE_SIZE];
|
||
|
SMapNode *mNodes;
|
||
|
int mLastNode;
|
||
|
|
||
|
public:
|
||
|
CMapBlock(int id,vector <void *> &freeList) :
|
||
|
mLastNode(0)
|
||
|
{
|
||
|
// Alloc node storage for MAPBLOCK_SIZE_NODES worth of nodes.
|
||
|
mNodes=(SMapNode *)((((unsigned long)mRaw)+MAP_NODE_SIZE)&~(unsigned long)0x1f);
|
||
|
// Set all nodes to initially be free.
|
||
|
int i;
|
||
|
for(i=0;i<MAPBLOCK_SIZE_NODES;i++)
|
||
|
{
|
||
|
mNodes[i].mMapBlockNum=id;
|
||
|
mNodes[i].mTag=MAPNODE_FREE;
|
||
|
freeList.push_back((void *)&mNodes[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool bOwnsNode(void *node)
|
||
|
{
|
||
|
return((((SMapNode *)node)>=&mNodes[0])&&(((SMapNode *)node)<&mNodes[MAPBLOCK_SIZE_NODES]));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
CMapPoolLow::CMapPoolLow()
|
||
|
{
|
||
|
mLastBlockNum=-1;
|
||
|
}
|
||
|
|
||
|
CMapPoolLow::~CMapPoolLow()
|
||
|
{
|
||
|
#if _DEBUG
|
||
|
#if _GAME
|
||
|
if(mFreeList.size()<mMapBlocks.size()*MAPBLOCK_SIZE_NODES)
|
||
|
{
|
||
|
// Com_Printf("[MEM][GAME] !!!! Map Pool Leaked %d nodes\n",(MAPBLOCK_SIZE_NODES*mMapBlocks.size())-mFreeList.size());
|
||
|
}
|
||
|
// Com_Printf("[MEM][GAME] Map Pool max. mem used = %d\n",mMapBlocks.size()*MAPBLOCK_SIZE_NODES*MAP_NODE_SIZE);
|
||
|
#elif _CGAME
|
||
|
if (mFreeList.size()<mMapBlocks.size()*MAPBLOCK_SIZE_NODES)
|
||
|
{
|
||
|
// Com_Printf("[MEM][CGAME] !!!! Map Pool Leaked %d nodes\n",(MAPBLOCK_SIZE_NODES*mMapBlocks.size())-mFreeList.size());
|
||
|
}
|
||
|
// Com_Printf("[MEM][CGAME] Map Pool max. mem used = %d\n",mMapBlocks.size()*MAPBLOCK_SIZE_NODES*MAP_NODE_SIZE);
|
||
|
#else
|
||
|
if (mFreeList.size()<mMapBlocks.size()*MAPBLOCK_SIZE_NODES)
|
||
|
{
|
||
|
// Com_Printf("[MEM][EXE] !!!! Map Pool Leaked %d nodes\n",(MAPBLOCK_SIZE_NODES*mMapBlocks.size())-mFreeList.size());
|
||
|
}
|
||
|
// Com_Printf("[MEM][EXE] Map Pool max. mem used = %d\n",mMapBlocks.size()*MAPBLOCK_SIZE_NODES*MAP_NODE_SIZE);
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
int i;
|
||
|
for (i=0;i<mMapBlocks.size();i++)
|
||
|
{
|
||
|
delete mMapBlocks[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void *CMapPoolLow::Alloc()
|
||
|
{
|
||
|
// Try to request a node. First we look in the free-list, but if that
|
||
|
// happens to be empty, we allocate more storage in the current CMapBlock.
|
||
|
void *node=0;
|
||
|
if(mFreeList.size())
|
||
|
{
|
||
|
// Retrieve the node to be recycled.
|
||
|
node=(void *)mFreeList[mFreeList.size()-1];
|
||
|
mFreeList.pop_back();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// None free, so alloc another block.
|
||
|
CMapBlock *block=new CMapBlock(mLastBlockNum+1,mFreeList);
|
||
|
assert(block);
|
||
|
mMapBlocks.push_back(block);
|
||
|
mLastBlockNum++;
|
||
|
node=(void *)mFreeList[mFreeList.size()-1];
|
||
|
mFreeList.pop_back();
|
||
|
}
|
||
|
|
||
|
// Validate we aren't somehow grabbing something that is already in use
|
||
|
// and also that the end marker is intact.
|
||
|
assert(((SMapNode *)node)->mTag==MAPNODE_FREE);
|
||
|
assert((((SMapNode *)node)->mMapBlockNum)>=0);
|
||
|
assert((((SMapNode *)node)->mMapBlockNum)<256);
|
||
|
assert((((SMapNode *)node)->mMapBlockNum)<=mLastBlockNum);
|
||
|
assert(mMapBlocks[((SMapNode *)node)->mMapBlockNum]->bOwnsNode(node));
|
||
|
|
||
|
// Ok, mark the node as in use.
|
||
|
((SMapNode *)node)->mTag=MAPNODE_INUSE;
|
||
|
|
||
|
return(node);
|
||
|
}
|
||
|
|
||
|
void CMapPoolLow::Free(void *p)
|
||
|
{
|
||
|
// Validate that someone isn't trying to double free this node and also
|
||
|
// that the end marker is intact.
|
||
|
assert(((SMapNode *)p)->mTag==MAPNODE_INUSE);
|
||
|
assert((((SMapNode *)p)->mMapBlockNum)>=0);
|
||
|
assert((((SMapNode *)p)->mMapBlockNum)<256);
|
||
|
assert((((SMapNode *)p)->mMapBlockNum)<=mLastBlockNum);
|
||
|
assert(mMapBlocks[((SMapNode *)p)->mMapBlockNum]->bOwnsNode(p));
|
||
|
|
||
|
// Ok, mark the the node as free.
|
||
|
((SMapNode *)p)->mTag=MAPNODE_FREE;
|
||
|
|
||
|
// Add a new freelist entry to point at this node.
|
||
|
mFreeList.push_back(p);
|
||
|
}
|
||
|
|
||
|
void CMapPoolLow::TouchMem()
|
||
|
{
|
||
|
int i,j;
|
||
|
unsigned char *memory;
|
||
|
int totSize=0;
|
||
|
for(i=0;i<mMapBlocks.size();i++)
|
||
|
{
|
||
|
memory=(unsigned char *)mMapBlocks[i];
|
||
|
totSize+=sizeof(CMapBlock);
|
||
|
for(j=0;j<sizeof(CMapBlock);j+=256)
|
||
|
{
|
||
|
HaHaOptimizer2+=memory[j];
|
||
|
}
|
||
|
}
|
||
|
#ifdef _DEBUG
|
||
|
// Com_Printf("MapPool: Bytes touched %i\n",totSize);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
////////////
|
||
|
// hString stuff
|
||
|
////////////
|
||
|
#define MAX_HASH (65536*2)
|
||
|
|
||
|
// Max number of strings we can ever deal with.
|
||
|
#define MAX_STRINGS 100000
|
||
|
|
||
|
// Size of a string storage block in bytes.
|
||
|
#define BLOCK_SIZE 65536
|
||
|
|
||
|
|
||
|
int HashFunction(const char *key)
|
||
|
{
|
||
|
long hash=0;
|
||
|
int i=0;
|
||
|
unsigned char letter;
|
||
|
|
||
|
letter = (unsigned char)*key++;
|
||
|
while (letter)
|
||
|
{
|
||
|
hash += (long)(letter) * (i + 119);
|
||
|
i++;
|
||
|
letter = (unsigned char)*key++;
|
||
|
}
|
||
|
hash &= MAX_HASH - 1;
|
||
|
return (int)hash;
|
||
|
}
|
||
|
|
||
|
|
||
|
class CHashHelper
|
||
|
{
|
||
|
int mHashes[MAX_HASH];
|
||
|
int mFindPtr;
|
||
|
int mFindPtrStart;
|
||
|
public:
|
||
|
CHashHelper()
|
||
|
{
|
||
|
int i;
|
||
|
for (i=0;i<MAX_HASH;i++)
|
||
|
{
|
||
|
mHashes[i]=0;
|
||
|
}
|
||
|
}
|
||
|
void Add(int hash,int value)
|
||
|
{
|
||
|
assert(hash>=0&&hash<MAX_HASH);
|
||
|
assert(value); // 0 is the empty marker
|
||
|
int i=hash;
|
||
|
while (mHashes[i])
|
||
|
{
|
||
|
assert(mHashes[i]!=value); //please don't insert things twice
|
||
|
i=(i+1)&(MAX_HASH-1);
|
||
|
assert(i!=hash); //hash table is full?
|
||
|
}
|
||
|
mHashes[i]=value;
|
||
|
}
|
||
|
int FindFirst(int hash)
|
||
|
{
|
||
|
mFindPtr=hash;
|
||
|
mFindPtrStart=hash;
|
||
|
return FindNext();
|
||
|
}
|
||
|
int FindNext()
|
||
|
{
|
||
|
assert(mFindPtr>=0&&mFindPtr<MAX_HASH);
|
||
|
int val=mHashes[mFindPtr];
|
||
|
mFindPtr=(mFindPtr+1)&(MAX_HASH-1);
|
||
|
assert(mFindPtr!=mFindPtrStart); //hash table full?
|
||
|
return val;
|
||
|
}
|
||
|
void TouchMem()
|
||
|
{
|
||
|
int i;
|
||
|
for(i=0;i<sizeof(mHashes);i+=256)
|
||
|
{
|
||
|
HaHaOptimizer2+=((unsigned char *)mHashes)[i];
|
||
|
}
|
||
|
#ifdef _DEBUG
|
||
|
// Com_Printf("Hash helper: Bytes touched %i\n",sizeof(mHashes));
|
||
|
#endif
|
||
|
}
|
||
|
};
|
||
|
|
||
|
CHashHelper &HashHelper()
|
||
|
{
|
||
|
static CHashHelper It;
|
||
|
return It;
|
||
|
}
|
||
|
|
||
|
static char *gCharPtrs[MAX_STRINGS];
|
||
|
|
||
|
class CHSBlock
|
||
|
{
|
||
|
int mBytesUsed;
|
||
|
char mRaw[BLOCK_SIZE];
|
||
|
|
||
|
public:
|
||
|
CHSBlock(void) :
|
||
|
mBytesUsed(0)
|
||
|
{
|
||
|
// So we can do a comparison of blocks for debug purposes.
|
||
|
memset(mRaw,0,BLOCK_SIZE);
|
||
|
};
|
||
|
|
||
|
char *Alloc(int sizeBytes)
|
||
|
{
|
||
|
// Remember to include 0 termintor in size.
|
||
|
sizeBytes++;
|
||
|
|
||
|
// Is it WAAAY to big? If so we complain loudly.
|
||
|
assert(sizeBytes<=BLOCK_SIZE);
|
||
|
|
||
|
// If we don't have space in the current block, return failure.
|
||
|
if(sizeBytes>(BLOCK_SIZE-mBytesUsed))
|
||
|
{
|
||
|
return(0);
|
||
|
}
|
||
|
|
||
|
// Return the pointer to the start of allocated space.
|
||
|
char *ret=&mRaw[mBytesUsed];
|
||
|
mBytesUsed+=sizeBytes;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
bool operator== (const CHSBlock &block) const
|
||
|
{
|
||
|
if(!memcmp(mRaw,block.mRaw,BLOCK_SIZE))
|
||
|
{
|
||
|
return(true);
|
||
|
}
|
||
|
return(false);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
class CPool
|
||
|
{
|
||
|
vector<CHSBlock *> mBlockVec;
|
||
|
|
||
|
public:
|
||
|
int mNextStringId;
|
||
|
int mLastBlockNum;
|
||
|
|
||
|
CPool(void) :
|
||
|
mNextStringId(1),
|
||
|
mLastBlockNum(-1)
|
||
|
{
|
||
|
memset(gCharPtrs,0,MAX_STRINGS*4);
|
||
|
}
|
||
|
|
||
|
~CPool(void)
|
||
|
{
|
||
|
int i;
|
||
|
for (i=0;i<mBlockVec.size();i++)
|
||
|
{
|
||
|
delete mBlockVec[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
char *Alloc(int sizeBytes,int &id)
|
||
|
{
|
||
|
// Can't alloc more than MAX_STRINGS.
|
||
|
assert(mNextStringId<MAX_STRINGS);
|
||
|
char *raw=0;
|
||
|
if (mLastBlockNum>=0)
|
||
|
{
|
||
|
// Get the pointer to the start of allocated space in the current block.
|
||
|
raw=mBlockVec[mLastBlockNum]->Alloc(sizeBytes);
|
||
|
}
|
||
|
if(!raw)
|
||
|
{
|
||
|
// Ok, make a new empty block and append it.
|
||
|
CHSBlock *block=new(CHSBlock);
|
||
|
mBlockVec.push_back(block);
|
||
|
mLastBlockNum++;
|
||
|
raw=mBlockVec[mLastBlockNum]->Alloc(sizeBytes);
|
||
|
}
|
||
|
// Should never really happen!!
|
||
|
assert(raw);
|
||
|
|
||
|
id=mNextStringId;
|
||
|
gCharPtrs[mNextStringId]=raw;
|
||
|
mNextStringId++;
|
||
|
|
||
|
return(raw);
|
||
|
}
|
||
|
|
||
|
bool operator== (const CPool &pool) const
|
||
|
{
|
||
|
int i;
|
||
|
for(i=0;i<mBlockVec.size();i++)
|
||
|
{
|
||
|
if(!(*mBlockVec[i]==*pool.mBlockVec[i]))
|
||
|
{
|
||
|
return(false);
|
||
|
}
|
||
|
}
|
||
|
return(true);
|
||
|
}
|
||
|
|
||
|
void TouchMem()
|
||
|
{
|
||
|
int i,j;
|
||
|
unsigned char *memory;
|
||
|
int totSize=0;
|
||
|
for(i=0;i<mBlockVec.size();i++)
|
||
|
{
|
||
|
memory=(unsigned char *)mBlockVec[i];
|
||
|
totSize+=sizeof(CHSBlock);
|
||
|
for(j=0;j<sizeof(CHSBlock);j+=256)
|
||
|
{
|
||
|
HaHaOptimizer2+=memory[j];
|
||
|
}
|
||
|
}
|
||
|
#ifdef _DEBUG
|
||
|
// Com_Printf("String Pool: Bytes touched %i\n",totSize);
|
||
|
#endif
|
||
|
}
|
||
|
};
|
||
|
|
||
|
#ifdef _DEBUG
|
||
|
CPool &TheDebugPool(void);
|
||
|
CPool &ThePool(void);
|
||
|
|
||
|
class CPoolChecker
|
||
|
{
|
||
|
public:
|
||
|
~CPoolChecker()
|
||
|
{
|
||
|
#if 0
|
||
|
int i;
|
||
|
for (i=1;i<ThePool().mNextStringId;i++)
|
||
|
{
|
||
|
OutputDebugString(gCharPtrs[i]);
|
||
|
OutputDebugString("\n");
|
||
|
}
|
||
|
#endif
|
||
|
#if _DEBUG
|
||
|
#if _GAME
|
||
|
// Com_Printf("[MEM][GAME] String Pool %d unique strings, %dK\n",ThePool().mNextStringId,(ThePool().mLastBlockNum+1)*BLOCK_SIZE/1024);
|
||
|
#elif _CGAME
|
||
|
// Com_Printf("[MEM][CGAME] String Pool %d unique strings, %dK\n",ThePool().mNextStringId,(ThePool().mLastBlockNum+1)*BLOCK_SIZE/1024);
|
||
|
#else
|
||
|
// Com_Printf("[MEM][EXE] String Pool %d unique strings, %dK\n",ThePool().mNextStringId,(ThePool().mLastBlockNum+1)*BLOCK_SIZE/1024);
|
||
|
#endif
|
||
|
#endif
|
||
|
// if this fails it means the string storage is CORRUPTED, let someone know
|
||
|
assert(TheDebugPool()==ThePool());
|
||
|
}
|
||
|
};
|
||
|
|
||
|
static CPoolChecker TheCPoolChecker;
|
||
|
|
||
|
CPool &TheDebugPool(void)
|
||
|
{
|
||
|
static CPool theDebugPool;
|
||
|
return(theDebugPool);
|
||
|
}
|
||
|
|
||
|
|
||
|
#endif
|
||
|
|
||
|
CPool &ThePool(void)
|
||
|
{
|
||
|
static CPool thePool;
|
||
|
return(thePool);
|
||
|
}
|
||
|
|
||
|
void TouchStringPool(void)
|
||
|
{
|
||
|
ThePool().TouchMem();
|
||
|
HashHelper().TouchMem();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Now the rest of the hString class.
|
||
|
//
|
||
|
|
||
|
void hstring::Init(const char *str)
|
||
|
{
|
||
|
if(!str)
|
||
|
{
|
||
|
mId=0;
|
||
|
return;
|
||
|
}
|
||
|
int hash=HashFunction(str);
|
||
|
int id=HashHelper().FindFirst(hash);
|
||
|
while (id)
|
||
|
{
|
||
|
assert(id>0&&id<ThePool().mNextStringId);
|
||
|
if (!strcmp(str,gCharPtrs[id]))
|
||
|
{
|
||
|
mId=id;
|
||
|
return;
|
||
|
}
|
||
|
id=HashHelper().FindNext();
|
||
|
}
|
||
|
char *raw=ThePool().Alloc(strlen(str),mId);
|
||
|
strcpy(raw,str);
|
||
|
HashHelper().Add(hash,mId);
|
||
|
#ifdef _DEBUG
|
||
|
int test;
|
||
|
raw=TheDebugPool().Alloc(strlen(str),test);
|
||
|
assert(test==mId);
|
||
|
strcpy(raw,str);
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
const char *hstring::c_str(void) const
|
||
|
{
|
||
|
if(!mId)
|
||
|
{
|
||
|
return("");
|
||
|
}
|
||
|
assert(mId>0&&mId<ThePool().mNextStringId);
|
||
|
return(gCharPtrs[mId]);
|
||
|
}
|
||
|
|
||
|
string hstring::str(void) const
|
||
|
{
|
||
|
if(!mId)
|
||
|
{
|
||
|
return(string());
|
||
|
}
|
||
|
assert(mId>0&&mId<ThePool().mNextStringId);
|
||
|
return string(gCharPtrs[mId]);
|
||
|
}
|
||
|
|
||
|
#endif // _DONETPROFILE_
|