csgo-2018-source/engine/dt_test.cpp

1045 lines
29 KiB
C++
Raw Permalink Normal View History

2021-07-25 12:11:47 +08:00
//========= 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<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 );
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<int> 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