Files
RP411/MUNGA/MEMBLOCK.cpp
T
CydandClaude Opus 4.8 4abbf8879f Initial import of Red Planet v4.10 Win32 source
Imports the current Win32 source for the pod-racing game 'Red Planet',
built on the MUNGA engine and its L4 (Win32/DirectX) platform layer:

- MUNGA / MUNGA_L4: cross-platform engine core and Win32 backend
- RP / RP_L4: Red Planet game logic and Win32 application
- DivLoader, Setup1: asset loader and installer project
- lib, MUNGA_L4/openal, MUNGA_L4/sos: third-party audio dependencies

Removed stale Subversion metadata and added .gitignore/.gitattributes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 07:59:51 -05:00

659 lines
19 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "memblock.h"
#include "scalar.h"
//#define MEMORY_VERIFY
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MemoryBlockHeader ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#if defined(USE_SIGNATURE)
int
Is_Signature_Bad(const volatile MemoryBlockHeader *)
{
return False;
}
#endif
//
//#############################################################################
//#############################################################################
//
Logical
MemoryBlockHeader::TestInstance()
{
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MemoryBlockBase ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MemoryBlockBase*
MemoryBlockBase::firstBlock = NULL;
//
//#############################################################################
//
// This function constructs a MemoryBlockBase variable, allocating a block of
// blockMemory from heap, preparing it for suballocation of a constant record
// blockSize.
//
// NOTE: Record blockSize must be large enough to contain a void pointer in order
// to properly set up the re-use chain!
//
// Rec_Size - blockSize in bytes of the suballocation unit
// Start - number of records to allocate initially
// Delta - number of records to allocate when growing the blockMemory block
//
//#############################################################################
//
MemoryBlockBase::MemoryBlockBase(
size_t rec_size,
size_t start,
size_t delta,
const char* name
)
{
//
//----------------------------------------------------------------------
// Make sure that the requested record blockSize is large enough if debugging
// is enabled
//----------------------------------------------------------------------
//
Verify(rec_size >= sizeof(MemoryBlockHeader));
//
//------------------------------------------------------------------------
// Set up the blockSize variables for the blockMemory block, and figure out the byte
// sizes of the initial block and delta blocks
//------------------------------------------------------------------------
//
recordSize = rec_size;
blockSize = start * recordSize;
deltaSize = delta * recordSize;
blockName = name;
//
//-------------------------------------------------------------------------
// Allocate a block big enough for the requested blockMemory plus a link to
// the next blockMemory block, initializing this link to NULL
//-------------------------------------------------------------------------
//
#if !defined(MEMORY_VERIFY)
blockMemory = (MemoryBlockHeader*)new Byte[sizeof(MemoryBlockHeader) + blockSize];
Verify(blockMemory);
blockMemory->nextBlock = NULL;
blockMemory->blockSize = blockSize;
//
//----------------------------------------------------------------------
// Establish the beginning of the firstHeaderRecord record block, and point
// the freeRecord record pointer to the beginning. There are no deletedRecord
// records yet, so make sure Deleted is NULL
//----------------------------------------------------------------------
//
firstHeaderRecord = (Byte*)(blockMemory + 1);
freeRecord = firstHeaderRecord;
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 (!firstBlock)
{
firstBlock = nextBlock = previousBlock = this;
}
else
{
MemoryBlockBase *block = firstBlock;
Check(block);
block = block->previousBlock;
Check(block);
nextBlock = firstBlock;
previousBlock = block;
block->nextBlock = this;
firstBlock->previousBlock = 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(this);
MemoryBlockHeader *block = blockMemory;
while (block)
{
//
//--------------------------------------------------------------------
// Save the address of the next blockMemory block, then delete this one and
// get ready to delete the next one
//--------------------------------------------------------------------
//
Check(block);
MemoryBlockHeader *next_block = block->nextBlock;
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 (firstBlock == this)
{
if (nextBlock == this)
{
firstBlock = NULL;
}
else
{
firstBlock = nextBlock;
goto Unlink;
}
}
else
{
Unlink:
MemoryBlockBase *next = nextBlock;
Check(next);
MemoryBlockBase *prev = previousBlock;
Check(prev);
next->previousBlock = prev;
prev->nextBlock = next;
}
}
//
//#############################################################################
//
// This function allocates a fixed blockSize record from the record blocks
//
//#############################################################################
//
void*
MemoryBlockBase::Grow()
{
#if defined(MEMORY_VERIFY)
Fail("MemoryBlockBase::Grow() not available!\n");
#endif
//
//---------------------------------------------------------------------
// If we have freeRecord space left in the firstHeaderRecord record block, allocate the
// new record from here, updating the next freeRecord record pointer
//---------------------------------------------------------------------
//
Check(this);
if (freeRecord - firstHeaderRecord <= blockSize - recordSize)
{
void *result = freeRecord;
freeRecord += recordSize;
return result;
}
//
//-----------------------------------------------------------------------
// Allocate a new block of records using the growth blockSize, and make the
// link field of the firstHeaderRecord block point to the new block. Then make the
// new block the firstHeaderRecord block, and make its next link NULL, as it is the
// end of the chain
//-----------------------------------------------------------------------
//
blockSize = deltaSize;
firstHeaderRecord -= sizeof(MemoryBlockHeader);
Byte *new_block = new Byte[blockSize + sizeof(MemoryBlockHeader)];
Verify(new_block);
MemoryBlockHeader *header = (MemoryBlockHeader*)firstHeaderRecord;
header->nextBlock = (MemoryBlockHeader*)new_block;
firstHeaderRecord = new_block;
header = (MemoryBlockHeader*)firstHeaderRecord;
header->nextBlock = NULL;
header->blockSize = deltaSize;
//
//---------------------------------------------------------------------
// Make 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 freeRecord pointer to
// reflect this allocation
//---------------------------------------------------------------------
//
firstHeaderRecord += sizeof(MemoryBlockHeader);
freeRecord = firstHeaderRecord + recordSize;
return (void*)firstHeaderRecord;
}
//
//#############################################################################
//#############################################################################
//
void
MemoryBlockBase::UsageReport()
{
#if !defined(MEMORY_VERIFY)
DEBUG_STREAM << "#Bytes #Rcds #Dels #Free Use% Name\n" << std::flush;
DEBUG_STREAM << "------ ----- ----- ----- ---- -------------------------------\n" << std::flush;
MemoryBlockBase *block = firstBlock;
do
{
Check(block);
MemoryBlockHeader *header = block->blockMemory;
size_t byte_count = 0;
size_t record_count = 0;
size_t deletion_count = 0;
size_t unused_count =
block->blockSize - (block->freeRecord - block->firstHeaderRecord);
unused_count /= block->recordSize;
while (header)
{
Check(header);
record_count += header->blockSize / block->recordSize;
byte_count += header->blockSize + sizeof(MemoryBlockHeader);
header = header->nextBlock;
}
Byte *deletion = block->deletedRecord;
while (deletion)
{
++deletion_count;
deletion = *(Byte**)deletion;
}
Scalar usage = 1.0f - (deletion_count+unused_count)/(float)record_count;
record_count -= deletion_count + unused_count;
DEBUG_STREAM << std::setw(6) << byte_count << std::setw(6) << record_count
<< std::setw(6) << deletion_count << std::setw(6) << unused_count << std::setw(4)
<< ((int)(usage*100.0f)) << '%';
if (block->blockName)
{
DEBUG_STREAM << ' ' << block->blockName << std::flush;
}
DEBUG_STREAM << std::endl << std::flush;
block = block->nextBlock;
}
while (block != firstBlock);
DEBUG_STREAM << std::endl << std::flush;
#endif
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MemoryBlock ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//
// This function allocates a fixed blockSize record from the record blocks
//
//#############################################################################
//
void*
MemoryBlock::New()
{
void *result;
#if defined(MEMORY_VERIFY)
result = new char[recordSize];
#else
//
//----------------------------------------------------------------------
// If we have a deletedRecord record, go ahead and reuse it, updating the next
// deletedRecord record value from the firstHeaderRecord one. This chain is independant
// of the firstHeaderRecord record block. If not, grow the blockMemory block by one
// record
//----------------------------------------------------------------------
//
Check(this);
if (deletedRecord)
{
result = (void*)deletedRecord;
deletedRecord = *(Byte**)deletedRecord;
}
else
{
result = Grow();
}
#endif
//
//----------------------------------------------------------------------
// If we are checking for unassigned variables, initialize the allocated
// memory with NANs
//----------------------------------------------------------------------
//
#if defined(DEBUG_NEW_ON)
#define SNAN_POSITIVE_LONG 0x7fb07fb0
long *filler = (long *)result;
for (int i = recordSize >> 2; i; --i)
{
*filler++ = SNAN_POSITIVE_LONG;
}
#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 deletedRecord region really
// belongs to us
//--------------------------------------------------------------------
//
Check(this);
#if DEBUG_LEVEL>1
//
//--------------------------------------------------------------------
// Make sure the address of this record is not already in the deletion
// chain
//--------------------------------------------------------------------
//
void *record;
for (
record = (void*)deletedRecord;
record;
record = *(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 = 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(block);
offset = (unsigned)((Byte*)where - (Byte*)(block + 1));
if (offset < block->blockSize)
{
Verify(!(offset % recordSize));
break;
}
//
//---------------------
// Go to the next block
//---------------------
//
block = block->nextBlock;
}
Verify(block);
//
//-----------------------------------------------------------
// Now make sure that the address is not in our future region
//-----------------------------------------------------------
//
Verify(block->nextBlock || offset < freeRecord-firstHeaderRecord);
#endif
//
//----------------------------------------------------------------------
// Make the first few bytes of the record act as the link pointer to the
// beginning of the firstHeaderRecord deletedRecord chain, then make the first record
// available for reuse this one
//----------------------------------------------------------------------
//
*(Byte**)where = deletedRecord;
deletedRecord = (Byte*)where;
#endif
}
//
//#############################################################################
//#############################################################################
//
void*
MemoryBlock::operator[](size_t index)
{
#if defined(MEMORY_VERIFY)
Fail("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(this);
MemoryBlockHeader
*block = blockMemory;
while (block)
{
//
//---------------------------------------------------------------
// If the index is in this block, go ahead and return its address
//---------------------------------------------------------------
//
Check(block);
Verify(recordSize);
int
records = block->blockSize / recordSize;
if (index < records)
{
return (Byte*)(block + 1) + index * recordSize;
}
//
//--------------------------------------------------------------------
// Save the address of the next blockMemory block, then delete this one and
// get ready to delete the next one
//--------------------------------------------------------------------
//
index -= records;
block = block->nextBlock;
}
//
//-----------------------------------------
// The record doesn't exist, so return NULL
//-----------------------------------------
//
return NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MemoryStack ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
void*
MemoryStack::Push(const void* what)
{
Check(this);
Check_Pointer(what);
Byte *block = firstHeaderRecord;
topOfStack = freeRecord;
Mem_Copy(Grow(), what, recordSize, blockSize - (freeRecord - firstHeaderRecord));
if (firstHeaderRecord != block)
{
topOfStack = firstHeaderRecord;
}
return topOfStack;
}
//
//#############################################################################
//#############################################################################
//
void*
MemoryStack::Push()
{
Check(this);
Byte *block = firstHeaderRecord;
topOfStack = freeRecord;
Grow();
if (firstHeaderRecord != block)
{
topOfStack = firstHeaderRecord;
}
return topOfStack;
}
//
//###########################################################################
//###########################################################################
//
void
MemoryStack::Pop()
{
MemoryBlockHeader
*block,
*new_block;
//
//-----------------------------------------
// Make sure that something is in the stack
//-----------------------------------------
//
Check(this);
if (topOfStack)
{
//
//--------------------------------------------------------------------
// If the topOfStack of the stack is not at the bottom of a blockMemory block, the
// freeRecord and topOfStack pointers can move normally
//--------------------------------------------------------------------
//
if (topOfStack != firstHeaderRecord)
{
//
//-------------------------------------------------------------------
// If the freeRecord pointer is at the bottom of a block, we have to delete
// the block and update the variables to the previous block
//-------------------------------------------------------------------
//
if (freeRecord == firstHeaderRecord)
{
new_block = (MemoryBlockHeader*)firstHeaderRecord - 1;
Check(new_block);
for (
block = blockMemory;
block->nextBlock != new_block;
block = block->nextBlock
)
{
Check(block);
}
delete new_block;
block->nextBlock = NULL;
firstHeaderRecord = (Byte*)(block + 1);
blockSize = block->blockSize;
}
//
//-----------------------------------------------
// Move the topOfStack and freeRecord pointers back one record
//-----------------------------------------------
//
freeRecord = topOfStack;
topOfStack -= 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 (!blockMemory->nextBlock)
{
topOfStack = NULL;
}
//
//--------------------------------------------------------------------
// Point the top of the stack to the last record of the previous block
//--------------------------------------------------------------------
//
else
{
new_block = (MemoryBlockHeader*)firstHeaderRecord - 1;
Check(new_block);
for (
block = blockMemory;
block->nextBlock != new_block;
block = block->nextBlock
)
{
Check(block);
}
topOfStack = (Byte*)(block + 1) + block->blockSize - recordSize;
}
}
}
#if defined(TEST_CLASS)
# include "memblock.tcp"
#endif