Files
Cyd 2b8ca921cb Initial full mirror of c:\VWE (source + assets + toolchain + outputs) via Git LFS
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.
2026-06-24 21:28:16 -05:00

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;
}
}
}