//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"

#include "utlpriorityqueue.h"
#include "utlmap.h"
#include "isaverestore.h"
#include "physics.h"
#include "physics_saverestore.h"
#include "saverestoretypes.h"
#include "gamestringpool.h"
#include "datacache/imdlcache.h"

#if !defined( CLIENT_DLL )
#include "entitylist.h"
#endif

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

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

static short PHYS_SAVE_RESTORE_VERSION = 5;

struct PhysBlockHeader_t
{
	int				nSaved;
	IPhysicsObject	*pWorldObject;

	inline void Clear()
	{
		nSaved = 0;
		pWorldObject = 0;
	}

	DECLARE_SIMPLE_DATADESC();
};
BEGIN_SIMPLE_DATADESC( PhysBlockHeader_t )
	DEFINE_FIELD( nSaved,	FIELD_INTEGER ),
	// NOTE: We want to save the actual address here for remapping, so use an integer
	DEFINE_FIELD( pWorldObject, FIELD_INTEGER ),	
END_DATADESC()

#if defined(_STATIC_LINKED) && defined(CLIENT_DLL)
const char *g_ppszPhysTypeNames[PIID_NUM_TYPES] =
{
	"Unknown",
	"IPhysicsObject",
	"IPhysicsFluidController",
	"IPhysicsSpring",
	"IPhysicsConstraintGroup",
	"IPhysicsConstraint",
	"IPhysicsShadowController",
	"IPhysicsPlayerController",
	"IPhysicsMotionController",
	"IPhysicsVehicleController",
};
#endif

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

struct BBox_t
{
	Vector mins, maxs;
};

struct Sphere_t
{
	float radius;
};


struct PhysObjectHeader_t
{
	PhysObjectHeader_t()
	{
		memset( this, 0, sizeof(*this) );
	}

	PhysInterfaceId_t 	type;
	EHANDLE				hEntity;
	string_t			fieldName;
	int 				nObjects;
	string_t			modelName;
	BBox_t				bbox;
	Sphere_t			sphere;
	int					iCollide;
	
	DECLARE_SIMPLE_DATADESC();
};

BEGIN_SIMPLE_DATADESC( PhysObjectHeader_t )
  	DEFINE_FIELD( type,			FIELD_INTEGER ),
  	DEFINE_FIELD( hEntity,		FIELD_EHANDLE ),
  	DEFINE_FIELD( fieldName,	FIELD_STRING ),
  	DEFINE_FIELD( nObjects,		FIELD_INTEGER ),
  	DEFINE_FIELD( modelName,	FIELD_STRING ),

	// Silence, Classcheck!
	// DEFINE_FIELD( bbox, BBox_t ),
	// DEFINE_FIELD( sphere, Sphere_t ),

  	DEFINE_FIELD( bbox.mins,	FIELD_VECTOR ),
  	DEFINE_FIELD( bbox.maxs,	FIELD_VECTOR ),
  	DEFINE_FIELD( sphere.radius, FIELD_FLOAT ),
  	DEFINE_FIELD( iCollide,		FIELD_INTEGER ),
END_DATADESC()

//-----------------------------------------------------------------------------
// Purpose:	The central manager of physics save/load
//

class CPhysSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler, 
									 public IPhysSaveRestoreManager
#if !defined( CLIENT_DLL )
									 , public IEntityListener
#endif
{
	struct QueuedItem_t;
public:
	CPhysSaveRestoreBlockHandler()
	{
		m_QueuedSaves.SetLessFunc( SaveQueueFunc );
		SetDefLessFunc( m_QueuedRestores );
		SetDefLessFunc( m_PhysObjectModels );
		SetDefLessFunc( m_PhysObjectCustomModels );
		SetDefLessFunc( m_PhysCollideBBoxModels );
	}

	const char *GetBlockName()
	{
		return "Physics";
	}

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

	virtual void PreSave( CSaveRestoreData * ) 
	{
		m_blockHeader.Clear();
	}
	
	//---------------------------------

	virtual void Save( ISave *pSave ) 
	{
		m_blockHeader.pWorldObject = g_PhysWorldObject;
		m_blockHeader.nSaved = m_QueuedSaves.Count();

		while ( m_QueuedSaves.Count() )
		{
			const QueuedItem_t &item = m_QueuedSaves.ElementAtHead();
			
			CBaseEntity *pOwner = item.header.hEntity.Get();
			
			if ( pOwner )
			{
				pSave->WriteAll( &item.header );
				pSave->StartBlock(); //  Need block here in case entity is NULL on load
				if ( item.header.nObjects )
				{
					for ( int i = 0; i < item.header.nObjects; i++ )
					{
						// Starting a block here allows the implementation of any individual physics
						// class save/load to change non-trivially while retaining the overall
						// integrity of the savefile.
						pSave->StartBlock();
						SavePhysicsObject( pSave, pOwner, item.ppPhysObj[i], item.header.type );
						pSave->EndBlock();
					}
				}
				// else, it will simply be recreated on restore
				pSave->EndBlock();
			}
			m_QueuedSaves.RemoveAtHead();
		}
	}
	
	//---------------------------------

	virtual void WriteSaveHeaders( ISave *pSave )
	{
		pSave->WriteShort( &PHYS_SAVE_RESTORE_VERSION );
		pSave->WriteAll( &m_blockHeader );
	}
	
	//---------------------------------

	virtual void PostSave() 
	{
		m_QueuedSaves.Purge();
	}
	
	//---------------------------------

	virtual void PreRestore() 
	{
#if !defined( CLIENT_DLL )
		gEntList.AddListenerEntity( this );
#endif

		// UNDONE: This never runs!!!!
		if ( physenv )
		{
			physprerestoreparams_t params;
			params.recreatedObjectCount = 0;
			physenv->PreRestore( params );
		}
	}
	
	//---------------------------------

	virtual void ReadRestoreHeaders( IRestore *pRestore )
	{
		// No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so.
		short version = pRestore->ReadShort();
		m_fDoLoad = ( version == PHYS_SAVE_RESTORE_VERSION );

		pRestore->ReadAll( &m_blockHeader );
	}

	//---------------------------------
	
	virtual void Restore( IRestore *pRestore, bool ) 
	{
		if ( m_fDoLoad )
		{
			if ( physenv )
			{
				physprerestoreparams_t params;
				params.recreatedObjectCount = 1;
				params.recreatedObjectList[0].pNewObject = g_PhysWorldObject;
				params.recreatedObjectList[0].pOldObject = m_blockHeader.pWorldObject;
				physenv->PreRestore( params );
			}

			PhysObjectHeader_t header;

			while ( m_blockHeader.nSaved-- )
			{
				pRestore->ReadAll( &header );
				pRestore->StartBlock();
				
				if ( header.hEntity != NULL )
				{
					RestoreBlock( pRestore, header );
				}

				pRestore->EndBlock();
			}
		}
	}
	
	//---------------------------------
	
	void RestoreBlock( IRestore *pRestore, const PhysObjectHeader_t &header ) 
	{
		CBaseEntity *  pOwner  = header.hEntity.Get();
		unsigned short iQueued = m_QueuedRestores.Find( pOwner );
		
		if ( iQueued != m_QueuedRestores.InvalidIndex() )
		{
			MDLCACHE_CRITICAL_SECTION();
			if ( pOwner->ShouldSavePhysics() && header.nObjects > 0 )
			{
				QueuedItem_t *pItem = m_QueuedRestores[iQueued]->FindItem( header.fieldName );
				
				if ( pItem )
				{
					int nObjects = min( header.nObjects, pItem->header.nObjects );
					if ( pItem->header.type == PIID_IPHYSICSOBJECT && nObjects == 1 )
					{
						RestorePhysicsObjectAndModel( pRestore, header, pItem, nObjects );
					}
					else
					{
						void **ppPhysObj = pItem->ppPhysObj;
						
						for ( int i = 0; i < nObjects; i++ )
						{
							pRestore->StartBlock();
							RestorePhysicsObject( pRestore, header, ppPhysObj + i );
							pRestore->EndBlock();
							if ( header.type == PIID_IPHYSICSMOTIONCONTROLLER )
							{
								void *pObj = ppPhysObj[i];
								IPhysicsMotionController *pController = (IPhysicsMotionController *)pObj;
								if ( pController )
								{
									// If the entity is the motion callback handler, then automatically set it
									// NOTE: This is usually the case
									IMotionEvent *pEvent = dynamic_cast<IMotionEvent *>(pOwner);
									if ( pEvent )
									{
										pController->SetEventHandler( pEvent );
									}
								}
							}
						}
					}
				}
			}
			else
				pOwner->CreateVPhysics();
		}
	}
	
	
	//---------------------------------

	void RestorePhysicsObjectAndModel( IRestore *pRestore, const PhysObjectHeader_t &header, CPhysSaveRestoreBlockHandler::QueuedItem_t *pItem, int nObjects )
	{
		if ( nObjects == 1 )
		{
			pRestore->StartBlock();
			
			CPhysCollide *pPhysCollide   = NULL;
			int 		  modelIndex 	 = -1;
			bool 		  fCustomCollide = false;
			
			if ( header.modelName != NULL_STRING )
			{
				CBaseEntity *pGlobalEntity = header.hEntity;
#if !defined( CLIENT_DLL )
				if ( NULL_STRING != pGlobalEntity->m_iGlobalname )
				{
					modelIndex = pGlobalEntity->GetModelIndex();
				}
				else
#endif
				{
					modelIndex = modelinfo->GetModelIndex( STRING( header.modelName ) );
					pGlobalEntity = NULL;
				}

				if ( modelIndex != -1 )
				{
					vcollide_t *pCollide = modelinfo->GetVCollide( modelIndex );
					if ( pCollide )
					{
						if ( pCollide->solidCount > 0 && pCollide->solids && header.iCollide < pCollide->solidCount )
							pPhysCollide = pCollide->solids[header.iCollide];
					}
				}
			}
			else if ( header.bbox.mins != vec3_origin || header.bbox.maxs != vec3_origin )
			{
				pPhysCollide = PhysCreateBbox( header.bbox.mins, header.bbox.maxs );
				fCustomCollide = true;
			}
			else if ( header.sphere.radius != 0 )
			{
				// HACKHACK: Handle spheres here!!!
				if ( !(*pItem->ppPhysObj) )
				{
					RestorePhysicsObject( pRestore, header, pItem->ppPhysObj, NULL );
				}
				return;
			}
			
			if ( pPhysCollide )
			{
				if ( !(*pItem->ppPhysObj) )
				{
					RestorePhysicsObject( pRestore, header, pItem->ppPhysObj, pPhysCollide );
					if ( (*pItem->ppPhysObj) )
					{
						IPhysicsObject *pObject = (IPhysicsObject *)(*pItem->ppPhysObj);
						if ( !fCustomCollide )
						{
							AssociateModel( pObject, modelIndex );
						}
						else
						{
							AssociateModel( pObject, pPhysCollide );
						}
					}
					else
						DevMsg( "Failed to restore physics object\n" );
				}
				else
					DevMsg( "Physics object pointer unexpectedly non-null before restore. Should be creating physics object in CreatePhysics()?\n" );
			}
			else
				DevMsg( "Failed to reestablish collision model for object\n" );
				
			pRestore->EndBlock();
		}
		else
			DevMsg( "Don't know how to reconsitite models for physobj array \n" );
	}
	
	//---------------------------------
	
	virtual void PostRestore() 
	{
		if ( physenv )
			physenv->PostRestore();

		unsigned short i = m_QueuedRestores.FirstInorder();
		while ( i != m_QueuedRestores.InvalidIndex() )
		{
			delete m_QueuedRestores[i];
			i = m_QueuedRestores.NextInorder( i );
		}
		
		m_QueuedRestores.RemoveAll();
#if !defined( CLIENT_DLL )
		gEntList.RemoveListenerEntity( this );
#endif
	}
	
	//---------------------------------
	
	void QueueSave( CBaseEntity *pOwner, typedescription_t *pTypeDesc, void **ppPhysObj, PhysInterfaceId_t type )
	{
		if ( !pOwner )
			return;

		bool fOnlyNotingExistence = !pOwner->ShouldSavePhysics();
		
		QueuedItem_t item;
		
		item.ppPhysObj		= ppPhysObj;
		item.header.hEntity = pOwner;
		item.header.type	= type;
		item.header.nObjects = ( !fOnlyNotingExistence ) ? pTypeDesc->fieldSize : 0;
		item.header.fieldName = AllocPooledString( pTypeDesc->fieldName ); 	
																	// A pooled string is used here because there is no way
																	// right now to save a non-string_t string and have it 
																	// compressed in the save symbol tables. Furthermore,
																	// the field name would normally be in the string
																	// pool anyway. (toml 12-10-02)
		item.header.modelName = NULL_STRING;
		memset( &item.header.bbox, 0, sizeof( item.header.bbox ) );
		item.header.sphere.radius = 0;
		
		if ( !fOnlyNotingExistence && type == PIID_IPHYSICSOBJECT )
		{
			// Don't doing the box thing for things like wheels on cars
			IPhysicsObject *pPhysObj = (IPhysicsObject *)(*ppPhysObj);

			if ( pPhysObj )
			{
				item.header.modelName = GetModelName( pPhysObj );
				item.header.iCollide = physcollision->CollideIndex( pPhysObj->GetCollide() );
				if ( item.header.modelName == NULL_STRING )
				{
					BBox_t *pBBox = GetBBox( pPhysObj );
					if ( pBBox != NULL )
					{
						item.header.bbox = *pBBox;
					}
					else 
					{
						if ( pPhysObj && pPhysObj->GetSphereRadius() != 0 )
						{
							item.header.sphere.radius = pPhysObj->GetSphereRadius();
						}
						else
						{
							DevMsg( "Don't know how to save model for physics object (class \"%s\")\n", pOwner->GetClassname() );
						}
					}
				}
			}
		}

		m_QueuedSaves.Insert( item );
	}

	//---------------------------------
	
	void QueueRestore( CBaseEntity *pOwner, typedescription_t *pTypeDesc, void **ppPhysObj, PhysInterfaceId_t type )
	{
		CEntityRestoreSet *pEntitySet = NULL;
		unsigned short 	   iEntitySet = m_QueuedRestores.Find( pOwner );
		
		if ( iEntitySet != m_QueuedRestores.InvalidIndex() )
		{
			pEntitySet = m_QueuedRestores[iEntitySet];
		}
		else
		{
			pEntitySet = new CEntityRestoreSet;
			m_QueuedRestores.Insert( pOwner, pEntitySet );
		}
		
		pEntitySet->Add( pOwner, pTypeDesc, ppPhysObj, type );

		memset( ppPhysObj, 0, pTypeDesc->fieldSize * sizeof( void * ) );
	}

	//---------------------------------
	
	void SavePhysicsObject( ISave *pSave, CBaseEntity *pOwner, void *pObject, PhysInterfaceId_t type )
	{
		if ( physenv )
		{
			if ( !pObject )
				return;
			physsaveparams_t params = { pSave, pObject, type };
			physenv->Save( params );
		}
	}
	
	//---------------------------------
	
	void RestorePhysicsObject( IRestore *pRestore, const PhysObjectHeader_t &header, void **ppObject, const CPhysCollide *pCollide = NULL )
	{
		if ( physenv )
		{
			physrestoreparams_t params = { pRestore, ppObject, header.type, header.hEntity.Get(), STRING(header.modelName), pCollide, physenv, physgametrace };
			physenv->Restore( params );
		}
	}
#if !defined( CLIENT_DLL )	
	//-----------------------------------------------------
	// IEntityListener methods
	// This object is only a listener during restore	
	virtual void OnEntityCreated( CBaseEntity *pEntity )
	{
	}

	//---------------------------------
	
	virtual void OnEntityDeleted( CBaseEntity *pEntity )
	{
		unsigned short iEntitySet = m_QueuedRestores.Find( pEntity );
		
		if ( iEntitySet != m_QueuedRestores.InvalidIndex() )
		{
			delete m_QueuedRestores[iEntitySet];
			m_QueuedRestores.RemoveAt( iEntitySet );
		}
	}
#endif

	//-----------------------------------------------------
	// IPhysSaveRestoreManager methods
	
	virtual void NoteBBox( const Vector &mins, const Vector &maxs, CPhysCollide *pCollide )
	{
		if ( pCollide && m_PhysCollideBBoxModels.Find( pCollide ) == m_PhysCollideBBoxModels.InvalidIndex() )
		{
			BBox_t box;
			box.mins = mins;
			box.maxs = maxs;
			m_PhysCollideBBoxModels.Insert( pCollide, box );
		}
	}

	//---------------------------------
	
	virtual void AssociateModel( IPhysicsObject *pObject, int modelIndex )
	{
		Assert( m_PhysObjectModels.Find( pObject ) == m_PhysObjectModels.InvalidIndex() );
		m_PhysObjectModels.Insert( pObject, modelIndex );
	}

	//---------------------------------
	
	virtual void AssociateModel( IPhysicsObject *pObject, const CPhysCollide *pModel )
	{
		Assert( m_PhysObjectCustomModels.Find( pObject ) == m_PhysObjectCustomModels.InvalidIndex() );
		m_PhysObjectCustomModels.Insert( pObject, pModel );
	}

	//---------------------------------
	
	virtual void ForgetModel( IPhysicsObject *pObject )
	{
		if ( !m_PhysObjectModels.Remove( pObject ) )
			m_PhysObjectCustomModels.Remove( pObject );
	}

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

	virtual void ForgetAllModels()
	{
		m_PhysObjectModels.RemoveAll();
		m_PhysObjectCustomModels.RemoveAll();
		m_PhysCollideBBoxModels.RemoveAll();
	}

	//---------------------------------
	
	string_t GetModelName( IPhysicsObject *pObject )
	{
		int i = m_PhysObjectModels.Find( pObject );
		if ( i == m_PhysObjectModels.InvalidIndex() )
			return NULL_STRING;
		return AllocPooledString( modelinfo->GetModelName( modelinfo->GetModel( m_PhysObjectModels[i] ) ) );
	}
	
	//---------------------------------
	
	BBox_t * GetBBox( IPhysicsObject *pObject )
	{
		int i = m_PhysObjectCustomModels.Find( pObject );
		if ( i == m_PhysObjectCustomModels.InvalidIndex() )
			return NULL;
		i = m_PhysCollideBBoxModels.Find( m_PhysObjectCustomModels[i] );
		if ( i == m_PhysCollideBBoxModels.InvalidIndex() )
			return NULL;
		return &(m_PhysCollideBBoxModels[i]);
	}

	//---------------------------------
	
private:
	struct QueuedItem_t
	{
		PhysObjectHeader_t	  header;
	 	void **				  ppPhysObj;
	};
	
	class CEntityRestoreSet : public CUtlVector<QueuedItem_t>
	{
	public:
		int Add( CBaseEntity *pOwner, typedescription_t *pTypeDesc, void **ppPhysObj, PhysInterfaceId_t type )
		{
			int i = AddToTail();
			
			Assert( ppPhysObj );
			Assert( *ppPhysObj == NULL ); // expected field to have been cleared
			Assert( pOwner );

			QueuedItem_t &item = Element( i );

			item.ppPhysObj			= ppPhysObj;
			item.header.hEntity 	= pOwner;
			item.header.type		= type;
			item.header.nObjects 	= pTypeDesc->fieldSize;
			item.header.fieldName 	= AllocPooledString( pTypeDesc->fieldName ); 	// See comment in CPhysSaveRestoreBlockHandler::QueueSave()
			
			return i;
		}
		
		QueuedItem_t *FindItem( string_t itemFieldName )
		{
			// generally, the set is very small, usually one, so linear search is not too gruesome;
			for ( int i = 0; i < Count(); i++ )
			{
				string_t testName = Element(i).header.fieldName;
				Assert( ( testName == itemFieldName && strcmp( STRING( testName ), STRING( itemFieldName ) ) == 0 ) ||
						( testName != itemFieldName && strcmp( STRING( testName ), STRING( itemFieldName ) ) != 0 ) );
				
				if ( testName == itemFieldName )
					return &(Element(i));
			}
			return NULL;
		}
	};
	
	//---------------------------------
	
	static bool SaveQueueFunc( const QueuedItem_t &left, const QueuedItem_t &right )
	{
		if ( left.header.type == right.header.type )
			return ( left.header.hEntity->entindex() > right.header.hEntity->entindex() );

		return ( left.header.type > right.header.type );
	}
	
	//---------------------------------

	CUtlPriorityQueue<QueuedItem_t> 			m_QueuedSaves;
	CUtlMap<CBaseEntity *, CEntityRestoreSet *>	m_QueuedRestores;
	bool 										m_fDoLoad;

	//---------------------------------
	
	CUtlMap<IPhysicsObject *, int>					m_PhysObjectModels;
	CUtlMap<IPhysicsObject *, const CPhysCollide *>	m_PhysObjectCustomModels;
	CUtlMap<const CPhysCollide *, BBox_t>			m_PhysCollideBBoxModels;

	//---------------------------------
	
	PhysBlockHeader_t							m_blockHeader;
};

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

CPhysSaveRestoreBlockHandler g_PhysSaveRestoreBlockHandler;

IPhysSaveRestoreManager *g_pPhysSaveRestoreManager = &g_PhysSaveRestoreBlockHandler;

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

ISaveRestoreBlockHandler *GetPhysSaveRestoreBlockHandler()
{
	return &g_PhysSaveRestoreBlockHandler;
}

static bool IsValidEntityPointer( void *ptr )
{
#if !defined( CLIENT_DLL )
	return gEntList.IsEntityPtr( ptr );
#else
	// Walk entities looking for pointer
	int c = ClientEntityList().GetHighestEntityIndex();
	for ( int i = 0; i <= c; i++ )
	{
		CBaseEntity *e = ClientEntityList().GetBaseEntity( i );
		if ( !e )
			continue;

		if ( e == ptr )
			return true;
	}
	return false;
#endif
}

//-----------------------------------------------------------------------------
// Purpose:	Classifies field and queues it up for physics save/restore.
//

class CPhysObjSaveRestoreOps : public CDefSaveRestoreOps
{
public:
	virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave )
	{
		CBaseEntity *pOwnerEntity = pSave->GetGameSaveRestoreInfo()->GetCurrentEntityContext();

		bool bFoundEntity = true;
		
		if ( IsValidEntityPointer(pOwnerEntity) == false )
		{
			bFoundEntity = false;

#if defined( CLIENT_DLL )
			pOwnerEntity = ClientEntityList().GetBaseEntityFromHandle( pOwnerEntity->GetRefEHandle() );

			if ( pOwnerEntity  )
			{
				bFoundEntity = true;
			}
#endif
		}

		AssertMsg( pOwnerEntity && bFoundEntity == true, "Physics save/load is only suitable for entities" );

		if ( m_type == PIID_UNKNOWN )
		{
			AssertMsg( 0, "Unknown physics save/load type");
			return;
		}
		g_PhysSaveRestoreBlockHandler.QueueSave( pOwnerEntity, fieldInfo.pTypeDesc, (void **)fieldInfo.pField, m_type );
	}
	
	virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore )
	{
		CBaseEntity *pOwnerEntity = pRestore->GetGameSaveRestoreInfo()->GetCurrentEntityContext();

		bool bFoundEntity = true;
		
		if ( IsValidEntityPointer(pOwnerEntity) == false )
		{
			bFoundEntity = false;

#if defined( CLIENT_DLL )
			pOwnerEntity = ClientEntityList().GetBaseEntityFromHandle( pOwnerEntity->GetRefEHandle() );

			if ( pOwnerEntity  )
			{
				bFoundEntity = true;
			}
#endif
		}

		AssertMsg( pOwnerEntity && bFoundEntity == true, "Physics save/load is only suitable for entities" );

		if ( m_type == PIID_UNKNOWN )
		{
			AssertMsg( 0, "Unknown physics save/load type");
			return;
		}
		
		g_PhysSaveRestoreBlockHandler.QueueRestore( pOwnerEntity, fieldInfo.pTypeDesc, (void **)fieldInfo.pField, m_type );
	}
	
	virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo )
	{
		memset( fieldInfo.pField, 0, fieldInfo.pTypeDesc->fieldSize * sizeof( void * ) );
	}
	
	virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo )
	{
		void **ppPhysObj = (void **)fieldInfo.pField;
		int nObjects = fieldInfo.pTypeDesc->fieldSize;
		for ( int i = 0; i < nObjects; i++ )
		{
			if ( ppPhysObj[i] != NULL )
				return false;
		}
		return true;
	}
	
	PhysInterfaceId_t m_type;
};

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

CPhysObjSaveRestoreOps g_PhysObjSaveRestoreOps[PIID_NUM_TYPES];

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

ISaveRestoreOps *GetPhysObjSaveRestoreOps( PhysInterfaceId_t type )
{
	static bool inited;
	if ( !inited )
	{
		inited = true;
		for ( int i = 0; i < PIID_NUM_TYPES; i++ )
		{
			g_PhysObjSaveRestoreOps[i].m_type = (PhysInterfaceId_t)i;
		}
	}
	return &g_PhysObjSaveRestoreOps[type];
}

//=============================================================================
