DarkPlaces
Game engine based on the Quake 1 engine by id Software, developed by LadyHavoc
 
sv_ents_csqc.c
Go to the documentation of this file.
1#include "quakedef.h"
2#include "protocol.h"
3
5
6// NOTE: this only works with DP5 protocol and upwards. For lower protocols
7// (including QUAKE), no packet loss handling for CSQC is done, which makes
8// CSQC basically useless.
9// Always use the DP5 protocol, or a higher one, when using CSQC entities.
11{
12 prvm_prog_t *prog = SVVM_prog;
13 // mark ALL csqc entities as requiring a FULL resend!
14 // I know this is a bad workaround, but better than nothing.
15 int i, n;
16 prvm_edict_t *ed;
17
18 n = client->csqcnumedicts;
19 for(i = 0; i < n; ++i)
20 {
22 {
23 ed = prog->edicts + i;
24 client->csqcentitysendflags[i] |= 0xFFFFFF; // FULL RESEND. We can't clear SCOPE_ASSUMED_EXISTING yet as this would cancel removes on a rejected send attempt.
25 if (!PRVM_serveredictfunction(ed, SendEntity)) // If it was ever sent to that client as a CSQC entity...
26 client->csqcentityscope[i] |= SCOPE_ASSUMED_EXISTING; // FORCE REMOVE.
27 }
28 }
29}
30void EntityFrameCSQC_LostFrame(client_t *client, int framenum)
31{
32 // marks a frame as lost
33 int i, j;
34 qbool valid;
35 int ringfirst, ringlast;
36 static int recoversendflags[MAX_EDICTS]; // client only
38
39 if(client->csqcentityframe_lastreset < 0)
40 return;
41 if(framenum < client->csqcentityframe_lastreset)
42 return; // no action required, as we resent that data anyway
43
44 // is our frame out of history?
45 ringfirst = client->csqcentityframehistory_next; // oldest entry
46 ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry
47
48 valid = false;
49
50 for(j = 0; j < NUM_CSQCENTITYDB_FRAMES; ++j)
51 {
52 d = &client->csqcentityframehistory[(ringfirst + j) % NUM_CSQCENTITYDB_FRAMES];
53 if(d->framenum < 0)
54 continue;
55 if(d->framenum == framenum)
56 break;
57 else if(d->framenum < framenum)
58 valid = true;
59 }
61 {
62 if(valid) // got beaten, i.e. there is a frame < framenum
63 {
64 // a non-csqc frame got lost... great
65 return;
66 }
67 else
68 {
69 // a too old frame got lost... sorry, cannot handle this
70 Con_DPrintf("CSQC entity DB: lost a frame too early to do any handling (resending ALL)...\n");
71 Con_DPrintf("Lost frame = %d\n", framenum);
72 Con_DPrintf("Entity DB = %d to %d\n", client->csqcentityframehistory[ringfirst].framenum, client->csqcentityframehistory[ringlast].framenum);
74 client->csqcentityframe_lastreset = -1;
75 }
76 return;
77 }
78
79 // so j is the frame that got lost
80 // ringlast is the frame that we have to go to
81 ringfirst = (ringfirst + j) % NUM_CSQCENTITYDB_FRAMES;
82 if(ringlast < ringfirst)
83 ringlast += NUM_CSQCENTITYDB_FRAMES;
84
85 memset(recoversendflags, 0, sizeof(recoversendflags));
86
87 for(j = ringfirst; j <= ringlast; ++j)
88 {
90 if(d->framenum < 0)
91 {
92 // deleted frame
93 }
94 else if(d->framenum < framenum)
95 {
96 // a frame in the past... should never happen
97 Con_Printf("CSQC entity DB encountered a frame from the past when recovering from PL...?\n");
98 }
99 else if(d->framenum == framenum)
100 {
101 // handling the actually lost frame now
102 for(i = 0; i < d->num; ++i)
103 {
104 int sf = d->sendflags[i];
105 int ent = d->entno[i];
106 if(sf < 0) // remove
107 recoversendflags[ent] |= -1; // all bits, including sign
108 else if(sf > 0)
109 recoversendflags[ent] |= sf;
110 }
111 }
112 else
113 {
114 // handling the frames that followed it now
115 for(i = 0; i < d->num; ++i)
116 {
117 int sf = d->sendflags[i];
118 int ent = d->entno[i];
119 if(sf < 0) // remove
120 {
121 recoversendflags[ent] = 0; // no need to update, we got a more recent remove (and will fix it THEN)
122 break; // no flags left to remove...
123 }
124 else if(sf > 0)
125 recoversendflags[ent] &= ~sf; // no need to update these bits, we already got them later
126 }
127 }
128 }
129
130 for(i = 0; i < client->csqcnumedicts; ++i)
131 {
132 if(recoversendflags[i] < 0)
133 client->csqcentityscope[i] |= SCOPE_ASSUMED_EXISTING; // FORCE REMOVE.
134 else
135 client->csqcentitysendflags[i] |= recoversendflags[i];
136 }
137}
138static int EntityFrameCSQC_AllocFrame(client_t *client, int framenum)
139{
140 int ringfirst = client->csqcentityframehistory_next; // oldest entry
141 client->csqcentityframehistory_next += 1;
143 client->csqcentityframehistory[ringfirst].framenum = framenum;
144 client->csqcentityframehistory[ringfirst].num = 0;
145 return ringfirst;
146}
147static void EntityFrameCSQC_DeallocFrame(client_t *client, int framenum)
148{
149 int ringfirst = client->csqcentityframehistory_next; // oldest entry
150 int ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry
151 if(framenum == client->csqcentityframehistory[ringlast].framenum)
152 {
153 client->csqcentityframehistory[ringlast].framenum = -1;
154 client->csqcentityframehistory[ringlast].num = 0;
155 client->csqcentityframehistory_next = ringlast;
156 }
157 else
158 Con_Printf("Trying to dealloc the wrong entity frame\n");
159}
160
161//[515]: we use only one array per-client for SendEntity feature
162// TODO: add some handling for entity send priorities, to better deal with huge
163// amounts of csqc networked entities
164qbool EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum)
165{
166 prvm_prog_t *prog = SVVM_prog;
167 int num, number, end, sendflags, nonplayer_splitpoint, nonplayer_splitpoint_number, nonplayer_index;
168 qbool sectionstarted = false;
169 const unsigned short *n;
170 prvm_edict_t *ed;
172 int dbframe = EntityFrameCSQC_AllocFrame(client, framenum);
173 csqcentityframedb_t *db = &client->csqcentityframehistory[dbframe];
174
175 if(client->csqcentityframe_lastreset < 0)
176 client->csqcentityframe_lastreset = framenum;
177
178 maxsize -= 24; // always fit in an empty svc_entities message (for packet loss detection!)
179
180 // make sure there is enough room to store the svc_csqcentities byte,
181 // the terminator (0x0000) and at least one entity update
182 if (msg->cursize + 32 >= maxsize)
183 return false;
184
185 if (client->csqcnumedicts < prog->num_edicts)
186 client->csqcnumedicts = prog->num_edicts;
187
188 number = 1;
189 for (num = 0, n = numbers;num < numnumbers;num++, n++)
190 {
191 end = *n;
192 for (;number < end;number++)
193 {
194 client->csqcentityscope[number] &= ~SCOPE_WANTSEND;
195 if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)
196 client->csqcentityscope[number] |= SCOPE_WANTREMOVE;
197 client->csqcentitysendflags[number] = 0xFFFFFF;
198 }
199 ed = prog->edicts + number;
200 client->csqcentityscope[number] &= ~SCOPE_WANTSEND;
201 if (PRVM_serveredictfunction(ed, SendEntity))
202 client->csqcentityscope[number] |= SCOPE_WANTUPDATE;
203 else
204 {
205 if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)
206 client->csqcentityscope[number] |= SCOPE_WANTREMOVE;
207 client->csqcentitysendflags[number] = 0xFFFFFF;
208 }
209 number++;
210 }
211 end = client->csqcnumedicts;
212 for (;number < end;number++)
213 {
214 client->csqcentityscope[number] &= ~SCOPE_WANTSEND;
215 if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)
216 client->csqcentityscope[number] |= SCOPE_WANTREMOVE;
217 client->csqcentitysendflags[number] = 0xFFFFFF;
218 }
219
220 // now try to emit the entity updates
221 end = client->csqcnumedicts;
222 // First send all removals.
223 nonplayer_index = 0;
224 for (number = 1;number < end;number++)
225 {
226 if (!(client->csqcentityscope[number] & SCOPE_WANTSEND))
227 continue;
229 goto outofspace;
230 ed = prog->edicts + number;
231 if (client->csqcentityscope[number] & SCOPE_WANTREMOVE) // Also implies ASSUMED_EXISTING.
232 {
233 // A removal. SendFlags have no power here.
234 // write a remove message
235 // first write the message identifier if needed
236 if(!sectionstarted)
237 {
238 sectionstarted = 1;
240 }
241 // write the remove message
242 {
243 ENTITYSIZEPROFILING_START(msg, number, 0);
244 MSG_WriteShort(msg, (unsigned short)number | 0x8000);
246 client->csqcentitysendflags[number] = 0xFFFFFF; // resend completely if it becomes active again
247 db->entno[db->num] = number;
248 db->sendflags[db->num] = -1;
249 db->num += 1;
250 ENTITYSIZEPROFILING_END(msg, number, 0);
251 }
252 if (msg->cursize + 17 >= maxsize)
253 goto outofspace;
254 }
255 else
256 {
257 // An update.
258 sendflags = client->csqcentitysendflags[number];
259 // Nothing to send? FINE.
260 if (!sendflags)
261 continue;
262 if (number > svs.maxclients)
263 ++nonplayer_index;
264 }
265 }
266
267 // If sv_sendentities_csqc_randomize_order is false, this is always 0.
268 // As such, nonplayer_splitpoint_number will be exactly
269 // svs.maxclients + 1. Thus, the shifting below will be a NOP.
270 //
271 // Otherwise, a random subsection of the non-player entities will be
272 // sent in the first pass, and the rest in the second pass.
273 //
274 // This makes it random which entities will be sent or not in case of
275 // running out of space in the message, guaranteeing that every entity
276 // eventually gets a chance to be sent.
277 //
278 // Note that player entities are never included in this. This is to
279 // ensure they keep having priority over anything else. If even sending
280 // the player entities alone runs out of message space, the experience
281 // will be horrible anyway, not much we can do about it - except maybe
282 // better culling.
283 nonplayer_splitpoint_number = svs.maxclients + 1;
284 if (sv_sendentities_csqc_randomize_order.integer && nonplayer_index > 0)
285 {
286 nonplayer_splitpoint = rand() % nonplayer_index;
287
288 // Convert the split point to an entity number.
289 // This must use the exact same conditions as the above
290 // incrementing of nonplayer_index.
291 nonplayer_index = 0;
292 for (number = 1;number < end;number++)
293 {
294 if (!(client->csqcentityscope[number] & SCOPE_WANTSEND))
295 continue;
297 goto outofspace;
298 ed = prog->edicts + number;
299 if (!(client->csqcentityscope[number] & SCOPE_WANTREMOVE))
300 {
301 // An update.
302 sendflags = client->csqcentitysendflags[number];
303 // Nothing to send? FINE.
304 if (!sendflags)
305 continue;
306 if (number > svs.maxclients)
307 {
308 if (nonplayer_index == nonplayer_splitpoint)
309 {
310 nonplayer_splitpoint_number = number;
311 break;
312 }
313 ++nonplayer_index;
314 }
315 }
316 }
317 }
318
319 for (num = 1;num < end;num++)
320 {
321 // Remap entity numbers as follows:
322 // - 1..maxclients stays as is
323 // - Otherwise, rotate so that maxclients+1 becomes nonplayer_splitpoint_number.
324 number = (num <= svs.maxclients)
325 ? num
326 : (num - (svs.maxclients + 1) + nonplayer_splitpoint_number);
327 if (number >= end)
328 number -= end - (svs.maxclients + 1);
329
330 if (!(client->csqcentityscope[number] & SCOPE_WANTSEND))
331 continue;
333 goto outofspace;
334 ed = prog->edicts + number;
335 if (!(client->csqcentityscope[number] & SCOPE_WANTREMOVE))
336 {
337 // save the cursize value in case we overflow and have to rollback
338 int oldcursize = msg->cursize;
339
340 // An update.
341 sendflags = client->csqcentitysendflags[number];
342 // Nothing to send? FINE.
343 if (!sendflags)
344 continue;
345 // If it's a new entity, always assume sendflags 0xFFFFFF.
346 if (!(client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING))
347 sendflags = 0xFFFFFF;
348
349 // write an update
350 if (PRVM_serveredictfunction(ed, SendEntity))
351 {
352 if(!sectionstarted)
354 {
355 int oldcursize2 = msg->cursize;
356 ENTITYSIZEPROFILING_START(msg, number, sendflags);
357 MSG_WriteShort(msg, number);
358 msg->allowoverflow = true;
360 PRVM_G_FLOAT(OFS_PARM1) = sendflags;
362 prog->ExecuteProgram(prog, PRVM_serveredictfunction(ed, SendEntity), "Null SendEntity\n");
363 msg->allowoverflow = false;
365 {
366 // Send rejected by CSQC. This means we want to remove it.
367 // CSQC requests we remove this one.
368 if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)
369 {
370 msg->cursize = oldcursize2;
371 msg->overflowed = false;
372 MSG_WriteShort(msg, (unsigned short)number | 0x8000);
374 client->csqcentitysendflags[number] = 0;
375 db->entno[db->num] = number;
376 db->sendflags[db->num] = -1;
377 db->num += 1;
378 // and take note that we have begun the svc_csqcentities
379 // section of the packet
380 sectionstarted = 1;
381 ENTITYSIZEPROFILING_END(msg, number, 0);
382 if (msg->cursize + 17 >= maxsize)
383 goto outofspace;
384 }
385 else
386 {
387 // Nothing to do. Just don't do it again.
388 msg->cursize = oldcursize;
389 msg->overflowed = false;
390 client->csqcentityscope[number] &= ~SCOPE_WANTSEND;
391 client->csqcentitysendflags[number] = 0;
392 }
393 continue;
394 }
395 else if(PRVM_G_FLOAT(OFS_RETURN) && msg->cursize + 2 <= maxsize)
396 {
397 // an update has been successfully written
398 client->csqcentitysendflags[number] = 0;
399 db->entno[db->num] = number;
400 db->sendflags[db->num] = sendflags;
401 db->num += 1;
402 client->csqcentityscope[number] &= ~SCOPE_WANTSEND;
404 // and take note that we have begun the svc_csqcentities
405 // section of the packet
406 sectionstarted = 1;
407 ENTITYSIZEPROFILING_END(msg, number, sendflags);
408 if (msg->cursize + 17 >= maxsize)
409 goto outofspace;
410 continue;
411 }
412 }
413 }
414 // self.SendEntity returned false (or does not exist) or the
415 // update was too big for this packet - rollback the buffer to its
416 // state before the writes occurred, we'll try again next frame
417 msg->cursize = oldcursize;
418 msg->overflowed = false;
419 }
420 }
421
422outofspace:
423 if (sectionstarted)
424 {
425 // write index 0 to end the update (0 is never used by real entities)
426 MSG_WriteShort(msg, 0);
427 }
428
429 if(db->num == 0)
430 // if no single ent got added, remove the frame from the DB again, to allow
431 // for a larger history
432 EntityFrameCSQC_DeallocFrame(client, framenum);
433
434 return sectionstarted;
435}
void MSG_WriteShort(sizebuf_t *sb, int c)
Definition com_msg.c:138
void MSG_WriteByte(sizebuf_t *sb, int c)
Definition com_msg.c:130
void Con_DPrintf(const char *fmt,...)
A Con_Printf that only shows up if the "developer" cvar is set.
Definition console.c:1544
void Con_Printf(const char *fmt,...)
Prints to all appropriate console targets.
Definition console.c:1514
entity self
#define n(x, y)
#define OFS_RETURN
Definition pr_comp.h:33
#define OFS_PARM0
Definition pr_comp.h:34
#define OFS_PARM1
Definition pr_comp.h:35
#define PRVM_serverglobaledict(fieldname)
Definition progsvm.h:180
#define PRVM_serveredictfunction(ed, fieldname)
Definition progsvm.h:176
#define PRVM_G_FLOAT(o)
Definition progsvm.h:882
#define SVVM_prog
Definition progsvm.h:766
#define PRVM_G_INT(o)
Definition progsvm.h:883
#define ENTITYSIZEPROFILING_START(msg, num, flags)
Definition protocol.h:39
#define SCOPE_WANTUPDATE
Definition protocol.h:51
#define SCOPE_EXISTED_ONCE
Definition protocol.h:53
#define SCOPE_ASSUMED_EXISTING
Definition protocol.h:54
#define SCOPE_WANTREMOVE
Definition protocol.h:50
#define ENTITYSIZEPROFILING_END(msg, num, flags)
Definition protocol.h:42
#define svc_csqcentities
Definition protocol.h:278
#define SCOPE_WANTSEND
Definition protocol.h:52
int i
#define MAX_EDICTS
max number of objects in game world at once (32768 protocol limit)
Definition qdefs.h:105
bool qbool
Definition qtypes.h:9
server_t sv
local server
Definition sv_main.c:223
#define NUM_CSQCENTITIES_PER_FRAME
Definition server.h:171
server_static_t svs
persistant server info
Definition sv_main.c:224
#define NUM_CSQCENTITYDB_FRAMES
Definition server.h:254
int csqcentityframe_lastreset
Definition server.h:257
csqcentityframedb_t csqcentityframehistory[NUM_CSQCENTITYDB_FRAMES]
Definition server.h:255
unsigned char csqcentityscope[MAX_EDICTS]
Definition server.h:251
int csqcnumedicts
Definition server.h:250
unsigned int csqcentitysendflags[MAX_EDICTS]
Definition server.h:252
int csqcentityframehistory_next
Definition server.h:256
unsigned short entno[NUM_CSQCENTITIES_PER_FRAME]
Definition server.h:176
int sendflags[NUM_CSQCENTITIES_PER_FRAME]
Definition server.h:177
Definition cvar.h:66
int integer
Definition cvar.h:73
int num_edicts
copies of some vars that were former read from sv
Definition progsvm.h:671
prvm_edict_t * edicts
Definition progsvm.h:680
void(* ExecuteProgram)(struct prvm_prog_s *prog, func_t fnum, const char *errormessage)
pointer to one of the *VM_ExecuteProgram functions
Definition progsvm.h:749
struct client_s * clients
client slots
Definition server.h:30
int maxclients
number of svs.clients slots (updated by maxplayers command)
Definition server.h:28
int writeentitiestoclient_cliententitynumber
Definition server.h:150
int writeentitiestoclient_clientnumber
Definition server.h:151
int cursize
Definition common.h:54
qbool overflowed
set to true if the buffer size failed
Definition common.h:51
qbool allowoverflow
if false, do a Sys_Error
Definition common.h:50
static void EntityFrameCSQC_LostAllFrames(client_t *client)
static void EntityFrameCSQC_DeallocFrame(client_t *client, int framenum)
cvar_t sv_sendentities_csqc_randomize_order
Definition sv_main.c:221
static int EntityFrameCSQC_AllocFrame(client_t *client, int framenum)
void EntityFrameCSQC_LostFrame(client_t *client, int framenum)
qbool EntityFrameCSQC_WriteFrame(sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum)