Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle dropped gamestate after UDP download #286

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions code/server/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,6 @@ struct leakyBucket_s {
leakyBucket_t *prev, *next;
};

typedef enum {
GSA_INIT = 0, // gamestate never sent with current sv.serverId
GSA_SENT_ONCE, // gamestate sent once, client can reply with any (messageAcknowledge - gamestateMessageNum) >= 0 and correct serverId
GSA_SENT_MANY, // gamestate sent many times, client must reply with exact gamestateMessageNum == gamestateMessageNum and correct serverId
GSA_ACKED // gamestate acknowledged, no retansmissions needed
} gameStateAck_t;

typedef struct client_s {
clientState_t state;
char userinfo[MAX_INFO_STRING]; // name, etc
Expand All @@ -174,11 +167,8 @@ typedef struct client_s {
sharedEntity_t *gentity; // SV_GentityNum(clientnum)
char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked

gameStateAck_t gamestateAck;
qboolean downloading; // set at "download", reset at gamestate retransmission
// int serverId; // last acknowledged serverId

// downloading
qboolean downloading; // set at "download", reset at gamestate retransmission
char downloadName[MAX_QPATH]; // if not empty string, we are downloading
fileHandle_t download; // file being downloaded
int downloadSize; // total bytes (can't use EOF because of paks)
Expand All @@ -190,6 +180,7 @@ typedef struct client_s {
int downloadBlockSize[MAX_DOWNLOAD_WINDOW];
qboolean downloadEOF; // We have sent the EOF block
int downloadSendTime; // time we last got an ack from the client
qboolean downloadGamestateDropCheck; // perform extra dropped gamestate check after downloads

int deltaMessage; // frame last client usercmd message
int lastPacketTime; // svs.time when packet was last received
Expand Down
102 changes: 51 additions & 51 deletions code/server/sv_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -1013,9 +1013,13 @@ This will be sent on the initial connection and upon each new map load.

It will be resent if the client acknowledges a later message but has
the wrong gamestate.

In cases where it's safe to assume client can't drop the gamestate and
use a previously sent one, resetCsUpdated can be set to true to avoid
unnecessary configstring updating.
================
*/
static void SV_SendClientGameState( client_t *client ) {
static void SV_SendClientGameState( client_t *client, qboolean resetCsUpdated ) {
int start;
entityState_t nullstate;
const svEntity_t *svEnt;
Expand All @@ -1028,12 +1032,6 @@ static void SV_SendClientGameState( client_t *client ) {

client->state = CS_PRIMED;

if ( client->gamestateAck == GSA_INIT ) {
client->gamestateAck = GSA_SENT_ONCE;
} else {
client->gamestateAck = GSA_SENT_MANY;
}

client->downloading = qfalse;

client->pureAuthentic = qfalse;
Expand Down Expand Up @@ -1082,7 +1080,9 @@ static void SV_SendClientGameState( client_t *client ) {
MSG_WriteBigString( &msg, sv.configstrings[start] );
}
}
client->csUpdated[ start ] = qfalse;
if ( resetCsUpdated ) {
client->csUpdated[ start ] = qfalse;
}
}

// write the baselines
Expand Down Expand Up @@ -1140,7 +1140,6 @@ void SV_ClientEnterWorld( client_t *client ) {
}

client->state = CS_ACTIVE;
client->gamestateAck = GSA_ACKED;

client->oldServerTime = 0;

Expand Down Expand Up @@ -1230,7 +1229,7 @@ static void SV_DoneDownload_f( client_t *cl ) {
Com_DPrintf( "clientDownload: %s Done\n", cl->name );

// resend the game state to update any clients that entered during the download
SV_SendClientGameState( cl );
SV_SendClientGameState( cl, qtrue );
}


Expand Down Expand Up @@ -1286,7 +1285,7 @@ static void SV_BeginDownload_f( client_t *cl ) {
cl->gentity = NULL;

cl->downloading = qtrue;
cl->gamestateAck = GSA_SENT_MANY; // expect exact messageAcknowledge next time
cl->downloadGamestateDropCheck = qtrue;
}


Expand Down Expand Up @@ -2163,7 +2162,7 @@ static void SV_UserMove( client_t *cl, msg_t *msg, qboolean delta ) {
// we didn't get a cp yet, don't assume anything and just send the gamestate all over again
if ( !SVC_RateLimit( &cl->gamestate_rate, 2, 1000 ) ) {
Com_DPrintf( "%s: didn't get cp command, resending gamestate\n", cl->name );
SV_SendClientGameState( cl );
SV_SendClientGameState( cl, qfalse );
}
return;
}
Expand Down Expand Up @@ -2269,42 +2268,12 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) {

cl->justConnected = qfalse;

// cl->serverId = serverId;

// if this is a usercmd from a previous gamestate,
// ignore it or retransmit the current gamestate
//
// if the client was downloading, let it stay at whatever serverId and
// gamestate it was at. This allows it to keep downloading even when
// the gamestate changes. After the download is finished, we'll
// notice and send it a new game state
//
if ( cl->state == CS_CONNECTED ) {
if ( !cl->downloading ) {
// send initial gamestate, client may not acknowledge it in next command but start downloading after SV_ClientCommand()
if ( !SVC_RateLimit( &cl->gamestate_rate, 2, 1000 ) ) {
SV_SendClientGameState( cl );
}
return;
}
} else if ( cl->gamestateAck != GSA_ACKED ) {
// early check for gamestate acknowledge
if ( serverId == sv.serverId ) {
const int delta = cl->messageAcknowledge - cl->gamestateMessageNum;
if ( delta == 0 || ( delta > 0 && cl->gamestateAck == GSA_SENT_ONCE ) ) {
cl->gamestateAck = GSA_ACKED;
// this client has acknowledged the new gamestate so it's
// safe to start sending it the real time again
Com_DPrintf( "%s acknowledged gamestate with delta %i\n", cl->name, delta );
cl->oldServerTime = 0;
}
}
if ( cl->oldServerTime && serverId == sv.serverId ) {
// this client has acknowledged the new gamestate so it's
// safe to start sending it the real time again
Com_DPrintf( "%s acknowledged gamestate\n", cl->name );
cl->oldServerTime = 0;
}
// else if ( cl->state == CS_PRIMED ) {
// in case of download intention client replies with (messageAcknowledge - gamestateMessageNum) >= 0 and (serverId == sv.serverId), sv.serverId can drift away later
// in case of lost gamestate client replies with (messageAcknowledge - gamestateMessageNum) > 0 and (serverId == sv.serverId)
// in case of disconnect/etc. client replies with any serverId
//}

// read optional clientCommand strings
do {
Expand All @@ -2320,12 +2289,25 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) {
}
} while ( 1 );

if ( cl->gamestateAck != GSA_ACKED ) {
// late check for gamestate resend
if ( cl->state == CS_PRIMED && cl->messageAcknowledge - cl->gamestateMessageNum > 0 ) {
if ( cl->downloading ) {
// waiting for "donedl" command
return;
}

// check for sending initial gamestate
if ( cl->state == CS_CONNECTED ) {
if ( !SVC_RateLimit( &cl->gamestate_rate, 2, 1000 ) ) {
SV_SendClientGameState( cl, qtrue );
}
return;
}

// check for dropped gamestate
if ( cl->state != CS_ACTIVE && serverId != sv.serverId ) {
if ( cl->messageAcknowledge - cl->gamestateMessageNum > 0 ) {
Com_DPrintf( "%s: dropped gamestate, resending\n", cl->name );
if ( !SVC_RateLimit( &cl->gamestate_rate, 2, 1000 ) ) {
SV_SendClientGameState( cl );
SV_SendClientGameState( cl, qtrue );
}
}
return;
Expand All @@ -2342,4 +2324,22 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) {
// if ( msg->readcount != msg->cursize ) {
// Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients );
// }

// extra check for dropped gamestate for post-UDP download clients, since after
// download client can have correct serverid but still be awaiting gamestate
if ( cl->downloadGamestateDropCheck ) {
const int gsDelta = cl->messageAcknowledge - cl->gamestateMessageNum;
// either a move command (implied by CS_ACTIVE) or exact acknowledge of
// gamestate message number implies client has the new gamestate
if ( cl->state == CS_ACTIVE || gsDelta == 0 ) {
Com_DPrintf( "%s: acknowledged post-download gamestate (state:%i gsDelta:%i)\n",
cl->name, cl->state, gsDelta );
cl->downloadGamestateDropCheck = qfalse;
} else if ( gsDelta > 20 ) {
Com_DPrintf( "%s: dropped post-download gamestate, resending\n", cl->name );
if ( !SVC_RateLimit( &cl->gamestate_rate, 2, 1000 ) ) {
SV_SendClientGameState( cl, qfalse );
}
}
}
}
2 changes: 1 addition & 1 deletion code/server/sv_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -586,11 +586,11 @@ void SV_SpawnServer( const char *mapname, qboolean killBots ) {
SV_DropClient( &svs.clients[i], denied );
} else {
if ( !isBot ) {
svs.clients[i].gamestateAck = GSA_INIT; // resend gamestate, accept first correct serverId
// when we get the next packet from a connected client,
// the new gamestate will be sent
svs.clients[i].state = CS_CONNECTED;
svs.clients[i].gentity = NULL;
svs.clients[i].downloadGamestateDropCheck = qfalse;
} else {
SV_ClientEnterWorld( &svs.clients[i] );
}
Expand Down