
/*
  File: include/basic/ResourceCache.h
  Description: Block allocation class
  Part of the Mantis project
  (c) 2002 Lycos Inc.

  This file is distributed for the sole purpose of allowing you to write
  software to interface with Sonique 2 and other Mantis programs. It is
  not to be modified or used for any other purpose without written
  authorization.

  If you redistribute this file, it must be in the original package
  complete with all other files provided, including the usage agreement.

  Do not remove this notice.
*/

#ifndef _RESOURCECACHE_H__
#define _RESOURCECACHE_H__

#ifdef DEBUG_INCLUDE_ORDER
#pragma message("Including " __FILE__ "\n" )
#endif

#if _MSC_VER > 1000
#pragma once
#endif


#include "typesport.h"
#include "threadport.h"
#include <stddef.h>
#include "Array.h"
#include "List.h"
#ifdef _WIN32
#if defined(DEBUG_TRACE_LEAKS_TOTAL) || defined(TRACE_PAGING)
#include "debug.h"
#endif
#endif




// disable "warning C4200: nonstandard extension used : zero-sized array in struct/union"
#pragma warning ( disable: 4200 )
// disable "warning C4127: conditional expression is constant"
#pragma warning ( disable: 4127 )

// what this does
//-----------------------------------
//
//	Keeps list of allocated memory.
//
// allows relief of system heap.. 
//
// I commented these out because I don't think plugin developers would want them automatically enabled...
// I think we should have a define in our projects which turns them on instead - NV
//#define DEBUG_TRACE_LEAKS_TOTAL
//#define TRACE_PAGING

//#define RES_USE_HEAP
//#define RES_USE_LIST
#if !defined(RES_USE_HEAP)
#define RES_USE_PAGED
//#pragma message("*********Using Paged Resource Cache*******")
#else
#pragma message("*********Using Heap Resource Cache*******")
#endif


#define RESOURCE_CACHE( type, pagesize, construct, var_name )    ResourceCache< type , pagesize , construct > var_name
#define INHERIT_RESOURCE_CACHE( type, pagesize, construct )    ResourceCache< type , pagesize , construct > ()
#define INITRESOURCECACHE( var_name ) var_name( )
#define INITRESOURCECACHE2( var_name ) var_name




template< class TYPE, int pageSize, bool bConstruct >
class ResourcePage;

template< class TYPE, int pageSize, bool bConstruct >
class Resource
{
public:
	TYPE								        data;
	ResourcePage<TYPE,pageSize,bConstruct>*		pPage;
};

template< class TYPE, int pageSize, bool bConstruct > 
class ResourceCache;

template< class TYPE, int pageSize, bool bConstruct > 
class ResourcePage
{
public:

	uint32						                nInUse;
	uint32						                nPageIndex;
	ResourceCache<TYPE,pageSize,bConstruct>*    pCache;
	//Resource<TYPE,pageSize,bConstruct>		Res[pageSize];

	__forceinline Resource<TYPE,pageSize,bConstruct>*	GetRes(){ return (Resource<TYPE,pageSize,bConstruct>*)  ((char*)this + sizeof(*this)); }

	ResourcePage( uint32 nindex, ResourceCache<TYPE,pageSize,bConstruct>* parent ) : nInUse(0) 
	{
		nPageIndex = nindex;
		pCache = parent;
	};

	void* operator new ( size_t size )
	{
		uint32 numElements = size/sizeof(ResourcePage<TYPE,pageSize,bConstruct>);
		return malloc( numElements * (sizeof(ResourcePage<TYPE,pageSize,bConstruct>) + sizeof(Resource<TYPE,pageSize,bConstruct>)*pageSize) );
	}
	void  operator delete( void * pThis )
	{
		free( pThis );
	}

	void* operator new[]( size_t size )
	{
		uint32 numElements = size/sizeof(ResourcePage<TYPE,pageSize,bConstruct>);
		return malloc( numElements * (sizeof(ResourcePage<TYPE,pageSize,bConstruct>) + sizeof(Resource<TYPE,pageSize,bConstruct>)*pageSize) );
	}
	void  operator delete[]( void * pThis )
	{
		free( pThis );
	}
};



template<class TYPE, int pageSize = 20, bool bConstruct = true > 
class ResourceCache
{
	typedef class AutoCriticalSection
	{
		CriticalSectionObject* pCS;
	public:
		AutoCriticalSection( CriticalSectionObject* pcs )
		{
			pCS = pcs;
			pCS->Enter();
		}

		~AutoCriticalSection()
		{
			if( pCS )
				pCS->Leave();
		}

		void Leave()
		{
			pCS->Leave();
			pCS = NULL;
		}
	} AutoCriticalSection;

private:

	ResourcePage<TYPE,pageSize,bConstruct>*								   		pFreePage;
	Array< Resource		<TYPE,pageSize,bConstruct>*,	pageSize*5, ArrayItem_NoConstruct >	FreeArray;
	Array< ResourcePage	<TYPE,pageSize,bConstruct>*,	pageSize*5 >		   	PageArray;
	
	CriticalSectionObject	m_cs;

protected:
	void	FirstInit(  TYPE* /*pData*/ )	{}
	void	LastDeInit( TYPE* /*pData*/ )	{}
	void	Init( TYPE* pData )				{ ::new( (void*) pData ) TYPE;		};
	void	DeInit( TYPE* pData )			{ (*pData).TYPE::~TYPE();			};

#ifdef _DEBUG
	virtual void ForceAVtable(){};
#endif

public:

	ResourceCache( ) : pFreePage(0) {};

	~ResourceCache()
	{
		FlushCache();
	}

	void FlushCache()
	{
		AutoCriticalSection acs(&m_cs);
		
		ASSERT( PageArray.GetCount() < 2 );
		
		if( pFreePage )
		{
			ASSERT( PageArray.GetCount() == 1 );
			ASSERT( 0 == pFreePage->nInUse  ); // didn't free everything?

			DeletePage( pFreePage );
		}

		ASSERT( PageArray.GetCount() == 0 );
#ifdef _DEBUG
		if ( PageArray.GetCount() )
		{

#ifdef DEBUG_TRACE_LEAKS_TOTAL
#ifdef _WIN32
			TRACE3("%s leaked %u page(s).\n", (const char*)dbgGetObj(this), pageSize, PageArray.GetCount() );
#endif
#endif
		}
#endif
		
	}

	void RemoveAll()
	{
		Resource<TYPE,pageSize,bConstruct>* item;
		int32 i;

		AutoCriticalSection acs(&m_cs);

		while( PageArray.GetCount() )
		{
			for (i = 0; i < pageSize; i++)
			{
				item = &PageArray[0]->GetRes()[i];
				if(FreeArray.Find(item) == -1)
					ReturnInternal(item);
			}

			if( pFreePage == PageArray[0] )
				pFreePage = NULL;
			DeletePage(PageArray[0]);
		}
		PageArray.RemoveAll();
		FreeArray.RemoveAll();
		pFreePage = NULL;
		
	}

	void AddNewPage()
	{
#ifdef TRACE_PAGING
#ifdef _WIN32		
		TRACE3( "%s  -- at %p -- creating page of %u items.\n", (const char*) dbgGetObj(this), (int) this, pageSize );
#endif
#endif
		// allocate a new page
		ResourcePage<TYPE,pageSize,bConstruct>* pPage;

		uint32 base = FreeArray.SetCount( FreeArray.GetCount() + pageSize );

		pPage = new ResourcePage<TYPE,pageSize,bConstruct>( PageArray.GetCount(), this );
	
		for( uint32 i = 0; i < pageSize; i++ )
		{
			FirstInit( &pPage->GetRes()[i].data );
			pPage->GetRes()[i].pPage = pPage;
			FreeArray[base + i] = &pPage->GetRes()[i];
		}

		PageArray.AddTail( pPage );
	}

	__forceinline TYPE* GetNew( )				
	{
		AutoCriticalSection acs(&m_cs);

		TYPE* pRetType;

		// check if any free.
		if( !FreeArray.GetCount() )
		{
			AddNewPage();
		}

		Resource<TYPE,pageSize,bConstruct>* pRes = FreeArray.Tail();
		FreeArray.RemoveTail();

		// get pointer
		pRetType = (TYPE*) &pRes->data;
		
		pRes->pPage->nInUse++;

		if( pFreePage == pRes->pPage )
		{
			pFreePage = NULL;
		}
		// init
//#pragma warning(push)
//#pragma warning( disable : 4127 )
		if( bConstruct ) 
			Init( pRetType );
//#pragma warning(pop)
		

		return pRetType;
		
	};
	void DeletePage( ResourcePage<TYPE,pageSize,bConstruct>* pPage )
	{
		#ifdef TRACE_PAGING
		#ifdef _WIN32
			TRACE3( "%s --  at %p -- deleting page of %u items.\n", (const char*)dbgTraceFunc(), (int) this, pageSize );
		#endif
		#endif

		// delete pPage, 
		Resource<TYPE,pageSize,bConstruct>** pSrcEntry, **pDstEntry, /* **pStartEntry, */ **pEndEntry;

		pDstEntry = pSrcEntry /* = pStartEntry*/ = &FreeArray.Head();
		pEndEntry = &FreeArray.Tail();

		while (pSrcEntry <= pEndEntry)
		{
			if ((*pSrcEntry)->pPage != pPage)
			{
				//this entry should stay, so don't replace it
				if (pSrcEntry != pDstEntry)
					*pDstEntry = *pSrcEntry;

				pDstEntry++;
			}
			pSrcEntry++;
		}


		// assert that something was found
		ASSERT( pSrcEntry != pDstEntry ); 
		ASSERT( pSrcEntry-pDstEntry == pageSize);

		FreeArray.SetCount(FreeArray.GetCount()-pageSize, false);

		//remove page from the page array and make sure all page indicies stay correct
		PageArray.RemoveAt(pPage->nPageIndex);

		uint32 i, count;
		count = PageArray.GetCount();
		for (i = pPage->nPageIndex; i < count; i++)
		{
			PageArray[i]->nPageIndex--;
			ASSERT(PageArray[i]->nPageIndex == i);
		}

		// last deinit
		for( i = 0; i < pageSize; i++ )
			LastDeInit( &pPage->GetRes()[i].data );

		delete pPage;
	}

	__forceinline static void	Return( TYPE* pResType )
    {
		register Resource<TYPE,pageSize,bConstruct>* pRes = (Resource<TYPE,pageSize,bConstruct>*) pResType;
		pRes->pPage->pCache->ReturnInternal( pRes );
	}

protected:
	__forceinline void	ReturnInternal( Resource<TYPE,pageSize,bConstruct>* pRes )	
	{ 
		AutoCriticalSection acs(&m_cs);
	//	TRACE3( "%s Resource Cache at %x Return item %d.\n", (char*) szName, (int) this, (int)pRes );
	

//#pragma warning(push)
//#pragma warning( disable : 4127 )
		if( bConstruct ) 
			DeInit( &pRes->data );
//#pragma warning(pop)


//		Resource<TYPE,pageSize,bConstruct>* pRes = (Resource<TYPE,pageSize,bConstruct>*) pResType;
		ResourcePage<TYPE,pageSize,bConstruct>* pPage = pRes->pPage;

//#define _RESCACHE_DEBUG
#ifdef _RESCACHE_DEBUG
		{
			int i, n;
			n = PageArray.GetCount();
			for (i = 0; i < n; i++)
			{
				if (pPage == PageArray[i])
					break;
			}
			ASSERT(i < n);
			for (i = 0; i < pageSize; i++)
			{
				if (&pPage->GetRes()[i] == pRes)
					break;
			}
			ASSERT(i < pageSize);
		}
#endif

		ASSERT( pPage->nInUse > 0 && pPage->nInUse <= pageSize );

		pPage->nInUse--;

		
		if( 0 == pPage->nInUse )
		{
			if(	pFreePage )
				DeletePage( pFreePage );
			
			pFreePage = pPage;
			FreeArray.AddTail( pRes );
			
		}
		else
		{
			// add this to the free resource array
			FreeArray.AddTail( pRes );
		}

	
	};

};



template <class TYPE, class INITTYPE>
class SharedCacheItem
{
public:
	TYPE* Item;
	INITTYPE Args;
};

template <class TYPE, class INITTYPE>
class SharedCache
{
public:
	~SharedCache()
	{
		ASSERT(m_Items.GetCount() == 0);
	}
	TYPE* Get(INITTYPE Args)
	{
		int32 i, n;
		TYPE* Ret;

		m_ItemCS.Enter();
		n = m_Items.GetCount();
		for( i = 0; i < n; i++ )
		{
			if( m_Items[i].Args == Args )
			{
				m_Items[i].Item->AddRef();
				Ret = Items[i].Item;
				break;
			}
		}
		if( i == n )
		{
			SharedCacheItem<TYPE,INITTYPE>* NewItem = m_Items.AddTail();
			Ret = NewItem->Item = new TYPE(Args);
			NewItem->Args = Args;
		}
		m_ItemCS.Leave();
		return Ret;
	}
	void Return(TYPE* Item)
	{
		int32 i, n;

		m_ItemCS.Enter();
		n = m_Items.GetCount();
		for( i = 0; i < n; i++ )
		{
			if( m_Items[i].Item == Item )
			{
				if( !m_Items[i].Item->Release() )
				{
					m_Items.RemoteAt(i);
				}
				break;
			}
		}
		ASSERT( i < n );
		m_ItemCS.Leave();
	}
protected:
	Array< SharedCacheItem<TYPE, INITTYPE> > m_Items;
	CriticalSectionObject m_ItemCS;
};


#ifdef DEBUG_INCLUDE_ORDER
#pragma message("Done including " __FILE__ "\n" )
#endif

#endif
