Skip to content

Commit b0d405a

Browse files
newbytfnewby
andauthored
Fix pmove_fixed (#70)
* cvar: add cg_packetdelay adds access to cl_packetdelay which is normally cheat protected; useful for both testing and ping equalizing. * cvar: add `cl_maxfps` which replaces `com_maxfps` Add a new client `cl_maxfps` which allows specifying the maximum desired fps value; which may not be the same as the maximum allowed. `com_maxfps` is now derived from `cl_maxfps`. Current values are 250 when `pmove_fixed=1` and 125 when `pmove_fixed=0` The default value of `cl_maxfps -1` should probably be preferred because it just picks the best currently supported max. Future patches will raise this to 500 in the pmove_fixed=1 case. * bug fixes: fix old nail bugs and refine prediction - remove fudge factors for prediction being a rendered frame ahea and use the time from command frame directly. --> 0 error on prediction. - fix ancient nail bugs where they were not being correctly snapped - fix ancient nail bugs where original position was wrong for collision calcs * platform: fix mingw cross compile The introduced byteswap.h dependency isn't portable, use supported intrinsics * pmove: add pmove_float add upstream `pmove_float` (default on) which makes jump height no longer fps dependent. Previously this specific behavior resulted from accumulated errors on gravity truncation; resolve by not truncating gravity except for one case which otherwise results in erratic accel. Note: This doesnt fix other fps dependent accel * pmove_fixed: refactor ClientEndFrame - not correctly executed under pmove_fixed - no reason to evaluate every _client_ frame - no reason to buffer underruns (client >1s stale has bigger problems) - merge per server frame checks * pmove-fixed: rectify and cleanup - remove client toggle (either it works for everyone or it's bugged and shouldn't be used) - remove realping, adds high per-frame overhead, not used - remove latency simulation, packetdelay exists and adds complexity to prediction cache / one less think to think about with pmove_fixed - move pmove_var fixup out of clientthink * pmove-fixed: add handling for overlapping client frames pmove_fixed would previously incorrectly overlap and not handle certain frames due to treatment of time. Many frames being assigned the same time was resulting in inconsistent processing, caching, etc. pmove also segmented ranges awkwardly, e.g. (pmsec*n, pmsec*(n+1)], change this to [pmsec%n,pmsec*(n+1)) which brings mod range in congruence. Resolve this by making it an internal detail to pmove, allowing external code to behave independently wrt pmove_fixed being enabled. * pmove: interp view pos with pmove_fixed When pmove_fixed is enable we want to generate interpolated subframes between movement frames so that we're updating the viewport at the intended rate (consider for example the case of walking in a straight line without moving mouse). --------- Co-authored-by: newby <newby@qwtf.live>
1 parent d83df8c commit b0d405a

21 files changed

Lines changed: 514 additions & 557 deletions

code/api/shared/q_endian.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ void CopyLongSwap(void *dest, void *src);
9393
#define LongLongSwap(x) bswap64(x)
9494
#endif
9595

96+
#elif defined(__MINGW32__)
97+
98+
#define ShortSwap(x) __builtin_bswap16(x)
99+
#define LongSwap(x) __builtin_bswap32(x)
100+
#define LongLongSwap(x) __builtin_bswap64(x)
101+
96102
#else
97103

98104
#include <byteswap.h>

code/cgame/cg_cvar.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ CG_CVAR( cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE )
148148
CG_CVAR( cl_maxpackets, "cl_maxpackets", "60", CVAR_ARCHIVE )
149149
CG_CVAR( r_maxpolys, "r_maxpolys", "1800", CVAR_ARCHIVE )
150150
CG_CVAR( r_maxpolyverts, "r_maxpolyverts", "9000", CVAR_ARCHIVE )
151-
CG_CVAR( com_maxfps, "com_maxfps", "85", CVAR_ARCHIVE )
151+
CG_CVAR( com_maxfps, "com_maxfps", "125", CVAR_CHEAT)
152+
CG_CVAR( cl_maxfps, "cl_maxfps", "-1", CVAR_ARCHIVE )
152153
CG_CVAR( cg_blood, "com_blood", "1", CVAR_ARCHIVE )
153154
CG_CVAR( cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE )
154155
CG_CVAR( cg_scorePlum, "cg_scorePlums", "1", CVAR_ARCHIVE )
@@ -163,6 +164,9 @@ CG_CVAR( cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0 )
163164
CG_CVAR( cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0 )
164165
CG_CVAR( cg_timescale, "timescale", "1", 0 )
165166
CG_CVAR( r_clear, "r_clear", "0", 0 )
167+
CG_CVAR( cg_packetdelay, "cg_packetdelay", "0", CVAR_USERINFO )
168+
CG_CVAR( cl_packetdelay, "cl_packetdelay", "0", CVAR_CHEAT )
169+
CG_CVAR( cg_pmovesmooth, "cg_pmovesmooth", "1", CVAR_ARCHIVE )
166170

167171

168172
CG_CVAR( r_debugSort, "r_debugSort", "0", CVAR_CHEAT )
@@ -225,8 +229,6 @@ CG_CVAR( cg_cmdTimeNudge, "cg_cmdTimeNudge", "0", CVAR_ARCHIVE | CVAR_USERI
225229
CG_CVAR( cg_projectileNudge, "cg_projectileNudge", "0", CVAR_ARCHIVE )
226230
CG_CVAR( cg_optimizePrediction, "cg_optimizePrediction", "1", CVAR_ARCHIVE )
227231
CG_CVAR( cl_timeNudge, "cl_timeNudge", "0", CVAR_ARCHIVE | CVAR_USERINFO )
228-
CG_CVAR( cg_latentSnaps, "cg_latentSnaps", "0", CVAR_USERINFO | CVAR_CHEAT )
229-
CG_CVAR( cg_latentCmds, "cg_latentCmds", "0", CVAR_USERINFO | CVAR_CHEAT )
230232
CG_CVAR( cg_plOut, "cg_plOut", "0", CVAR_USERINFO | CVAR_CHEAT )
231233
CG_CVAR( cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE )
232234
CG_CVAR( cg_predictWeapons, "cg_predictWeapons", "0", CVAR_ARCHIVE )

code/cgame/cg_local.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,6 @@ typedef struct centity_s {
314314
// exact interpolated position of entity on this frame
315315
vec3_t lerpOrigin;
316316
vec3_t lerpAngles;
317-
318317
} centity_t;
319318

320319

@@ -764,6 +763,7 @@ typedef struct {
764763
vec3_t kick_origin;
765764

766765
// temp working variables for player view
766+
vec3_t view_org, last_pmove_fixed;
767767
float bobfracsin;
768768
int bobcycle;
769769
float xyspeed;
@@ -1451,14 +1451,16 @@ typedef struct {
14511451
float oldtimescale; // Timescale value prior to pausing
14521452

14531453
qboolean pmove_fixed;
1454-
int pmove_msec;
1454+
int pmove_msec;
1455+
int pmove_float;
14551456

14561457
int sv_fps;
14571458

14581459
qboolean synchronousClients;
14591460

14601461
qboolean sv_cheats;
14611462

1463+
int active_maxfps;
14621464
} cgs_t;
14631465

14641466
//==============================================================================
@@ -2302,5 +2304,6 @@ extern int cvar_developer;
23022304
extern int dll_trap_R_AddRefEntityToScene2;
23032305
extern int dll_trap_R_AddLinearLightToScene;
23042306

2307+
void CG_Update_MaxFPS(void);
23052308

23062309
#endif //__CG_LOCAL_H

code/cgame/cg_localents.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ static void CG_UntrackPredictedEnt(localEntity_t *le) {
967967

968968
void CG_MatchPredictedEnt(centity_t* cent) {
969969
localEntity_t* match = NULL;
970-
float best = SQR(64);
970+
float best = SQR(32);
971971
vec3_t diff;
972972

973973
// Can only match entities we own.
@@ -1025,6 +1025,7 @@ void CG_UpdateLocalPredictedEnts(void) {
10251025
}
10261026

10271027
void CG_AddPredictedMissile(entityState_t* ent, vec3_t origin, vec3_t forward) {
1028+
usercmd_t cmd;
10281029
localEntity_t *le;
10291030
int vel, tmod = 0;
10301031

@@ -1058,15 +1059,21 @@ void CG_AddPredictedMissile(entityState_t* ent, vec3_t origin, vec3_t forward) {
10581059
le->leFlags = LEF_PREDICTED;
10591060
le->pred_weapon = ent->weapon;
10601061

1062+
// Technically we could chain this from where we predict the event but this
1063+
// should always be correct since command generation is tied to frame
1064+
// generation.
1065+
trap_GetUserCmd( trap_GetCurrentCmdNumber(), &cmd );
1066+
10611067
le->pos.trType = TR_LINEAR;
1062-
le->pos.trTime = cg.time - tmod + cl_timeNudge.integer;
1068+
le->pos.trTime = cmd.serverTime - tmod + cl_timeNudge.integer;
10631069

1064-
VectorCopy(origin, le->lerp.trBase);
1065-
VectorCopy(origin, le->pos.trBase);
10661070
if (ent->weapon == WP_SUPERNAILGUN || ent->weapon == WP_NAILGUN) {
1067-
VectorMA(le->pos.trBase, -15, forward, le->pos.trBase);
1068-
le->pos.trBase[2] -= 6;
1071+
VectorMA(origin, -15, forward, origin);
1072+
origin[2] -= 6;
1073+
SnapVector(origin);
10691074
}
1075+
VectorCopy(origin, le->lerp.trBase);
1076+
VectorCopy(origin, le->pos.trBase);
10701077

10711078
VectorScale(forward, vel, le->pos.trDelta );
10721079
SnapVector(le->pos.trDelta);

code/cgame/cg_main.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ static cvarLimitTable_t cvarLimitTable[] = {
170170
{ &cl_maxpackets, "cl_maxpackets", 40, 30, -1, 0, 0, qfalse },
171171
{ &r_maxpolys, "r_maxpolys", 1800, 1800, -1, 0, 0, qfalse },
172172
{ &r_maxpolyverts, "r_maxpolyverts", 9000, 9000, -1, 0, 0, qfalse },
173-
{ &com_maxfps, "com_maxfps", 85, 30, 130, 0, 0, qfalse },
174173
{ &cg_fov, "cg_fov", 90, 5, 135, 0, 0, qfalse },
175174
{ &cg_thirdPerson, "cg_thirdPerson", 0, 0, 0, 0, 0, qfalse },
176175
{ &r_lodCurveError, "r_lodCurveError", 250, 1, -1, 0, 0, qfalse },
@@ -198,8 +197,6 @@ static cvarLimitTable_t cvarLimitTable[] = {
198197
//Unlagged cvars
199198
{ &cg_cmdTimeNudge, "cg_cmdTimeNudge", 0, 0, 999, 0, 0, qtrue },
200199
{ &cl_timeNudge, "cl_timeNudge", 0, -50, 50, 0, 0, qtrue },
201-
{ &cg_latentSnaps, "cg_latentSnaps", 0, 0, 10, 0, 0, qtrue },
202-
{ &cg_latentCmds, "cg_latentCmds", 0, 0, MAX_LATENT_CMDS - 1 , 0, 0, qtrue },
203200
{ &cg_plOut, "cg_plOut", 0, 0, 100 , 0, 0, qtrue },
204201
// hunkmegs
205202
{ &com_hunkmegs, "com_hunkmegs", 128, 128, -1, 0, 0, qfalse },
@@ -209,6 +206,8 @@ static cvarLimitTable_t cvarLimitTable[] = {
209206
{ &cg_cl_yawspeed, "cl_yawspeed", 140, 0, 0, 0, 0, qfalse },
210207
{ &cg_cl_pitchspeed, "cl_pitchspeed", 140, 0, 0, 0, 0, qfalse },
211208
{ &cg_cl_freelook, "cl_freelook", 1, 1, 1, 0, 0, qfalse },
209+
210+
{ &cg_packetdelay, "cg_packetdelay", 0, 0, 200, 0, 0, qfalse },
212211
};
213212

214213
static const int cvarLimitTableSize = (int)ARRAY_LEN( cvarLimitTable );
@@ -515,6 +514,8 @@ void CG_UpdateCvars( void ) {
515514
}
516515
else if (cv->vmCvar == &cg_pipeTrail) {
517516
CG_UpdateColorFromCvar(cg_pipeTrail.string, colorPipeTrail, &cg.pipeTrailTeam, cg.pipeTrailColor);
517+
} else if (cv->vmCvar == &cl_maxfps || cv->vmCvar == &com_maxfps) {
518+
CG_Update_MaxFPS();
518519
}
519520
}
520521
}
@@ -538,6 +539,12 @@ void CG_UpdateCvars( void ) {
538539

539540
// limit cvars
540541
CG_LimitCvars();
542+
543+
// Handle packet delay
544+
if (cg_packetdelay.integer != cl_packetdelay.integer) {
545+
trap_Cvar_Set("cl_packetdelay", cg_packetdelay.string);
546+
trap_Cvar_Update(&cl_packetdelay);
547+
}
541548
}
542549

543550

code/cgame/cg_predict.c

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -664,11 +664,11 @@ void CG_PredictPlayerState( void ) {
664664
cg.predictedPlayerState = cg.snap->ps;
665665
}
666666

667-
// demo playback just copies the moves
668-
if ( cg.demoPlayback ||
669-
(cg.snap->ps.pm_flags & PMF_FOLLOW) ||
670-
(cg.snap->ps.pm_flags & PMF_CHASE) ) {
667+
// demo playback/spectating just copies the moves
668+
if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ||
669+
(cg.snap->ps.pm_flags & PMF_CHASE) ) {
671670
CG_InterpolatePlayerState( qfalse );
671+
VectorCopy( cg.predictedPlayerState.origin, cg.view_org );
672672
return;
673673
}
674674

@@ -736,6 +736,7 @@ void CG_PredictPlayerState( void ) {
736736

737737
cg_pmove.pmove_fixed = (int)cgs.pmove_fixed;
738738
cg_pmove.pmove_msec = cgs.pmove_msec;
739+
cg_pmove.pmove_float = cgs.pmove_float;
739740

740741
// Like the comments described above, a player's state is entirely
741742
// re-predicted from the last valid snapshot every client frame, which
@@ -755,10 +756,7 @@ void CG_PredictPlayerState( void ) {
755756
// except a frame following a new snapshot in which there was a prediction
756757
// error. This yeilds anywhere from a 15% to 40% performance increase,
757758
// depending on how much of a bottleneck the CPU is.
758-
759-
// we check for cg_latentCmds because it'll mess up the optimization
760-
// FIXME: make cg_latentCmds work with cg_optimizePrediction?
761-
if ( cg_optimizePrediction.integer && !cg_latentCmds.integer ) {
759+
if ( cg_optimizePrediction.integer) {
762760
if ( cg.nextFrameTeleport || cg.thisFrameTeleport ) {
763761
// do a full predict
764762
cg.lastPredictedCommand = 0;
@@ -829,10 +827,6 @@ void CG_PredictPlayerState( void ) {
829827
// get the command
830828
trap_GetUserCmd( cmdNum, &cg_pmove.cmd );
831829

832-
if ( cg_pmove.pmove_fixed ) {
833-
PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd );
834-
}
835-
836830
// don't do anything if the time is before the snapshot player time
837831
if ( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) {
838832
continue;
@@ -917,24 +911,21 @@ void CG_PredictPlayerState( void ) {
917911
VectorSet( cg_pmove.groundVelocity, 0, 0, 0 );
918912
}
919913

920-
if ( cg_pmove.pmove_fixed ) {
921-
cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + cgs.pmove_msec-1) / cgs.pmove_msec) * cgs.pmove_msec;
922-
}
923-
924914
// ydnar: if server respawning, freeze the player
925915
if ( cg.serverRespawning ) {
926916
cg_pmove.ps->pm_type = PM_FREEZE;
927917
}
928918

929919
cg_pmove.airleft = (cg.waterundertime - cg.time);
920+
cg_pmove.retflags = 0;
930921

931922
if( cg.agentDataEntity && cg.agentDataEntity->currentValid ) {
932923
cg_pmove.agentclass = cg.agentDataEntity->currentState.torsoAnim;
933924
} else {
934925
cg_pmove.agentclass = 0;
935926
}
936-
// we check for cg_latentCmds because it'll mess up the optimization
937-
if ( cg_optimizePrediction.integer && !cg_latentCmds.integer ) {
927+
928+
if ( cg_optimizePrediction.integer ) {
938929
// if we need to predict this command, or we've run out of space in the saved states queue
939930
if ( cmdNum >= predictCmd || (stateIndex + 1) % NUM_SAVED_STATES == cg.stateHead ) {
940931
// run the Pmove
@@ -985,6 +976,8 @@ void CG_PredictPlayerState( void ) {
985976
//CG_CheckChangedPredictableEvents(&cg.predictedPlayerState);
986977
}
987978

979+
VectorCopy( cg.predictedPlayerState.origin, cg.view_org );
980+
988981
if ( cg_showmiss.integer > 1 ) {
989982
CG_Printf( BOX_PRINT_MODE_CHAT, "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time );
990983
}
@@ -1016,4 +1009,23 @@ void CG_PredictPlayerState( void ) {
10161009
cg.eventSequence = cg.predictedPlayerState.eventSequence;
10171010
}
10181011
}
1012+
1013+
// Need to interp with pmove_fixed to avoid effective 1/pmove_msec fps.
1014+
if (cgs.pmove_fixed) {
1015+
if ((cg_pmove.retflags & PMRF_PMOVE_PARTIAL) == 0) // New full frame
1016+
VectorCopy(oldPlayerState.origin, cg.last_pmove_fixed);
1017+
1018+
// We can be very conservative about when we choose to stitch.
1019+
if (cg_pmovesmooth.integer &&
1020+
cg.predictedPlayerState.commandTime - oldPlayerState.commandTime <= cgs.pmove_msec &&
1021+
DistanceSquared(cg.predictedPlayerState.origin, cg.last_pmove_fixed) < SQR(16) &&
1022+
!cg.thisFrameTeleport) {
1023+
vec3_t diff;
1024+
float dt = 1 + (cg.predictedPlayerState.commandTime % cgs.pmove_msec);
1025+
dt /= 1.0 * cgs.pmove_msec;
1026+
1027+
VectorSubtract(cg.predictedPlayerState.origin, cg.last_pmove_fixed, diff);
1028+
VectorMA(cg.last_pmove_fixed, dt, diff, cg.view_org);
1029+
}
1030+
}
10191031
}

code/cgame/cg_servercmds.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ void CG_ParseSysteminfo( void ) {
217217
} else if ( cgs.pmove_msec > 33 ) {
218218
cgs.pmove_msec = 33;
219219
}
220+
cgs.pmove_float = ( atoi( Info_ValueForKey( info, "pmove_float" ) ) ) ? qtrue : qfalse;
221+
CG_Update_MaxFPS();
220222

221223
cgs.sv_fps = Q_atoi( Info_ValueForKey( info, "sv_fps" ) );
222224

@@ -899,3 +901,23 @@ void CG_ExecuteNewServerCommands( int latestSequence ) {
899901
}
900902
}
901903
}
904+
905+
906+
/*
907+
====================
908+
CG_Update_MaxFPS
909+
910+
Update `com_maxfps` to max allowed value by gamesettings and `cl_maxfps`.
911+
====================
912+
*/
913+
void CG_Update_MaxFPS(void) {
914+
// Temp: Higher than 250 wants changes later in stack at higher pings.
915+
int fps = cl_maxfps.integer == -1 ? 250 : cl_maxfps.integer;
916+
fps = Q_max(Q_min(fps, cgs.pmove_fixed ? 500 : 125), 60);
917+
918+
if (com_maxfps.integer == fps)
919+
return;
920+
921+
trap_Cvar_Set("com_maxfps", va("%d", fps));
922+
trap_Cvar_Update(&com_maxfps);
923+
}

code/cgame/cg_snapshot.c

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -306,26 +306,13 @@ static snapshot_t *CG_ReadNextSnapshot( void ) {
306306
// try to read the snapshot from the client system
307307
cgs.processedSnapshotNum++;
308308
r = trap_GetSnapshot( cgs.processedSnapshotNum, dest );
309-
// the client wants latent snaps and the just-read snapshot is valid
310-
if ( cg_latentSnaps.integer && r ) {
311-
int i = 0, time = dest->serverTime;
312-
313-
// keep grabbing one snapshot earlier until we get to the right time
314-
while ( dest->serverTime > time - cg_latentSnaps.integer * (1000 / cgs.sv_fps) ) {
315-
if ( !(r = trap_GetSnapshot( cgs.processedSnapshotNum - i, dest )) ) {
316-
// the snapshot is not valid, so stop here
317-
break;
318-
}
319-
320-
// go back one more
321-
i++;
322-
}
323-
}
324309

310+
#if 0
325311
// FIXME: why would trap_GetSnapshot return a snapshot with the same server time
326312
if ( cg.snap && r && dest->serverTime == cg.snap->serverTime ) {
327313
//continue;
328314
}
315+
#endif
329316

330317
// if it succeeded, return
331318
if ( r ) {
@@ -349,7 +336,6 @@ static snapshot_t *CG_ReadNextSnapshot( void ) {
349336
return NULL;
350337
}
351338

352-
353339
/*
354340
============
355341
CG_ProcessSnapshots
@@ -377,13 +363,8 @@ void CG_ProcessSnapshots( void ) {
377363
trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime );
378364
if ( n != cg.latestSnapshotNum ) {
379365
if ( n < cg.latestSnapshotNum ) {
380-
// this may actually happen with lag simulation going on
381-
if ( cg_latentSnaps.integer ) {
382-
CG_Printf( BOX_PRINT_MODE_CHAT, "WARNING: CG_ProcessSnapshots: n < cg.latestSnapshotNum\n" );
383-
} else {
384366
// this should never happen
385-
CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" );
386-
}
367+
CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" );
387368
}
388369
cg.latestSnapshotNum = n;
389370
}
@@ -423,12 +404,7 @@ void CG_ProcessSnapshots( void ) {
423404

424405
// if time went backwards, we have a level restart
425406
if ( cg.nextSnap->serverTime < cg.snap->serverTime ) {
426-
// this may actually happen with lag simulation going on
427-
if ( cg_latentSnaps.integer ) {
428-
CG_Printf( BOX_PRINT_MODE_CHAT, "WARNING: CG_ProcessSnapshots: Server time went backwards\n" );
429-
} else {
430-
CG_Error( "CG_ProcessSnapshots: Server time went backwards" );
431-
}
407+
CG_Error( "CG_ProcessSnapshots: Server time went backwards" );
432408
}
433409
}
434410

0 commit comments

Comments
 (0)