/*==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 . 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 #else #include #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 T* hsSoftNew(T*& obj) { try { obj = TRACKED_NEW T; } catch (...) { obj = nil; } return obj; } inline template 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