|
|
|
/*==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 <cstring>
|
|
|
|
#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");
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
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];
|
|
|
|
snprintf(fname,arrsize(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];
|
|
|
|
snprintf(fnm,arrsize(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
|
|
|
|
{
|
|
|
|
snprintf(fnm,arrsize(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
|