You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1010 lines
28 KiB
1010 lines
28 KiB
//========= Copyright 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" |
|
|
|
// 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<CTestStruct> m_UtlVectorStruct; |
|
CUtlVector<float> m_UtlVectorFloat; |
|
CUtlVector<char> 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<CTestStruct> m_UtlVectorStruct; |
|
CUtlVector<float> m_UtlVectorFloat; |
|
CUtlVector<char> 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<CTestStruct> &c = pClient->m_Sub.m_UtlVectorStruct; |
|
CUtlVector<CTestStruct> &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<float> &c = pClient->m_Sub.m_UtlVectorFloat; |
|
CUtlVector<float> &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<char> &c = pClient->m_Sub.m_UtlVectorChar; |
|
CUtlVector<char> &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 ); |
|
|
|
// Send the version with the exclude props. |
|
bfWrite.WriteOneBit( 1 ); |
|
|
|
bfWrite.WriteOneBit( bNeedsDecoder?1:0 ); |
|
|
|
if( !SendTable_WriteInfos( pTable, &bfWrite ) ) |
|
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 RunDataTableTest() |
|
{ |
|
RecvTable *pRecvTable = &REFERENCE_RECV_TABLE(DT_DTTest); |
|
SendTable *pSendTable = &REFERENCE_SEND_TABLE(DT_DTTest); |
|
|
|
|
|
ALIGN4 unsigned char buf[4096] ALIGN4_POST; |
|
bf_write x = bf_write(buf, 4096); |
|
bf_read y = bf_read(buf, 4096); |
|
x.WriteUBitLong(1, 1); |
|
x.WriteUBitLong(3, 2); |
|
x.WriteUBitLong(7, 3); |
|
x.WriteUBitLong(0x31415926, 32); |
|
Verify( y.ReadOneBit() == 1 ); |
|
Verify( y.ReadUBitLong(5) == 7*4+3 ); |
|
Verify( y.ReadUBitLong(32) == 0x31415926 ); |
|
|
|
|
|
// 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." ); |
|
} |
|
bfWrite.WriteOneBit(0); |
|
|
|
|
|
// Receive the SendTable's info. |
|
bf_read bfRead( "RunDataTableTest->bfRead", commBuf, sizeof(commBuf)); |
|
while( bfRead.ReadOneBit() ) |
|
{ |
|
bool bNeedsDecoder = bfRead.ReadOneBit()!=0; |
|
|
|
if( !RecvTable_RecvClassInfos( &bfRead, bNeedsDecoder ) ) |
|
{ |
|
Assert( !"RunDataTableTest: RecvTable_ReadInfos failed." ); |
|
continue; |
|
} |
|
} |
|
|
|
// 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 ); |
|
|
|
// 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 ); |
|
} |
|
} |
|
|
|
// Fully encode it. |
|
bf_write bfFullEncoded( "RunDataTableTest->bfFullEncoded", fullEncoded, sizeof(fullEncoded) ); |
|
if( !SendTable_Encode( pSendTable, &dtServer, &bfFullEncoded, -1, NULL ) ) |
|
{ |
|
Assert(false); |
|
} |
|
|
|
|
|
ALIGN4 unsigned char deltaEncoded[4096] ALIGN4_POST; |
|
bf_write bfDeltaEncoded( "RunDataTableTest->bfDeltaEncoded", deltaEncoded, sizeof(deltaEncoded) ); |
|
|
|
if ( iIteration == 0 ) |
|
{ |
|
// On the first iteration, just write the whole state. |
|
if( !SendTable_Encode( pSendTable, &dtServer, &bfDeltaEncoded, -1, NULL ) ) |
|
{ |
|
Assert( false ); |
|
} |
|
} |
|
else |
|
{ |
|
// Figure out the delta between the newly encoded one and the previously encoded one. |
|
ALIGN4 int deltaProps[MAX_DATATABLE_PROPS] ALIGN4_POST; |
|
|
|
bf_read fullEncodedRead( "RunDataTableTest->fullEncodedRead", fullEncoded, sizeof( fullEncoded ), bfFullEncoded.GetNumBitsWritten() ); |
|
bf_read prevEncodedRead( "RunDataTableTest->prevEncodedRead", prevEncoded, sizeof( prevEncoded ) ); |
|
|
|
int nDeltaProps = SendTable_CalcDelta( |
|
pSendTable, |
|
prevEncoded, sizeof( prevEncoded ) * 8, |
|
fullEncoded, bfFullEncoded.GetNumBitsWritten(), |
|
deltaProps, |
|
ARRAYSIZE( deltaProps ), |
|
-1 ); |
|
|
|
Assert( nDeltaProps != -1 ); // BAD: buffer overflow |
|
|
|
|
|
// Reencode with just the delta. This is what is actually sent to the client. |
|
SendTable_WritePropList( |
|
pSendTable, |
|
fullEncoded, |
|
bfFullEncoded.GetNumBitsWritten(), |
|
&bfDeltaEncoded, |
|
-1111, |
|
deltaProps, |
|
nDeltaProps ); |
|
} |
|
|
|
memcpy( prevEncoded, fullEncoded, 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. |
|
ALIGN4 unsigned char copyEncoded[4096] ALIGN4_POST; |
|
bf_read bfReadDeltaEncoded( "RunDataTableTest->bfReadDeltaEncoded", deltaEncoded, sizeof( deltaEncoded ) ); |
|
bf_write bfCopyEncoded( "RunDataTableTest->bfCopyEncoded", copyEncoded, sizeof(copyEncoded) ); |
|
|
|
RecvTable_CopyEncoding( pRecvTable, &bfReadDeltaEncoded, &bfCopyEncoded, -1 ); |
|
|
|
// Decode.. |
|
bf_read bfDecode( "RunDataTableTest->copyEncoded", copyEncoded, sizeof( copyEncoded ) ); |
|
if(!RecvTable_Decode(pRecvTable, &dtClient, &bfDecode, 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 ); |
|
} |
|
|
|
SendTable_Term(); |
|
RecvTable_Term(); |
|
} |
|
|
|
|
|
#endif
|
|
|