From 3685b1710eb6b98140bb6df1ce0b4d4c6c67c45f Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 6 Jun 2026 17:36:40 +0200 Subject: [PATCH 1/6] Added experimental overflow sorting. --- .../Source/GameNetwork/NetCommandList.cpp | 109 +++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp index 579b8578c72..577d5db0b21 100644 --- a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp @@ -122,6 +122,74 @@ void NetCommandList::reset() { m_lastMessageInserted = nullptr; } +static Bool shouldInsertAfterLastInsertedMessage(NetCommandRef* lastMessageInserted, NetCommandRef* msg) +{ + if (lastMessageInserted->getCommand()->getNetCommandType() != msg->getCommand()->getNetCommandType()) { + return false; + } + + if (lastMessageInserted->getCommand()->getPlayerID() != msg->getCommand()->getPlayerID()) { + return false; + } + + { + const UnsignedShort prevID = lastMessageInserted->getCommand()->getID(); + const UnsignedShort curID = msg->getCommand()->getID(); + + if (DoesCommandRequireACommandID(lastMessageInserted->getCommand()->getNetCommandType()) + || DoesCommandRequireACommandID(msg->getCommand()->getNetCommandType())) { + const Bool wrapAround = std::abs(static_cast(curID) - static_cast(prevID)) > MAXSHORT; + if (wrapAround) { + if (prevID <= curID) { + return false; + } + } else { + if (prevID >= curID) { + return false; + } + } + } else { + if (prevID >= curID) { + return false; + } + } + } + + NetCommandRef* theNext = lastMessageInserted->getNext(); + if (theNext) { + if (theNext->getCommand()->getNetCommandType() <= msg->getCommand()->getNetCommandType()) { + return false; + } + + if (theNext->getCommand()->getPlayerID() <= msg->getCommand()->getPlayerID()) { + return false; + } + + const UnsignedShort curID = msg->getCommand()->getID(); + const UnsignedShort nextID = theNext->getCommand()->getID(); + + if (DoesCommandRequireACommandID(theNext->getCommand()->getNetCommandType()) + || DoesCommandRequireACommandID(msg->getCommand()->getNetCommandType())) { + const Bool wrapAround = std::abs(static_cast(nextID) - static_cast(curID)) > MAXSHORT; + if (wrapAround) { + if (curID >= nextID) { + return false; + } + } else { + if (curID <= nextID) { + return false; + } + } + } else { + if (curID >= nextID) { + return false; + } + } + } + + return true; +} + /** * Insert sorts msg. Assumes that all the previous message inserts were done using this function. * The message is sorted in based first on command type, then player id, and then command id. @@ -155,13 +223,15 @@ NetCommandRef * NetCommandList::addMessage(NetCommandRef *&msg) { // So saving the placement of the last message inserted can give us a huge boost in // efficiency. NetCommandRef *theNext = m_lastMessageInserted->getNext(); + //* if ((m_lastMessageInserted->getCommand()->getNetCommandType() == msg->getCommand()->getNetCommandType()) && (m_lastMessageInserted->getCommand()->getPlayerID() == msg->getCommand()->getPlayerID()) && (m_lastMessageInserted->getCommand()->getID() < msg->getCommand()->getID()) && ((theNext == nullptr) || ((theNext->getCommand()->getNetCommandType() > msg->getCommand()->getNetCommandType()) || (theNext->getCommand()->getPlayerID() > msg->getCommand()->getPlayerID()) || (theNext->getCommand()->getID() > msg->getCommand()->getID())))) { - + //*/ + //if (shouldInsertAfterLastInsertedMessage(m_lastMessageInserted, msg)) { // Make sure this command isn't already in the list. if (isEqualCommandMsg(m_lastMessageInserted->getCommand(), msg->getCommand())) { @@ -281,9 +351,44 @@ NetCommandRef * NetCommandList::addMessage(NetCommandRef *&msg) { // Find the position within the player's section based on the command ID. // If the command type doesn't require a command ID, sort by whatever it should be sorted by. - while ((tempmsg != nullptr) && (msg->getCommand()->getNetCommandType() == tempmsg->getCommand()->getNetCommandType()) && (msg->getCommand()->getPlayerID() == tempmsg->getCommand()->getPlayerID()) && (msg->getCommand()->getSortNumber() > tempmsg->getCommand()->getSortNumber())) { + /* + for (const NetCommandMsg* newMessageCmd = msg->getCommand(); tempmsg != nullptr; tempmsg = tempmsg->getNext()) { + if (newMessageCmd->getNetCommandType() != tempmsg->getCommand()->getNetCommandType()) + break; + if (newMessageCmd->getPlayerID() != tempmsg->getCommand()->getPlayerID()) + break; + + const UnsignedShort prevID = tempmsg->getCommand()->getSortNumber(); + const UnsignedShort curID = msg->getCommand()->getSortNumber(); + + if (DoesCommandRequireACommandID(newMessageCmd->getNetCommandType()) + || DoesCommandRequireACommandID(tempmsg->getCommand()->getNetCommandType())) { + const Bool wrapAround = std::abs(static_cast(curID) - static_cast(prevID)) > MAXSHORT; + if (wrapAround) { + if (prevID <= curID) { + break; + } + } else { + if (prevID >= curID) { + break; + } + } + } else { + if (prevID >= curID) { + break; + } + } + } + //*/ + + //* + while ((tempmsg != nullptr) + && (msg->getCommand()->getNetCommandType() == tempmsg->getCommand()->getNetCommandType()) + && (msg->getCommand()->getPlayerID() == tempmsg->getCommand()->getPlayerID()) + && (msg->getCommand()->getSortNumber() > tempmsg->getCommand()->getSortNumber())) { tempmsg = tempmsg->getNext(); } + //*/ if (tempmsg == nullptr) { // Make sure this command isn't already in the list. From e60c7b5d0ac4666e23ce3eb08e9634d58ff1c5fa Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 6 Jun 2026 17:39:07 +0200 Subject: [PATCH 2/6] Revert "Added experimental overflow sorting." This reverts commit 3685b1710eb6b98140bb6df1ce0b4d4c6c67c45f. --- .../Source/GameNetwork/NetCommandList.cpp | 109 +----------------- 1 file changed, 2 insertions(+), 107 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp index 577d5db0b21..579b8578c72 100644 --- a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp @@ -122,74 +122,6 @@ void NetCommandList::reset() { m_lastMessageInserted = nullptr; } -static Bool shouldInsertAfterLastInsertedMessage(NetCommandRef* lastMessageInserted, NetCommandRef* msg) -{ - if (lastMessageInserted->getCommand()->getNetCommandType() != msg->getCommand()->getNetCommandType()) { - return false; - } - - if (lastMessageInserted->getCommand()->getPlayerID() != msg->getCommand()->getPlayerID()) { - return false; - } - - { - const UnsignedShort prevID = lastMessageInserted->getCommand()->getID(); - const UnsignedShort curID = msg->getCommand()->getID(); - - if (DoesCommandRequireACommandID(lastMessageInserted->getCommand()->getNetCommandType()) - || DoesCommandRequireACommandID(msg->getCommand()->getNetCommandType())) { - const Bool wrapAround = std::abs(static_cast(curID) - static_cast(prevID)) > MAXSHORT; - if (wrapAround) { - if (prevID <= curID) { - return false; - } - } else { - if (prevID >= curID) { - return false; - } - } - } else { - if (prevID >= curID) { - return false; - } - } - } - - NetCommandRef* theNext = lastMessageInserted->getNext(); - if (theNext) { - if (theNext->getCommand()->getNetCommandType() <= msg->getCommand()->getNetCommandType()) { - return false; - } - - if (theNext->getCommand()->getPlayerID() <= msg->getCommand()->getPlayerID()) { - return false; - } - - const UnsignedShort curID = msg->getCommand()->getID(); - const UnsignedShort nextID = theNext->getCommand()->getID(); - - if (DoesCommandRequireACommandID(theNext->getCommand()->getNetCommandType()) - || DoesCommandRequireACommandID(msg->getCommand()->getNetCommandType())) { - const Bool wrapAround = std::abs(static_cast(nextID) - static_cast(curID)) > MAXSHORT; - if (wrapAround) { - if (curID >= nextID) { - return false; - } - } else { - if (curID <= nextID) { - return false; - } - } - } else { - if (curID >= nextID) { - return false; - } - } - } - - return true; -} - /** * Insert sorts msg. Assumes that all the previous message inserts were done using this function. * The message is sorted in based first on command type, then player id, and then command id. @@ -223,15 +155,13 @@ NetCommandRef * NetCommandList::addMessage(NetCommandRef *&msg) { // So saving the placement of the last message inserted can give us a huge boost in // efficiency. NetCommandRef *theNext = m_lastMessageInserted->getNext(); - //* if ((m_lastMessageInserted->getCommand()->getNetCommandType() == msg->getCommand()->getNetCommandType()) && (m_lastMessageInserted->getCommand()->getPlayerID() == msg->getCommand()->getPlayerID()) && (m_lastMessageInserted->getCommand()->getID() < msg->getCommand()->getID()) && ((theNext == nullptr) || ((theNext->getCommand()->getNetCommandType() > msg->getCommand()->getNetCommandType()) || (theNext->getCommand()->getPlayerID() > msg->getCommand()->getPlayerID()) || (theNext->getCommand()->getID() > msg->getCommand()->getID())))) { - //*/ - //if (shouldInsertAfterLastInsertedMessage(m_lastMessageInserted, msg)) { + // Make sure this command isn't already in the list. if (isEqualCommandMsg(m_lastMessageInserted->getCommand(), msg->getCommand())) { @@ -351,44 +281,9 @@ NetCommandRef * NetCommandList::addMessage(NetCommandRef *&msg) { // Find the position within the player's section based on the command ID. // If the command type doesn't require a command ID, sort by whatever it should be sorted by. - /* - for (const NetCommandMsg* newMessageCmd = msg->getCommand(); tempmsg != nullptr; tempmsg = tempmsg->getNext()) { - if (newMessageCmd->getNetCommandType() != tempmsg->getCommand()->getNetCommandType()) - break; - if (newMessageCmd->getPlayerID() != tempmsg->getCommand()->getPlayerID()) - break; - - const UnsignedShort prevID = tempmsg->getCommand()->getSortNumber(); - const UnsignedShort curID = msg->getCommand()->getSortNumber(); - - if (DoesCommandRequireACommandID(newMessageCmd->getNetCommandType()) - || DoesCommandRequireACommandID(tempmsg->getCommand()->getNetCommandType())) { - const Bool wrapAround = std::abs(static_cast(curID) - static_cast(prevID)) > MAXSHORT; - if (wrapAround) { - if (prevID <= curID) { - break; - } - } else { - if (prevID >= curID) { - break; - } - } - } else { - if (prevID >= curID) { - break; - } - } - } - //*/ - - //* - while ((tempmsg != nullptr) - && (msg->getCommand()->getNetCommandType() == tempmsg->getCommand()->getNetCommandType()) - && (msg->getCommand()->getPlayerID() == tempmsg->getCommand()->getPlayerID()) - && (msg->getCommand()->getSortNumber() > tempmsg->getCommand()->getSortNumber())) { + while ((tempmsg != nullptr) && (msg->getCommand()->getNetCommandType() == tempmsg->getCommand()->getNetCommandType()) && (msg->getCommand()->getPlayerID() == tempmsg->getCommand()->getPlayerID()) && (msg->getCommand()->getSortNumber() > tempmsg->getCommand()->getSortNumber())) { tempmsg = tempmsg->getNext(); } - //*/ if (tempmsg == nullptr) { // Make sure this command isn't already in the list. From 5efd806fa4e5da1f7950e8034d9bc965f025c0d5 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 6 Jun 2026 17:42:47 +0200 Subject: [PATCH 3/6] Added test code. --- .../GameLogic/System/GameLogicDispatch.cpp | 26 +++++++++++++++++++ .../Source/GameNetwork/NetworkUtil.cpp | 22 +++++++++++++--- .../GameEngine/Include/Common/MessageStream.h | 2 ++ .../Source/GameLogic/System/GameLogic.cpp | 12 +++++++++ 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 7a202c7f8f7..603dde43c5b 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -422,6 +422,32 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) GameMessage::Type msgType = msg->getType(); switch( msgType ) { + case GameMessage::MSG_TEST_SEQUENTIAL_ORDER: + { + static int s_seq[MAX_PLAYER_COUNT] = { 0 }; + static int count = 15'000; + + if (s_seq[msg->getPlayerIndex()] == count) + { + s_seq[msg->getPlayerIndex()] = 0; + } + + const int seq = msg->getArgument(0)->integer; + if (s_seq[msg->getPlayerIndex()] + 1 != seq) + { + const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE( + "GUI:SequentialOrderMismatch", L"Frame:%d Player:%ls --- Expected seq %d, Actual seq %d!"); + TheInGameUI->message(mismatchDetailsStr, TheGameLogic->getFrame(), msgPlayer->getPlayerDisplayName().str(), s_seq[msg->getPlayerIndex()] + 1, seq); + + s_seq[msg->getPlayerIndex()] = seq; + } + else + { + s_seq[msg->getPlayerIndex()] += 1; + } + } + break; + //--------------------------------------------------------------------------------------------- case GameMessage::MSG_NEW_GAME: { diff --git a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp index d48280759b6..a7b58a8b50c 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -25,6 +25,8 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine +#include "GameClient/GameText.h" +#include "GameClient/InGameUI.h" #include "GameNetwork/networkutil.h" #ifdef DEBUG_LOGGING @@ -102,10 +104,22 @@ UnsignedInt ResolveIP(AsciiString host) /** * Returns the next network command ID. */ -UnsignedShort GenerateNextCommandID() { - static UnsignedShort commandID = 64000; - ++commandID; - return commandID; +static UnsignedShort s_commandID = 0; +UnsignedShort GenerateNextCommandID() +{ + if (s_commandID == MAXUINT16) + { + const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE( + "NETWORK:COMMANDIDOVERFLOW", L"The command ID is about to overflow"); + TheInGameUI->message(mismatchDetailsStr); + } + + return s_commandID++; +} + +void ResetCommandID() +{ + s_commandID = 0; } /** diff --git a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h index fc7945a3156..c3a204c4e3c 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h @@ -614,6 +614,8 @@ class GameMessage : public MemoryPoolObject MSG_DEBUG_KILL_OBJECT, #endif + MSG_TEST_SEQUENTIAL_ORDER, + //********************************************************************************************************* MSG_END_NETWORK_MESSAGES = 1999, ///< MARKER TO DELINEATE MESSAGES THAT GO OVER THE NETWORK //********************************************************************************************************* diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 622aeb5da10..97126091f8e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3738,6 +3738,18 @@ void GameLogic::update() TheTerrainLogic->UPDATE(); } + if (/*m_frame >= 100 && */getGameMode() != GAME_SHELL && getGameMode() != GAME_NONE) { + static int interval = 1; + static int count = 15; + if (interval > 0 && m_frame % interval == 0) { + for (int i = 1; i <= count; ++i) { + GameMessage* msg = TheMessageStream->appendMessage(GameMessage::MSG_TEST_SEQUENTIAL_ORDER); + msg->appendIntegerArgument(i + ((m_frame - 0/*100*/) % 1000) * count); + //msg->appendIntegerArgument(m_frame); + } + } + } + // force CRC calculation, so we can keep a cache of the last N CRCs. We do this right where the recorder // would be getting the CRC anyway, so replays can get the CRCs from the exact instant in time as the original. Bool isMPGameOrReplay = (TheRecorder && TheRecorder->isMultiplayer() && getGameMode() != GAME_SHELL && getGameMode() != GAME_NONE); From 33f90dd2315ac8d3139607bff3bd066de22dd9d9 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 6 Jun 2026 17:43:02 +0200 Subject: [PATCH 4/6] Revert "Added test code." This reverts commit 5efd806fa4e5da1f7950e8034d9bc965f025c0d5. --- .../GameLogic/System/GameLogicDispatch.cpp | 26 ------------------- .../Source/GameNetwork/NetworkUtil.cpp | 22 +++------------- .../GameEngine/Include/Common/MessageStream.h | 2 -- .../Source/GameLogic/System/GameLogic.cpp | 12 --------- 4 files changed, 4 insertions(+), 58 deletions(-) diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 603dde43c5b..7a202c7f8f7 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -422,32 +422,6 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) GameMessage::Type msgType = msg->getType(); switch( msgType ) { - case GameMessage::MSG_TEST_SEQUENTIAL_ORDER: - { - static int s_seq[MAX_PLAYER_COUNT] = { 0 }; - static int count = 15'000; - - if (s_seq[msg->getPlayerIndex()] == count) - { - s_seq[msg->getPlayerIndex()] = 0; - } - - const int seq = msg->getArgument(0)->integer; - if (s_seq[msg->getPlayerIndex()] + 1 != seq) - { - const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE( - "GUI:SequentialOrderMismatch", L"Frame:%d Player:%ls --- Expected seq %d, Actual seq %d!"); - TheInGameUI->message(mismatchDetailsStr, TheGameLogic->getFrame(), msgPlayer->getPlayerDisplayName().str(), s_seq[msg->getPlayerIndex()] + 1, seq); - - s_seq[msg->getPlayerIndex()] = seq; - } - else - { - s_seq[msg->getPlayerIndex()] += 1; - } - } - break; - //--------------------------------------------------------------------------------------------- case GameMessage::MSG_NEW_GAME: { diff --git a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp index a7b58a8b50c..d48280759b6 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -25,8 +25,6 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine -#include "GameClient/GameText.h" -#include "GameClient/InGameUI.h" #include "GameNetwork/networkutil.h" #ifdef DEBUG_LOGGING @@ -104,22 +102,10 @@ UnsignedInt ResolveIP(AsciiString host) /** * Returns the next network command ID. */ -static UnsignedShort s_commandID = 0; -UnsignedShort GenerateNextCommandID() -{ - if (s_commandID == MAXUINT16) - { - const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE( - "NETWORK:COMMANDIDOVERFLOW", L"The command ID is about to overflow"); - TheInGameUI->message(mismatchDetailsStr); - } - - return s_commandID++; -} - -void ResetCommandID() -{ - s_commandID = 0; +UnsignedShort GenerateNextCommandID() { + static UnsignedShort commandID = 64000; + ++commandID; + return commandID; } /** diff --git a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h index c3a204c4e3c..fc7945a3156 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h @@ -614,8 +614,6 @@ class GameMessage : public MemoryPoolObject MSG_DEBUG_KILL_OBJECT, #endif - MSG_TEST_SEQUENTIAL_ORDER, - //********************************************************************************************************* MSG_END_NETWORK_MESSAGES = 1999, ///< MARKER TO DELINEATE MESSAGES THAT GO OVER THE NETWORK //********************************************************************************************************* diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 97126091f8e..622aeb5da10 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3738,18 +3738,6 @@ void GameLogic::update() TheTerrainLogic->UPDATE(); } - if (/*m_frame >= 100 && */getGameMode() != GAME_SHELL && getGameMode() != GAME_NONE) { - static int interval = 1; - static int count = 15; - if (interval > 0 && m_frame % interval == 0) { - for (int i = 1; i <= count; ++i) { - GameMessage* msg = TheMessageStream->appendMessage(GameMessage::MSG_TEST_SEQUENTIAL_ORDER); - msg->appendIntegerArgument(i + ((m_frame - 0/*100*/) % 1000) * count); - //msg->appendIntegerArgument(m_frame); - } - } - } - // force CRC calculation, so we can keep a cache of the last N CRCs. We do this right where the recorder // would be getting the CRC anyway, so replays can get the CRCs from the exact instant in time as the original. Bool isMPGameOrReplay = (TheRecorder && TheRecorder->isMultiplayer() && getGameMode() != GAME_SHELL && getGameMode() != GAME_NONE); From 5f2134d3b72258d074c0a154219317e08f83d5d1 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 6 Jun 2026 17:45:40 +0200 Subject: [PATCH 5/6] Added code to reset command id. --- .../Include/GameNetwork/networkutil.h | 1 + .../Source/GameNetwork/NetCommandList.cpp | 4 +++ .../GameEngine/Source/GameNetwork/Network.cpp | 26 ++++++++++++++++--- .../Source/GameNetwork/NetworkUtil.cpp | 13 +++++++--- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/Core/GameEngine/Include/GameNetwork/networkutil.h b/Core/GameEngine/Include/GameNetwork/networkutil.h index 722c59fd7ff..6f570f37fcd 100644 --- a/Core/GameEngine/Include/GameNetwork/networkutil.h +++ b/Core/GameEngine/Include/GameNetwork/networkutil.h @@ -30,6 +30,7 @@ UnsignedInt AssembleIp(UnsignedByte a, UnsignedByte b, UnsignedByte c, UnsignedByte d); UnsignedInt ResolveIP(AsciiString host); UnsignedShort GenerateNextCommandID(); +void ResetCommandID(); Bool DoesCommandRequireACommandID(NetCommandType type); Bool CommandRequiresAck(const NetCommandMsg *msg); Bool CommandRequiresDirectSend(const NetCommandMsg *msg); diff --git a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp index 579b8578c72..e0157281b30 100644 --- a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp @@ -125,6 +125,10 @@ void NetCommandList::reset() { /** * Insert sorts msg. Assumes that all the previous message inserts were done using this function. * The message is sorted in based first on command type, then player id, and then command id. + * + * TheSuperHackers @info Caball009 06/06/2026 The command id may overflow and the sorting in this function + * does not check against that. Changing the sorting implementation to deal with overflows is not retail compatible, + * unlike resetting the command id. */ NetCommandRef * NetCommandList::addMessage(NetCommandMsg *cmdMsg) { if (cmdMsg == nullptr) { diff --git a/Core/GameEngine/Source/GameNetwork/Network.cpp b/Core/GameEngine/Source/GameNetwork/Network.cpp index b5501e32e26..05577174137 100644 --- a/Core/GameEngine/Source/GameNetwork/Network.cpp +++ b/Core/GameEngine/Source/GameNetwork/Network.cpp @@ -37,6 +37,7 @@ #include "Common/Player.h" #include "Common/PlayerList.h" #include "GameNetwork/NetworkInterface.h" +#include "GameNetwork/networkutil.h" #include "GameNetwork/udp.h" #include "GameNetwork/Transport.h" #include "strtok_r.h" @@ -201,6 +202,7 @@ class Network : public NetworkInterface Int m_frameRate; Int m_lastExecutionFrame; ///< The highest frame number that a command could have been executed on. Int m_lastFrameCompleted; + UnsignedInt m_lastFrameResetCommandID; ///< The last frame the network command ID was reset Bool m_didSelfSlug; __int64 m_perfCountFreq; ///< The frequency of the performance counter. @@ -273,6 +275,8 @@ Network::Network() m_conMgr = nullptr; m_messageWindow = nullptr; + m_lastFrameResetCommandID = 0; + #if defined(RTS_DEBUG) m_networkOn = TRUE; #endif @@ -331,6 +335,7 @@ void Network::init() m_frameRate = 30; m_lastExecutionFrame = m_runAhead - 1; // subtract 1 since we're starting on frame 0 m_lastFrameCompleted = m_runAhead - 1; // subtract 1 since we're starting on frame 0 + m_lastFrameResetCommandID = 0; m_frameDataReady = FALSE; m_isStalling = FALSE; m_didSelfSlug = FALSE; @@ -356,6 +361,7 @@ void Network::init() DEBUG_LOG(("FRAME_DATA_LENGTH = %d", FRAME_DATA_LENGTH)); DEBUG_LOG(("FRAMES_TO_KEEP = %d", FRAMES_TO_KEEP)); + ResetCommandID(); #if defined(RTS_DEBUG) m_networkOn = TRUE; @@ -456,6 +462,7 @@ Bool Network::isMessageTypeWithinNetworkRange(GameMessage::Type type) { void Network::GetCommandsFromCommandList() { GameMessage *msg = TheCommandList->getFirstMessage(); GameMessage *next = nullptr; + while (msg != nullptr) { next = msg->next(); if (isMessageTypeWithinNetworkRange(msg->getType())) { // Is this something we should be sending to the other players? @@ -702,6 +709,19 @@ void Network::update() } #endif + const UnsignedInt frame = TheGameLogic->getFrame(); + + if (m_localStatus == NETLOCALSTATUS_INGAME) { + if (frame + m_runAhead > m_lastExecutionFrame) { + if (frame >= m_lastFrameResetCommandID + MAX_FRAMES_AHEAD) { + // TheSuperHackers @bugfix Caball009 06/06/2026 Reset the network command id to prevent it from overflowing. + // This prevents commands from being sorted incorrectly, which can cause spurious mismatches at low CRC intervals. + m_lastFrameResetCommandID = frame; + ResetCommandID(); + } + } + } + GetCommandsFromCommandList(); // Remove commands from TheCommandList and send them to the connection manager. if (m_conMgr != nullptr) { if (m_localStatus == NETLOCALSTATUS_INGAME) { @@ -718,11 +738,11 @@ void Network::update() endOfGameCheck(); } - if (AllCommandsReady(TheGameLogic->getFrame())) { // If all the commands are ready for the next frame... + if (AllCommandsReady(frame)) { // If all the commands are ready for the next frame... m_conMgr->handleAllCommandsReady(); -// DEBUG_LOG(("Network::update - frame %d is ready", TheGameLogic->getFrame())); +// DEBUG_LOG(("Network::update - frame %d is ready", frame)); if (timeForNewFrame()) { // This needs to come after any other pre-frame execution checks as this changes the timing variables. - RelayCommandsToCommandList(TheGameLogic->getFrame()); // Put the commands for the next frame on TheCommandList. + RelayCommandsToCommandList(frame); // Put the commands for the next frame on TheCommandList. m_frameDataReady = TRUE; // Tell the GameEngine to run the commands for the new frame. } } diff --git a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp index d48280759b6..c73e3ebab54 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -102,10 +102,15 @@ UnsignedInt ResolveIP(AsciiString host) /** * Returns the next network command ID. */ -UnsignedShort GenerateNextCommandID() { - static UnsignedShort commandID = 64000; - ++commandID; - return commandID; +static UnsignedShort s_commandID = 0; +UnsignedShort GenerateNextCommandID() +{ + return s_commandID++; +} + +void ResetCommandID() +{ + s_commandID = 0; } /** From 1bccb3ab3dc31fff54c03b1c168b1d3c3cddb13a Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sun, 7 Jun 2026 02:12:54 +0200 Subject: [PATCH 6/6] Changed implementation. --- Core/GameEngine/Include/GameNetwork/networkutil.h | 1 + Core/GameEngine/Source/GameNetwork/Network.cpp | 11 +++-------- Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp | 5 +++++ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Core/GameEngine/Include/GameNetwork/networkutil.h b/Core/GameEngine/Include/GameNetwork/networkutil.h index 6f570f37fcd..8a9cea28d07 100644 --- a/Core/GameEngine/Include/GameNetwork/networkutil.h +++ b/Core/GameEngine/Include/GameNetwork/networkutil.h @@ -30,6 +30,7 @@ UnsignedInt AssembleIp(UnsignedByte a, UnsignedByte b, UnsignedByte c, UnsignedByte d); UnsignedInt ResolveIP(AsciiString host); UnsignedShort GenerateNextCommandID(); +UnsignedShort GetCommandID(); void ResetCommandID(); Bool DoesCommandRequireACommandID(NetCommandType type); Bool CommandRequiresAck(const NetCommandMsg *msg); diff --git a/Core/GameEngine/Source/GameNetwork/Network.cpp b/Core/GameEngine/Source/GameNetwork/Network.cpp index 05577174137..354c611220f 100644 --- a/Core/GameEngine/Source/GameNetwork/Network.cpp +++ b/Core/GameEngine/Source/GameNetwork/Network.cpp @@ -202,7 +202,6 @@ class Network : public NetworkInterface Int m_frameRate; Int m_lastExecutionFrame; ///< The highest frame number that a command could have been executed on. Int m_lastFrameCompleted; - UnsignedInt m_lastFrameResetCommandID; ///< The last frame the network command ID was reset Bool m_didSelfSlug; __int64 m_perfCountFreq; ///< The frequency of the performance counter. @@ -275,8 +274,6 @@ Network::Network() m_conMgr = nullptr; m_messageWindow = nullptr; - m_lastFrameResetCommandID = 0; - #if defined(RTS_DEBUG) m_networkOn = TRUE; #endif @@ -335,7 +332,6 @@ void Network::init() m_frameRate = 30; m_lastExecutionFrame = m_runAhead - 1; // subtract 1 since we're starting on frame 0 m_lastFrameCompleted = m_runAhead - 1; // subtract 1 since we're starting on frame 0 - m_lastFrameResetCommandID = 0; m_frameDataReady = FALSE; m_isStalling = FALSE; m_didSelfSlug = FALSE; @@ -713,10 +709,9 @@ void Network::update() if (m_localStatus == NETLOCALSTATUS_INGAME) { if (frame + m_runAhead > m_lastExecutionFrame) { - if (frame >= m_lastFrameResetCommandID + MAX_FRAMES_AHEAD) { - // TheSuperHackers @bugfix Caball009 06/06/2026 Reset the network command id to prevent it from overflowing. - // This prevents commands from being sorted incorrectly, which can cause spurious mismatches at low CRC intervals. - m_lastFrameResetCommandID = frame; + // TheSuperHackers @bugfix Caball009 06/06/2026 Reset the network command id to prevent it from overflowing. + // This prevents commands from being sorted incorrectly, which can cause spurious mismatches at low CRC intervals. + if (GetCommandID() >= 64000) { ResetCommandID(); } } diff --git a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp index c73e3ebab54..3f8883be13f 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -108,6 +108,11 @@ UnsignedShort GenerateNextCommandID() return s_commandID++; } +UnsignedShort GetCommandID() +{ + return s_commandID; +} + void ResetCommandID() { s_commandID = 0;