//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Serialized Digital Object caching and manipulation
//
//=============================================================================

#ifndef SBOCACHE_H
#define SBOCACHE_H
#ifdef _WIN32
#pragma once
#endif

#include "tier1/utlhashmaplarge.h"
#include "tier1/utlqueue.h"
#include "tier1/utlvector.h"
#include <algorithm>

namespace GCSDK
{
// Call to register SDOs. All SDO types must be registered before loaded
#define REG_SDO( classname )	GSDOCache().RegisterSDO( classname::k_eType, #classname )

// A string used to tell the difference between nil objects and actual objects in memcached
extern const char k_rgchNilObjSerializedValue[];


//-----------------------------------------------------------------------------
// Purpose: Keeps a moving average of a data set
//-----------------------------------------------------------------------------
template< int SAMPLES >
class CMovingAverage
{
public:
	CMovingAverage()
	{
		Reset();
	}

	void Reset()
	{
		memset( m_rglSamples, 0, sizeof( m_rglSamples ) );
		m_cSamples = 0;
		m_lTotal = 0;
	}

	void AddSample( int64 lSample )
	{
		int iIndex = m_cSamples % SAMPLES;
		m_lTotal += ( lSample - m_rglSamples[iIndex] );
		m_rglSamples[iIndex] = lSample;
		m_cSamples++;
	}

	uint64 GetAveragedSample() const
	{
		if ( !m_cSamples )
			return 0;

		int64 iMax = (int64)MIN( m_cSamples, SAMPLES );
		return m_lTotal / iMax;
	}

private:
	int64 m_rglSamples[SAMPLES];
	int64 m_lTotal;
	uint64 m_cSamples;
};


//-----------------------------------------------------------------------------
// Purpose: Global accessor to the manager
//-----------------------------------------------------------------------------
class CSDOCache;
CSDOCache &GSDOCache();


//-----------------------------------------------------------------------------
// Purpose: interface to a Database Backed Object
//-----------------------------------------------------------------------------
class ISDO
{
public:
	virtual ~ISDO() {}

	// Identification
	virtual int GetType() const = 0;
	virtual uint32 GetHashCode() const = 0;
	virtual bool IsEqual( const ISDO *pSDO ) const = 0;

	// Ref counting
	virtual int AddRef() = 0;
	virtual int Release() = 0;
	virtual int GetRefCount() = 0;

	// memory usage
	virtual size_t CubBytesUsed() = 0;

	// Serialization tools
	virtual bool BReadFromBuffer( const byte *pubData, int cubData ) = 0;
	virtual void WriteToBuffer( CUtlBuffer &memBuffer ) = 0;

	// memcached batching tools
	virtual void GetMemcachedKeyName( CFmtStr &strDest ) = 0;

	// SQL loading
	virtual bool BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const = 0;

	// post-load initialization (whether loaded from SQL or memcached)
	virtual void PostLoadInit() = 0;

	// comparison function for validating memcached copies vs SQL copies
	virtual bool IsIdentical( ISDO *pSDO ) = 0;

	// Returns true if this is not the actual object, but a placeholder to remember that this object
	// doesn't actually exist
	virtual bool BNilObject() const = 0;

	// Allocs an SDO that must return true for IsEqual( this ) and BNilObject(). This is so we can
	// cache load attempts for objects that don't exist
	virtual ISDO *AllocNilObject() = 0;

	// Creates a key name that looks like "Prefix_%u" but faster
};


//-----------------------------------------------------------------------------
// Purpose: base class for a Serialized Digital Object
//-----------------------------------------------------------------------------
template<typename KeyType, int eSDOType, class ProtoMsg>
class CBaseSDO : public ISDO
{
public:
	typedef KeyType KeyType_t;
	enum { k_eType = eSDOType };

	CBaseSDO( const KeyType &key ) : m_Key( key ), m_nRefCount( 0 ) {}

	const KeyType &GetKey() const		{ return m_Key; }

	// ISDO implementation
	virtual int AddRef();
	virtual int Release();
	virtual int GetRefCount();
	virtual int GetType() const			{ return eSDOType; }
	virtual bool BNilObject() const			{ return false; }
	virtual uint32 GetHashCode() const;
	virtual bool BReadFromBuffer( const byte *pubData, int cubData );
	virtual void WriteToBuffer( CUtlBuffer &memBuffer );

	// Implement this in a subclass if there's some value like 0 or -1 that's invalid so
	// the system can know to not even attempt to load it
	static bool BKeyValid( const KeyType_t &key ) { return true; }

	// We use protobufs for all serialization
	virtual void SerializeToProtobuf( ProtoMsg &msg ) = 0;
	virtual bool DeserializeFromProtobuf( const ProtoMsg &msg ) = 0;

	// default comparison function - override to do your own compare
	virtual bool IsEqual( const ISDO *pSDO ) const;

	// default load from SQL is no-op as not all types have permanent storage - override to create a
	// batch load
	virtual bool BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const;

	// override to do initialization after load
	virtual void PostLoadInit() {}

	// compares the serialized versions by default. Override to have more specific behavior
	virtual bool IsIdentical( ISDO *pSDO );

	// Creates a copy of the object with the same key
	virtual ISDO *AllocNilObject();

	// tools
	bool WriteToMemcached();
	bool DeleteFromMemcached();

	// Creates a key name that looks like "Prefix_%u" but faster. Also makes sure the correct buffer size is passed
	template <size_t prefixBufSize>
	void CreateSimpleMemcachedName( CFmtStr &strDest, char (&rgchPrefix)[prefixBufSize], uint32 unSuffix )
	{
		CSDOCache::CreateSimpleMemcachedName( strDest, rgchPrefix, prefixBufSize - 1, unSuffix );
	}

private:
	int m_nRefCount;
	KeyType m_Key;
};


//-----------------------------------------------------------------------------
// Purpose: Represents an object we tried to load but didn't exist. We use
//	this to cache the failure so we don't keep trying to look it up
//-----------------------------------------------------------------------------
template<typename KeyType, int eSDOType, class ProtoMsg>
class CNilSDO : public CBaseSDO<KeyType, eSDOType, ProtoMsg>
{
public:
	CNilSDO( const KeyType &key, const char *pchMemcachedKeyName ) : CBaseSDO<KeyType, eSDOType, ProtoMsg>( key ), m_sMemcachedKeyName( pchMemcachedKeyName ) {}

	virtual bool BNilObject() const										{ return true; }
	virtual bool BReadFromBuffer( const byte *pubData, int cubData )	{ return false; }
	virtual void WriteToBuffer( CUtlBuffer &memBuffer )					{ memBuffer.PutString( k_rgchNilObjSerializedValue ); }
	virtual void SerializeToProtobuf( ProtoMsg &msg )					{}
	virtual bool DeserializeFromProtobuf( const ProtoMsg &msg )			{ return false; }
	virtual size_t CubBytesUsed()										{ return sizeof( *this ) + m_sMemcachedKeyName.Length(); }
	virtual void GetMemcachedKeyName( CFmtStr &strKey )					{ V_strncpy( strKey.Access(), m_sMemcachedKeyName, FMTSTR_STD_LEN ); }
private:
	CUtlString m_sMemcachedKeyName;
};


//-----------------------------------------------------------------------------
// Purpose: references to a database-backed object
//			maintains refcount of the object
//-----------------------------------------------------------------------------
template<class T>
class CSDORef
{
	T *m_pSDO;
public:
	CSDORef() { m_pSDO = NULL; }
	explicit CSDORef( CSDORef<T> &SDORef ) { m_pSDO = SDORef.Get(); if ( m_pSDO ) m_pSDO->AddRef(); }
	explicit CSDORef( T *pSDO ) { m_pSDO = pSDO; if ( m_pSDO ) m_pSDO->AddRef(); }
	~CSDORef() { if ( m_pSDO ) m_pSDO->Release(); }

	T *Get() { return m_pSDO; }
	const T *Get() const { return m_pSDO; }

	T *operator->() { return Get(); }
	const T *operator->() const { return Get(); }

	operator const T *() const	{ return m_pSDO; }
	operator const T *()        { return m_pSDO; }
	operator T *()				{ return m_pSDO; }

	CSDORef<T> &operator=( T *pSDO ) { if ( m_pSDO ) m_pSDO->Release();  m_pSDO = pSDO; if ( m_pSDO ) m_pSDO->AddRef(); return *this; }

	bool operator !() const { return Get() == NULL; }

	bool IsValid( void ) const { return Get() != NULL; }
};


//-----------------------------------------------------------------------------
// Purpose: manages a cache of SDO objects
//-----------------------------------------------------------------------------
class CSDOCache
{
public:
	CSDOCache();
	~CSDOCache();

	// Call to register SDOs. All SDO types must be registered before loaded
	void RegisterSDO( int nType, const char *pchName );

	// A struct to hold stats for the system. This is generated code in Steam. It would be great to make
	// it generated code here if we could bring Steam's operational stats system in the GC
	struct StatsSDOCache_t
	{
		uint64 m_cItemsLRUd;
		uint64 m_cBytesLRUd;
		uint64 m_cItemsUnreferenced;
		uint64 m_cBytesUnreferenced;
		uint64 m_cItemsInCache;
		uint64 m_cBytesInCacheEst;
		uint64 m_cItemsQueuedToLoad;
		uint64 m_cItemsLoadedFromMemcached;
		uint64 m_cItemsLoadedFromSQL;
		uint64 m_cItemsFailedLoadFromSQL;
		uint64 m_cQueuedMemcachedRequests;
		uint64 m_cQueuedSQLRequests;
		uint64 m_nSQLBatchSizeAvgx100;
		uint64 m_nMemcachedBatchSizeAvgx100;
		uint64 m_cSQLRequestsRejectedTooBusy;
		uint64 m_cMemcachedRequestsRejectedTooBusy;
		uint64 m_cNilItemsLoadedFromMemcached;
		uint64 m_cNilItemsLoadedFromSQL;
	};

	// loads a SDO, and assigns a reference to it
	// returns false if the item couldn't be loaded, or timed out loading
	template<class T>
	bool BYldLoadSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbTimeoutLoading = NULL );

	// gets access to a SDO, but only if it's currently loaded
	template<class T>
	bool BGetLoadedSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbFoundNil = NULL );

	// starts loading a SDO you're going to reference soon with the above BYldLoadSDO()
	// use this to batch up requests, hinting a set then getting reference to a set is significantly faster
	template<class T>
	void HintLoadSDO( const typename T::KeyType_t &key );
	// as above, but starts load a set
	template<class T>
	void HintLoadSDO( const CUtlVector<typename T::KeyType_t> &vecKeys );

	// Clears a nil object if one exists for this key.
	template<class T>
	void RemoveNil( const typename T::KeyType_t &key );

	// force a deletes a SDO from the cache - waits until the object is not referenced
	template<class T>
	bool BYldDeleteSDO( const typename T::KeyType_t &key, uint64 unMicrosecondsToWaitForUnreferenced );

	// SDO refcount management
	void OnSDOReferenced( ISDO *pSDO );
	void OnSDOReleased( ISDO *pSDO );

	// writes a SDO to memcached immediately
	bool WriteSDOToMemcached( ISDO *pSDO );
	// delete the SDO record from memcached
	bool DeleteSDOFromMemcached( ISDO *pSDO );

	// job results
	void OnSDOLoadSuccess( int eSDO, int iRequestID, bool bNilObj, ISDO **ppSDO );
	void OnMemcachedSDOLoadFailure( int eSDO, int iRequestID );
	void OnSQLSDOLoadFailure( int eSDO, int iRequestID, bool bSQLLayerSucceeded );
	void OnMemcachedLoadJobComplete( JobID_t jobID );
	void OnSQLLoadJobComplete( int eSDO, JobID_t jobID );

	// test access - deletes all unreferenced objects
	void Flush();

	// stats access
	StatsSDOCache_t &GetStats()	{ return m_StatsSDOCache; }
	int CubReferencedEst();	// number of bytes referenced in the cache

	// prints info about the class
	void Dump();

	static void CreateSimpleMemcachedName( CFmtStr &strDest, const char *pchPrefix, uint32 unPrefixLen, uint32 unSuffix );

	// memcached verification - returns the number of mismatches
//**tempcomment**		void YldVerifyMemcachedData( CreateSDOFunc_t pCreateSDOFunc, CUtlVector<uint32> &vecIDs, int *pcMatches, int *pcMismatches );

#ifdef DBGFLAG_VALIDATE
	void Validate( CValidator &validator, const char *pchName );
#endif

	// Functions that need to be in the frame loop
	virtual bool BFrameFuncRunJobsUntilCompleted( CLimitTimer &limitTimer );
	virtual bool BFrameFuncRunMemcachedQueriesUntilCompleted( CLimitTimer &limitTimer );
	virtual bool BFrameFuncRunSQLQueriesUntilCompleted( CLimitTimer &limitTimer );

private:
	// Custom comparator for our hash map
	class CDefPISDOEquals
	{
	public:
		CDefPISDOEquals() {}
		CDefPISDOEquals( int i ) {}
		inline bool operator()( const ISDO *lhs, const ISDO *rhs ) const { return ( lhs->IsEqual( rhs ) );	}
		inline bool operator!() const { return false; }
	};

	class CPISDOHashFunctor
	{
	public:
		uint32 operator()(const ISDO *pSDO ) const { return pSDO->GetHashCode(); }
	};

	template<class T>
	int FindLoadedSDO( const typename T::KeyType_t &key );

	template<class T>
	int QueueLoad( const typename T::KeyType_t &key );
	int QueueMemcachedLoad( ISDO *pSDO );

	// items already loaded - Maps the SDO to the LRU position
	CUtlHashMapLarge<ISDO *, int, CDefPISDOEquals, CPISDOHashFunctor> m_mapISDOLoaded;

	// items we have queued to load, in the state of either being loaded from memcached or SQL
	// maps SDO to a list of jobs waiting on the load
	CUtlHashMapLarge<ISDO *, CCopyableUtlVector<JobID_t>, CDefPISDOEquals, CPISDOHashFunctor> m_mapQueuedRequests;

	// requests to load from memcached
	CUtlLinkedList<int, int> m_queueMemcachedRequests;

	// Jobs currently processing memcached load requests
	CUtlVector<JobID_t> m_vecMemcachedJobs;

	// Loading from SQL is divided by SDO type
	struct SQLRequestManager_t
	{
		// requests to load from SQL. Maps to an ID in the map of queued requests
		CUtlLinkedList<int, int> m_queueRequestIDsToLoadFromSQL;

		// SQL jobs we have active doing reads for cache items
		CUtlVector<JobID_t> m_vecSQLJobs;
	};

	// a queue of requests to load from SQL for each type
	CUtlHashMapLarge<int, SQLRequestManager_t *> m_mapQueueSQLRequests;

	// jobs to wake up, since we've satisfied their SDO load request
	struct JobToWake_t
	{
		JobID_t m_jobID;
		bool m_bLoadLayerSuccess;
	};
	CUtlLinkedList<JobToWake_t, int> m_queueJobsToContinue;

	struct LRUItem_t
	{
		ISDO *	m_pSDO;
		size_t	m_cub;
	};
	CUtlLinkedList<LRUItem_t, int> m_listLRU;
	uint32 m_cubLRUItems;
	void RemoveSDOFromLRU( int iMapSDOLoaded );

	struct TypeStats_t
	{
		TypeStats_t() 
			: m_nLoaded( 0 )
			, m_nRefed( 0 )
			, m_cubUnrefed( 0 )
			, m_nNilObjects( 0 )
		{}

		CUtlString	m_strName;
		int			m_nLoaded;
		int			m_nRefed;
		int			m_cubUnrefed;
		int			m_nNilObjects;
	};

	StatsSDOCache_t m_StatsSDOCache;
	CMovingAverage<100> m_StatMemcachedBatchSize, m_StatSQLBatchSize;
	CUtlMap<int, TypeStats_t> m_mapTypeStats;
};


//-----------------------------------------------------------------------------
// Definition of CBaseSDO template functions now that CSDOCache is defined and
// GSDOCache() can safely be used.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Purpose: adds a reference
//-----------------------------------------------------------------------------
template<typename KeyType, int ESDOType, class ProtoMsg>
int CBaseSDO<KeyType,ESDOType,ProtoMsg>::AddRef()
{
	if ( ++m_nRefCount == 1 )
		GSDOCache().OnSDOReferenced( this );

	return m_nRefCount;
}


//-----------------------------------------------------------------------------
// Purpose: releases a reference
//-----------------------------------------------------------------------------
template<typename KeyType, int ESDOType, class ProtoMsg>
int CBaseSDO<KeyType,ESDOType,ProtoMsg>::Release()
{
	DbgVerify( m_nRefCount > 0 );

	int nRefCount = --m_nRefCount;

	if ( nRefCount == 0 )
		GSDOCache().OnSDOReleased( this );

	return nRefCount;
}


//-----------------------------------------------------------------------------
// Purpose: ref count
//-----------------------------------------------------------------------------
template<typename KeyType, int ESDOType, class ProtoMsg>
int CBaseSDO<KeyType,ESDOType,ProtoMsg>::GetRefCount()
{
	return m_nRefCount;
}


//-----------------------------------------------------------------------------
// Purpose: Hashes the object for insertion into a hashtable
//-----------------------------------------------------------------------------
template<typename KeyType, int ESDOType, class ProtoMsg>
uint32 CBaseSDO<KeyType,ESDOType,ProtoMsg>::GetHashCode() const
{
#pragma pack( push, 1 )
	struct hashcode_t
	{
		int m_Type;
		KeyType_t m_Key;
	} hashStruct = { ESDOType, m_Key };
#pragma pack( pop )

	return PearsonsHashFunctor<hashcode_t>()( hashStruct );
}


//-----------------------------------------------------------------------------
// Purpose: Deserializes the object
//-----------------------------------------------------------------------------
template<typename KeyType, int ESDOType, class ProtoMsg>
bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::BReadFromBuffer( const byte *pubData, int cubData )
{
	ProtoMsg msg;
	if ( !msg.ParseFromArray( pubData, cubData ) )
		return false;

	if ( !DeserializeFromProtobuf( msg ) )
		return false;

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Serializes the object
//-----------------------------------------------------------------------------
template<typename KeyType, int ESDOType, class ProtoMsg>
void CBaseSDO<KeyType,ESDOType,ProtoMsg>::WriteToBuffer( CUtlBuffer &memBuffer )
{
	ProtoMsg *pMsg = CProtoBufMsg<ProtoMsg>::AllocProto();

	SerializeToProtobuf( *pMsg );
	uint32 unSize = pMsg->ByteSize();
	memBuffer.EnsureCapacity( memBuffer.Size() + unSize );
	pMsg->SerializeWithCachedSizesToArray( (uint8*)memBuffer.Base() + memBuffer.TellPut() );
	memBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, memBuffer.TellPut() + unSize );
	
	CProtoBufMsg<ProtoMsg>::FreeProto( pMsg );
}


//-----------------------------------------------------------------------------
// Purpose: does an immediate write of the object to memcached
//-----------------------------------------------------------------------------
template<typename KeyType, int ESDOType, class ProtoMsg>
bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::WriteToMemcached()
{
	return GSDOCache().WriteSDOToMemcached( this );
}


//-----------------------------------------------------------------------------
// Purpose: does an immediate write of the object to memcached
//-----------------------------------------------------------------------------
template<typename KeyType, int ESDOType, class ProtoMsg>
bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::DeleteFromMemcached()
{
	return GSDOCache().DeleteSDOFromMemcached( this );
}


//-----------------------------------------------------------------------------
// Purpose: default equality function - compares type and key
//-----------------------------------------------------------------------------
template<typename KeyType, int ESDOType, class ProtoMsg>
bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::IsEqual( const ISDO *pSDO ) const
{
	if ( GetType() != pSDO->GetType() )
		return false;

	return ( GetKey() == static_cast<const CBaseSDO<KeyType,ESDOType,ProtoMsg> *>( pSDO )->GetKey() );
}


//-----------------------------------------------------------------------------
// Purpose: Batch load a group of SDO's of the same type from SQL. 
//  Default is no-op as not all types have permanent storage.
//-----------------------------------------------------------------------------
template<typename KeyType, int ESDOType, class ProtoMsg>
bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::BYldLoadFromSQL( CUtlVector<ISDO *> &vecSDOToLoad, CUtlVector<bool> &vecResults ) const
{
	FOR_EACH_VEC( vecResults, i )
	{
		vecResults[i] = true;
	}

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: default validation function - compares serialized versions
//-----------------------------------------------------------------------------
bool CompareSDOObjects( ISDO *pSDO1, ISDO *pSDO2 );

template<typename KeyType, int ESDOType, class ProtoMsg>
bool CBaseSDO<KeyType,ESDOType,ProtoMsg>::IsIdentical( ISDO *pSDO )
{
	return CompareSDOObjects( this, pSDO );
}


//-----------------------------------------------------------------------------
// Purpose: default validation function - compares serialized versions
//-----------------------------------------------------------------------------
template<typename KeyType, int ESDOType, class ProtoMsg>
ISDO *CBaseSDO<KeyType,ESDOType,ProtoMsg>::AllocNilObject()
{
	CFmtStr strKey;
	GetMemcachedKeyName( strKey );
	return new CNilSDO<KeyType,ESDOType,ProtoMsg>( GetKey(), strKey );
}


//-----------------------------------------------------------------------------
// Purpose: Finds a loaded SDO in memory. Returns the index of the object
//	into the loaded SDOs map
//-----------------------------------------------------------------------------
template<class T>
int CSDOCache::FindLoadedSDO( const typename T::KeyType_t &key )
{
	// see if we have it in cache first
	T probe( key );
	return m_mapISDOLoaded.Find( &probe );
}


//-----------------------------------------------------------------------------
// Purpose: Queues loading an SDO. Returns the index of the entry in the
//	load queue
//-----------------------------------------------------------------------------
template<class T>
int CSDOCache::QueueLoad( const typename T::KeyType_t &key )
{
	T probe( key );
	int iMap = m_mapQueuedRequests.Find( &probe );
	if ( m_mapQueuedRequests.IsValidIndex( iMap ) )
		return iMap;

	return QueueMemcachedLoad( new T( key ) );
}


//-----------------------------------------------------------------------------
// Purpose: Preloads the object into the local cache
//-----------------------------------------------------------------------------
template<class T>
void CSDOCache::HintLoadSDO( const typename T::KeyType_t &key )
{
	// see if this is something we should even try to load
	if ( !T::BKeyValid( key ) )
		return;

	// see if we have it in cache first
	if ( !m_mapISDOLoaded.IsValidIndex( FindLoadedSDO<T>( key ) ) )
	{
		QueueLoad<T>( key );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Preloads a set set of objects into the local cache
//-----------------------------------------------------------------------------
template<class T>
void CSDOCache::HintLoadSDO( const CUtlVector<typename T::KeyType_t> &vecKeys )
{
	FOR_EACH_VEC( vecKeys, i )
	{
		HintLoadSDO<T>( vecKeys[i] );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Returns an already-loaded SDO
//-----------------------------------------------------------------------------
template<class T>
bool CSDOCache::BGetLoadedSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbFoundNil )
{
	if ( NULL != pbFoundNil )
	{
		*pbFoundNil = false;
	}

	int iMap = FindLoadedSDO<T>( key );
	if ( !m_mapISDOLoaded.IsValidIndex( iMap ) )
		return false;

	ISDO *pObj = m_mapISDOLoaded.Key( iMap );
	if ( pObj->BNilObject() )
	{
		int iLRU = m_mapISDOLoaded[ iMap ];
		Assert( m_listLRU.IsValidIndex( iLRU ) );
		if ( m_listLRU.IsValidIndex( iLRU ) )
		{
			// Even though we don't return nil objects, this is a hit on it
			// so it needs to go to the back of the LRU
			m_listLRU.LinkToTail( m_mapISDOLoaded[ iMap ] );
		}

		if ( NULL != pbFoundNil )
		{
			*pbFoundNil = true;
		}

		return false;
	}
	else
	{
		*pPSDORef = assert_cast<T*>( pObj );
		return true;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Loads the object into memory
//-----------------------------------------------------------------------------
template<class T>
bool CSDOCache::BYldLoadSDO( CSDORef<T> *pPSDORef, const typename T::KeyType_t &key, bool *pbTimeoutLoading /* = NULL */ )
{
	VPROF_BUDGET( "CSDOCache::BYldLoadSDO", VPROF_BUDGETGROUP_STEAM );
	if ( pbTimeoutLoading )
		*pbTimeoutLoading = false;

	// Clear the current object the ref is holding
	*pPSDORef = NULL;

	// see if this is something we should even try to load
	if ( !T::BKeyValid( key ) )
		return false;

	// see if we have it in cache first
	bool bFoundNil = false;
	if ( BGetLoadedSDO( pPSDORef, key, &bFoundNil ) )
		return true;

	// If we've already tried to look this up in the past don't bother looking it up again
	if ( bFoundNil )
		return false;

	// otherwise batch it for load
	int iMap = QueueLoad<T>( key );

	// make sure we could queue it
	if ( !m_mapQueuedRequests.IsValidIndex( iMap ) )
		return false;

	// add the current job to this list waiting for the object to load 
	m_mapQueuedRequests[iMap].AddToTail( GJobCur().GetJobID() );

	// wait for it to load (loader will signal our job when done)
	if ( !GJobCur().BYieldingWaitForWorkItem() )
	{
		if ( pbTimeoutLoading )
			*pbTimeoutLoading = true;
		return false;
	}

	// should be loaded - look up in the load map and try again
	bool bRet = BGetLoadedSDO( pPSDORef, key );
	//Assert( bRet );

	return bRet;
}


//-----------------------------------------------------------------------------
// Clears a nil object if one exists for this key.
//-----------------------------------------------------------------------------
template<class T>
void CSDOCache::RemoveNil( const typename T::KeyType_t &key )
{
	// See if this key is allowed to exist
	if ( !T::BKeyValid( key ) )
	{
		AssertMsg( false, "RemoveNil called with an invalid key" );
		return;
	}

	// Remove the nil if there is one
	int iMap = FindLoadedSDO< T >( key );
	if ( m_mapISDOLoaded.IsValidIndex( iMap ) )
	{
		ISDO *pSDO = m_mapISDOLoaded.Key( iMap );
		if ( !pSDO->BNilObject() )
			return;

		int iMapStats = m_mapTypeStats.Find( pSDO->GetType() );
		if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
		{
			m_mapTypeStats[iMapStats].m_nNilObjects--;
		}

		RemoveSDOFromLRU( iMap );
		m_mapISDOLoaded.RemoveAt( iMap );
		delete pSDO;
	}

	// Remove the object from memcached. Do this here because while we have for sure removed any nil that was in memory
	// this may have been a nil that was not in memory but instead cached in memcached.
	T temp( key );
	DeleteSDOFromMemcached( &temp );
}


//-----------------------------------------------------------------------------
// Purpose: reloads an existing element from the SQL DB
//-----------------------------------------------------------------------------
template<class T>
bool CSDOCache::BYldDeleteSDO( const typename T::KeyType_t &key, uint64 unMicrosecondsToWaitForUnreferenced )
{
	// see if we have it in cache first
	int iMap = FindLoadedSDO<T>( key );
	if ( !m_mapISDOLoaded.IsValidIndex( iMap ) )
	{
		T temp( key );
		temp.DeleteFromMemcached();
		return true; /* we're good, it's not loaded */
	}

	assert_cast<T *>(m_mapISDOLoaded.Key( iMap ))->DeleteFromMemcached();

	// check the ref count
	int64 cAttempts = MAX( 1LL, (int64)(unMicrosecondsToWaitForUnreferenced / k_cMicroSecPerShellFrame) );
	while ( cAttempts-- > 0 )
	{
		if ( 0 == m_mapISDOLoaded.Key( iMap )->GetRefCount() )
		{
			// delete the object
			Assert( m_listLRU.IsValidIndex( m_mapISDOLoaded[iMap] ) );

			RemoveSDOFromLRU( iMap );
			ISDO *pSDO = m_mapISDOLoaded.Key( iMap );
			m_mapISDOLoaded.RemoveAt( iMap );

			int iMapStats = m_mapTypeStats.Find( m_mapISDOLoaded.Key( iMap )->GetType() );
			if ( m_mapTypeStats.IsValidIndex( iMapStats ) )
			{
				if ( pSDO->BNilObject() )
				{
					m_mapTypeStats[iMapStats].m_nNilObjects--;
				}
				else
				{
					m_mapTypeStats[iMapStats].m_nLoaded--;
				}
			}

			delete pSDO;
			return true;
		}
		else
		{
			GJobCur().BYieldingWaitOneFrame();
		}
	}
	
	// couldn't reload
	return false;
}


//-----------------------------------------------------------------------------
// Purpose: A class to factor out the common code in most SDO SQL loading funcitons
//-----------------------------------------------------------------------------
template<class T>
class CSDOSQLLoadHelper
{
public:

	//this is the results of the load helper, abstracted to provide a more efficient means to lookup the queries without
	//having to do very expensive memory management
	template< class SCH >
	class CResults
	{
	public:

		//given a key, this will return the range of indices that include this key. -1 will be set to the start if no match is found. This will return
		//the number of matches found
		int			GetKeyIndexRange( typename T::KeyType_t Key, int& nStart, int& nEnd ) const;

		//given a key, this will return the first schema that matches this key, or NULL if none is found. Only use this if you know there
		//is a maximum of a single value
		const SCH*	GetSingleResultForKey( typename T::KeyType_t Key ) const;

		//used to start iteration over a group of results. Given a key, this will return the index that can be used to get the result or -1 if no match is found
		int			GetFirstResultIndex( typename T::KeyType_t Key ) const;
		//given an index that was previously obtained from GetFirst/NextResultIndex, this will get the next result that has the same key, or return -1 if no further keys of that type are found
		int			GetNextResultIndex( int nOldResultIndex ) const;
		//given an index from GetFirst/NextResultIndex that is valid (i.e. not -1), this will return the result that was obtained
		const SCH*	GetResultFromIndex( int nIndex ) const;
		//given an index returned by the GetFirst/NextResultIndex, this will determine if it is valid
		static int	InvalidIndex()		{ return -1; }

	private:
		//given a key, this maps to the specific result
		template< class TKeyType >
		struct SKeyToResult
		{
			bool operator< (const SKeyToResult< TKeyType >& rhs ) const			{ return m_Key < rhs.m_Key; }
			typename TKeyType	m_Key;				//key value
			uint32				m_nResultIndex;		//index into our result list
		};

		friend class CSDOSQLLoadHelper;

		CUtlVector< SKeyToResult< typename T::KeyType_t > >	m_KeyToResult;
		CUtlVector< SCH >							m_Results;
	};


	// Initializes with the vector of objects being loaded
	CSDOSQLLoadHelper( const CUtlVector<ISDO *> *vecSDOToLoad, const char *pchProfileName );

	// Loads all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
	template<class SCH>
	bool BYieldingExecuteSingleTable( int nFieldID, CResults< SCH >& Results );
	// Loads the specified columns for all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
	template<class SCH>
	bool BYieldingExecuteSingleTable( int nFieldID, const CColumnSet &csetRead, CResults< SCH >& Results );

	// Functions to load rows from more than one table at a time

	// Loads all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
	template<class SCH>
	void AddTableToQuery( int nFieldID );

	// Loads the specified columns for all rows in the SCH table whose field nFieldID match the key of an SDO being loaded
	template<class SCH>
	void AddTableToQuery( int nFieldID, const CColumnSet &csetRead );

	// Executes the mutli-table query
	bool BYieldingExecute();
		
	// Gets the results for a table from a multi-table query
	template<class SCH>
	bool BGetResults( int nQuery, CResults< SCH >& Results );

private:
	CUtlVector<typename T::KeyType_t> m_vecKeys;
	CSQLAccess m_sqlAccess;

	struct Query_t
	{
		Query_t( const CColumnSet &columnSet, int nKeyCol ) : m_ColumnSet( columnSet ), m_nKeyCol( nKeyCol ) {}
		CColumnSet	m_ColumnSet;
		int			m_nKeyCol;
	};

	CUtlVector<Query_t> m_vecQueries;
};

//utility to help with iteration through a SQL load result list
#define FOR_EACH_SQL_LOAD_RESULT( Results, Key, Index )		for( int Index = Results.GetFirstResultIndex( Key ); Index != Results.InvalidIndex(); Index = Results.GetNextResultIndex( Index ) )


//-----------------------------------------------------------------------------
// Purpose: Constructor. Initializes with the vector of objects being loaded
//-----------------------------------------------------------------------------
template<class T>
CSDOSQLLoadHelper<T>::CSDOSQLLoadHelper( const CUtlVector<ISDO *> *vecSDOToLoad, const char *pchProfileName )
	: m_vecKeys( 0, vecSDOToLoad->Count() )
{
	FOR_EACH_VEC( *vecSDOToLoad, i )
	{
		m_vecKeys.AddToTail( ( (T*)vecSDOToLoad->Element( i ) )->GetKey() );
	}

	Assert( m_vecKeys.Count() > 0 );
	DbgVerify( m_sqlAccess.BBeginTransaction( pchProfileName ) );
}


//-----------------------------------------------------------------------------
// Purpose: Loads all rows in the SCH table whose field nFieldID match the 
//	key of an SDO being loaded.
//-----------------------------------------------------------------------------
template<class T>
template<class SCH>
bool CSDOSQLLoadHelper<T>::BYieldingExecuteSingleTable( int nFieldID, CResults< SCH >& Results )
{
	static const CColumnSet cSetRead = CColumnSet::Full<SCH>();
	return BYieldingExecuteSingleTable( nFieldID, cSetRead, Results );
}


//-----------------------------------------------------------------------------
// Purpose: Loads the specified columns for all rows in the SCH table whose 
//	field nFieldID match the key of an SDO being loaded
//-----------------------------------------------------------------------------
template<class T>
template<class SCH>
bool CSDOSQLLoadHelper<T>::BYieldingExecuteSingleTable( int nFieldID, const CColumnSet &csetRead, CResults< SCH >& Results )
{
	AddTableToQuery<SCH>( nFieldID, csetRead );
	if ( !BYieldingExecute() )
		return false;

	return BGetResults<SCH>( 0, Results );
}


//-----------------------------------------------------------------------------
// Purpose: Loads all rows in the SCH table whose field nFieldID match the key 
//	of an SDO being loaded
//-----------------------------------------------------------------------------
template<class T>
template<class SCH>
void CSDOSQLLoadHelper<T>::AddTableToQuery( int nFieldID )
{
	static const CColumnSet cSetRead = CColumnSet::Full<SCH>();
	AddTableToQuery<SCH>( nFieldID, cSetRead );
}


//-----------------------------------------------------------------------------
// Purpose: Loads the specified columns for all rows in the SCH table whose 
//	field nFieldID match the key of an SDO being loaded
//-----------------------------------------------------------------------------
template<class T>
template<class SCH>
void CSDOSQLLoadHelper<T>::AddTableToQuery( int nFieldID, const CColumnSet &csetRead )
{
	Assert( csetRead.GetRecordInfo() == GSchemaFull().GetSchema( SCH::k_iTable ).GetRecordInfo() );

	// Bind the params
	FOR_EACH_VEC( m_vecKeys, i )
	{
		m_sqlAccess.AddBindParam( m_vecKeys[i] );
	}

	// Build the query
	CUtlString sCommand;
	{
		TSQLCmdStr sSelect;
		const char *pchColumnName = GSchemaFull().GetSchema( SCH::k_iTable ).GetRecordInfo()->GetColumnInfo( nFieldID ).GetName();

		BuildSelectStatementText( &sSelect, csetRead );
		sCommand.Format( "%s WHERE %s IN (%.*s)", sSelect.Access(), pchColumnName, GetInsertArgStringChars( m_vecKeys.Count() ), GetInsertArgString() );
	}

	// Execute. Because we're in a transaction this will delay to the commit
	DbgVerify( m_sqlAccess.BYieldingExecute( NULL, sCommand ) );

	m_vecQueries.AddToTail( Query_t( csetRead, nFieldID ) );
}


//-----------------------------------------------------------------------------
// Purpose: Executes the mutli-table query
//-----------------------------------------------------------------------------
template<class T>
bool CSDOSQLLoadHelper<T>::BYieldingExecute()
{
	if ( 0 == m_vecKeys.Count() )
	{
		m_sqlAccess.RollbackTransaction();
		return false;
	}

	if ( !m_sqlAccess.BCommitTransaction() )
		return false;

	Assert( (uint32)m_vecQueries.Count() == m_sqlAccess.GetResultSetCount() );
	return (uint32)m_vecQueries.Count() == m_sqlAccess.GetResultSetCount();
}


//-----------------------------------------------------------------------------
// Purpose: Gets the results for a table from a multi-table query
//-----------------------------------------------------------------------------
template<class T>
template<class SCH>
bool CSDOSQLLoadHelper<T>::BGetResults( int nQuery, CResults< SCH >& Results )
{
	//clear any previous results
	Results.m_KeyToResult.Purge();
	Results.m_Results.Purge();

	IGCSQLResultSetList *pSQLResults = m_sqlAccess.GetResults();
	Assert( pSQLResults && nQuery >= 0 && (uint32)nQuery < pSQLResults->GetResultSetCount() && pSQLResults->GetResultSetCount() == (uint32)m_vecQueries.Count() );
	if ( NULL == pSQLResults || nQuery < 0 || (uint32)nQuery >= pSQLResults->GetResultSetCount() || pSQLResults->GetResultSetCount() != (uint32)m_vecQueries.Count() )
		return false;

	Assert( m_vecQueries[nQuery].m_ColumnSet.GetRecordInfo()->GetTableID() == SCH::k_iTable );
	if ( m_vecQueries[nQuery].m_ColumnSet.GetRecordInfo()->GetTableID() != SCH::k_iTable )
		return false;

	//copy the results from the SQL queries over to our result list
	Results.m_Results.EnsureCapacity( pSQLResults->GetResultSet( nQuery )->GetRowCount() );
	if ( !CopyResultToSchVector( pSQLResults->GetResultSet( nQuery ), m_vecQueries[nQuery].m_ColumnSet, &Results.m_Results ) )
		return false;

	// Make a map that counts maps from our results into a sorted list for fast lookup
	Results.m_KeyToResult.SetSize( Results.m_Results.Count() );
	FOR_EACH_VEC( Results.m_Results, nCurrResult )
	{
		//get our key value
		uint8 *pubData;
		uint32 cubData;
		if ( !Results.m_Results[ nCurrResult ].BGetField( m_vecQueries[nQuery].m_nKeyCol, &pubData, &cubData ) )
			return false;

		Assert( cubData == sizeof( T::KeyType_t ) );
		if ( cubData != sizeof( T::KeyType_t ) )
		{
			Results.m_KeyToResult.Purge();
			Results.m_Results.Purge();
			return false;
		}

		//setup our record
		Results.m_KeyToResult[ nCurrResult ].m_Key = *((T::KeyType_t *)pubData);
		Results.m_KeyToResult[ nCurrResult ].m_nResultIndex = nCurrResult;
	}

	//sort for binary search capabilities
	std::sort( Results.m_KeyToResult.begin(), Results.m_KeyToResult.end() );

	return true;
}

template< typename T >
template< typename SCH >
int CSDOSQLLoadHelper< T >::CResults< SCH >::GetKeyIndexRange( typename T::KeyType_t Key, int& nStart, int& nEnd ) const
{
	//find the start of the range
	nStart = GetFirstResultIndex( Key );
	if( nStart == InvalidIndex() )
	{
		nEnd = InvalidIndex();
		return 0;
	}
	//expand the end as long as it lies on a key in range and with the same value
	for( nEnd = nStart + 1; ( nEnd < m_KeyToResult.Count() ) && ( m_KeyToResult[ nEnd ].m_Key == Key ); nEnd++ )
	{
	}
	//and return the resulting number of elements we found
	return nEnd - nStart;
}


template< typename T >
template< typename SCH >
const SCH* CSDOSQLLoadHelper< T >::CResults< SCH >::GetSingleResultForKey( typename T::KeyType_t Key ) const
{
	int nIndex = GetFirstResultIndex( Key );
	AssertMsg( ( nIndex == InvalidIndex() ) || ( GetNextResultIndex( nIndex ) == InvalidIndex() ), "Requested a result from a SQL load helper assuming it was a singular entry, but found multiple instances of it" );
	return GetResultFromIndex( nIndex );
}

template< typename T >
template< typename SCH >
int CSDOSQLLoadHelper< T >::CResults< SCH >::GetFirstResultIndex( typename T::KeyType_t Key ) const
{
	//dummy entry to compare against
	SKeyToResult< typename T::KeyType_t > SearchKey;
	SearchKey.m_Key = Key;	

	//binary search to find our index
	const SKeyToResult< typename T::KeyType_t >* pMatch = std::lower_bound( m_KeyToResult.begin(), m_KeyToResult.end(), SearchKey );
	//see if we found a match
	if( ( pMatch == m_KeyToResult.end() ) || ( pMatch->m_Key != Key ) )
		return InvalidIndex();

	return ( pMatch - m_KeyToResult.begin() );
}

template< typename T >
template< typename SCH >
int CSDOSQLLoadHelper< T >::CResults< SCH >::GetNextResultIndex( int nOldResultIndex ) const
{
	//handle out of range elements. Either invalid, or the last element or beyond in our array
	if( ( nOldResultIndex < 0 ) || ( nOldResultIndex + 1 >= m_KeyToResult.Count() ) )
		return InvalidIndex();

	//see if we are less than the next value in the list. If so, we aren't equal and need to stop iteration	
	if( m_KeyToResult[ nOldResultIndex ] < m_KeyToResult[ nOldResultIndex + 1 ] )
		return InvalidIndex();
	
	//same key for the next element, so return a match
	return nOldResultIndex + 1;
}

template< typename T >
template< typename SCH >
const SCH* CSDOSQLLoadHelper< T >::CResults< SCH >::GetResultFromIndex( int nIndex ) const
{
	//ensure that the index is in range
	if( ( nIndex < 0 ) || ( nIndex >= m_KeyToResult.Count() ) )
		return NULL;
	return &( m_Results[ m_KeyToResult[ nIndex ].m_nResultIndex ] );
}

} // namespace GCSDK

#endif // SDOCACHE_H