Skip to content

[ZH] Implement Headless Mode #651

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 73 commits into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
e4b6f18
Make CRC-related commandline arguments work when compiling Release wi…
helmutbuhler Mar 22, 2025
fb4881f
Add comments to Xfer.h
helmutbuhler Mar 22, 2025
10991d6
Add some comments to crc command line arguments
helmutbuhler Mar 22, 2025
320cd7c
Resest Frame Counter earlier in GameLogic::prepareNewGame instead of …
helmutbuhler Mar 23, 2025
2e0a25c
Improved CRC Debugging: Optionally save crc data into a file per fram…
helmutbuhler Mar 29, 2025
adfd721
Improved logging for mismatch debugging.
helmutbuhler Mar 29, 2025
ba13902
Make condition for checking TheCRCFirstFrameToLog consistent.
helmutbuhler Mar 29, 2025
1b2f5a8
Add comment regarding g_keepCRCSaves
helmutbuhler Mar 29, 2025
fb5d977
Remove transform crclogging in Object::crc. This was likely copypaste…
helmutbuhler Mar 29, 2025
46a4ab4
Delete CRC Frame files properly
helmutbuhler Mar 29, 2025
9f7287e
Improve comments
helmutbuhler Apr 10, 2025
4a22c73
Reset framecounter and call CRCDebugStartNewGame in GameLogic::startN…
helmutbuhler Apr 10, 2025
e536826
Fix TEAM_ID_INVALID
helmutbuhler Apr 10, 2025
21b4851
Some code cleanup in CRCDebug
helmutbuhler Apr 10, 2025
88a5b8e
Merge branch 'hb_sh' into improve_crc_logging_ea
helmutbuhler Apr 10, 2025
2be0870
Add define NORMAL_LOG_IN_CRC_LOG to control normal logging in crc logs.
helmutbuhler Apr 10, 2025
3813f09
Add cmake options to control logging
helmutbuhler Apr 10, 2025
83e7ec1
Add m_headless global flag to optionally run the game in headless mode.
helmutbuhler Apr 10, 2025
16aa073
Add some comments where logic-client-separation is broken.
helmutbuhler Apr 10, 2025
4229f1d
Add some more checks for headless
helmutbuhler Apr 10, 2025
c7c8b1d
Add comment about logic-client architecture and headless mode
helmutbuhler Apr 10, 2025
f69e41f
Add -headless commandline option
helmutbuhler Apr 11, 2025
7498de0
Fix headless when shutting down the engine.
helmutbuhler Apr 13, 2025
bf0a2e5
Rename RadarHeadless to RadarDummy
helmutbuhler Apr 13, 2025
7df3c68
Simplify RadarDummy
helmutbuhler Apr 13, 2025
8d325d9
Change TheWindowManager Check in ControlBar::getStarImage
helmutbuhler Apr 13, 2025
741a9a3
Merge branch 'main' into improve_crc_logging_ea
helmutbuhler Apr 13, 2025
bb744e7
Readd logging configuration to cmake files
helmutbuhler Apr 13, 2025
bf19c8c
Merge branch 'improve_crc_logging_ea' into headless_sh
helmutbuhler Apr 13, 2025
dd16528
Merge branch 'main' of https://github.com/TheSuperHackers/GeneralsGam…
helmutbuhler Apr 14, 2025
adfbe8b
Remove config-logging.cmake
helmutbuhler Apr 14, 2025
6fdf9ed
Fix wrong cmake feature names
helmutbuhler Apr 14, 2025
c26a4fa
Add RTS_DEBUG_INCLUDE_DEBUG_LOG_IN_CRC_LOG option (again)
helmutbuhler Apr 14, 2025
4917362
Fix comments
helmutbuhler Apr 15, 2025
40a9407
Merge branch 'improve_crc_logging_ea' into headless_sh
helmutbuhler Apr 15, 2025
5fba809
Merge branch 'main' of https://github.com/TheSuperHackers/GeneralsGam…
helmutbuhler Apr 15, 2025
73adde5
Merge branch 'main' of https://github.com/TheSuperHackers/GeneralsGam…
helmutbuhler Apr 15, 2025
e50eec4
Add null checks for W3DDisplay::m_3DScene
helmutbuhler Apr 17, 2025
232da78
Merge branch 'main' into headless_sh
helmutbuhler Apr 19, 2025
016187a
Merge branch 'main' into headless_sh
helmutbuhler Apr 24, 2025
d6c8339
Move m_headless check for W3DView to GameClient
helmutbuhler Apr 21, 2025
c26971e
Some minor code moving around
helmutbuhler Apr 21, 2025
8eb9730
Add missing headless check for Sizzle Video
helmutbuhler Apr 22, 2025
5951bc8
Add missing headless checks for m_bibBuffer in BaseHeightMapRenderObj…
helmutbuhler Apr 22, 2025
39ae00c
Add DummyGameWindowManager to avoid null checks
helmutbuhler Apr 23, 2025
8759b91
Remove TheWindowManager NULL checks
helmutbuhler Apr 23, 2025
cf8963d
Remove more TheWindowManager NULL checks
helmutbuhler Apr 24, 2025
d371c76
Fix stupid dates in comments
helmutbuhler Apr 24, 2025
9513645
Remove one more TheWindowManager NULL check
helmutbuhler Apr 24, 2025
1cf0c69
Fix some white space stuff
helmutbuhler Apr 24, 2025
d8bcd57
Add comments for DummyGameWindowManager
helmutbuhler Apr 24, 2025
a3c5b19
Fix logic-client-separation in W3DBridgeBuffer: It's needed for maps …
helmutbuhler Apr 26, 2025
194e04a
Merge branch 'main' into headless_sh
jaapdeheer May 15, 2025
9c151d9
Prefix new use of `_DEBUG` & `_INTERNAL` with `RTS`
jaapdeheer May 15, 2025
d938ee0
Add back newline
jaapdeheer May 15, 2025
174ab67
Rename `DummyGameWindow` to `GameWindowDummy`
jaapdeheer May 15, 2025
8cdad71
Rename `DummyGameWindowManager` to `GameWindowManagerDummy`
jaapdeheer May 15, 2025
91e7e3a
Drop commented code
jaapdeheer May 16, 2025
6b4ddde
Move `GameWindowDummy` to `GameWindow.h`
jaapdeheer May 16, 2025
9ea9f0a
Add missing if (TheMouse != NULL) check
helmutbuhler May 9, 2025
dd5eacb
Fix Division by Zero in headless
helmutbuhler May 12, 2025
26eff48
Add AudioManagerDummy for headless mode
helmutbuhler May 17, 2025
6f9e555
Don't override removeAudioEvent and addAudioEvent
helmutbuhler May 17, 2025
fdb453a
Merge branch 'headless_sh' of https://github.com/helmutbuhler/CnC_Gen…
helmutbuhler May 17, 2025
f4c19b6
Fix potential crash in headless mode by ensuring that DummyGameWindow…
helmutbuhler May 12, 2025
a63809e
Add comment for AudioManagerDummy
helmutbuhler May 17, 2025
37ce6f7
Fix comment date formats
helmutbuhler May 17, 2025
3c38c97
Add MouseDummy and remove NULL checks for TheMouse
helmutbuhler May 17, 2025
7401f27
Use RTS_DEBUG and RTS_INTERNAL
helmutbuhler May 17, 2025
097b898
Comment headless-checks that will be removed soon
helmutbuhler May 18, 2025
fa2dbe6
Move headless-check in W3DView::setCameraTransform higher up
helmutbuhler May 18, 2025
6bb77c0
Move headless-check in W3DGhostObject::restoreParentObject higher up
helmutbuhler May 18, 2025
385750e
Resolve minor suggestions
helmutbuhler May 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion GeneralsMD/Code/GameEngine/Include/Common/CRCDebug.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@
void dumpReal(Real r, AsciiString name, AsciiString fname, Int line);

void outputCRCDebugLines( void );
void CRCDebugStartNewGame( void );
void outputCRCDumpLines( void );

void addCRCDebugLine(const char *fmt, ...);
void addCRCDebugLineNoCounter(const char *fmt, ...);
void addCRCDumpLine(const char *fmt, ...);
void addCRCGenLine(const char *fmt, ...);
#define CRCDEBUG_LOG(x) addCRCDebugLine x
Expand All @@ -97,7 +99,9 @@
extern Bool g_crcModuleDataFromLogic;

extern Bool g_keepCRCSaves;

extern Bool g_saveDebugCRCPerFrame;
extern AsciiString g_saveDebugCRCPerFrameDir;

extern Bool g_logObjectCRCs;

#else // DEBUG_CRC
Expand Down
5 changes: 5 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ class GlobalData : public SubsystemInterface
Bool m_dumpAssetUsage;
Int m_framesPerSecondLimit;
Int m_chipSetType; ///<See W3DShaderManager::ChipsetType for options

// TheSuperHackers @feature helmutbuhler 04/11/2025
// Run game without graphics, input or audio.
Bool m_headless;

Bool m_windowed;
Int m_xResolution;
Int m_yResolution;
Expand Down
24 changes: 24 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/Radar.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,30 @@ class Radar : public Snapshot,
// EXTERNALS //////////////////////////////////////////////////////////////////////////////////////
extern Radar *TheRadar; ///< the radar singleton extern

// TheSuperHackers @feature helmutbuhler 04/10/2025
// Radar that does nothing
class RadarHeadless : public Radar
{
public:
RadarHeadless(){}
virtual ~RadarHeadless() {}

virtual void init() { }
virtual void reset() { Radar::reset(); }
virtual void update() { Radar::update(); }

virtual void refreshTerrain(TerrainLogic *terrain) { Radar::refreshTerrain(terrain); }
virtual void queueTerrainRefresh() { Radar::queueTerrainRefresh(); }
virtual void newMap(TerrainLogic *terrain) { Radar::newMap(terrain); }
virtual void draw(Int pixelX, Int pixelY, Int width, Int height) { }
virtual void clearShroud() { }
virtual void setShroudLevel(Int x, Int y, CellShroudStatus setting) { }

virtual void crc(Xfer *xfer) { }
virtual void xfer(Xfer *xfer) { }
virtual void loadPostProcess() { Radar::loadPostProcess(); }
};

#endif // __RADAR_H_


Expand Down
8 changes: 8 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/Xfer.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
// Desc: The Xfer system is capable of setting up operations to work with blocks of data
// from other subsystems. It can work things such as file reading, file writing,
// CRC computations etc
//
// TheSuperHackers @info helmutbuhler 04/09/2025
// The baseclass Xfer has 3 implementations:
// - XferLoad: Load gamestate
// - XferSave: Save gamestate
// - XferCRC: Calculate gamestate CRC
// - XferDeepCRC: This derives from XferCRC and also writes the gamestate data relevant
// to crc calculation to a file (only used in developer builds)
///////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once
Expand Down
40 changes: 40 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,44 @@ inline Drawable* GameClient::findDrawableByID( const DrawableID id )
// the singleton
extern GameClient *TheGameClient;


// TheSuperHackers @logic-client-separation helmutbuhler 04/11/2025
// Some information about the architecture and headless mode:
// The game is structurally separated into GameLogic and GameClient.
// The Logic is responsible for everything that affects the game mechanic and what is synchronized over
// the network. The Client is responsible for rendering, input, audio and similar stuff.
//
// Unfortunately there are some places in the code that make the Logic depend on the Client.
// (Search for @logic-client-separation)
// That means if we want to run the game headless, we cannot just disable the Client. We need to disable
// the parts in the Client that don't work in headless mode and need to keep the parts that are needed
// to run the Logic.
// The following describes which parts we disable in headless mode:
//
// GameEngine:
// TheGameClient is partially disabled:
// TheKeyboard = NULL
// TheMouse = NULL
// TheDisplay is partially disabled:
// m_3DInterfaceScene = NULL
// m_2DScene = NULL
// m_3DScene = NULL
// (m_assetManager remains!)
// TheWindowManager = NULL
// TheIMEManager = NULL
// TheTerrainVisual is partially disabled:
// TheTerrainTracksRenderObjClassSystem = NULL
// TheW3DShadowManager = NULL
// TheWaterRenderObj = NULL
// TheSmudgeManager = NULL
// TheTerrainRenderObject is partially disabled:
// m_treeBuffer = NULL
// m_propBuffer = NULL
// m_bibBuffer = NULL
// m_bridgeBuffer = NULL
// m_waypointBuffer = NULL
// m_roadBuffer = NULL
// m_shroud = NULL
// TheRadar = RadarHeadless

#endif // _GAME_INTERFACE_H_
166 changes: 109 additions & 57 deletions GeneralsMD/Code/GameEngine/Source/Common/CRCDebug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "Common/CRCDebug.h"
#include "Common/Debug.h"
#include "Common/PerfTimer.h"
#include "Common/LocalFileSystem.h"
#include "GameClient/InGameUI.h"
#include "GameNetwork/IPEnumeration.h"
#include <cstdarg>
Expand All @@ -41,11 +42,12 @@
#ifdef DEBUG_CRC

static const Int MaxStrings = 64000;
static const Int MaxStringLen = 1024;

static char DebugStrings[MaxStrings][1024];
static char DebugStrings[MaxStrings][MaxStringLen];
static Int nextDebugString = 0;
static Int numDebugStrings = 0;
//static char DumpStrings[MaxStrings][1024];
//static char DumpStrings[MaxStrings][MaxStringLen];
//static Int nextDumpString = 0;
//static Int numDumpStrings = 0;

Expand Down Expand Up @@ -91,12 +93,8 @@ CRCVerification::~CRCVerification()
#endif
}

static Bool dumped = FALSE;
void outputCRCDebugLines( void )
{
if (dumped)
return;
dumped = TRUE;
IPEnumeration ips;
AsciiString fname;
fname.format("crcDebug%s.txt", ips.getMachineName().str());
Expand All @@ -116,6 +114,60 @@ void outputCRCDebugLines( void )
if (fp) fclose(fp);
}

Int lastCRCDebugFrame = 0;
Int lastCRCDebugIndex = 0;
extern Bool inCRCGen;

void CRCDebugStartNewGame()
{
if (g_saveDebugCRCPerFrame)
{
// Create folder for frame data, if it doesn't exist yet.
CreateDirectory(g_saveDebugCRCPerFrameDir.str(), NULL);

// Delete existing files
FilenameList files;
AsciiString dir = g_saveDebugCRCPerFrameDir;
dir.concat("/");
TheLocalFileSystem->getFileListInDirectory(dir.str(), "", "DebugFrame_*.txt", files, FALSE);
FilenameList::iterator it;
for (it = files.begin(); it != files.end(); ++it)
{
DeleteFile(it->str());
}
}
nextDebugString = 0;
numDebugStrings = 0;
lastCRCDebugFrame = 0;
lastCRCDebugIndex = 0;
}

static void outputCRCDebugLinesPerFrame()
{
if (!g_saveDebugCRCPerFrame || numDebugStrings == 0)
return;
AsciiString fname;
fname.format("%s/DebugFrame_%06d.txt", g_saveDebugCRCPerFrameDir.str(), lastCRCDebugFrame);
FILE *fp = fopen(fname.str(), "wt");
int start = 0;
int end = nextDebugString;
if (numDebugStrings >= MaxStrings)
start = nextDebugString - MaxStrings;
nextDebugString = 0;
numDebugStrings = 0;
if (!fp)
return;

for (Int i=start; i<end; ++i)
{
const char *line = DebugStrings[ (i + MaxStrings) % MaxStrings ];
//DEBUG_LOG(("%s\n", line));
fprintf(fp, "%s\n", line);
}

fclose(fp);
}

void outputCRCDumpLines( void )
{
/*
Expand All @@ -137,64 +189,76 @@ static AsciiString getFname(AsciiString path)
return path.reverseFind('\\') + 1;
}

Int lastCRCDebugFrame = 0;
Int lastCRCDebugIndex = 0;
extern Bool inCRCGen;
void addCRCDebugLine(const char *fmt, ...)
static void addCRCDebugLineInternal(bool count, const char *fmt, va_list args)
{
if (dumped)// || inCRCGen /*|| !TheGameLogic->isInGameLogicUpdate()*/)
if (TheGameLogic == NULL || !(IS_FRAME_OK_TO_LOG))
return;

if (IS_FRAME_OK_TO_LOG)
if (lastCRCDebugFrame != TheGameLogic->getFrame())
{
outputCRCDebugLinesPerFrame();
lastCRCDebugFrame = TheGameLogic->getFrame();
lastCRCDebugIndex = 0;
}

if (lastCRCDebugFrame != TheGameLogic->getFrame())
{
lastCRCDebugFrame = TheGameLogic->getFrame();
lastCRCDebugIndex = 0;
}

sprintf(DebugStrings[nextDebugString], "%d:%d ", TheGameLogic->getFrame(), lastCRCDebugIndex++);
//DebugStrings[nextDebugString][0] = 0;
Int len = strlen(DebugStrings[nextDebugString]);
if (count)
sprintf(DebugStrings[nextDebugString], "%d:%05d ", TheGameLogic->getFrame(), lastCRCDebugIndex++);
else
DebugStrings[nextDebugString][0] = 0;
Int len = strlen(DebugStrings[nextDebugString]);

va_list va;
va_start( va, fmt );
_vsnprintf(DebugStrings[nextDebugString]+len, 1024-len, fmt, va );
DebugStrings[nextDebugString][1023] = 0;
va_end( va );
_vsnprintf(DebugStrings[nextDebugString]+len, MaxStringLen-len, fmt, args);
DebugStrings[nextDebugString][MaxStringLen-1] = 0;

char *tmp = DebugStrings[nextDebugString];
while (tmp && *tmp)
char *tmp = DebugStrings[nextDebugString];
while (tmp && *tmp)
{
if (*tmp == '\r' || *tmp == '\n')
{
if (*tmp == '\r' || *tmp == '\n')
{
*tmp = ' ';
}
++tmp;
*tmp = ' ';
}
++tmp;
}

//DEBUG_LOG(("%s\n", DebugStrings[nextDebugString]));
//DEBUG_LOG(("%s\n", DebugStrings[nextDebugString]));

++nextDebugString;
++numDebugStrings;
if (nextDebugString == MaxStrings)
nextDebugString = 0;
++nextDebugString;
++numDebugStrings;
if (nextDebugString == MaxStrings)
nextDebugString = 0;
}

}
void addCRCDebugLine(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
addCRCDebugLineInternal(true, fmt, args);
va_end(args);
}

void addCRCDebugLineNoCounter(const char *fmt, ...)
{
// TheSuperHackers @feature helmutbuhler 04/09/2025
// This version doesn't increase the lastCRCDebugIndex counter
// and can be used for logging lines that don't necessarily match up on all peers.
// (Otherwise the numbers would no longer match up and the diff would be very difficult to read)
va_list args;
va_start(args, fmt);
addCRCDebugLineInternal(false, fmt, args);
va_end(args);
}

void addCRCGenLine(const char *fmt, ...)
{
if (dumped || !(IS_FRAME_OK_TO_LOG))
if (!(IS_FRAME_OK_TO_LOG))
return;

static char buf[1024];
static char buf[MaxStringLen];
va_list va;
va_start( va, fmt );
_vsnprintf(buf, 1024, fmt, va );
_vsnprintf(buf, MaxStringLen, fmt, va );
va_end( va );
buf[1023] = 0;
buf[MaxStringLen-1] = 0;
addCRCDebugLine("%s", buf);

//DEBUG_LOG(("%s", buf));
Expand All @@ -205,8 +269,8 @@ void addCRCDumpLine(const char *fmt, ...)
/*
va_list va;
va_start( va, fmt );
_vsnprintf(DumpStrings[nextDumpString], 1024, fmt, va );
DumpStrings[nextDumpString][1023] = 0;
_vsnprintf(DumpStrings[nextDumpString], MaxStringLen, fmt, va );
DumpStrings[nextDumpString][MaxStringLen-1] = 0;
va_end( va );

++nextDumpString;
Expand All @@ -218,9 +282,6 @@ void addCRCDumpLine(const char *fmt, ...)

void dumpVector3(const Vector3 *v, AsciiString name, AsciiString fname, Int line)
{
if (dumped)
return;

if (!(IS_FRAME_OK_TO_LOG)) return;
fname.toLower();
fname = getFname(fname);
Expand All @@ -231,9 +292,6 @@ void dumpVector3(const Vector3 *v, AsciiString name, AsciiString fname, Int line

void dumpCoord3D(const Coord3D *c, AsciiString name, AsciiString fname, Int line)
{
if (dumped)
return;

if (!(IS_FRAME_OK_TO_LOG)) return;
fname.toLower();
fname = getFname(fname);
Expand All @@ -244,9 +302,6 @@ void dumpCoord3D(const Coord3D *c, AsciiString name, AsciiString fname, Int line

void dumpMatrix3D(const Matrix3D *m, AsciiString name, AsciiString fname, Int line)
{
if (dumped)
return;

if (!(IS_FRAME_OK_TO_LOG)) return;
fname.toLower();
fname = getFname(fname);
Expand All @@ -260,9 +315,6 @@ void dumpMatrix3D(const Matrix3D *m, AsciiString name, AsciiString fname, Int li

void dumpReal(Real r, AsciiString name, AsciiString fname, Int line)
{
if (dumped)
return;

if (!(IS_FRAME_OK_TO_LOG)) return;
fname.toLower();
fname = getFname(fname);
Expand Down
Loading