//========= Copyright Valve Corporation, All rights reserved. ============//
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// model loading and caching
//
//===========================================================================//

#include <memory.h>
#include "tier0/vprof.h"
#include "tier0/icommandline.h"
#include "tier1/utllinkedlist.h"
#include "tier1/utlmap.h"
#include "datacache/imdlcache.h"
#include "istudiorender.h"
#include "filesystem.h"
#include "optimize.h"
#include "materialsystem/imaterialsystemhardwareconfig.h"
#include "materialsystem/imesh.h"
#include "datacache/idatacache.h"
#include "studio.h"
#include "vcollide.h"
#include "utldict.h"
#include "convar.h"
#include "datacache_common.h"
#include "mempool.h"
#include "vphysics_interface.h"
#include "phyfile.h"
#include "studiobyteswap.h"
#include "tier2/fileutils.h"
#include "filesystem/IQueuedLoader.h"
#include "tier1/lzmaDecoder.h"
#include "functors.h"

// XXX remove this later. (henryg)
#if 0 && defined(_DEBUG) && defined(_WIN32) && !defined(_X360)
typedef struct LARGE_INTEGER { unsigned long long QuadPart; } LARGE_INTEGER;
extern "C" void __stdcall OutputDebugStringA( const char *lpOutputString );
extern "C" long __stdcall QueryPerformanceCounter( LARGE_INTEGER *lpPerformanceCount );
extern "C" long __stdcall QueryPerformanceFrequency( LARGE_INTEGER *lpPerformanceCount );
namespace {
	class CDebugMicroTimer
	{
	public:
		CDebugMicroTimer(const char* n) : name(n) { QueryPerformanceCounter(&start); }
		~CDebugMicroTimer() {
			LARGE_INTEGER end;
			char outbuf[128];
			QueryPerformanceCounter(&end);
			if (!freq) QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
			V_snprintf(outbuf, 128, "%s %6d us\n", name, (int)((end.QuadPart - start.QuadPart) * 1000000 / freq));
			OutputDebugStringA(outbuf);
		}
		LARGE_INTEGER start;
		const char* name;
		static long long freq;
	};
	long long CDebugMicroTimer::freq = 0;
}
#define DEBUG_SCOPE_TIMER(name) CDebugMicroTimer dbgLocalTimer(#name)
#else
#define DEBUG_SCOPE_TIMER(name) (void)0
#endif

#ifdef _RETAIL
#define NO_LOG_MDLCACHE 1
#endif

#ifdef NO_LOG_MDLCACHE
#define LogMdlCache() 0
#else
#define LogMdlCache() mod_trace_load.GetBool()
#endif

#define MdlCacheMsg		if ( !LogMdlCache() ) ; else Msg
#define MdlCacheWarning if ( !LogMdlCache() ) ; else Warning

#if defined( _X360 )
#define AsyncMdlCache() 0	// Explicitly OFF for 360 (incompatible)
#else
#define AsyncMdlCache() 0
#endif

#define ERROR_MODEL		"models/error.mdl"
#define IDSTUDIOHEADER	(('T'<<24)+('S'<<16)+('D'<<8)+'I')

#define MakeCacheID( handle, type )	( ( (uint)(handle) << 16 ) | (uint)(type) )
#define HandleFromCacheID( id)		( (MDLHandle_t)((id) >> 16) )
#define TypeFromCacheID( id )		( (MDLCacheDataType_t)((id) & 0xffff) )

enum
{
	STUDIODATA_FLAGS_STUDIOMESH_LOADED	= 0x0001,
	STUDIODATA_FLAGS_VCOLLISION_LOADED	= 0x0002,
	STUDIODATA_ERROR_MODEL				= 0x0004,
	STUDIODATA_FLAGS_NO_STUDIOMESH		= 0x0008,
	STUDIODATA_FLAGS_NO_VERTEX_DATA		= 0x0010,
	STUDIODATA_FLAGS_VCOLLISION_SHARED	= 0x0020,
	STUDIODATA_FLAGS_LOCKED_MDL			= 0x0040,
};

// only models with type "mod_studio" have this data
struct studiodata_t
{
	// The .mdl file
	DataCacheHandle_t	m_MDLCache;

	// the vphysics.dll collision model
	vcollide_t			m_VCollisionData;

	studiohwdata_t		m_HardwareData;
#if defined( USE_HARDWARE_CACHE )
	DataCacheHandle_t	m_HardwareDataCache;
#endif

	unsigned short		m_nFlags;

	short				m_nRefCount;

	// pointer to the virtual version of the model
	virtualmodel_t		*m_pVirtualModel;

	// array of cache handles to demand loaded virtual model data
	int					m_nAnimBlockCount;
	DataCacheHandle_t	*m_pAnimBlock;
	unsigned int	 	*m_iFakeAnimBlockStall;

	// vertex data is usually compressed to save memory (model decal code only needs some data)
	DataCacheHandle_t	m_VertexCache;
	bool				m_VertexDataIsCompressed;

	int					m_nAutoplaySequenceCount;
	unsigned short		*m_pAutoplaySequenceList;

	void				*m_pUserData;

	DECLARE_FIXEDSIZE_ALLOCATOR_MT( studiodata_t );
};

DEFINE_FIXEDSIZE_ALLOCATOR_MT( studiodata_t, 128, CUtlMemoryPool::GROW_SLOW );

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

class CTempAllocHelper
{
public:
	CTempAllocHelper()
	{
		m_pData = NULL;
	}

	~CTempAllocHelper()
	{
		Free();
	}

	void *Get()
	{
		return m_pData;
	}

	void Alloc( int nSize )
	{
		m_pData = malloc( nSize );
	}

	void Free()
	{
		if ( m_pData )
		{
			free( m_pData );
			m_pData = NULL;
		}
	}
private:
	void *m_pData;
};

//-----------------------------------------------------------------------------
// ConVars
//-----------------------------------------------------------------------------
static ConVar r_rootlod( "r_rootlod", "0", FCVAR_ARCHIVE );
static ConVar mod_forcedata( "mod_forcedata", ( AsyncMdlCache() ) ? "0" : "1",	0, "Forces all model file data into cache on model load." );
static ConVar mod_test_not_available( "mod_test_not_available", "0", FCVAR_CHEAT );
static ConVar mod_test_mesh_not_available( "mod_test_mesh_not_available", "0", FCVAR_CHEAT );
static ConVar mod_test_verts_not_available( "mod_test_verts_not_available", "0", FCVAR_CHEAT );
static ConVar mod_load_mesh_async( "mod_load_mesh_async", ( AsyncMdlCache() ) ? "1" : "0" );
static ConVar mod_load_anims_async( "mod_load_anims_async", ( IsX360() || AsyncMdlCache() ) ? "1" : "0" );
static ConVar mod_load_vcollide_async( "mod_load_vcollide_async",  ( AsyncMdlCache() ) ? "1" : "0" );
static ConVar mod_trace_load( "mod_trace_load", "0" );
static ConVar mod_lock_mdls_on_load( "mod_lock_mdls_on_load", ( IsX360() ) ? "1" : "0" );
static ConVar mod_load_fakestall( "mod_load_fakestall", "0", 0, "Forces all ANI file loading to stall for specified ms\n");

//-----------------------------------------------------------------------------
// Utility functions
//-----------------------------------------------------------------------------

#if defined( USE_HARDWARE_CACHE )
unsigned ComputeHardwareDataSize( studiohwdata_t *pData )
{
	unsigned size = 0;
	for ( int i = pData->m_RootLOD; i < pData->m_NumLODs; i++ )
	{
		studioloddata_t *pLOD = &pData->m_pLODs[i];
		for ( int j = 0; j < pData->m_NumStudioMeshes; j++ )
		{
			studiomeshdata_t *pMeshData = &pLOD->m_pMeshData[j];
			for ( int k = 0; k < pMeshData->m_NumGroup; k++ )
			{
				size += pMeshData->m_pMeshGroup[k].m_pMesh->ComputeMemoryUsed();
			}
		}
	}
	return size;
}
#endif

//-----------------------------------------------------------------------------
// Async support
//-----------------------------------------------------------------------------

#define MDLCACHE_NONE ((MDLCacheDataType_t)-1)

struct AsyncInfo_t
{
	AsyncInfo_t() : hControl( NULL ), hModel( MDLHANDLE_INVALID ), type( MDLCACHE_NONE ), iAnimBlock( 0 ) {}

	FSAsyncControl_t	hControl;
	MDLHandle_t			hModel;
	MDLCacheDataType_t	type;
	int					iAnimBlock;
};

const intp NO_ASYNC = CUtlFixedLinkedList< AsyncInfo_t >::InvalidIndex();

//-------------------------------------

CUtlMap<int, intp> g_AsyncInfoMap( DefLessFunc( int ) );
CThreadFastMutex g_AsyncInfoMapMutex;

inline int MakeAsyncInfoKey( MDLHandle_t hModel, MDLCacheDataType_t type, int iAnimBlock )
{
	Assert( type <= 7 && iAnimBlock < 8*1024 );
	return ( ( ( (int)hModel) << 16 ) | ( (int)type << 13 ) | iAnimBlock );
}

inline intp GetAsyncInfoIndex( MDLHandle_t hModel, MDLCacheDataType_t type, int iAnimBlock = 0 )
{
	AUTO_LOCK( g_AsyncInfoMapMutex );
	int key = MakeAsyncInfoKey( hModel, type, iAnimBlock );
	int i = g_AsyncInfoMap.Find( key );
	if ( i == g_AsyncInfoMap.InvalidIndex() )
	{
		return NO_ASYNC;
	}
	return g_AsyncInfoMap[i];
}

inline intp SetAsyncInfoIndex( MDLHandle_t hModel, MDLCacheDataType_t type, int iAnimBlock, intp index )
{
	AUTO_LOCK( g_AsyncInfoMapMutex );
	Assert( index == NO_ASYNC || GetAsyncInfoIndex( hModel, type, iAnimBlock ) == NO_ASYNC );
	int key = MakeAsyncInfoKey( hModel, type, iAnimBlock );
	if ( index == NO_ASYNC )
	{
		g_AsyncInfoMap.Remove( key );
	}
	else
	{
		g_AsyncInfoMap.Insert( key, index );
	}

	return index;
}

inline intp SetAsyncInfoIndex( MDLHandle_t hModel, MDLCacheDataType_t type, intp index )
{
	return SetAsyncInfoIndex( hModel, type, 0, index );
}

//-----------------------------------------------------------------------------
// QUEUED LOADING
// Populates the cache by pushing expected MDL's (and all of their data).
// The Model cache i/o behavior is unchanged during gameplay, ideally the cache
// should yield miss free behaviour.
//-----------------------------------------------------------------------------

struct ModelParts_t
{
	enum BufferType_t
	{
		BUFFER_MDL = 0,
		BUFFER_VTX = 1,
		BUFFER_VVD = 2,
		BUFFER_PHY = 3,
		BUFFER_MAXPARTS,
	};

	ModelParts_t()
	{
		nLoadedParts = 0;
		nExpectedParts = 0;
		hMDL = MDLHANDLE_INVALID;
		hFileCache = 0;
		bHeaderLoaded = false;
		bMaterialsPending = false;
		bTexturesPending = false;
	}

	// thread safe, only one thread will get a positive result
	bool DoFinalProcessing()
	{
		// indicates that all buffers have arrived
		// when all parts are present, returns true ( guaranteed once ), and marked as completed
		return nLoadedParts.AssignIf( nExpectedParts, nExpectedParts | 0x80000000 );
	}

	CUtlBuffer		Buffers[BUFFER_MAXPARTS];
	MDLHandle_t		hMDL;

	// async material loading on PC
	FileCacheHandle_t hFileCache;
	bool			bHeaderLoaded;
	bool			bMaterialsPending;
	bool			bTexturesPending;
	CUtlVector< IMaterial* > Materials;

	// bit flags
	CInterlockedInt	nLoadedParts;
	int				nExpectedParts;

private:
	ModelParts_t(const ModelParts_t&); // no impl
	ModelParts_t& operator=(const ModelParts_t&); // no impl
};

struct CleanupModelParts_t
{
	FileCacheHandle_t hFileCache;
	CUtlVector< IMaterial* > Materials;
};

//-----------------------------------------------------------------------------
// Implementation of the simple studio data cache (no caching)
//-----------------------------------------------------------------------------
class CMDLCache : public CTier3AppSystem< IMDLCache >, public IStudioDataCache, public CDefaultDataCacheClient
{
	typedef CTier3AppSystem< IMDLCache > BaseClass;

public:
	CMDLCache();

	// Inherited from IAppSystem
	virtual bool Connect( CreateInterfaceFn factory );
	virtual void Disconnect();
	virtual void *QueryInterface( const char *pInterfaceName );
	virtual InitReturnVal_t Init();
	virtual void Shutdown();

	// Inherited from IStudioDataCache
	bool VerifyHeaders( studiohdr_t *pStudioHdr );
	vertexFileHeader_t *CacheVertexData( studiohdr_t *pStudioHdr );

	// Inherited from IMDLCache
	virtual MDLHandle_t FindMDL( const char *pMDLRelativePath );
	virtual int AddRef( MDLHandle_t handle );
	virtual int Release( MDLHandle_t handle );
	virtual int GetRef( MDLHandle_t handle );
	virtual void MarkAsLoaded(MDLHandle_t handle);

	virtual studiohdr_t *GetStudioHdr( MDLHandle_t handle );
	virtual studiohwdata_t *GetHardwareData( MDLHandle_t handle );
	virtual vcollide_t *GetVCollide( MDLHandle_t handle ) { return GetVCollideEx( handle, true); }
	virtual vcollide_t *GetVCollideEx( MDLHandle_t handle, bool synchronousLoad = true );
	virtual unsigned char *GetAnimBlock( MDLHandle_t handle, int nBlock );
	virtual virtualmodel_t *GetVirtualModel( MDLHandle_t handle );
	virtual virtualmodel_t *GetVirtualModelFast( const studiohdr_t *pStudioHdr, MDLHandle_t handle );
	virtual int GetAutoplayList( MDLHandle_t handle, unsigned short **pOut );
	virtual void TouchAllData( MDLHandle_t handle );
	virtual void SetUserData( MDLHandle_t handle, void* pData );
	virtual void *GetUserData( MDLHandle_t handle );
	virtual bool IsErrorModel( MDLHandle_t handle );
	virtual void SetCacheNotify( IMDLCacheNotify *pNotify );
	virtual vertexFileHeader_t *GetVertexData( MDLHandle_t handle );
	virtual void Flush( MDLCacheFlush_t nFlushFlags = MDLCACHE_FLUSH_ALL );
	virtual void Flush( MDLHandle_t handle, int nFlushFlags = MDLCACHE_FLUSH_ALL );
	virtual const char *GetModelName( MDLHandle_t handle );

	IDataCacheSection *GetCacheSection( MDLCacheDataType_t type )
	{
		switch ( type )
		{
		case MDLCACHE_STUDIOHWDATA:
		case MDLCACHE_VERTEXES:
			// meshes and vertexes are isolated to their own section
			return m_pMeshCacheSection;

		case MDLCACHE_ANIMBLOCK:
			// anim blocks have their own section
			return m_pAnimBlockCacheSection;

		default:
			// everybody else
			return m_pModelCacheSection;
		}
	}

	void *AllocData( MDLCacheDataType_t type, int size );
	void FreeData( MDLCacheDataType_t type, void *pData );
	void CacheData( DataCacheHandle_t *c, void *pData, int size, const char *name, MDLCacheDataType_t type, DataCacheClientID_t id = (DataCacheClientID_t)-1 );
	void *CheckData( DataCacheHandle_t c, MDLCacheDataType_t type );
	void *CheckDataNoTouch( DataCacheHandle_t c, MDLCacheDataType_t type );
	void UncacheData( DataCacheHandle_t c, MDLCacheDataType_t type, bool bLockedOk = false );

	void DisableAsync() { mod_load_mesh_async.SetValue( 0 ); mod_load_anims_async.SetValue( 0 ); }

	virtual void BeginLock();
	virtual void EndLock();
	virtual int *GetFrameUnlockCounterPtrOLD();
	virtual int *GetFrameUnlockCounterPtr( MDLCacheDataType_t type );

	virtual void FinishPendingLoads();

	// Task switch
	void ReleaseMaterialSystemObjects();
	void RestoreMaterialSystemObjects( int nChangeFlags );
	virtual bool GetVCollideSize( MDLHandle_t handle, int *pVCollideSize );

	virtual void BeginMapLoad();
	virtual void EndMapLoad();

	virtual void InitPreloadData( bool rebuild );
	virtual void ShutdownPreloadData();

	virtual bool IsDataLoaded( MDLHandle_t handle, MDLCacheDataType_t type );

	virtual studiohdr_t *LockStudioHdr( MDLHandle_t handle );
	virtual void UnlockStudioHdr( MDLHandle_t handle );

	virtual bool PreloadModel( MDLHandle_t handle );
	virtual void ResetErrorModelStatus( MDLHandle_t handle );

	virtual void MarkFrame();

	// Queued loading
	void ProcessQueuedData( ModelParts_t *pModelParts, bool bHeaderOnly = false );
	static void	QueuedLoaderCallback_MDL( void *pContext, void  *pContext2, const void *pData, int nSize, LoaderError_t loaderError );
	static void	ProcessDynamicLoad( ModelParts_t *pModelParts );
	static void CleanupDynamicLoad( CleanupModelParts_t *pCleanup );

private:
	// Inits, shuts downs studiodata_t
	void InitStudioData( MDLHandle_t handle );
	void ShutdownStudioData( MDLHandle_t handle );

	// Returns the *actual* name of the model (could be an error model if the requested model didn't load)
	const char *GetActualModelName( MDLHandle_t handle );

	// Constructs a filename based on a model handle
	void MakeFilename( MDLHandle_t handle, const char *pszExtension, char *pszFileName, int nMaxLength );

	// Inform filesystem that we unloaded a particular file
	void NotifyFileUnloaded( MDLHandle_t handle, const char *pszExtension );

	// Attempts to load a MDL file, validates that it's ok.
	bool ReadMDLFile( MDLHandle_t handle, const char *pMDLFileName, CUtlBuffer &buf );

	// Unserializes the VCollide file associated w/ models (the vphysics representation)
	void UnserializeVCollide( MDLHandle_t handle, bool synchronousLoad );

	// Destroys the VCollide associated w/ models
	void DestroyVCollide( MDLHandle_t handle );

	// Unserializes the MDL
	studiohdr_t *UnserializeMDL( MDLHandle_t handle, void *pData, int nDataSize, bool bDataValid );

	// Unserializes an animation block from disk
	unsigned char *UnserializeAnimBlock( MDLHandle_t handle, int nBlock );

	// Allocates/frees the anim blocks
	void AllocateAnimBlocks( studiodata_t *pStudioData, int nCount );
	void FreeAnimBlocks( MDLHandle_t handle );

	// Allocates/frees the virtual model
	void AllocateVirtualModel( MDLHandle_t handle );
	void FreeVirtualModel( MDLHandle_t handle );

	// Purpose: Pulls all submodels/.ani file models into the cache
	void UnserializeAllVirtualModelsAndAnimBlocks( MDLHandle_t handle );

	// Loads/unloads the static meshes
	bool LoadHardwareData( MDLHandle_t handle ); // returns false if not ready
	void UnloadHardwareData( MDLHandle_t handle, bool bCacheRemove = true, bool bLockedOk = false );

	// Allocates/frees autoplay sequence list
	void AllocateAutoplaySequences( studiodata_t *pStudioData, int nCount );
	void FreeAutoplaySequences( studiodata_t *pStudioData );

	FSAsyncStatus_t LoadData( const char *pszFilename, const char *pszPathID, bool bAsync, FSAsyncControl_t *pControl ) { return LoadData( pszFilename, pszPathID, NULL, 0, 0, bAsync, pControl ); }
	FSAsyncStatus_t LoadData( const char *pszFilename, const char *pszPathID, void *pDest, int nBytes, int nOffset, bool bAsync, FSAsyncControl_t *pControl );
	vertexFileHeader_t *LoadVertexData( studiohdr_t *pStudioHdr );
	vertexFileHeader_t *BuildAndCacheVertexData( studiohdr_t *pStudioHdr, vertexFileHeader_t *pRawVvdHdr  );
	bool BuildHardwareData( MDLHandle_t handle, studiodata_t *pStudioData, studiohdr_t *pStudioHdr, OptimizedModel::FileHeader_t *pVtxHdr );
	void ConvertFlexData( studiohdr_t *pStudioHdr );

	int ProcessPendingAsync( intp iAsync );
	void ProcessPendingAsyncs( MDLCacheDataType_t type = MDLCACHE_NONE );
	bool ClearAsync( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, bool bAbort = false );

	const char *GetVTXExtension();

	virtual bool HandleCacheNotification( const DataCacheNotification_t &notification  );
	virtual bool GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen  );

	virtual bool GetAsyncLoad( MDLCacheDataType_t type );
	virtual bool SetAsyncLoad( MDLCacheDataType_t type, bool bAsync );

	// Creates the 360 file if it doesn't exist or is out of date
	int UpdateOrCreate( studiohdr_t *pHdr, const char *pFilename, char *pX360Filename, int maxLen, const char *pPathID, bool bForce = false );

	// Attempts to read the platform native file - on 360 it can read and swap Win32 file as a fallback
	bool ReadFileNative( char *pFileName, const char *pPath, CUtlBuffer &buf, int nMaxBytes = 0, MDLCacheDataType_t type = MDLCACHE_NONE );

	// Creates a thin cache entry (to be used for model decals) from fat vertex data
	vertexFileHeader_t * CreateThinVertexes( vertexFileHeader_t * originalData, const studiohdr_t * pStudioHdr, int * cacheLength );

	// Processes raw data (from an I/O source) into the cache. Sets the cache state as expected for bad data.
	bool ProcessDataIntoCache( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, void *pData, int nDataSize, bool bDataValid );

	void BreakFrameLock( bool bModels = true, bool bMesh = true );
	void RestoreFrameLock();

private:
	IDataCacheSection *m_pModelCacheSection;
	IDataCacheSection *m_pMeshCacheSection;
	IDataCacheSection *m_pAnimBlockCacheSection;

	int m_nModelCacheFrameLocks;
	int m_nMeshCacheFrameLocks;

	CUtlDict< studiodata_t*, MDLHandle_t > m_MDLDict;

	IMDLCacheNotify *m_pCacheNotify;

	CUtlFixedLinkedList< AsyncInfo_t > m_PendingAsyncs;

	CThreadFastMutex m_QueuedLoadingMutex;
	CThreadFastMutex m_AsyncMutex;

	bool m_bLostVideoMemory : 1;
	bool m_bConnected : 1;
	bool m_bInitialized : 1;
};

//-----------------------------------------------------------------------------
// Singleton interface
//-----------------------------------------------------------------------------
static CMDLCache g_MDLCache;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMDLCache, IMDLCache, MDLCACHE_INTERFACE_VERSION, g_MDLCache );
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMDLCache, IStudioDataCache, STUDIO_DATA_CACHE_INTERFACE_VERSION, g_MDLCache );


//-----------------------------------------------------------------------------
// Task switch
//-----------------------------------------------------------------------------
static void ReleaseMaterialSystemObjects( )
{
	g_MDLCache.ReleaseMaterialSystemObjects();
}

static void RestoreMaterialSystemObjects( int nChangeFlags )
{
	g_MDLCache.RestoreMaterialSystemObjects( nChangeFlags );
}



//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CMDLCache::CMDLCache() : BaseClass( false )
{
	m_bLostVideoMemory = false;
	m_bConnected = false;
	m_bInitialized = false;
	m_pCacheNotify = NULL;
	m_pModelCacheSection = NULL;
	m_pMeshCacheSection = NULL;
	m_pAnimBlockCacheSection = NULL;
	m_nModelCacheFrameLocks = 0;
	m_nMeshCacheFrameLocks = 0;
}


//-----------------------------------------------------------------------------
// Connect, disconnect
//-----------------------------------------------------------------------------
bool CMDLCache::Connect( CreateInterfaceFn factory )
{
	// Connect can be called twice, because this inherits from 2 appsystems.
	if ( m_bConnected )
		return true;

	if ( !BaseClass::Connect( factory ) )
		return false;

	if ( !g_pMaterialSystemHardwareConfig || !g_pPhysicsCollision || !g_pStudioRender || !g_pMaterialSystem )
		return false;

	m_bConnected = true;
	if( g_pMaterialSystem )
	{
		g_pMaterialSystem->AddReleaseFunc( ::ReleaseMaterialSystemObjects );
		g_pMaterialSystem->AddRestoreFunc( ::RestoreMaterialSystemObjects );
	}

	return true;
}

void CMDLCache::Disconnect()
{
	if ( g_pMaterialSystem && m_bConnected )
	{
		g_pMaterialSystem->RemoveReleaseFunc( ::ReleaseMaterialSystemObjects );
		g_pMaterialSystem->RemoveRestoreFunc( ::RestoreMaterialSystemObjects );
		m_bConnected = false;
	}

	BaseClass::Disconnect();
}


//-----------------------------------------------------------------------------
// Query Interface
//-----------------------------------------------------------------------------
void *CMDLCache::QueryInterface( const char *pInterfaceName )
{
	if (!Q_strncmp(	pInterfaceName, STUDIO_DATA_CACHE_INTERFACE_VERSION, Q_strlen(STUDIO_DATA_CACHE_INTERFACE_VERSION) + 1))
		return (IStudioDataCache*)this;

	if (!Q_strncmp(	pInterfaceName, MDLCACHE_INTERFACE_VERSION, Q_strlen(MDLCACHE_INTERFACE_VERSION) + 1))
		return (IMDLCache*)this;

	return NULL;
}


//-----------------------------------------------------------------------------
// Init/Shutdown
//-----------------------------------------------------------------------------

#define MODEL_CACHE_MODEL_SECTION_NAME		"ModelData"
#define MODEL_CACHE_MESH_SECTION_NAME		"ModelMesh"
#define MODEL_CACHE_ANIMBLOCK_SECTION_NAME	"AnimBlock"

// #define ENABLE_CACHE_WATCH 1

#if defined( ENABLE_CACHE_WATCH )
static ConVar cache_watch( "cache_watch", "", 0 );

static void CacheLog( const char *fileName, const char *accessType )
{
	if ( Q_stristr( fileName, cache_watch.GetString() ) )
	{
		Msg( "%s access to %s\n", accessType, fileName );
	}
}
#endif

InitReturnVal_t CMDLCache::Init()
{
	// Can be called twice since it inherits from 2 appsystems
	if ( m_bInitialized )
		return INIT_OK;

	InitReturnVal_t nRetVal = BaseClass::Init();
	if ( nRetVal != INIT_OK )
		return nRetVal;

	if ( !m_pModelCacheSection )
	{
		m_pModelCacheSection = g_pDataCache->AddSection( this, MODEL_CACHE_MODEL_SECTION_NAME );
	}

	if ( !m_pMeshCacheSection )
	{
		unsigned int meshLimit = (unsigned)-1;
		DataCacheLimits_t limits( meshLimit, (unsigned)-1, 0, 0 );
		m_pMeshCacheSection = g_pDataCache->AddSection( this, MODEL_CACHE_MESH_SECTION_NAME, limits );
	}

	if ( !m_pAnimBlockCacheSection )
	{
		// 360 tuned to worst case, ep_outland_12a, less than 6 MB is not a viable working set
		unsigned int animBlockLimit = IsX360() ? 6*1024*1024 : (unsigned)-1;
		DataCacheLimits_t limits( animBlockLimit, (unsigned)-1, 0, 0 );
		m_pAnimBlockCacheSection = g_pDataCache->AddSection( this, MODEL_CACHE_ANIMBLOCK_SECTION_NAME, limits );
	}

	if ( IsX360() )
	{
		// By default, source data is assumed to be non-native to the 360.
		StudioByteSwap::ActivateByteSwapping( true );
		StudioByteSwap::SetCollisionInterface( g_pPhysicsCollision );
	}
	m_bLostVideoMemory = false;
	m_bInitialized = true;

#if defined( ENABLE_CACHE_WATCH )
	g_pFullFileSystem->AddLoggingFunc( &CacheLog );
#endif

	return INIT_OK;
}

void CMDLCache::Shutdown()
{
	if ( !m_bInitialized )
		return;
#if defined( ENABLE_CACHE_WATCH )
	g_pFullFileSystem->RemoveLoggingFunc( CacheLog );
#endif
	m_bInitialized = false;

	if ( m_pModelCacheSection || m_pMeshCacheSection )
	{
		// Free all MDLs that haven't been cleaned up
		MDLHandle_t i = m_MDLDict.First();
		while ( i != m_MDLDict.InvalidIndex() )
		{
			ShutdownStudioData( i );
			i = m_MDLDict.Next( i );
		}

		m_MDLDict.Purge();

		if ( m_pModelCacheSection )
		{
			g_pDataCache->RemoveSection( MODEL_CACHE_MODEL_SECTION_NAME );
			m_pModelCacheSection = NULL;
		}
		if ( m_pMeshCacheSection )
		{
			g_pDataCache->RemoveSection( MODEL_CACHE_MESH_SECTION_NAME );
			m_pMeshCacheSection = NULL;
		}
	}

	if ( m_pAnimBlockCacheSection )
	{
		g_pDataCache->RemoveSection( MODEL_CACHE_ANIMBLOCK_SECTION_NAME );
		m_pAnimBlockCacheSection = NULL;
	}

	BaseClass::Shutdown();
}


//-----------------------------------------------------------------------------
// Flushes an MDLHandle_t
//-----------------------------------------------------------------------------
void CMDLCache::Flush( MDLHandle_t handle, int nFlushFlags )
{
	studiodata_t *pStudioData = m_MDLDict[handle];
	Assert( pStudioData != NULL );

	bool bIgnoreLock = ( nFlushFlags & MDLCACHE_FLUSH_IGNORELOCK ) != 0;

	// release the hardware portion
	if ( nFlushFlags & MDLCACHE_FLUSH_STUDIOHWDATA )
	{
		if ( ClearAsync( handle, MDLCACHE_STUDIOHWDATA, 0, true ) )
		{
			m_pMeshCacheSection->Unlock( pStudioData->m_VertexCache );
		}
		UnloadHardwareData( handle, true, bIgnoreLock );
	}

	// free collision
	if ( nFlushFlags & MDLCACHE_FLUSH_VCOLLIDE )
	{
		DestroyVCollide( handle );
	}

	// Free animations
	if ( nFlushFlags & MDLCACHE_FLUSH_VIRTUALMODEL )
	{
		FreeVirtualModel( handle );
	}

	if ( nFlushFlags & MDLCACHE_FLUSH_ANIMBLOCK )
	{
		FreeAnimBlocks( handle );
	}

	if ( nFlushFlags & MDLCACHE_FLUSH_AUTOPLAY )
	{
		// Free autoplay sequences
		FreeAutoplaySequences( pStudioData );
	}

	if ( nFlushFlags & MDLCACHE_FLUSH_STUDIOHDR )
	{
		MdlCacheMsg( "MDLCache: Free studiohdr %s\n", GetModelName( handle ) );

		if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL )
		{
			GetCacheSection( MDLCACHE_STUDIOHDR )->Unlock( pStudioData->m_MDLCache );
			pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_LOCKED_MDL;
		}
		UncacheData( pStudioData->m_MDLCache, MDLCACHE_STUDIOHDR, bIgnoreLock );
		pStudioData->m_MDLCache = NULL;
	}

	if ( nFlushFlags & MDLCACHE_FLUSH_VERTEXES )
	{
		MdlCacheMsg( "MDLCache: Free VVD %s\n", GetModelName( handle ) );

		ClearAsync( handle, MDLCACHE_VERTEXES, 0, true );

		UncacheData( pStudioData->m_VertexCache, MDLCACHE_VERTEXES, bIgnoreLock );
		pStudioData->m_VertexCache = NULL;
	}

	// Now check whatever files are not loaded, make sure file system knows
	// that we don't have them loaded.
	if ( !IsDataLoaded( handle, MDLCACHE_STUDIOHDR ) )
		NotifyFileUnloaded( handle, ".mdl" );

	if ( !IsDataLoaded( handle, MDLCACHE_STUDIOHWDATA ) )
		NotifyFileUnloaded( handle, GetVTXExtension() );

	if ( !IsDataLoaded( handle, MDLCACHE_VERTEXES ) )
		NotifyFileUnloaded( handle, ".vvd" );

	if ( !IsDataLoaded( handle, MDLCACHE_VCOLLIDE ) )
		NotifyFileUnloaded( handle, ".phy" );
}


//-----------------------------------------------------------------------------
// Inits, shuts downs studiodata_t
//-----------------------------------------------------------------------------
void CMDLCache::InitStudioData( MDLHandle_t handle )
{
	Assert( m_MDLDict[handle] == NULL );

	studiodata_t *pStudioData = new studiodata_t;
	m_MDLDict[handle] = pStudioData;
	memset( pStudioData, 0, sizeof( studiodata_t ) );
}

void CMDLCache::ShutdownStudioData( MDLHandle_t handle )
{
	Flush( handle );

	studiodata_t *pStudioData = m_MDLDict[handle];
	Assert( pStudioData != NULL );
	delete pStudioData;
	m_MDLDict[handle] = NULL;
}


//-----------------------------------------------------------------------------
// Sets the cache notify
//-----------------------------------------------------------------------------
void CMDLCache::SetCacheNotify( IMDLCacheNotify *pNotify )
{
	m_pCacheNotify = pNotify;
}


//-----------------------------------------------------------------------------
// Returns the name of the model
//-----------------------------------------------------------------------------
const char *CMDLCache::GetModelName( MDLHandle_t handle )
{
	if ( handle == MDLHANDLE_INVALID  )
		return ERROR_MODEL;

	return m_MDLDict.GetElementName( handle );
}


//-----------------------------------------------------------------------------
// Returns the *actual* name of the model (could be an error model)
//-----------------------------------------------------------------------------
const char *CMDLCache::GetActualModelName( MDLHandle_t handle )
{
	if ( handle == MDLHANDLE_INVALID )
		return ERROR_MODEL;

	if ( m_MDLDict[handle]->m_nFlags & STUDIODATA_ERROR_MODEL )
		return ERROR_MODEL;

	return m_MDLDict.GetElementName( handle );
}


//-----------------------------------------------------------------------------
// Constructs a filename based on a model handle
//-----------------------------------------------------------------------------
void CMDLCache::MakeFilename( MDLHandle_t handle, const char *pszExtension, char *pszFileName, int nMaxLength )
{
	Q_strncpy( pszFileName, GetActualModelName( handle ), nMaxLength );
	Q_SetExtension( pszFileName, pszExtension, nMaxLength );
	Q_FixSlashes( pszFileName );
#ifdef POSIX
	Q_strlower( pszFileName );
#endif
}

//-----------------------------------------------------------------------------
void CMDLCache::NotifyFileUnloaded( MDLHandle_t handle, const char *pszExtension )
{
	if ( handle == MDLHANDLE_INVALID )
		return;
	if ( !m_MDLDict.IsValidIndex( handle ) )
		return;

	char szFilename[MAX_PATH];
	V_strcpy_safe( szFilename, m_MDLDict.GetElementName( handle ) );
	V_SetExtension( szFilename, pszExtension, sizeof(szFilename) );
	V_FixSlashes( szFilename );
	g_pFullFileSystem->NotifyFileUnloaded( szFilename, "game" );
}


//-----------------------------------------------------------------------------
// Finds an MDL
//-----------------------------------------------------------------------------
MDLHandle_t CMDLCache::FindMDL( const char *pMDLRelativePath )
{
	// can't trust provided path
	// ensure provided path correctly resolves (Dictionary is case-insensitive)
	char szFixedName[MAX_PATH];
	V_strncpy( szFixedName, pMDLRelativePath, sizeof( szFixedName ) );
	V_RemoveDotSlashes( szFixedName, '/' );

	MDLHandle_t handle = m_MDLDict.Find( szFixedName );
	if ( handle == m_MDLDict.InvalidIndex() )
	{
		handle = m_MDLDict.Insert( szFixedName, NULL );
		InitStudioData( handle );
	}

	AddRef( handle );
	return handle;
}

//-----------------------------------------------------------------------------
// Reference counting
//-----------------------------------------------------------------------------
int CMDLCache::AddRef( MDLHandle_t handle )
{
	return ++m_MDLDict[handle]->m_nRefCount;
}

int CMDLCache::Release( MDLHandle_t handle )
{
	// Deal with shutdown order issues (i.e. datamodel shutting down after mdlcache)
	if ( !m_bInitialized )
		return 0;

	// NOTE: It can be null during shutdown because multiple studiomdls
	// could be referencing the same virtual model
	if ( !m_MDLDict[handle] )
		return 0;

	Assert( m_MDLDict[handle]->m_nRefCount > 0 );

	int nRefCount = --m_MDLDict[handle]->m_nRefCount;
	if ( nRefCount <= 0 )
	{
		ShutdownStudioData( handle );
		m_MDLDict.RemoveAt( handle );
	}

	return nRefCount;
}

int CMDLCache::GetRef( MDLHandle_t handle )
{
	if ( !m_bInitialized )
		return 0;

	if ( !m_MDLDict[handle] )
		return 0;

	return m_MDLDict[handle]->m_nRefCount;
}

//-----------------------------------------------------------------------------
// Unserializes the PHY file associated w/ models (the vphysics representation)
//-----------------------------------------------------------------------------
void CMDLCache::UnserializeVCollide( MDLHandle_t handle, bool synchronousLoad )
{
	VPROF( "CMDLCache::UnserializeVCollide" );

	// FIXME: Should the vcollde be played into cacheable memory?
	studiodata_t *pStudioData = m_MDLDict[handle];

	intp iAsync = GetAsyncInfoIndex( handle, MDLCACHE_VCOLLIDE );

	if ( iAsync == NO_ASYNC )
	{
		// clear existing data
		pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_VCOLLISION_LOADED;
		memset( &pStudioData->m_VCollisionData, 0, sizeof( pStudioData->m_VCollisionData ) );

#if 0
		// FIXME:  ywb
		// If we don't ask for the virtual model to load, then we can get a hitch later on after startup
		// Should we async load the sub .mdls during startup assuming they'll all be resident by the time the level can actually
		//  start drawing?
		if ( pStudioData->m_pVirtualModel || synchronousLoad )
#endif
		{
			virtualmodel_t *pVirtualModel = GetVirtualModel( handle );
			if ( pVirtualModel )
			{
				for ( int i = 1; i < pVirtualModel->m_group.Count(); i++ )
				{
					MDLHandle_t sharedHandle = VoidPtrToMDLHandle(pVirtualModel->m_group[i].cache);
					studiodata_t *pData = m_MDLDict[sharedHandle];
					if ( !(pData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED) )
					{
						UnserializeVCollide( sharedHandle, synchronousLoad );
					}
					if ( pData->m_VCollisionData.solidCount > 0 )
					{
						pStudioData->m_VCollisionData = pData->m_VCollisionData;
						pStudioData->m_nFlags |= STUDIODATA_FLAGS_VCOLLISION_SHARED;
						return;
					}
				}
			}
		}

		char pFileName[MAX_PATH];
		MakeFilename( handle, ".phy", pFileName, sizeof(pFileName) );
		if ( IsX360() )
		{
			char pX360Filename[MAX_PATH];
			UpdateOrCreate( NULL, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" );
			Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) );
		}

		bool bAsyncLoad = mod_load_vcollide_async.GetBool() && !synchronousLoad;

		MdlCacheMsg( "MDLCache: %s load vcollide %s\n", bAsyncLoad ? "Async" : "Sync", GetModelName( handle ) );

		AsyncInfo_t info;
		if ( IsDebug() )
		{
			memset( &info, 0xdd, sizeof( AsyncInfo_t ) );
		}
		info.hModel = handle;
		info.type = MDLCACHE_VCOLLIDE;
		info.iAnimBlock = 0;
		info.hControl = NULL;
		LoadData( pFileName, "GAME", bAsyncLoad, &info.hControl );
		{
			AUTO_LOCK( m_AsyncMutex );
			iAsync = SetAsyncInfoIndex( handle, MDLCACHE_VCOLLIDE, m_PendingAsyncs.AddToTail( info ) );
		}
	}
	else if ( synchronousLoad )
	{
		AsyncInfo_t *pInfo;
		{
			AUTO_LOCK( m_AsyncMutex );
			pInfo = &m_PendingAsyncs[iAsync];
		}
		if ( pInfo->hControl )
		{
			g_pFullFileSystem->AsyncFinish( pInfo->hControl, true );
		}
	}

	ProcessPendingAsync( iAsync );
}


//-----------------------------------------------------------------------------
// Free model's collision data
//-----------------------------------------------------------------------------
void CMDLCache::DestroyVCollide( MDLHandle_t handle )
{
	studiodata_t *pStudioData = m_MDLDict[handle];

	if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_SHARED )
		return;

	if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED )
	{
		pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_VCOLLISION_LOADED;
		if ( pStudioData->m_VCollisionData.solidCount )
		{
			if ( m_pCacheNotify )
			{
				m_pCacheNotify->OnDataUnloaded( MDLCACHE_VCOLLIDE, handle );
			}

			MdlCacheMsg("MDLCache: Unload vcollide %s\n", GetModelName( handle ) );

			g_pPhysicsCollision->VCollideUnload( &pStudioData->m_VCollisionData );
		}
	}
}


//-----------------------------------------------------------------------------
// Unserializes the PHY file associated w/ models (the vphysics representation)
//-----------------------------------------------------------------------------
vcollide_t *CMDLCache::GetVCollideEx( MDLHandle_t handle, bool synchronousLoad /*= true*/ )
{
	if ( mod_test_not_available.GetBool() )
		return NULL;

	if ( handle == MDLHANDLE_INVALID )
		return NULL;

	studiodata_t *pStudioData = m_MDLDict[handle];

	if ( ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED ) == 0 )
	{
		UnserializeVCollide( handle, synchronousLoad );
	}

	// We've loaded an empty collision file or no file was found, so return NULL
	if ( !pStudioData->m_VCollisionData.solidCount )
		return NULL;

	return &pStudioData->m_VCollisionData;
}


bool CMDLCache::GetVCollideSize( MDLHandle_t handle, int *pVCollideSize )
{
	*pVCollideSize = 0;

	studiodata_t *pStudioData = m_MDLDict[handle];
	if ( ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED ) == 0 )
		return false;

	vcollide_t *pCollide = &pStudioData->m_VCollisionData;
	for ( int j = 0; j < pCollide->solidCount; j++ )
	{
		*pVCollideSize += g_pPhysicsCollision->CollideSize( pCollide->solids[j] );
	}
	*pVCollideSize += pCollide->descSize;
	return true;
}

//-----------------------------------------------------------------------------
// Allocates/frees the anim blocks
//-----------------------------------------------------------------------------
void CMDLCache::AllocateAnimBlocks( studiodata_t *pStudioData, int nCount )
{
	Assert( pStudioData->m_pAnimBlock == NULL );

	pStudioData->m_nAnimBlockCount = nCount;
	pStudioData->m_pAnimBlock = new DataCacheHandle_t[pStudioData->m_nAnimBlockCount];

	memset( pStudioData->m_pAnimBlock, 0, sizeof(DataCacheHandle_t) * pStudioData->m_nAnimBlockCount );

	pStudioData->m_iFakeAnimBlockStall = new unsigned int [pStudioData->m_nAnimBlockCount];
	memset( pStudioData->m_iFakeAnimBlockStall, 0, sizeof( unsigned int ) * pStudioData->m_nAnimBlockCount );
}

void CMDLCache::FreeAnimBlocks( MDLHandle_t handle )
{
	studiodata_t *pStudioData = m_MDLDict[handle];

	if ( pStudioData->m_pAnimBlock )
	{
		for (int i = 0; i < pStudioData->m_nAnimBlockCount; ++i )
		{
			MdlCacheMsg( "MDLCache: Free Anim block: %d\n", i );

			ClearAsync( handle, MDLCACHE_ANIMBLOCK, i, true );
			if ( pStudioData->m_pAnimBlock[i] )
			{
				UncacheData( pStudioData->m_pAnimBlock[i], MDLCACHE_ANIMBLOCK, true );
			}
		}

		delete[] pStudioData->m_pAnimBlock;
		pStudioData->m_pAnimBlock = NULL;

		delete[] pStudioData->m_iFakeAnimBlockStall;
		pStudioData->m_iFakeAnimBlockStall = NULL;
	}

	pStudioData->m_nAnimBlockCount = 0;
}


//-----------------------------------------------------------------------------
// Unserializes an animation block from disk
//-----------------------------------------------------------------------------
unsigned char *CMDLCache::UnserializeAnimBlock( MDLHandle_t handle, int nBlock )
{
	VPROF( "CMDLCache::UnserializeAnimBlock" );

	if ( IsX360() && g_pQueuedLoader->IsMapLoading() )
	{
		// anim block i/o is not allowed at this stage
		return NULL;
	}

	// Block 0 is never used!!!
	Assert( nBlock > 0 );

	studiodata_t *pStudioData = m_MDLDict[handle];

	intp iAsync = GetAsyncInfoIndex( handle, MDLCACHE_ANIMBLOCK, nBlock );

	if ( iAsync == NO_ASYNC )
	{
		studiohdr_t *pStudioHdr = GetStudioHdr( handle );

		// FIXME: For consistency, the block name maybe shouldn't have 'model' in it.
		char const *pModelName = pStudioHdr->pszAnimBlockName();
		mstudioanimblock_t *pBlock = pStudioHdr->pAnimBlock( nBlock );
		int nSize = pBlock->dataend - pBlock->datastart;
		if ( nSize == 0 )
			return NULL;

		// allocate space in the cache
		pStudioData->m_pAnimBlock[nBlock] = NULL;

		char pFileName[MAX_PATH];
		Q_strncpy( pFileName, pModelName, sizeof(pFileName) );
		Q_FixSlashes( pFileName );
#ifdef POSIX
		Q_strlower( pFileName );
#endif
		if ( IsX360() )
		{
			char pX360Filename[MAX_PATH];
			UpdateOrCreate( pStudioHdr, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" );
			Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) );
		}

		MdlCacheMsg( "MDLCache: Begin load Anim Block %s (block %i)\n", GetModelName( handle ), nBlock );

		AsyncInfo_t info;
		if ( IsDebug() )
		{
			memset( &info, 0xdd, sizeof( AsyncInfo_t ) );
		}
		info.hModel = handle;
		info.type = MDLCACHE_ANIMBLOCK;
		info.iAnimBlock = nBlock;
		info.hControl = NULL;
		LoadData( pFileName, "GAME", NULL, nSize, pBlock->datastart, mod_load_anims_async.GetBool(), &info.hControl );
		{
			AUTO_LOCK( m_AsyncMutex );
			iAsync = SetAsyncInfoIndex( handle, MDLCACHE_ANIMBLOCK, nBlock, m_PendingAsyncs.AddToTail( info ) );
		}
	}

	ProcessPendingAsync( iAsync );

	return ( unsigned char * )CheckData( pStudioData->m_pAnimBlock[nBlock], MDLCACHE_ANIMBLOCK );
}

//-----------------------------------------------------------------------------
// Gets at an animation block associated with an MDL
//-----------------------------------------------------------------------------
unsigned char *CMDLCache::GetAnimBlock( MDLHandle_t handle, int nBlock )
{
	if ( mod_test_not_available.GetBool() )
		return NULL;

	if ( handle == MDLHANDLE_INVALID )
		return NULL;

	// Allocate animation blocks if we don't have them yet
	studiodata_t *pStudioData = m_MDLDict[handle];
	if ( pStudioData->m_pAnimBlock == NULL )
	{
		studiohdr_t *pStudioHdr = GetStudioHdr( handle );
		AllocateAnimBlocks( pStudioData, pStudioHdr->numanimblocks );
	}

	// check for request being in range
	if ( nBlock < 0 || nBlock >= pStudioData->m_nAnimBlockCount)
		return NULL;

	// Check the cache to see if the animation is in memory
	unsigned char *pData = ( unsigned char * )CheckData( pStudioData->m_pAnimBlock[nBlock], MDLCACHE_ANIMBLOCK );
	if ( !pData )
	{
		pStudioData->m_pAnimBlock[nBlock] = NULL;

		// It's not in memory, read it off of disk
		pData = UnserializeAnimBlock( handle, nBlock );
	}

	if (mod_load_fakestall.GetInt())
	{
		unsigned int t = Plat_MSTime();
		if (pStudioData->m_iFakeAnimBlockStall[nBlock] == 0 || pStudioData->m_iFakeAnimBlockStall[nBlock] > t)
		{
			pStudioData->m_iFakeAnimBlockStall[nBlock] = t;
		}

		if ((int)(t - pStudioData->m_iFakeAnimBlockStall[nBlock]) < mod_load_fakestall.GetInt())
		{
			return NULL;
		}
	}
	return pData;
}


//-----------------------------------------------------------------------------
// Allocates/frees autoplay sequence list
//-----------------------------------------------------------------------------
void CMDLCache::AllocateAutoplaySequences( studiodata_t *pStudioData, int nCount )
{
	FreeAutoplaySequences( pStudioData );

	pStudioData->m_nAutoplaySequenceCount = nCount;
	pStudioData->m_pAutoplaySequenceList = new unsigned short[nCount];
}

void CMDLCache::FreeAutoplaySequences( studiodata_t *pStudioData )
{
	if ( pStudioData->m_pAutoplaySequenceList )
	{
		delete[] pStudioData->m_pAutoplaySequenceList;
		pStudioData->m_pAutoplaySequenceList = NULL;
	}

	pStudioData->m_nAutoplaySequenceCount = 0;
}


//-----------------------------------------------------------------------------
// Gets the autoplay list
//-----------------------------------------------------------------------------
int CMDLCache::GetAutoplayList( MDLHandle_t handle, unsigned short **pAutoplayList )
{
	if ( pAutoplayList )
	{
		*pAutoplayList = NULL;
	}

	if ( handle == MDLHANDLE_INVALID )
		return 0;

	virtualmodel_t *pVirtualModel = GetVirtualModel( handle );
	if ( pVirtualModel )
	{
		if ( pAutoplayList && pVirtualModel->m_autoplaySequences.Count() )
		{
			*pAutoplayList = pVirtualModel->m_autoplaySequences.Base();
		}
		return pVirtualModel->m_autoplaySequences.Count();
	}

	// FIXME: Should we cache autoplay info here on demand instead of in unserializeMDL?
	studiodata_t *pStudioData = m_MDLDict[handle];
	if ( pAutoplayList )
	{
		*pAutoplayList = pStudioData->m_pAutoplaySequenceList;
	}

	return pStudioData->m_nAutoplaySequenceCount;
}


//-----------------------------------------------------------------------------
// Allocates/frees the virtual model
//-----------------------------------------------------------------------------
void CMDLCache::AllocateVirtualModel( MDLHandle_t handle )
{
	studiodata_t *pStudioData = m_MDLDict[handle];
	Assert( pStudioData->m_pVirtualModel == NULL );
	pStudioData->m_pVirtualModel = new virtualmodel_t;

	// FIXME: The old code slammed these; could have leaked memory?
	Assert( pStudioData->m_nAnimBlockCount == 0 );
	Assert( pStudioData->m_pAnimBlock == NULL );
}

void CMDLCache::FreeVirtualModel( MDLHandle_t handle )
{
	studiodata_t *pStudioData = m_MDLDict[handle];
	if ( pStudioData && pStudioData->m_pVirtualModel )
	{
		int nGroupCount = pStudioData->m_pVirtualModel->m_group.Count();
		Assert( (nGroupCount >= 1) && pStudioData->m_pVirtualModel->m_group[0].cache == MDLHandleToVirtual(handle) );

		// NOTE: Start at *1* here because the 0th element contains a reference to *this* handle
		for ( int i = 1; i < nGroupCount; ++i )
		{
			MDLHandle_t h = VoidPtrToMDLHandle( pStudioData->m_pVirtualModel->m_group[i].cache );
			FreeVirtualModel( h );
			Release( h );
		}

		delete pStudioData->m_pVirtualModel;
		pStudioData->m_pVirtualModel = NULL;
	}
}


//-----------------------------------------------------------------------------
// Returns the virtual model
//-----------------------------------------------------------------------------
virtualmodel_t *CMDLCache::GetVirtualModel( MDLHandle_t handle )
{
	if ( mod_test_not_available.GetBool() )
		return NULL;

	if ( handle == MDLHANDLE_INVALID )
		return NULL;

	studiohdr_t *pStudioHdr = GetStudioHdr( handle );

	if ( pStudioHdr == NULL )
		return NULL;

	return GetVirtualModelFast( pStudioHdr, handle );
}

virtualmodel_t *CMDLCache::GetVirtualModelFast( const studiohdr_t *pStudioHdr, MDLHandle_t handle )
{
	if (pStudioHdr->numincludemodels == 0)
		return NULL;

	studiodata_t *pStudioData = m_MDLDict[handle];
	if ( !pStudioData )
		return NULL;
	
	if ( !pStudioData->m_pVirtualModel )
	{
		DevMsg( 2, "Loading virtual model for %s\n", pStudioHdr->pszName() );

		CMDLCacheCriticalSection criticalSection( this );

		AllocateVirtualModel( handle );

		// Group has to be zero to ensure refcounting is correct
		int nGroup = pStudioData->m_pVirtualModel->m_group.AddToTail( );
		Assert( nGroup == 0 );
		pStudioData->m_pVirtualModel->m_group[nGroup].cache = MDLHandleToVirtual(handle);

		// Add all dependent data
		pStudioData->m_pVirtualModel->AppendModels( 0, pStudioHdr );
	}

	return pStudioData->m_pVirtualModel;
}

//-----------------------------------------------------------------------------
// Purpose: Pulls all submodels/.ani file models into the cache
// to avoid runtime hitches and load animations at load time, set mod_forcedata to be 1
//-----------------------------------------------------------------------------
void CMDLCache::UnserializeAllVirtualModelsAndAnimBlocks( MDLHandle_t handle )
{
	if ( handle == MDLHANDLE_INVALID )
		return;

	// might be re-loading, discard old virtualmodel to force rebuild
	// unfortunately, the virtualmodel does build data into the cacheable studiohdr
	FreeVirtualModel( handle );

	if ( IsX360() && g_pQueuedLoader->IsMapLoading() )
	{
		// queued loading has to do it
		return;
	}

	// don't load the submodel data
	if ( !mod_forcedata.GetBool() )
		return;

	// if not present, will instance and load the submodels
	GetVirtualModel( handle );

	if ( IsX360() )
	{
		// 360 does not drive the anims into its small cache section
		return;
	}

	// Note that the animblocks start at 1!!!
	studiohdr_t *pStudioHdr = GetStudioHdr( handle );
	for ( int i = 1 ; i < (int)pStudioHdr->numanimblocks; ++i )
	{
		GetAnimBlock( handle, i );
	}

	ProcessPendingAsyncs( MDLCACHE_ANIMBLOCK );
}


//-----------------------------------------------------------------------------
// Loads the static meshes
//-----------------------------------------------------------------------------
bool CMDLCache::LoadHardwareData( MDLHandle_t handle )
{
	Assert( handle != MDLHANDLE_INVALID );

	// Don't try to load VTX files if we don't have focus...
	if ( m_bLostVideoMemory )
		return false;

	studiodata_t *pStudioData = m_MDLDict[handle];

	CMDLCacheCriticalSection criticalSection( this );

	// Load up the model
	studiohdr_t *pStudioHdr = GetStudioHdr( handle );
	if ( !pStudioHdr || !pStudioHdr->numbodyparts )
	{
		pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH;
		return true;
	}

	if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH )
	{
		return false;
	}

	if ( LogMdlCache() &&
		 GetAsyncInfoIndex( handle, MDLCACHE_STUDIOHWDATA ) == NO_ASYNC &&
		 GetAsyncInfoIndex( handle, MDLCACHE_VERTEXES ) == NO_ASYNC )
	{
		MdlCacheMsg( "MDLCache: Begin load studiomdl %s\n", GetModelName( handle ) );
	}

	// Vertex data is required to call LoadModel(), so make sure that's ready
	if ( !GetVertexData( handle ) )
	{
		if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_VERTEX_DATA )
		{
			pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH;
		}
		return false;
	}

	intp iAsync = GetAsyncInfoIndex( handle, MDLCACHE_STUDIOHWDATA );

	if ( iAsync == NO_ASYNC )
	{
		m_pMeshCacheSection->Lock( pStudioData->m_VertexCache );

		// load and persist the vtx file
		// use model name for correct path
		char pFileName[MAX_PATH];
		MakeFilename( handle, GetVTXExtension(), pFileName, sizeof(pFileName) );
		if ( IsX360() )
		{
			char pX360Filename[MAX_PATH];
			UpdateOrCreate( pStudioHdr, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" );
			Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) );
		}

		MdlCacheMsg("MDLCache: Begin load VTX %s\n", GetModelName( handle ) );

		AsyncInfo_t info;
		if ( IsDebug() )
		{
			memset( &info, 0xdd, sizeof( AsyncInfo_t ) );
		}
		info.hModel = handle;
		info.type = MDLCACHE_STUDIOHWDATA;
		info.iAnimBlock = 0;
		info.hControl = NULL;
		LoadData( pFileName, "GAME", mod_load_mesh_async.GetBool(), &info.hControl );
		{
			AUTO_LOCK( m_AsyncMutex );
			iAsync = SetAsyncInfoIndex( handle, MDLCACHE_STUDIOHWDATA, m_PendingAsyncs.AddToTail( info ) );
		}
	}

	if ( ProcessPendingAsync( iAsync ) > 0 )
	{
		if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH )
		{
			return false;
		}

		return ( pStudioData->m_HardwareData.m_NumStudioMeshes != 0 );
	}

	return false;
}

void CMDLCache::ConvertFlexData( studiohdr_t *pStudioHdr )
{
	float flVertAnimFixedPointScale = pStudioHdr->VertAnimFixedPointScale();

	for ( int i = 0; i < pStudioHdr->numbodyparts; i++ )
	{
		mstudiobodyparts_t *pBody = pStudioHdr->pBodypart( i );
		for ( int j = 0; j < pBody->nummodels; j++ )
		{
			mstudiomodel_t *pModel = pBody->pModel( j );
			for ( int k = 0; k < pModel->nummeshes; k++ )
			{
				mstudiomesh_t *pMesh = pModel->pMesh( k );
				for ( int l = 0; l < pMesh->numflexes; l++ )
				{
					mstudioflex_t *pFlex = pMesh->pFlex( l );
					bool bIsWrinkleAnim = ( pFlex->vertanimtype == STUDIO_VERT_ANIM_WRINKLE );
					for ( int m = 0; m < pFlex->numverts; m++ )
					{
						mstudiovertanim_t *pVAnim = bIsWrinkleAnim ?
							pFlex->pVertanimWrinkle( m ) : pFlex->pVertanim( m );
						pVAnim->ConvertToFixed( flVertAnimFixedPointScale );
					}
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool CMDLCache::BuildHardwareData( MDLHandle_t handle, studiodata_t *pStudioData, studiohdr_t *pStudioHdr, OptimizedModel::FileHeader_t *pVtxHdr )
{
	if ( pVtxHdr )
	{
		MdlCacheMsg("MDLCache: Alloc VTX %s\n", pStudioHdr->pszName() );

		// check header
		if ( pVtxHdr->version != OPTIMIZED_MODEL_FILE_VERSION )
		{
			Warning( "Error Index File for '%s' version %d should be %d\n", pStudioHdr->pszName(), pVtxHdr->version, OPTIMIZED_MODEL_FILE_VERSION );
			pVtxHdr = NULL;
		}
		else if ( pVtxHdr->checkSum != pStudioHdr->checksum )
		{
			Warning( "Error Index File for '%s' checksum %d should be %d\n", pStudioHdr->pszName(), pVtxHdr->checkSum, pStudioHdr->checksum );
			pVtxHdr = NULL;
		}
	}

	if ( !pVtxHdr )
	{
		pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH;
		return false;
	}

	CTempAllocHelper pOriginalData;
	if ( IsX360() )
	{
		unsigned char *pInputData = (unsigned char *)pVtxHdr + sizeof( OptimizedModel::FileHeader_t );
		if ( CLZMA::IsCompressed( pInputData ) )
		{
			// vtx arrives compressed, decode and cache the results
			unsigned int nOriginalSize = CLZMA::GetActualSize( pInputData );
			pOriginalData.Alloc( sizeof( OptimizedModel::FileHeader_t ) + nOriginalSize );
			V_memcpy( pOriginalData.Get(), pVtxHdr, sizeof( OptimizedModel::FileHeader_t ) );
			unsigned int nOutputSize = CLZMA::Uncompress( pInputData, sizeof( OptimizedModel::FileHeader_t ) + (unsigned char *)pOriginalData.Get() );
			if ( nOutputSize != nOriginalSize )
			{
				// decoder failure
				return false;
			}

			pVtxHdr = (OptimizedModel::FileHeader_t *)pOriginalData.Get();
		}
	}

	MdlCacheMsg( "MDLCache: Load studiomdl %s\n", pStudioHdr->pszName() );

	Assert( GetVertexData( handle ) );

	if( pStudioHdr->version == 49 )
	{
		for( int i = 0; i < pVtxHdr->numBodyParts; i++)
		{
			OptimizedModel::BodyPartHeader_t *pBodyPartHdr = pVtxHdr->pBodyPart(i);

			for( int j = 0; j < pBodyPartHdr->numModels; j++ )
			{
				OptimizedModel::ModelHeader_t *pModelHdr = pBodyPartHdr->pModel(j);

				for( int k = 0; k < pModelHdr->numLODs; k++)
				{
					OptimizedModel::ModelLODHeader_t *pModelLODHdr = pModelHdr->pLOD(k);

					for( int l = 0; l < pModelLODHdr->numMeshes; l++ )
					{
						OptimizedModel::MeshHeader_t *pMeshHdr = pModelLODHdr->pMesh(l);
						pMeshHdr->flags |= OptimizedModel::MESH_IS_MDL49;

						for( int m = 0; m < pMeshHdr->numStripGroups; m++ )
						{
							OptimizedModel::StripGroupHeader_t *pStripGroupHdr = pMeshHdr->pStripGroup(m);
							pStripGroupHdr->flags |= OptimizedModel::STRIPGROUP_IS_MDL49;
						}
					}
				}
			}
		}
	}

	BeginLock();
	bool bLoaded = g_pStudioRender->LoadModel( pStudioHdr, pVtxHdr, &pStudioData->m_HardwareData );
	EndLock();

	if ( bLoaded )
	{
		pStudioData->m_nFlags |= STUDIODATA_FLAGS_STUDIOMESH_LOADED;
	}
	else
	{
		pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH;
	}

	if ( m_pCacheNotify )
	{
		m_pCacheNotify->OnDataLoaded( MDLCACHE_STUDIOHWDATA, handle );
	}

#if defined( USE_HARDWARE_CACHE )
	GetCacheSection( MDLCACHE_STUDIOHWDATA )->Add( MakeCacheID( handle, MDLCACHE_STUDIOHWDATA ), &pStudioData->m_HardwareData, ComputeHardwareDataSize( &pStudioData->m_HardwareData ), &pStudioData->m_HardwareDataCache );
#endif
	return true;
}


//-----------------------------------------------------------------------------
// Loads the static meshes
//-----------------------------------------------------------------------------
void CMDLCache::UnloadHardwareData( MDLHandle_t handle, bool bCacheRemove, bool bLockedOk )
{
	if ( handle == MDLHANDLE_INVALID )
		return;

	// Don't load it if it's loaded
	studiodata_t *pStudioData = m_MDLDict[handle];
	if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_STUDIOMESH_LOADED )
	{
#if defined( USE_HARDWARE_CACHE )
		if ( bCacheRemove )
		{
			if ( GetCacheSection( MDLCACHE_STUDIOHWDATA )->BreakLock( pStudioData->m_HardwareDataCache ) && !bLockedOk )
			{
				DevMsg( "Warning: freed a locked resource\n" );
				Assert( 0 );
			}

			GetCacheSection( MDLCACHE_STUDIOHWDATA )->Remove( pStudioData->m_HardwareDataCache );
		}
#endif

		if ( m_pCacheNotify )
		{
			m_pCacheNotify->OnDataUnloaded( MDLCACHE_STUDIOHWDATA, handle );
		}

		MdlCacheMsg("MDLCache: Unload studiomdl %s\n", GetModelName( handle ) );

		g_pStudioRender->UnloadModel( &pStudioData->m_HardwareData );
		memset( &pStudioData->m_HardwareData, 0, sizeof( pStudioData->m_HardwareData ) );
		pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_STUDIOMESH_LOADED;

		NotifyFileUnloaded( handle, ".mdl" );

	}
}


//-----------------------------------------------------------------------------
// Returns the hardware data associated with an MDL
//-----------------------------------------------------------------------------
studiohwdata_t *CMDLCache::GetHardwareData( MDLHandle_t handle )
{
	if ( mod_test_not_available.GetBool() )
		return NULL;

	if ( mod_test_mesh_not_available.GetBool() )
		return NULL;

	studiodata_t *pStudioData = m_MDLDict[handle];
	m_pMeshCacheSection->LockMutex();
	if ( ( pStudioData->m_nFlags & (STUDIODATA_FLAGS_STUDIOMESH_LOADED | STUDIODATA_FLAGS_NO_STUDIOMESH) ) == 0 )
	{
		m_pMeshCacheSection->UnlockMutex();
		if ( !LoadHardwareData( handle ) )
		{
			return NULL;
		}
	}
	else
	{
#if defined( USE_HARDWARE_CACHE )
		CheckData( pStudioData->m_HardwareDataCache, MDLCACHE_STUDIOHWDATA );
#endif
		m_pMeshCacheSection->UnlockMutex();
	}

	// didn't load, don't return an empty pointer
	if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH )
		return NULL;

	return &pStudioData->m_HardwareData;
}


//-----------------------------------------------------------------------------
// Task switch
//-----------------------------------------------------------------------------
void CMDLCache::ReleaseMaterialSystemObjects()
{
	Assert( !m_bLostVideoMemory );
	m_bLostVideoMemory = true;

	BreakFrameLock( false );

	// Free all hardware data
	MDLHandle_t i = m_MDLDict.First();
	while ( i != m_MDLDict.InvalidIndex() )
	{
		UnloadHardwareData( i );
		i = m_MDLDict.Next( i );
	}

	RestoreFrameLock();
}

void CMDLCache::RestoreMaterialSystemObjects( int nChangeFlags )
{
	Assert( m_bLostVideoMemory );
	m_bLostVideoMemory = false;

	BreakFrameLock( false );

	// Restore all hardware data
	MDLHandle_t i = m_MDLDict.First();
	while ( i != m_MDLDict.InvalidIndex() )
	{
		studiodata_t *pStudioData = m_MDLDict[i];

		bool bIsMDLInMemory = GetCacheSection( MDLCACHE_STUDIOHDR )->IsPresent( pStudioData->m_MDLCache );

		// If the vertex format changed, we have to free the data because we may be using different .vtx files.
		if ( nChangeFlags & MATERIAL_RESTORE_VERTEX_FORMAT_CHANGED )
		{
			MdlCacheMsg( "MDLCache: Free studiohdr\n" );
			MdlCacheMsg( "MDLCache: Free VVD\n" );
			MdlCacheMsg( "MDLCache: Free VTX\n" );

			// FIXME: Do we have to free m_MDLCache + m_VertexCache?
			// Certainly we have to free m_IndexCache, cause that's a dx-level specific vtx file.
			ClearAsync( i, MDLCACHE_STUDIOHWDATA, 0, true );

			Flush( i, MDLCACHE_FLUSH_VERTEXES );
		}

		// Only restore the hardware data of those studiohdrs which are currently in memory
		if ( bIsMDLInMemory )
		{
			GetHardwareData( i );
		}

		i = m_MDLDict.Next( i );
	}

	RestoreFrameLock();
}


void CMDLCache::MarkAsLoaded(MDLHandle_t handle)
{
	if ( mod_lock_mdls_on_load.GetBool() )
	{
		g_MDLCache.GetStudioHdr(handle);
		if ( !( m_MDLDict[handle]->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL ) )
		{
			m_MDLDict[handle]->m_nFlags |= STUDIODATA_FLAGS_LOCKED_MDL;
			GetCacheSection( MDLCACHE_STUDIOHDR )->Lock( m_MDLDict[handle]->m_MDLCache );
		}
	}
}


//-----------------------------------------------------------------------------
// Callback for UpdateOrCreate utility function - swaps any studiomdl file type.
//-----------------------------------------------------------------------------
static bool MdlcacheCreateCallback( const char *pSourceName, const char *pTargetName, const char *pPathID, void *pHdr )
{
	// Missing studio files are permissible and not spewed as errors
	bool retval = false;
	CUtlBuffer sourceBuf;
	bool bOk = g_pFullFileSystem->ReadFile( pSourceName, NULL, sourceBuf );
	if ( bOk )
	{
		CUtlBuffer targetBuf;
		targetBuf.EnsureCapacity( sourceBuf.TellPut() + BYTESWAP_ALIGNMENT_PADDING );

		int bytes = StudioByteSwap::ByteswapStudioFile( pTargetName, targetBuf.Base(), sourceBuf.Base(), sourceBuf.TellPut(), (studiohdr_t*)pHdr );
		if ( bytes )
		{
			// If the file was an .mdl, attempt to swap the .ani as well
			if ( Q_stristr( pSourceName, ".mdl" ) )
			{
				char szANISourceName[ MAX_PATH ];
				Q_StripExtension( pSourceName, szANISourceName, sizeof( szANISourceName ) );
				Q_strncat( szANISourceName, ".ani", sizeof( szANISourceName ), COPY_ALL_CHARACTERS );
				UpdateOrCreate( szANISourceName, NULL, 0, pPathID, MdlcacheCreateCallback, true, targetBuf.Base() );
			}

			targetBuf.SeekPut( CUtlBuffer::SEEK_HEAD, bytes );
			g_pFullFileSystem->WriteFile( pTargetName, pPathID, targetBuf );
			retval = true;
		}
		else
		{
			Warning( "Failed to create %s\n", pTargetName );
		}
	}
	return retval;
}

//-----------------------------------------------------------------------------
// Calls utility function to create .360 version of a file.
//-----------------------------------------------------------------------------
int CMDLCache::UpdateOrCreate( studiohdr_t *pHdr, const char *pSourceName, char *pTargetName, int targetLen, const char *pPathID, bool bForce )
{
	return ::UpdateOrCreate( pSourceName, pTargetName, targetLen, pPathID, MdlcacheCreateCallback, bForce, pHdr );
}

//-----------------------------------------------------------------------------
// Purpose: Attempts to read a file native to the current platform
//-----------------------------------------------------------------------------
bool CMDLCache::ReadFileNative( char *pFileName, const char *pPath, CUtlBuffer &buf, int nMaxBytes, MDLCacheDataType_t type )
{
	bool bOk = false;

	if ( IsX360() )
	{
		// Read the 360 version
		char pX360Filename[ MAX_PATH ];
		UpdateOrCreate( NULL, pFileName, pX360Filename, sizeof( pX360Filename ), pPath );
		bOk = g_pFullFileSystem->ReadFile( pX360Filename, pPath, buf, nMaxBytes );
	}
	else
	{
		// Read the PC version
		bOk = g_pFullFileSystem->ReadFile( pFileName, pPath, buf, nMaxBytes );

		if( bOk && type == MDLCACHE_STUDIOHDR )
		{
			studiohdr_t* pStudioHdr = ( studiohdr_t* ) buf.PeekGet();

			if ( pStudioHdr->studiohdr2index == 0 )
			{
				// We always need this now, so make room for it in the buffer now.
				int bufferContentsEnd = buf.TellMaxPut();
				int maskBits = VALIGNOF( studiohdr2_t ) - 1;
				int offsetStudiohdr2 = ( bufferContentsEnd + maskBits ) & ~maskBits;
				int sizeIncrease = ( offsetStudiohdr2 - bufferContentsEnd )  + sizeof( studiohdr2_t );
				buf.SeekPut( CUtlBuffer::SEEK_CURRENT, sizeIncrease );

				// Re-get the pointer after resizing, because it has probably moved.
				pStudioHdr = ( studiohdr_t* ) buf.Base();
				studiohdr2_t* pStudioHdr2 = ( studiohdr2_t* ) ( ( byte * ) pStudioHdr + offsetStudiohdr2 );
				memset( pStudioHdr2, 0, sizeof( studiohdr2_t ) );
				pStudioHdr2->flMaxEyeDeflection = 0.866f; // Matches studio.h.

				pStudioHdr->studiohdr2index = offsetStudiohdr2;
				// Also make sure the structure knows about the extra bytes 
				// we've added so they get copied around.
				pStudioHdr->length += sizeIncrease;
			}
		}
	}

	return bOk;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
studiohdr_t *CMDLCache::UnserializeMDL( MDLHandle_t handle, void *pData, int nDataSize, bool bDataValid )
{
	if ( !bDataValid || nDataSize <= 0 || pData == NULL)
	{
		return NULL;
	}

	CTempAllocHelper pOriginalData;
	if ( IsX360() )
	{
		if ( CLZMA::IsCompressed( (unsigned char *)pData ) )
		{
			// mdl arrives compressed, decode and cache the results
			unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)pData );
			pOriginalData.Alloc( nOriginalSize );
			unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)pData, (unsigned char *)pOriginalData.Get() );
			if ( nOutputSize != nOriginalSize )
			{
				// decoder failure
				return NULL;
			}

			pData = pOriginalData.Get();
			nDataSize = nOriginalSize;
		}
	}

	studiohdr_t	*pStudioHdrIn = (studiohdr_t *)pData;

	if ( r_rootlod.GetInt() > 0 )
	{
		// raw data is already setup for lod 0, override otherwise
		Studio_SetRootLOD( pStudioHdrIn, r_rootlod.GetInt() );
	}

	// critical! store a back link to our data
	// this is fetched when re-establishing dependent cached data (vtx/vvd)
	pStudioHdrIn->SetVirtualModel( MDLHandleToVirtual( handle ) );

	MdlCacheMsg( "MDLCache: Alloc studiohdr %s\n", GetModelName( handle ) );

	// allocate cache space
	MemAlloc_PushAllocDbgInfo( "Models:StudioHdr", 0);
	studiohdr_t *pHdr = (studiohdr_t *)AllocData( MDLCACHE_STUDIOHDR, pStudioHdrIn->length );
	MemAlloc_PopAllocDbgInfo();
	if ( !pHdr )
		return NULL;

	CacheData( &m_MDLDict[handle]->m_MDLCache, pHdr, pStudioHdrIn->length, GetModelName( handle ), MDLCACHE_STUDIOHDR, MakeCacheID( handle, MDLCACHE_STUDIOHDR) );

	if ( mod_lock_mdls_on_load.GetBool() )
	{
		GetCacheSection( MDLCACHE_STUDIOHDR )->Lock( m_MDLDict[handle]->m_MDLCache );
		m_MDLDict[handle]->m_nFlags |= STUDIODATA_FLAGS_LOCKED_MDL;
	}

	// FIXME: Is there any way we can compute the size to load *before* loading in
	// and read directly into cache memory? It would be nice to reduce cache overhead here.
	// move the complete, relocatable model to the cache
	memcpy( pHdr, pStudioHdrIn, pStudioHdrIn->length );

	// On first load, convert the flex deltas from fp16 to 16-bit fixed-point
	if ( (pHdr->flags & STUDIOHDR_FLAGS_FLEXES_CONVERTED) == 0 )
	{
		ConvertFlexData( pHdr );

		// Mark as converted so it only happens once
		pHdr->flags |= STUDIOHDR_FLAGS_FLEXES_CONVERTED;
	}

	if ( m_pCacheNotify )
	{
		m_pCacheNotify->OnDataLoaded( MDLCACHE_STUDIOHDR, handle );
	}

	return pHdr;
}


//-----------------------------------------------------------------------------
// Attempts to load a MDL file, validates that it's ok.
//-----------------------------------------------------------------------------
bool CMDLCache::ReadMDLFile( MDLHandle_t handle, const char *pMDLFileName, CUtlBuffer &buf )
{
	VPROF( "CMDLCache::ReadMDLFile" );

	char pFileName[ MAX_PATH ];
	Q_strncpy( pFileName, pMDLFileName, sizeof( pFileName ) );
	Q_FixSlashes( pFileName );
#ifdef POSIX
	Q_strlower( pFileName );
#endif

	MdlCacheMsg( "MDLCache: Load studiohdr %s\n", pFileName );

	MEM_ALLOC_CREDIT();

	bool bOk = ReadFileNative( pFileName, "GAME", buf, 0, MDLCACHE_STUDIOHDR );
	if ( !bOk )
	{
		DevWarning( "Failed to load %s!\n", pMDLFileName );
		return false;
	}

	if ( IsX360() )
	{
		if ( CLZMA::IsCompressed( (unsigned char *)buf.PeekGet() ) )
		{
			// mdl arrives compressed, decode and cache the results
			unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)buf.PeekGet() );
			void *pOriginalData = malloc( nOriginalSize );
			unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)buf.PeekGet(), (unsigned char *)pOriginalData );
			if ( nOutputSize != nOriginalSize )
			{
				// decoder failure
				free( pOriginalData );
				return false;
			}

			// replace caller's buffer
			buf.Purge();
			buf.Put( pOriginalData, nOriginalSize );
			free( pOriginalData );
		}
	}

    if ( buf.Size() < sizeof(studiohdr_t) )
    {
        DevWarning( "Empty model %s\n", pMDLFileName );
        return false;
    }

	studiohdr_t *pStudioHdr = (studiohdr_t*)buf.PeekGet();
	if ( !pStudioHdr )
	{
		DevWarning( "Failed to read model %s from buffer!\n", pMDLFileName );
		return false;
	}
	if ( pStudioHdr->id != IDSTUDIOHEADER )
	{
		DevWarning( "Model %s not a .MDL format file!\n", pMDLFileName );
		return false;
	}

	// critical! store a back link to our data
	// this is fetched when re-establishing dependent cached data (vtx/vvd)
	pStudioHdr->SetVirtualModel( MDLHandleToVirtual( handle ) );

	// Make sure all dependent files are valid
	if ( !VerifyHeaders( pStudioHdr ) )
	{
		DevWarning( "Model %s has mismatched .vvd + .vtx files!\n", pMDLFileName );
		return false;
	}

	return true;
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
studiohdr_t *CMDLCache::LockStudioHdr( MDLHandle_t handle )
{
	if ( handle == MDLHANDLE_INVALID )
	{
		return NULL;
	}

	CMDLCacheCriticalSection cacheCriticalSection( this );
	studiohdr_t *pStdioHdr = GetStudioHdr( handle );
	// @TODO (toml 9/12/2006) need this?: AddRef( handle );
	if ( !pStdioHdr )
	{
		return NULL;
	}

	GetCacheSection( MDLCACHE_STUDIOHDR )->Lock( m_MDLDict[handle]->m_MDLCache );
	return pStdioHdr;
}

void CMDLCache::UnlockStudioHdr( MDLHandle_t handle )
{
	if ( handle == MDLHANDLE_INVALID )
	{
		return;
	}

	CMDLCacheCriticalSection cacheCriticalSection( this );
	studiohdr_t *pStdioHdr = GetStudioHdr( handle );
	if ( pStdioHdr )
	{
		GetCacheSection( MDLCACHE_STUDIOHDR )->Unlock( m_MDLDict[handle]->m_MDLCache );
	}
	// @TODO (toml 9/12/2006) need this?: Release( handle );
}

//-----------------------------------------------------------------------------
// Loading the data in
//-----------------------------------------------------------------------------
studiohdr_t *CMDLCache::GetStudioHdr( MDLHandle_t handle )
{
	if ( handle == MDLHANDLE_INVALID )
		return NULL;

	// Returning a pointer to data inside the cache when it's unlocked is just a bad idea.
	// It's technically legal, but the pointer can get invalidated if anything else looks at the cache.
	// Don't do that.
	// Assert( m_pModelCacheSection->IsFrameLocking() );
	// Assert( m_pMeshCacheSection->IsFrameLocking() );

	studiodata_t *pStudioData = m_MDLDict[handle];

	if( !pStudioData )
		return NULL;

#if _DEBUG
	VPROF_INCREMENT_COUNTER( "GetStudioHdr", 1 );
#endif
	studiohdr_t *pHdr = (studiohdr_t*)CheckData( m_MDLDict[handle]->m_MDLCache, MDLCACHE_STUDIOHDR );
	if ( !pHdr )
	{
		m_MDLDict[handle]->m_MDLCache = NULL;

		CMDLCacheCriticalSection cacheCriticalSection( this );

		// load the file
		const char *pModelName = GetActualModelName( handle );
		if ( developer.GetInt() > 1 )
		{
			DevMsg( "Loading %s\n", pModelName );
		}

		// Load file to temporary space
		CUtlBuffer buf;
		if ( !ReadMDLFile( handle, pModelName, buf ) )
		{
			bool bOk = false;
			if ( ( m_MDLDict[handle]->m_nFlags & STUDIODATA_ERROR_MODEL ) == 0 )
			{
				buf.Clear(); // clear buffer for next file read

				m_MDLDict[handle]->m_nFlags |= STUDIODATA_ERROR_MODEL;
				bOk = ReadMDLFile( handle, ERROR_MODEL, buf );
			}

			if ( !bOk )
			{
				if (IsOSX())
				{
					// rbarris wants this to go somewhere like the console.log prior to crashing, which is what the Error call will do next
					printf("\n ##### Model %s not found and %s couldn't be loaded", pModelName, ERROR_MODEL );
					fflush( stdout );
				}
				Error( "Model %s not found and %s couldn't be loaded", pModelName, ERROR_MODEL );
				return NULL;
			}
		}

		// put it in the cache
		if ( ProcessDataIntoCache( handle, MDLCACHE_STUDIOHDR, 0, buf.Base(), buf.TellMaxPut(), true ) )
		{
			pHdr = (studiohdr_t*)CheckData( m_MDLDict[handle]->m_MDLCache, MDLCACHE_STUDIOHDR );
		}
	}

	return pHdr;
}


//-----------------------------------------------------------------------------
// Gets/sets user data associated with the MDL
//-----------------------------------------------------------------------------
void CMDLCache::SetUserData( MDLHandle_t handle, void* pData )
{
	if ( handle == MDLHANDLE_INVALID )
		return;

	m_MDLDict[handle]->m_pUserData = pData;
}

void *CMDLCache::GetUserData( MDLHandle_t handle )
{
	if ( handle == MDLHANDLE_INVALID )
		return NULL;
	return m_MDLDict[handle]->m_pUserData;
}


//-----------------------------------------------------------------------------
// Polls information about a particular mdl
//-----------------------------------------------------------------------------
bool CMDLCache::IsErrorModel( MDLHandle_t handle )
{
	if ( handle == MDLHANDLE_INVALID )
		return false;

	return (m_MDLDict[handle]->m_nFlags & STUDIODATA_ERROR_MODEL) != 0;
}


//-----------------------------------------------------------------------------
// Brings all data associated with an MDL into memory
//-----------------------------------------------------------------------------
void CMDLCache::TouchAllData( MDLHandle_t handle )
{
	studiohdr_t *pStudioHdr = GetStudioHdr( handle );
	virtualmodel_t *pVModel = GetVirtualModel( handle );
	if ( pVModel )
	{
		// skip self, start at children
		// ensure all sub models are cached
		for ( int i=1; i<pVModel->m_group.Count(); ++i )
		{
			MDLHandle_t childHandle = VoidPtrToMDLHandle( pVModel->m_group[i].cache );
			if ( childHandle != MDLHANDLE_INVALID )
			{
				// FIXME: Should this be calling TouchAllData on the child?
				GetStudioHdr( childHandle );
			}
		}
	}

	if ( !IsX360() )
	{
		// cache the anims
		// Note that the animblocks start at 1!!!
		for ( int i=1; i< (int)pStudioHdr->numanimblocks; ++i )
		{
			pStudioHdr->GetAnimBlock( i );
		}
	}

	// cache the vertexes
	if ( pStudioHdr->numbodyparts )
	{
		CacheVertexData( pStudioHdr );
		GetHardwareData( handle );
	}
}


//-----------------------------------------------------------------------------
// Flushes all data
//-----------------------------------------------------------------------------
void CMDLCache::Flush( MDLCacheFlush_t nFlushFlags )
{
	// Free all MDLs that haven't been cleaned up
	MDLHandle_t i = m_MDLDict.First();
	while ( i != m_MDLDict.InvalidIndex() )
	{
		Flush( i, nFlushFlags );
		i = m_MDLDict.Next( i );
	}
}

//-----------------------------------------------------------------------------
// Cache handlers
//-----------------------------------------------------------------------------
static const char *g_ppszTypes[] =
{
	"studiohdr",	// MDLCACHE_STUDIOHDR
	"studiohwdata", // MDLCACHE_STUDIOHWDATA
	"vcollide",		// MDLCACHE_VCOLLIDE
	"animblock",	// MDLCACHE_ANIMBLOCK
	"virtualmodel", // MDLCACHE_VIRTUALMODEL
	"vertexes",		// MDLCACHE_VERTEXES
};

bool CMDLCache::HandleCacheNotification( const DataCacheNotification_t &notification  )
{
	switch ( notification.type )
	{
	case DC_AGE_DISCARD:
	case DC_FLUSH_DISCARD:
	case DC_REMOVED:
		{
			MdlCacheMsg( "MDLCache: Data cache discard %s %s\n", g_ppszTypes[TypeFromCacheID( notification.clientId )], GetModelName( HandleFromCacheID( notification.clientId ) ) );

			if ( (DataCacheClientID_t)(intp)notification.pItemData == notification.clientId ||
				 TypeFromCacheID(notification.clientId) != MDLCACHE_STUDIOHWDATA )
			{
				Assert( notification.pItemData );
				FreeData( TypeFromCacheID(notification.clientId), (void *)notification.pItemData );
			}
			else
			{
				UnloadHardwareData( HandleFromCacheID( notification.clientId ), false );
			}
			return true;
		}
	}

	return CDefaultDataCacheClient::HandleCacheNotification( notification );
}

bool CMDLCache::GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen  )
{
	if ( (DataCacheClientID_t)(uintp)pItem == clientId )
	{
		return false;
	}

	MDLHandle_t handle = HandleFromCacheID( clientId );
	MDLCacheDataType_t type = TypeFromCacheID( clientId );

	Q_snprintf( pDest, nMaxLen, "%s - %s", g_ppszTypes[type], GetModelName( handle ) );

	return false;
}

//-----------------------------------------------------------------------------
// Flushes all data
//-----------------------------------------------------------------------------
void CMDLCache::BeginLock()
{
	m_pModelCacheSection->BeginFrameLocking();
	m_pMeshCacheSection->BeginFrameLocking();
}

//-----------------------------------------------------------------------------
// Flushes all data
//-----------------------------------------------------------------------------
void CMDLCache::EndLock()
{
	m_pModelCacheSection->EndFrameLocking();
	m_pMeshCacheSection->EndFrameLocking();
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CMDLCache::BreakFrameLock( bool bModels, bool bMesh )
{
	if ( bModels )
	{
		if ( m_pModelCacheSection->IsFrameLocking() )
		{
			Assert( !m_nModelCacheFrameLocks );
			m_nModelCacheFrameLocks = 0;
			do
			{
				m_nModelCacheFrameLocks++;
			} while ( m_pModelCacheSection->EndFrameLocking() );
		}

	}

	if ( bMesh )
	{
		if ( m_pMeshCacheSection->IsFrameLocking() )
		{
			Assert( !m_nMeshCacheFrameLocks );
			m_nMeshCacheFrameLocks = 0;
			do
			{
				m_nMeshCacheFrameLocks++;
			} while ( m_pMeshCacheSection->EndFrameLocking() );
		}
	}

}

void CMDLCache::RestoreFrameLock()
{
	while ( m_nModelCacheFrameLocks )
	{
		m_pModelCacheSection->BeginFrameLocking();
		m_nModelCacheFrameLocks--;
	}
	while ( m_nMeshCacheFrameLocks )
	{
		m_pMeshCacheSection->BeginFrameLocking();
		m_nMeshCacheFrameLocks--;
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int *CMDLCache::GetFrameUnlockCounterPtrOLD()
{
	return GetCacheSection( MDLCACHE_STUDIOHDR )->GetFrameUnlockCounterPtr();
}

int *CMDLCache::GetFrameUnlockCounterPtr( MDLCacheDataType_t type )
{
	return GetCacheSection( type )->GetFrameUnlockCounterPtr();
}

//-----------------------------------------------------------------------------
// Completes all pending async operations
//-----------------------------------------------------------------------------
void CMDLCache::FinishPendingLoads()
{
	if ( !ThreadInMainThread() )
	{
		return;
	}

	AUTO_LOCK( m_AsyncMutex );

	// finish just our known jobs
	intp iAsync = m_PendingAsyncs.Head();
	while ( iAsync != m_PendingAsyncs.InvalidIndex() )
	{
		AsyncInfo_t &info = m_PendingAsyncs[iAsync];
		if ( info.hControl )
		{
			g_pFullFileSystem->AsyncFinish( info.hControl, true );
		}
		iAsync = m_PendingAsyncs.Next( iAsync );
	}

	ProcessPendingAsyncs();
}

//-----------------------------------------------------------------------------
// Notify map load has started
//-----------------------------------------------------------------------------
void CMDLCache::BeginMapLoad()
{
	BreakFrameLock();

	studiodata_t *pStudioData;

	// Unlock prior map MDLs prior to load
	MDLHandle_t i = m_MDLDict.First();
	while ( i != m_MDLDict.InvalidIndex() )
	{
		pStudioData = m_MDLDict[i];
		if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL )
		{
			GetCacheSection( MDLCACHE_STUDIOHDR )->Unlock( pStudioData->m_MDLCache );
			pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_LOCKED_MDL;
		}
		i = m_MDLDict.Next( i );
	}
}

//-----------------------------------------------------------------------------
// Notify map load is complete
//-----------------------------------------------------------------------------
void CMDLCache::EndMapLoad()
{
	FinishPendingLoads();

	// Remove all stray MDLs not referenced during load
	if ( mod_lock_mdls_on_load.GetBool() )
	{
		studiodata_t *pStudioData;
		MDLHandle_t i = m_MDLDict.First();
		while ( i != m_MDLDict.InvalidIndex() )
		{
			pStudioData = m_MDLDict[i];
			if ( !(pStudioData->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL) )
			{
				Flush( i, MDLCACHE_FLUSH_STUDIOHDR );
			}
			i = m_MDLDict.Next( i );
		}
	}

	RestoreFrameLock();
}


//-----------------------------------------------------------------------------
// Is a particular part of the model data loaded?
//-----------------------------------------------------------------------------
bool CMDLCache::IsDataLoaded( MDLHandle_t handle, MDLCacheDataType_t type )
{
	if ( handle == MDLHANDLE_INVALID || !m_MDLDict.IsValidIndex( handle ) )
		return false;

	studiodata_t *pData = m_MDLDict[ handle ];
	switch( type )
	{
	case MDLCACHE_STUDIOHDR:
		return GetCacheSection( MDLCACHE_STUDIOHDR )->IsPresent( pData->m_MDLCache );

	case MDLCACHE_STUDIOHWDATA:
		return ( pData->m_nFlags & STUDIODATA_FLAGS_STUDIOMESH_LOADED ) != 0;

	case MDLCACHE_VCOLLIDE:
		return ( pData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED ) != 0;

	case MDLCACHE_ANIMBLOCK:
		{
			if ( !pData->m_pAnimBlock )
				return false;

			for (int i = 0; i < pData->m_nAnimBlockCount; ++i )
			{
				if ( !pData->m_pAnimBlock[i] )
					return false;

				if ( !GetCacheSection( type )->IsPresent( pData->m_pAnimBlock[i] ) )
					return false;
			}
			return true;
		}

	case MDLCACHE_VIRTUALMODEL:
		return ( pData->m_pVirtualModel != 0 );

	case MDLCACHE_VERTEXES:
		return m_pMeshCacheSection->IsPresent( pData->m_VertexCache );
	}
	return false;
}


//-----------------------------------------------------------------------------
// Get the correct extension for our dx
//-----------------------------------------------------------------------------
const char *CMDLCache::GetVTXExtension()
{
	if ( IsPC() )
	{
		if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 90 )
		{
			return ".dx90.vtx";
		}
		else if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 80 )
		{
			return ".dx80.vtx";
		}
		else
		{
			return ".sw.vtx";
		}
	}

	return ".dx90.vtx";
}

//-----------------------------------------------------------------------------
// Minimal presence and header validation, no data loads
// Return true if successful, false otherwise.
//-----------------------------------------------------------------------------
bool CMDLCache::VerifyHeaders( studiohdr_t *pStudioHdr )
{
	VPROF( "CMDLCache::VerifyHeaders" );

	if ( developer.GetInt() < 2 )
	{
		return true;
	}

	// model has no vertex data
	if ( !pStudioHdr->numbodyparts )
	{
		// valid
		return true;
	}

	char pFileName[ MAX_PATH ];
	MDLHandle_t handle = VoidPtrToMDLHandle( pStudioHdr->VirtualModel() );

	MakeFilename( handle, ".vvd", pFileName, sizeof(pFileName) );

	MdlCacheMsg("MDLCache: Load VVD (verify) %s\n", pFileName );

	// vvd header only
	CUtlBuffer vvdHeader( 0, sizeof(vertexFileHeader_t) );
	if ( !ReadFileNative( pFileName, "GAME", vvdHeader, sizeof(vertexFileHeader_t) ) )
	{
		return false;
	}

	vertexFileHeader_t *pVertexHdr = (vertexFileHeader_t*)vvdHeader.PeekGet();

	// check
	if (( pVertexHdr->id != MODEL_VERTEX_FILE_ID ) ||
		( pVertexHdr->version != MODEL_VERTEX_FILE_VERSION ) ||
		( pVertexHdr->checksum != pStudioHdr->checksum ))
	{
		return false;
	}

	// load the VTX file
	// use model name for correct path
	MakeFilename( handle, GetVTXExtension(), pFileName, sizeof(pFileName) );

	MdlCacheMsg("MDLCache: Load VTX (verify) %s\n", pFileName );

	// vtx header only
	CUtlBuffer vtxHeader( 0, sizeof(OptimizedModel::FileHeader_t) );
	if ( !ReadFileNative( pFileName, "GAME", vtxHeader, sizeof(OptimizedModel::FileHeader_t) ) )
	{
		return false;
	}

	// check
	OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t*)vtxHeader.PeekGet();
	if (( pVtxHdr->version != OPTIMIZED_MODEL_FILE_VERSION ) ||
		( pVtxHdr->checkSum != pStudioHdr->checksum ))
	{
		return false;
	}

	// valid
	return true;
}


//-----------------------------------------------------------------------------
// Cache model's specified dynamic data
//-----------------------------------------------------------------------------
vertexFileHeader_t *CMDLCache::CacheVertexData( studiohdr_t *pStudioHdr )
{
	VPROF( "CMDLCache::CacheVertexData" );

	vertexFileHeader_t	*pVvdHdr;
	MDLHandle_t			handle;

	Assert( pStudioHdr );

	handle = VoidPtrToMDLHandle( pStudioHdr->VirtualModel() );
	Assert( handle != MDLHANDLE_INVALID );

	pVvdHdr = (vertexFileHeader_t *)CheckData( m_MDLDict[handle]->m_VertexCache, MDLCACHE_VERTEXES );
	if ( pVvdHdr )
	{
		return pVvdHdr;
	}

	m_MDLDict[handle]->m_VertexCache = NULL;

	return LoadVertexData( pStudioHdr );
}

//-----------------------------------------------------------------------------
// Start an async transfer
//-----------------------------------------------------------------------------
FSAsyncStatus_t CMDLCache::LoadData( const char *pszFilename, const char *pszPathID, void *pDest, int nBytes, int nOffset, bool bAsync, FSAsyncControl_t *pControl )
{
	if ( !*pControl )
	{
		if ( IsX360() && g_pQueuedLoader->IsMapLoading() )
		{
			DevWarning( "CMDLCache: Non-Optimal loading path for %s\n", pszFilename );
		}

		FileAsyncRequest_t asyncRequest;
		asyncRequest.pszFilename = pszFilename;
		asyncRequest.pszPathID = pszPathID;
		asyncRequest.pData = pDest;
		asyncRequest.nBytes = nBytes;
		asyncRequest.nOffset = nOffset;

		if ( !pDest )
		{
			asyncRequest.flags = FSASYNC_FLAGS_ALLOCNOFREE;
		}

		if ( !bAsync )
		{
			asyncRequest.flags |= FSASYNC_FLAGS_SYNC;
		}

		MEM_ALLOC_CREDIT();
		return g_pFullFileSystem->AsyncRead( asyncRequest, pControl );
	}

	return FSASYNC_ERR_FAILURE;
}

//-----------------------------------------------------------------------------
// Determine the maximum number of 'real' bone influences used by any vertex in a model
// (100% binding to bone zero doesn't count)
//-----------------------------------------------------------------------------
int ComputeMaxRealBoneInfluences( vertexFileHeader_t * vertexFile, int lod )
{
	const mstudiovertex_t * verts = vertexFile->GetVertexData();
	int numVerts = vertexFile->numLODVertexes[ lod ];
	Assert(verts);

	int maxWeights = 0;
	for (int i = 0;i < numVerts;i++)
	{
		if ( verts[i].m_BoneWeights.numbones > 0 )
		{
			int numWeights = 0;
			for (int j = 0;j < MAX_NUM_BONES_PER_VERT;j++)
			{
				if ( verts[i].m_BoneWeights.weight[j] > 0 )
					numWeights = j + 1;
			}
			if ( ( numWeights == 1 ) && ( verts[i].m_BoneWeights.bone[0] == 0 ) )
			{
				// 100% binding to first bone - not really skinned (the first bone is just the model transform)
				numWeights = 0;
			}
			maxWeights = max( numWeights, maxWeights );
		}
	}
	return maxWeights;
}

//-----------------------------------------------------------------------------
// Generate thin vertices (containing just the data needed to do model decals)
//-----------------------------------------------------------------------------
vertexFileHeader_t * CMDLCache::CreateThinVertexes( vertexFileHeader_t * originalData, const studiohdr_t * pStudioHdr, int * cacheLength )
{
	int rootLod = min( (int)pStudioHdr->rootLOD, ( originalData->numLODs - 1 ) );
	int numVerts = originalData->numLODVertexes[ rootLod ] + 1; // Add 1 vert to support prefetch during array access

	int numBoneInfluences = ComputeMaxRealBoneInfluences( originalData, rootLod );
	// Only store (N-1) weights (all N weights sum to 1, so we can re-compute the Nth weight later)
	int numStoredWeights = max( 0, ( numBoneInfluences - 1 ) );

	int vertexSize = 2*sizeof( Vector ) + numBoneInfluences*sizeof( unsigned char ) + numStoredWeights*sizeof( float );
	*cacheLength = sizeof( vertexFileHeader_t ) + sizeof( thinModelVertices_t ) + numVerts*vertexSize;

	// Allocate cache space for the thin data
	MemAlloc_PushAllocDbgInfo( "Models:Vertex data", 0);
	vertexFileHeader_t * pNewVvdHdr = (vertexFileHeader_t *)AllocData( MDLCACHE_VERTEXES, *cacheLength );
	MemAlloc_PopAllocDbgInfo();

	Assert( pNewVvdHdr );
	if ( pNewVvdHdr )
	{
		// Copy the header and set it up to hold thin vertex data
		memcpy( (void *)pNewVvdHdr, (void *)originalData, sizeof( vertexFileHeader_t ) );
		pNewVvdHdr->id					= MODEL_VERTEX_FILE_THIN_ID;
		pNewVvdHdr->numFixups			= 0;
		pNewVvdHdr->fixupTableStart		= 0;
		pNewVvdHdr->tangentDataStart	= 0;
		pNewVvdHdr->vertexDataStart		= sizeof( vertexFileHeader_t );

		// Set up the thin vertex structure
 		thinModelVertices_t	* pNewThinVerts = (thinModelVertices_t	*)( pNewVvdHdr		+ 1 );
		Vector				* pPositions	= (Vector				*)( pNewThinVerts	+ 1 );
		float				* pBoneWeights	= (float				*)( pPositions		+ numVerts );
		// Alloc the (short) normals here to avoid mis-aligning the float data
		unsigned short		* pNormals		= (unsigned short		*)( pBoneWeights	+ numVerts*numStoredWeights );
		// Alloc the (char) indices here to avoid mis-aligning the float/short data
		char				* pBoneIndices	= (char					*)( pNormals		+ numVerts );
		if ( numStoredWeights == 0 )
			pBoneWeights = NULL;
		if ( numBoneInfluences == 0 )
			pBoneIndices = NULL;
		pNewThinVerts->Init( numBoneInfluences, pPositions, pNormals, pBoneWeights, pBoneIndices );

		// Copy over the original data
		const mstudiovertex_t * srcVertexData = originalData->GetVertexData();
		for ( int i = 0; i < numVerts; i++ )
		{
			pNewThinVerts->SetPosition( i, srcVertexData[ i ].m_vecPosition );
			pNewThinVerts->SetNormal(   i, srcVertexData[ i ].m_vecNormal );
			if ( numBoneInfluences > 0 )
			{
				mstudioboneweight_t boneWeights;
				boneWeights.numbones = numBoneInfluences;
				for ( int j = 0; j < numStoredWeights; j++ )
				{
					boneWeights.weight[ j ] = srcVertexData[ i ].m_BoneWeights.weight[ j ];
				}
				for ( int j = 0; j < numBoneInfluences; j++ )
				{
					boneWeights.bone[ j ] = srcVertexData[ i ].m_BoneWeights.bone[ j ];
				}
				pNewThinVerts->SetBoneWeights( i, boneWeights );
			}
		}
	}

	return pNewVvdHdr;
}

//-----------------------------------------------------------------------------
// Process the provided raw data into the cache. Distributes to low level
// unserialization or build methods.
//-----------------------------------------------------------------------------
bool CMDLCache::ProcessDataIntoCache( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, void *pData, int nDataSize, bool bDataValid )
{
	studiohdr_t *pStudioHdrCurrent = NULL;
	if ( type != MDLCACHE_STUDIOHDR )
	{
		// can only get the studiohdr once the header has been processed successfully into the cache
		// causes a ProcessDataIntoCache() with the studiohdr data
		pStudioHdrCurrent = GetStudioHdr( handle );
		if ( !pStudioHdrCurrent )
		{
			return false;
		}
	}

	studiodata_t *pStudioDataCurrent = m_MDLDict[handle];

	if ( !pStudioDataCurrent )
	{
		return false;
	}

	switch ( type )
	{
	case MDLCACHE_STUDIOHDR:
		{
			pStudioHdrCurrent = UnserializeMDL( handle, pData, nDataSize, bDataValid );
			if ( !pStudioHdrCurrent )
			{
				return false;
			}

			if (!Studio_ConvertStudioHdrToNewVersion( pStudioHdrCurrent ))
			{
				Warning( "MDLCache: %s needs to be recompiled\n", pStudioHdrCurrent->pszName() );
			}

			if ( pStudioHdrCurrent->numincludemodels == 0 )
			{
				// perf optimization, calculate once and cache off the autoplay sequences
				int nCount = pStudioHdrCurrent->CountAutoplaySequences();
				if ( nCount )
				{
					AllocateAutoplaySequences( m_MDLDict[handle], nCount );
					pStudioHdrCurrent->CopyAutoplaySequences( m_MDLDict[handle]->m_pAutoplaySequenceList, nCount );
				}
			}

			// Load animations
			UnserializeAllVirtualModelsAndAnimBlocks( handle );
			break;
		}

	case MDLCACHE_VERTEXES:
		{
			if ( bDataValid )
			{
				BuildAndCacheVertexData( pStudioHdrCurrent, (vertexFileHeader_t *)pData );
			}
			else
			{
				pStudioDataCurrent->m_nFlags |= STUDIODATA_FLAGS_NO_VERTEX_DATA;
				if ( pStudioHdrCurrent->numbodyparts )
				{
					// expected data not valid
					Warning( "MDLCache: Failed load of .VVD data for %s\n", pStudioHdrCurrent->pszName() );
					return false;
				}
			}
			break;
		}

	case MDLCACHE_STUDIOHWDATA:
		{
			if ( bDataValid )
			{
				BuildHardwareData( handle, pStudioDataCurrent, pStudioHdrCurrent, (OptimizedModel::FileHeader_t *)pData );
			}
			else
			{
				pStudioDataCurrent->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH;
				if ( pStudioHdrCurrent->numbodyparts )
				{
					// expected data not valid
					Warning( "MDLCache: Failed load of .VTX data for %s\n", pStudioHdrCurrent->pszName() );
					return false;
				}
			}

			m_pMeshCacheSection->Unlock( pStudioDataCurrent->m_VertexCache );
			m_pMeshCacheSection->Age( pStudioDataCurrent->m_VertexCache );

			// FIXME: thin VVD data on PC too (have to address alt-tab, various DX8/DX7/debug software paths in studiorender, tools, etc)
			static bool bCompressedVVDs = CommandLine()->CheckParm( "-no_compressed_vvds" ) == NULL;
			if ( IsX360() && !( pStudioDataCurrent->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH ) && bCompressedVVDs )
			{
				// Replace the cached vertex data with a thin version (used for model decals).
				// Flexed meshes require the fat data to remain, for CPU mesh anim.
				if ( pStudioHdrCurrent->numflexdesc == 0 )
				{
					vertexFileHeader_t *originalVertexData = GetVertexData( handle );
					Assert( originalVertexData );
					if ( originalVertexData )
					{
						int thinVertexDataSize = 0;
						vertexFileHeader_t *thinVertexData = CreateThinVertexes( originalVertexData, pStudioHdrCurrent, &thinVertexDataSize );
						Assert( thinVertexData && ( thinVertexDataSize > 0 ) );
						if ( thinVertexData && ( thinVertexDataSize > 0 ) )
						{
							// Remove the original cache entry (and free it)
							Flush( handle, MDLCACHE_FLUSH_VERTEXES | MDLCACHE_FLUSH_IGNORELOCK );
							// Add the new one
							CacheData( &pStudioDataCurrent->m_VertexCache, thinVertexData, thinVertexDataSize, pStudioHdrCurrent->pszName(), MDLCACHE_VERTEXES, MakeCacheID( handle, MDLCACHE_VERTEXES) );
						}
					}
				}
			}

			break;
		}

	case MDLCACHE_ANIMBLOCK:
		{
			MEM_ALLOC_CREDIT_( __FILE__ ": Anim Blocks" );

			if ( bDataValid )
			{
				MdlCacheMsg( "MDLCache: Finish load anim block %s (block %i)\n", pStudioHdrCurrent->pszName(), iAnimBlock );

				char pCacheName[MAX_PATH];
				Q_snprintf( pCacheName, MAX_PATH, "%s (block %i)", pStudioHdrCurrent->pszName(), iAnimBlock );

				if ( IsX360() )
				{
					if ( CLZMA::IsCompressed( (unsigned char *)pData ) )
					{
						// anim block arrives compressed, decode and cache the results
						unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)pData );

						// get a "fake" (not really aligned) optimal read buffer, as expected by the free logic
						void *pOriginalData = g_pFullFileSystem->AllocOptimalReadBuffer( FILESYSTEM_INVALID_HANDLE, nOriginalSize, 0 );
						unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)pData, (unsigned char *)pOriginalData );
						if ( nOutputSize != nOriginalSize )
						{
							// decoder failure
							g_pFullFileSystem->FreeOptimalReadBuffer( pOriginalData );
							return false;
						}

						// input i/o buffer is now unused
						g_pFullFileSystem->FreeOptimalReadBuffer( pData );

						// datacache will now own the data
						pData = pOriginalData;
						nDataSize = nOriginalSize;
					}
				}

				CacheData( &pStudioDataCurrent->m_pAnimBlock[iAnimBlock], pData, nDataSize, pCacheName, MDLCACHE_ANIMBLOCK, MakeCacheID( handle, MDLCACHE_ANIMBLOCK) );
			}
			else
			{
				MdlCacheMsg( "MDLCache: Failed load anim block %s (block %i)\n", pStudioHdrCurrent->pszName(), iAnimBlock );
				if ( pStudioDataCurrent->m_pAnimBlock )
				{
					pStudioDataCurrent->m_pAnimBlock[iAnimBlock] = NULL;
				}
				return false;
			}
			break;
		}

	case MDLCACHE_VCOLLIDE:
		{
			// always marked as loaded, vcollides are not present for every model
			pStudioDataCurrent->m_nFlags |= STUDIODATA_FLAGS_VCOLLISION_LOADED;

			if ( bDataValid )
			{
				MdlCacheMsg( "MDLCache: Finish load vcollide for %s\n", pStudioHdrCurrent->pszName() );

				CTempAllocHelper pOriginalData;
				if ( IsX360() )
				{
					if ( CLZMA::IsCompressed( (unsigned char *)pData ) )
					{
						// phy arrives compressed, decode and cache the results
						unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)pData );
						pOriginalData.Alloc( nOriginalSize );
						unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)pData, (unsigned char *)pOriginalData.Get() );
						if ( nOutputSize != nOriginalSize )
						{
							// decoder failure
							return NULL;
						}

						pData = pOriginalData.Get();
						nDataSize = nOriginalSize;
					}
				}

				CUtlBuffer buf( pData, nDataSize, CUtlBuffer::READ_ONLY );
				buf.SeekPut( CUtlBuffer::SEEK_HEAD, nDataSize );

				phyheader_t header;
				buf.Get( &header, sizeof( phyheader_t ) );
				if ( ( header.size == sizeof( header ) ) && header.solidCount > 0 )
				{
					int nBufSize = buf.TellMaxPut() - buf.TellGet();
					vcollide_t *pCollide = &pStudioDataCurrent->m_VCollisionData;
					g_pPhysicsCollision->VCollideLoad( pCollide, header.solidCount, (const char*)buf.PeekGet(), nBufSize );
					if ( m_pCacheNotify )
					{
						m_pCacheNotify->OnDataLoaded( MDLCACHE_VCOLLIDE, handle );
					}
				}
			}
			else
			{
				MdlCacheWarning( "MDLCache: Failed load of .PHY data for %s\n", pStudioHdrCurrent->pszName() );
				return false;
			}
			break;
		}

	default:
		Assert( 0 );
	}

	// success
	return true;
}

//-----------------------------------------------------------------------------
// Returns:
//	<0: indeterminate at this time
//	=0: pending
//	>0:	completed
//-----------------------------------------------------------------------------
int CMDLCache::ProcessPendingAsync( intp iAsync )
{
	if ( !ThreadInMainThread() )
	{
		return -1;
	}

	ASSERT_NO_REENTRY();

	void *pData = NULL;
	int nBytesRead = 0;

	AsyncInfo_t *pInfo;
	{
		AUTO_LOCK( m_AsyncMutex );
		pInfo = &m_PendingAsyncs[iAsync];
	}
	Assert( pInfo->hControl );

	FSAsyncStatus_t status = g_pFullFileSystem->AsyncGetResult( pInfo->hControl, &pData, &nBytesRead );
	if ( status == FSASYNC_STATUS_PENDING )
	{
		return 0;
	}

	AsyncInfo_t info = *pInfo;
	pInfo = &info;
	ClearAsync( pInfo->hModel, pInfo->type, pInfo->iAnimBlock );

	switch ( pInfo->type )
	{
	case MDLCACHE_VERTEXES:
	case MDLCACHE_STUDIOHWDATA:
	case MDLCACHE_VCOLLIDE:
		{
			ProcessDataIntoCache( pInfo->hModel, pInfo->type, 0, pData, nBytesRead, status == FSASYNC_OK );
			g_pFullFileSystem->FreeOptimalReadBuffer( pData );
			break;
		}

	case MDLCACHE_ANIMBLOCK:
		{
			// cache assumes ownership of valid async'd data
			if ( !ProcessDataIntoCache( pInfo->hModel, MDLCACHE_ANIMBLOCK, pInfo->iAnimBlock, pData, nBytesRead, status == FSASYNC_OK ) )
			{
				g_pFullFileSystem->FreeOptimalReadBuffer( pData );
			}
			break;
		}

	default:
		Assert( 0 );
	}

	return 1;
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CMDLCache::ProcessPendingAsyncs( MDLCacheDataType_t type )
{
	if ( !ThreadInMainThread() )
	{
		return;
	}

	if ( !m_PendingAsyncs.Count() )
	{
		return;
	}

	static bool bReentering;
	if ( bReentering )
	{
		return;
	}
	bReentering = true;

	AUTO_LOCK( m_AsyncMutex );

	// Process all of the completed loads that were requested before a new one. This ensures two
	// things -- the LRU is in correct order, and it catches precached items lurking
	// in the async queue that have only been requested once (thus aren't being cached
	// and might lurk forever, e.g., wood gibs in the citadel)
	intp current = m_PendingAsyncs.Head();
	while ( current != m_PendingAsyncs.InvalidIndex() )
	{
		intp next = m_PendingAsyncs.Next( current );

		if ( type == MDLCACHE_NONE || m_PendingAsyncs[current].type == type )
		{
			// process, also removes from list
			if ( ProcessPendingAsync( current ) <= 0 )
			{
				// indeterminate or pending
				break;
			}
		}

		current = next;
	}

	bReentering = false;
}

//-----------------------------------------------------------------------------
// Cache model's specified dynamic data
//-----------------------------------------------------------------------------
bool CMDLCache::ClearAsync( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, bool bAbort )
{
	intp iAsyncInfo = GetAsyncInfoIndex( handle, type, iAnimBlock );
	if ( iAsyncInfo != NO_ASYNC )
	{
		AsyncInfo_t *pInfo;
		{
			AUTO_LOCK( m_AsyncMutex );
			pInfo = &m_PendingAsyncs[iAsyncInfo];
		}
		if ( pInfo->hControl )
		{
			if ( bAbort )
			{
				g_pFullFileSystem->AsyncAbort(  pInfo->hControl );
				void *pData;
				int ignored;
				if ( g_pFullFileSystem->AsyncGetResult(  pInfo->hControl, &pData, &ignored ) == FSASYNC_OK )
				{
					g_pFullFileSystem->FreeOptimalReadBuffer( pData );
				}
			}
			g_pFullFileSystem->AsyncRelease(  pInfo->hControl );
			pInfo->hControl = NULL;
		}

		SetAsyncInfoIndex( handle, type, iAnimBlock, NO_ASYNC );
		{
			AUTO_LOCK( m_AsyncMutex );
			m_PendingAsyncs.Remove( iAsyncInfo );
		}

		return true;
	}
	return false;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CMDLCache::GetAsyncLoad( MDLCacheDataType_t type )
{
	switch ( type )
	{
	case MDLCACHE_STUDIOHDR:
		return false;
	case MDLCACHE_STUDIOHWDATA:
		return mod_load_mesh_async.GetBool();
	case MDLCACHE_VCOLLIDE:
		return mod_load_vcollide_async.GetBool();
	case MDLCACHE_ANIMBLOCK:
		return mod_load_anims_async.GetBool();
	case MDLCACHE_VIRTUALMODEL:
		return false;
	case MDLCACHE_VERTEXES:
		return mod_load_mesh_async.GetBool();
	}
	return false;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CMDLCache::SetAsyncLoad( MDLCacheDataType_t type, bool bAsync )
{
	bool bRetVal = false;
	switch ( type )
	{
	case MDLCACHE_STUDIOHDR:
		break;
	case MDLCACHE_STUDIOHWDATA:
		bRetVal = mod_load_mesh_async.GetBool();
		mod_load_mesh_async.SetValue( bAsync );
		break;
	case MDLCACHE_VCOLLIDE:
		bRetVal = mod_load_vcollide_async.GetBool();
		mod_load_vcollide_async.SetValue( bAsync );
		break;
	case MDLCACHE_ANIMBLOCK:
		bRetVal = mod_load_anims_async.GetBool();
		mod_load_anims_async.SetValue( bAsync );
		break;
	case MDLCACHE_VIRTUALMODEL:
		return false;
		break;
	case MDLCACHE_VERTEXES:
		bRetVal = mod_load_mesh_async.GetBool();
		mod_load_mesh_async.SetValue( bAsync );
		break;
	}
	return bRetVal;
}

//-----------------------------------------------------------------------------
// Cache model's specified dynamic data
//-----------------------------------------------------------------------------
vertexFileHeader_t *CMDLCache::BuildAndCacheVertexData( studiohdr_t *pStudioHdr, vertexFileHeader_t *pRawVvdHdr  )
{
	MDLHandle_t	handle = VoidPtrToMDLHandle( pStudioHdr->VirtualModel() );
	vertexFileHeader_t *pVvdHdr;

	MdlCacheMsg( "MDLCache: Load VVD for %s\n", pStudioHdr->pszName() );

	Assert( pRawVvdHdr );

	// check header
	if ( pRawVvdHdr->id != MODEL_VERTEX_FILE_ID )
	{
		Warning( "Error Vertex File for '%s' id %d should be %d\n", pStudioHdr->pszName(), pRawVvdHdr->id, MODEL_VERTEX_FILE_ID );
		return NULL;
	}
	if ( pRawVvdHdr->version != MODEL_VERTEX_FILE_VERSION )
	{
		Warning( "Error Vertex File for '%s' version %d should be %d\n", pStudioHdr->pszName(), pRawVvdHdr->version, MODEL_VERTEX_FILE_VERSION );
		return NULL;
	}
	if ( pRawVvdHdr->checksum != pStudioHdr->checksum )
	{
		Warning( "Error Vertex File for '%s' checksum %d should be %d\n", pStudioHdr->pszName(), pRawVvdHdr->checksum, pStudioHdr->checksum );
		return NULL;
	}

	Assert( pRawVvdHdr->numLODs );
	if ( !pRawVvdHdr->numLODs )
	{
		return NULL;
	}

	CTempAllocHelper pOriginalData;
	if ( IsX360() )
	{
		unsigned char *pInput = (unsigned char *)pRawVvdHdr + sizeof( vertexFileHeader_t );
		if ( CLZMA::IsCompressed( pInput ) )
		{
			// vvd arrives compressed, decode and cache the results
			unsigned int nOriginalSize = CLZMA::GetActualSize( pInput );
			pOriginalData.Alloc( sizeof( vertexFileHeader_t ) + nOriginalSize );
			V_memcpy( pOriginalData.Get(), pRawVvdHdr, sizeof( vertexFileHeader_t ) );
			unsigned int nOutputSize = CLZMA::Uncompress( pInput, sizeof( vertexFileHeader_t ) + (unsigned char *)pOriginalData.Get() );
			if ( nOutputSize != nOriginalSize )
			{
				// decoder failure
				return NULL;
			}

			pRawVvdHdr = (vertexFileHeader_t *)pOriginalData.Get();
		}
	}

	bool bNeedsTangentS = IsX360() || (g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 80);
	int rootLOD = min( (int)pStudioHdr->rootLOD, pRawVvdHdr->numLODs - 1 );

	// determine final cache footprint, possibly truncated due to lod
	int cacheLength = Studio_VertexDataSize( pRawVvdHdr, rootLOD, bNeedsTangentS );

	MdlCacheMsg("MDLCache: Alloc VVD %s\n", GetModelName( handle ) );

	// allocate cache space
	MemAlloc_PushAllocDbgInfo( "Models:Vertex data", 0);
	pVvdHdr = (vertexFileHeader_t *)AllocData( MDLCACHE_VERTEXES, cacheLength );
	MemAlloc_PopAllocDbgInfo();

	GetCacheSection( MDLCACHE_VERTEXES )->BeginFrameLocking();

	CacheData( &m_MDLDict[handle]->m_VertexCache, pVvdHdr, cacheLength, pStudioHdr->pszName(), MDLCACHE_VERTEXES, MakeCacheID( handle, MDLCACHE_VERTEXES) );

	// expected 32 byte alignment
	Assert( ((int64)pVvdHdr & 0x1F) == 0 );

	// load minimum vertexes and fixup
	Studio_LoadVertexes( pRawVvdHdr, pVvdHdr, rootLOD, bNeedsTangentS );

	GetCacheSection( MDLCACHE_VERTEXES )->EndFrameLocking();

	return pVvdHdr;
}

//-----------------------------------------------------------------------------
// Load and cache model's specified dynamic data
//-----------------------------------------------------------------------------
vertexFileHeader_t *CMDLCache::LoadVertexData( studiohdr_t *pStudioHdr )
{
	char				pFileName[MAX_PATH];
	MDLHandle_t			handle;

	Assert( pStudioHdr );
	handle = VoidPtrToMDLHandle( pStudioHdr->VirtualModel() );
	Assert( !m_MDLDict[handle]->m_VertexCache );

	studiodata_t *pStudioData = m_MDLDict[handle];

	if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_VERTEX_DATA )
	{
		return NULL;
	}

	intp iAsync = GetAsyncInfoIndex( handle, MDLCACHE_VERTEXES );

	if ( iAsync == NO_ASYNC )
	{
		// load the VVD file
		// use model name for correct path
		MakeFilename( handle, ".vvd", pFileName, sizeof(pFileName) );
		if ( IsX360() )
		{
			char pX360Filename[MAX_PATH];
			UpdateOrCreate( pStudioHdr, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" );
			Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) );
		}

		MdlCacheMsg( "MDLCache: Begin load VVD %s\n", pFileName );

		AsyncInfo_t info;
		if ( IsDebug() )
		{
			memset( &info, 0xdd, sizeof( AsyncInfo_t ) );
		}
		info.hModel = handle;
		info.type = MDLCACHE_VERTEXES;
		info.iAnimBlock = 0;
		info.hControl = NULL;
		LoadData( pFileName, "GAME", mod_load_mesh_async.GetBool(), &info.hControl );
		{
			AUTO_LOCK( m_AsyncMutex );
			iAsync = SetAsyncInfoIndex( handle, MDLCACHE_VERTEXES, m_PendingAsyncs.AddToTail( info ) );
		}
	}

	ProcessPendingAsync( iAsync );

	return (vertexFileHeader_t *)CheckData( m_MDLDict[handle]->m_VertexCache, MDLCACHE_VERTEXES );
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
vertexFileHeader_t *CMDLCache::GetVertexData( MDLHandle_t handle )
{
	if ( mod_test_not_available.GetBool() )
		return NULL;

	if ( mod_test_verts_not_available.GetBool() )
		return NULL;

	return CacheVertexData( GetStudioHdr( handle ) );
}


//-----------------------------------------------------------------------------
// Allocates a cacheable item
//-----------------------------------------------------------------------------
void *CMDLCache::AllocData( MDLCacheDataType_t type, int size )
{
	void *pData = _aligned_malloc( size, 32 );

	if ( !pData )
	{
		Error( "CMDLCache:: Out of memory" );
		return NULL;
	}

	return pData;
}


//-----------------------------------------------------------------------------
// Caches an item
//-----------------------------------------------------------------------------
void CMDLCache::CacheData( DataCacheHandle_t *c, void *pData, int size, const char *name, MDLCacheDataType_t type, DataCacheClientID_t id )
{
	if ( !pData )
	{
		return;
	}

	if ( id == (DataCacheClientID_t)-1 )
		id = (DataCacheClientID_t)(intp)pData;

	GetCacheSection( type )->Add(id, pData, size, c );
}

//-----------------------------------------------------------------------------
// returns the cached data, and moves to the head of the LRU list
// if present, otherwise returns NULL
//-----------------------------------------------------------------------------
void *CMDLCache::CheckData( DataCacheHandle_t c, MDLCacheDataType_t type )
{
	return GetCacheSection( type )->Get( c, true );
}

//-----------------------------------------------------------------------------
// returns the cached data, if present, otherwise returns NULL
//-----------------------------------------------------------------------------
void *CMDLCache::CheckDataNoTouch( DataCacheHandle_t c, MDLCacheDataType_t type )
{
	return GetCacheSection( type )->GetNoTouch( c, true );
}

//-----------------------------------------------------------------------------
// Frees a cache item
//-----------------------------------------------------------------------------
void CMDLCache::UncacheData( DataCacheHandle_t c, MDLCacheDataType_t type, bool bLockedOk )
{
	if ( c == DC_INVALID_HANDLE )
		return;

	if ( !GetCacheSection( type )->IsPresent( c ) )
		return;

	if ( GetCacheSection( type )->BreakLock( c )  && !bLockedOk )
	{
		DevMsg( "Warning: freed a locked resource\n" );
		Assert( 0 );
	}

	const void *pItemData;
	GetCacheSection( type )->Remove( c, &pItemData );

	FreeData( type, (void *)pItemData );
}


//-----------------------------------------------------------------------------
// Frees memory for an item
//-----------------------------------------------------------------------------
void CMDLCache::FreeData( MDLCacheDataType_t type, void *pData )
{
	if ( type != MDLCACHE_ANIMBLOCK )
	{
		_aligned_free( (void *)pData );
	}
	else
	{
		g_pFullFileSystem->FreeOptimalReadBuffer( pData );
	}
}


void CMDLCache::InitPreloadData( bool rebuild )
{
}

void CMDLCache::ShutdownPreloadData()
{
}

//-----------------------------------------------------------------------------
// Work function for processing a model delivered by the queued loader.
// ProcessDataIntoCache() is invoked for each MDL datum.
//-----------------------------------------------------------------------------
void CMDLCache::ProcessQueuedData( ModelParts_t *pModelParts, bool bHeaderOnly )
{
	void *pData;
	int nSize;

	// the studiohdr is critical, ensure it's setup as expected
	MDLHandle_t handle = pModelParts->hMDL;
	studiohdr_t *pStudioHdr = NULL;
	if ( !pModelParts->bHeaderLoaded && ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_MDL ) ) )
	{
		DEBUG_SCOPE_TIMER(mdl);
		pData = pModelParts->Buffers[ModelParts_t::BUFFER_MDL].Base();
		nSize = pModelParts->Buffers[ModelParts_t::BUFFER_MDL].TellMaxPut();
		ProcessDataIntoCache( handle, MDLCACHE_STUDIOHDR, 0, pData, nSize, nSize != 0 );
		LockStudioHdr( handle );
		g_pFullFileSystem->FreeOptimalReadBuffer( pData );
		pModelParts->bHeaderLoaded = true;
	}

	if ( bHeaderOnly )
	{
		return;
	}

	bool bAbort = false;
	pStudioHdr = (studiohdr_t *)CheckDataNoTouch( m_MDLDict[handle]->m_MDLCache, MDLCACHE_STUDIOHDR );
	if ( !pStudioHdr )
	{
		// The header is expected to be loaded and locked, everything depends on it!
		// but if the async read fails, we might not have it
		//Assert( 0 );
		DevWarning( "CMDLCache:: Error MDLCACHE_STUDIOHDR not present for '%s'\n", GetModelName( handle ) );

		// cannot unravel any of this model's dependant data, abort any further processing
		bAbort = true;
	}

	if ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_PHY ) )
	{
		DEBUG_SCOPE_TIMER(phy);
		// regardless of error, call job callback so caller can do cleanup of their context
		pData = pModelParts->Buffers[ModelParts_t::BUFFER_PHY].Base();
		nSize = bAbort ? 0 : pModelParts->Buffers[ModelParts_t::BUFFER_PHY].TellMaxPut();
		ProcessDataIntoCache( handle, MDLCACHE_VCOLLIDE, 0, pData, nSize, nSize != 0 );
		g_pFullFileSystem->FreeOptimalReadBuffer( pData );
	}

	// vvd vertexes before vtx
	if ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_VVD ) )
	{
		DEBUG_SCOPE_TIMER(vvd);
		pData = pModelParts->Buffers[ModelParts_t::BUFFER_VVD].Base();
		nSize = bAbort ? 0 : pModelParts->Buffers[ModelParts_t::BUFFER_VVD].TellMaxPut();
		ProcessDataIntoCache( handle, MDLCACHE_VERTEXES, 0, pData, nSize, nSize != 0 );
		g_pFullFileSystem->FreeOptimalReadBuffer( pData );
	}

	// can construct meshes after vvd and vtx vertexes arrive
	if ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_VTX ) )
	{
		DEBUG_SCOPE_TIMER(vtx);
		pData = pModelParts->Buffers[ModelParts_t::BUFFER_VTX].Base();
		nSize = bAbort ?  0 : pModelParts->Buffers[ModelParts_t::BUFFER_VTX].TellMaxPut();

		// ProcessDataIntoCache() will do an unlock, so lock
		studiodata_t *pStudioData = m_MDLDict[handle];
		GetCacheSection( MDLCACHE_STUDIOHWDATA )->Lock( pStudioData->m_VertexCache );
		{
			// constructing the static meshes isn't thread safe
			AUTO_LOCK( m_QueuedLoadingMutex );
			ProcessDataIntoCache( handle, MDLCACHE_STUDIOHWDATA, 0, pData, nSize, nSize != 0 );
		}
		g_pFullFileSystem->FreeOptimalReadBuffer( pData );
	}

	UnlockStudioHdr( handle );
	delete pModelParts;
}

//-----------------------------------------------------------------------------
// Journals each of the incoming MDL components until all arrive (or error).
// Not all components exist, but that information is not known at job submission.
//-----------------------------------------------------------------------------
void CMDLCache::QueuedLoaderCallback_MDL( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError )
{
	// validity is denoted by a nonzero buffer
	nSize = ( loaderError == LOADERERROR_NONE ) ? nSize : 0;

	// journal each incoming buffer
	ModelParts_t *pModelParts = (ModelParts_t *)pContext;
	ModelParts_t::BufferType_t bufferType = static_cast< ModelParts_t::BufferType_t >((intp)pContext2);
	pModelParts->Buffers[bufferType].SetExternalBuffer( (void *)pData, nSize, nSize, CUtlBuffer::READ_ONLY );
	pModelParts->nLoadedParts += (1 << bufferType);

	// wait for all components
	if ( pModelParts->DoFinalProcessing() )
	{
		if ( !IsPC() )
		{
			// now have all components, process the raw data into the cache
			g_MDLCache.ProcessQueuedData( pModelParts );
		}
		else
		{
			// PC background load path. pull in material dependencies on the fly.
			Assert( ThreadInMainThread() );

			g_MDLCache.ProcessQueuedData( pModelParts, true );

			// preload all possible paths to VMTs
			{
				DEBUG_SCOPE_TIMER(findvmt);
				MaterialLock_t hMatLock = materials->Lock();
				if ( studiohdr_t * pHdr = g_MDLCache.GetStudioHdr( pModelParts->hMDL ) )
				{
					if ( !(pHdr->flags & STUDIOHDR_FLAGS_OBSOLETE) )
					{
						char buf[MAX_PATH];
						V_strcpy( buf, "materials/" );
						int prefixLen = V_strlen( buf );
						
						for ( int t = 0; t < pHdr->numtextures; ++t )
						{
							// XXX this does not take remaps from vtxdata into account;
							// right now i am not caring about that. we will hitch if any
							// LODs remap to materials that are not in the header. (henryg)
							const char *pTexture = pHdr->pTexture(t)->pszName();
							pTexture += ( pTexture[0] == CORRECT_PATH_SEPARATOR || pTexture[0] == INCORRECT_PATH_SEPARATOR );
							for ( int cd = 0; cd < pHdr->numcdtextures; ++cd )
							{
								const char *pCdTexture = pHdr->pCdtexture( cd );
								pCdTexture += ( pCdTexture[0] == CORRECT_PATH_SEPARATOR || pCdTexture[0] == INCORRECT_PATH_SEPARATOR );
								V_ComposeFileName( pCdTexture, pTexture, buf + prefixLen, MAX_PATH - prefixLen );
								V_strncat( buf, ".vmt", MAX_PATH, COPY_ALL_CHARACTERS );
								pModelParts->bMaterialsPending = true;
								const char *pbuf = buf;
								g_pFullFileSystem->AddFilesToFileCache( pModelParts->hFileCache, &pbuf, 1, "GAME" );
								if ( materials->IsMaterialLoaded( buf + prefixLen ) )
								{
									// found a loaded one. still cache it in case it unloads,
									// but we can stop adding more potential paths to the cache
									// since this one is known to be valid.
									break;
								}
							}
						}
					}
				}
				materials->Unlock(hMatLock);
			}

			// queue functor which will start polling every frame by re-queuing itself
			g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) );
		}
	}
}

void CMDLCache::ProcessDynamicLoad( ModelParts_t *pModelParts )
{
	Assert( IsPC() && ThreadInMainThread() );

	if ( !g_pFullFileSystem->IsFileCacheLoaded( pModelParts->hFileCache ) )
	{
		// poll again next frame...
		g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) );
		return;
	}

	if ( pModelParts->bMaterialsPending )
	{
		DEBUG_SCOPE_TIMER(processvmt);
		pModelParts->bMaterialsPending = false;
		pModelParts->bTexturesPending = true;

		MaterialLock_t hMatLock = materials->Lock();
		materials->SetAsyncTextureLoadCache( pModelParts->hFileCache );

		// Load all the materials
		studiohdr_t * pHdr = g_MDLCache.GetStudioHdr( pModelParts->hMDL );
		if ( pHdr && !(pHdr->flags & STUDIOHDR_FLAGS_OBSOLETE) )
		{
			// build strings inside a buffer that already contains a materials/ prefix
			char buf[MAX_PATH];
			V_strcpy( buf, "materials/" );
			int prefixLen = V_strlen( buf );

			// XXX this does not take remaps from vtxdata into account;
			// right now i am not caring about that. we will hitch if any
			// LODs remap to materials that are not in the header. (henryg)
			for ( int t = 0; t < pHdr->numtextures; ++t )
			{
				const char *pTexture = pHdr->pTexture(t)->pszName();
				pTexture += ( pTexture[0] == CORRECT_PATH_SEPARATOR || pTexture[0] == INCORRECT_PATH_SEPARATOR );
				for ( int cd = 0; cd < pHdr->numcdtextures; ++cd )
				{
					const char *pCdTexture = pHdr->pCdtexture( cd );
					pCdTexture += ( pCdTexture[0] == CORRECT_PATH_SEPARATOR || pCdTexture[0] == INCORRECT_PATH_SEPARATOR );
					V_ComposeFileName( pCdTexture, pTexture, buf + prefixLen, MAX_PATH - prefixLen );
					IMaterial* pMaterial = materials->FindMaterial( buf + prefixLen, TEXTURE_GROUP_MODEL, false );
					if ( !IsErrorMaterial( pMaterial ) && !pMaterial->IsPrecached() )
					{
						pModelParts->Materials.AddToTail( pMaterial );
						pMaterial->IncrementReferenceCount();
						// Force texture loads while material system is set to capture
						// them and redirect to an error texture... this will populate
						// the file cache with all the requested textures
						pMaterial->RefreshPreservingMaterialVars();
						break;
					}
				}
			}
		}

		materials->SetAsyncTextureLoadCache( NULL );
		materials->Unlock( hMatLock );

		// poll again next frame... dont want to do too much work right now
		g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) );
		return;
	}
	
	if ( pModelParts->bTexturesPending )
	{
		DEBUG_SCOPE_TIMER(matrefresh);
		pModelParts->bTexturesPending = false;

		// Perform the real material loads now while raw texture files are cached.
		FOR_EACH_VEC( pModelParts->Materials, i )
		{
			IMaterial* pMaterial = pModelParts->Materials[i];
			if ( !IsErrorMaterial( pMaterial ) && pMaterial->IsPrecached() )
			{
				// Do a full reload to get the correct textures and computed flags
				pMaterial->Refresh();
			}
		}

		// poll again next frame... dont want to do too much work right now
		g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) );
		return;
	}

	// done. finish and clean up.
	Assert( !pModelParts->bTexturesPending && !pModelParts->bMaterialsPending );

	// pull out cached items we want to overlap with final processing
	CleanupModelParts_t *pCleanup = new CleanupModelParts_t;
	pCleanup->hFileCache = pModelParts->hFileCache;
	pCleanup->Materials.Swap( pModelParts->Materials );
	g_pQueuedLoader->QueueCleanupDynamicLoadFunctor( CreateFunctor( CleanupDynamicLoad, pCleanup ) );

	{
		DEBUG_SCOPE_TIMER(processall);
		g_MDLCache.ProcessQueuedData( pModelParts ); // pModelParts is deleted here
	}
}

void CMDLCache::CleanupDynamicLoad( CleanupModelParts_t *pCleanup )
{
	Assert( IsPC() && ThreadInMainThread() );

	// remove extra material refs, unload cached files
	FOR_EACH_VEC( pCleanup->Materials, i )
	{
		pCleanup->Materials[i]->DecrementReferenceCount();
	}

	g_pFullFileSystem->DestroyFileCache( pCleanup->hFileCache );

	delete pCleanup;
}

//-----------------------------------------------------------------------------
// Build a queued loader job to get the MDL ant all of its components into the cache.
//-----------------------------------------------------------------------------
bool CMDLCache::PreloadModel( MDLHandle_t handle )
{
	if ( g_pQueuedLoader->IsDynamic() == false )
	{
		if ( !IsX360() )
		{
			return false;
		}

		if ( !g_pQueuedLoader->IsMapLoading() || handle == MDLHANDLE_INVALID )
		{
			return false;
		}
	}

	if ( !g_pQueuedLoader->IsBatching() )
	{
		// batching must be active, following code depends on its behavior
		DevWarning( "CMDLCache:: Late preload of model '%s'\n", GetModelName( handle ) );
		return false;
	}

	// determine existing presence
	// actual necessity is not established here, allowable absent files need their i/o error to occur
	bool bNeedsMDL = !IsDataLoaded( handle, MDLCACHE_STUDIOHDR );
	bool bNeedsVTX = !IsDataLoaded( handle, MDLCACHE_STUDIOHWDATA );
	bool bNeedsVVD = !IsDataLoaded( handle, MDLCACHE_VERTEXES );
	bool bNeedsPHY = !IsDataLoaded( handle, MDLCACHE_VCOLLIDE );
	if ( !bNeedsMDL && !bNeedsVTX && !bNeedsVVD && !bNeedsPHY )
	{
		// nothing to do
		return true;
	}

	char szFilename[MAX_PATH];
	char szNameOnDisk[MAX_PATH];
	V_strncpy( szFilename, GetActualModelName( handle ), sizeof( szFilename ) );
	V_StripExtension( szFilename, szFilename, sizeof( szFilename ) );

	// need to gather all model parts (mdl, vtx, vvd, phy, ani)
	ModelParts_t *pModelParts = new ModelParts_t;
	pModelParts->hMDL = handle;
	pModelParts->hFileCache = g_pFullFileSystem->CreateFileCache();

	// create multiple loader jobs to perform gathering i/o operations
	LoaderJob_t loaderJob;
	loaderJob.m_pPathID = "GAME";
	loaderJob.m_pCallback = QueuedLoaderCallback_MDL;
	loaderJob.m_pContext = (void *)pModelParts;
	loaderJob.m_Priority = LOADERPRIORITY_DURINGPRELOAD;
	loaderJob.m_bPersistTargetData = true;

	if ( bNeedsMDL )
	{
		V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.mdl", szFilename, GetPlatformExt() );
		loaderJob.m_pFilename = szNameOnDisk;
		loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_MDL;
		g_pQueuedLoader->AddJob( &loaderJob );
		pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_MDL;
	}

	if ( bNeedsVTX )
	{
		// vtx extensions are .xxx.vtx, need to re-form as, ???.xxx.yyy.vtx
		char szTempName[MAX_PATH];
		V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s", szFilename, GetVTXExtension() );
		V_StripExtension( szNameOnDisk, szTempName, sizeof( szTempName ) );
		V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.vtx", szTempName, GetPlatformExt() );
		loaderJob.m_pFilename = szNameOnDisk;
		loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_VTX;
		g_pQueuedLoader->AddJob( &loaderJob );
		pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_VTX;
	}

	if ( bNeedsVVD )
	{
		V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.vvd", szFilename, GetPlatformExt() );
		loaderJob.m_pFilename = szNameOnDisk;
		loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_VVD;
		g_pQueuedLoader->AddJob( &loaderJob );
		pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_VVD;
	}

	if ( bNeedsPHY )
	{
		V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.phy", szFilename, GetPlatformExt() );
		loaderJob.m_pFilename = szNameOnDisk;
		loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_PHY;
		g_pQueuedLoader->AddJob( &loaderJob );
		pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_PHY;
	}

	if ( !pModelParts->nExpectedParts )
	{
		g_pFullFileSystem->DestroyFileCache( pModelParts->hFileCache );
		delete pModelParts;
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Clear the STUDIODATA_ERROR_MODEL flag.
//-----------------------------------------------------------------------------
void CMDLCache::ResetErrorModelStatus( MDLHandle_t handle )
{
	if ( handle == MDLHANDLE_INVALID )
		return;

	m_MDLDict[handle]->m_nFlags &= ~STUDIODATA_ERROR_MODEL;
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CMDLCache::MarkFrame()
{
	ProcessPendingAsyncs();
}

//-----------------------------------------------------------------------------
// Purpose: bind studiohdr_t support functions to the mdlcacher
//-----------------------------------------------------------------------------
const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *pModelName ) const
{
	MDLHandle_t handle = g_MDLCache.FindMDL( pModelName );
	*cache = MDLHandleToVirtual(handle);
	return g_MDLCache.GetStudioHdr( handle );
}

virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const
{
	if (numincludemodels == 0)
		return NULL;

    return g_MDLCache.GetVirtualModelFast( this, VoidPtrToMDLHandle( VirtualModel() ) );
}

byte *studiohdr_t::GetAnimBlock( int i ) const
{
	return g_MDLCache.GetAnimBlock( VoidPtrToMDLHandle( VirtualModel() ), i );
}

int studiohdr_t::GetAutoplayList( unsigned short **pOut ) const
{
	return g_MDLCache.GetAutoplayList( VoidPtrToMDLHandle( VirtualModel() ), pOut );
}

const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const
{
	return g_MDLCache.GetStudioHdr( VoidPtrToMDLHandle( cache ) );
}