From 5090f6da276d673f6ee0b149ee2f83ef82f62902 Mon Sep 17 00:00:00 2001 From: divverent Date: Sat, 1 Nov 2008 17:44:32 +0000 Subject: [PATCH] csqc packet log code (for entityframedatabase 5 only, i.e. DP5 and higher) git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@8544 d7cf8633-e32d-0410-b094-e92efae38249 --- protocol.c | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++--- protocol.h | 4 ++ server.h | 14 ++++ sv_main.c | 14 +++- sv_user.c | 3 + 5 files changed, 223 insertions(+), 12 deletions(-) diff --git a/protocol.c b/protocol.c index fa17b60e..d4931797 100644 --- a/protocol.c +++ b/protocol.c @@ -1,4 +1,3 @@ - #include "quakedef.h" #define ENTITYSIZEPROFILING_START(msg, num) \ @@ -264,12 +263,174 @@ void EntityFrameQuake_ISeeDeadEntities(void) } } -// FIXME FIXME FIXME: at this time the CSQC entity writing does not store -// packet logs and thus if an update is lost it is never repeated, this makes -// csqc entities useless at the moment. +// NOTE: this only works with DP5 protocol and upwards. For lower protocols +// (including QUAKE), no packet loss handling for CSQC is done, which makes +// CSQC basically useless. +// Always use the DP5 protocol, or a higher one, when using CSQC entities. +static void EntityFrameCSQC_LostAllFrames(client_t *client) +{ + // mark ALL csqc entities as requiring a FULL resend! + // I know this is a bad workaround, but better than nothing. + int i, n; + prvm_eval_t *val; + prvm_edict_t *ed; + + if(prog->fieldoffsets.SendEntity < 0 || prog->fieldoffsets.Version < 0) + return; + + n = client->csqcnumedicts; + for(i = 0; i < n; ++i) + { + if(client->csqcentityglobalhistory[i]) + { + ed = prog->edicts + i; + val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.SendEntity); + if (val->function) + client->csqcentitysendflags[i] |= 0xFFFFFF; // FULL RESEND + else // if it was ever sent to that client as a CSQC entity + { + client->csqcentityscope[i] = 1; // REMOVE + client->csqcentitysendflags[i] |= 0xFFFFFF; + } + } + } +} +void EntityFrameCSQC_LostFrame(client_t *client, int framenum) +{ + // marks a frame as lost + int i, j, n; + qboolean valid; + int ringfirst, ringlast; + int recoversendflags[MAX_EDICTS]; + csqcentityframedb_t *d; + + n = client->csqcnumedicts; + + // is our frame out of history? + ringfirst = client->csqcentityframehistory_next; // oldest entry + ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry + + valid = false; + + for(j = 0; j < NUM_CSQCENTITYDB_FRAMES; ++j) + { + d = &client->csqcentityframehistory[(ringfirst + j) % NUM_CSQCENTITYDB_FRAMES]; + if(d->framenum < 0) + continue; + if(d->framenum == framenum) + break; + else if(d->framenum < framenum) + valid = true; + } + if(j == NUM_CSQCENTITYDB_FRAMES) + { + if(valid) // got beaten, i.e. there is a frame < framenum + { + // a non-csqc frame got lost... great + return; + } + else + { + // a too old frame got lost... sorry, cannot handle this + Con_DPrintf("CSQC entity DB: lost a frame too early to do any handling (resending ALL)...\n"); + Con_DPrintf("Lost frame = %d\n", framenum); + Con_DPrintf("Entity DB = %d to %d\n", client->csqcentityframehistory[ringfirst].framenum, client->csqcentityframehistory[ringlast].framenum); + EntityFrameCSQC_LostAllFrames(client); + } + return; + } + + // so j is the frame that got lost + // ringlast is the frame that we have to go to + ringfirst = (ringfirst + j) % NUM_CSQCENTITYDB_FRAMES; + if(ringlast < ringfirst) + ringlast += NUM_CSQCENTITYDB_FRAMES; + + memset(recoversendflags, 0, sizeof(recoversendflags)); + + for(j = ringfirst; j <= ringlast; ++j) + { + d = &client->csqcentityframehistory[j % NUM_CSQCENTITYDB_FRAMES]; + if(d->framenum < 0) + { + // deleted frame + } + else if(d->framenum < framenum) + { + // a frame in the past... should never happen + Con_Printf("CSQC entity DB encountered a frame from the past when recovering from PL...?\n"); + } + else if(d->framenum == framenum) + { + // handling the actually lost frame now + for(i = 0; i < d->num; ++i) + { + int sf = d->sendflags[i]; + int ent = d->entno[i]; + if(sf < 0) // remove + recoversendflags[ent] |= -1; // all bits, including sign + else if(sf > 0) + recoversendflags[ent] |= sf; + } + } + else + { + // handling the frames that followed it now + for(i = 0; i < d->num; ++i) + { + int sf = d->sendflags[i]; + int ent = d->entno[i]; + if(sf < 0) // remove + { + recoversendflags[ent] = 0; // no need to update, we got a more recent remove (and will fix it THEN) + break; // no flags left to remove... + } + else if(sf > 0) + recoversendflags[ent] &= ~sf; // no need to update these bits, we already got them later + } + } + } + + for(i = 0; i < client->csqcnumedicts; ++i) + { + if(recoversendflags[i] < 0) + { + // a remove got lost, then either send a remove or - if it was + // recreated later - a FULL update to make totally sure + client->csqcentityscope[i] = 1; + client->csqcentitysendflags[i] = 0xFFFFFF; + } + else + client->csqcentitysendflags[i] |= recoversendflags[i]; + } +} +static int EntityFrameCSQC_AllocFrame(client_t *client, int framenum) +{ + int ringfirst = client->csqcentityframehistory_next; // oldest entry + client->csqcentityframehistory_next += 1; + client->csqcentityframehistory_next %= NUM_CSQCENTITYDB_FRAMES; + client->csqcentityframehistory[ringfirst].framenum = framenum; + client->csqcentityframehistory[ringfirst].num = 0; + return ringfirst; +} +static void EntityFrameCSQC_DeallocFrame(client_t *client, int framenum) +{ + int ringfirst = client->csqcentityframehistory_next; // oldest entry + int ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry + if(framenum == client->csqcentityframehistory[ringlast].framenum) + { + client->csqcentityframehistory[ringlast].framenum = -1; + client->csqcentityframehistory[ringlast].num = 0; + client->csqcentityframehistory_next = ringlast; + } + else + Con_Printf("Trying to dealloc the wrong entity frame\n"); +} //[515]: we use only one array per-client for SendEntity feature -void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numstates, const entity_state_t *states) +// TODO: add some handling for entity send priorities, to better deal with huge +// amounts of csqc networked entities +void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numstates, const entity_state_t *states, int framenum) { int num, number, end, sendflags; qboolean sectionstarted = false; @@ -277,6 +438,10 @@ void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numstates, con prvm_edict_t *ed; prvm_eval_t *val; client_t *client = svs.clients + sv.writeentitiestoclient_clientnumber; + int dbframe = EntityFrameCSQC_AllocFrame(client, framenum); + csqcentityframedb_t *db = &client->csqcentityframehistory[dbframe]; + + maxsize -= 24; // always fit in an empty svc_entities message (for packet loss detection!) // if this server progs is not CSQC-aware, return early if(prog->fieldoffsets.SendEntity < 0 || prog->fieldoffsets.Version < 0) @@ -349,6 +514,8 @@ void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numstates, con sendflags = client->csqcentitysendflags[number]; if (!sendflags) continue; + if(db->num >= NUM_CSQCENTITIES_PER_FRAME) + break; ed = prog->edicts + number; // entity scope is either update (2) or remove (1) if (client->csqcentityscope[number] == 1) @@ -366,6 +533,10 @@ void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numstates, con MSG_WriteShort(msg, (unsigned short)number | 0x8000); client->csqcentityscope[number] = 0; client->csqcentitysendflags[number] = 0xFFFFFF; // resend completely if it becomes active again + db->entno[db->num] = number; + db->sendflags[db->num] = -1; + db->num += 1; + client->csqcentityglobalhistory[number] = 1; ENTITYSIZEPROFILING_END(msg, number); } if (msg->cursize + 17 >= maxsize) @@ -395,12 +566,16 @@ void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numstates, con { // an update has been successfully written client->csqcentitysendflags[number] = 0; + db->entno[db->num] = number; + db->sendflags[db->num] = sendflags; + db->num += 1; + client->csqcentityglobalhistory[number] = 1; // and take note that we have begun the svc_csqcentities // section of the packet sectionstarted = 1; + ENTITYSIZEPROFILING_END(msg, number); if (msg->cursize + 17 >= maxsize) break; - ENTITYSIZEPROFILING_END(msg, number); continue; } } @@ -417,6 +592,11 @@ void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numstates, con // write index 0 to end the update (0 is never used by real entities) MSG_WriteShort(msg, 0); } + + if(db->num == 0) + // if no single ent got added, remove the frame from the DB again, to allow + // for a larger history + EntityFrameCSQC_DeallocFrame(client, framenum); } void Protocol_UpdateClientStats(const int *stats) @@ -1752,7 +1932,7 @@ void EntityFrame5_FreeDatabase(entityframe5_database_t *d) Mem_Free(d); } -void EntityFrame5_ExpandEdicts(entityframe5_database_t *d, int newmax) +static void EntityFrame5_ExpandEdicts(entityframe5_database_t *d, int newmax) { if (d->maxedicts < newmax) { @@ -1783,7 +1963,7 @@ void EntityFrame5_ExpandEdicts(entityframe5_database_t *d, int newmax) } } -int EntityState5_Priority(entityframe5_database_t *d, int stateindex) +static int EntityState5_Priority(entityframe5_database_t *d, int stateindex) { int limit, priority; entity_state_t *s; @@ -1972,7 +2152,7 @@ void EntityState5_WriteUpdate(int number, const entity_state_t *s, int changedbi ENTITYSIZEPROFILING_END(msg, s->number); } -void EntityState5_ReadUpdate(entity_state_t *s, int number) +static void EntityState5_ReadUpdate(entity_state_t *s, int number) { int bits; bits = MSG_ReadByte(); @@ -2138,7 +2318,7 @@ void EntityState5_ReadUpdate(entity_state_t *s, int number) } } -int EntityState5_DeltaBits(const entity_state_t *o, const entity_state_t *n) +static int EntityState5_DeltaBits(const entity_state_t *o, const entity_state_t *n) { unsigned int bits = 0; if (n->active) diff --git a/protocol.h b/protocol.h index 8cb06002..ec4a291a 100644 --- a/protocol.h +++ b/protocol.h @@ -967,5 +967,9 @@ void EntityFrameQW_FreeDatabase(entityframeqw_database_t *d); void EntityStateQW_ReadPlayerUpdate(void); void EntityFrameQW_CL_ReadFrame(qboolean delta); +struct client_s; +void EntityFrameCSQC_LostFrame(struct client_s *client, int framenum); +void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numstates, const entity_state_t *states, int framenum); + #endif diff --git a/server.h b/server.h index 15b7c3d0..fe11c266 100644 --- a/server.h +++ b/server.h @@ -152,6 +152,15 @@ typedef struct server_s unsigned char csqcentityversion[MAX_EDICTS]; // legacy } server_t; +#define NUM_CSQCENTITIES_PER_FRAME 1024 +typedef struct csqcentityframedb_s +{ + int framenum; + int num; + unsigned short entno[NUM_CSQCENTITIES_PER_FRAME]; + int sendflags[NUM_CSQCENTITIES_PER_FRAME]; +} csqcentityframedb_t; + // if defined this does ping smoothing, otherwise it does not //#define NUM_PING_TIMES 16 @@ -225,6 +234,11 @@ typedef struct client_s unsigned char csqcentityscope[MAX_EDICTS]; unsigned int csqcentitysendflags[MAX_EDICTS]; +#define NUM_CSQCENTITYDB_FRAMES 64 + unsigned char csqcentityglobalhistory[MAX_EDICTS]; // set to 1 if the entity was ever csqc networked to the client, and never reset back to 0 + csqcentityframedb_t csqcentityframehistory[NUM_CSQCENTITYDB_FRAMES]; + int csqcentityframehistory_next; + // prevent animated names float nametime; diff --git a/sv_main.c b/sv_main.c index bc6de193..8caa1788 100644 --- a/sv_main.c +++ b/sv_main.c @@ -31,7 +31,6 @@ static void SV_VM_Setup(); void VM_CustomStats_Clear (void); void VM_SV_UpdateCustomStats (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats); -void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numstates, const entity_state_t *states); cvar_t coop = {0, "coop","0", "coop mode, 0 = no coop, 1 = coop mode, multiple players playing through the singleplayer game (coop mode also shuts off deathmatch)"}; cvar_t deathmatch = {0, "deathmatch","0", "deathmatch mode, values depend on mod but typically 0 = no deathmatch, 1 = normal deathmatch with respawning weapons, 2 = weapons stay (players can only pick up new weapons)"}; @@ -738,7 +737,15 @@ void SV_SendServerinfo (client_t *client) { client->csqcentityscope[i] = 0; client->csqcentitysendflags[i] = 0xFFFFFF; + client->csqcentityglobalhistory[i] = 0; } + for (i = 0;i < NUM_CSQCENTITYDB_FRAMES;i++) + { + client->csqcentityframehistory[i].num = 0; + client->csqcentityframehistory[i].framenum = -1; + } + client->csqcnumedicts = 0; + client->csqcentityframehistory_next = 0; SZ_Clear (&client->netconnection->message); MSG_WriteByte (&client->netconnection->message, svc_print); @@ -1447,7 +1454,10 @@ void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t * if (sv_cullentities_stats.integer) Con_Printf("client \"%s\" entities: %d total, %d visible, %d culled by: %d pvs %d trace\n", client->name, sv.writeentitiestoclient_stats_totalentities, sv.writeentitiestoclient_stats_visibleentities, sv.writeentitiestoclient_stats_culled_pvs + sv.writeentitiestoclient_stats_culled_trace, sv.writeentitiestoclient_stats_culled_pvs, sv.writeentitiestoclient_stats_culled_trace); - EntityFrameCSQC_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates); + if(client->entitydatabase5) + EntityFrameCSQC_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates, client->entitydatabase5->latestframenum + 1); + else + EntityFrameCSQC_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates, 0); if (client->entitydatabase5) EntityFrame5_WriteFrame(msg, maxsize, client->entitydatabase5, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1, client->movesequence); diff --git a/sv_user.c b/sv_user.c index 91f505c1..78ff1f9f 100644 --- a/sv_user.c +++ b/sv_user.c @@ -706,7 +706,10 @@ void SV_ApplyClientMove (void) void SV_FrameLost(int framenum) { if (host_client->entitydatabase5) + { EntityFrame5_LostFrame(host_client->entitydatabase5, framenum); + EntityFrameCSQC_LostFrame(host_client, framenum); + } } void SV_FrameAck(int framenum) -- 2.39.2