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.
862 lines
24 KiB
862 lines
24 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/>. |
|
|
|
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 "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 <class T> T* hsSoftNew(T*& obj) |
|
{ |
|
try { |
|
obj = new T; |
|
} |
|
catch (...) { |
|
obj = nil; |
|
} |
|
return obj; |
|
} |
|
|
|
inline template <class T> 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
|
|
|