//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// // ---------------------------------------------------------------------------------------- // // This is a datatable test case. // It is run in debug mode when the engine starts to catch any bugs in datatable code. // This can also serve as a simple example of how datatables work separately from the // intricacies of the entity system. // ---------------------------------------------------------------------------------------- // // This is also a good place to test new code since it's run right when the engine // starts up. It can also be put into standalone apps easily. // ---------------------------------------------------------------------------------------- // // Things it tests: // - Data transmission integrity. // - Delta calculation. // - Strings, floats, vectors, recursive datatables, and ints. // - Fixed-length arrays. // - Variable-length arrays. // - Exclude props. // - Recursive datatables. // - Datatable proxies returning false. // - CUtlVectors of regular types (like floats) and data tables. // ---------------------------------------------------------------------------------------- // // Things it does not test: // - Quantization. // - Clamping. // - The entity system's usage of data tables. // - Stress testing - too many properties, maxing out delta bits. // - Built-in and custom client and server proxies. // ---------------------------------------------------------------------------------------- // // At a high level, the test is setup as such: // - Server structure and datatable. // - Client structure and datatable. // - A function table with a function to modify and compare each element. // - A function that initializes the server structure and tries random changes to it and // verifies that the client receives the deltas and changes correctly. // ---------------------------------------------------------------------------------------- // // Eventually it would be nice to stress-test the entities with tests for: // - Misordered proxy callbacks and missing data. // ---------------------------------------------------------------------------------------- // #include "quakedef.h" #include "dt.h" #include "dt_send.h" #include "dt_recv.h" #include "tier0/dbg.h" #include "dt_utlvector_send.h" #include "dt_utlvector_recv.h" #include "vstdlib/random.h" #include "ents_shared.h" #include "netmessages.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" // If datatables support these again, then uncomment this to have them tested. //#define SUPPORT_ARRAYS_OF_DATATABLES #ifdef _DEBUG class DTTestSub2Sub { public: int m_Int2; }; class DTTestSub2 { public: int m_Int; DTTestSub2Sub m_Sub; }; class CTestStruct { public: int a,b; float f; }; #define MAX_STRUCTARRAY_ELEMENTS 11 #define MAX_FLOATARRAY_ELEMENTS 18 #define MAX_CHARARRAY_ELEMENTS 22 // ------------------------------------------------------------------------------------------- // // DTTestServerSub and its DataTable. // ------------------------------------------------------------------------------------------- // class DTTestServerSub { public: float m_FloatArray[3]; char m_Strings[2][64]; CUtlVector m_UtlVectorStruct; CUtlVector m_UtlVectorFloat; CUtlVector m_UtlVectorChar; }; void SendProxy_DTTestServerSubString( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ) { SendProxy_StringToString( pProp, pStruct, pData, pOut, iElement, objectID); } BEGIN_SEND_TABLE_NOBASE( CTestStruct, DT_TestStruct ) SendPropInt( SENDINFO_NOCHECK( a ) ), SendPropInt( SENDINFO_NOCHECK( b ) ), SendPropFloat( SENDINFO_NOCHECK( f ) ) END_SEND_TABLE() BEGIN_SEND_TABLE_NOBASE(DTTestServerSub, DT_DTTestSub) // - Auto type conversions (receiving an array of floats into an array of ints). SendPropArray( SendPropFloat(SENDINFO_NOCHECK(m_FloatArray[0]), 0, SPROP_NOSCALE), m_FloatArray), SendPropUtlVectorDataTable( m_UtlVectorStruct, MAX_STRUCTARRAY_ELEMENTS, DT_TestStruct ), SendPropArray( SendPropString(SENDINFO_NOCHECK(m_Strings[0]), 0, SendProxy_DTTestServerSubString), m_Strings ), SendPropUtlVector( SENDINFO_UTLVECTOR( m_UtlVectorChar ), MAX_CHARARRAY_ELEMENTS, SendPropInt( NULL, 0, sizeof( char ), 0 ) ), SendPropUtlVector( SENDINFO_UTLVECTOR( m_UtlVectorFloat ), MAX_FLOATARRAY_ELEMENTS, // max elements SendPropFloat( NULL, 0, 0, 0, SPROP_NOSCALE ) ) END_SEND_TABLE() BEGIN_SEND_TABLE_NOBASE(DTTestSub2Sub, DT_DTTestSub2Sub) SendPropInt( SENDINFO_NOCHECK( m_Int2 ), 32 ), END_SEND_TABLE() BEGIN_SEND_TABLE_NOBASE(DTTestSub2, DT_DTTestSub2) SendPropDataTable(SENDINFO_DT(m_Sub), &REFERENCE_SEND_TABLE(DT_DTTestSub2Sub)), SendPropInt( SENDINFO_NOCHECK( m_Int ), 32 ), END_SEND_TABLE() // ------------------------------------------------------------------------------------------- // // DTTestServer and its DataTable. // ------------------------------------------------------------------------------------------- // class DTTestServer { public: DTTestServerSub m_Sub; DTTestSub2 m_Sub2; float m_Float; #if defined( SUPPORT_ARRAYS_OF_DATATABLES ) DTTestServerSub m_SubArray[2]; #endif Vector m_Vector; char m_String[64]; int m_Int; int m_IntArray[32]; // Note that the server and client array length are different. char m_CharArray[8]; int m_VLALength; int m_VLA[16]; }; void SendProxy_DTTestServerFloat( const SendProp *pProp, void *pStruct, void *pData, DVariant *pOut, int iElement, int objectID ) { SendProxy_FloatToFloat(pProp, pStruct, pData, pOut, iElement, objectID); } void SendProxy_DTTestServerVector( const SendProp *pProp, void *pStruct, void *pData, DVariant *pOut, int iElement, int objectID ) { SendProxy_VectorToVector(pProp, pStruct, pData, pOut, iElement, objectID); } void SendProxy_DTTestServerString( const SendProp *pProp, void *pStruct, void *pData, DVariant *pOut, int iElement, int objectID ) { SendProxy_StringToString(pProp, pStruct, pData, pOut, iElement, objectID); } void SendProxy_DTTestServerInt( const SendProp *pProp, void *pStruct, void *pData, DVariant *pOut, int iElement, int objectID ) { SendProxy_Int32ToInt32(pProp, pStruct, pData, pOut, iElement, objectID); } bool g_bSendSub = true; void* SendProxy_DTTestServerSub( const SendProp *pProp, const void *pStruct, const void *pData, CSendProxyRecipients *pRecipients, int objectID ) { if( !g_bSendSub ) return NULL; return SendProxy_DataTableToDataTable( pProp, pStruct, pData, pRecipients, objectID ); } REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_DTTestServerSub ); int ArrayLengthSendProxy_VLALength( const void *pStruct, int objectID ) { DTTestServer *pServer = (DTTestServer*)pStruct; return pServer->m_VLALength; } BEGIN_SEND_TABLE_NOBASE(DTTestServer, DT_DTTest) SendPropVariableLengthArray( ArrayLengthSendProxy_VLALength, SendPropInt( SENDINFO_NOCHECK( m_VLA[0] ) ), m_VLA ), // Test exclude props. SendPropExclude( "DT_DTTest", "m_Int" ), SendPropDataTable(SENDINFO_DT(m_Sub), &REFERENCE_SEND_TABLE(DT_DTTestSub), SendProxy_DTTestServerSub), SendPropFloat (SENDINFO_NOCHECK(m_Float), 32, SPROP_NOSCALE), SendPropDataTable(SENDINFO_DT(m_Sub2), &REFERENCE_SEND_TABLE(DT_DTTestSub2)), SendPropInt (SENDINFO_NOCHECK(m_Int), 23, SPROP_UNSIGNED), SendPropExclude( "DT_DTTestSub", "m_FloatArray" ), SendPropString(SENDINFO_NOCHECK(m_String)), SendPropArray( SendPropInt(SENDINFO_NOCHECK(m_CharArray[0]), 8), m_CharArray), SendPropArray( SendPropInt (SENDINFO_NOCHECK(m_IntArray[0]), 23, SPROP_UNSIGNED), m_IntArray), #if defined( SUPPORT_ARRAYS_OF_DATATABLES ) SendPropArray( SendPropDataTable(SENDINFO_DT(m_SubArray[0]), &REFERENCE_SEND_TABLE(DT_DTTestSub), SendProxy_DTTestServerSub), m_SubArray ), #endif SendPropVector(SENDINFO_NOCHECK(m_Vector), 32, SPROP_NOSCALE) END_SEND_TABLE() // ------------------------------------------------------------------------------------------- // // DTTestClientSub and its DataTable. // ------------------------------------------------------------------------------------------- // class DTTestClientSub { public: char m_Strings[2][64]; float m_FloatArray[3]; CUtlVector m_UtlVectorStruct; CUtlVector m_UtlVectorFloat; CUtlVector m_UtlVectorChar; }; void RecvProxy_DTTestClientSubString( const CRecvProxyData *pData, void *pStruct, void *pOut ) { RecvProxy_StringToString( pData, pStruct, pOut ); } BEGIN_RECV_TABLE_NOBASE( CTestStruct, DT_TestStruct ) RecvPropInt( RECVINFO( a ) ), RecvPropInt( RECVINFO( b ) ), RecvPropFloat( RECVINFO( f ) ), END_RECV_TABLE() BEGIN_RECV_TABLE_NOBASE(DTTestClientSub, DT_DTTestSub) // - Auto type conversions (receiving an array of floats into an array of ints). RecvPropArray( RecvPropFloat(RECVINFO(m_FloatArray[0])), m_FloatArray), RecvPropUtlVector( RECVINFO_UTLVECTOR( m_UtlVectorFloat ), MAX_FLOATARRAY_ELEMENTS, RecvPropFloat(NULL,0,0) ), RecvPropUtlVectorDataTable( m_UtlVectorStruct, MAX_STRUCTARRAY_ELEMENTS, DT_TestStruct ), RecvPropUtlVector( RECVINFO_UTLVECTOR( m_UtlVectorChar ), MAX_CHARARRAY_ELEMENTS, RecvPropInt( NULL, 0, sizeof( char ) ) ), RecvPropArray( RecvPropString(RECVINFO(m_Strings[0]), 0, RecvProxy_DTTestClientSubString), m_Strings), END_RECV_TABLE() BEGIN_RECV_TABLE_NOBASE(DTTestSub2Sub, DT_DTTestSub2Sub) RecvPropInt( RECVINFO( m_Int2 ), 32 ), END_RECV_TABLE() BEGIN_RECV_TABLE_NOBASE(DTTestSub2, DT_DTTestSub2) RecvPropDataTable(RECVINFO_DT(m_Sub), 0, &REFERENCE_RECV_TABLE(DT_DTTestSub2Sub)), RecvPropInt( RECVINFO( m_Int ) ), END_RECV_TABLE() // ------------------------------------------------------------------------------------------- // // DTTestClient and DataTable. // ------------------------------------------------------------------------------------------- // class DTTestClient { public: DTTestClientSub m_Sub; long m_Guard1; DTTestSub2 m_Sub2; long m_Guard2; #if defined( SUPPORT_ARRAYS_OF_DATATABLES ) DTTestClientSub m_SubArray[2]; #endif long m_Guard3; float m_Float; long m_Guard4; Vector m_Vector; long m_Guard5; char m_String[64]; long m_Guard6; int m_Int; long m_Guard7; int m_IntArray[32]; // Note that the server and client array length are different. long m_Guard8; char m_CharArray[8]; long m_Guard9; int m_VLALength; int m_VLA[16]; }; void RecvProxyArrayLength_VLA( void *pStruct, int objectID, int currentArrayLength ) { DTTestClient *pClient = (DTTestClient*)pStruct; pClient->m_VLALength = currentArrayLength; } BEGIN_RECV_TABLE_NOBASE(DTTestClient, DT_DTTest) #if defined( SUPPORT_ARRAYS_OF_DATATABLES ) RecvPropArray( RecvPropDataTable(RECVINFO_DT(m_SubArray[0]), 0, &REFERENCE_RECV_TABLE(DT_DTTestSub), RecvProxy_DTTestClientSub), m_SubArray ), #endif RecvPropFloat (RECVINFO(m_Float), 0), RecvPropDataTable(RECVINFO_DT(m_Sub), 0, &REFERENCE_RECV_TABLE(DT_DTTestSub)), RecvPropDataTable(RECVINFO_DT(m_Sub2), 0, &REFERENCE_RECV_TABLE(DT_DTTestSub2)), // - Arrays with and without the SPROP_ONEBITDELTA flag. RecvPropArray( RecvPropInt (RECVINFO(m_CharArray[0]), 8), m_CharArray), RecvPropVector(RECVINFO(m_Vector), 0), RecvPropString(RECVINFO_STRING(m_String), 0), RecvPropInt (RECVINFO(m_Int), 0), // - Arrays with and without the SPROP_ONEBITDELTA flag. // - Array size mismatches between the client and the server. RecvPropArray( RecvPropInt (RECVINFO(m_IntArray[0]), 0), m_IntArray), RecvPropInt( RECVINFO( m_VLALength ) ), RecvPropVariableLengthArray( RecvProxyArrayLength_VLA, RecvPropInt( RECVINFO( m_VLA[0] ) ), m_VLA ) END_RECV_TABLE() // ------------------------------------------------------------------------------------------- // // Functions that act on the data. // ------------------------------------------------------------------------------------------- // typedef bool (*CompareElementFn)(DTTestClient *pClient, DTTestServer *pServer); typedef void (*RandomlyChangeElementFn)(DTTestServer *pServer); float FRand(double minVal, double maxVal) { return (float)(((double)rand() / VALVE_RAND_MAX) * (maxVal - minVal) + minVal); } void RandomlyChangeStringGeneric(char *str, int size) { for(int i=0; i < size-1; i++) str[i] = (char)rand(); str[size-1] = 0; } bool CompareTestSubString0(DTTestClient *pClient, DTTestServer *pServer) { return strcmp(pClient->m_Sub.m_Strings[0], pServer->m_Sub.m_Strings[0]) == 0; } void RandomlyChangeSubString0(DTTestServer *pServer) { if( g_bSendSub ) RandomlyChangeStringGeneric(pServer->m_Sub.m_Strings[0], sizeof(pServer->m_Sub.m_Strings[0])); } bool CompareTestSubString1(DTTestClient *pClient, DTTestServer *pServer) { return strcmp(pClient->m_Sub.m_Strings[1], pServer->m_Sub.m_Strings[1]) == 0; } void RandomlyChangeSubString1(DTTestServer *pServer) { if( g_bSendSub ) RandomlyChangeStringGeneric(pServer->m_Sub.m_Strings[1], sizeof(pServer->m_Sub.m_Strings[1])); } bool CompareFloat(DTTestClient *pClient, DTTestServer *pServer) { return pClient->m_Float == pServer->m_Float; } void RandomlyChangeFloat(DTTestServer *pServer) { pServer->m_Float = FRand(-500000, 500000); } bool CompareVector(DTTestClient *pClient, DTTestServer *pServer) { return pClient->m_Vector.x == pServer->m_Vector.x && pClient->m_Vector.y == pServer->m_Vector.y && pClient->m_Vector.z == pServer->m_Vector.z; } void RandomlyChangeVector(DTTestServer *pServer) { pServer->m_Vector.x = FRand(-500000, 500000); pServer->m_Vector.y = FRand(-500000, 500000); pServer->m_Vector.z = FRand(-500000, 500000); } bool CompareString(DTTestClient *pClient, DTTestServer *pServer) { return strcmp(pClient->m_String, pServer->m_String) == 0; } void RandomlyChangeString(DTTestServer *pServer) { //memset( pServer->m_String, , sizeof( pServer->m_String ) ); Q_strncpy( pServer->m_String, "a", sizeof( pServer->m_String ) ); //RandomlyChangeStringGeneric(pServer->m_String, sizeof(pServer->m_String)); } bool CompareInt(DTTestClient *pClient, DTTestServer *pServer) { // (m_Int is the exclude prop we're testing) // return pClient->m_Int == pServer->m_Int; return true; } void RandomlyChangeInt(DTTestServer *pServer) { pServer->m_Int = (int)rand(); } bool CompareIntArray(DTTestClient *pClient, DTTestServer *pServer) { // Just verify however much of the data we can. int leastElements = (sizeof(pClient->m_IntArray) < sizeof(pServer->m_IntArray)) ? (sizeof(pClient->m_IntArray)/sizeof(pClient->m_IntArray[0])) : (sizeof(pServer->m_IntArray)/sizeof(pServer->m_IntArray[0])); return memcmp(pClient->m_IntArray, pServer->m_IntArray, leastElements*sizeof(int)) == 0; } void RandomlyChangeIntArray(DTTestServer *pServer) { // Change a random subset of the array. int nElements = sizeof(pServer->m_IntArray) / sizeof(pServer->m_IntArray[0]); int nChanges = 4 + rand() % nElements; for(int i=0; i < nChanges; i++) { pServer->m_IntArray[rand() % nElements] = (int)rand(); } } bool CompareFloatArray(DTTestClient *pClient, DTTestServer *pServer) { // m_FloatArray is an ExcludeProp. /* int leastElements = (sizeof(pClient->m_Sub.m_FloatArray) < sizeof(pServer->m_Sub.m_FloatArray)) ? (sizeof(pClient->m_Sub.m_FloatArray)/sizeof(pClient->m_Sub.m_FloatArray[0])) : (sizeof(pServer->m_Sub.m_FloatArray)/sizeof(pServer->m_Sub.m_FloatArray[0])); for(int i=0; i < leastElements; i++) { if(pClient->m_Sub.m_FloatArray[i] != pServer->m_Sub.m_FloatArray[i]) return false; } */ return true; } void RandomlyChangeFloatArray(DTTestServer *pServer) { // Change a random subset of the array. int nElements = sizeof(pServer->m_Sub.m_FloatArray) / sizeof(pServer->m_Sub.m_FloatArray[0]); int nChanges = 4 + rand() % nElements; for(int i=0; i < nChanges; i++) { pServer->m_Sub.m_FloatArray[rand() % nElements] = (float)rand() * 0.943123f; } } bool CompareCharArray(DTTestClient *pClient, DTTestServer *pServer) { return memcmp(pClient->m_CharArray, pServer->m_CharArray, sizeof(pClient->m_CharArray)) == 0; } void RandomlyChangeCharArray(DTTestServer *pServer) { for(int i=0; i < (sizeof(pServer->m_CharArray) / sizeof(pServer->m_CharArray[0])); i++) pServer->m_CharArray[i] = (char)rand(); } bool CompareSubArray(DTTestClient *pClient, DTTestServer *pServer ) { #if defined( SUPPORT_ARRAYS_OF_DATATABLES ) for( int i=0; i < 2; i++ ) { for( int z=0; z < sizeof(pServer->m_SubArray[0].m_FloatArray) / sizeof(pServer->m_SubArray[0].m_FloatArray[0]); z++ ) { if( pServer->m_SubArray[i].m_FloatArray[z] != pClient->m_SubArray[i].m_FloatArray[z] ) return false; } for( int iString=0; iString < sizeof(pServer->m_SubArray[0].m_Strings) / sizeof(pServer->m_SubArray[0].m_Strings[0]); iString++ ) { for( z=0; z < sizeof(pServer->m_SubArray[0].m_Strings[0]) / sizeof(pServer->m_SubArray[0].m_Strings[0][0]); z++ ) { if( pServer->m_SubArray[i].m_Strings[iString][z] != pClient->m_SubArray[i].m_Strings[iString][z] ) return false; // Check for null termination. if( pServer->m_SubArray[i].m_Strings[iString][z] == 0 ) break; } } } #endif return true; } void RandomlyChangeSubArray(DTTestServer *pServer) { #if defined( SUPPORT_ARRAYS_OF_DATATABLES ) if( !g_bSendSub ) return; for( int i=0; i < 2; i++ ) { int index = rand() & 1; for( int z=0; z < sizeof(pServer->m_SubArray[0].m_FloatArray) / sizeof(pServer->m_SubArray[0].m_FloatArray[0]); z++ ) pServer->m_SubArray[index].m_FloatArray[z] = rand() * 0.932f; for( int iString=0; iString < sizeof(pServer->m_SubArray[0].m_Strings) / sizeof(pServer->m_SubArray[0].m_Strings[0]); iString++ ) { int stringLen = sizeof(pServer->m_SubArray[0].m_Strings[0]) / sizeof(pServer->m_SubArray[0].m_Strings[0][0]); for( z=0; z < stringLen; z++ ) pServer->m_SubArray[index].m_Strings[iString][z] = (char)rand(); // null-terminate it pServer->m_SubArray[index].m_Strings[iString][stringLen-1] = 0; } } #endif } bool CompareSub2( DTTestClient *pClient, DTTestServer *pServer ) { return memcmp( &pClient->m_Sub2, &pServer->m_Sub2, sizeof( pClient->m_Sub2 ) ) == 0; } void RandomlyChangeSub2( DTTestServer *pServer ) { pServer->m_Sub2.m_Int = rand(); } bool CompareSub2Sub( DTTestClient *pClient, DTTestServer *pServer ) { return pClient->m_Sub2.m_Sub.m_Int2 == pServer->m_Sub2.m_Sub.m_Int2; } void RandomlyChangeSub2Sub( DTTestServer *pServer ) { pServer->m_Sub2.m_Sub.m_Int2 = rand(); } bool CompareVLA( DTTestClient *pClient, DTTestServer *pServer ) { if ( pClient->m_VLALength != pServer->m_VLALength ) return false; for ( int i=0; i < pClient->m_VLALength; i++ ) { if ( pClient->m_VLA[i] != pServer->m_VLA[i] ) return false; } return true; } void RandomlyChangeVLA( DTTestServer *pServer ) { pServer->m_VLALength = rand() % ARRAYSIZE( pServer->m_VLA ); for ( int i=0; i < pServer->m_VLALength; i++ ) pServer->m_VLA[i] = rand() * rand(); } bool CompareUtlVectorStruct( DTTestClient *pClient, DTTestServer *pServer ) { CUtlVector &c = pClient->m_Sub.m_UtlVectorStruct; CUtlVector &s = pServer->m_Sub.m_UtlVectorStruct; if ( c.Count() != s.Count() ) return false; for ( int i=0; i < c.Count(); i++ ) { if ( c[i].a != s[i].a || c[i].b != s[i].b || c[i].f != s[i].f ) return false; } return true; } void RandomlyChangeUtlVectorStruct( DTTestServer *pServer ) { if ( !g_bSendSub ) return; int nElements = rand() % MAX_STRUCTARRAY_ELEMENTS; pServer->m_Sub.m_UtlVectorStruct.SetSize( nElements ); for ( int i=0; i < nElements; i++ ) { pServer->m_Sub.m_UtlVectorStruct[i].a = rand(); pServer->m_Sub.m_UtlVectorStruct[i].b = rand(); pServer->m_Sub.m_UtlVectorStruct[i].f = rand(); } } bool CompareUtlVectorFloat( DTTestClient *pClient, DTTestServer *pServer ) { CUtlVector &c = pClient->m_Sub.m_UtlVectorFloat; CUtlVector &s = pServer->m_Sub.m_UtlVectorFloat; if ( c.Count() != s.Count() ) return false; for ( int i=0; i < c.Count(); i++ ) { if ( c[i] != s[i] ) return false; } return true; } void RandomlyChangeUtlVectorChar( DTTestServer *pServer ) { if ( !g_bSendSub ) return; int nElements = rand() % MAX_CHARARRAY_ELEMENTS; pServer->m_Sub.m_UtlVectorChar.SetSize( nElements ); for ( int i=0; i < nElements; i++ ) pServer->m_Sub.m_UtlVectorChar[i] = (char)rand(); } bool CompareUtlVectorChar( DTTestClient *pClient, DTTestServer *pServer ) { CUtlVector &c = pClient->m_Sub.m_UtlVectorChar; CUtlVector &s = pServer->m_Sub.m_UtlVectorChar; if ( c.Count() != s.Count() ) return false; for ( int i=0; i < c.Count(); i++ ) { if ( c[i] != s[i] ) return false; } return true; } void RandomlyChangeUtlVectorFloat( DTTestServer *pServer ) { if ( !g_bSendSub ) return; int nElements = rand() % MAX_FLOATARRAY_ELEMENTS; pServer->m_Sub.m_UtlVectorFloat.SetSize( nElements ); for ( int i=0; i < nElements; i++ ) pServer->m_Sub.m_UtlVectorFloat[i] = rand() / 0.93; } typedef struct { CompareElementFn m_CompareFn; RandomlyChangeElementFn m_ChangeFn; } VarTestInfo; VarTestInfo g_VarTestInfos[] = { {CompareVLA, RandomlyChangeVLA}, {CompareUtlVectorStruct,RandomlyChangeUtlVectorStruct}, {CompareUtlVectorFloat, RandomlyChangeUtlVectorFloat}, {CompareUtlVectorChar, RandomlyChangeUtlVectorChar}, {CompareFloat, RandomlyChangeFloat}, {CompareSub2, RandomlyChangeSub2}, {CompareSub2Sub, RandomlyChangeSub2Sub}, {CompareInt, RandomlyChangeInt}, {CompareFloatArray, RandomlyChangeFloatArray}, {CompareTestSubString0, RandomlyChangeSubString0}, {CompareTestSubString1, RandomlyChangeSubString1}, {CompareCharArray, RandomlyChangeCharArray}, {CompareVector, RandomlyChangeVector}, {CompareString, RandomlyChangeString}, {CompareIntArray, RandomlyChangeIntArray}, {CompareSubArray, RandomlyChangeSubArray} }; #define NUMVARTESTINFOS (sizeof(g_VarTestInfos) / sizeof(g_VarTestInfos[0])) int g_GuardOffsets[] = { offsetof( DTTestClient, m_Guard1 ), offsetof( DTTestClient, m_Guard2 ), offsetof( DTTestClient, m_Guard3 ), offsetof( DTTestClient, m_Guard4 ), offsetof( DTTestClient, m_Guard5 ), offsetof( DTTestClient, m_Guard6 ), offsetof( DTTestClient, m_Guard7 ), offsetof( DTTestClient, m_Guard8 ), offsetof( DTTestClient, m_Guard9 ) }; int g_nGuardOffsets = sizeof( g_GuardOffsets ) / sizeof( g_GuardOffsets[0] ); void SetGuardBytes( DTTestClient *pClient ) { for( int i=0; i < g_nGuardOffsets; i++ ) { unsigned char *pDest = ((unsigned char *)pClient) + g_GuardOffsets[i]; *((long*)pDest) = i; } } void CheckGuardBytes( DTTestClient *pClient ) { for( int i=0; i < g_nGuardOffsets; i++ ) { unsigned char *pDest = ((unsigned char *)pClient) + g_GuardOffsets[i]; Assert( *((long*)pDest) == i ); } } // ------------------------------------------------------------------------------------------- // // TEST CODE // ------------------------------------------------------------------------------------------- // bool CompareDTTest(DTTestClient *pClient, DTTestServer *pServer) { for(int iVar=0; iVar < NUMVARTESTINFOS; iVar++) { if(!g_VarTestInfos[iVar].m_CompareFn(pClient, pServer)) { Assert( !"CompareDTTest: comparison failed. There is a new datatable bug." ); return false; } } return true; } bool WriteSendTable_R( SendTable *pTable, bf_write &bfWrite, bool bNeedsDecoder ) { if( pTable->GetWriteFlag() ) return true; pTable->SetWriteFlag( true ); if( !SendTable_WriteInfos( pTable, bfWrite, bNeedsDecoder, false ) ) return false; for( int i=0; i < pTable->m_nProps; i++ ) { SendProp *pProp = &pTable->m_pProps[i]; if( pProp->m_Type == DPT_DataTable ) if( !WriteSendTable_R( pProp->GetDataTable(), bfWrite, false ) ) return false; } return true; } void TestDeltaBitEncoders() { ALIGN4 char tempData[8192] ALIGN4_POST; bf_write bfw( "", tempData, sizeof( tempData ) ); int nSamples = 500; CUtlVector written; written.SetSize( nSamples ); RandomSeed( 4 ); { CDeltaBitsWriter deltaw( &bfw ); int nProp = 1; for ( int i=0; i < nSamples; i++ ) { nProp += RandomInt( 1, 5 ); deltaw.WritePropIndex( nProp ); written[i] = nProp; } } bf_read bfr( tempData, sizeof( tempData ) ); CDeltaBitsReader deltar( &bfr ); int nCurProp = 0; while ( 1 ) { int nTestIndex = deltar.ReadNextPropIndex(); if ( nTestIndex == -1 ) { Assert( nCurProp == nSamples ); break; } else { Assert( nTestIndex == written[nCurProp++] ); } } Assert( nCurProp == nSamples ); } void RunDataTableTest() { TestDeltaBitEncoders(); RecvTable *pRecvTable = &REFERENCE_RECV_TABLE(DT_DTTest); SendTable *pSendTable = &REFERENCE_SEND_TABLE(DT_DTTest); // Initialize the send and receive modules. SendTable_Init( &pSendTable, 1 ); RecvTable_Init( &pRecvTable, 1 ); pSendTable->SetWriteFlag( false ); // Send DataTable info to the client. ALIGN4 unsigned char commBuf[8192] ALIGN4_POST; bf_write bfWrite( "RunDataTableTest->commBuf", commBuf, sizeof(commBuf) ); if( !WriteSendTable_R( pSendTable, bfWrite, true ) ) { Assert( !"RunDataTableTest: SendTable_SendInfo failed." ); } // Signal no more send tables. SendTable_WriteInfos( NULL, bfWrite, false, true ); // Receive the SendTable's info. CSVCMsg_SendTable_t msg; bf_read bfRead( "RunDataTableTest->bfRead", commBuf, sizeof(commBuf)); while ( 1 ) { int type = bfRead.ReadVarInt32(); if( !msg.ReadFromBuffer( bfRead ) ) { Assert( !"RunDataTableTest: ReadFromBuffer failed." ); break; } int msgType = msg.GetType(); if ( type != msgType ) { Assert( !"RunDataTableTest: ReadFromBuffer failed." ); break; } if( msg.is_end() ) break; if( !RecvTable_RecvClassInfos( msg ) ) { Assert( !"RunDataTableTest: RecvTable_ReadInfos failed." ); break; } } // Register our receive table. if( !RecvTable_CreateDecoders( NULL, false ) ) { Assert(false); } // Setup the data with all zeros. DTTestServer dtServer; DTTestClient dtClient; ALIGN4 unsigned char prevEncoded[4096] ALIGN4_POST; ALIGN4 unsigned char fullEncoded[4096] ALIGN4_POST; memset(&dtServer, 0, sizeof(dtServer)); memset(&dtClient, 0, sizeof(dtClient)); memset(prevEncoded, 0, sizeof(prevEncoded)); SetGuardBytes( &dtClient ); SerializedEntityHandle_t startEntity = g_pSerializedEntities->AllocateSerializedEntity( __FILE__, __LINE__ ); SerializedEntityHandle_t endEntity = g_pSerializedEntities->AllocateSerializedEntity( __FILE__, __LINE__ ); SerializedEntityHandle_t received = g_pSerializedEntities->AllocateSerializedEntity( __FILE__, __LINE__ ); // Now loop around, changing the data a little bit each time and send/recv deltas. int nIterations = 25; for( int iIteration=0; iIteration < nIterations; iIteration++ ) { // Change the server's data. g_bSendSub = true; if( (iIteration % 5) == 0 ) { g_bSendSub = false; // every 8th time, don't send the subtable } if( (iIteration & 3) == 0 ) { // Every once in a while, change ALL the properties. for( int iChange=0; iChange < NUMVARTESTINFOS; iChange++ ) g_VarTestInfos[iChange].m_ChangeFn( &dtServer ); } else { int nChanges = 3 + rand() % NUMVARTESTINFOS; for( int iChange=0; iChange < nChanges; iChange++ ) { int iInfo = rand() % NUMVARTESTINFOS; g_VarTestInfos[iInfo].m_ChangeFn( &dtServer ); } } if( !SendTable_Encode( pSendTable, startEntity, &dtServer, -1, NULL ) ) { Assert(false); } // Fully encode it. bf_write bfFullEncoded( "RunDataTableTest->bfFullEncoded", fullEncoded, sizeof(fullEncoded) ); SendTable_WritePropList( pSendTable, startEntity, &bfFullEncoded, -1, NULL ); ALIGN4 unsigned char deltaEncoded[4096] ALIGN4_POST; bf_write bfDeltaEncoded( "RunDataTableTest->bfDeltaEncoded", deltaEncoded, sizeof(deltaEncoded) ); if ( iIteration == 0 ) { if( !SendTable_Encode( pSendTable, endEntity, &dtServer, -11111, NULL ) ) { Assert(false); } SendTable_WritePropList( pSendTable, endEntity, &bfDeltaEncoded, -1111, NULL ); } else { // Figure out the delta between the newly encoded one and the previously encoded one. CalcDeltaResultsList_t deltaProps; SendTable_CalcDelta( pSendTable, startEntity, endEntity, -1111, deltaProps ); SendTable_WritePropList( pSendTable, endEntity, &bfDeltaEncoded, -1, &deltaProps ); } memcpy( prevEncoded, fullEncoded, sizeof( prevEncoded ) ); bf_read bfDecode( "RunDataTableTest->copyEncoded", prevEncoded, sizeof( prevEncoded ) ); // This step isn't necessary to have the client decode the data but it's here to test // RecvTable_CopyEncoding (and RecvTable_MergeDeltas). This call should just make an exact // copy of the encoded data. if ( !RecvTable_ReadFieldList( pRecvTable, bfDecode, received, 1111, false ) ) { Assert( false ); } if ( !RecvTable_Decode( pRecvTable, &dtClient, received, 1111 ) ) { Assert(false); } // Make sure it didn't go into memory it shouldn't have. CheckGuardBytes( &dtClient ); // Verify that only the changed properties were sent and that they were received correctly. CompareDTTest( &dtClient, &dtServer ); } g_pSerializedEntities->ReleaseSerializedEntity( received ); g_pSerializedEntities->ReleaseSerializedEntity( endEntity ); g_pSerializedEntities->ReleaseSerializedEntity( startEntity ); SendTable_Term(); RecvTable_Term(); } #endif