-
Notifications
You must be signed in to change notification settings - Fork 44
/
Copy pathsv_ents_csqc.c
435 lines (408 loc) · 14.2 KB
/
sv_ents_csqc.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
#include "quakedef.h"
#include "protocol.h"
extern cvar_t sv_sendentities_csqc_randomize_order;
// 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)
{
prvm_prog_t *prog = SVVM_prog;
// mark ALL csqc entities as requiring a FULL resend!
// I know this is a bad workaround, but better than nothing.
int i, n;
prvm_edict_t *ed;
n = client->csqcnumedicts;
for(i = 0; i < n; ++i)
{
if(client->csqcentityscope[i] & SCOPE_EXISTED_ONCE)
{
ed = prog->edicts + i;
client->csqcentitysendflags[i] |= 0xFFFFFF; // FULL RESEND. We can't clear SCOPE_ASSUMED_EXISTING yet as this would cancel removes on a rejected send attempt.
if (!PRVM_serveredictfunction(ed, SendEntity)) // If it was ever sent to that client as a CSQC entity...
client->csqcentityscope[i] |= SCOPE_ASSUMED_EXISTING; // FORCE REMOVE.
}
}
}
void EntityFrameCSQC_LostFrame(client_t *client, int framenum)
{
// marks a frame as lost
int i, j;
qbool valid;
int ringfirst, ringlast;
static int recoversendflags[MAX_EDICTS]; // client only
csqcentityframedb_t *d;
if(client->csqcentityframe_lastreset < 0)
return;
if(framenum < client->csqcentityframe_lastreset)
return; // no action required, as we resent that data anyway
// 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);
client->csqcentityframe_lastreset = -1;
}
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)
client->csqcentityscope[i] |= SCOPE_ASSUMED_EXISTING; // FORCE REMOVE.
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
// TODO: add some handling for entity send priorities, to better deal with huge
// amounts of csqc networked entities
qbool EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum)
{
prvm_prog_t *prog = SVVM_prog;
int num, number, end, sendflags, nonplayer_splitpoint, nonplayer_splitpoint_number, nonplayer_index;
qbool sectionstarted = false;
const unsigned short *n;
prvm_edict_t *ed;
client_t *client = svs.clients + sv.writeentitiestoclient_clientnumber;
int dbframe = EntityFrameCSQC_AllocFrame(client, framenum);
csqcentityframedb_t *db = &client->csqcentityframehistory[dbframe];
if(client->csqcentityframe_lastreset < 0)
client->csqcentityframe_lastreset = framenum;
maxsize -= 24; // always fit in an empty svc_entities message (for packet loss detection!)
// 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 false;
if (client->csqcnumedicts < prog->num_edicts)
client->csqcnumedicts = prog->num_edicts;
number = 1;
for (num = 0, n = numbers;num < numnumbers;num++, n++)
{
end = *n;
for (;number < end;number++)
{
client->csqcentityscope[number] &= ~SCOPE_WANTSEND;
if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)
client->csqcentityscope[number] |= SCOPE_WANTREMOVE;
client->csqcentitysendflags[number] = 0xFFFFFF;
}
ed = prog->edicts + number;
client->csqcentityscope[number] &= ~SCOPE_WANTSEND;
if (PRVM_serveredictfunction(ed, SendEntity))
client->csqcentityscope[number] |= SCOPE_WANTUPDATE;
else
{
if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)
client->csqcentityscope[number] |= SCOPE_WANTREMOVE;
client->csqcentitysendflags[number] = 0xFFFFFF;
}
number++;
}
end = client->csqcnumedicts;
for (;number < end;number++)
{
client->csqcentityscope[number] &= ~SCOPE_WANTSEND;
if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)
client->csqcentityscope[number] |= SCOPE_WANTREMOVE;
client->csqcentitysendflags[number] = 0xFFFFFF;
}
// now try to emit the entity updates
end = client->csqcnumedicts;
// First send all removals.
nonplayer_index = 0;
for (number = 1;number < end;number++)
{
if (!(client->csqcentityscope[number] & SCOPE_WANTSEND))
continue;
if(db->num >= NUM_CSQCENTITIES_PER_FRAME)
goto outofspace;
ed = prog->edicts + number;
if (client->csqcentityscope[number] & SCOPE_WANTREMOVE) // Also implies ASSUMED_EXISTING.
{
// A removal. SendFlags have no power here.
// write a remove message
// first write the message identifier if needed
if(!sectionstarted)
{
sectionstarted = 1;
MSG_WriteByte(msg, svc_csqcentities);
}
// write the remove message
{
ENTITYSIZEPROFILING_START(msg, number, 0);
MSG_WriteShort(msg, (unsigned short)number | 0x8000);
client->csqcentityscope[number] &= ~(SCOPE_WANTSEND | SCOPE_ASSUMED_EXISTING);
client->csqcentitysendflags[number] = 0xFFFFFF; // resend completely if it becomes active again
db->entno[db->num] = number;
db->sendflags[db->num] = -1;
db->num += 1;
ENTITYSIZEPROFILING_END(msg, number, 0);
}
if (msg->cursize + 17 >= maxsize)
goto outofspace;
}
else
{
// An update.
sendflags = client->csqcentitysendflags[number];
// Nothing to send? FINE.
if (!sendflags)
continue;
if (number > svs.maxclients)
++nonplayer_index;
}
}
// If sv_sendentities_csqc_randomize_order is false, this is always 0.
// As such, nonplayer_splitpoint_number will be exactly
// svs.maxclients + 1. Thus, the shifting below will be a NOP.
//
// Otherwise, a random subsection of the non-player entities will be
// sent in the first pass, and the rest in the second pass.
//
// This makes it random which entities will be sent or not in case of
// running out of space in the message, guaranteeing that every entity
// eventually gets a chance to be sent.
//
// Note that player entities are never included in this. This is to
// ensure they keep having priority over anything else. If even sending
// the player entities alone runs out of message space, the experience
// will be horrible anyway, not much we can do about it - except maybe
// better culling.
nonplayer_splitpoint_number = svs.maxclients + 1;
if (sv_sendentities_csqc_randomize_order.integer && nonplayer_index > 0)
{
nonplayer_splitpoint = rand() % nonplayer_index;
// Convert the split point to an entity number.
// This must use the exact same conditions as the above
// incrementing of nonplayer_index.
nonplayer_index = 0;
for (number = 1;number < end;number++)
{
if (!(client->csqcentityscope[number] & SCOPE_WANTSEND))
continue;
if(db->num >= NUM_CSQCENTITIES_PER_FRAME)
goto outofspace;
ed = prog->edicts + number;
if (!(client->csqcentityscope[number] & SCOPE_WANTREMOVE))
{
// An update.
sendflags = client->csqcentitysendflags[number];
// Nothing to send? FINE.
if (!sendflags)
continue;
if (number > svs.maxclients)
{
if (nonplayer_index == nonplayer_splitpoint)
{
nonplayer_splitpoint_number = number;
break;
}
++nonplayer_index;
}
}
}
}
for (num = 1;num < end;num++)
{
// Remap entity numbers as follows:
// - 1..maxclients stays as is
// - Otherwise, rotate so that maxclients+1 becomes nonplayer_splitpoint_number.
number = (num <= svs.maxclients)
? num
: (num - (svs.maxclients + 1) + nonplayer_splitpoint_number);
if (number >= end)
number -= end - (svs.maxclients + 1);
if (!(client->csqcentityscope[number] & SCOPE_WANTSEND))
continue;
if(db->num >= NUM_CSQCENTITIES_PER_FRAME)
goto outofspace;
ed = prog->edicts + number;
if (!(client->csqcentityscope[number] & SCOPE_WANTREMOVE))
{
// save the cursize value in case we overflow and have to rollback
int oldcursize = msg->cursize;
// An update.
sendflags = client->csqcentitysendflags[number];
// Nothing to send? FINE.
if (!sendflags)
continue;
// If it's a new entity, always assume sendflags 0xFFFFFF.
if (!(client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING))
sendflags = 0xFFFFFF;
// write an update
if (PRVM_serveredictfunction(ed, SendEntity))
{
if(!sectionstarted)
MSG_WriteByte(msg, svc_csqcentities);
{
int oldcursize2 = msg->cursize;
ENTITYSIZEPROFILING_START(msg, number, sendflags);
MSG_WriteShort(msg, number);
msg->allowoverflow = true;
PRVM_G_INT(OFS_PARM0) = sv.writeentitiestoclient_cliententitynumber;
PRVM_G_FLOAT(OFS_PARM1) = sendflags;
PRVM_serverglobaledict(self) = number;
prog->ExecuteProgram(prog, PRVM_serveredictfunction(ed, SendEntity), "Null SendEntity\n");
msg->allowoverflow = false;
if(!PRVM_G_FLOAT(OFS_RETURN))
{
// Send rejected by CSQC. This means we want to remove it.
// CSQC requests we remove this one.
if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)
{
msg->cursize = oldcursize2;
msg->overflowed = false;
MSG_WriteShort(msg, (unsigned short)number | 0x8000);
client->csqcentityscope[number] &= ~(SCOPE_WANTSEND | SCOPE_ASSUMED_EXISTING);
client->csqcentitysendflags[number] = 0;
db->entno[db->num] = number;
db->sendflags[db->num] = -1;
db->num += 1;
// and take note that we have begun the svc_csqcentities
// section of the packet
sectionstarted = 1;
ENTITYSIZEPROFILING_END(msg, number, 0);
if (msg->cursize + 17 >= maxsize)
goto outofspace;
}
else
{
// Nothing to do. Just don't do it again.
msg->cursize = oldcursize;
msg->overflowed = false;
client->csqcentityscope[number] &= ~SCOPE_WANTSEND;
client->csqcentitysendflags[number] = 0;
}
continue;
}
else if(PRVM_G_FLOAT(OFS_RETURN) && msg->cursize + 2 <= maxsize)
{
// an update has been successfully written
client->csqcentitysendflags[number] = 0;
db->entno[db->num] = number;
db->sendflags[db->num] = sendflags;
db->num += 1;
client->csqcentityscope[number] &= ~SCOPE_WANTSEND;
client->csqcentityscope[number] |= SCOPE_EXISTED_ONCE | SCOPE_ASSUMED_EXISTING;
// and take note that we have begun the svc_csqcentities
// section of the packet
sectionstarted = 1;
ENTITYSIZEPROFILING_END(msg, number, sendflags);
if (msg->cursize + 17 >= maxsize)
goto outofspace;
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;
}
}
outofspace:
if (sectionstarted)
{
// 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);
return sectionstarted;
}