Archival snapshot of the Virtual World Entertainment Tesla cockpit software, 1994-1996: MUNGA engine and L4 pod layer source (Borland C++ 5.0), BT/RP game code, and game content (models, audio, maps, gauges, Division renderer data). Includes third-party libraries: Division dVS/DPL graphics, HMI SOS audio, WATTCP networking. Files are preserved byte-for-byte (.gitattributes disables all line-ending conversion). README.md documents the layout, target hardware, and toolchain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
679 lines
20 KiB
C++
679 lines
20 KiB
C++
//===========================================================================//
|
|
// File: memblock.cc //
|
|
// Project: MUNGA Brick: Memory Manager //
|
|
// Contents: Implementation details of the 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 <munga.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(MEMBLOCK_HPP)
|
|
#include <memblock.hpp>
|
|
#endif
|
|
|
|
#if !defined(SCALAR_HPP)
|
|
#include <scalar.hpp>
|
|
#endif
|
|
|
|
//#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";
|
|
DEBUG_STREAM << "------ ----- ----- ----- ---- -------------------------------\n";
|
|
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 << setw(6) << byte_count << setw(6) << record_count
|
|
<< setw(6) << deletion_count << setw(6) << unused_count << setw(4)
|
|
<< ((int)(usage*100.0f)) << '%';
|
|
if (block->blockName)
|
|
{
|
|
DEBUG_STREAM << ' ' << block->blockName;
|
|
}
|
|
DEBUG_STREAM << endl;
|
|
block = block->nextBlock;
|
|
}
|
|
while (block != firstBlock);
|
|
DEBUG_STREAM << endl;
|
|
#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
|
|
|