From 0e23b900bb32c842fcf95b6d5d3dcf81e795403a Mon Sep 17 00:00:00 2001 From: Boondorl Date: Mon, 30 Jun 2025 00:05:53 -0400 Subject: [PATCH] Added dynamic tic stabilization Attempts to balance periods of rough traffic by putting in an artificial delay, smoothing playback but further increasing input delay. This can be disabled with net_ticbalance. --- src/common/engine/i_net.h | 1 + src/d_net.cpp | 66 +++++++++++++++++++++++++++++++++++++-- wadsrc/static/menudef.txt | 1 + 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/common/engine/i_net.h b/src/common/engine/i_net.h index 6e92e945f..510388fad 100644 --- a/src/common/engine/i_net.h +++ b/src/common/engine/i_net.h @@ -11,6 +11,7 @@ enum ENetConstants BACKUPTICS = 35 * 5, // Remember up to 5 seconds of data. MAXTICDUP = 3, MAXSENDTICS = 35 * 1, // Only send up to 1 second of data at a time. + STABILITYTICS = 12, LOCALCMDTICS = (BACKUPTICS * MAXTICDUP), MAX_MSGLEN = 14000, }; diff --git a/src/d_net.cpp b/src/d_net.cpp index 90dbd83b2..92c2e36e8 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -116,6 +116,14 @@ int LastSentConsistency = 0; // Last consistency we sent out. If < CurrentC int CurrentConsistency = 0; // Last consistency we generated. FClientNetState ClientStates[MAXPLAYERS] = {}; +// Try and stabilize uneven connections by checking for spikes in available +// sequences. If they're found, try and average out a buffer to prioritize +// making the experience smoother over very stop and go heavy. +static int StabilityBuffer = 0; +static int PrevAvailableDiff = 0; +static size_t CurStabilityTic = 0u; +static int StabilityTics[STABILITYTICS] = {}; + // If we're sending a packet to ourselves, store it here instead. This is the simplest way to execute // playback as it means in the world running code itself all player commands are built the exact same way // instead of having to rely on pulling from the correct local buffers. It also ensures all commands are @@ -157,7 +165,7 @@ extern bool advancedemo; CVAR(Bool, vid_dontdowait, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR(Bool, vid_lowerinbackground, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR(Bool, net_ticbalance, false, CVAR_SERVERINFO | CVAR_NOSAVE) // Currently deprecated, but may be brought back later. +CVAR(Bool, net_ticbalance, true, CVAR_SERVERINFO | CVAR_NOSAVE) CVAR(Bool, net_extratic, false, CVAR_SERVERINFO | CVAR_NOSAVE) CVAR(Bool, net_limitsaves, true, CVAR_SERVERINFO | CVAR_NOSAVE) CVAR(Bool, net_repeatableactioncooldown, true, CVAR_SERVERINFO | CVAR_NOSAVE) @@ -395,6 +403,9 @@ void Net_ClearBuffers() LastEnterTic = LastGameUpdate = EnterTic; gametic = ClientTic = 0; SkipCommandTimer = SkipCommandAmount = CommandsAhead = 0; + StabilityBuffer = PrevAvailableDiff = 0; + CurStabilityTic = 0u; + memset(StabilityTics, 0, sizeof(StabilityTics)); NetEvents.ResetStream(); CutsceneReady = 0u; @@ -502,6 +513,9 @@ void Net_ResetCommands(bool midTic) bCommandsReset = midTic; ++CurrentLobbyID; SkipCommandTimer = SkipCommandAmount = CommandsAhead = 0; + StabilityBuffer = PrevAvailableDiff = 0; + CurStabilityTic = 0u; + memset(StabilityTics, 0, sizeof(StabilityTics)); int tic = gametic / TicDup; if (midTic) @@ -1292,6 +1306,7 @@ static bool Net_UpdateStatus() if (updated) { + lowestDiff -= StabilityBuffer; if (lowestDiff > 0) { if (SkipCommandTimer++ > TICRATE / 2) @@ -2090,6 +2105,47 @@ static bool ShouldStabilizeTick() && gameaction != ga_worlddone && gameaction != ga_completed && gameaction != ga_screenshot && gameaction != ga_fullconsole; } +// If the connection has been unstable then let the game lag behind for a little bit +// while we wait for it to stabilize, otherwise everything will appear to jitter around. +static void CalculateNetStabilityBuffer(int diff) +{ + if (!netgame || demoplayback) + { + StabilityBuffer = 0; + return; + } + + if (diff < 0) + diff = 0; + + if (!(gametic % TicDup)) + { + StabilityTics[CurStabilityTic++ % STABILITYTICS] = diff > PrevAvailableDiff ? diff : 0; + PrevAvailableDiff = diff; + } + + // If we're not balancing latency, just give an extra tic for padding + // and nothing else. + if (!net_ticbalance) + { + StabilityBuffer = 1; + return; + } + + double total = 0.0; + int unstableCount = 0; + for (int t : StabilityTics) + { + if (t > 0) + { + ++unstableCount; + total += t; + } + } + + StabilityBuffer = unstableCount > 0 ? static_cast(ceil(total / unstableCount)) : 0; +} + // // TryRunTics // @@ -2148,8 +2204,12 @@ void TryRunTics() // If the amount of tics to run is falling behind the amount of available tics, // speed the playsim up a bit to help catch up. int runTics = min(totalTics, availableTics); - if (totalTics > 0 && totalTics < availableTics && !singletics) - ++runTics; + if (!singletics && totalTics > 0) + { + CalculateNetStabilityBuffer(availableTics - totalTics); + if (totalTics < availableTics - StabilityBuffer) + ++runTics; + } // Test player prediction code in singleplayer // by running the gametic behind the ClientTic diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 634c1da30..046a7f1f6 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -2440,6 +2440,7 @@ OptionMenu NetworkOptions protected StaticText " " StaticText "$NETMNU_HOSTOPTIONS", 1 Option "$NETMNU_EXTRATICS", "net_extratic", "OnOff" + Option "$NETMNU_BALANCETICS", "net_ticbalance", "OnOff" Option "$NETMNU_LIMITCONVO", "net_limitconversations", "OnOff" Option "$NETMNU_REPEATCOOLDOWN", "net_repeatableactioncooldown", "OnOff" Option "$NETMNU_DISABLEPAUSE", "net_disablepause", "PauseTypes"