Skip to content

Commit bc0d447

Browse files
authored
bugfix(network): Fix runahead logic update to better follow the maximum network latency and avoid stutters and slow downs (TheSuperHackers#1482)
1 parent ca3d6e6 commit bc0d447

File tree

8 files changed

+38
-56
lines changed

8 files changed

+38
-56
lines changed

Generals/Code/GameEngine/Include/GameNetwork/ConnectionManager.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class ConnectionManager
174174

175175
// void doPerFrameMetrics(UnsignedInt frame);
176176
void getMinimumFps(Int &minFps, Int &minFpsPlayer); ///< Returns the smallest FPS in the m_fpsAverages list.
177-
Real getMaximumLatency(); ///< This actually sums the two biggest latencies in the m_latencyAverages list.
177+
Real getMaximumLatency(); ///< Returns the highest average latency between players.
178178

179179
void requestFrameDataResend(Int playerID, UnsignedInt frame); ///< request of this player that he send the specified frame's data.
180180

Generals/Code/GameEngine/Include/GameNetwork/NetworkDefs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ static const Int WOL_NAME_LEN = 64;
3636
/// Max number of commands per frame
3737
static const Int MAX_COMMANDS = 256;
3838

39+
extern Int MIN_LOGIC_FRAMES;
3940
extern Int MAX_FRAMES_AHEAD;
4041
extern Int MIN_RUNAHEAD;
4142

Generals/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,22 +1246,20 @@ void ConnectionManager::updateRunAhead(Int oldRunAhead, Int frameRate, Bool didS
12461246
// if the minimum fps is within 10% of the desired framerate, then keep the current minimum fps.
12471247
minFps = frameRate;
12481248
}
1249-
if (minFps < 5) {
1250-
minFps = 5; // Absolutely do not run below 5 fps.
1251-
}
1252-
if (minFps > TheGlobalData->m_framesPerSecondLimit) {
1253-
minFps = TheGlobalData->m_framesPerSecondLimit; // Cap to 30 FPS.
1254-
}
1249+
1250+
// TheSuperHackers @info this clamps the logic time scale fps in network games
1251+
minFps = clamp<Int>(MIN_LOGIC_FRAMES, minFps, TheGlobalData->m_framesPerSecondLimit);
12551252
DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("ConnectionManager::updateRunAhead - minFps after adjustment is %d", minFps));
1256-
Int newRunAhead = (Int)((getMaximumLatency() / 2.0) * (Real)minFps);
1257-
newRunAhead += (newRunAhead * TheGlobalData->m_networkRunAheadSlack) / 100; // Add in 10% of slack to the run ahead in case of network hiccups.
1258-
if (newRunAhead < MIN_RUNAHEAD) {
1259-
newRunAhead = MIN_RUNAHEAD; // make sure its at least MIN_RUNAHEAD.
1260-
}
12611253

1262-
if (newRunAhead > (MAX_FRAMES_AHEAD / 2)) {
1263-
newRunAhead = MAX_FRAMES_AHEAD / 2; // dont let run ahead get out of hand.
1264-
}
1254+
// TheSuperHackers @bugfix Mauller 21/08/2025 calculate the runahead so it always follows the latency
1255+
// The runahead should always be rounded up to the next integer value to prevent variations in latency from causing stutter
1256+
// The network slack pushes the runahead up to the next value when the latency is within the slack percentage of the current runahead
1257+
const Real runAheadSlackScale = 1.0f + ( (Real)TheGlobalData->m_networkRunAheadSlack / 100.0f );
1258+
Int newRunAhead = ceilf( getMaximumLatency() * runAheadSlackScale * (Real)minFps );
1259+
1260+
// TheSuperHackers @info if the runahead goes below 3 logic frames it can start to introduce stutter
1261+
// We also limit the upper range of the runahead to prevent it getting out of hand
1262+
newRunAhead = clamp<Int>(MIN_RUNAHEAD, newRunAhead, MAX_FRAMES_AHEAD / 2);
12651263

12661264
NetRunAheadCommandMsg *msg = newInstance(NetRunAheadCommandMsg);
12671265
msg->setPlayerID(m_localSlot);
@@ -1361,24 +1359,15 @@ void ConnectionManager::updateRunAhead(Int oldRunAhead, Int frameRate, Bool didS
13611359
}
13621360

13631361
Real ConnectionManager::getMaximumLatency() {
1364-
// This works for 2 player games because the latency for the packet router is always 0.
1365-
Real lat1 = 0.0;
1366-
Real lat2 = 0.0;
1362+
Real maxLatency = 0.0f;
13671363

13681364
for (Int i = 0; i < MAX_SLOTS; ++i) {
1369-
if (isPlayerConnected(i)) {
1370-
if (m_latencyAverages[i] != 0.0) {
1371-
if (m_latencyAverages[i] > lat1) {
1372-
lat2 = lat1;
1373-
lat1 = m_latencyAverages[i];
1374-
} else if (m_latencyAverages[i] > lat2) {
1375-
lat2 = m_latencyAverages[i];
1376-
}
1377-
}
1365+
if (isPlayerConnected(i) && m_latencyAverages[i] > maxLatency) {
1366+
maxLatency = m_latencyAverages[i];
13781367
}
13791368
}
13801369

1381-
return (lat1 + lat2);
1370+
return maxLatency;
13821371
}
13831372

13841373
void ConnectionManager::getMinimumFps(Int &minFps, Int &minFpsPlayer) {

Generals/Code/GameEngine/Source/GameNetwork/NetworkUtil.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
// TheSuperHackers @tweak Mauller 26/08/2025 reduce the minimum runahead from 10
3131
// This lets network games run at latencies down to 133ms when the network conditions allow
32+
Int MIN_LOGIC_FRAMES = 5;
3233
Int MAX_FRAMES_AHEAD = 128;
3334
Int MIN_RUNAHEAD = 4;
3435
Int FRAME_DATA_LENGTH = (MAX_FRAMES_AHEAD+1)*2;

GeneralsMD/Code/GameEngine/Include/GameNetwork/ConnectionManager.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class ConnectionManager
174174

175175
// void doPerFrameMetrics(UnsignedInt frame);
176176
void getMinimumFps(Int &minFps, Int &minFpsPlayer); ///< Returns the smallest FPS in the m_fpsAverages list.
177-
Real getMaximumLatency(); ///< This actually sums the two biggest latencies in the m_latencyAverages list.
177+
Real getMaximumLatency(); ///< Returns the highest average latency between players.
178178

179179
void requestFrameDataResend(Int playerID, UnsignedInt frame); ///< request of this player that he send the specified frame's data.
180180

GeneralsMD/Code/GameEngine/Include/GameNetwork/NetworkDefs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ static const Int WOL_NAME_LEN = 64;
3636
/// Max number of commands per frame
3737
static const Int MAX_COMMANDS = 256;
3838

39+
extern Int MIN_LOGIC_FRAMES;
3940
extern Int MAX_FRAMES_AHEAD;
4041
extern Int MIN_RUNAHEAD;
4142

GeneralsMD/Code/GameEngine/Source/GameNetwork/ConnectionManager.cpp

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,22 +1246,20 @@ void ConnectionManager::updateRunAhead(Int oldRunAhead, Int frameRate, Bool didS
12461246
// if the minimum fps is within 10% of the desired framerate, then keep the current minimum fps.
12471247
minFps = frameRate;
12481248
}
1249-
if (minFps < 5) {
1250-
minFps = 5; // Absolutely do not run below 5 fps.
1251-
}
1252-
if (minFps > TheGlobalData->m_framesPerSecondLimit) {
1253-
minFps = TheGlobalData->m_framesPerSecondLimit; // Cap to 30 FPS.
1254-
}
1249+
1250+
// TheSuperHackers @info this clamps the logic time scale fps in network games
1251+
minFps = clamp<Int>(MIN_LOGIC_FRAMES, minFps, TheGlobalData->m_framesPerSecondLimit);
12551252
DEBUG_LOG_LEVEL(DEBUG_LEVEL_NET, ("ConnectionManager::updateRunAhead - minFps after adjustment is %d", minFps));
1256-
Int newRunAhead = (Int)((getMaximumLatency() / 2.0) * (Real)minFps);
1257-
newRunAhead += (newRunAhead * TheGlobalData->m_networkRunAheadSlack) / 100; // Add in 10% of slack to the run ahead in case of network hiccups.
1258-
if (newRunAhead < MIN_RUNAHEAD) {
1259-
newRunAhead = MIN_RUNAHEAD; // make sure its at least MIN_RUNAHEAD.
1260-
}
12611253

1262-
if (newRunAhead > (MAX_FRAMES_AHEAD / 2)) {
1263-
newRunAhead = MAX_FRAMES_AHEAD / 2; // dont let run ahead get out of hand.
1264-
}
1254+
// TheSuperHackers @bugfix Mauller 21/08/2025 calculate the runahead so it always follows the latency
1255+
// The runahead should always be rounded up to the next integer value to prevent variations in latency from causing stutter
1256+
// The network slack pushes the runahead up to the next value when the latency is within the slack percentage of the current runahead
1257+
const Real runAheadSlackScale = 1.0f + ( (Real)TheGlobalData->m_networkRunAheadSlack / 100.0f );
1258+
Int newRunAhead = ceilf( getMaximumLatency() * runAheadSlackScale * (Real)minFps );
1259+
1260+
// TheSuperHackers @info if the runahead goes below 3 logic frames it can start to introduce stutter
1261+
// We also limit the upper range of the runahead to prevent it getting out of hand
1262+
newRunAhead = clamp<Int>(MIN_RUNAHEAD, newRunAhead, MAX_FRAMES_AHEAD / 2);
12651263

12661264
NetRunAheadCommandMsg *msg = newInstance(NetRunAheadCommandMsg);
12671265
msg->setPlayerID(m_localSlot);
@@ -1361,24 +1359,15 @@ void ConnectionManager::updateRunAhead(Int oldRunAhead, Int frameRate, Bool didS
13611359
}
13621360

13631361
Real ConnectionManager::getMaximumLatency() {
1364-
// This works for 2 player games because the latency for the packet router is always 0.
1365-
Real lat1 = 0.0;
1366-
Real lat2 = 0.0;
1362+
Real maxLatency = 0.0f;
13671363

13681364
for (Int i = 0; i < MAX_SLOTS; ++i) {
1369-
if (isPlayerConnected(i)) {
1370-
if (m_latencyAverages[i] != 0.0) {
1371-
if (m_latencyAverages[i] > lat1) {
1372-
lat2 = lat1;
1373-
lat1 = m_latencyAverages[i];
1374-
} else if (m_latencyAverages[i] > lat2) {
1375-
lat2 = m_latencyAverages[i];
1376-
}
1377-
}
1365+
if (isPlayerConnected(i) && m_latencyAverages[i] > maxLatency) {
1366+
maxLatency = m_latencyAverages[i];
13781367
}
13791368
}
13801369

1381-
return (lat1 + lat2);
1370+
return maxLatency;
13821371
}
13831372

13841373
void ConnectionManager::getMinimumFps(Int &minFps, Int &minFpsPlayer) {

GeneralsMD/Code/GameEngine/Source/GameNetwork/NetworkUtil.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
// TheSuperHackers @tweak Mauller 26/08/2025 reduce the minimum runahead from 10
3131
// This lets network games run at latencies down to 133ms when the network conditions allow
32+
Int MIN_LOGIC_FRAMES = 5;
3233
Int MAX_FRAMES_AHEAD = 128;
3334
Int MIN_RUNAHEAD = 4;
3435
Int FRAME_DATA_LENGTH = (MAX_FRAMES_AHEAD+1)*2;

0 commit comments

Comments
 (0)