/*==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 .
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
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==*/
#include
#pragma hdrstop
#include "hsMemory.h"
#include "hsExceptions.h"
#define DO_MEMORY_REPORTS // dumps memory reports upon start up of engine
///////////////////////////////////////////////////////////////////////////////////////////
void HSMemory::BlockMove(const void* src, void* dst, uint32_t length)
{
memmove(dst, src, length);
}
bool HSMemory::EqualBlocks(const void* block1, const void* block2, uint32_t length)
{
const uint8_t* byte1 = (uint8_t*)block1;
const uint8_t* byte2 = (uint8_t*)block2;
while (length--)
if (*byte1++ != *byte2++)
return false;
return true;
}
void* HSMemory::New(uint32_t size)
{
return new uint32_t[(size + 3) >> 2];
}
void HSMemory::Delete(void* block)
{
delete[] (uint32_t*)block;
}
void* HSMemory::Copy(uint32_t length, const void* source)
{
void* destination = HSMemory::New(length);
HSMemory::BlockMove(source, destination, length);
return destination;
}
void HSMemory::Clear(void* m, uint32_t byteLen)
{
uint8_t* mem = (uint8_t*)m;
uint8_t* memStop = mem + byteLen;
if (byteLen > 8)
{ while (uintptr_t(mem) & 3)
*mem++ = 0;
uint32_t* mem32 = (uint32_t*)mem;
uint32_t* mem32Stop = (uint32_t*)(uintptr_t(memStop) & ~3);
do {
*mem32++ = 0;
} while (mem32 < mem32Stop);
mem = (uint8_t*)mem32;
// fall through to finish any remaining bytes (0..3)
}
while (mem < memStop)
*mem++ = 0;
hsAssert(mem == memStop, "oops");
}
//////////////////////////////////////////////////////////////////////////////////////
#if 0
template T* hsSoftNew(T*& obj)
{
try {
obj = new T;
}
catch (...) {
obj = nil;
}
return obj;
}
inline template T* hsSoftNew(T*& obj, unsigned count)
{
try {
obj = new T[count];
}
catch (...) {
obj = nil;
}
return obj;
}
#endif
void* HSMemory::SoftNew(uint32_t size)
{
uint32_t* p;
hsTry {
p = new uint32_t[(size + 3) >> 2];
} hsCatch(...) {
p = nil;
}
return p;
}
//////////////////////////////////////////////////////////////////////////////////////
struct hsPrivateChunk {
hsPrivateChunk* fNext;
char* fAvailableAddr;
uint32_t fAvailableSize;
hsDebugCode(uint32_t fSize;)
hsDebugCode(uint32_t fCount;)
static hsPrivateChunk* NewPrivateChunk(hsPrivateChunk* next, uint32_t chunkSize);
};
hsPrivateChunk* hsPrivateChunk::NewPrivateChunk(hsPrivateChunk* next, uint32_t 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_t 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_t chunkSize)
{
fChunkSize = chunkSize;
}
void* hsChunkAllocator::Allocate(uint32_t 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_t 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_t elemSize) const { return (char*)fStop - elemSize; }
uint32_t GetSize() const { return (char*)fStop - (char*)fFirst; }
bool CanPrepend() const { return fFirst != this->GetTop(); }
int PrependSize() const { return (char*)fFirst - (char*)this->GetTop(); }
bool CanAppend() const { return fStop != this->GetBottom(); }
int AppendSize() const { return (char*)this->GetBottom() - (char*)fStop; }
void* Prepend(uint32_t elemSize)
{
hsAssert(this->CanPrepend(), "bad prepend");
fFirst = (char*)fFirst - elemSize;
hsAssert((char*)fFirst >= (char*)this->GetTop(), "bad elemSize");
return fFirst;
}
void* Append(uint32_t elemSize)
{
hsAssert(this->CanAppend(), "bad append");
void* data = fStop;
fStop = (char*)fStop + elemSize;
hsAssert((char*)fStop <= (char*)fBottom, "bad elemSize");
return data;
}
bool PopHead(uint32_t elemSize, void* data)
{
hsAssert(fFirst != fStop, "Empty");
if( data )
HSMemory::BlockMove(fFirst, data, elemSize);
fFirst = (char*)fFirst + elemSize;
return fFirst == fStop;
}
bool PopTail(uint32_t 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_t elemSize, uint32_t elemCount, hsAppenderHead* prev)
{
uint32_t 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_t elemSize, uint32_t elemCount, hsAppenderHead* next)
{
uint32_t 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_t elemSize, uint32_t elemCount)
: fFirstBlock(nil), fElemSize(elemSize), fElemCount(elemCount), fCount(0)
{
}
hsAppender::~hsAppender()
{
this->Reset();
}
uint32_t hsAppender::CopyInto(void* data) const
{
if (data)
{ const hsAppenderHead* head = fFirstBlock;
hsDebugCode(uint32_t totalSize = 0;)
while (head != nil)
{ uint32_t 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;
}
bool 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;
}
bool 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;
}
bool 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;
}
bool 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_t 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 = 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_t)( 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_t)(normsize+500)/1000;
for(int x=len; x < 25; x++)
fputc(' ',DumpLogFile); // make even columns
fprintf(DumpLogFile,"%5ld K %5ld %s\n",(uint32_t)(normsize+500)/1000,GrandTotal,errStr);//, allocs);
fclose(DumpLogFile);
}
}
}
cmbh_last = heap_state.pBlockHeader;
delete ltb;
}
#endif