From c5b75d83916b1bfe6afe097d57a9fd4d552db090 Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Sat, 23 Aug 2025 15:42:44 +0100 Subject: [PATCH 1/2] bugfix(network): fix runahead update logic so the runahead always follows the max network latency --- .../Include/GameNetwork/ConnectionManager.h | 2 +- .../Include/GameNetwork/NetworkDefs.h | 1 + .../Source/GameNetwork/ConnectionManager.cpp | 43 +++++++------------ .../Source/GameNetwork/NetworkUtil.cpp | 1 + .../Include/GameNetwork/ConnectionManager.h | 2 +- .../Include/GameNetwork/NetworkDefs.h | 1 + .../Source/GameNetwork/ConnectionManager.cpp | 43 +++++++------------ .../Source/GameNetwork/NetworkUtil.cpp | 1 + 8 files changed, 38 insertions(+), 56 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameNetwork/ConnectionManager.h b/Generals/Code/GameEngine/Include/GameNetwork/ConnectionManager.h index 8ed430b04f8..d871e2be9d5 100644 --- a/Generals/Code/GameEngine/Include/GameNetwork/ConnectionManager.h +++ b/Generals/Code/GameEngine/Include/GameNetwork/ConnectionManager.h @@ -174,7 +174,7 @@ class ConnectionManager // void doPerFrameMetrics(UnsignedInt frame); void getMinimumFps(Int &minFps, Int &minFpsPlayer); ///< Returns the smallest FPS in the m_fpsAverages list. - Real getMaximumLatency(); ///< This actually sums the two biggest latencies in the m_latencyAverages list. + Real getMaximumLatency(); ///< Returns the highest average latency between players. void requestFrameDataResend(Int playerID, UnsignedInt frame); ///< request of this player that he send the specified frame's data. diff --git a/Generals/Code/GameEngine/Include/GameNetwork/NetworkDefs.h b/Generals/Code/GameEngine/Include/GameNetwork/NetworkDefs.h index bec66357c3e..f26530ec162 100644 --- a/Generals/Code/GameEngine/Include/GameNetwork/NetworkDefs.h +++ b/Generals/Code/GameEngine/Include/GameNetwork/NetworkDefs.h @@ -36,6 +36,7 @@ static const Int WOL_NAME_LEN = 64; /// Max number of commands per frame static const Int MAX_COMMANDS = 256; +extern Int MIN_LOGIC_FRAMES; extern Int MAX_FRAMES_AHEAD; extern Int MIN_RUNAHEAD; diff --git a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp index b78a6f1dfa0..607c81d138a 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -1246,22 +1246,20 @@ void ConnectionManager::updateRunAhead(Int oldRunAhead, Int frameRate, Bool didS // if the minimum fps is within 10% of the desired framerate, then keep the current minimum fps. minFps = frameRate; } - if (minFps < 5) { - minFps = 5; // Absolutely do not run below 5 fps. - } - if (minFps > TheGlobalData->m_framesPerSecondLimit) { - minFps = TheGlobalData->m_framesPerSecondLimit; // Cap to 30 FPS. - } + + // TheSuperHackers @info this clamps the logic time scale fps in network games + minFps = clamp(MIN_LOGIC_FRAMES, minFps, TheGlobalData->m_framesPerSecondLimit); DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("ConnectionManager::updateRunAhead - minFps after adjustment is %d", minFps)); - Int newRunAhead = (Int)((getMaximumLatency() / 2.0) * (Real)minFps); - newRunAhead += (newRunAhead * TheGlobalData->m_networkRunAheadSlack) / 100; // Add in 10% of slack to the run ahead in case of network hiccups. - if (newRunAhead < MIN_RUNAHEAD) { - newRunAhead = MIN_RUNAHEAD; // make sure its at least MIN_RUNAHEAD. - } - if (newRunAhead > (MAX_FRAMES_AHEAD / 2)) { - newRunAhead = MAX_FRAMES_AHEAD / 2; // dont let run ahead get out of hand. - } + // TheSuperHackers @bugfix Mauller 21/08/2025 calculate the runahead so it always follows the latency + // The runahead should always be rounded up to the next integer value to prevent variations in latency from causing stutter + // The network slack pushes the runahead up to the next value when the latency is within the slack percentage of the current runahead + const Real runAheadSlackScale = 1.0f + ( (Real)TheGlobalData->m_networkRunAheadSlack / 100.0f ); + Int newRunAhead = ceilf( getMaximumLatency() * runAheadSlackScale * (Real)minFps ); + + // TheSuperHackers @info if the runahead goes below 3 logic frames it can start to introduce stutter + // We also limit the upper range of the runahead to prevent it getting out of hand + newRunAhead = clamp(MIN_RUNAHEAD, newRunAhead, MAX_FRAMES_AHEAD / 2); NetRunAheadCommandMsg *msg = newInstance(NetRunAheadCommandMsg); msg->setPlayerID(m_localSlot); @@ -1361,24 +1359,15 @@ void ConnectionManager::updateRunAhead(Int oldRunAhead, Int frameRate, Bool didS } Real ConnectionManager::getMaximumLatency() { - // This works for 2 player games because the latency for the packet router is always 0. - Real lat1 = 0.0; - Real lat2 = 0.0; + Real Maxlat = 0.0f; for (Int i = 0; i < MAX_SLOTS; ++i) { - if (isPlayerConnected(i)) { - if (m_latencyAverages[i] != 0.0) { - if (m_latencyAverages[i] > lat1) { - lat2 = lat1; - lat1 = m_latencyAverages[i]; - } else if (m_latencyAverages[i] > lat2) { - lat2 = m_latencyAverages[i]; - } - } + if (isPlayerConnected(i) && m_latencyAverages[i] > Maxlat) { + Maxlat = m_latencyAverages[i]; } } - return (lat1 + lat2); + return Maxlat; } void ConnectionManager::getMinimumFps(Int &minFps, Int &minFpsPlayer) { diff --git a/Generals/Code/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Generals/Code/GameEngine/Source/GameNetwork/NetworkUtil.cpp index 03465874a83..4e3d7f18995 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -29,6 +29,7 @@ // TheSuperHackers @tweak Mauller 26/08/2025 reduce the minimum runahead from 10 // This lets network games run at latencies down to 133ms when the network conditions allow +Int MIN_LOGIC_FRAMES = 5; Int MAX_FRAMES_AHEAD = 128; Int MIN_RUNAHEAD = 4; Int FRAME_DATA_LENGTH = (MAX_FRAMES_AHEAD+1)*2; diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/ConnectionManager.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/ConnectionManager.h index 0a9629f59c2..da26083c991 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/ConnectionManager.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/ConnectionManager.h @@ -174,7 +174,7 @@ class ConnectionManager // void doPerFrameMetrics(UnsignedInt frame); void getMinimumFps(Int &minFps, Int &minFpsPlayer); ///< Returns the smallest FPS in the m_fpsAverages list. - Real getMaximumLatency(); ///< This actually sums the two biggest latencies in the m_latencyAverages list. + Real getMaximumLatency(); ///< Returns the highest average latency between players. void requestFrameDataResend(Int playerID, UnsignedInt frame); ///< request of this player that he send the specified frame's data. diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/NetworkDefs.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/NetworkDefs.h index 20e65b3bbab..4c9595dba2f 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/NetworkDefs.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/NetworkDefs.h @@ -36,6 +36,7 @@ static const Int WOL_NAME_LEN = 64; /// Max number of commands per frame static const Int MAX_COMMANDS = 256; +extern Int MIN_LOGIC_FRAMES; extern Int MAX_FRAMES_AHEAD; extern Int MIN_RUNAHEAD; diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp index 26a19a93555..aff7d108bf0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -1246,22 +1246,20 @@ void ConnectionManager::updateRunAhead(Int oldRunAhead, Int frameRate, Bool didS // if the minimum fps is within 10% of the desired framerate, then keep the current minimum fps. minFps = frameRate; } - if (minFps < 5) { - minFps = 5; // Absolutely do not run below 5 fps. - } - if (minFps > TheGlobalData->m_framesPerSecondLimit) { - minFps = TheGlobalData->m_framesPerSecondLimit; // Cap to 30 FPS. - } + + // TheSuperHackers @info this clamps the logic time scale fps in network games + minFps = clamp(MIN_LOGIC_FRAMES, minFps, TheGlobalData->m_framesPerSecondLimit); DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("ConnectionManager::updateRunAhead - minFps after adjustment is %d", minFps)); - Int newRunAhead = (Int)((getMaximumLatency() / 2.0) * (Real)minFps); - newRunAhead += (newRunAhead * TheGlobalData->m_networkRunAheadSlack) / 100; // Add in 10% of slack to the run ahead in case of network hiccups. - if (newRunAhead < MIN_RUNAHEAD) { - newRunAhead = MIN_RUNAHEAD; // make sure its at least MIN_RUNAHEAD. - } - if (newRunAhead > (MAX_FRAMES_AHEAD / 2)) { - newRunAhead = MAX_FRAMES_AHEAD / 2; // dont let run ahead get out of hand. - } + // TheSuperHackers @bugfix Mauller 21/08/2025 calculate the runahead so it always follows the latency + // The runahead should always be rounded up to the next integer value to prevent variations in latency from causing stutter + // The network slack pushes the runahead up to the next value when the latency is within the slack percentage of the current runahead + const Real runAheadSlackScale = 1.0f + ( (Real)TheGlobalData->m_networkRunAheadSlack / 100.0f ); + Int newRunAhead = ceilf( getMaximumLatency() * runAheadSlackScale * (Real)minFps ); + + // TheSuperHackers @info if the runahead goes below 3 logic frames it can start to introduce stutter + // We also limit the upper range of the runahead to prevent it getting out of hand + newRunAhead = clamp(MIN_RUNAHEAD, newRunAhead, MAX_FRAMES_AHEAD / 2); NetRunAheadCommandMsg *msg = newInstance(NetRunAheadCommandMsg); msg->setPlayerID(m_localSlot); @@ -1361,24 +1359,15 @@ void ConnectionManager::updateRunAhead(Int oldRunAhead, Int frameRate, Bool didS } Real ConnectionManager::getMaximumLatency() { - // This works for 2 player games because the latency for the packet router is always 0. - Real lat1 = 0.0; - Real lat2 = 0.0; + Real Maxlat = 0.0f; for (Int i = 0; i < MAX_SLOTS; ++i) { - if (isPlayerConnected(i)) { - if (m_latencyAverages[i] != 0.0) { - if (m_latencyAverages[i] > lat1) { - lat2 = lat1; - lat1 = m_latencyAverages[i]; - } else if (m_latencyAverages[i] > lat2) { - lat2 = m_latencyAverages[i]; - } - } + if (isPlayerConnected(i) && m_latencyAverages[i] > Maxlat) { + Maxlat = m_latencyAverages[i]; } } - return (lat1 + lat2); + return Maxlat; } void ConnectionManager::getMinimumFps(Int &minFps, Int &minFpsPlayer) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/NetworkUtil.cpp index 21daa5b7b1b..bd63851cb44 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -29,6 +29,7 @@ // TheSuperHackers @tweak Mauller 26/08/2025 reduce the minimum runahead from 10 // This lets network games run at latencies down to 133ms when the network conditions allow +Int MIN_LOGIC_FRAMES = 5; Int MAX_FRAMES_AHEAD = 128; Int MIN_RUNAHEAD = 4; Int FRAME_DATA_LENGTH = (MAX_FRAMES_AHEAD+1)*2; From 070e3f66247858bb6e621745c8035723a602516c Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Tue, 9 Sep 2025 08:18:40 +0200 Subject: [PATCH 2/2] Change Maxlat variable to camelCase --- .../GameEngine/Source/GameNetwork/ConnectionManager.cpp | 8 ++++---- .../GameEngine/Source/GameNetwork/ConnectionManager.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp index 607c81d138a..18c199a900e 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -1359,15 +1359,15 @@ void ConnectionManager::updateRunAhead(Int oldRunAhead, Int frameRate, Bool didS } Real ConnectionManager::getMaximumLatency() { - Real Maxlat = 0.0f; + Real maxLatency = 0.0f; for (Int i = 0; i < MAX_SLOTS; ++i) { - if (isPlayerConnected(i) && m_latencyAverages[i] > Maxlat) { - Maxlat = m_latencyAverages[i]; + if (isPlayerConnected(i) && m_latencyAverages[i] > maxLatency) { + maxLatency = m_latencyAverages[i]; } } - return Maxlat; + return maxLatency; } void ConnectionManager::getMinimumFps(Int &minFps, Int &minFpsPlayer) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp index aff7d108bf0..b256cd61143 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -1359,15 +1359,15 @@ void ConnectionManager::updateRunAhead(Int oldRunAhead, Int frameRate, Bool didS } Real ConnectionManager::getMaximumLatency() { - Real Maxlat = 0.0f; + Real maxLatency = 0.0f; for (Int i = 0; i < MAX_SLOTS; ++i) { - if (isPlayerConnected(i) && m_latencyAverages[i] > Maxlat) { - Maxlat = m_latencyAverages[i]; + if (isPlayerConnected(i) && m_latencyAverages[i] > maxLatency) { + maxLatency = m_latencyAverages[i]; } } - return Maxlat; + return maxLatency; } void ConnectionManager::getMinimumFps(Int &minFps, Int &minFpsPlayer) {