//========= Copyright © 1996-2007, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "blob_networkbypass.h" #include "ispsharedmemory.h" #ifndef CLIENT_DLL #include "npc_surface.h" #endif #include "tier0/memdbgon.h" BlobNetworkBypass_t *g_pBlobNetworkBypass; #ifdef CLIENT_DLL CInterpolatedVar< Vector > s_PositionInterpolators[BLOB_MAX_LEVEL_PARTICLES]; CInterpolatedVar< float > s_RadiusInterpolators[BLOB_MAX_LEVEL_PARTICLES]; CInterpolatedVar< Vector > s_ClosestSurfDirInterpolators[BLOB_MAX_LEVEL_PARTICLES]; BlobParticleInterpolation_t g_BlobParticleInterpolation; void BlobNetworkBypass_CustomDemoDataCallback( uint8 *pData, size_t iSize ); #endif class CBlobParticleNetworkBypassAutoGame : public CAutoGameSystemPerFrame { public: virtual bool Init() { m_pSharedMemory = engine->GetSinglePlayerSharedMemorySpace( "BlobParticleNetworkBypass" ); m_pSharedMemory->Init( sizeof( BlobNetworkBypass_t ) ); g_pBlobNetworkBypass = (BlobNetworkBypass_t *)m_pSharedMemory->Base(); #ifdef CLIENT_DLL float fInterpAmount = TICK_INTERVAL * (C_BaseEntity::IsSimulatingOnAlternateTicks()?2:1); for( int i = 0; i != BLOB_MAX_LEVEL_PARTICLES; ++i ) { s_PositionInterpolators[i].Setup( &g_BlobParticleInterpolation.vInterpolatedPositions[i], LATCH_ANIMATION_VAR ); //LATCH_SIMULATION_VAR, LATCH_ANIMATION_VAR s_PositionInterpolators[i].SetInterpolationAmount( fInterpAmount ); //fInterpAmount s_RadiusInterpolators[i].Setup( &g_BlobParticleInterpolation.vInterpolatedRadii[i], LATCH_ANIMATION_VAR ); //LATCH_SIMULATION_VAR, LATCH_ANIMATION_VAR s_RadiusInterpolators[i].SetInterpolationAmount( fInterpAmount ); //fInterpAmount s_ClosestSurfDirInterpolators[i].Setup( &g_BlobParticleInterpolation.vInterpolatedClosestSurfDir[i], LATCH_ANIMATION_VAR ); //LATCH_SIMULATION_VAR, LATCH_ANIMATION_VAR s_ClosestSurfDirInterpolators[i].SetInterpolationAmount( fInterpAmount ); //fInterpAmount } m_iOldHighestIndexUsed = 0; memset( &m_bOldInUse, 0, sizeof( m_bOldInUse ) ); engine->RegisterDemoCustomDataCallback( MAKE_STRING( "BlobNetworkBypass_CustomDemoDataCallback" ), BlobNetworkBypass_CustomDemoDataCallback ); #endif return true; } virtual void Shutdown() { m_pSharedMemory->Release(); m_pSharedMemory = NULL; g_pBlobNetworkBypass = NULL; } #ifdef CLIENT_DLL virtual void PreRender( void ); unsigned int m_iOldHighestIndexUsed; CBitVec m_bOldInUse; #else virtual void PreClientUpdate() { CNPC_Surface::UpdateBypassParticleData(); } #endif ISPSharedMemory *m_pSharedMemory; }; static CBlobParticleNetworkBypassAutoGame s_CBPNBAG; #ifndef CLIENT_DLL int AllocateBlobNetworkBypassIndex( void ) { int retval; if( g_pBlobNetworkBypass->iNumParticlesAllocated == g_pBlobNetworkBypass->iHighestIndexUsed ) { //no holes in the allocations, allocate from the end retval = g_pBlobNetworkBypass->iHighestIndexUsed; ++g_pBlobNetworkBypass->iHighestIndexUsed; } else { CBitVec notUsed; g_pBlobNetworkBypass->bCurrentlyInUse.Not( ¬Used ); retval = notUsed.FindNextSetBit( 0 ); Assert( retval < (int)g_pBlobNetworkBypass->iHighestIndexUsed ); } ++g_pBlobNetworkBypass->iNumParticlesAllocated; g_pBlobNetworkBypass->bCurrentlyInUse.Set( retval ); return retval; } void ReleaseBlobNetworkBypassIndex( int iIndex ) { Assert( g_pBlobNetworkBypass->bCurrentlyInUse.IsBitSet( iIndex ) ); g_pBlobNetworkBypass->bCurrentlyInUse.Clear( iIndex ); g_pBlobNetworkBypass->vParticlePositions[iIndex] = vec3_origin; g_pBlobNetworkBypass->vParticleRadii[iIndex] = 1.0f; g_pBlobNetworkBypass->vParticleClosestSurfDir[iIndex] = vec3_origin; --g_pBlobNetworkBypass->iNumParticlesAllocated; Assert( iIndex < (int)g_pBlobNetworkBypass->iHighestIndexUsed ); if( iIndex == ((int)g_pBlobNetworkBypass->iHighestIndexUsed - 1) ) { //search for newest high index int iOldHighestIntUsed = g_pBlobNetworkBypass->iHighestIndexUsed / BITS_PER_INT; for( int i = iOldHighestIntUsed; i >= 0; --i ) { if( (g_pBlobNetworkBypass->bCurrentlyInUse.GetDWord( i ) & (-1)) != 0 ) { int iLowBit = i * BITS_PER_INT; int iHighBit = iLowBit + BITS_PER_INT; for( int j = iHighBit; --j >= iLowBit; ) { if( g_pBlobNetworkBypass->bCurrentlyInUse.IsBitSet( j ) ) { g_pBlobNetworkBypass->iHighestIndexUsed = (uint32)j + 1; break; } } break; } } } Assert( g_pBlobNetworkBypass->iHighestIndexUsed >= g_pBlobNetworkBypass->iNumParticlesAllocated ); } #else void CBlobParticleNetworkBypassAutoGame::PreRender( void ) { if( engine->IsRecordingDemo() && g_pBlobNetworkBypass->bDataUpdated ) { //record the update, TODO: compress the data by omitting the holes int iMaxIndex = MAX(g_pBlobNetworkBypass->iHighestIndexUsed, m_iOldHighestIndexUsed); int iBitMax = (iMaxIndex / BITS_PER_INT) + 1; size_t iDataSize = sizeof( int ) + sizeof( float ) + sizeof( int ) + sizeof( int ) + (sizeof( int ) * iBitMax) + iMaxIndex*( sizeof( Vector ) + sizeof( float ) + sizeof( Vector ) ); uint8 *pData = new uint8 [iDataSize]; uint8 *pWrite = pData; //let the receiver know how much of each array to expect *(int *)pWrite = LittleDWord( iMaxIndex ); pWrite += sizeof( int ); //write the update timestamp *(float *)pWrite = g_pBlobNetworkBypass->fTimeDataUpdated; pWrite += sizeof( float ); //record usage information, also helps us effectively compress the subsequent data by omitting the holes. *(int *)pWrite = LittleDWord( g_pBlobNetworkBypass->iHighestIndexUsed ); pWrite += sizeof( int ); *(int *)pWrite = LittleDWord( g_pBlobNetworkBypass->iNumParticlesAllocated ); pWrite += sizeof( int ); int *pIntParser = (int *)&g_pBlobNetworkBypass->bCurrentlyInUse; for( int i = 0; i != iBitMax; ++i ) { //convert and write the bitfield integers *(int *)pWrite = LittleDWord( *pIntParser ); pWrite += sizeof( int ); ++pIntParser; } //write positions memcpy( pWrite, g_pBlobNetworkBypass->vParticlePositions, sizeof( Vector ) * iMaxIndex ); pWrite += sizeof( Vector ) * iMaxIndex; //write radii memcpy( pWrite, g_pBlobNetworkBypass->vParticleRadii, sizeof( float ) * iMaxIndex ); pWrite += sizeof( float ) * iMaxIndex; //write closest surface direction memcpy( pWrite, g_pBlobNetworkBypass->vParticleClosestSurfDir, sizeof( Vector ) * iMaxIndex ); pWrite += sizeof( Vector ) * iMaxIndex; engine->RecordDemoCustomData( BlobNetworkBypass_CustomDemoDataCallback, pData, iDataSize ); Assert( pWrite == (pData + iDataSize) ); delete []pData; } //invalidate interpolation on freed indices, do a quick update for brand new indices { //operate on smaller chunks based on the assumption that LARGE portions of the end of the bitvecs are empty CBitVec *pCurrentlyInUse = (CBitVec *)&g_pBlobNetworkBypass->bCurrentlyInUse; CBitVec *pOldInUse = (CBitVec *)&m_bOldInUse; int iStop = (MAX(g_pBlobNetworkBypass->iHighestIndexUsed, m_iOldHighestIndexUsed) / BITS_PER_INT) + 1; int iBaseIndex = 0; //float fNewIndicesUpdateTime = g_pBlobNetworkBypass->bPositionsUpdated ? g_pBlobNetworkBypass->fTimeDataUpdated : gpGlobals->curtime; for( int i = 0; i != iStop; ++i ) { CBitVec bInUseXOR; pCurrentlyInUse->Xor( *pOldInUse, &bInUseXOR ); //find bits that changed int j = 0; while( (j = bInUseXOR.FindNextSetBit( j )) != -1 ) { int iChangedUsageIndex = iBaseIndex + j; if( pOldInUse->IsBitSet( iChangedUsageIndex ) ) { //index no longer used g_BlobParticleInterpolation.vInterpolatedPositions[iChangedUsageIndex] = vec3_origin; s_PositionInterpolators[iChangedUsageIndex].ClearHistory(); g_BlobParticleInterpolation.vInterpolatedRadii[iChangedUsageIndex] = 1.0f; s_RadiusInterpolators[iChangedUsageIndex].ClearHistory(); g_BlobParticleInterpolation.vInterpolatedClosestSurfDir[iChangedUsageIndex] = vec3_origin; s_ClosestSurfDirInterpolators[iChangedUsageIndex].ClearHistory(); } else { //index just started being used. Assume we got an out of band update to the position g_BlobParticleInterpolation.vInterpolatedPositions[iChangedUsageIndex] = g_pBlobNetworkBypass->vParticlePositions[iChangedUsageIndex]; s_PositionInterpolators[iChangedUsageIndex].Reset( gpGlobals->curtime ); g_BlobParticleInterpolation.vInterpolatedRadii[iChangedUsageIndex] = g_pBlobNetworkBypass->vParticleRadii[iChangedUsageIndex]; s_RadiusInterpolators[iChangedUsageIndex].Reset( gpGlobals->curtime ); g_BlobParticleInterpolation.vInterpolatedClosestSurfDir[iChangedUsageIndex] = g_pBlobNetworkBypass->vParticleClosestSurfDir[iChangedUsageIndex]; s_ClosestSurfDirInterpolators[iChangedUsageIndex].Reset( gpGlobals->curtime ); //s_PositionInterpolators[iChangedUsageIndex].NoteChanged( gpGlobals->curtime, fNewIndicesUpdateTime, true ); } ++j; if( j == BITS_PER_INT ) break; } iBaseIndex += BITS_PER_INT; ++pCurrentlyInUse; ++pOldInUse; } memcpy( &m_bOldInUse, &g_pBlobNetworkBypass->bCurrentlyInUse, sizeof( m_bOldInUse ) ); m_iOldHighestIndexUsed = g_pBlobNetworkBypass->iHighestIndexUsed; } if( g_pBlobNetworkBypass->iHighestIndexUsed == 0 ) return; static ConVarRef cl_interpREF( "cl_interp" ); //now do the interpolation of positions still in use { float fInterpTime = gpGlobals->curtime - cl_interpREF.GetFloat(); CBitVec *pIntParser = (CBitVec *)&g_pBlobNetworkBypass->bCurrentlyInUse; int iStop = (g_pBlobNetworkBypass->iHighestIndexUsed / BITS_PER_INT) + 1; int iBaseIndex = 0; for( int i = 0; i != iStop; ++i ) { int j = 0; while( (j = pIntParser->FindNextSetBit( j )) != -1 ) { int iUpdateIndex = iBaseIndex + j; if( g_pBlobNetworkBypass->bDataUpdated ) { g_BlobParticleInterpolation.vInterpolatedPositions[iUpdateIndex] = g_pBlobNetworkBypass->vParticlePositions[iUpdateIndex]; s_PositionInterpolators[iUpdateIndex].NoteChanged( gpGlobals->curtime, g_pBlobNetworkBypass->fTimeDataUpdated, true ); g_BlobParticleInterpolation.vInterpolatedRadii[iUpdateIndex] = g_pBlobNetworkBypass->vParticleRadii[iUpdateIndex]; s_RadiusInterpolators[iUpdateIndex].NoteChanged( gpGlobals->curtime, g_pBlobNetworkBypass->fTimeDataUpdated, true ); g_BlobParticleInterpolation.vInterpolatedClosestSurfDir[iUpdateIndex] = g_pBlobNetworkBypass->vParticleClosestSurfDir[iUpdateIndex]; s_ClosestSurfDirInterpolators[iUpdateIndex].NoteChanged( gpGlobals->curtime, g_pBlobNetworkBypass->fTimeDataUpdated, true ); //s_PositionInterpolators[iUpdateIndex].AddToHead( gpGlobals->curtime, &g_pBlobNetworkBypass->vParticlePositions[iUpdateIndex], false ); } s_PositionInterpolators[iUpdateIndex].Interpolate( fInterpTime ); s_RadiusInterpolators[iUpdateIndex].Interpolate( fInterpTime ); s_ClosestSurfDirInterpolators[iUpdateIndex].Interpolate( fInterpTime ); ++j; if( j == BITS_PER_INT ) break; } iBaseIndex += BITS_PER_INT; ++pIntParser; } g_pBlobNetworkBypass->bDataUpdated = false; } } void BlobNetworkBypass_CustomDemoDataCallback( uint8 *pData, size_t iSize ) { // FIXME: need a version number! uint8 *pParse = pData; int iMaxIndex = LittleDWord( *(int *)pParse ); pParse += sizeof( int ); int iBitMax = (iMaxIndex / BITS_PER_INT) + 1; Assert( iSize == (sizeof( int ) + sizeof( float ) + sizeof( int ) + sizeof( int ) + (sizeof( int ) * iBitMax) + iMaxIndex*( sizeof( Vector ) + sizeof( float ) + sizeof( Vector ) )) ); g_pBlobNetworkBypass->fTimeDataUpdated = *(float *)pParse; pParse += sizeof( float ); g_pBlobNetworkBypass->iHighestIndexUsed = LittleDWord( *(int *)pParse ); pParse += sizeof( int ); g_pBlobNetworkBypass->iNumParticlesAllocated = LittleDWord( *(int *)pParse ); pParse += sizeof( int ); int *pIntParser = (int *)&g_pBlobNetworkBypass->bCurrentlyInUse; for( int i = 0; i != iBitMax; ++i ) { //read and convert the bitfield integers *pIntParser = LittleDWord( *(int *)pParse ); pParse += sizeof( int ); ++pIntParser; } //read positions memcpy( g_pBlobNetworkBypass->vParticlePositions, pParse, sizeof( Vector ) * iMaxIndex ); pParse += sizeof( Vector ) * iMaxIndex; //read radii memcpy( g_pBlobNetworkBypass->vParticleRadii, pParse, sizeof( float ) * iMaxIndex ); pParse += sizeof( float ) * iMaxIndex; //read closest surface direction memcpy( g_pBlobNetworkBypass->vParticleClosestSurfDir, pParse, sizeof( Vector ) * iMaxIndex ); pParse += sizeof( Vector ) * iMaxIndex; g_pBlobNetworkBypass->bDataUpdated = true; Assert( pParse == (pData + iSize) ); } #endif