Skip to content

Commit 842c7a5

Browse files
authored
fix(recorder): Fix memory leak of RecorderClass::m_crcInfo (TheSuperHackers#2713)
1 parent 5852010 commit 842c7a5

4 files changed

Lines changed: 168 additions & 182 deletions

File tree

Generals/Code/GameEngine/Include/Common/Recorder.h

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,39 @@ enum RecorderModeType CPP_11(: Int) {
5353
RECORDERMODETYPE_NONE // this is a valid state to be in on the shell map, or in saved games
5454
};
5555

56-
class CRCInfo;
56+
class RecorderClass : public SubsystemInterface
57+
{
58+
protected:
59+
// TheSuperHackers @info helmutbuhler 03/04/2025 CRC overview:
60+
// Each peer periodically computes a CRC from its local game state and broadcasts it to all peers, including itself,
61+
// to verify synchronization. CRC messages are received a few frames later in network games to avoid stalling every
62+
// frame while waiting for all peers. This works because all peers compare the same received CRCs on the same frame.
63+
//
64+
// Replays are different: recorded CRC messages appear on the frame they were originally received, so directly
65+
// comparing them against the current local state would mismatch. To handle this, local CRCs must be queued until the
66+
// corresponding replay CRC messages arrive. This class implements that queue.
67+
class CRCInfo
68+
{
69+
public:
70+
CRCInfo();
71+
CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer);
72+
void addCRC(UnsignedInt val);
73+
UnsignedInt readCRC();
74+
int GetQueueSize() const { return m_data.size(); }
75+
UnsignedInt getLocalPlayer() const { return m_localPlayer; }
76+
void setSawCRCMismatch() { m_sawCRCMismatch = TRUE; }
77+
Bool sawCRCMismatch() const { return m_sawCRCMismatch; }
78+
79+
protected:
80+
Bool m_sawCRCMismatch;
81+
Bool m_skippedOne;
82+
UnsignedInt m_localPlayer;
83+
std::list<UnsignedInt> m_data;
84+
};
5785

58-
class RecorderClass : public SubsystemInterface {
5986
public:
6087
struct ReplayHeader;
6188

62-
public:
6389
RecorderClass(); ///< Constructor.
6490
virtual ~RecorderClass() override; ///< Destructor.
6591

@@ -86,9 +112,6 @@ class RecorderClass : public SubsystemInterface {
86112

87113
public:
88114
void handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback);
89-
protected:
90-
CRCInfo *m_crcInfo;
91-
public:
92115

93116
// read in info relating to a replay, conditionally setting up m_file for playback
94117
struct ReplayHeader
@@ -158,6 +181,7 @@ class RecorderClass : public SubsystemInterface {
158181

159182
CullBadCommandsResult cullBadCommands(); ///< prevent the user from giving mouse commands that he shouldn't be able to do during playback.
160183

184+
CRCInfo m_crcInfo;
161185
File* m_file;
162186
AsciiString m_fileName;
163187
Int m_currentFilePosition;

Generals/Code/GameEngine/Source/Common/Recorder.cpp

Lines changed: 54 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,50 @@ static FILE* openStatsLogFile()
100100
}
101101
#endif
102102

103+
RecorderClass::CRCInfo::CRCInfo() :
104+
m_sawCRCMismatch(FALSE),
105+
m_skippedOne(FALSE),
106+
m_localPlayer(0)
107+
{}
108+
109+
RecorderClass::CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer)
110+
{
111+
m_sawCRCMismatch = FALSE;
112+
m_skippedOne = !isMultiplayer;
113+
m_localPlayer = localPlayer;
114+
}
115+
116+
void RecorderClass::CRCInfo::addCRC(UnsignedInt val)
117+
{
118+
// TheSuperHackers @fix helmutbuhler 03/04/2025
119+
// In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network.
120+
// Perhaps this happens because the network is not yet set up on frame 0.
121+
// So we also don't queue up the first local crc message, otherwise the crc
122+
// messages wouldn't match up anymore and we'd desync immediately during playback.
123+
if (!m_skippedOne)
124+
{
125+
m_skippedOne = TRUE;
126+
return;
127+
}
128+
129+
m_data.push_back(val);
130+
//DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)", val, m_data.size(), !m_data.empty()));
131+
}
132+
133+
UnsignedInt RecorderClass::CRCInfo::readCRC()
134+
{
135+
if (m_data.empty())
136+
{
137+
DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d", m_data.size()));
138+
return 0;
139+
}
140+
141+
UnsignedInt val = m_data.front();
142+
m_data.pop_front();
143+
//DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d", val, !m_data.empty(), m_data.size()));
144+
return val;
145+
}
146+
103147
void RecorderClass::logGameStart(AsciiString options)
104148
{
105149
if (!m_file)
@@ -933,96 +977,21 @@ AsciiString RecorderClass::getCurrentReplayFilename()
933977
return AsciiString::TheEmptyString;
934978
}
935979

936-
// TheSuperHackers @info helmutbuhler 03/04/2025
937-
// Some info about CRC:
938-
// In each game, each peer periodically calculates a CRC from the local gamestate and sends that
939-
// in a message to all peers (including itself) so that everyone can check that the crc is synchronous.
940-
// In a network game, there is a delay between sending the CRC message and receiving it. This is
941-
// necessary because if you were to wait each frame for all messages from all peers, things would go
942-
// horribly slow.
943-
// But this delay is not a problem for CRC checking because everyone receives the CRC in the same frame
944-
// and every peer just makes sure all the received CRCs are equal.
945-
// While playing replays, this is a problem however: The CRC messages in the replays appear on the frame
946-
// they were received, which can be a few frames delayed if it was a network game. And if we were to
947-
// compare those with the local gamestate, they wouldn't sync up.
948-
// So, in order to fix this, we need to queue up our local CRCs,
949-
// so that we can check it with the crc messages that come later.
950-
// This class is basically that queue.
951-
class CRCInfo
952-
{
953-
public:
954-
CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer);
955-
void addCRC(UnsignedInt val);
956-
UnsignedInt readCRC();
957-
958-
int GetQueueSize() const { return m_data.size(); }
959-
960-
UnsignedInt getLocalPlayer() { return m_localPlayer; }
961-
962-
void setSawCRCMismatch() { m_sawCRCMismatch = TRUE; }
963-
Bool sawCRCMismatch() const { return m_sawCRCMismatch; }
964-
965-
protected:
966-
967-
Bool m_sawCRCMismatch;
968-
Bool m_skippedOne;
969-
std::list<UnsignedInt> m_data;
970-
UnsignedInt m_localPlayer;
971-
};
972-
973-
CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer)
974-
{
975-
m_localPlayer = localPlayer;
976-
m_skippedOne = !isMultiplayer;
977-
m_sawCRCMismatch = FALSE;
978-
}
979-
980-
void CRCInfo::addCRC(UnsignedInt val)
981-
{
982-
// TheSuperHackers @fix helmutbuhler 03/04/2025
983-
// In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network.
984-
// Perhaps this happens because the network is not yet set up on frame 0.
985-
// So we also don't queue up the first local crc message, otherwise the crc
986-
// messages wouldn't match up anymore and we'd desync immediately during playback.
987-
if (!m_skippedOne)
988-
{
989-
m_skippedOne = TRUE;
990-
return;
991-
}
992-
993-
m_data.push_back(val);
994-
//DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)", val, m_data.size(), !m_data.empty()));
995-
}
996-
997-
UnsignedInt CRCInfo::readCRC()
998-
{
999-
if (m_data.empty())
1000-
{
1001-
DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d", m_data.size()));
1002-
return 0;
1003-
}
1004-
1005-
UnsignedInt val = m_data.front();
1006-
m_data.pop_front();
1007-
//DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d", val, !m_data.empty(), m_data.size()));
1008-
return val;
1009-
}
1010-
1011980
Bool RecorderClass::sawCRCMismatch() const
1012981
{
1013-
return m_crcInfo->sawCRCMismatch();
982+
return m_crcInfo.sawCRCMismatch();
1014983
}
1015984

1016985
void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback)
1017986
{
1018987
if (fromPlayback)
1019988
{
1020989
//DEBUG_LOG(("RecorderClass::handleCRCMessage() - Adding CRC of %X from %d to m_crcInfo", newCRC, playerIndex));
1021-
m_crcInfo->addCRC(newCRC);
990+
m_crcInfo.addCRC(newCRC);
1022991
return;
1023992
}
1024993

1025-
Int localPlayerIndex = m_crcInfo->getLocalPlayer();
994+
Int localPlayerIndex = m_crcInfo.getLocalPlayer();
1026995
Bool samePlayer = FALSE;
1027996
AsciiString playerName;
1028997
playerName.format("player%d", localPlayerIndex);
@@ -1031,10 +1000,10 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f
10311000
samePlayer = TRUE;
10321001
if (samePlayer || (localPlayerIndex < 0))
10331002
{
1034-
UnsignedInt playbackCRC = m_crcInfo->readCRC();
1003+
UnsignedInt playbackCRC = m_crcInfo.readCRC();
10351004
//DEBUG_LOG(("RecorderClass::handleCRCMessage() - Comparing CRCs of InGame:%8.8X Replay:%8.8X Frame:%d from Player %d",
1036-
// playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo->GetQueueSize()-1, playerIndex));
1037-
if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo->sawCRCMismatch())
1005+
// playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo.GetQueueSize()-1, playerIndex));
1006+
if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo.sawCRCMismatch())
10381007
{
10391008
// Since we don't seem to have any *visible* desyncs when replaying games, but get this warning
10401009
// virtually every replay, the assumption is our CRC checking is faulty. Since we're at the
@@ -1048,7 +1017,7 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f
10481017
// TheSuperHackers @info helmutbuhler 03/04/2025
10491018
// Note: We subtract the queue size from the frame number. This way we calculate the correct frame
10501019
// the mismatch first happened in case the NetCRCInterval is set to 1 during the game.
1051-
const UnsignedInt mismatchFrame = TheGameLogic->getFrame() - m_crcInfo->GetQueueSize() - 1;
1020+
const UnsignedInt mismatchFrame = TheGameLogic->getFrame() - m_crcInfo.GetQueueSize() - 1;
10521021

10531022
// Now also prints a UI message for it.
10541023
const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE("GUI:CRCMismatchDetails", L"InGame:%8.8X Replay:%8.8X Frame:%d");
@@ -1070,7 +1039,7 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f
10701039
TheGameLogic->setGamePaused(pause, pauseMusic, pauseInput);
10711040

10721041
// Mark this mismatch as seen when we had the chance to pause once.
1073-
m_crcInfo->setSawCRCMismatch();
1042+
m_crcInfo.setSawCRCMismatch();
10741043
}
10751044
}
10761045
return;
@@ -1192,9 +1161,9 @@ Bool RecorderClass::playbackFile(AsciiString filename)
11921161
#endif
11931162

11941163
Bool isMultiplayer = m_gameInfo.getSlot(header.localPlayerIndex)->getIP() != 0;
1195-
m_crcInfo = NEW CRCInfo(header.localPlayerIndex, isMultiplayer);
1164+
m_crcInfo = CRCInfo(header.localPlayerIndex, isMultiplayer);
11961165
REPLAY_CRC_INTERVAL = m_gameInfo.getCRCInterval();
1197-
DEBUG_LOG(("Player index is %d, replay CRC interval is %d", m_crcInfo->getLocalPlayer(), REPLAY_CRC_INTERVAL));
1166+
DEBUG_LOG(("Player index is %d, replay CRC interval is %d", m_crcInfo.getLocalPlayer(), REPLAY_CRC_INTERVAL));
11981167

11991168
Int difficulty = 0;
12001169
m_file->read(&difficulty, sizeof(difficulty));

GeneralsMD/Code/GameEngine/Include/Common/Recorder.h

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,39 @@ enum RecorderModeType CPP_11(: Int) {
5353
RECORDERMODETYPE_NONE // this is a valid state to be in on the shell map, or in saved games
5454
};
5555

56-
class CRCInfo;
56+
class RecorderClass : public SubsystemInterface
57+
{
58+
protected:
59+
// TheSuperHackers @info helmutbuhler 03/04/2025 CRC overview:
60+
// Each peer periodically computes a CRC from its local game state and broadcasts it to all peers, including itself,
61+
// to verify synchronization. CRC messages are received a few frames later in network games to avoid stalling every
62+
// frame while waiting for all peers. This works because all peers compare the same received CRCs on the same frame.
63+
//
64+
// Replays are different: recorded CRC messages appear on the frame they were originally received, so directly
65+
// comparing them against the current local state would mismatch. To handle this, local CRCs must be queued until the
66+
// corresponding replay CRC messages arrive. This class implements that queue.
67+
class CRCInfo
68+
{
69+
public:
70+
CRCInfo();
71+
CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer);
72+
void addCRC(UnsignedInt val);
73+
UnsignedInt readCRC();
74+
int GetQueueSize() const { return m_data.size(); }
75+
UnsignedInt getLocalPlayer() const { return m_localPlayer; }
76+
void setSawCRCMismatch() { m_sawCRCMismatch = TRUE; }
77+
Bool sawCRCMismatch() const { return m_sawCRCMismatch; }
78+
79+
protected:
80+
Bool m_sawCRCMismatch;
81+
Bool m_skippedOne;
82+
UnsignedInt m_localPlayer;
83+
std::list<UnsignedInt> m_data;
84+
};
5785

58-
class RecorderClass : public SubsystemInterface {
5986
public:
6087
struct ReplayHeader;
6188

62-
public:
6389
RecorderClass(); ///< Constructor.
6490
virtual ~RecorderClass() override; ///< Destructor.
6591

@@ -86,9 +112,6 @@ class RecorderClass : public SubsystemInterface {
86112

87113
public:
88114
void handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback);
89-
protected:
90-
CRCInfo *m_crcInfo;
91-
public:
92115

93116
// read in info relating to a replay, conditionally setting up m_file for playback
94117
struct ReplayHeader
@@ -158,6 +181,7 @@ class RecorderClass : public SubsystemInterface {
158181

159182
CullBadCommandsResult cullBadCommands(); ///< prevent the user from giving mouse commands that he shouldn't be able to do during playback.
160183

184+
CRCInfo m_crcInfo;
161185
File* m_file;
162186
AsciiString m_fileName;
163187
Int m_currentFilePosition;

0 commit comments

Comments
 (0)