reverted the cleanup of entity state building because it was sapping
authorhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Sun, 21 Oct 2007 11:21:47 +0000 (11:21 +0000)
committerhavoc <havoc@d7cf8633-e32d-0410-b094-e92efae38249>
Sun, 21 Oct 2007 11:21:47 +0000 (11:21 +0000)
massive amounts of performance even with only 16 players, now it once
again builds them only once per frame and filters them per client

git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@7648 d7cf8633-e32d-0410-b094-e92efae38249

protocol.c
protocol.h
server.h
sv_main.c

index bc21122..71cfa15 100644 (file)
@@ -9,11 +9,17 @@ entity_state_t defaultstate =
        {0,0,0},//float netcenter[3]; // ! for network prioritization, this is the center of the bounding box (which may differ from the origin)
        {0,0,0},//float origin[3];
        {0,0,0},//float angles[3];
-       0,//int number; // entity number this state is for
        0,//int effects;
+       0,//unsigned int customizeentityforclient; // !
+       0,//unsigned short number; // entity number this state is for
        0,//unsigned short modelindex;
        0,//unsigned short frame;
        0,//unsigned short tagentity;
+       0,//unsigned short specialvisibilityradius; // ! larger if it has effects/light
+       0,//unsigned short viewmodelforclient; // !
+       0,//unsigned short exteriormodelforclient; // ! not shown if first person viewing from this entity, shown in all other cases
+       0,//unsigned short nodrawtoclient; // !
+       0,//unsigned short drawonlytoclient; // !
        {0,0,0,0},//unsigned short light[4]; // color*256 (0.00 to 255.996), and radius*1
        0,//unsigned char active; // true if a valid state
        0,//unsigned char lightstyle;
@@ -29,7 +35,7 @@ entity_state_t defaultstate =
        0,//unsigned char tagindex;
        {32, 32, 32},//unsigned char colormod[3];
        // padding to a multiple of 8 bytes (to align the double time)
-       0//unsigned char unused; // !
+       {0,0,0,0,0}//unsigned char unused[5]; // !
 };
 
 // LordHavoc: I own protocol ranges 96, 97, 3500-3599
index ddc9542..a0f340b 100644 (file)
@@ -337,7 +337,7 @@ void Protocol_Names(char *buffer, size_t buffersize);
 #define RENDER_NOSELFSHADOW 262144 // render lighting on this entity before its own shadow is added to the scene
 // (note: all RENDER_NOSELFSHADOW entities are grouped together and rendered in a batch before their shadows are rendered, so they can not shadow eachother either)
 
-// this is 80 bytes
+// this is 96 bytes
 typedef struct entity_state_s
 {
        // ! means this is not sent to client
@@ -346,10 +346,16 @@ typedef struct entity_state_s
        float origin[3];
        float angles[3];
        int effects;
+       unsigned int customizeentityforclient; // !
        unsigned short number; // entity number this state is for
        unsigned short modelindex;
        unsigned short frame;
        unsigned short tagentity;
+       unsigned short specialvisibilityradius; // ! larger if it has effects/light
+       unsigned short viewmodelforclient; // !
+       unsigned short exteriormodelforclient; // ! not shown if first person viewing from this entity, shown in all other cases
+       unsigned short nodrawtoclient; // !
+       unsigned short drawonlytoclient; // !
        unsigned short light[4]; // color*256 (0.00 to 255.996), and radius*1
        unsigned char active; // true if a valid state
        unsigned char lightstyle;
@@ -365,7 +371,7 @@ typedef struct entity_state_s
        unsigned char tagindex;
        unsigned char colormod[3];
        // padding to a multiple of 8 bytes (to align the double time)
-       unsigned char unused;
+       unsigned char unused[5];
 }
 entity_state_t;
 
index 16ddb66..3e9b711 100644 (file)
--- a/server.h
+++ b/server.h
@@ -139,6 +139,14 @@ typedef struct server_s
        int writeentitiestoclient_pvsbytes;
        unsigned char writeentitiestoclient_pvs[MAX_MAP_LEAFS/8];
        entity_state_t writeentitiestoclient_sendstates[MAX_EDICTS];
+
+       int numsendentities;
+       entity_state_t sendentities[MAX_EDICTS];
+       entity_state_t *sendentitiesindex[MAX_EDICTS];
+
+       int sententitiesmark;
+       int sententities[MAX_EDICTS];
+       int sententitiesconsideration[MAX_EDICTS];
 } server_t;
 
 // if defined this does ping smoothing, otherwise it does not
index b2afd26..4f0dce0 100644 (file)
--- a/sv_main.c
+++ b/sv_main.c
@@ -916,29 +916,16 @@ crosses a waterline.
 =============================================================================
 */
 
-static qboolean SV_BuildEntityState (entity_state_t *cs, prvm_edict_t *ent, int enumber)
+static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int enumber)
 {
        int i;
-       unsigned int tagentity;
        unsigned int modelindex, effects, flags, glowsize, lightstyle, lightpflags, light[4], specialvisibilityradius;
        unsigned int customizeentityforclient;
        float f;
-       vec3_t cullmins, cullmaxs, netcenter;
+       vec3_t cullmins, cullmaxs;
        model_t *model;
        prvm_eval_t *val;
 
-       // see if the customizeentityforclient extension is used by this entity
-       customizeentityforclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.customizeentityforclient)->function;
-       if (customizeentityforclient)
-       {
-               prog->globals.server->self = enumber;
-               prog->globals.server->other = sv.writeentitiestoclient_cliententitynumber;
-               PRVM_ExecuteProgram(customizeentityforclient, "customizeentityforclient: NULL function");
-               // customizeentityforclient can return false to reject the entity
-               if (!PRVM_G_FLOAT(OFS_RETURN))
-                       return false;
-       }
-
        // this 2 billion unit check is actually to detect NAN origins
        // (we really don't want to send those)
        if (!(VectorLength2(ent->fields.server->origin) < 2000000000.0*2000000000.0))
@@ -1020,73 +1007,137 @@ static qboolean SV_BuildEntityState (entity_state_t *cs, prvm_edict_t *ent, int
                        specialvisibilityradius = max(specialvisibilityradius, 100);
        }
 
-       // don't send uninteresting entities
-       if (enumber != sv.writeentitiestoclient_cliententitynumber)
+       // early culling checks
+       // (final culling is done by SV_MarkWriteEntityStateToClient)
+       customizeentityforclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.customizeentityforclient)->function;
+       if (!customizeentityforclient && enumber > svs.maxclients && (!modelindex && !specialvisibilityradius))
+               return false;
+
+       *cs = defaultstate;
+       cs->active = true;
+       cs->number = enumber;
+       VectorCopy(ent->fields.server->origin, cs->origin);
+       VectorCopy(ent->fields.server->angles, cs->angles);
+       cs->flags = flags;
+       cs->effects = effects;
+       cs->colormap = (unsigned)ent->fields.server->colormap;
+       cs->modelindex = modelindex;
+       cs->skin = (unsigned)ent->fields.server->skin;
+       cs->frame = (unsigned)ent->fields.server->frame;
+       cs->viewmodelforclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.viewmodelforclient)->edict;
+       cs->exteriormodelforclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.exteriormodeltoclient)->edict;
+       cs->nodrawtoclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.nodrawtoclient)->edict;
+       cs->drawonlytoclient = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.drawonlytoclient)->edict;
+       cs->customizeentityforclient = customizeentityforclient;
+       cs->tagentity = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_entity)->edict;
+       cs->tagindex = (unsigned char)PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_index)->_float;
+       cs->glowsize = glowsize;
+
+       // don't need to init cs->colormod because the defaultstate did that for us
+       //cs->colormod[0] = cs->colormod[1] = cs->colormod[2] = 32;
+       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.colormod);
+       if (val->vector[0] || val->vector[1] || val->vector[2])
        {
-               if (!modelindex && !specialvisibilityradius)
-                       return false;
-               if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.nodrawtoclient)->edict == sv.writeentitiestoclient_cliententitynumber)
-                       return false;
-               if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.drawonlytoclient)->edict && PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.drawonlytoclient)->edict != sv.writeentitiestoclient_cliententitynumber)
-                       return false;
-               if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.viewmodelforclient)->edict && PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.viewmodelforclient)->edict != sv.writeentitiestoclient_cliententitynumber)
-                       return false;
-               if (flags & RENDER_VIEWMODEL && PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.viewmodelforclient)->edict != sv.writeentitiestoclient_cliententitynumber)
-                       return false;
-               if (effects & EF_NODRAW)
-                       return false;
+               i = (int)(val->vector[0] * 32.0f);cs->colormod[0] = bound(0, i, 255);
+               i = (int)(val->vector[1] * 32.0f);cs->colormod[1] = bound(0, i, 255);
+               i = (int)(val->vector[2] * 32.0f);cs->colormod[2] = bound(0, i, 255);
        }
 
-       // don't send child if parent was rejected
-       // FIXME: it would be better to force the parent to send...
-       tagentity = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_entity)->edict;
-       if (tagentity && !SV_BuildEntityState(NULL, PRVM_EDICT_NUM(tagentity), tagentity))
-               return false;
+       cs->modelindex = modelindex;
+
+       cs->alpha = 255;
+       f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.alpha)->_float * 255.0f);
+       if (f)
+       {
+               i = (int)f;
+               cs->alpha = (unsigned char)bound(0, i, 255);
+       }
+       // halflife
+       f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.renderamt)->_float);
+       if (f)
+       {
+               i = (int)f;
+               cs->alpha = (unsigned char)bound(0, i, 255);
+       }
+
+       cs->scale = 16;
+       f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.scale)->_float * 16.0f);
+       if (f)
+       {
+               i = (int)f;
+               cs->scale = (unsigned char)bound(0, i, 255);
+       }
+
+       cs->glowcolor = 254;
+       f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_color)->_float);
+       if (f)
+               cs->glowcolor = (int)f;
+
+       if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.fullbright)->_float)
+               cs->effects |= EF_FULLBRIGHT;
+
+       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.modelflags);
+       if (val && val->_float)
+               cs->effects |= ((unsigned int)val->_float & 0xff) << 24;
+
+       if (ent->fields.server->movetype == MOVETYPE_STEP)
+               cs->flags |= RENDER_STEP;
+       if (cs->number != sv.writeentitiestoclient_cliententitynumber && (cs->effects & EF_LOWPRECISION) && cs->origin[0] >= -32768 && cs->origin[1] >= -32768 && cs->origin[2] >= -32768 && cs->origin[0] <= 32767 && cs->origin[1] <= 32767 && cs->origin[2] <= 32767)
+               cs->flags |= RENDER_LOWPRECISION;
+       if (ent->fields.server->colormap >= 1024)
+               cs->flags |= RENDER_COLORMAPPED;
+       if (cs->viewmodelforclient)
+               cs->flags |= RENDER_VIEWMODEL; // show relative to the view
+
+       cs->light[0] = light[0];
+       cs->light[1] = light[1];
+       cs->light[2] = light[2];
+       cs->light[3] = light[3];
+       cs->lightstyle = lightstyle;
+       cs->lightpflags = lightpflags;
+
+       cs->specialvisibilityradius = specialvisibilityradius;
 
        // calculate the visible box of this entity (don't use the physics box
        // as that is often smaller than a model, and would not count
        // specialvisibilityradius)
        if ((model = sv.models[modelindex]))
        {
-               float scale = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_color)->_float;
-               if (scale)
-                       scale *= (1.0f / 16.0f);
-               else
-                       scale = 1;
-               if (ent->fields.server->angles[0] || ent->fields.server->angles[2]) // pitch and roll
+               float scale = cs->scale * (1.0f / 16.0f);
+               if (cs->angles[0] || cs->angles[2]) // pitch and roll
                {
-                       VectorMA(ent->fields.server->origin, scale, model->rotatedmins, cullmins);
-                       VectorMA(ent->fields.server->origin, scale, model->rotatedmaxs, cullmaxs);
+                       VectorMA(cs->origin, scale, model->rotatedmins, cullmins);
+                       VectorMA(cs->origin, scale, model->rotatedmaxs, cullmaxs);
                }
-               else if (ent->fields.server->angles[1])
+               else if (cs->angles[1])
                {
-                       VectorMA(ent->fields.server->origin, scale, model->yawmins, cullmins);
-                       VectorMA(ent->fields.server->origin, scale, model->yawmaxs, cullmaxs);
+                       VectorMA(cs->origin, scale, model->yawmins, cullmins);
+                       VectorMA(cs->origin, scale, model->yawmaxs, cullmaxs);
                }
                else
                {
-                       VectorMA(ent->fields.server->origin, scale, model->normalmins, cullmins);
-                       VectorMA(ent->fields.server->origin, scale, model->normalmaxs, cullmaxs);
+                       VectorMA(cs->origin, scale, model->normalmins, cullmins);
+                       VectorMA(cs->origin, scale, model->normalmaxs, cullmaxs);
                }
        }
        else
        {
                // if there is no model (or it could not be loaded), use the physics box
-               VectorAdd(ent->fields.server->origin, ent->fields.server->mins, cullmins);
-               VectorAdd(ent->fields.server->origin, ent->fields.server->maxs, cullmaxs);
+               VectorAdd(cs->origin, ent->fields.server->mins, cullmins);
+               VectorAdd(cs->origin, ent->fields.server->maxs, cullmaxs);
        }
        if (specialvisibilityradius)
        {
-               cullmins[0] = min(cullmins[0], ent->fields.server->origin[0] - specialvisibilityradius);
-               cullmins[1] = min(cullmins[1], ent->fields.server->origin[1] - specialvisibilityradius);
-               cullmins[2] = min(cullmins[2], ent->fields.server->origin[2] - specialvisibilityradius);
-               cullmaxs[0] = max(cullmaxs[0], ent->fields.server->origin[0] + specialvisibilityradius);
-               cullmaxs[1] = max(cullmaxs[1], ent->fields.server->origin[1] + specialvisibilityradius);
-               cullmaxs[2] = max(cullmaxs[2], ent->fields.server->origin[2] + specialvisibilityradius);
+               cullmins[0] = min(cullmins[0], cs->origin[0] - specialvisibilityradius);
+               cullmins[1] = min(cullmins[1], cs->origin[1] - specialvisibilityradius);
+               cullmins[2] = min(cullmins[2], cs->origin[2] - specialvisibilityradius);
+               cullmaxs[0] = max(cullmaxs[0], cs->origin[0] + specialvisibilityradius);
+               cullmaxs[1] = max(cullmaxs[1], cs->origin[1] + specialvisibilityradius);
+               cullmaxs[2] = max(cullmaxs[2], cs->origin[2] + specialvisibilityradius);
        }
 
        // calculate center of bbox for network prioritization purposes
-       VectorMAM(0.5f, cullmins, 0.5f, cullmaxs, netcenter);
+       VectorMAM(0.5f, cullmins, 0.5f, cullmaxs, cs->netcenter);
 
        // if culling box has moved, update pvs cluster links
        if (!VectorCompare(cullmins, ent->priv.server->cullmins) || !VectorCompare(cullmaxs, ent->priv.server->cullmaxs))
@@ -1106,36 +1157,107 @@ static qboolean SV_BuildEntityState (entity_state_t *cs, prvm_edict_t *ent, int
                }
        }
 
-       if (enumber != sv.writeentitiestoclient_cliententitynumber && !(effects & EF_NODEPTHTEST) && !(flags & RENDER_VIEWMODEL) && !tagentity)
+       return true;
+}
+
+void SV_PrepareEntitiesForSending(void)
+{
+       int e;
+       prvm_edict_t *ent;
+       // send all entities that touch the pvs
+       sv.numsendentities = 0;
+       sv.sendentitiesindex[0] = NULL;
+       memset(sv.sendentitiesindex, 0, prog->num_edicts * sizeof(*sv.sendentitiesindex));
+       for (e = 1, ent = PRVM_NEXT_EDICT(prog->edicts);e < prog->num_edicts;e++, ent = PRVM_NEXT_EDICT(ent))
+       {
+               if (!ent->priv.server->free && SV_PrepareEntityForSending(ent, sv.sendentities + sv.numsendentities, e))
+               {
+                       sv.sendentitiesindex[e] = sv.sendentities + sv.numsendentities;
+                       sv.numsendentities++;
+               }
+       }
+}
+
+void SV_MarkWriteEntityStateToClient(entity_state_t *s)
+{
+       int isbmodel;
+       model_t *model;
+       prvm_edict_t *ed;
+       if (sv.sententitiesconsideration[s->number] == sv.sententitiesmark)
+               return;
+       sv.sententitiesconsideration[s->number] = sv.sententitiesmark;
+       sv.writeentitiestoclient_stats_totalentities++;
+
+       if (s->customizeentityforclient)
+       {
+               prog->globals.server->self = s->number;
+               prog->globals.server->other = sv.writeentitiestoclient_cliententitynumber;
+               PRVM_ExecuteProgram(s->customizeentityforclient, "customizeentityforclient: NULL function");
+               if(!PRVM_G_FLOAT(OFS_RETURN) || !SV_PrepareEntityForSending(PRVM_EDICT_NUM(s->number), s, s->number))
+                       return;
+       }
+
+       // never reject player
+       if (s->number != sv.writeentitiestoclient_cliententitynumber)
        {
-               qboolean isbmodel = (model = sv.models[modelindex]) != NULL && model->name[0] == '*';
-               if (!isbmodel || !sv_cullentities_nevercullbmodels.integer)
+               // check various rejection conditions
+               if (s->nodrawtoclient == sv.writeentitiestoclient_cliententitynumber)
+                       return;
+               if (s->drawonlytoclient && s->drawonlytoclient != sv.writeentitiestoclient_cliententitynumber)
+                       return;
+               if (s->effects & EF_NODRAW)
+                       return;
+               // LordHavoc: only send entities with a model or important effects
+               if (!s->modelindex && s->specialvisibilityradius == 0)
+                       return;
+
+               isbmodel = (model = sv.models[s->modelindex]) != NULL && model->name[0] == '*';
+               // viewmodels don't have visibility checking
+               if (s->viewmodelforclient)
                {
-                       // cull based on visibility
+                       if (s->viewmodelforclient != sv.writeentitiestoclient_cliententitynumber)
+                               return;
+               }
+               else if (s->tagentity)
+               {
+                       // tag attached entities simply check their parent
+                       if (!sv.sendentitiesindex[s->tagentity])
+                               return;
+                       SV_MarkWriteEntityStateToClient(sv.sendentitiesindex[s->tagentity]);
+                       if (sv.sententities[s->tagentity] != sv.sententitiesmark)
+                               return;
+               }
+               // always send world submodels in newer protocols because they don't
+               // generate much traffic (in old protocols they hog bandwidth)
+               // but only if sv_cullentities_nevercullbmodels is off
+               else if (!(s->effects & EF_NODEPTHTEST) && (!isbmodel || !sv_cullentities_nevercullbmodels.integer || sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE))
+               {
+                       // entity has survived every check so far, check if visible
+                       ed = PRVM_EDICT_NUM(s->number);
 
                        // if not touching a visible leaf
                        if (sv_cullentities_pvs.integer && sv.writeentitiestoclient_pvsbytes)
                        {
-                               if (ent->priv.server->pvs_numclusters < 0)
+                               if (ed->priv.server->pvs_numclusters < 0)
                                {
                                        // entity too big for clusters list
-                                       if (sv.worldmodel && sv.worldmodel->brush.BoxTouchingPVS && !sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, sv.writeentitiestoclient_pvs, cullmins, cullmaxs))
+                                       if (sv.worldmodel && sv.worldmodel->brush.BoxTouchingPVS && !sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, sv.writeentitiestoclient_pvs, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
                                        {
                                                sv.writeentitiestoclient_stats_culled_pvs++;
-                                               return false;
+                                               return;
                                        }
                                }
                                else
                                {
                                        int i;
                                        // check cached clusters list
-                                       for (i = 0;i < ent->priv.server->pvs_numclusters;i++)
-                                               if (CHECKPVSBIT(sv.writeentitiestoclient_pvs, ent->priv.server->pvs_clusterlist[i]))
+                                       for (i = 0;i < ed->priv.server->pvs_numclusters;i++)
+                                               if (CHECKPVSBIT(sv.writeentitiestoclient_pvs, ed->priv.server->pvs_clusterlist[i]))
                                                        break;
-                                       if (i == ent->priv.server->pvs_numclusters)
+                                       if (i == ed->priv.server->pvs_numclusters)
                                        {
                                                sv.writeentitiestoclient_stats_culled_pvs++;
-                                               return false;
+                                               return;
                                        }
                                }
                        }
@@ -1143,14 +1265,14 @@ static qboolean SV_BuildEntityState (entity_state_t *cs, prvm_edict_t *ent, int
                        // or not seen by random tracelines
                        if (sv_cullentities_trace.integer && !isbmodel)
                        {
-                               int samples = specialvisibilityradius ? sv_cullentities_trace_samples_extra.integer : sv_cullentities_trace_samples.integer;
+                               int samples = s->specialvisibilityradius ? sv_cullentities_trace_samples_extra.integer : sv_cullentities_trace_samples.integer;
                                float enlarge = sv_cullentities_trace_enlarge.value;
 
-                               qboolean visible = true;
+                               qboolean visible = TRUE;
 
                                do
                                {
-                                       if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, sv.writeentitiestoclient_testeye, cullmins, cullmaxs))
+                                       if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, sv.writeentitiestoclient_testeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
                                                break; // directly visible from the server's view
 
                                        if(sv_cullentities_trace_prediction.integer)
@@ -1165,7 +1287,7 @@ static qboolean SV_BuildEntityState (entity_state_t *cs, prvm_edict_t *ent, int
                                                VectorMA(sv.writeentitiestoclient_testeye, predtime, host_client->edict->fields.server->velocity, predeye);
                                                if(sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, sv.writeentitiestoclient_testeye, predeye)) // must be able to go there...
                                                {
-                                                       if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, predeye, cullmins, cullmaxs))
+                                                       if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, predeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
                                                                break; // directly visible from the predicted view
                                                }
                                                else
@@ -1180,107 +1302,27 @@ static qboolean SV_BuildEntityState (entity_state_t *cs, prvm_edict_t *ent, int
                                while(0);
 
                                if(visible)
-                                       svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[enumber] = realtime + sv_cullentities_trace_delay.value;
-                               else if (realtime > svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[enumber])
+                                       svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number] = realtime + sv_cullentities_trace_delay.value;
+                               else if (realtime > svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number])
                                {
                                        sv.writeentitiestoclient_stats_culled_trace++;
-                                       return false;
+                                       return;
                                }
                        }
                }
        }
 
-       // if the caller was just checking...  return true
-       if (!cs)
-               return true;
-
-       *cs = defaultstate;
-       cs->active = true;
-       cs->number = enumber;
-       VectorCopy(netcenter, cs->netcenter);
-       VectorCopy(ent->fields.server->origin, cs->origin);
-       VectorCopy(ent->fields.server->angles, cs->angles);
-       cs->flags = flags;
-       cs->effects = effects;
-       cs->colormap = (unsigned)ent->fields.server->colormap;
-       cs->modelindex = modelindex;
-       cs->skin = (unsigned)ent->fields.server->skin;
-       cs->frame = (unsigned)ent->fields.server->frame;
-       cs->tagentity = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_entity)->edict;
-       cs->tagindex = (unsigned char)PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_index)->_float;
-       cs->glowsize = glowsize;
-
-       // don't need to init cs->colormod because the defaultstate did that for us
-       //cs->colormod[0] = cs->colormod[1] = cs->colormod[2] = 32;
-       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.colormod);
-       if (val->vector[0] || val->vector[1] || val->vector[2])
-       {
-               i = (int)(val->vector[0] * 32.0f);cs->colormod[0] = bound(0, i, 255);
-               i = (int)(val->vector[1] * 32.0f);cs->colormod[1] = bound(0, i, 255);
-               i = (int)(val->vector[2] * 32.0f);cs->colormod[2] = bound(0, i, 255);
-       }
-
-       cs->modelindex = modelindex;
-
-       cs->alpha = 255;
-       f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.alpha)->_float * 255.0f);
-       if (f)
-       {
-               i = (int)f;
-               cs->alpha = (unsigned char)bound(0, i, 255);
-       }
-       // halflife
-       f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.renderamt)->_float);
-       if (f)
-       {
-               i = (int)f;
-               cs->alpha = (unsigned char)bound(0, i, 255);
-       }
-
-       cs->scale = 16;
-       f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.scale)->_float * 16.0f);
-       if (f)
-       {
-               i = (int)f;
-               cs->scale = (unsigned char)bound(0, i, 255);
-       }
-
-       cs->glowcolor = 254;
-       f = (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_color)->_float);
-       if (f)
-               cs->glowcolor = (int)f;
-
-       if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.fullbright)->_float)
-               cs->effects |= EF_FULLBRIGHT;
-
-       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.modelflags);
-       if (val && val->_float)
-               cs->effects |= ((unsigned int)val->_float & 0xff) << 24;
-
-       if (ent->fields.server->movetype == MOVETYPE_STEP)
-               cs->flags |= RENDER_STEP;
-       if (cs->number != sv.writeentitiestoclient_cliententitynumber && (cs->effects & EF_LOWPRECISION) && cs->origin[0] >= -32768 && cs->origin[1] >= -32768 && cs->origin[2] >= -32768 && cs->origin[0] <= 32767 && cs->origin[1] <= 32767 && cs->origin[2] <= 32767)
-               cs->flags |= RENDER_LOWPRECISION;
-       if (ent->fields.server->colormap >= 1024)
-               cs->flags |= RENDER_COLORMAPPED;
-       if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_trail)->edict && PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_trail)->edict == sv.writeentitiestoclient_cliententitynumber)
-               cs->flags |= RENDER_EXTERIORMODEL;
-
-       cs->light[0] = light[0];
-       cs->light[1] = light[1];
-       cs->light[2] = light[2];
-       cs->light[3] = light[3];
-       cs->lightstyle = lightstyle;
-       cs->lightpflags = lightpflags;
-
-       return true;
+       // this just marks it for sending
+       // FIXME: it would be more efficient to send here, but the entity
+       // compressor isn't that flexible
+       sv.writeentitiestoclient_stats_visibleentities++;
+       sv.sententities[s->number] = sv.sententitiesmark;
 }
 
-static void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg)
+void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg)
 {
-       int i;
-       int numsendstates;
-       prvm_edict_t *ent;
+       int i, numsendstates;
+       entity_state_t *s;
 
        // if there isn't enough space to accomplish anything, skip it
        if (msg->cursize + 25 > msg->maxsize)
@@ -1303,11 +1345,22 @@ static void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, size
 
        sv.writeentitiestoclient_cliententitynumber = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes
 
-       // send all entities that touch the pvs
+       sv.sententitiesmark++;
+
+       for (i = 0;i < sv.numsendentities;i++)
+               SV_MarkWriteEntityStateToClient(sv.sendentities + i);
+
        numsendstates = 0;
-       for (i = 1, ent = PRVM_NEXT_EDICT(prog->edicts);i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent))
-               if (!ent->priv.server->free && SV_BuildEntityState(sv.writeentitiestoclient_sendstates + numsendstates, ent, i))
-                       numsendstates++;
+       for (i = 0;i < sv.numsendentities;i++)
+       {
+               if (sv.sententities[sv.sendentities[i].number] == sv.sententitiesmark)
+               {
+                       s = &sv.writeentitiestoclient_sendstates[numsendstates++];
+                       *s = sv.sendentities[i];
+                       if (s->exteriormodelforclient && s->exteriormodelforclient == sv.writeentitiestoclient_cliententitynumber)
+                               s->flags |= RENDER_EXTERIORMODEL;
+               }
+       }
 
        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);
@@ -1904,7 +1957,7 @@ SV_SendClientMessages
 */
 void SV_SendClientMessages (void)
 {
-       int i;
+       int i, prepared = false;
 
        if (sv.protocol == PROTOCOL_QUAKEWORLD)
                Sys_Error("SV_SendClientMessages: no quakeworld support\n");
@@ -1928,6 +1981,12 @@ void SV_SendClientMessages (void)
                        continue;
                }
 
+               if (!prepared)
+               {
+                       prepared = true;
+                       // only prepare entities once per frame
+                       SV_PrepareEntitiesForSending();
+               }
                SV_SendClientDatagram (host_client);
        }