Make the client asynchronous, e.g. decouble net and refresh frames.

This is largely based upon the cl_async 1 mode from KMQuake2, which in
turn is based upon r1q2. The origins of this code may be even older...
Different to KMQuake2 the asynchonous mode is not optional, the client
is always asynchonous. Since we're mainly integrating this rather
fundamental change to simplify the complex internal timing between
client, server and refresh, there's no point in keeping it optional.

The old cl_maxfps cvar controls the network frames. 30 frames should be
enough, even Q3A hasn't more. The new gl_maxfps cvar controls the render
frames. It's set to 95 fps by default to avoid possible remnant of the
famous 125hz bug.
This commit is contained in:
Yamagi Burmeister 2016-08-04 17:36:42 +02:00
parent fa4eacc976
commit 4ae8706d22
7 changed files with 307 additions and 127 deletions

View file

@ -418,6 +418,7 @@ CL_RequestNextDownload(void)
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
MSG_WriteString(&cls.netchan.message, va("begin %i\n", precache_spawncount));
cls.forcePacket = true;
}
void
@ -500,6 +501,7 @@ CL_CheckOrDownloadFile(char *filename)
}
cls.downloadnumber++;
cls.forcePacket = true;
return false;
}
@ -606,6 +608,7 @@ CL_ParseDownload(void)
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
SZ_Print(&cls.netchan.message, "nextdl");
cls.forcePacket = true;
}
else
{

View file

@ -427,12 +427,12 @@ CL_AdjustAngles(void)
if (in_speed.state & 1)
{
speed = cls.frametime * cl_anglespeedkey->value;
speed = cls.nframetime * cl_anglespeedkey->value;
}
else
{
speed = cls.frametime;
speed = cls.nframetime;
}
if (!(in_strafe.state & 1))
@ -553,7 +553,7 @@ CL_FinishMove(usercmd_t *cmd)
}
/* send milliseconds of time to apply the move */
ms = cls.frametime * 1000;
ms = cls.nframetime * 1000;
if (ms > 250)
{
@ -576,36 +576,6 @@ CL_FinishMove(usercmd_t *cmd)
cmd->lightlevel = (byte)cl_lightlevel->value;
}
usercmd_t
CL_CreateCmd(void)
{
usercmd_t cmd;
frame_msec = sys_frame_time - old_sys_frame_time;
if (frame_msec < 1)
{
frame_msec = 1;
}
if (frame_msec > 200)
{
frame_msec = 200;
}
/* get basic movement from keyboard */
CL_BaseMove(&cmd);
/* allow mice or other external controllers to add to the move */
IN_Move(&cmd);
CL_FinishMove(&cmd);
old_sys_frame_time = sys_frame_time;
return cmd;
}
void
IN_CenterView(void)
{
@ -662,6 +632,123 @@ CL_InitInput(void)
cl_nodelta = Cvar_Get("cl_nodelta", "0", 0);
}
void
CL_RefreshCmd(void)
{
int ms;
usercmd_t *cmd;
// CMD to fill
cmd = &cl.cmds[cls.netchan.outgoing_sequence & (CMD_BACKUP - 1)];
// Calculate delta
frame_msec = sys_frame_time - old_sys_frame_time;
// Check bounds
if (frame_msec < 1)
{
return;
}
else if (frame_msec > 200)
{
frame_msec = 200;
}
// Add movement
CL_BaseMove(cmd);
IN_Move(cmd);
// Clamp angels for prediction
CL_ClampPitch();
cmd->angles[0] = ANGLE2SHORT(cl.viewangles[0]);
cmd->angles[1] = ANGLE2SHORT(cl.viewangles[1]);
cmd->angles[2] = ANGLE2SHORT(cl.viewangles[2]);
// Update time for prediction
ms = (int)(cls.nframetime * 1000.0f);
if (ms > 250)
{
ms = 100;
}
cmd->msec = ms;
// Update frame time for the next call
old_sys_frame_time = sys_frame_time;
// Important events are send immediately
if (((in_attack.state & 2)) || (in_use.state & 2))
{
cls.forcePacket = true;
}
}
void
CL_RefreshMove(void)
{
usercmd_t *cmd;
// CMD to fill
cmd = &cl.cmds[cls.netchan.outgoing_sequence & (CMD_BACKUP - 1)];
// Calculate delta
frame_msec = sys_frame_time - old_sys_frame_time;
// Check bounds
if (frame_msec < 1)
{
return;
}
else if (frame_msec > 200)
{
frame_msec = 200;
}
// Add movement
CL_BaseMove(cmd);
IN_Move(cmd);
old_sys_frame_time = sys_frame_time;
}
void
CL_FinalizeCmd(void)
{
usercmd_t *cmd;
// CMD to fill
cmd = &cl.cmds[cls.netchan.outgoing_sequence & (CMD_BACKUP - 1)];
// Mouse button events
if (in_attack.state & 3)
{
cmd->buttons |= BUTTON_ATTACK;
}
in_attack.state &= ~2;
if (in_use.state & 3)
{
cmd->buttons |= BUTTON_USE;
}
in_use.state &= ~2;
// Keyboard events
if (anykeydown && cls.key_dest == key_game)
{
cmd->buttons |= BUTTON_ANY;
}
cmd->impulse = in_impulse;
in_impulse = 0;
// Set light level for muzzle flash
cmd->lightlevel = (byte)cl_lightlevel->value;
}
void
CL_SendCmd(void)
{
@ -672,14 +759,14 @@ CL_SendCmd(void)
usercmd_t nullcmd;
int checksumIndex;
/* build a command even if not connected */
memset(&buf, 0, sizeof(buf));
/* save this command off for prediction */
i = cls.netchan.outgoing_sequence & (CMD_BACKUP - 1);
cmd = &cl.cmds[i];
cl.cmd_time[i] = cls.realtime; /* for netgraph ping calculation */
*cmd = CL_CreateCmd();
CL_FinalizeCmd();
cl.cmd = *cmd;
@ -691,10 +778,9 @@ CL_SendCmd(void)
if (cls.state == ca_connected)
{
if (cls.netchan.message.cursize ||
(curtime - cls.netchan.last_sent > 100))
(curtime - cls.netchan.last_sent > 1000))
{
byte zero_data = 0;
Netchan_Transmit(&cls.netchan, 0, &zero_data);
Netchan_Transmit(&cls.netchan, 0, buf.data);
}
return;
@ -761,5 +847,9 @@ CL_SendCmd(void)
/* deliver the message */
Netchan_Transmit(&cls.netchan, buf.cursize, buf.data);
/* Reinit the current cmd buffer */
cmd = &cl.cmds[cls.netchan.outgoing_sequence & (CMD_BACKUP - 1)];
memset(cmd, 0, sizeof(*cmd));
}

View file

@ -188,7 +188,7 @@ CL_RunDLights(void)
return;
}
dl->radius -= cls.frametime * dl->decay;
dl->radius -= cls.rframetime * dl->decay;
if (dl->radius < 0)
{

View file

@ -95,6 +95,7 @@ cvar_t *hand;
cvar_t *gender;
cvar_t *gender_auto;
cvar_t *gl_maxfps;
cvar_t *gl_stereo;
cvar_t *gl_stereo_separation;
@ -506,7 +507,7 @@ CL_InitLocal(void)
cl_noskins = Cvar_Get("cl_noskins", "0", 0);
cl_autoskins = Cvar_Get("cl_autoskins", "0", 0);
cl_predict = Cvar_Get("cl_predict", "1", 0);
cl_maxfps = Cvar_Get("cl_maxfps", "95", CVAR_ARCHIVE);
cl_maxfps = Cvar_Get("cl_maxfps", "30", CVAR_ARCHIVE);
cl_drawfps = Cvar_Get("cl_drawfps", "0", CVAR_ARCHIVE);
cl_upspeed = Cvar_Get("cl_upspeed", "200", 0);
@ -534,6 +535,8 @@ CL_InitLocal(void)
cl_paused = Cvar_Get("paused", "0", 0);
cl_timedemo = Cvar_Get("timedemo", "0", 0);
gl_maxfps = Cvar_Get("gl_maxfps", "95", CVAR_ARCHIVE);
gl_stereo = Cvar_Get( "gl_stereo", "0", CVAR_ARCHIVE );
gl_stereo_separation = Cvar_Get( "gl_stereo_separation", "1", CVAR_ARCHIVE );
gl_stereo_convergence = Cvar_Get( "gl_stereo_convergence", "1.4", CVAR_ARCHIVE );
@ -753,41 +756,41 @@ CL_SendCommand(void)
void
CL_Frame(int msec)
{
static int extratime;
static int lasttimecalled;
static int packetdelta = 0;
static int renderdelta = 0;
static int miscdelta = 0;
qboolean packetframe = true;
qboolean renderframe = true;
qboolean miscframe = true;
if (dedicated->value)
{
return;
}
extratime += msec;
// Adjust deltas
packetdelta += msec;
renderdelta += msec;
miscdelta += msec;
if (!cl_timedemo->value)
// Calculate simulation time
cls.nframetime = packetdelta * 0.001f;
cls.rframetime = renderdelta * 0.001f;
cls.realtime = curtime;
cl.time += msec;
// Don't extrapolate too far ahead
if (cls.nframetime > 0.5f)
{
if ((cls.state == ca_connected) && (extratime < 100))
{
return; /* don't flood packets out while connecting */
}
if (extratime < 1000 / cl_maxfps->value)
{
return; /* framerate is too high */
}
cls.nframetime = 0.5f;
}
/* decide the simulation time */
cls.frametime = extratime / 1000.0;
cl.time += extratime;
cls.realtime = curtime;
extratime = 0;
if (cls.frametime > (1.0 / 5))
if (cls.rframetime > 0.5f)
{
cls.frametime = (1.0 / 5);
cls.rframetime = 0.5f;
}
/* if in the debugger last frame, don't timeout */
@ -796,77 +799,150 @@ CL_Frame(int msec)
cls.netchan.last_received = Sys_Milliseconds();
}
/* fetch results from server */
CL_ReadPackets();
/* send a new command message to the server */
CL_SendCommand();
/* predict all unacknowledged movements */
CL_PredictMovement();
/* allow renderer DLL change */
VID_CheckChanges();
if (!cl.refresh_prepped && (cls.state == ca_active))
if (!cl_timedemo->value)
{
CL_PrepRefresh();
// Don't flood while connecting
if ((cls.state == ca_connected) && (packetdelta < 100))
{
packetframe = false;
}
// Network frames
if (packetdelta < (1000.0f / cl_maxfps->value))
{
packetframe = false;
}
else if (cls.nframetime == cls.rframetime)
{
packetframe = false;
}
// Render frames
if (renderdelta < (1000.0f / gl_maxfps->value))
{
renderframe = false;
}
// Misc. stuff at 10 FPS
if (miscdelta < 100.0f)
{
miscframe = false;
}
// TODO: Do we need the cl_sleep stuff?
}
else if (msec < 1)
{
return;
}
/* update the screen */
if (host_speeds->value)
// Update input stuff
if (packetframe || renderframe)
{
time_before_ref = Sys_Milliseconds();
CL_ReadPackets();
Sys_SendKeyEvents();
Cbuf_Execute();
CL_FixCvarCheats();
if (cls.state > ca_connecting)
{
CL_RefreshCmd();
}
else
{
CL_RefreshMove();
}
}
SCR_UpdateScreen();
if (host_speeds->value)
if (cls.forcePacket || userinfo_modified)
{
time_after_ref = Sys_Milliseconds();
packetframe = true;
cls.forcePacket = false;
}
/* update audio */
S_Update(cl.refdef.vieworg, cl.v_forward, cl.v_right, cl.v_up);
if (packetframe)
{
packetdelta = 0;
CL_SendCmd();
CL_CheckForResend();
}
if (renderframe)
{
renderdelta = 0;
if (miscframe)
{
miscdelta = 0;
VID_CheckChanges();
}
CL_PredictMovement(); // TODO: Make called function async
if (!cl.refresh_prepped && (cls.state == ca_active))
{
CL_PrepRefresh();
}
/* update the screen */
if (host_speeds->value)
{
time_before_ref = Sys_Milliseconds();
}
SCR_UpdateScreen();
if (host_speeds->value)
{
time_after_ref = Sys_Milliseconds();
}
/* update audio */
S_Update(cl.refdef.vieworg, cl.v_forward, cl.v_right, cl.v_up);
#ifdef CDA
CDAudio_Update();
if (miscframe)
{
CDAudio_Update();
}
#endif
/* advance local effects for next frame */
CL_RunDLights();
/* advance local effects for next frame */
CL_RunDLights();
CL_RunLightStyles();
SCR_RunCinematic();
SCR_RunConsole();
CL_RunLightStyles();
/* Update framecounter */
cls.framecount++;
SCR_RunCinematic();
SCR_RunConsole();
cls.framecount++;
if (log_stats->value)
{
if (cls.state == ca_active)
if (log_stats->value)
{
if (!lasttimecalled)
if (cls.state == ca_active)
{
lasttimecalled = Sys_Milliseconds();
if (log_stats_file)
if (!lasttimecalled)
{
fprintf(log_stats_file, "0\n");
}
}
lasttimecalled = Sys_Milliseconds();
else
{
int now = Sys_Milliseconds();
if (log_stats_file)
{
fprintf(log_stats_file, "%d\n", now - lasttimecalled);
if (log_stats_file)
{
fprintf(log_stats_file, "0\n");
}
}
lasttimecalled = now;
else
{
int now = Sys_Milliseconds();
if (log_stats_file)
{
fprintf(log_stats_file, "%d\n", now - lasttimecalled);
}
lasttimecalled = now;
}
}
}
}

View file

@ -233,6 +233,7 @@ CL_PredictMovement(void)
int i;
int step;
int oldz;
static int last_step_frame = 0;
if (cls.state != ca_active)
{
@ -272,20 +273,24 @@ CL_PredictMovement(void)
}
/* copy current state to pmove */
memset (&pm, 0, sizeof(pm));
pm.trace = CL_PMTrace;
pm.pointcontents = CL_PMpointcontents;
pm_airaccelerate = strtod(cl.configstrings[CS_AIRACCEL], (char **)NULL);
pm_airaccelerate = atof(cl.configstrings[CS_AIRACCEL]);
pm.s = cl.frame.playerstate.pmove;
VectorSet(pm.mins, -16, -16, -24);
VectorSet(pm.maxs, 16, 16, 32);
/* run frames */
while (++ack < current)
while (++ack <= current)
{
frame = ack & (CMD_BACKUP - 1);
cmd = &cl.cmds[frame];
// Ignore null entries
if (!cmd->msec)
{
continue;
}
pm.cmd = *cmd;
Pmove(&pm);
@ -297,10 +302,11 @@ CL_PredictMovement(void)
oldz = cl.predicted_origins[oldframe][2];
step = pm.s.origin[2] - oldz;
if ((step > 63) && (step < 160) && (pm.s.pm_flags & PMF_ON_GROUND))
if (last_step_frame != current && step > 63 && step < 160 && (pm.s.pm_flags & PMF_ON_GROUND))
{
cl.predicted_step = step * 0.125f;
cl.predicted_step_time = cls.realtime - cls.frametime * 500;
cl.predicted_step_time = cls.realtime - cls.nframetime * 500;
last_step_frame = current;
}
/* copy results out for rendering */

View file

@ -319,7 +319,7 @@ SCR_DrawCenterString(void)
void
SCR_CheckDrawCenterString(void)
{
scr_centertime_off -= cls.frametime;
scr_centertime_off -= cls.rframetime;
if (scr_centertime_off <= 0)
{
@ -514,7 +514,7 @@ SCR_RunConsole(void)
if (scr_conlines < scr_con_current)
{
scr_con_current -= scr_conspeed->value * cls.frametime;
scr_con_current -= scr_conspeed->value * cls.rframetime;
if (scr_conlines > scr_con_current)
{
@ -523,7 +523,7 @@ SCR_RunConsole(void)
}
else if (scr_conlines > scr_con_current)
{
scr_con_current += scr_conspeed->value * cls.frametime;
scr_con_current += scr_conspeed->value * cls.rframetime;
if (scr_conlines < scr_con_current)
{
@ -1541,13 +1541,13 @@ SCR_UpdateScreen(void)
if (cl_drawfps->value)
{
char s[8];
sprintf(s, "%3.0ffps", 1 / cls.frametime);
sprintf(s, "%3.0ffps", 1 / cls.rframetime);
DrawString(viddef.width - 64, 0, s);
}
if (scr_timegraph->value)
{
SCR_DebugGraph(cls.frametime * 300, 0);
SCR_DebugGraph(cls.rframetime * 300, 0);
}
if (scr_debuggraph->value || scr_timegraph->value ||

View file

@ -210,7 +210,8 @@ typedef struct
int framecount;
int realtime; /* always increasing, no clamping, etc */
float frametime; /* seconds since last frame */
float rframetime; /* seconds since last render frame */
float nframetime; /* network frame time */
/* screen rendering information */
float disable_screen; /* showing loading plaque between levels */
@ -231,6 +232,8 @@ typedef struct
int challenge; /* from the server to use for connecting */
qboolean forcePacket; /* Forces a package to be send at the next frame. */
FILE *download; /* file transfer from server */
char downloadtempname[MAX_OSPATH];
char downloadname[MAX_OSPATH];
@ -438,7 +441,9 @@ extern kbutton_t in_strafe;
extern kbutton_t in_speed;
void CL_InitInput (void);
void CL_RefreshCmd(void);
void CL_SendCmd (void);
void CL_RefreshMove(void);
void CL_SendMove (usercmd_t *cmd);
void CL_ClearState (void);