From 4a735470b947550cd0a3aedeec9a35ed02f11dc8 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 17 Jul 2023 00:44:34 -0400 Subject: [PATCH] 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. --- src/d_clisrv.c | 274 +++++++++++++++++++++++++++++++++++++++---------- src/d_clisrv.h | 6 ++ src/d_net.c | 3 + src/d_netcmd.c | 1 + src/f_wipe.c | 2 + src/g_game.c | 1 + src/p_setup.c | 1 + 7 files changed, 235 insertions(+), 53 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 9b3187cbb..13dab4539 100755 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -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; diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 04a7b5ba2..49fb5fc1d 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -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); diff --git a/src/d_net.c b/src/d_net.c index 768c9ac7e..6d8c72942 100644 --- a/src/d_net.c +++ b/src/d_net.c @@ -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]); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 2cdf70843..8fd207037 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -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); diff --git a/src/f_wipe.c b/src/f_wipe.c index 6014fb7f9..4bcfb029b 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -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; diff --git a/src/g_game.c b/src/g_game.c index bcfe69105..eea804c83 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -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(); diff --git a/src/p_setup.c b/src/p_setup.c index a10326986..e4a46c0f4 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -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 } }