You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

859 lines
21 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, 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 <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#if HS_BUILD_FOR_MAC
#include <Memory.h>
#else
#include <string.h>
#endif
#include "hsMemory.h"
#include "hsExceptions.h"
#define DO_MEMORY_REPORTS // dumps memory reports upon start up of engine
///////////////////////////////////////////////////////////////////////////////////////////
#if HS_BUILD_FOR_MAC
void HSMemory::BlockMove(const void* src, void* dst, UInt32 length)
{
::BlockMoveData(src, dst, length);
}
#else
void HSMemory::BlockMove(const void* src, void* dst, UInt32 length)
{
memmove(dst, src, length);
}
#endif
hsBool HSMemory::EqualBlocks(const void* block1, const void* block2, UInt32 length)
{
const Byte* byte1 = (Byte*)block1;
const Byte* byte2 = (Byte*)block2;
while (length--)
if (*byte1++ != *byte2++)
return false;
return true;
}
void* HSMemory::New(UInt32 size)
{
return TRACKED_NEW UInt32[(size + 3) >> 2];
}
void HSMemory::Delete(void* block)
{
delete[] (UInt32*)block;
}
void* HSMemory::Copy(UInt32 length, const void* source)
{
void* destination = HSMemory::New(length);
HSMemory::BlockMove(source, destination, length);
return destination;
}
void HSMemory::Clear(void* m, UInt32 byteLen)
{
UInt8* mem = (UInt8*)m;
UInt8* memStop = mem + byteLen;
if (byteLen > 8)
{ while (UInt32(mem) & 3)
*mem++ = 0;
UInt32* mem32 = (UInt32*)mem;
UInt32* mem32Stop = (UInt32*)(UInt32(memStop) & ~3);
do {
*mem32++ = 0;
} while (mem32 < mem32Stop);
mem = (UInt8*)mem32;
// fall through to finish any remaining bytes (0..3)
}
while (mem < memStop)
*mem++ = 0;
hsAssert(mem == memStop, "oops");
}
//////////////////////////////////////////////////////////////////////////////////////
#if 0
template <class T> T* hsSoftNew(T*& obj)
{
try {
obj = TRACKED_NEW T;
}
catch (...) {
obj = nil;
}
return obj;
}
inline template <class T> T* hsSoftNew(T*& obj, unsigned count)
{
try {
obj = TRACKED_NEW T[count];
}
catch (...) {
obj = nil;
}
return obj;
}
#endif
void* HSMemory::SoftNew(UInt32 size)
{
UInt32* p;
hsTry {
p = TRACKED_NEW UInt32[(size + 3) >> 2];
} hsCatch(...) {
p = nil;
}
return p;
}
//////////////////////////////////////////////////////////////////////////////////////
struct hsPrivateChunk {
hsPrivateChunk* fNext;
char* fAvailableAddr;
UInt32 fAvailableSize;
hsDebugCode(UInt32 fSize;)
hsDebugCode(UInt32 fCount;)
static hsPrivateChunk* NewPrivateChunk(hsPrivateChunk* next, UInt32 chunkSize);
};
hsPrivateChunk* hsPrivateChunk::NewPrivateChunk(hsPrivateChunk* next, UInt32 chunkSize)
{
hsPrivateChunk* chunk = (hsPrivateChunk*)HSMemory::New(sizeof(hsPrivateChunk) + chunkSize);
chunk->fNext = next;
chunk->fAvailableAddr = (char*)chunk + sizeof(hsPrivateChunk);
chunk->fAvailableSize = chunkSize;
hsDebugCode(chunk->fSize = chunkSize;)
hsDebugCode(chunk->fCount = 0;)
return chunk;
}
hsChunkAllocator::hsChunkAllocator(UInt32 chunkSize) : fChunkSize(chunkSize), fChunk(nil)
{
hsDebugCode(fChunkCount = 0;)
}
hsChunkAllocator::~hsChunkAllocator()
{
this->Reset();
}
void hsChunkAllocator::Reset()
{
hsPrivateChunk* chunk = fChunk;
while (chunk)
{ hsPrivateChunk* next = chunk->fNext;
HSMemory::Delete(chunk);
chunk = next;
}
fChunk = nil;
hsDebugCode(fChunkCount = 0;)
}
void hsChunkAllocator::SetChunkSize(UInt32 chunkSize)
{
fChunkSize = chunkSize;
}
void* hsChunkAllocator::Allocate(UInt32 size, const void* data)
{
void* addr;
if (fChunk == nil || fChunk->fAvailableSize < size)
{ if (size > fChunkSize)
fChunkSize = size;
fChunk = hsPrivateChunk::NewPrivateChunk(fChunk, fChunkSize);
hsDebugCode(fChunkCount += 1;)
}
addr = fChunk->fAvailableAddr;
fChunk->fAvailableAddr += size;
fChunk->fAvailableSize -= size;
hsDebugCode(fChunk->fCount += 1;)
if (data)
HSMemory::BlockMove(data, addr, size);
return addr;
}
void* hsChunkAllocator::SoftAllocate(UInt32 size, const void* data)
{
void* addr;
hsTry {
addr = this->Allocate(size, data);
}
hsCatch(...) {
addr = nil;
}
return addr;
}
//////////////////////////////////////////////////////////////////////////////////////
struct hsAppenderHead {
struct hsAppenderHead* fNext;
struct hsAppenderHead* fPrev;
void* fFirst;
void* fStop;
void* fBottom;
void* GetTop() const { return (char*)this + sizeof(*this); }
void* GetBottom() const { return fBottom; }
void* GetStop() const { return fStop; }
void* GetFirst() const { return fFirst; }
void* GetLast(UInt32 elemSize) const { return (char*)fStop - elemSize; }
UInt32 GetSize() const { return (char*)fStop - (char*)fFirst; }
hsBool CanPrepend() const { return fFirst != this->GetTop(); }
int PrependSize() const { return (char*)fFirst - (char*)this->GetTop(); }
hsBool CanAppend() const { return fStop != this->GetBottom(); }
int AppendSize() const { return (char*)this->GetBottom() - (char*)fStop; }
void* Prepend(UInt32 elemSize)
{
hsAssert(this->CanPrepend(), "bad prepend");
fFirst = (char*)fFirst - elemSize;
hsAssert((char*)fFirst >= (char*)this->GetTop(), "bad elemSize");
return fFirst;
}
void* Append(UInt32 elemSize)
{
hsAssert(this->CanAppend(), "bad append");
void* data = fStop;
fStop = (char*)fStop + elemSize;
hsAssert((char*)fStop <= (char*)fBottom, "bad elemSize");
return data;
}
hsBool PopHead(UInt32 elemSize, void* data)
{
hsAssert(fFirst != fStop, "Empty");
if( data )
HSMemory::BlockMove(fFirst, data, elemSize);
fFirst = (char*)fFirst + elemSize;
return fFirst == fStop;
}
hsBool PopTail(UInt32 elemSize, void* data)
{
hsAssert(fFirst != fStop, "Empty");
fStop = (char*)fStop - elemSize;
if( data )
HSMemory::BlockMove(fStop, data, elemSize);
return fFirst == fStop;
}
static hsAppenderHead* NewAppend(UInt32 elemSize, UInt32 elemCount, hsAppenderHead* prev)
{
UInt32 dataSize = elemSize * elemCount;
hsAppenderHead* head = (hsAppenderHead*)HSMemory::New(sizeof(hsAppenderHead) + dataSize);
head->fNext = nil;
head->fPrev = prev;
head->fFirst = head->GetTop();
head->fStop = head->fFirst;
head->fBottom = (char*)head->fFirst + dataSize;
return head;
}
static hsAppenderHead* NewPrepend(UInt32 elemSize, UInt32 elemCount, hsAppenderHead* next)
{
UInt32 dataSize = elemSize * elemCount;
hsAppenderHead* head = (hsAppenderHead*)HSMemory::New(sizeof(hsAppenderHead) + dataSize);
head->fNext = next;
head->fPrev = nil;
head->fBottom = (char*)head->GetTop() + dataSize;
head->fFirst = head->fBottom;
head->fStop = head->fBottom;
return head;
}
};
////////////////////////////////////////////////////////////////////////////////////////
hsAppender::hsAppender(UInt32 elemSize, UInt32 elemCount)
: fFirstBlock(nil), fElemSize(elemSize), fElemCount(elemCount), fCount(0)
{
}
hsAppender::~hsAppender()
{
this->Reset();
}
UInt32 hsAppender::CopyInto(void* data) const
{
if (data)
{ const hsAppenderHead* head = fFirstBlock;
hsDebugCode(UInt32 totalSize = 0;)
while (head != nil)
{ UInt32 size = head->GetSize();
HSMemory::BlockMove(head->GetFirst(), data, size);
data = (char*)data + size;
head = head->fNext;
hsDebugCode(totalSize += size;)
}
hsAssert(totalSize == fCount * fElemSize, "bad size");
}
return fCount * fElemSize;
}
void hsAppender::Reset()
{
hsAppenderHead* head = fFirstBlock;
while (head != nil)
{ hsAppenderHead* next = head->fNext;
HSMemory::Delete(head);
head = next;
}
fCount = 0;
fFirstBlock = nil;
fLastBlock = nil;
}
void* hsAppender::PushHead()
{
if (fFirstBlock == nil)
{ fFirstBlock = hsAppenderHead::NewPrepend(fElemSize, fElemCount, nil);
fLastBlock = fFirstBlock;
}
else if (fFirstBlock->CanPrepend() == false)
fFirstBlock = hsAppenderHead::NewPrepend(fElemSize, fElemCount, fFirstBlock);
fCount += 1;
return fFirstBlock->Prepend(fElemSize);
}
void hsAppender::PushHead(const void* data)
{
void* addr = this->PushHead();
if (data)
HSMemory::BlockMove(data, addr, fElemSize);
}
void* hsAppender::PeekHead() const
{
if (fFirstBlock)
return (char*)fFirstBlock->fFirst;
else
return nil;
}
hsBool hsAppender::PopHead(void* data)
{
if (fCount == 0)
return false;
fCount -= 1;
if (fFirstBlock->PopHead(fElemSize, data))
{ hsAppenderHead* next = fFirstBlock->fNext;
if (next)
next->fPrev = nil;
HSMemory::Delete(fFirstBlock);
fFirstBlock = next;
if (next == nil)
fLastBlock = nil;
}
return true;
}
int hsAppender::PopHead(int count, void* data)
{
hsThrowIfBadParam(count >= 0);
int sizeNeeded = count * fElemSize;
int origCount = fCount;
while (fCount > 0)
{ int size = fFirstBlock->GetSize();
if (size > sizeNeeded)
size = sizeNeeded;
if (fFirstBlock->PopHead(size, data))
{ hsAppenderHead* next = fFirstBlock->fNext;
if (next)
next->fPrev = nil;
HSMemory::Delete(fFirstBlock);
fFirstBlock = next;
if (next == nil)
fLastBlock = nil;
}
if (data)
data = (void*)((char*)data + size);
sizeNeeded -= size;
fCount -= size / fElemSize;
hsAssert(int(fCount) >= 0, "bad fElemSize");
}
return origCount - fCount; // return number of elements popped
}
void* hsAppender::PushTail()
{
if (fFirstBlock == nil)
{ fFirstBlock = hsAppenderHead::NewAppend(fElemSize, fElemCount, nil);
fLastBlock = fFirstBlock;
}
else if (fLastBlock->CanAppend() == false)
{ fLastBlock->fNext = hsAppenderHead::NewAppend(fElemSize, fElemCount, fLastBlock);
fLastBlock = fLastBlock->fNext;
}
fCount += 1;
return fLastBlock->Append(fElemSize);
}
void hsAppender::PushTail(const void* data)
{
void* addr = this->PushTail();
if (data)
HSMemory::BlockMove(data, addr, fElemSize);
}
void hsAppender::PushTail(int count, const void* data)
{
hsThrowIfBadParam(count < 0);
int sizeNeeded = count * fElemSize;
while (sizeNeeded > 0)
{ if (fFirstBlock == nil)
{ hsAssert(fCount == 0, "uninited count");
fFirstBlock = hsAppenderHead::NewAppend(fElemSize, fElemCount, nil);
fLastBlock = fFirstBlock;
}
else if (fLastBlock->CanAppend() == false)
{ fLastBlock->fNext = hsAppenderHead::NewAppend(fElemSize, fElemCount, fLastBlock);
fLastBlock = fLastBlock->fNext;
}
int size = fLastBlock->AppendSize();
hsAssert(size > 0, "bad appendsize");
if (size > sizeNeeded)
size = sizeNeeded;
void* dst = fLastBlock->Append(size);
if (data)
{ HSMemory::BlockMove(data, dst, size);
data = (char*)data + size;
}
sizeNeeded -= size;
}
fCount += count;
}
void* hsAppender::PeekTail() const
{
if (fLastBlock)
return (char*)fLastBlock->fStop - fElemSize;
else
return nil;
}
hsBool hsAppender::PopTail(void* data)
{
if (fCount == 0)
return false;
fCount -= 1;
if (fLastBlock->PopTail(fElemSize, data))
{ hsAppenderHead* prev = fLastBlock->fPrev;
if (prev)
prev->fNext = nil;
HSMemory::Delete(fLastBlock);
fLastBlock = prev;
if (prev == nil)
fFirstBlock = nil;
}
return true;
}
//////////////////////////////////////////////////////////////////////////
hsAppenderIterator::hsAppenderIterator(const hsAppender* list)
{
this->ResetToHead(list);
}
void hsAppenderIterator::ResetToHead(const hsAppender* list)
{
fAppender = list;
fCurrBlock = nil;
if (fAppender)
{ fCurrBlock = fAppender->fFirstBlock;
if (fCurrBlock)
fCurrItem = fCurrBlock->GetFirst();
}
}
void hsAppenderIterator::ResetToTail(const hsAppender* list)
{
fAppender = list;
fCurrBlock = nil;
if (fAppender)
{ fCurrBlock = fAppender->fLastBlock;
if (fCurrBlock)
fCurrItem = fCurrBlock->GetLast(fAppender->fElemSize);
}
}
void* hsAppenderIterator::Next()
{
void* item = nil;
if (fCurrBlock)
{ item = fCurrItem;
fCurrItem = (char*)fCurrItem + fAppender->fElemSize;
if (fCurrItem == fCurrBlock->GetBottom())
{ fCurrBlock = fCurrBlock->fNext;
if (fCurrBlock)
fCurrItem = fCurrBlock->GetFirst();
}
else if (fCurrItem == fCurrBlock->GetStop())
{ hsAssert(fCurrBlock->fNext == nil, "oops");
fCurrBlock = nil;
}
}
return item;
}
hsBool hsAppenderIterator::Next(void* data)
{
void* addr = this->Next();
if (addr)
{ if (data)
HSMemory::BlockMove(addr, data, fAppender->fElemSize);
return true;
}
return false;
}
int hsAppenderIterator::Next(int count, void* data)
{
int origCount = count;
while (count > 0 && this->Next(data))
{ if (data)
data = (void*)((char*)data + fAppender->fElemSize);
count -= 1;
}
return origCount - count;
}
void* hsAppenderIterator::Prev()
{
void* item = nil;
if (fCurrBlock)
{ item = fCurrItem;
fCurrItem = (char*)fCurrItem - fAppender->fElemSize;
if (item == fCurrBlock->GetTop())
{ fCurrBlock = fCurrBlock->fPrev;
if (fCurrBlock)
fCurrItem = fCurrBlock->GetLast(fAppender->fElemSize);
}
else if (item == fCurrBlock->GetFirst())
{ hsAssert(fCurrBlock->fPrev == nil, "oops");
fCurrBlock = nil;
}
}
return item;
}
hsBool hsAppenderIterator::Prev(void* data)
{
void* addr = this->Prev();
if (addr)
{ if (data)
HSMemory::BlockMove(addr, data, fAppender->fElemSize);
return true;
}
return false;
}
//-------------------------------------------------------------
//
// MEMORY USE REPORTING CODE
//
//-------------------------------------------------------------
#if 1//!(defined(HS_DEBUGGING)&&(HS_BUILD_FOR_WIN32)&& defined(HS_FIND_MEM_LEAKS))
// EMPTY STUB
void SortNDumpUnfreedMemory(const char *, bool ) // file name base, and FULL report indicator
{
}
#else
typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
struct _CrtMemBlockHeader *pBlockHeaderPrev;
char *szFileName; // File name
int nLine; // Line number
size_t nDataSize; // Size of user block
int nBlockUse; // Type of block
long lRequest; // Allocation number
// Buffer just before (lower than) the user's memory:
unsigned char gap[4];
} _CrtMemBlockHeader;
/* In an actual memory block in the debug heap,
* this structure is followed by:
* unsigned char data[nDataSize];
* unsigned char anotherGap[4];
*/
//
// Dump formatted string to OutputDebugString
//
void __cdecl DebugMsg( LPSTR fmt, ... )
{
char buff[256];
wvsprintf(buff, fmt, (char *)(&fmt+1));
hsStatusMessage(buff);
}
char *TrimFileName(char *name) // Trim file name of leading Directories
{
int len = 0;
char *ptr;
if (!name) return NULL;
len = strlen(name);
ptr = name + len;
for ( ptr--; ptr > name; ptr--)
{
if (*ptr == '\\')
{
ptr++;
break;
}
}
return ptr;
}
//
// Loop thru all unfreed blocks in the heap and dump out detailed info
//
struct looktbl {
char * fName; // Name of file
// long fAllocs; // Number of Alloc calls
long fBytes; // Total Bytes Alloc'd
};
#define LTBLMAX 300
//---------------------------------------------------------------------------
// This routine will report on the memory used in the engine.
// If argument full is true, it gives a full dump from the start of the program
// if !full, then each time the routine is called it remembers where it finishes off, then the next
// call with !full, it will (attempt) to report on the newest allocations, backward to the last checkpoint
//--------------------------------------------------------------------------
void SortNDumpUnfreedMemory(const char *nm, bool full) // file name base, and FULL report indicator
{
#ifndef DO_MEMORY_REPORTS
if (!full) // full is launched by control M...partials are called each time the engine starts
return;
#endif
char fname[512];
sprintf(fname,"%s_dmp.txt",nm);
char *errStr = "";
_CrtMemState heap_state;
static UInt32 GrandTotal =0;
static _CrtMemBlockHeader *cmbh_last; // Remember this header for next incremental check DANGER this
// could break if this is freed...(gives bad report)
_CrtMemBlockHeader *cmbh_last_good;
_CrtMemBlockHeader *cmbh;
// Get Current heap state
_CrtMemCheckpoint(&heap_state);
cmbh = heap_state.pBlockHeader;
long totsize= 0; // Track Total Bytes
long normsize = 0; // Track total of NORMAL Blocks
looktbl *ltb = TRACKED_NEW looktbl[LTBLMAX];
long tblEnd=1; // first is "NULL";
memset((void *)ltb,0,sizeof(looktbl) * LTBLMAX); // clear table area
char *ftrim;
ltb[0].fName = "NULL"; // Use first Table Pos for NULL
long tblpos;
while (cmbh != NULL) // Accumulate Stats to table
{
if (cmbh == cmbh_last && !full) // full indicates ignore last "checkpoint", stop at last checkpoint if !full
break;
cmbh_last_good = cmbh;
totsize += cmbh->nDataSize;
if (cmbh->nBlockUse == _NORMAL_BLOCK)
{
normsize += cmbh->nDataSize;
if (cmbh->szFileName != NULL) // Shorten to just the file name, looks better, and strcmps faster
{
ftrim = TrimFileName(cmbh->szFileName);
for (tblpos = 1; tblpos < tblEnd; tblpos++) // find the name in the table
{
if (!strcmp(ftrim,ltb[tblpos].fName))
break; // found it
}
}
else
{
tblpos = 0; // Use "NULL", first pos of table
}
if (tblpos == tblEnd) // Did not find it...add it
{
tblEnd++;
if (tblEnd >= LTBLMAX)
{ DebugMsg("DumpUnfreedMemoryInfo: EXCEED MAX TABLE LENGTH\n");
tblEnd--;
break;
}
ltb[tblpos].fName = ftrim; // Add name
}
// Add Stats
// ltb[tblpos].fAllocs++;
ltb[tblpos].fBytes += cmbh->nDataSize;
}
cmbh = cmbh->pBlockHeaderNext;
}
// This Code relies on the _CrtMemBlockHeader *cmbh_last_good for the "last" checkpoint to still be around...
// If the following occurs, that chunk has been deleted. we could fix this by allocating our own
// chunk and keeping it (watch for mem leaks though) or figuring out an "approximat" re syncying routine
// that works before we run thru collecting data. PBG
if (cmbh_last && !full && cmbh == NULL)
{
//hsAssert(0,"Stats error: incremental mem check point has been deleted");
errStr = "CHECK POINT ERROR, Results Inacurate";
}
if (normsize) // Don't write out about nothing
{
CreateDirectory("Reports",NULL); // stick em in a sub directory
char fnm[512];
sprintf(fnm,"Reports\\%s",fname);
FILE * DumpLogFile = fopen( fnm, "w" );
// long allocs=0;
if ( DumpLogFile != NULL )
{
// Print Stats
fprintf(DumpLogFile, "Filename Total=%ld(k) %s\n",(normsize + 500)/1000,errStr);
for (int i = 0; i < tblEnd; i++)
{ //fprintf(DumpLogFile,"%s\t%ld\n",ltb[i].fName, (ltb[i].fBytes+500)/1000);//,ltb[i].fAllocs);
fprintf(DumpLogFile,"%s ",ltb[i].fName);
int len = strlen(ltb[i].fName);
for(int x=len; x < 25; x++)
fputc(' ',DumpLogFile); // make even columns
fprintf(DumpLogFile,"%5ld K\n",(UInt32)( ltb[i].fBytes+500)/1000);//,ltb[i].fAllocs);
//allocs += ltb[i].fAllocs;
}
DebugMsg("MEMORY USE FILE DUMPED TO %s \n",fname);
DebugMsg("MEMORY Check: Total size %ld, Normal Size: %ld\n",totsize,normsize);
fclose(DumpLogFile);
}
static int first=1;
if (!full) // if this is a partial mem dump, write to the ROOMS.txt file a summary
{
sprintf(fnm,"Reports\\%s","ROOMS.txt");
if (first)
{ DumpLogFile = fopen( fnm, "w" ); // first time clobber the old
if (DumpLogFile)
fprintf(DumpLogFile, "Filename Memory-Used(K) RunningTotal\n");// \tAllocation Calls \n" );
first = 0;
}
else
DumpLogFile = fopen( fnm, "a+" );
if( DumpLogFile)
{ fprintf(DumpLogFile,"%s ",nm);
int len = strlen(nm);
GrandTotal += (UInt32)(normsize+500)/1000;
for(int x=len; x < 25; x++)
fputc(' ',DumpLogFile); // make even columns
fprintf(DumpLogFile,"%5ld K %5ld %s\n",(UInt32)(normsize+500)/1000,GrandTotal,errStr);//, allocs);
fclose(DumpLogFile);
}
}
}
cmbh_last = heap_state.pBlockHeader;
delete ltb;
}
#endif