
//**************************************************************************
//**
//** z_zone.c : Heretic 2 : Raven Software, Corp.
//**
//** $RCSfile: z_zone.c,v $
//** $Revision: 1.2 $
//** $Date: 96/01/06 03:23:53 $
//** $Author: bgokey $
//**
//**************************************************************************

#include <stdlib.h>
#include "h2def.h"

/*
==============================================================================

						ZONE MEMORY ALLOCATION

There is never any space between memblocks, and there will never be two
contiguous free memblocks.

The rover can be left pointing at a non-empty block

It is of no value to free a cachable block, because it will get overwritten
automatically if needed

==============================================================================
*/

#define	ZONEID	0x1d4a11


typedef struct
{
	int		size;		// total bytes malloced, including header
	memblock_t	blocklist;		// start / end cap for linked list
	memblock_t	*rover;
} STRUCTSPACKEDNOT memzone_t;

memzone_t	*mainzone;

/*
========================
=
= Z_ClearZone
=
========================
*/

/*
void Z_ClearZone (memzone_t *zone)
{
	memblock_t	*block;
	
// set the entire zone to one free block

	zone->blocklist.next = zone->blocklist.prev = block =
		(memblock_t *)( (byte *)zone + sizeof(memzone_t) );
	zone->blocklist.user = (void *)zone;
	zone->blocklist.tag = PU_STATIC;
	zone->rover = block;
	
	block->prev = block->next = &zone->blocklist;
	block->user = NULL;	// free block
	block->size = zone->size - sizeof(memzone_t);
}
*/


/*
========================
=
= Z_Init
=
========================
*/

void Z_Init (void)
{
	memblock_t	*block;
	int		size;

#ifdef _NO_ZMALLOC //rww begin
	if (1) return;
#endif //rww end

	mainzone = (memzone_t *)I_ZoneBase (&size);
	mainzone->size = size;

// set the entire zone to one free block

	mainzone->blocklist.next = mainzone->blocklist.prev = block =
		(memblock_t *)( (byte *)mainzone + sizeof(memzone_t) );
	mainzone->blocklist.user = (void *)mainzone;
	mainzone->blocklist.tag = PU_STATIC;
	mainzone->rover = block;
	
	block->prev = block->next = &mainzone->blocklist;
	block->user = NULL;	// free block
	block->size = mainzone->size - sizeof(memzone_t);

	//rww begin - check heap for errors immediately
#ifdef _ZONE_DEBUG
	Z_CheckHeap();
#endif
	//rww end
}


/*
========================
=
= Z_Free
=
========================
*/

void Z_Free (void *ptr)
{
	memblock_t	*block, *other;
#ifdef _NO_ZMALLOC //rww begin
	if (1) { free(ptr); return; }
#endif //rww end
	
	block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t));
#ifndef _ZONE_TRIMMING
	if (block->id != ZONEID)
		I_Error ("Z_Free: freed a pointer without ZONEID");
#endif
		
	if (block->user > (void **)0x100)	// smaller values are not pointers
		*block->user = 0;		// clear the user's mark
	block->user = NULL;	// mark as free
	block->tag = 0;
#ifndef _ZONE_TRIMMING
	block->id = 0;
#endif
	
	other = block->prev;
	if (!other->user)
	{	// merge with previous free block
		other->size += block->size;
		other->next = block->next;
		other->next->prev = other;
		if (block == mainzone->rover)
			mainzone->rover = other;
		block = other;
	}
	
	other = block->next;
	if (!other->user)
	{	// merge the next free block onto the end
		block->size += other->size;
		block->next = other->next;
		block->next->prev = block;
		if (other == mainzone->rover)
			mainzone->rover = block;
	}
}


/*
========================
=
= Z_Malloc
=
= You can pass a NULL user if the tag is < PU_PURGELEVEL
========================
*/

#define MINFRAGMENT	64

void *Z_Malloc (int size, int tag, void *user)
{
	int		extra;
	memblock_t	*start, *rover, *new, *base;

#ifdef _NO_ZMALLOC //rww begin
	if (1) { void *p = malloc(size); if (user) { *(void **)user = p; } return ; }
#endif //rww end

//
// scan through the block list looking for the first free block
// of sufficient size, throwing out any purgable blocks along the way
//
	size += sizeof(memblock_t);	// account for size of block header
	
	//rww begin
	//4-byte align zone allocations
	size = (size+3)&~3;
	//rww end

	//rww begin - check heap for errors each allocation
#ifdef _ZONE_DEBUG
	ST_Message("Performing pre-allocation heap check\n(%i, %i, %0x)\n", size, tag, user);
	Z_CheckHeap();
#endif
	//rww end

//
// if there is a free block behind the rover, back up over them
//
	base = mainzone->rover;
	if (!base->prev->user)
		base = base->prev;
	
	rover = base;
	start = base->prev;
	
	do
	{
		if (rover == start)	// scaned all the way around the list
		{
			//rww begin - check the heap for errors before we make the assumption we ran out of space
			Z_CheckHeap();
			//rww end
			I_Error ("Z_Malloc: failed on allocation of %i bytes",size);
		}
		if (rover->user)
		{
			if (rover->tag < PU_PURGELEVEL)
			// hit a block that can't be purged, so move base past it
				base = rover = rover->next;
			else
			{
			// free the rover block (adding the size to base)
				base = base->prev;	// the rover can be the base block
				Z_Free ((byte *)rover+sizeof(memblock_t));
				base = base->next;
				rover = base->next;
			}
		}
		else
			rover = rover->next;
	} while (base->user || base->size < size);
	
//
// found a block big enough
//
	extra = base->size - size;
	if (extra >  MINFRAGMENT)
	{	// there will be a free fragment after the allocated block
		new = (memblock_t *) ((byte *)base + size );
		new->size = extra;
		new->user = NULL;		// free block
		new->tag = 0;
		new->prev = base;
		new->next = base->next;
		new->next->prev = new;
		base->next = new;
		base->size = size;
	}
	
	if (user)
	{
		base->user = user;			// mark as an in use block
		*(void **)user = (void *) ((byte *)base + sizeof(memblock_t));
	}
	else
	{
		if (tag >= PU_PURGELEVEL)
			I_Error ("Z_Malloc: an owner is required for purgable blocks");
		base->user = (void *)2;		// mark as in use, but unowned	
	}
	base->tag = tag;
	
	mainzone->rover = base->next;	// next allocation will start looking here
	
#ifndef _ZONE_TRIMMING
	base->id = ZONEID;
#endif

	//rww begin - check heap for errors each allocation
#ifdef _ZONE_DEBUG
	ST_Message("Performing post-allocation heap check.\n");
	Z_CheckHeap();
#endif
	//rww end

	return (void *) ((byte *)base + sizeof(memblock_t));
}


/*
========================
=
= Z_FreeTags
=
========================
*/

void Z_FreeTags (int lowtag, int hightag)
{
	memblock_t	*block, *next;

#ifdef _NO_ZMALLOC //rww begin
	if (1) return;
#endif //rww end

	for (block = mainzone->blocklist.next ; block != &mainzone->blocklist 
	; block = next)
	{
		next = block->next;		// get link before freeing
		if (!block->user)
			continue;			// free block
		if (block->tag >= lowtag && block->tag <= hightag)
			Z_Free ( (byte *)block+sizeof(memblock_t));
	}
}

/*
========================
=
= Z_DumpHeap
=
========================
*/

/*
void Z_DumpHeap (int lowtag, int hightag)
{
	memblock_t	*block;
	
	HEXENPRINTF ("zone size: %i  location: %p\n",mainzone->size,mainzone);
	HEXENPRINTF ("tag range: %i to %i\n",lowtag, hightag);
	
	for (block = mainzone->blocklist.next ; ; block = block->next)
	{
		if (block->tag >= lowtag && block->tag <= hightag)
			HEXENPRINTF ("block:%p    size:%7i    user:%p    tag:%3i\n",
			block, block->size, block->user, block->tag);
		
		if (block->next == &mainzone->blocklist)
			break;			// all blocks have been hit	
		if ( (byte *)block + block->size != (byte *)block->next)
			HEXENPRINTF ("ERROR: block size does not touch the next block\n");
		if ( block->next->prev != block)
			HEXENPRINTF ("ERROR: next block doesn't have proper back link\n");
		if (!block->user && !block->next->user)
			HEXENPRINTF ("ERROR: two consecutive free blocks\n");
	}
}
*/

/*
========================
=
= Z_FileDumpHeap
=
========================
*/

/*
void Z_FileDumpHeap (FILE *f)
{
	memblock_t	*block;
	
	fprintf (f,"zone size: %i  location: %p\n",mainzone->size,mainzone);
	
	for (block = mainzone->blocklist.next ; ; block = block->next)
	{
		fprintf (f,"block:%p    size:%7i    user:%p    tag:%3i\n",
		block, block->size, block->user, block->tag);
		
		if (block->next == &mainzone->blocklist)
			break;			// all blocks have been hit	
		if ( (byte *)block + block->size != (byte *)block->next)
			fprintf (f,"ERROR: block size does not touch the next block\n");
		if ( block->next->prev != block)
			fprintf (f,"ERROR: next block doesn't have proper back link\n");
		if (!block->user && !block->next->user)
			fprintf (f,"ERROR: two consecutive free blocks\n");
	}
}
*/

/*
========================
=
= Z_CheckHeap
=
========================
*/

void Z_CheckHeap (void)
{
	memblock_t	*block;

#ifdef _NO_ZMALLOC //rww begin
	if (1) return;
#endif //rww end

	for (block = mainzone->blocklist.next ; ; block = block->next)
	{
		if (block->next == &mainzone->blocklist)
			break;			// all blocks have been hit	
		if ( (byte *)block + block->size != (byte *)block->next)
			I_Error ("Z_CheckHeap: block size does not touch the next block (base %0x)\n", (int)mainzone); //rww - print base addr
		if ( block->next->prev != block)
			I_Error ("Z_CheckHeap: next block doesn't have proper back link (base %0x)\n", (int)mainzone); //rww - print base addr
		if (!block->user && !block->next->user)
			I_Error ("Z_CheckHeap: two consecutive free blocks (base %0x)\n", (int)mainzone); //rww - print base addr
	}
}


/*
========================
=
= Z_ChangeTag
=
========================
*/

void Z_ChangeTag2 (void *ptr, int tag)
{
	memblock_t	*block;

#ifdef _NO_ZMALLOC //rww begin
	if (1) return;
#endif //rww end

	block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t));
#ifndef _ZONE_TRIMMING
	if (block->id != ZONEID)
		I_Error ("Z_ChangeTag: freed a pointer without ZONEID");
#endif
	if (tag >= PU_PURGELEVEL && (unsigned)block->user < 0x100)
		I_Error ("Z_ChangeTag: an owner is required for purgable blocks");
	block->tag = tag;
}


/*
========================
=
= Z_FreeMemory
=
========================
*/

/*
int Z_FreeMemory (void)
{
	memblock_t	*block;
	int			free;
	
	free = 0;
	for (block = mainzone->blocklist.next ; block != &mainzone->blocklist 
	; block = block->next)
		if (!block->user || block->tag >= PU_PURGELEVEL)
			free += block->size;
	return free;
}
*/
