Complete disaster-recovery snapshot: engine/game source, game data assets, VC6 toolchain + DX SDKs, build outputs, deployed game, and _UNUSED archive. Large binaries in Git LFS; text preserved byte-for-byte (core.autocrlf=false, no eol attributes). See RECOVERY.md for the one-clone rebuild procedure.
923 lines
28 KiB
C++
923 lines
28 KiB
C++
//===========================================================================//
|
|
// File: memblock.cc //
|
|
// Project: MUNGA Brick: Memory Manager //
|
|
// Contents: Implementation details of the m_blockMemory block class //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// 10/20/94 JMA Initial coding. //
|
|
// 10/28/94 JMA Made compatible with SGI CC //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
|
|
// PROPRIETARY AND CONFIDENTIAL //
|
|
//===========================================================================//
|
|
|
|
#include "StuffHeaders.hpp"
|
|
|
|
//#define MEMORY_VERIFY
|
|
//#define MEMORY_BLOCK_VERIFY
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MemoryBlockBase ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
MemoryBlockBase*
|
|
MemoryBlockBase::s_firstBlock = NULL;
|
|
|
|
//
|
|
//#############################################################################
|
|
//
|
|
// This function constructs a MemoryBlockBase variable, allocating a block of
|
|
// m_blockMemory from heap, preparing it for suballocation of a constant record
|
|
// m_size.
|
|
//
|
|
// NOTE: Record m_size must be large enough to contain a void pointer in order
|
|
// to properly set up the re-use chain!
|
|
//
|
|
// Rec_Size - m_size in bytes of the suballocation unit
|
|
// Start - number of records to allocate initially
|
|
// Delta - number of records to allocate when growing the m_blockMemory block
|
|
//
|
|
//#############################################################################
|
|
//
|
|
MemoryBlockBase::MemoryBlockBase(
|
|
size_t rec_size,
|
|
size_t start,
|
|
size_t delta,
|
|
const char* name,
|
|
HGOSHEAP parent
|
|
)
|
|
{
|
|
//
|
|
//-----------------------------------------------------------------
|
|
// Make sure that the requested record m_size is large enough if
|
|
// debugging is enabled
|
|
//-----------------------------------------------------------------
|
|
//
|
|
Verify(rec_size >= sizeof(MemoryBlockHeader));
|
|
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
// Set up the m_size variables for the m_blockMemory block, and figure out
|
|
// the byte sizes of the initial block and delta blocks
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
Verify(parent != g_Heap);
|
|
m_heap = gos_CreateMemoryHeap(const_cast<char*>(name), 0, parent);
|
|
m_recordSize = (rec_size+3)&~3;
|
|
m_size = ((start * m_recordSize + sizeof(MemoryBlockHeader) + 0xFFF) & ~0xFFF) - sizeof(MemoryBlockHeader);
|
|
m_growthSize = ((delta * m_recordSize + sizeof(MemoryBlockHeader) + 0xFFF) & ~0xFFF) - sizeof(MemoryBlockHeader);
|
|
m_name = name;
|
|
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
// Allocate a block big enough for the requested m_blockMemory plus a link to
|
|
// the next m_blockMemory block, initializing this link to NULL
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
#if !defined(MEMORY_VERIFY)
|
|
m_blockMemory =
|
|
Cast_Pointer(
|
|
MemoryBlockHeader*,
|
|
new(m_heap) BYTE[sizeof(MemoryBlockHeader) + m_size]
|
|
);
|
|
Check_Object(m_blockMemory);
|
|
m_blockMemory->m_next = NULL;
|
|
m_blockMemory->m_size = m_size;
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Establish the beginning of the m_firstHeaderRecord record block, and
|
|
// point the m_freeRecord record pointer to the beginning. There are no
|
|
// m_deletedRecord records yet, so make sure Deleted is NULL
|
|
//--------------------------------------------------------------------
|
|
//
|
|
m_firstHeaderRecord = Cast_Pointer(BYTE*, m_blockMemory + 1);
|
|
Check_Pointer(m_firstHeaderRecord);
|
|
m_freeRecord = m_firstHeaderRecord;
|
|
m_deletedRecord = NULL;
|
|
#endif
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If this is the first memory block, set the first block pointer and
|
|
// initialize the ring. If not, insert the new block at the end of the ring
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (!s_firstBlock)
|
|
{
|
|
s_firstBlock = m_next = m_previous = this;
|
|
}
|
|
else
|
|
{
|
|
MemoryBlockBase *block = s_firstBlock;
|
|
Check_Object(block);
|
|
block = block->m_previous;
|
|
Check_Object(block);
|
|
m_next = s_firstBlock;
|
|
m_previous = block;
|
|
block->m_next = this;
|
|
s_firstBlock->m_previous = this;
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//
|
|
// This function destroys the MemoryBlockBase object, deleting any additional
|
|
// record blocks which were allocated
|
|
//
|
|
//#############################################################################
|
|
//
|
|
MemoryBlockBase::~MemoryBlockBase()
|
|
{
|
|
#if !defined(MEMORY_VERIFY)
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Find the address of the first record block, then delete blocks until we
|
|
// come to the end of the chain
|
|
//------------------------------------------------------------------------
|
|
//
|
|
Check_Object(this);
|
|
MemoryBlockHeader *block = m_blockMemory;
|
|
|
|
while (block)
|
|
{
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Save the address of the next m_blockMemory block, then delete this one
|
|
// and get ready to delete the next one
|
|
//---------------------------------------------------------------------
|
|
//
|
|
Check_Object(block);
|
|
MemoryBlockHeader *next_block = block->m_next;
|
|
delete block;
|
|
block = next_block;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Remove the block from the ring. If the block is the first one, set the
|
|
// first block pointer correctly. Note that we memoryblocks, as static
|
|
// objects, should deconstruct in the opposite order they constructed, so
|
|
// when we reach the first block, it should be the only one in the queue.
|
|
//------------------------------------------------------------------------
|
|
//
|
|
if (s_firstBlock == this)
|
|
{
|
|
if (m_next == this)
|
|
{
|
|
s_firstBlock = NULL;
|
|
}
|
|
else
|
|
{
|
|
s_firstBlock = m_next;
|
|
goto Unlink;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Unlink:
|
|
MemoryBlockBase *next = m_next;
|
|
Check_Object(next);
|
|
MemoryBlockBase *prev = m_previous;
|
|
Check_Object(prev);
|
|
next->m_previous = prev;
|
|
prev->m_next = next;
|
|
}
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//
|
|
// This function allocates a fixed m_size record from the record blocks
|
|
//
|
|
//#############################################################################
|
|
//
|
|
void*
|
|
MemoryBlockBase::Grow()
|
|
{
|
|
#if defined(MEMORY_VERIFY)
|
|
STOP(("MemoryBlockBase::Grow() not available!\n"));
|
|
#endif
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// If we have m_freeRecord space left in the m_firstHeaderRecord record block,
|
|
// allocate the new record from here, updating the next m_freeRecord record
|
|
// pointer
|
|
//------------------------------------------------------------------------
|
|
//
|
|
Check_Object(this);
|
|
if (m_freeRecord - m_firstHeaderRecord <= m_size - m_recordSize)
|
|
{
|
|
void *result = m_freeRecord;
|
|
m_freeRecord += m_recordSize;
|
|
return result;
|
|
}
|
|
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
// Allocate a new block of records using the growth m_size, and make the
|
|
// link field of the m_firstHeaderRecord block point to the new block. Then
|
|
// make the new block the m_firstHeaderRecord block, and make its next link
|
|
// NULL, as it is the end of the chain
|
|
//-------------------------------------------------------------------------
|
|
//
|
|
m_size = m_growthSize;
|
|
m_firstHeaderRecord -= sizeof(MemoryBlockHeader);
|
|
BYTE *new_block = new(m_heap) BYTE[m_size + sizeof(MemoryBlockHeader)];
|
|
Check_Pointer(new_block);
|
|
|
|
MemoryBlockHeader *header =
|
|
Cast_Pointer(MemoryBlockHeader*, m_firstHeaderRecord);
|
|
Check_Object(header);
|
|
header->m_next = Cast_Pointer(MemoryBlockHeader*, new_block);
|
|
Check_Object(header->m_next);
|
|
|
|
m_firstHeaderRecord = new_block;
|
|
header = Cast_Pointer(MemoryBlockHeader*, m_firstHeaderRecord);
|
|
Check_Object(header);
|
|
header->m_next = NULL;
|
|
header->m_size = m_growthSize;
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Make m_firstHeaderRecord point to the first available address in the new
|
|
// block (having skipped the first field allocated to the link pointer), and
|
|
// allocate this first block to the caller. Update the m_freeRecord pointer
|
|
// to reflect this allocation
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
m_firstHeaderRecord += sizeof(MemoryBlockHeader);
|
|
m_freeRecord = m_firstHeaderRecord + m_recordSize;
|
|
return m_firstHeaderRecord;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
MemoryBlockBase::UsageReport()
|
|
{
|
|
#if !defined(MEMORY_VERIFY)
|
|
SPEW((GROUP_STUFF_MEMORY, " #Bytes #Rcds #Dels #Free Use% Name"));
|
|
SPEW((GROUP_STUFF_MEMORY, "------- ------ ------ ------ ---- -------------------------------"));
|
|
MemoryBlockBase *block = s_firstBlock;
|
|
do
|
|
{
|
|
Check_Object(block);
|
|
MemoryBlockHeader *header = block->m_blockMemory;
|
|
size_t byte_count = 0;
|
|
size_t record_count = 0;
|
|
size_t deletion_count = 0;
|
|
size_t unused_count =
|
|
block->m_size - (block->m_freeRecord - block->m_firstHeaderRecord);
|
|
unused_count /= block->m_recordSize;
|
|
while (header)
|
|
{
|
|
Check_Object(header);
|
|
record_count += header->m_size / block->m_recordSize;
|
|
byte_count += header->m_size + sizeof(MemoryBlockHeader);
|
|
header = header->m_next;
|
|
}
|
|
BYTE *deletion = block->m_deletedRecord;
|
|
while (deletion)
|
|
{
|
|
++deletion_count;
|
|
deletion = *Cast_Pointer(BYTE**, deletion);
|
|
}
|
|
record_count -= deletion_count + unused_count;
|
|
if( record_count!=0 )
|
|
{
|
|
SPEW((
|
|
GROUP_STUFF_MEMORY,
|
|
"%7d%7d%7d%7d%4d%%+",
|
|
byte_count,
|
|
record_count,
|
|
deletion_count,
|
|
unused_count,
|
|
static_cast<int>((1.0f - (deletion_count+unused_count)/(float)record_count)*100.0f)
|
|
));
|
|
}
|
|
if (block->m_name)
|
|
SPEW((GROUP_STUFF_MEMORY, " %s", block->m_name));
|
|
SPEW((GROUP_STUFF_MEMORY, ""));
|
|
block = block->m_next;
|
|
}
|
|
while (block != s_firstBlock);
|
|
SPEW((GROUP_STUFF_MEMORY, ""));
|
|
#endif
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
MemoryBlockBase::CollapseBlocks()
|
|
{
|
|
#if defined(LAB_ONLY)
|
|
SPEW((GROUP_STUFF_MEMORY, "MemoryBlock statistics prior to collapsing..."));
|
|
UsageReport();
|
|
#endif
|
|
MemoryBlockBase *block = s_firstBlock;
|
|
do
|
|
{
|
|
Check_Object(block);
|
|
block->Collapse();
|
|
block = block->m_next;
|
|
}
|
|
while (block != s_firstBlock);
|
|
}
|
|
|
|
#if 0
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
static int
|
|
compare_function(const void* a, const void* b)
|
|
{
|
|
return static_cast<const char*>(a) - static_cast<const char*>(b);
|
|
}
|
|
#endif
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void
|
|
MemoryBlockBase::Collapse()
|
|
{
|
|
#if !defined(MEMORY_VERIFY)
|
|
//
|
|
//---------------------------------------
|
|
// count up the number of deleted records
|
|
//---------------------------------------
|
|
//
|
|
BYTE *deletion = m_deletedRecord;
|
|
size_t deletion_count = 0;
|
|
while (deletion)
|
|
{
|
|
Check_Pointer(deletion);
|
|
++deletion_count;
|
|
deletion = *Cast_Pointer(BYTE**, deletion);
|
|
}
|
|
if (!deletion_count)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Now, build a table of pointers that big and fill it in with the deletion
|
|
// pointers. Fill it in backwards because the last blocks to be allocated
|
|
// will probably be the last blocks to be deleted, and thus show up first in
|
|
// the chain
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
BYTE **deletions = new(m_heap) BYTE*[deletion_count+1];
|
|
deletion = m_deletedRecord;
|
|
int i=deletion_count-1;
|
|
deletions[deletion_count] = reinterpret_cast<BYTE*>(SNAN_NEGATIVE_LONG);
|
|
deletions[i--] = deletion;
|
|
deletion = *Cast_Pointer(BYTE**, deletion);
|
|
int j;
|
|
while (deletion)
|
|
{
|
|
//
|
|
//---------------------------------------------------
|
|
// We might as well build in the insertion stuff here
|
|
//---------------------------------------------------
|
|
//
|
|
Check_Pointer(deletion);
|
|
j=i--;
|
|
while (deletions[j+1] < deletion)
|
|
{
|
|
Verify(j < deletion_count);
|
|
deletions[j] = deletions[j+1];
|
|
++j;
|
|
}
|
|
deletions[j] = deletion;
|
|
deletion = *Cast_Pointer(BYTE**, deletion);
|
|
}
|
|
Verify(i == -1);
|
|
|
|
//
|
|
//------------------------------------------------------------------------
|
|
// Now, step through each additional block and see if it can be eliminated
|
|
//------------------------------------------------------------------------
|
|
//
|
|
MemoryBlockHeader *last_real_block = m_blockMemory;
|
|
MemoryBlockHeader *header = m_blockMemory->m_next;
|
|
while (header)
|
|
{
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// try to locate the first data entry in the block in the sorted table
|
|
//--------------------------------------------------------------------
|
|
//
|
|
Check_Object(header);
|
|
i = 0;
|
|
j = deletion_count - 1;
|
|
int m = 0;
|
|
BYTE *key = Cast_Pointer(BYTE*, header) + sizeof(*header);
|
|
while (i <= j)
|
|
{
|
|
m = (i + j) >> 1;
|
|
if (deletions[m] == key)
|
|
{
|
|
break;
|
|
}
|
|
else if (deletions[m] > key)
|
|
{
|
|
j = m - 1;
|
|
}
|
|
else
|
|
{
|
|
i = m + 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// If the record wasn't found, then this block cannot be deleted, so it
|
|
// is marked as the last real block and is so linked
|
|
//---------------------------------------------------------------------
|
|
//
|
|
if (i > j)
|
|
{
|
|
Real_Block:
|
|
last_real_block->m_next = header;
|
|
last_real_block = header;
|
|
header = header->m_next;
|
|
continue;
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// We found the beginning, so now we have to see if we can find the end.
|
|
// If this block has a link, it was all used up. Otherwise, we have to
|
|
// ignore the unused space at the end of the block
|
|
//----------------------------------------------------------------------
|
|
//
|
|
BYTE **start = &deletions[m];
|
|
if (header->m_next)
|
|
{
|
|
i = header->m_size / m_recordSize;
|
|
}
|
|
else
|
|
{
|
|
i = m_freeRecord - m_firstHeaderRecord;
|
|
i /= m_recordSize;
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Move the pointer to where the last entry for this block should be if
|
|
// all used records where later deleted. If this is an impossible
|
|
// location, or if the addresses do not match, this block cannot be
|
|
// collapsed
|
|
//---------------------------------------------------------------------
|
|
//
|
|
BYTE **end = start + i - 1;
|
|
if (
|
|
end - deletions >= deletion_count
|
|
|| *end !=
|
|
Cast_Pointer(BYTE*, header) + sizeof(*header) + (i-1)*m_recordSize
|
|
)
|
|
{
|
|
goto Real_Block;
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------------------------
|
|
// This block may now be killed, but save the next block address for
|
|
// further checking
|
|
//------------------------------------------------------------------
|
|
//
|
|
MemoryBlockHeader *next_block = header->m_next;
|
|
delete header;
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// Now, move everything following our section of deleted records to where
|
|
// our section begins, but only if there is something to move
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
++end;
|
|
j =
|
|
Cast_Pointer(BYTE*, &deletions[deletion_count])
|
|
- Cast_Pointer(BYTE*, end);
|
|
deletion_count -= i;
|
|
if (j>0)
|
|
{
|
|
memmove(start, end, j);
|
|
}
|
|
|
|
//
|
|
//----------------------------
|
|
// Check the next header block
|
|
//----------------------------
|
|
//
|
|
header = next_block;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Now, clean up the deletion array and reset all the current deletion
|
|
// pointers
|
|
//--------------------------------------------------------------------
|
|
//
|
|
deletion = NULL;
|
|
for (i=0; i<deletion_count; ++i)
|
|
{
|
|
Check_Pointer(deletions[i]);
|
|
*Cast_Pointer(BYTE**, deletions[i]) = deletion;
|
|
deletion = deletions[i];
|
|
}
|
|
m_deletedRecord = deletion;
|
|
delete[] deletions;
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Set the rest of the pointers to deal with this new last block if the last
|
|
// block is different than what it used to be. Note that this block should
|
|
// be marked as full
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
last_real_block->m_next = NULL;
|
|
if (
|
|
m_firstHeaderRecord !=
|
|
Cast_Pointer(BYTE*, last_real_block) + sizeof(*last_real_block)
|
|
)
|
|
{
|
|
m_size = last_real_block->m_size;
|
|
m_firstHeaderRecord =
|
|
Cast_Pointer(BYTE*, last_real_block) + sizeof(*last_real_block);
|
|
m_freeRecord = m_firstHeaderRecord + m_size;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MemoryBlock ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
//
|
|
//#############################################################################
|
|
//
|
|
// This function allocates a fixed m_size record from the record blocks
|
|
//
|
|
//#############################################################################
|
|
//
|
|
void*
|
|
MemoryBlock::New()
|
|
{
|
|
void *result;
|
|
#if defined(MEMORY_VERIFY)
|
|
result = new(m_heap) char[m_recordSize];
|
|
#else
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// If we have a m_deletedRecord record, go ahead and reuse it, updating the next
|
|
// m_deletedRecord record value from the m_firstHeaderRecord one. This chain is independant
|
|
// of the m_firstHeaderRecord record block. If not, grow the m_blockMemory block by one
|
|
// record
|
|
//----------------------------------------------------------------------
|
|
//
|
|
Check_Object(this);
|
|
if (m_deletedRecord)
|
|
{
|
|
result = m_deletedRecord;
|
|
m_deletedRecord = *Cast_Pointer(BYTE**, m_deletedRecord);
|
|
}
|
|
else
|
|
{
|
|
result = Grow();
|
|
}
|
|
#endif
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// If we are checking for unassigned variables, initialize the allocated
|
|
// memory with NANs
|
|
//----------------------------------------------------------------------
|
|
//
|
|
#if defined(_ARMOR)
|
|
Flood_Memory_With_NAN(result, m_recordSize);
|
|
#endif
|
|
|
|
//
|
|
//-------------------------------------
|
|
// Return the address of the new record
|
|
//-------------------------------------
|
|
//
|
|
return result;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//
|
|
// This function deallocates a record, making it available for re-use
|
|
//
|
|
//#############################################################################
|
|
//
|
|
void
|
|
MemoryBlock::Delete(
|
|
void* where
|
|
)
|
|
{
|
|
#if defined(MEMORY_VERIFY)
|
|
delete where;
|
|
#else
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// If we are in debug2 mode, check to see if the m_deletedRecord region really
|
|
// belongs to us
|
|
//--------------------------------------------------------------------
|
|
//
|
|
Check_Object(this);
|
|
|
|
#if defined(MEMORY_BLOCK_VERIFY)
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Make sure the address of this record is not already in the deletion
|
|
// chain
|
|
//--------------------------------------------------------------------
|
|
//
|
|
void *record;
|
|
for (
|
|
record = m_deletedRecord;
|
|
record;
|
|
record = *Cast_Pointer(BYTE**, record)
|
|
)
|
|
{
|
|
if (record == where)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
Verify(!record);
|
|
|
|
//
|
|
//------------------------------------------------------------------
|
|
// Find the address of the first record block, then check each block
|
|
// until we come to the end of the chain
|
|
//------------------------------------------------------------------
|
|
//
|
|
unsigned
|
|
offset;
|
|
MemoryBlockHeader*
|
|
block = m_blockMemory;
|
|
|
|
while (block)
|
|
{
|
|
|
|
//
|
|
//---------------------------------------------------------------
|
|
// If the record is in this block and is positioned correctly, go
|
|
// ahead and break as we have found a legal place within a block
|
|
//---------------------------------------------------------------
|
|
//
|
|
Check_Object(block);
|
|
offset =
|
|
static_cast<unsigned>(
|
|
Cast_Pointer(BYTE*, where) - Cast_Pointer(BYTE*, block + 1)
|
|
);
|
|
if (offset < block->m_size)
|
|
{
|
|
Verify(!(offset % m_recordSize));
|
|
break;
|
|
}
|
|
|
|
//
|
|
//---------------------
|
|
// Go to the next block
|
|
//---------------------
|
|
//
|
|
block = block->m_next;
|
|
}
|
|
Verify(block);
|
|
|
|
//
|
|
//-----------------------------------------------------------
|
|
// Now make sure that the address is not in our future region
|
|
//-----------------------------------------------------------
|
|
//
|
|
Verify(block->m_next || offset < m_freeRecord-m_firstHeaderRecord);
|
|
|
|
#endif
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
// Make the first few bytes of the record act as the link pointer to the
|
|
// beginning of the m_firstHeaderRecord m_deletedRecord chain, then make the first record
|
|
// available for reuse this one
|
|
//----------------------------------------------------------------------
|
|
//
|
|
*Cast_Pointer(BYTE**, where) = m_deletedRecord;
|
|
m_deletedRecord = Cast_Pointer(BYTE*, where);
|
|
#endif
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void*
|
|
MemoryBlock::operator[](size_t index)
|
|
{
|
|
#if defined(MEMORY_VERIFY)
|
|
STOP(("MemoryBlock::operator[] not available!\n"));
|
|
#endif
|
|
|
|
//
|
|
//------------------------------------------------------------------
|
|
// Find the address of the first record block, then check each block
|
|
// until we come to the end of the chain
|
|
//------------------------------------------------------------------
|
|
//
|
|
Check_Object(this);
|
|
MemoryBlockHeader
|
|
*block = m_blockMemory;
|
|
|
|
while (block)
|
|
{
|
|
//
|
|
//---------------------------------------------------------------
|
|
// If the index is in this block, go ahead and return its address
|
|
//---------------------------------------------------------------
|
|
//
|
|
Check_Object(block);
|
|
Verify(m_recordSize);
|
|
int
|
|
records = block->m_size / m_recordSize;
|
|
if (index < records)
|
|
{
|
|
return Cast_Pointer(BYTE*, block + 1) + index * m_recordSize;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Save the address of the next m_blockMemory block, then delete this one and
|
|
// get ready to delete the next one
|
|
//--------------------------------------------------------------------
|
|
//
|
|
index -= records;
|
|
block = block->m_next;
|
|
}
|
|
|
|
//
|
|
//-----------------------------------------
|
|
// The record doesn't exist, so return NULL
|
|
//-----------------------------------------
|
|
//
|
|
return NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MemoryStack ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void*
|
|
MemoryStack::Push(const void* what)
|
|
{
|
|
Check_Object(this);
|
|
Check_Pointer(what);
|
|
|
|
BYTE *block = m_firstHeaderRecord;
|
|
m_topOfStack = m_freeRecord;
|
|
BYTE *target = static_cast<BYTE*>(Grow());
|
|
Mem_Copy(
|
|
target,
|
|
what,
|
|
m_recordSize,
|
|
m_size - (target - m_firstHeaderRecord)
|
|
);
|
|
if (m_firstHeaderRecord != block)
|
|
{
|
|
m_topOfStack = m_firstHeaderRecord;
|
|
}
|
|
return m_topOfStack;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
//#############################################################################
|
|
//
|
|
void*
|
|
MemoryStack::Push()
|
|
{
|
|
Check_Object(this);
|
|
BYTE *block = m_firstHeaderRecord;
|
|
m_topOfStack = m_freeRecord;
|
|
Grow();
|
|
|
|
if (m_firstHeaderRecord != block)
|
|
{
|
|
m_topOfStack = m_firstHeaderRecord;
|
|
}
|
|
return m_topOfStack;
|
|
}
|
|
|
|
//
|
|
//###########################################################################
|
|
//###########################################################################
|
|
//
|
|
void
|
|
MemoryStack::Pop()
|
|
{
|
|
MemoryBlockHeader
|
|
*block,
|
|
*new_block;
|
|
|
|
//
|
|
//-----------------------------------------
|
|
// Make sure that something is in the stack
|
|
//-----------------------------------------
|
|
//
|
|
Check_Object(this);
|
|
if (m_topOfStack)
|
|
{
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// If the m_topOfStack of the stack is not at the bottom of a m_blockMemory block, the
|
|
// m_freeRecord and m_topOfStack pointers can move normally
|
|
//--------------------------------------------------------------------
|
|
//
|
|
if (m_topOfStack != m_firstHeaderRecord)
|
|
{
|
|
//
|
|
//-------------------------------------------------------------------
|
|
// If the m_freeRecord pointer is at the bottom of a block, we have to delete
|
|
// the block and update the variables to the previous block
|
|
//-------------------------------------------------------------------
|
|
//
|
|
if (m_freeRecord == m_firstHeaderRecord)
|
|
{
|
|
new_block =
|
|
Cast_Pointer(MemoryBlockHeader*, m_firstHeaderRecord - 1);
|
|
Check_Object(new_block);
|
|
for (
|
|
block = m_blockMemory;
|
|
block->m_next != new_block;
|
|
block = block->m_next
|
|
)
|
|
{
|
|
Check_Object(block);
|
|
}
|
|
|
|
delete new_block;
|
|
block->m_next = NULL;
|
|
m_firstHeaderRecord = Cast_Pointer(BYTE*, block + 1);
|
|
m_size = block->m_size;
|
|
}
|
|
|
|
//
|
|
//-----------------------------------------------
|
|
// Move the m_topOfStack and m_freeRecord pointers back one record
|
|
//-----------------------------------------------
|
|
//
|
|
m_freeRecord = m_topOfStack;
|
|
m_topOfStack -= m_recordSize;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Otherwise, we have to wrap the top of the stack pointer back to the
|
|
// previous block. If there is only one allocated block, then the
|
|
// stack is empty
|
|
//--------------------------------------------------------------------
|
|
//
|
|
else if (!m_blockMemory->m_next)
|
|
{
|
|
m_topOfStack = NULL;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Point the top of the stack to the last record of the previous block
|
|
//--------------------------------------------------------------------
|
|
//
|
|
else
|
|
{
|
|
new_block = Cast_Pointer(MemoryBlockHeader*, m_firstHeaderRecord - 1);
|
|
Check_Object(new_block);
|
|
for (
|
|
block = m_blockMemory;
|
|
block->m_next != new_block;
|
|
block = block->m_next
|
|
)
|
|
{
|
|
Check_Object(block);
|
|
}
|
|
m_topOfStack =
|
|
Cast_Pointer(BYTE*, block + 1) + block->m_size - m_recordSize;
|
|
}
|
|
}
|
|
}
|
|
|