//===========================================================================//
// 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

