Compare commits

..

7 Commits

Author SHA1 Message Date
Alibek Omarov
ca3b4a9d7f engine: host: oops 2024-04-18 16:48:41 +03:00
Alibek Omarov
87ac217887 engine: common: net_encode: add bitmask operation to delta tests 2024-04-18 16:44:15 +03:00
Alibek Omarov
213650db8f engine: exit with non-zero return code if engine tests are failed 2024-04-18 16:36:18 +03:00
Alibek Omarov
ac46164a6e engine: common: net_encode: implement basic delta tests 2024-04-17 20:27:22 +03:00
Alibek Omarov
6d12d84e94 engine: common: net_encode: apply post_multiplier to integer fields, like GoldSrc does 2024-04-17 06:23:22 +03:00
Alibek Omarov
2a18cde60c engine: common: net_encode: fix applying integer clamp before multiplier
Without this fix, data still might overflow after applying multiplier
and potentially send incorrect data.
2024-04-17 06:16:44 +03:00
Alibek Omarov
35783bcec2 engine: common: net_encode: fix inaccuracy in DT_TIMEWINDOW_* encoding
By avoiding double rounding, we get more accurate time on client when timebase
gets rounded down and target time gets rounded up:

```
>>> round(123.1) - round(124.51)
-2
>>> round(123.1 - 124.51)
-1
```
2024-04-17 06:09:52 +03:00
6 changed files with 209 additions and 70 deletions

View File

@@ -941,6 +941,7 @@ static void Host_RunTests( int stage )
TEST_LIST_1_CLIENT;
#endif
Msg( "Done! %d passed, %d failed\n", tests_stats.passed, tests_stats.failed );
error_on_exit = tests_stats.failed > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
Sys_Quit();
}
}

View File

@@ -315,6 +315,43 @@ static const delta_field_t ent_fields[] =
{ NULL },
};
#if XASH_ENGINE_TESTS
typedef struct delta_test_struct_t
{
char dt_string[128]; // always signed
float dt_timewindow_big; // always signed
float dt_timewindow_8; // always signed
float dt_angle; // always_signed
float dt_float_signed;
float dt_float_unsigned;
int32_t dt_integer_signed;
uint32_t dt_integer_unsigned;
int16_t dt_short_signed;
uint16_t dt_short_unsigned;
int8_t dt_byte_signed;
uint8_t dt_byte_unsigned;
} delta_test_struct_t;
#define TEST_DEF( x ) #x, offsetof( delta_test_struct_t, x ), sizeof( ((delta_test_struct_t *)0)->x )
static const delta_field_t test_fields[] =
{
{ TEST_DEF( dt_string ) },
{ TEST_DEF( dt_timewindow_big )},
{ TEST_DEF( dt_timewindow_8 )},
{ TEST_DEF( dt_angle ) },
{ TEST_DEF( dt_float_signed ) },
{ TEST_DEF( dt_float_unsigned ) },
{ TEST_DEF( dt_integer_signed ) },
{ TEST_DEF( dt_integer_unsigned ) },
{ TEST_DEF( dt_short_signed ) },
{ TEST_DEF( dt_short_unsigned ) },
{ TEST_DEF( dt_byte_signed ) },
{ TEST_DEF( dt_byte_unsigned ) },
{ NULL },
};
#endif
enum
{
DT_EVENT_T = 0,
@@ -325,6 +362,10 @@ enum
DT_ENTITY_STATE_T,
DT_ENTITY_STATE_PLAYER_T,
DT_CUSTOM_ENTITY_STATE_T,
#if XASH_ENGINE_TESTS
DT_DELTA_TEST_STRUCT_T,
#endif
DT_STRUCT_COUNT
};
static delta_info_t dt_info[] =
@@ -337,7 +378,10 @@ static delta_info_t dt_info[] =
[DT_ENTITY_STATE_T] = { "entity_state_t", ent_fields, NUM_FIELDS( ent_fields ) },
[DT_ENTITY_STATE_PLAYER_T] = { "entity_state_player_t", ent_fields, NUM_FIELDS( ent_fields ) },
[DT_CUSTOM_ENTITY_STATE_T] = { "custom_entity_state_t", ent_fields, NUM_FIELDS( ent_fields ) },
{ NULL },
#if XASH_ENGINE_TESTS
[DT_DELTA_TEST_STRUCT_T] = { "delta_test_struct_t", test_fields, NUM_FIELDS( test_fields ) },
#endif
[DT_STRUCT_COUNT] = { NULL },
};
static delta_info_t *Delta_FindStruct( const char *name )
@@ -411,9 +455,7 @@ static void Delta_CustomEncode( delta_info_t *dt, const void *from, const void *
dt->pFields[i].bInactive = false;
if( dt->userCallback )
{
dt->userCallback( dt->pFields, from, to );
}
}
static delta_field_t *Delta_FindFieldInfo( const delta_field_t *pInfo, const char *fieldName )
@@ -974,14 +1016,14 @@ static qboolean Delta_CompareField( delta_t *pField, void *from, void *to, doubl
toF = *(uint8_t *)((int8_t *)to + pField->offset );
}
if( !Q_equal(pField->multiplier, 1.0f ))
{
fromF *= pField->multiplier;
toF *= pField->multiplier;
}
fromF = Delta_ClampIntegerField( pField, fromF, signbit, pField->bits );
toF = Delta_ClampIntegerField( pField, toF, signbit, pField->bits );
if( !Q_equal(pField->multiplier, 1.0f ))
fromF *= pField->multiplier;
if( !Q_equal( pField->multiplier, 1.0f ))
toF *= pField->multiplier;
}
else if( pField->flags & DT_SHORT )
{
@@ -996,14 +1038,14 @@ static qboolean Delta_CompareField( delta_t *pField, void *from, void *to, doubl
toF = *(uint16_t *)((int8_t *)to + pField->offset );
}
if( !Q_equal(pField->multiplier, 1.0f ))
{
fromF *= pField->multiplier;
toF *= pField->multiplier;
}
fromF = Delta_ClampIntegerField( pField, fromF, signbit, pField->bits );
toF = Delta_ClampIntegerField( pField, toF, signbit, pField->bits );
if( !Q_equal(pField->multiplier, 1.0f ))
fromF *= pField->multiplier;
if( !Q_equal( pField->multiplier, 1.0f ))
toF *= pField->multiplier;
}
else if( pField->flags & DT_INTEGER )
{
@@ -1017,14 +1059,15 @@ static qboolean Delta_CompareField( delta_t *pField, void *from, void *to, doubl
fromF = *(uint32_t *)((int8_t *)from + pField->offset );
toF = *(uint32_t *)((int8_t *)to + pField->offset );
}
fromF = Delta_ClampIntegerField( pField, fromF, signbit, pField->bits );
toF = Delta_ClampIntegerField( pField, toF, signbit, pField->bits );
if( !Q_equal(pField->multiplier, 1.0f ))
{
fromF *= pField->multiplier;
if( !Q_equal( pField->multiplier, 1.0f ))
toF *= pField->multiplier;
}
fromF = Delta_ClampIntegerField( pField, fromF, signbit, pField->bits );
toF = Delta_ClampIntegerField( pField, toF, signbit, pField->bits );
}
else if( pField->flags & ( DT_ANGLE|DT_FLOAT ))
{
@@ -1034,33 +1077,17 @@ static qboolean Delta_CompareField( delta_t *pField, void *from, void *to, doubl
}
else if( pField->flags & DT_TIMEWINDOW_8 )
{
val_a = Q_rint((*(float *)((byte *)from + pField->offset )) * 100.0f );
val_b = Q_rint((*(float *)((byte *)to + pField->offset )) * 100.0f );
val_a -= Q_rint(timebase * 100.0);
val_b -= Q_rint(timebase * 100.0);
fromF = FloatAsInt( val_a );
toF = FloatAsInt( val_b );
val_a = *(float *)((byte *)from + pField->offset );
val_b = *(float *)((byte *)to + pField->offset );
fromF = Q_rint(( timebase - val_a ) * 100.0 );
toF = Q_rint(( timebase - val_b ) * 100.0 );
}
else if( pField->flags & DT_TIMEWINDOW_BIG )
{
val_a = (*(float *)((byte *)from + pField->offset ));
val_b = (*(float *)((byte *)to + pField->offset ));
if( !Q_equal( pField->multiplier, 1.0f ))
{
val_a *= pField->multiplier;
val_b *= pField->multiplier;
val_a = (timebase * pField->multiplier) - val_a;
val_b = (timebase * pField->multiplier) - val_b;
}
else
{
val_a = timebase - val_a;
val_b = timebase - val_b;
}
fromF = FloatAsInt( val_a );
toF = FloatAsInt( val_b );
val_a = *(float *)((byte *)from + pField->offset );
val_b = *(float *)((byte *)to + pField->offset );
fromF = Q_rint(( timebase - val_a ) * pField->multiplier );
toF = Q_rint(( timebase - val_b ) * pField->multiplier );
}
else if( pField->flags & DT_STRING )
{
@@ -1144,6 +1171,7 @@ static qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, v
int signbit = FBitSet( pField->flags, DT_SIGNED ) ? 1 : 0;
float flValue, flAngle;
uint iValue;
int dt;
const char *pStr;
if( Delta_CompareField( pField, from, to, timebase ))
@@ -1160,11 +1188,11 @@ static qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, v
iValue = *(int8_t *)((int8_t *)to + pField->offset );
else
iValue = *(uint8_t *)((int8_t *)to + pField->offset );
iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );
if( !Q_equal( pField->multiplier, 1.0 ) )
if( !Q_equal( pField->multiplier, 1.0 ))
iValue *= pField->multiplier;
iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );
MSG_WriteBitLong( msg, iValue, pField->bits, signbit );
}
else if( pField->flags & DT_SHORT )
@@ -1173,11 +1201,11 @@ static qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, v
iValue = *(int16_t *)((int8_t *)to + pField->offset );
else
iValue = *(uint16_t *)((int8_t *)to + pField->offset );
iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );
if( !Q_equal( pField->multiplier, 1.0 ) )
if( !Q_equal( pField->multiplier, 1.0 ))
iValue *= pField->multiplier;
iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );
MSG_WriteBitLong( msg, iValue, pField->bits, signbit );
}
else if( pField->flags & DT_INTEGER )
@@ -1186,11 +1214,11 @@ static qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, v
iValue = *(int32_t *)((int8_t *)to + pField->offset );
else
iValue = *(uint32_t *)((int8_t *)to + pField->offset );
iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );
if( !Q_equal( pField->multiplier, 1.0 ) )
if( !Q_equal( pField->multiplier, 1.0 ))
iValue *= pField->multiplier;
iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );
MSG_WriteBitLong( msg, iValue, pField->bits, signbit );
}
else if( pField->flags & DT_FLOAT )
@@ -1210,19 +1238,17 @@ static qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, v
}
else if( pField->flags & DT_TIMEWINDOW_8 )
{
signbit = 1; // timewindow is always signed
flValue = *(float *)((byte *)to + pField->offset );
iValue = (int)Q_rint( timebase * 100.0 ) - (int)Q_rint( flValue * 100.0 );
iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );
MSG_WriteBitLong( msg, iValue, pField->bits, signbit );
dt = Q_rint(( timebase - flValue ) * 100.0 );
dt = Delta_ClampIntegerField( pField, dt, 1, pField->bits );
MSG_WriteSBitLong( msg, dt, pField->bits );
}
else if( pField->flags & DT_TIMEWINDOW_BIG )
{
signbit = 1; // timewindow is always signed
flValue = *(float *)((byte *)to + pField->offset );
iValue = (int)Q_rint( timebase * pField->multiplier ) - (int)Q_rint( flValue * pField->multiplier );
iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits );
MSG_WriteBitLong( msg, iValue, pField->bits, signbit );
dt = Q_rint(( timebase - flValue ) * pField->multiplier );
dt = Delta_ClampIntegerField( pField, dt, 1, pField->bits );
MSG_WriteSBitLong( msg, dt, pField->bits );
}
else if( pField->flags & DT_STRING )
{
@@ -1306,9 +1332,12 @@ static qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, vo
if( pField->flags & DT_BYTE )
{
iValue = MSG_ReadBitLong( msg, pField->bits, bSigned );
if( !Q_equal( pField->multiplier, 1.0 ) )
if( !Q_equal( pField->multiplier, 1.0 ))
iValue /= pField->multiplier;
if( !Q_equal( pField->post_multiplier, 1.0 ))
iValue *= pField->post_multiplier;
if( bSigned )
*(int8_t *)((uint8_t *)to + pField->offset ) = iValue;
else
@@ -1317,9 +1346,12 @@ static qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, vo
else if( pField->flags & DT_SHORT )
{
iValue = MSG_ReadBitLong( msg, pField->bits, bSigned );
if( !Q_equal( pField->multiplier, 1.0 ) )
if( !Q_equal( pField->multiplier, 1.0 ))
iValue /= pField->multiplier;
if( !Q_equal( pField->post_multiplier, 1.0 ))
iValue *= pField->post_multiplier;
if( bSigned )
*(int16_t *)((uint8_t *)to + pField->offset ) = iValue;
else
@@ -1328,9 +1360,12 @@ static qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, vo
else if( pField->flags & DT_INTEGER )
{
iValue = MSG_ReadBitLong( msg, pField->bits, bSigned );
if( !Q_equal( pField->multiplier, 1.0 ) )
if( !Q_equal( pField->multiplier, 1.0 ))
iValue /= pField->multiplier;
if( !Q_equal( pField->post_multiplier, 1.0 ))
iValue *= pField->post_multiplier;
if( bSigned )
*(int32_t *)((uint8_t *)to + pField->offset ) = iValue;
else
@@ -1344,11 +1379,11 @@ static qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, vo
else
flValue = iValue;
if( !Q_equal( pField->multiplier, 1.0 ) )
flValue = flValue / pField->multiplier;
if( !Q_equal( pField->multiplier, 1.0 ))
flValue /= pField->multiplier;
if( !Q_equal( pField->post_multiplier, 1.0 ) )
flValue = flValue * pField->post_multiplier;
if( !Q_equal( pField->post_multiplier, 1.0 ))
flValue *= pField->post_multiplier;
*(float *)((byte *)to + pField->offset ) = flValue;
}
@@ -1370,7 +1405,7 @@ static qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, vo
bSigned = true; // timewindow is always signed
iValue = MSG_ReadBitLong( msg, pField->bits, bSigned );
if( !Q_equal( pField->multiplier, 1.0 ) )
if( !Q_equal( pField->multiplier, 1.0 ))
flTime = ( timebase * pField->multiplier - (int)iValue ) / pField->multiplier;
else
flTime = timebase - (int)iValue;
@@ -2069,3 +2104,103 @@ void GAME_EXPORT Delta_UnsetFieldByIndex( delta_t *pFields, int fieldNumber )
dt->pFields[fieldNumber].bInactive = true;
}
#if XASH_ENGINE_TESTS
#include "tests.h"
void Test_RunDelta( void )
{
delta_info_t *dt = &dt_info[DT_DELTA_TEST_STRUCT_T];
delta_test_struct_t from, to = { 0 };
delta_test_struct_t null = { 0 };
sizebuf_t msg;
int i;
char buffer[4096] = { 0 };
const double timebase = 123.123;
// a1ba: netbuffer bitmasks are initialized in netchan for some reason
// initialize it ourselves just in case
MSG_InitMasks(); // initialize bit-masks
Delta_AddField( dt, "dt_string", DT_STRING, 1, 1.0f, 1.0f );
Delta_AddField( dt, "dt_timewindow_big", DT_TIMEWINDOW_BIG, 24, 1000.f, 1.0f );
Delta_AddField( dt, "dt_timewindow_8", DT_TIMEWINDOW_8, 8, 1.0f, 1.0f );
Delta_AddField( dt, "dt_angle", DT_ANGLE, 16, 1.0f, 1.0f );
Delta_AddField( dt, "dt_float_signed", DT_FLOAT | DT_SIGNED, 22, 100.0f, 1.0f );
Delta_AddField( dt, "dt_float_unsigned", DT_FLOAT, 24, 10000.0f, 0.1f );
Delta_AddField( dt, "dt_integer_signed", DT_INTEGER | DT_SIGNED, 24, 1.0f, 1.0f );
Delta_AddField( dt, "dt_integer_unsigned", DT_INTEGER, 24, 1.0f, 1.0f );
Delta_AddField( dt, "dt_short_signed", DT_SHORT | DT_SIGNED, 16, 1.0f, 1.0f );
Delta_AddField( dt, "dt_short_unsigned", DT_SHORT, 15, 0.125f, 1.0f );
Delta_AddField( dt, "dt_byte_signed", DT_BYTE | DT_SIGNED, 6, 1.0f, 1.0f );
Delta_AddField( dt, "dt_byte_unsigned", DT_BYTE, 8, 1.0f, 1.0f );
Q_strncpy( from.dt_string, "test data check it's the same", sizeof( from.dt_string ));
from.dt_timewindow_big = timebase + 2.3456;
from.dt_timewindow_8 = timebase + 0.0234;
from.dt_angle = 160.245f;
from.dt_float_signed = -15.123f;
from.dt_float_unsigned = 1235.321f;
from.dt_integer_signed = -412784;
from.dt_integer_unsigned = 123453;
from.dt_short_signed = -12343;
from.dt_short_unsigned = 32131;
from.dt_byte_signed = 16;
from.dt_byte_unsigned = 218;
MSG_Init( &msg, "test message", buffer, sizeof( buffer ));
for( i = 0; i < dt->numFields; i++ )
Delta_WriteField( &msg, &dt->pFields[i], &null, &from, timebase );
MSG_SeekToBit( &msg, 0, SEEK_SET );
for( i = 0; i < dt->numFields; i++ )
Delta_ReadField( &msg, &dt->pFields[i], &null, &to, timebase );
Con_Printf( "struct as encoded to delta:\n" );
TASSERT_STR( from.dt_string, to.dt_string );
// the epsilon value is derived from multiplier value
TASSERT( Q_equal_e( from.dt_timewindow_big, to.dt_timewindow_big, 0.001f ));
// dt_timewindow_8 type has multiplier locked at 100.0f
TASSERT( Q_equal_e( from.dt_timewindow_8, to.dt_timewindow_8, 0.01f ));
TASSERT( Q_equal_e( from.dt_angle, to.dt_angle, 0.1f ));
TASSERT( Q_equal_e( from.dt_float_signed, to.dt_float_signed, 0.01f ));
// dt_float_unsigned has post-multiplier that doesn't affect network data
// and therefore should be reverted back when comparing
TASSERT( Q_equal_e( from.dt_float_unsigned, to.dt_float_unsigned * 10.f , 0.01f ));
TASSERT_EQi( from.dt_integer_signed, to.dt_integer_signed );
TASSERT_EQi( from.dt_integer_unsigned, to.dt_integer_unsigned );
TASSERT_EQi( from.dt_short_signed, to.dt_short_signed );
TASSERT(( from.dt_short_unsigned & ( 0xffff << 3 )) == to.dt_short_unsigned );
TASSERT_EQi( from.dt_byte_signed, to.dt_byte_signed );
TASSERT_EQi( from.dt_byte_unsigned, to.dt_byte_unsigned );
Con_Printf( "from.dt_timewindow_big = %f\n", from.dt_timewindow_big );
Con_Printf( "to.dt_timewindow_big = %f\n", to.dt_timewindow_big );
Con_Printf( "from.dt_timewindow_8 = %f\n", from.dt_timewindow_8 );
Con_Printf( "to.dt_timewindow_8 = %f\n", to.dt_timewindow_8 );
Con_Printf( "from.dt_angle = %f\n", from.dt_angle );
Con_Printf( "to.dt_angle = %f\n", to.dt_angle );
Con_Printf( "from.dt_float_signed = %f\n", from.dt_float_signed );
Con_Printf( "to.dt_float_signed = %f\n", to.dt_float_signed );
Con_Printf( "from.dt_float_unsigned = %f\n", from.dt_float_unsigned );
Con_Printf( "to.dt_float_unsigned = %f\n", to.dt_float_unsigned );
Con_Printf( "from.dt_integer_signed = %i\n", from.dt_integer_signed );
Con_Printf( "to.dt_integer_signed = %i\n", to.dt_integer_signed );
Con_Printf( "from.dt_integer_unsigned = %i\n", from.dt_integer_unsigned );
Con_Printf( "to.dt_integer_unsigned = %i\n", to.dt_integer_unsigned );
Con_Printf( "from.dt_short_signed = %i\n", from.dt_short_signed );
Con_Printf( "to.dt_short_signed = %i\n", to.dt_short_signed );
Con_Printf( "from.dt_short_unsigned = %i\n", from.dt_short_unsigned );
Con_Printf( "to.dt_short_unsigned = %i\n", to.dt_short_unsigned );
Con_Printf( "from.dt_byte_signed = %i\n", from.dt_byte_signed );
Con_Printf( "to.dt_byte_signed = %i\n", to.dt_byte_signed );
Con_Printf( "from.dt_byte_unsigned = %i\n", from.dt_byte_unsigned );
Con_Printf( "to.dt_byte_unsigned = %i\n", to.dt_byte_unsigned );
}
#endif // XASH_ENGINE_TESTS

View File

@@ -53,7 +53,7 @@ GNU General Public License for more details.
#include "library.h"
#include "whereami.h"
static int error_on_exit = 0; // arg for exit();
int error_on_exit = 0; // arg for exit();
/*
================

View File

@@ -41,7 +41,7 @@ NOTE: never change this structure because all dll descriptions in xash code
writes into struct by offsets not names
========================================================================
*/
extern int error_on_exit;
void Sys_Sleep( int msec );
double Sys_DoubleTime( void );
char *Sys_GetClipboardData( void );

View File

@@ -39,13 +39,15 @@ void Test_RunCon( void );
void Test_RunVOX( void );
void Test_RunIPFilter( void );
void Test_RunGamma( void );
void Test_RunDelta( void );
#define TEST_LIST_0 \
Test_RunLibCommon(); \
Test_RunCommon(); \
Test_RunCmd(); \
Test_RunCvar(); \
Test_RunIPFilter();
Test_RunIPFilter(); \
Test_RunDelta();
#define TEST_LIST_0_CLIENT \
Test_RunCon(); \

View File

@@ -73,7 +73,8 @@ GNU General Public License for more details.
#define Q_min( a, b ) (((a) < (b)) ? (a) : (b))
#define Q_max( a, b ) (((a) > (b)) ? (a) : (b))
#define Q_equal( a, b ) (((a) > ((b) - EQUAL_EPSILON)) && ((a) < ((b) + EQUAL_EPSILON)))
#define Q_equal_e( a, b, e ) (((a) > ((b) - (e))) && ((a) < ((b) + (e))))
#define Q_equal( a, b ) Q_equal_e( a, b, EQUAL_EPSILON )
#define Q_recip( a ) ((float)(1.0f / (float)(a)))
#define Q_floor( a ) ((float)(int)(a))
#define Q_ceil( a ) ((float)(int)((a) + 1))