Even more SRB2Kart netcode improvement ports

- Prevent connection timeout during the waiting gamestate from the last commit.
- Keep client connections alive during fades / other internal loops.
- More consistently timeout clients when they reach the end of BACKUPTICS.
- Dedicated servers will not run any game logic if no nodes are sending packets to it, to reduce CPU usage when there is no one interacting with your server.
  - Unlike SRB2Kart, the amount of time is configurable with the "dedicatedidletime" console variable. Setting this to 0 will disable this feature.
- CL_SendClientCmd uses exact packet types instead of magic number offsets.
This commit is contained in:
Sally Coolatta 2023-07-17 00:44:34 -04:00
parent 80bf4d6c2d
commit 4a735470b9
7 changed files with 235 additions and 53 deletions

View file

@ -120,6 +120,8 @@ UINT8 hu_redownloadinggamestate = 0;
// true when a player is connecting or disconnecting so that the gameplay has stopped in its tracks
boolean hu_stopped = false;
consvar_t cv_dedicatedidletime = CVAR_INIT ("dedicatedidletime", "10", CV_SAVE, CV_Unsigned, NULL);
UINT8 adminpassmd5[16];
boolean adminpasswordset = false;
@ -4520,6 +4522,7 @@ static void HandlePacketFromPlayer(SINT8 node)
netconsole = 0;
else
netconsole = nodetoplayer[node];
#ifdef PARANOIA
if (netconsole >= MAXPLAYERS)
I_Error("bad table nodetoplayer: node %d player %d", doomcom->remotenode, netconsole);
@ -4558,15 +4561,20 @@ static void HandlePacketFromPlayer(SINT8 node)
// Update the nettics
nettics[node] = realend;
// Don't do anything for packets of type NODEKEEPALIVE?
if (netconsole == -1 || netbuffer->packettype == PT_NODEKEEPALIVE
|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
// This should probably still timeout though, as the node should always have a player 1 number
if (netconsole == -1)
break;
// As long as clients send valid ticcmds, the server can keep running, so reset the timeout
/// \todo Use a separate cvar for that kind of timeout?
freezetimeout[node] = I_GetTime() + connectiontimeout;
// Don't do anything for packets of type NODEKEEPALIVE?
// Sryder 2018/07/01: Update the freezetimeout still!
if (netbuffer->packettype == PT_NODEKEEPALIVE
|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
break;
// If we've alredy received a ticcmd for this tic, just submit it for the next one.
tic_t faketic = maketic;
if ((!!(netcmds[maketic % BACKUPTICS][netconsole].angleturn & TICCMD_RECEIVED))
@ -4630,6 +4638,21 @@ static void HandlePacketFromPlayer(SINT8 node)
}
}
break;
case PT_BASICKEEPALIVE:
if (client)
break;
// This should probably still timeout though, as the node should always have a player 1 number
if (netconsole == -1)
break;
// If a client sends this it should mean they are done receiving the savegame
sendingsavegame[node] = false;
// As long as clients send keep alives, the server can keep running, so reset the timeout
/// \todo Use a separate cvar for that kind of timeout?
freezetimeout[node] = I_GetTime() + connectiontimeout;
break;
case PT_TEXTCMD2: // splitscreen special
netconsole = nodetoplayer2[node];
/* FALLTHRU */
@ -5056,39 +5079,66 @@ static INT16 Consistancy(void)
return (INT16)(ret & 0xFFFF);
}
// confusing, but this DOESN'T send PT_NODEKEEPALIVE, it sends PT_BASICKEEPALIVE
// used during wipes to tell the server that a node is still connected
static void CL_SendClientKeepAlive(void)
{
netbuffer->packettype = PT_BASICKEEPALIVE;
HSendPacket(servernode, false, 0, 0);
}
static void SV_SendServerKeepAlive(void)
{
INT32 n;
for (n = 1; n < MAXNETNODES; n++)
{
if (nodeingame[n])
{
netbuffer->packettype = PT_BASICKEEPALIVE;
HSendPacket(n, false, 0, 0);
}
}
}
// send the client packet to the server
static void CL_SendClientCmd(void)
{
size_t packetsize = 0;
boolean mis = false;
netbuffer->packettype = PT_CLIENTCMD;
if (cl_packetmissed)
netbuffer->packettype++;
{
netbuffer->packettype = PT_CLIENTMIS;
mis = true;
}
netbuffer->u.clientpak.resendfrom = (UINT8)(neededtic & UINT8_MAX);
netbuffer->u.clientpak.client_tic = (UINT8)(gametic & UINT8_MAX);
if (gamestate == GS_WAITINGPLAYERS)
{
// Send PT_NODEKEEPALIVE packet
netbuffer->packettype += 4;
netbuffer->packettype = (mis ? PT_NODEKEEPALIVEMIS : PT_NODEKEEPALIVE);
packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
HSendPacket(servernode, false, 0, packetsize);
}
else if (gamestate != GS_NULL && (addedtogame || dedicated))
{
packetsize = sizeof (clientcmd_pak);
G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);
// Send a special packet with 2 cmd for splitscreen
if (splitscreen || botingame)
{
netbuffer->packettype += 2;
G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
netbuffer->packettype = (mis ? PT_CLIENT2MIS : PT_CLIENT2CMD);
packetsize = sizeof (client2cmd_pak);
G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
}
else
packetsize = sizeof (clientcmd_pak);
HSendPacket(servernode, false, 0, packetsize);
}
@ -5099,7 +5149,7 @@ static void CL_SendClientCmd(void)
if (localtextcmd[0])
{
netbuffer->packettype = PT_TEXTCMD;
M_Memcpy(netbuffer->u.textcmd,localtextcmd, localtextcmd[0]+1);
M_Memcpy(netbuffer->u.textcmd, localtextcmd, localtextcmd[0]+1);
// All extra data have been sent
if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // Send can fail...
localtextcmd[0] = 0;
@ -5479,28 +5529,11 @@ static inline void PingUpdate(void)
pingmeasurecount = 1; //Reset count
}
void NetUpdate(void)
static tic_t gametime = 0;
static void UpdatePingTable(void)
{
static tic_t gametime = 0;
static tic_t resptime = 0;
tic_t nowtime;
INT32 i;
INT32 realtics;
nowtime = I_GetTime();
realtics = nowtime - gametime;
if (realtics <= 0) // nothing new to update
return;
if (realtics > 5)
{
if (server)
realtics = 1;
else
realtics = 5;
}
gametime = nowtime;
if (server)
{
@ -5512,6 +5545,150 @@ void NetUpdate(void)
realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i]));
pingmeasurecount++;
}
}
// Handle timeouts to prevent definitive freezes from happenning
static void HandleNodeTimeouts(void)
{
INT32 i;
if (server)
{
for (i = 1; i < MAXNETNODES; i++)
if (nodeingame[i] && freezetimeout[i] < I_GetTime())
Net_ConnectionTimeout(i);
// In case the cvar value was lowered
if (joindelay)
joindelay = min(joindelay - 1, 3 * (tic_t)cv_joindelay.value * TICRATE);
}
}
// Keep the network alive while not advancing tics!
void NetKeepAlive(void)
{
tic_t nowtime;
INT32 realtics;
nowtime = I_GetTime();
realtics = nowtime - gametime;
// return if there's no time passed since the last call
if (realtics <= 0) // nothing new to update
return;
UpdatePingTable();
GetPackets();
#ifdef MASTERSERVER
MasterClient_Ticker();
#endif
if (client)
{
// send keep alive
CL_SendClientKeepAlive();
// No need to check for resynch because we aren't running any tics
}
else
{
SV_SendServerKeepAlive();
}
// No else because no tics are being run and we can't resynch during this
Net_AckTicker();
HandleNodeTimeouts();
FileSendTicker();
}
void NetUpdate(void)
{
static tic_t resptime = 0;
tic_t nowtime;
INT32 i;
INT32 realtics;
nowtime = I_GetTime();
realtics = nowtime - gametime;
if (realtics <= 0) // nothing new to update
return;
if (realtics > 5)
{
if (server)
realtics = 1;
else
realtics = 5;
}
if (server && dedicated && gamestate == GS_LEVEL)
{
const tic_t dedicatedidletime = cv_dedicatedidletime.value * TICRATE;
static tic_t dedicatedidletimeprev = 0;
static tic_t dedicatedidle = 0;
if (dedicatedidletime > 0)
{
for (i = 1; i < MAXNETNODES; ++i)
if (nodeingame[i])
{
if (dedicatedidle >= dedicatedidletime)
{
CONS_Printf("DEDICATED: Awakening from idle (Node %d detected...)\n", i);
dedicatedidle = 0;
}
break;
}
if (i == MAXNETNODES)
{
if (leveltime == 2)
{
// On next tick...
dedicatedidle = dedicatedidletime-1;
}
else if (dedicatedidle >= dedicatedidletime)
{
if (D_GetExistingTextcmd(gametic, 0) || D_GetExistingTextcmd(gametic+1, 0))
{
CONS_Printf("DEDICATED: Awakening from idle (Netxcmd detected...)\n");
dedicatedidle = 0;
}
else
{
realtics = 0;
}
}
else if ((dedicatedidle += realtics) >= dedicatedidletime)
{
const char *idlereason = "at round start";
if (leveltime > 3)
idlereason = va("for %d seconds", dedicatedidle/TICRATE);
CONS_Printf("DEDICATED: No nodes %s, idling...\n", idlereason);
realtics = 0;
dedicatedidle = dedicatedidletime;
}
}
}
else
{
if (dedicatedidletimeprev > 0 && dedicatedidle >= dedicatedidletimeprev)
{
CONS_Printf("DEDICATED: Awakening from idle (Idle disabled...)\n");
}
dedicatedidle = 0;
}
dedicatedidletimeprev = dedicatedidletime;
}
gametime = nowtime;
UpdatePingTable();
if (client)
maketic = neededtic;
@ -5543,25 +5720,26 @@ void NetUpdate(void)
}
else
{
if (!demoplayback)
if (!demoplayback && realtics > 0)
{
INT32 counts;
hu_redownloadinggamestate = false;
firstticstosend = gametic;
for (i = 0; i < MAXNETNODES; i++)
if (nodeingame[i] && nettics[i] < firstticstosend)
{
firstticstosend = nettics[i];
if (maketic + 1 >= nettics[i] + BACKUPTICS)
Net_ConnectionTimeout(i);
}
// Don't erase tics not acknowledged
counts = realtics;
firstticstosend = gametic;
for (i = 0; i < MAXNETNODES; i++)
{
if (!nodeingame[i])
continue;
if (nettics[i] < firstticstosend)
firstticstosend = nettics[i];
if (maketic + counts >= nettics[i] + (BACKUPTICS - TICRATE))
Net_ConnectionTimeout(i);
}
if (maketic + counts >= firstticstosend + BACKUPTICS)
counts = firstticstosend+BACKUPTICS-maketic-1;
@ -5578,20 +5756,10 @@ void NetUpdate(void)
}
Net_AckTicker();
// Handle timeouts to prevent definitive freezes from happenning
if (server)
{
for (i = 1; i < MAXNETNODES; i++)
if (nodeingame[i] && freezetimeout[i] < I_GetTime())
Net_ConnectionTimeout(i);
// In case the cvar value was lowered
if (joindelay)
joindelay = min(joindelay - 1, 3 * (tic_t)cv_joindelay.value * TICRATE);
}
HandleNodeTimeouts();
nowtime /= NEWTICRATERATIO;
if (nowtime > resptime)
{
resptime = nowtime;

View file

@ -77,6 +77,8 @@ typedef enum
PT_ASKLUAFILE, // Client telling the server they don't have the file
PT_HASLUAFILE, // Client telling the server they have the file
PT_BASICKEEPALIVE,// Keep the network alive during wipes, as tics aren't advanced and NetUpdate isn't called
// Add non-PT_CANFAIL packet types here to avoid breaking MS compatibility.
PT_CANFAIL, // This is kind of a priority. Anything bigger than CANFAIL
@ -398,6 +400,7 @@ extern tic_t servermaxping;
extern consvar_t cv_netticbuffer, cv_allownewplayer, cv_joinnextround, cv_maxplayers, cv_joindelay, cv_rejointimeout;
extern consvar_t cv_resynchattempts, cv_blamecfail;
extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed;
extern consvar_t cv_dedicatedidletime;
// Used in d_net, the only dependence
tic_t ExpandTics(INT32 low, INT32 node);
@ -412,6 +415,9 @@ void SendKick(UINT8 playernum, UINT8 msg);
// Create any new ticcmds and broadcast to other players.
void NetUpdate(void);
// Maintain connections to nodes without timing them all out.
void NetKeepAlive(void);
void SV_StartSinglePlayerServer(void);
boolean SV_SpawnServer(void);
void SV_StopServer(void);

View file

@ -869,6 +869,9 @@ static void DebugPrintpacket(const char *header)
(UINT32)ExpandTics(netbuffer->u.clientpak.client_tic, doomcom->remotenode),
(UINT32)ExpandTics (netbuffer->u.clientpak.resendfrom, doomcom->remotenode));
break;
case PT_BASICKEEPALIVE:
fprintf(debugfile, " wipetime\n");
break;
case PT_TEXTCMD:
case PT_TEXTCMD2:
fprintf(debugfile, " length %d\n ", netbuffer->u.textcmd[0]);

View file

@ -599,6 +599,7 @@ void D_RegisterServerCommands(void)
CV_RegisterVar(&cv_joinnextround);
CV_RegisterVar(&cv_showjoinaddress);
CV_RegisterVar(&cv_blamecfail);
CV_RegisterVar(&cv_dedicatedidletime);
#endif
COM_AddCommand("ping", Command_Ping_f, COM_LUA);

View file

@ -614,6 +614,8 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
if (moviemode)
M_SaveFrame();
NetKeepAlive(); // Update the network so we don't cause timeouts
}
WipeInAction = false;

View file

@ -1915,6 +1915,7 @@ void G_PreLevelTitleCard(void)
ST_runTitleCard();
ST_preLevelTitleCardDrawer();
I_FinishUpdate(); // page flip or blit buffer
NetKeepAlive(); // Prevent timeouts
if (moviemode)
M_SaveFrame();

View file

@ -7436,6 +7436,7 @@ static void P_RunSpecialStageWipe(void)
lastwipetic = nowtime;
if (moviemode) // make sure we save frames for the white hold too
M_SaveFrame();
NetKeepAlive(); // Prevent timeout
}
}