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

