Merge pull request #1203 from BjossiAlfreds/silent-precache

Attempts to speed up level load times
This commit is contained in:
Yamagi 2025-05-11 16:07:53 +02:00 committed by GitHub
commit b64207a97e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 551 additions and 194 deletions

View file

@ -13,6 +13,7 @@ have been renamed. The prefixes are:
* `ogg_`: Ogg/Vorbis music playback.
* `r_`: Common to all renderers.
* `s_`: Sound system.
* `sv_`: Server
* `sw_`: Software renderer.
* `vid_`: Video backend.
@ -57,6 +58,40 @@ it's `+set busywait 0` (setting the `busywait` cvar) and `-portable`
time for the next frame. The latter is more CPU friendly but can be
rather inaccurate, especially on Windows. Use with care.
* **sv_optimize_sp_loadtime**<br />
**sv_optimize_mp_loadtime**: These cvars enable/disable optimizations
that speed up level load times (or more accurately, client connection).
sp stands for singleplayer and mp for multiplayer, respectively.
The sp version is enabled by default (value 7) while multiplayer is 0.
The cvar value is a bitmask for 3 optimization features:
* **1: Message utilization**<br />
When the server sends the client configstrings and other data
during the connection process, the message buffer is only used
around 50-60%. When this flag is enabled, the message is used
much better, dramatically reducing the amount of messages needed
to deliver all the data to the client.
* **2: Server send rate**<br />
By default, the server sends messages to clients once every
0.1 seconds, roughly. This slows down sending data to clients,
especially in singleplayer. This is normal for active clients,
but for connecting/inactive clients, this delay is unnecessary.
When this flag is set, the server will send messages to
inactive clients ~8x more frequently.
* **4: Reconnection**<br />
When the server changes maps, like on level transitions,
the server first sends a "changing" command to all clients,
and then a "reconnect" command. The delay between these commands
can be quite long, ~1 second. This flag will avoid this delay when
set, by sending the two commands within the same message.
Simply add these flag values together to get the cvar value you want.
For example, sendrate + reconnect = 2 + 4 = 6.
Set to 7 for all optimizations, or 0 to disable them entirely.
* **cl_maxfps**: The approximate framerate for client/server ("packet")
frames if *cl_async* is `1`. If set to `-1` (the default), the engine
will choose a packet framerate appropriate for the render framerate.

View file

@ -222,9 +222,7 @@ CL_Record_f(void)
}
MSG_WriteByte(&buf, svc_configstring);
MSG_WriteShort(&buf, i);
MSG_WriteString(&buf, cl.configstrings[i]);
MSG_WriteConfigString(&buf, i, cl.configstrings[i]);
}
}

View file

@ -444,6 +444,13 @@ CL_Changing_f(void)
SCR_BeginLoadingPlaque();
cls.state = ca_connected; /* not active anymore, but not disconnected */
/* reset this to 0 just in case it didn't get a chance to settle normally
this became a problem with the faster client connection changes
but is a good idea to do this regardless
*/
anykeydown = 0;
Com_Printf("\nChanging map...\n");
#ifdef USE_CURL

View file

@ -240,7 +240,7 @@ CL_PrepRefresh(void)
{
char mapname[MAX_QPATH];
int i;
char name[MAX_QPATH];
char *name;
float rotate;
vec3_t axis;
@ -260,33 +260,25 @@ CL_PrepRefresh(void)
Com_Printf("Map: %s\r", mapname);
SCR_UpdateScreen();
R_BeginRegistration (mapname);
Com_Printf(" \r");
/* precache status bar pics */
Com_Printf("pics\r");
SCR_UpdateScreen();
SCR_TouchPics();
Com_Printf(" \r");
CL_RegisterTEntModels();
num_cl_weaponmodels = 1;
strcpy(cl_weaponmodels[0], "weapon.md2");
Com_Printf("models\r");
SCR_UpdateScreen();
for (i = 1; i < MAX_MODELS && cl.configstrings[CS_MODELS + i][0]; i++)
{
strcpy(name, cl.configstrings[CS_MODELS + i]);
name[37] = 0; /* never go beyond one line */
name = cl.configstrings[CS_MODELS + i];
if (name[0] != '*')
{
Com_Printf("%s\r", name);
}
SCR_UpdateScreen();
IN_Update();
if (name[0] == '#')
if (*name == '#')
{
/* special player weapon model */
if (num_cl_weaponmodels < MAX_CLIENTWEAPONMODELS)
@ -294,6 +286,7 @@ CL_PrepRefresh(void)
Q_strlcpy(cl_weaponmodels[num_cl_weaponmodels],
cl.configstrings[CS_MODELS + i] + 1,
sizeof(cl_weaponmodels[num_cl_weaponmodels]));
num_cl_weaponmodels++;
}
}
@ -301,20 +294,9 @@ CL_PrepRefresh(void)
{
cl.model_draw[i] = R_RegisterModel(cl.configstrings[CS_MODELS + i]);
if (name[0] == '*')
{
cl.model_clip[i] = CM_InlineModel(cl.configstrings[CS_MODELS + i]);
}
else
{
cl.model_clip[i] = NULL;
}
}
if (name[0] != '*')
{
Com_Printf(" \r");
cl.model_clip[i] = (*name == '*') ?
CM_InlineModel(name) :
NULL;
}
}
@ -324,23 +306,17 @@ CL_PrepRefresh(void)
for (i = 1; i < MAX_IMAGES && cl.configstrings[CS_IMAGES + i][0]; i++)
{
cl.image_precache[i] = Draw_FindPic(cl.configstrings[CS_IMAGES + i]);
IN_Update();
}
Com_Printf(" \r");
Com_Printf("clients\r");
SCR_UpdateScreen();
for (i = 0; i < MAX_CLIENTS; i++)
{
if (!cl.configstrings[CS_PLAYERSKINS + i][0])
if (cl.configstrings[CS_PLAYERSKINS + i][0])
{
continue;
CL_ParseClientinfo(i);
}
Com_Printf("client %i\r", i);
SCR_UpdateScreen();
IN_Update();
CL_ParseClientinfo(i);
Com_Printf(" \r");
}
CL_LoadClientinfo(&cl.baseclientinfo, "unnamed\\male/grunt");
@ -351,6 +327,7 @@ CL_PrepRefresh(void)
rotate = (float)strtod(cl.configstrings[CS_SKYROTATE], (char **)NULL);
sscanf(cl.configstrings[CS_SKYAXIS], "%f %f %f", &axis[0], &axis[1], &axis[2]);
R_SetSky(cl.configstrings[CS_SKY], rotate, axis);
Com_Printf(" \r");
/* the renderer can now free unneeded stuff */

View file

@ -106,6 +106,10 @@ void SZ_Print(sizebuf_t *buf, char *data); /* strcats onto the sizebuf */
struct usercmd_s;
struct entity_state_s;
size_t MSG_ConfigString_Size(const char *s);
size_t MSG_DeltaEntity_Size(const entity_state_t *from, const entity_state_t *to,
qboolean force, qboolean newentity);
void MSG_WriteChar(sizebuf_t *sb, int c);
void MSG_WriteByte(sizebuf_t *sb, int c);
void MSG_WriteShort(sizebuf_t *sb, int c);
@ -116,8 +120,11 @@ void MSG_WriteCoord(sizebuf_t *sb, float f);
void MSG_WritePos(sizebuf_t *sb, vec3_t pos);
void MSG_WriteAngle(sizebuf_t *sb, float f);
void MSG_WriteAngle16(sizebuf_t *sb, float f);
void MSG_WriteConfigString(sizebuf_t *sb, short index, const char *s);
void MSG_WriteDeltaUsercmd(sizebuf_t *sb, struct usercmd_s *from,
struct usercmd_s *cmd);
int DeltaEntityBits(const struct entity_state_s *from,
const struct entity_state_s *to, qboolean newentity);
void MSG_WriteDeltaEntity(struct entity_state_s *from,
struct entity_state_s *to, sizebuf_t *msg,
qboolean force, qboolean newentity);

View file

@ -191,6 +191,170 @@ vec3_t bytedirs[NUMVERTEXNORMALS] = {
{-0.688191, -0.587785, -0.425325}
};
size_t
MSG_ConfigString_Size(const char *s)
{
return strlen(s) + 4; /* string length + null char + message type + index */
}
size_t
MSG_DeltaEntity_Size(const entity_state_t *from, const entity_state_t *to,
qboolean force, qboolean newentity)
{
size_t sz;
int bits = DeltaEntityBits(from, to, newentity);
if (!bits && !force)
{
return 0;
}
sz = 1;
if (bits & 0xff000000)
{
sz += 3;
}
else if (bits & 0x00ff0000)
{
sz += 2;
}
else if (bits & 0x0000ff00)
{
sz++;
}
if (bits & U_NUMBER16)
{
sz += 2;
}
else
{
sz++;
}
if (bits & U_MODEL)
{
sz++;
}
if (bits & U_MODEL2)
{
sz++;
}
if (bits & U_MODEL3)
{
sz++;
}
if (bits & U_MODEL4)
{
sz++;
}
if (bits & U_FRAME8)
{
sz++;
}
if (bits & U_FRAME16)
{
sz += 2;
}
if ((bits & U_SKIN8) && (bits & U_SKIN16)) /*used for laser colors */
{
sz += 4;
}
else if (bits & U_SKIN8)
{
sz++;
}
else if (bits & U_SKIN16)
{
sz += 2;
}
if ((bits & (U_EFFECTS8 | U_EFFECTS16)) == (U_EFFECTS8 | U_EFFECTS16))
{
sz += 4;
}
else if (bits & U_EFFECTS8)
{
sz++;
}
else if (bits & U_EFFECTS16)
{
sz += 2;
}
if ((bits & (U_RENDERFX8 | U_RENDERFX16)) == (U_RENDERFX8 | U_RENDERFX16))
{
sz += 4;
}
else if (bits & U_RENDERFX8)
{
sz++;
}
else if (bits & U_RENDERFX16)
{
sz += 2;
}
if (bits & U_ORIGIN1)
{
sz += 2;
}
if (bits & U_ORIGIN2)
{
sz += 2;
}
if (bits & U_ORIGIN3)
{
sz += 2;
}
if (bits & U_ANGLE1)
{
sz++;
}
if (bits & U_ANGLE2)
{
sz++;
}
if (bits & U_ANGLE3)
{
sz++;
}
if (bits & U_OLDORIGIN)
{
sz += 6;
}
if (bits & U_SOUND)
{
sz++;
}
if (bits & U_EVENT)
{
sz++;
}
if (bits & U_SOLID)
{
sz += 2;
}
return sz;
}
void
MSG_WriteChar(sizebuf_t *sb, int c)
{
@ -286,6 +450,13 @@ MSG_WriteAngle16(sizebuf_t *sb, float f)
MSG_WriteShort(sb, ANGLE2SHORT(f));
}
void
MSG_WriteConfigString(sizebuf_t *buf, short index, const char *s)
{
MSG_WriteShort(buf, index);
MSG_WriteString(buf, s);
}
void
MSG_WriteDeltaUsercmd(sizebuf_t *buf, usercmd_t *from, usercmd_t *cmd)
{
@ -428,28 +599,12 @@ MSG_ReadDir(sizebuf_t *sb, vec3_t dir)
* Writes part of a packetentities message.
* Can delta from either a baseline or a previous packet_entity
*/
void
MSG_WriteDeltaEntity(entity_state_t *from,
entity_state_t *to,
sizebuf_t *msg,
qboolean force,
int
DeltaEntityBits(const entity_state_t *from,
const entity_state_t *to,
qboolean newentity)
{
int bits;
if (!to->number)
{
Com_Error(ERR_FATAL, "Unset entity number");
}
if (to->number >= MAX_EDICTS)
{
Com_Error(ERR_DROP, "%s: bad entity %d >= %d\n",
__func__, to->number, MAX_EDICTS);
}
/* send an update */
bits = 0;
int bits = 0;
if (to->number >= 256)
{
@ -594,12 +749,6 @@ MSG_WriteDeltaEntity(entity_state_t *from,
bits |= U_OLDORIGIN;
}
/* write the message */
if (!bits && !force)
{
return; /* nothing to send! */
}
if (bits & 0xff000000)
{
bits |= U_MOREBITS3 | U_MOREBITS2 | U_MOREBITS1;
@ -615,6 +764,37 @@ MSG_WriteDeltaEntity(entity_state_t *from,
bits |= U_MOREBITS1;
}
return bits;
}
void
MSG_WriteDeltaEntity(entity_state_t *from,
entity_state_t *to,
sizebuf_t *msg,
qboolean force,
qboolean newentity)
{
int bits;
if (!to->number)
{
Com_Error(ERR_FATAL, "Unset entity number");
}
if (to->number >= MAX_EDICTS)
{
Com_Error(ERR_DROP, "%s: bad entity %d >= %d\n",
__func__, to->number, MAX_EDICTS);
}
/* send an update */
bits = DeltaEntityBits(from, to, newentity);
if (!bits && !force)
{
return;
}
MSG_WriteByte(msg, bits & 255);
if (bits & 0xff000000)

View file

@ -176,8 +176,14 @@ typedef struct
FILE *demofile;
sizebuf_t demo_multicast;
byte demo_multicast_buf[MAX_MSGLEN];
int gamemode;
} server_static_t;
#define GAMEMODE_SP 1
#define GAMEMODE_COOP 2
#define GAMEMODE_DM 3
extern netadr_t net_from;
extern sizebuf_t net_message;
@ -186,6 +192,9 @@ extern netadr_t master_adr[MAX_MASTERS]; /* address of the master server */
extern server_static_t svs; /* persistant server info */
extern server_t sv; /* local server */
extern cvar_t *sv_optimize_sp_loadtime;
extern cvar_t *sv_optimize_mp_loadtime;
extern cvar_t *sv_paused;
extern cvar_t *maxclients;
extern cvar_t *sv_noreload; /* don't reload level state when reentering */
@ -228,6 +237,7 @@ void SV_FlushRedirect(int sv_redirected, char *outputbuf);
void SV_DemoCompleted(void);
void SV_SendClientMessages(void);
void SV_SendPrepClientMessages(void);
void SV_Multicast(vec3_t origin, multicast_t to);
void SV_StartSound(vec3_t origin, edict_t *entity, int channel,
@ -283,5 +293,13 @@ int SV_PointContents(vec3_t p);
trace_t SV_Trace(vec3_t start, vec3_t mins, vec3_t maxs,
vec3_t end, edict_t *passedict, int contentmask);
/* loadtime optimizations */
#define OPTIMIZE_MSGUTIL 1
#define OPTIMIZE_SENDRATE 2
#define OPTIMIZE_RECONNECT 4
int SV_Optimizations(void);
#endif

View file

@ -612,8 +612,7 @@ SV_ServerRecord_f(void)
if (sv.configstrings[i][0])
{
MSG_WriteByte(&buf, svc_configstring);
MSG_WriteShort(&buf, i);
MSG_WriteString(&buf, sv.configstrings[i]);
MSG_WriteConfigString(&buf, i, sv.configstrings[i]);
if (buf.cursize + 67 >= buf.maxsize)
{

View file

@ -212,8 +212,7 @@ PF_Configstring(int index, char *val)
/* send the update to everyone */
SZ_Clear(&sv.multicast);
MSG_WriteChar(&sv.multicast, svc_configstring);
MSG_WriteShort(&sv.multicast, index);
MSG_WriteString(&sv.multicast, val);
MSG_WriteConfigString(&sv.multicast, index, val);
SV_Multicast(vec3_origin, MULTICAST_ALL_R);
}

View file

@ -26,10 +26,6 @@
#include "header/server.h"
#define GAMEMODE_SP 0
#define GAMEMODE_COOP 1
#define GAMEMODE_DM 2
server_static_t svs; /* persistant server info */
server_t sv; /* local server */
@ -67,8 +63,7 @@ SV_FindIndex(char *name, int start, int max, qboolean create)
{
/* send the update to everyone */
MSG_WriteChar(&sv.multicast, svc_configstring);
MSG_WriteShort(&sv.multicast, start + i);
MSG_WriteString(&sv.multicast, name);
MSG_WriteConfigString(&sv.multicast, start + i, name);
SV_Multicast(vec3_origin, MULTICAST_ALL_R);
}
@ -436,6 +431,7 @@ SV_InitGame(void)
Cvar_FullSet("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
}
svs.gamemode = gamemode;
svs.spawncount = randk();
svs.clients = Z_Malloc(sizeof(client_t) * maxclients->value);
svs.num_client_entities = maxclients->value * UPDATE_BACKUP * 64;
@ -494,6 +490,7 @@ SV_Map(qboolean attractloop, char *levelstring, qboolean loadgame, qboolean isau
char *ch;
size_t l;
char spawnpoint[MAX_QPATH];
char *ext;
sv.loadgame = loadgame;
sv.attractloop = attractloop;
@ -549,7 +546,9 @@ SV_Map(qboolean attractloop, char *levelstring, qboolean loadgame, qboolean isau
--l;
}
if ((l > 4) && !strcmp(level + l - 4, ".cin"))
ext = (l <= 4) ? NULL : level + l - 4;
if (ext && !strcmp(ext, ".cin"))
{
#ifndef DEDICATED_ONLY
SCR_BeginLoadingPlaque(); /* for local system */
@ -557,7 +556,7 @@ SV_Map(qboolean attractloop, char *levelstring, qboolean loadgame, qboolean isau
SV_BroadcastCommand("changing\n");
SV_SpawnServer(level, spawnpoint, ss_cinematic, attractloop, loadgame, isautosave);
}
else if ((l > 4) && !strcmp(level + l - 4, ".dm2"))
else if (ext && !strcmp(ext, ".dm2"))
{
#ifndef DEDICATED_ONLY
SCR_BeginLoadingPlaque(); /* for local system */
@ -565,10 +564,10 @@ SV_Map(qboolean attractloop, char *levelstring, qboolean loadgame, qboolean isau
SV_BroadcastCommand("changing\n");
SV_SpawnServer(level, spawnpoint, ss_demo, attractloop, loadgame, isautosave);
}
else if ((l > 4) && (!strcmp(level + l - 4, ".pcx") ||
!strcmp(level + l - 4, ".tga") ||
!strcmp(level + l - 4, ".jpg") ||
!strcmp(level + l - 4, ".png")))
else if (ext && (!strcmp(ext, ".pcx") ||
!strcmp(ext, ".tga") ||
!strcmp(ext, ".jpg") ||
!strcmp(ext, ".png")))
{
#ifndef DEDICATED_ONLY
SCR_BeginLoadingPlaque(); /* for local system */
@ -582,7 +581,14 @@ SV_Map(qboolean attractloop, char *levelstring, qboolean loadgame, qboolean isau
SCR_BeginLoadingPlaque(); /* for local system */
#endif
SV_BroadcastCommand("changing\n");
SV_SendClientMessages();
/* for some reason calling send messages here causes a lengthy reconnect delay */
if (!(SV_Optimizations() & OPTIMIZE_RECONNECT))
{
SV_SendClientMessages();
SV_SendPrepClientMessages();
}
SV_SpawnServer(level, spawnpoint, ss_game, attractloop, loadgame, isautosave);
Cbuf_CopyToDefer();
}

View file

@ -32,6 +32,9 @@ netadr_t master_adr[MAX_MASTERS]; /* address of group servers */
client_t *sv_client; /* current client */
cvar_t *sv_optimize_sp_loadtime;
cvar_t *sv_optimize_mp_loadtime;
cvar_t *sv_paused;
cvar_t *sv_timedemo;
cvar_t *sv_enforcetime;
@ -373,9 +376,27 @@ SV_RunGameFrame(void)
#endif
}
int
SV_Optimizations(void)
{
cvar_t *cv;
if (svs.gamemode <= 0 || svs.gamemode > 3)
{
return 0;
}
cv = (svs.gamemode == GAMEMODE_SP) ?
sv_optimize_sp_loadtime : sv_optimize_mp_loadtime;
return cv ? cv->value : 0;
}
void
SV_Frame(int usec)
{
int opt_sendrate;
#ifndef DEDICATED_ONLY
time_before_game = time_after_game = 0;
#endif
@ -397,6 +418,16 @@ SV_Frame(int usec)
/* get packets from clients */
SV_ReadPackets();
/* send messages more often to new clients getting ready for spawning in
speeds up the process of sending configstrings, entty deltas, etc.
*/
opt_sendrate = SV_Optimizations() & OPTIMIZE_SENDRATE;
if (opt_sendrate)
{
SV_SendPrepClientMessages();
}
/* move autonomous things around if enough time has passed */
if (!sv_timedemo->value && (svs.realtime < sv.time))
{
@ -427,6 +458,12 @@ SV_Frame(int usec)
/* send messages back to the clients that had packets read this frame */
SV_SendClientMessages();
/* if not optimizing, send all messages here */
if (!opt_sendrate)
{
SV_SendPrepClientMessages();
}
/* save the entire world state if recording a serverdemo */
SV_RecordDemoMessage();
@ -574,6 +611,9 @@ SV_Init(void)
{
SV_InitOperatorCommands();
sv_optimize_sp_loadtime = Cvar_Get("sv_optimize_sp_loadtime", "7", 0);
sv_optimize_mp_loadtime = Cvar_Get("sv_optimize_mp_loadtime", "0", 0);
rcon_password = Cvar_Get("rcon_password", "", 0);
Cvar_Get("skill", "1", 0);
Cvar_Get("singleplayer", "0", CVAR_SERVERINFO | CVAR_LATCH);

View file

@ -136,15 +136,64 @@ SV_BroadcastCommand(const char *fmt, ...)
* MULTICAST_PVS send to clients potentially visible from org
* MULTICAST_PHS send to clients potentially hearable from org
*/
static qboolean
SV_WereConnected(const vec3_t origin, const byte *mask, int area1)
{
vec3_t origin2;
int leafnum;
int cluster;
VectorCopy(origin, origin2);
leafnum = CM_PointLeafnum(origin2);
cluster = CM_LeafCluster(leafnum);
// cluster can be -1 if we're in the void (or sometimes just at a wall)
// and using a negative index into mask[] would be invalid
if (cluster >= 0 && (mask[cluster >> 3] & (1 << (cluster & 7))))
{
if (CM_AreasConnected(area1, CM_LeafArea(leafnum)))
{
return true;
}
}
// if the client is currently in water, do a second check
if (CM_PointContents(origin2, 0) & MASK_WATER)
{
// if the client is half-submerged in opaque water so its origin
// is below the water, but the head/camera is still above the water
// and thus should be able to see/hear explosions or similar
// that are above the water.
// so try again at a slightly higher position
// FIXME: OTOH, we have a similar problem if we're over water and shoot under water (near water level) => can't see explosion
origin2[2] += 32.0f;
leafnum = CM_PointLeafnum(origin2);
cluster = CM_LeafCluster(leafnum);
if (cluster >= 0 && (mask[cluster >> 3] & (1 << (cluster & 7))))
{
if (CM_AreasConnected(area1, CM_LeafArea(leafnum)))
{
return true;
}
}
}
return false;
}
void
SV_Multicast(vec3_t origin, multicast_t to)
{
client_t *client;
byte *mask;
int leafnum = 0, cluster;
int leafnum, cluster;
int j;
qboolean reliable;
int area1, area2;
int area1;
reliable = false;
@ -155,6 +204,7 @@ SV_Multicast(vec3_t origin, multicast_t to)
}
else
{
leafnum = 0;
area1 = 0;
}
@ -208,53 +258,14 @@ SV_Multicast(vec3_t origin, multicast_t to)
if (mask)
{
vec3_t origin;
VectorCopy(client->edict->s.origin, origin);
qboolean wereConnected = false;
for(int i=0; i<2; ++i)
if (!SV_WereConnected(client->edict->s.origin, mask, area1))
{
leafnum = CM_PointLeafnum(origin);
cluster = CM_LeafCluster(leafnum);
area2 = CM_LeafArea(leafnum);
// cluster can be -1 if we're in the void (or sometimes just at a wall)
// and using a negative index into mask[] would be invalid
if (cluster >= 0 && CM_AreasConnected(area1, area2) && (mask[cluster >> 3] & (1 << (cluster & 7))))
{
wereConnected = true;
break;
}
// if the client is currently *not* in water, do *not* do a second check
if((CM_PointContents(origin, 0) & MASK_WATER) == 0)
{
break; // wereConnected remains false
}
// if the client is half-submerged in opaque water so its origin
// is below the water, but the head/camera is still above the water
// and thus should be able to see/hear explosions or similar
// that are above the water.
// so try again at a slightly higher position
origin[2] += 32.0f;
// FIXME: OTOH, we have a similar problem if we're over water and shoot under water (near water level) => can't see explosion
}
if (!wereConnected)
{
continue; // don't send message to this client, continue with next client
continue;
}
}
if (reliable)
{
SZ_Write(&client->netchan.message, sv.multicast.data,
sv.multicast.cursize);
}
else
{
SZ_Write(&client->datagram, sv.multicast.data, sv.multicast.cursize);
}
SZ_Write(reliable ? &client->netchan.message : &client->datagram,
sv.multicast.data, sv.multicast.cursize);
}
SZ_Clear(&sv.multicast);
@ -519,6 +530,57 @@ SV_RateDrop(client_t *c)
return false;
}
static int
SV_NextDemoChunk(byte *msgbuf)
{
size_t r;
int n;
if (sv_paused->value)
{
return 0;
}
r = FS_FRead(&n, 4, 1, sv.demofile);
if (r != 4)
{
return -1;
}
n = LittleLong(n);
if (n == -1)
{
return -1;
}
if (n > MAX_MSGLEN)
{
Com_Error(ERR_DROP,
"SV_SendClientMessages: msglen > MAX_MSGLEN");
}
r = FS_FRead(msgbuf, n, 1, sv.demofile);
return (r == n) ? n : -1;
}
/* if the reliable message
overflowed, drop the
client */
static void
SV_SendDisconnect(client_t *c)
{
SZ_Clear(&c->netchan.message);
SZ_Clear(&c->datagram);
SV_BroadcastPrintf(PRINT_HIGH, "%s overflowed\n", c->name);
SV_DropClient(c);
Netchan_Transmit(&c->netchan, 0, NULL);
}
void
SV_SendClientMessages(void)
{
@ -526,69 +588,35 @@ SV_SendClientMessages(void)
client_t *c;
int msglen;
byte msgbuf[MAX_MSGLEN];
size_t r;
msglen = 0;
/* read the next demo message if needed */
if (sv.demofile && (sv.state == ss_demo))
{
if (sv_paused->value)
msglen = SV_NextDemoChunk(msgbuf);
if (msglen < 0)
{
msglen = 0;
}
else
{
/* get the next message */
r = FS_FRead(&msglen, 4, 1, sv.demofile);
if (r != 4)
{
SV_DemoCompleted();
return;
}
msglen = LittleLong(msglen);
if (msglen == -1)
{
SV_DemoCompleted();
return;
}
if (msglen > MAX_MSGLEN)
{
Com_Error(ERR_DROP,
"SV_SendClientMessages: msglen > MAX_MSGLEN");
}
r = FS_FRead(msgbuf, msglen, 1, sv.demofile);
if (r != msglen)
{
SV_DemoCompleted();
return;
}
SV_DemoCompleted();
return;
}
}
else
{
msglen = 0;
}
/* send a message to each connected client */
/* send a message to each spawned client */
for (i = 0, c = svs.clients; i < maxclients->value; i++, c++)
{
if (!c->state)
if (c->state == cs_free)
{
continue;
}
/* if the reliable message
overflowed, drop the
client */
if (c->netchan.message.overflowed)
{
SZ_Clear(&c->netchan.message);
SZ_Clear(&c->datagram);
SV_BroadcastPrintf(PRINT_HIGH, "%s overflowed\n", c->name);
SV_DropClient(c);
SV_SendDisconnect(c);
continue;
}
if ((sv.state == ss_cinematic) ||
@ -607,15 +635,43 @@ SV_SendClientMessages(void)
SV_SendClientDatagram(c);
}
else
{
/* just update reliable if needed */
if (c->netchan.message.cursize ||
(curtime - c->netchan.last_sent > 1000))
{
Netchan_Transmit(&c->netchan, 0, NULL);
}
}
/* messages to non-spawned clients are sent by SendPrepClientMessages */
}
}
void
SV_SendPrepClientMessages(void)
{
client_t *c;
int i;
if ((sv.state == ss_cinematic) ||
(sv.state == ss_demo) ||
(sv.state == ss_pic))
{
return;
}
/* send a message to each inactive client if needed */
for (i = 0, c = svs.clients; i < maxclients->value; i++, c++)
{
if ((c->state == cs_free) || (c->state == cs_spawned))
{
continue;
}
if (c->netchan.message.overflowed)
{
SV_SendDisconnect(c);
continue;
}
/* just update reliable if needed */
if (c->netchan.message.cursize ||
(curtime - c->netchan.last_sent > 1000))
{
Netchan_Transmit(&c->netchan, 0, NULL);
}
}
}

View file

@ -28,6 +28,9 @@
#define MAX_STRINGCMDS 8
#define CMD_MARGIN 40 /* space in message reserved for command */
#define SAFE_MARGIN 24 /* space reserved for more data added elsewhere */
edict_t *sv_player;
void
@ -115,6 +118,8 @@ void
SV_Configstrings_f(void)
{
int start;
char *cs;
int max_msgutil;
Com_DPrintf("Configstrings() from %s\n", sv_client->name);
@ -134,16 +139,30 @@ SV_Configstrings_f(void)
start = (int)strtol(Cmd_Argv(2), (char **)NULL, 10);
/* write a packet full of data */
while (sv_client->netchan.message.cursize < MAX_MSGLEN / 2 &&
start < MAX_CONFIGSTRINGS)
if (start < 0)
{
if (sv.configstrings[start][0])
start = 0;
}
/* 560 is roughly the legacy safety margin */
max_msgutil = (SV_Optimizations() & OPTIMIZE_MSGUTIL) ?
SAFE_MARGIN : 560;
/* write a packet full of data */
while (start < MAX_CONFIGSTRINGS)
{
cs = sv.configstrings[start];
if (*cs != '\0')
{
if ((sv_client->netchan.message.cursize + MSG_ConfigString_Size(cs))
> (MAX_MSGLEN - (CMD_MARGIN + max_msgutil)))
{
break;
}
MSG_WriteByte(&sv_client->netchan.message, svc_configstring);
MSG_WriteShort(&sv_client->netchan.message, start);
MSG_WriteString(&sv_client->netchan.message,
sv.configstrings[start]);
MSG_WriteConfigString(&sv_client->netchan.message, start, cs);
}
start++;
@ -168,6 +187,7 @@ void
SV_Baselines_f(void)
{
int start;
int max_msgutil;
entity_state_t nullstate;
entity_state_t *base;
@ -188,16 +208,31 @@ SV_Baselines_f(void)
}
start = (int)strtol(Cmd_Argv(2), (char **)NULL, 10);
if (start < 0)
{
start = 0;
}
memset(&nullstate, 0, sizeof(nullstate));
/* 560 is roughly the legacy safety margin */
max_msgutil = (SV_Optimizations() & OPTIMIZE_MSGUTIL) ?
SAFE_MARGIN : 560;
/* write a packet full of data */
while (sv_client->netchan.message.cursize < MAX_MSGLEN / 2 &&
start < MAX_EDICTS)
while (start < MAX_EDICTS)
{
base = &sv.baselines[start];
if (base->modelindex || base->sound || base->effects)
{
if ((sv_client->netchan.message.cursize + MSG_DeltaEntity_Size(&nullstate, base, true, true))
> (MAX_MSGLEN - (CMD_MARGIN + max_msgutil)))
{
break;
}
MSG_WriteByte(&sv_client->netchan.message, svc_spawnbaseline);
MSG_WriteDeltaEntity(&nullstate, base,
&sv_client->netchan.message,