From: havoc Date: Wed, 20 Aug 2008 18:33:42 +0000 (+0000) Subject: redesigned csqc shared entity .Version handling, now internally uses a X-Git-Tag: xonotic-v0.1.0preview~2122 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=commitdiff_plain;h=abc955133d73f40d03447e0243b80e96740c86c9;hp=7f81dc664a496edf46a3cd537f2dc6dcb60ccf4c redesigned csqc shared entity .Version handling, now internally uses a flags value instead, while keeping compatibility with .Version (simply sets flags to 1), each frame the flags value is merged into the per-client entity flags and cleared, the flags value is exposed to qc as .float SendFlags, and is a complete alternative to .Version, allowing multiple updatable properties on an entity to be independently tracked git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@8462 d7cf8633-e32d-0410-b094-e92efae38249 --- diff --git a/progsvm.h b/progsvm.h index 90b5511e..41640fc4 100644 --- a/progsvm.h +++ b/progsvm.h @@ -128,7 +128,8 @@ typedef struct prvm_prog_fieldoffsets_s { // server and client use a lot of similar fields, so this is combined int SendEntity; // ssqc - int Version; // ssqc + int SendFlags; // ssqc + int Version; // ssqc (legacy) int alpha; // ssqc / csqc int ammo_cells1; // ssqc - Dissolution of Eternity mission pack int ammo_lava_nails; // ssqc - Dissolution of Eternity mission pack diff --git a/protocol.c b/protocol.c index 11996d3a..66b4db3a 100644 --- a/protocol.c +++ b/protocol.c @@ -251,139 +251,142 @@ void EntityFrameQuake_ISeeDeadEntities(void) // packet logs and thus if an update is lost it is never repeated, this makes // csqc entities useless at the moment. -void EntityFrameCSQC_WriteState (sizebuf_t *msg, int maxsize, int number, qboolean doupdate, qboolean *sectionstarted) -{ - int version; - prvm_eval_t *val, *val2; - version = 0; - if (doupdate) - { - if (msg->cursize + !*sectionstarted + 2 + 1 + 2 > maxsize) - return; - val2 = PRVM_EDICTFIELDVALUE((&prog->edicts[number]), prog->fieldoffsets.Version); - version = (int)val2->_float; - // LordHavoc: do some validity checks on self.Version - // if self.Version reaches 255, it will soon exceed the byte used to - // store an entity version in the client struct, so we need to reset - // all the version to 1 and force all the existing clients' version of - // it to 255 (which we're not allowing to actually occur) - if (version < 0) - val2->_float = 0; - if (version >= 255) - { - int i; - val2->_float = version = 1; - // since we just reset the Version field to 1, it may accidentally - // end up being equal to an existing client version now or in the - // future, so to fix this situation we have to loop over all - // clients and change their versions for this entity to be -1 - // which never matches, thus causing them to receive the update - // soon, as they should - for (i = 0;i < svs.maxclients;i++) - if (svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number]) - svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number] = 255; - } - } - // if the version already matches, we don't need to do anything as the - // latest version has already been sent. - if (svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number] == version) - return; - if (version) - { - // if there's no SendEntity function, treat it as a remove - val = PRVM_EDICTFIELDVALUE((&prog->edicts[number]), prog->fieldoffsets.SendEntity); - if (val->function) - { - // there is a function to call, save the cursize value incase we - // have to do a rollback due to overflow - int oldcursize = msg->cursize; - if(!*sectionstarted) - MSG_WriteByte(msg, svc_csqcentities); - MSG_WriteShort(msg, number); - msg->allowoverflow = true; - PRVM_G_INT(OFS_PARM0) = sv.writeentitiestoclient_cliententitynumber; - prog->globals.server->self = number; - PRVM_ExecuteProgram(val->function, "Null SendEntity\n"); - msg->allowoverflow = false; - if(PRVM_G_FLOAT(OFS_RETURN)) - { - if (msg->cursize + 2 > maxsize) - { - // if the packet no longer has enough room to write the - // final index code that ends the message, rollback to the - // state before we tried to write anything and then return - msg->cursize = oldcursize; - msg->overflowed = false; - return; - } - // an update has been successfully written, update the version - svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number] = version; - // and take note that we have begun the svc_csqcentities - // section of the packet - *sectionstarted = 1; - return; - } - else - { - // rollback the buffer to its state before the writes - msg->cursize = oldcursize; - msg->overflowed = false; - // if the function returned FALSE, simply write a remove - // this is done by falling through to the remove code below - version = 0; - } - } - } - // write a remove message if needed - // if already removed, do nothing - if (!svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number]) - return; - // if there isn't enough room to write the remove message, just return, as - // it will be handled in a later packet - if (msg->cursize + !*sectionstarted + 2 + 2 > maxsize) - return; - // first write the message identifier if needed - if(!*sectionstarted) - { - *sectionstarted = 1; - MSG_WriteByte(msg, svc_csqcentities); - } - // write the remove message - MSG_WriteShort(msg, (unsigned short)number | 0x8000); - svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number] = 0; -} - //[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) { - int i, num; + int num, number, end, sendflags; qboolean sectionstarted = false; const entity_state_t *n; + prvm_edict_t *ed; + prvm_eval_t *val; + client_t *client = svs.clients + sv.writeentitiestoclient_clientnumber; // if this server progs is not CSQC-aware, return early if(prog->fieldoffsets.SendEntity < 0 || prog->fieldoffsets.Version < 0) return; + // make sure there is enough room to store the svc_csqcentities byte, // the terminator (0x0000) and at least one entity update if (msg->cursize + 32 >= maxsize) return; - num = 1; + if (client->csqcnumedicts < prog->num_edicts) + client->csqcnumedicts = prog->num_edicts; + + number = 1; + for (num = 0, n = states;num < numstates;num++, n++) + { + end = n->number; + for (;number < end;number++) + { + if (client->csqcentityscope[number]) + { + client->csqcentityscope[number] = 1; + client->csqcentitysendflags[number] = 0xFFFFFF; + } + } + ed = prog->edicts + number; + val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.SendEntity); + if (val->function) + client->csqcentityscope[number] = 2; + else if (client->csqcentityscope[number]) + { + client->csqcentityscope[number] = 1; + client->csqcentitysendflags[number] = 0xFFFFFF; + } + number++; + } + end = client->csqcnumedicts; + for (;number < end;number++) + { + if (client->csqcentityscope[number]) + { + client->csqcentityscope[number] = 1; + client->csqcentitysendflags[number] = 0xFFFFFF; + } + } + + /* + // mark all scope entities as remove + for (number = 1;number < client->csqcnumedicts;number++) + if (client->csqcentityscope[number]) + client->csqcentityscope[number] = 1; + // keep visible entities for (i = 0, n = states;i < numstates;i++, n++) { - // all entities between the previous entity state and this one are dead - for (;num < n->number;num++) - if(svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[num]) - EntityFrameCSQC_WriteState(msg, maxsize, num, false, §ionstarted); - // update this entity - EntityFrameCSQC_WriteState(msg, maxsize, num, true, §ionstarted); - // advance to next entity so the next iteration doesn't immediately remove it - num++; + number = n->number; + ed = prog->edicts + number; + val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.SendEntity); + if (val->function) + client->csqcentityscope[number] = 2; + } + */ + + // now try to emit the entity updates + // (FIXME: prioritize by distance?) + end = client->csqcnumedicts; + for (number = 1;number < end;number++) + { + if (!client->csqcentityscope[number]) + continue; + sendflags = client->csqcentitysendflags[number]; + if (!sendflags) + continue; + ed = prog->edicts + number; + // entity scope is either update (2) or remove (1) + if (client->csqcentityscope[number] == 1) + { + // write a remove message + // first write the message identifier if needed + if(!sectionstarted) + { + sectionstarted = 1; + MSG_WriteByte(msg, svc_csqcentities); + } + // write the remove message + MSG_WriteShort(msg, (unsigned short)number | 0x8000); + client->csqcentityscope[number] = 0; + client->csqcentitysendflags[number] = 0; + if (msg->cursize + 17 >= maxsize) + break; + } + else + { + // write an update + // save the cursize value in case we overflow and have to rollback + int oldcursize = msg->cursize; + client->csqcentityscope[number] = 1; + val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.SendEntity); + if (val->function) + { + if(!sectionstarted) + MSG_WriteByte(msg, svc_csqcentities); + MSG_WriteShort(msg, number); + msg->allowoverflow = true; + PRVM_G_INT(OFS_PARM0) = sv.writeentitiestoclient_cliententitynumber; + PRVM_G_FLOAT(OFS_PARM1) = sendflags; + prog->globals.server->self = number; + PRVM_ExecuteProgram(val->function, "Null SendEntity\n"); + msg->allowoverflow = false; + if(PRVM_G_FLOAT(OFS_RETURN) && msg->cursize + 2 <= maxsize) + { + // an update has been successfully written + client->csqcentitysendflags[number] = 0; + // and take note that we have begun the svc_csqcentities + // section of the packet + sectionstarted = 1; + if (msg->cursize + 17 >= maxsize) + break; + continue; + } + } + // self.SendEntity returned false (or does not exist) or the + // update was too big for this packet - rollback the buffer to its + // state before the writes occurred, we'll try again next frame + msg->cursize = oldcursize; + msg->overflowed = false; + } } - // all remaining entities are dead - for (;num < prog->num_edicts;num++) - if(svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[num]) - EntityFrameCSQC_WriteState(msg, maxsize, num, false, §ionstarted); if (sectionstarted) { // write index 0 to end the update (0 is never used by real entities) diff --git a/prvm_edict.c b/prvm_edict.c index 998c2258..65ba909d 100644 --- a/prvm_edict.c +++ b/prvm_edict.c @@ -1369,6 +1369,7 @@ void PRVM_FindOffsets(void) // server and client qc use a lot of similar fields, so this is combined prog->fieldoffsets.SendEntity = PRVM_ED_FindFieldOffset("SendEntity"); + prog->fieldoffsets.SendFlags = PRVM_ED_FindFieldOffset("SendFlags"); prog->fieldoffsets.Version = PRVM_ED_FindFieldOffset("Version"); prog->fieldoffsets.alpha = PRVM_ED_FindFieldOffset("alpha"); prog->fieldoffsets.ammo_cells1 = PRVM_ED_FindFieldOffset("ammo_cells1"); diff --git a/server.h b/server.h index 4105076a..15b7c3d0 100644 --- a/server.h +++ b/server.h @@ -147,6 +147,9 @@ typedef struct server_s int sententitiesmark; int sententities[MAX_EDICTS]; int sententitiesconsideration[MAX_EDICTS]; + + // legacy support for self.Version based csqc entity networking + unsigned char csqcentityversion[MAX_EDICTS]; // legacy } server_t; // if defined this does ping smoothing, otherwise it does not @@ -215,8 +218,12 @@ typedef struct client_s // visibility state float visibletime[MAX_EDICTS]; - // version number of csqc-based entity to decide whether to send it - unsigned char csqcentityversion[MAX_EDICTS]; + // scope is whether an entity is currently being networked to this client + // sendflags is what properties have changed on the entity since the last + // update that was sent + int csqcnumedicts; + unsigned char csqcentityscope[MAX_EDICTS]; + unsigned int csqcentitysendflags[MAX_EDICTS]; // prevent animated names float nametime; diff --git a/sv_main.c b/sv_main.c index fedd7c38..8c8c5cab 100644 --- a/sv_main.c +++ b/sv_main.c @@ -215,6 +215,7 @@ prvm_required_field_t reqfields[] = {ev_entity, "nodrawtoclient"}, {ev_entity, "tag_entity"}, {ev_entity, "viewmodelforclient"}, + {ev_float, "SendFlags"}, {ev_float, "Version"}, {ev_float, "alpha"}, {ev_float, "ammo_cells1"}, @@ -733,7 +734,11 @@ void SV_SendServerinfo (client_t *client) } // reset csqc entity versions - memset(client->csqcentityversion, 0, sizeof(client->csqcentityversion)); + for (i = 0;i < prog->max_edicts;i++) + { + client->csqcentityscope[i] = 0; + client->csqcentitysendflags[i] = 0xFFFFFF; + } SZ_Clear (&client->netconnection->message); MSG_WriteByte (&client->netconnection->message, svc_print); @@ -954,12 +959,14 @@ crosses a waterline. static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int enumber) { int i; + unsigned int sendflags; + unsigned int version; unsigned int modelindex, effects, flags, glowsize, lightstyle, lightpflags, light[4], specialvisibilityradius; unsigned int customizeentityforclient; float f; vec3_t cullmins, cullmaxs; dp_model_t *model; - prvm_eval_t *val; + prvm_eval_t *val, *val2; // this 2 billion unit check is actually to detect NAN origins // (we really don't want to send those) @@ -1192,6 +1199,30 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c } } + // we need to do some csqc entity upkeep here + // get self.SendFlags and clear them + // (to let the QC know that they've been read) + val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.SendEntity); + if (val->function) + { + val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.SendFlags); + sendflags = (unsigned int)val->_float; + val->_float = 0; + // legacy self.Version system + val2 = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.Version); + if (val2->_float) + { + version = (unsigned int)val2->_float; + if (sv.csqcentityversion[enumber] != version) + sendflags = 0xFFFFFF; + sv.csqcentityversion[enumber] = version; + } + // move sendflags into the per-client sendflags + if (sendflags) + for (i = 0;i < svs.maxclients;i++) + svs.clients[i].csqcentitysendflags[enumber] |= sendflags; + } + return true; } @@ -2850,6 +2881,9 @@ static void SV_VM_CB_InitEdict(prvm_edict_t *e) static void SV_VM_CB_FreeEdict(prvm_edict_t *ed) { + int i; + int e; + World_UnlinkEdict(ed); // unlink from world bsp ed->fields.server->model = 0; @@ -2862,6 +2896,16 @@ static void SV_VM_CB_FreeEdict(prvm_edict_t *ed) VectorClear(ed->fields.server->angles); ed->fields.server->nextthink = -1; ed->fields.server->solid = 0; + + // make sure csqc networking is aware of the removed entity + e = PRVM_NUM_FOR_EDICT(ed); + sv.csqcentityversion[e] = 0; + for (i = 0;i < svs.maxclients;i++) + { + if (svs.clients[i].csqcentityscope[e]) + svs.clients[i].csqcentityscope[e] = 1; // removed, awaiting send + svs.clients[i].csqcentitysendflags[e] = 0xFFFFFF; + } } static void SV_VM_CB_CountEdicts(void)