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

Open
wants to merge 51 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
51 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
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
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
10 changes: 10 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,16 @@ class Radar : public Snapshot,
// EXTERNALS //////////////////////////////////////////////////////////////////////////////////////
extern Radar *TheRadar; ///< the radar singleton extern

// TheSuperHackers @feature helmutbuhler 04/10/2025
// Radar that does nothing. Used for Headless Mode.
class RadarDummy : public Radar
{
public:
virtual void draw(Int pixelX, Int pixelY, Int width, Int height) { }
virtual void clearShroud() { }
virtual void setShroudLevel(Int x, Int y, CellShroudStatus setting) { }
};

#endif // __RADAR_H_


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 @@ -251,4 +251,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 = RadarDummy
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RadarDummy, DummyGameWindow, DummyGameWindowManager are not consistently named.

I suggest to put "Dummy" at the end so that it groups properly with the normal class.

Radar
RadarDummy
GameWindow
GameWindowDummy
GameWindowManager
GameWindowManagerDummy

Alternatively can also use "Null" instead of "Dummy". Either name is fine.


#endif // _GAME_INTERFACE_H_
13 changes: 13 additions & 0 deletions GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,15 @@ Int parseQuickStart( char *args[], int num )
return 1;
}

Int parseHeadless( char *args[], int num )
{
if (TheWritableGlobalData)
{
TheWritableGlobalData->m_headless = TRUE;
}
return 1;
}

Int parseConstantDebug( char *args[], int num )
{
if (TheWritableGlobalData)
Expand Down Expand Up @@ -1205,6 +1214,10 @@ static CommandLineParam params[] =
{ "-noshaders", parseNoShaders },
{ "-quickstart", parseQuickStart },

// TheSuperHackers @feature helmutbuhler 04/11/2025
// This runs the game without a window, graphics, input and audio. Used for testing.
{ "-headless", parseHeadless },

#if (defined(_DEBUG) || defined(_INTERNAL))
{ "-noaudio", parseNoAudio },
{ "-map", parseMapName },
Expand Down
16 changes: 10 additions & 6 deletions GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ void GameEngine::init( int argc, char *argv[] )
initSubsystem(TheCrateSystem,"TheCrateSystem", MSGNEW("GameEngineSubsystem") CrateSystem(), &xferCRC, "Data\\INI\\Default\\Crate.ini", "Data\\INI\\Crate.ini");
initSubsystem(ThePlayerList,"ThePlayerList", MSGNEW("GameEngineSubsystem") PlayerList(), NULL);
initSubsystem(TheRecorder,"TheRecorder", createRecorder(), NULL);
initSubsystem(TheRadar,"TheRadar", createRadar(), NULL);
initSubsystem(TheRadar,"TheRadar", TheGlobalData->m_headless ? NEW RadarDummy : createRadar(), NULL);
initSubsystem(TheVictoryConditions,"TheVictoryConditions", createVictoryConditions(), NULL);


Expand Down Expand Up @@ -709,11 +709,15 @@ void GameEngine::init( int argc, char *argv[] )
void GameEngine::reset( void )
{

WindowLayout *background = TheWindowManager->winCreateLayout("Menus/BlankWindow.wnd");
DEBUG_ASSERTCRASH(background,("We Couldn't Load Menus/BlankWindow.wnd"));
background->hide(FALSE);
background->bringForward();
background->getFirstWindow()->winClearStatus(WIN_STATUS_IMAGE);
WindowLayout *background = TheWindowManager ? TheWindowManager->winCreateLayout("Menus/BlankWindow.wnd") : NULL;
if (background != NULL)
{
DEBUG_ASSERTCRASH(background,("We Couldn't Load Menus/BlankWindow.wnd"));
background->hide(FALSE);
background->bringForward();
background->getFirstWindow()->winClearStatus(WIN_STATUS_IMAGE);
}

Bool deleteNetwork = false;
if (TheGameLogic->isInMultiplayerGame())
deleteNetwork = true;
Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ GlobalData::GlobalData()
m_dumpAssetUsage = FALSE;
m_framesPerSecondLimit = 0;
m_chipSetType = 0;
m_headless = FALSE;
m_windowed = 0;
m_xResolution = 800;
m_yResolution = 600;
Expand Down
2 changes: 1 addition & 1 deletion GeneralsMD/Code/GameEngine/Source/Common/RandomValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ Int GetGameClientRandomValue( int lo, int hi, const char *file, int line )
/**/
#ifdef DEBUG_RANDOM_CLIENT
DEBUG_LOG(( "%d: GetGameClientRandomValue = %d (%d - %d), %s line %d\n",
TheGameLogic->getFrame(), rval, lo, hi, file, line ));
TheGameLogic ? TheGameLogic->getFrame() : -1, rval, lo, hi, file, line ));
#endif
/**/

Expand Down
2 changes: 1 addition & 1 deletion GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1610,7 +1610,7 @@ RecorderModeType RecorderClass::getMode() {
void RecorderClass::initControls()
{
NameKeyType parentReplayControlID = TheNameKeyGenerator->nameToKey( AsciiString("ReplayControl.wnd:ParentReplayControl") );
GameWindow *parentReplayControl = TheWindowManager->winGetWindowFromId( NULL, parentReplayControlID );
GameWindow *parentReplayControl = TheWindowManager ? TheWindowManager->winGetWindowFromId( NULL, parentReplayControlID ) : NULL;

Bool show = (getMode() != RECORDERMODETYPE_PLAYBACK);
if (parentReplayControl)
Expand Down
7 changes: 5 additions & 2 deletions GeneralsMD/Code/GameEngine/Source/Common/System/Radar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,11 @@ void Radar::newMap( TerrainLogic *terrain )

// keep a pointer for our radar window
Int id = NAMEKEY( "ControlBar.wnd:LeftHUD" );
m_radarWindow = TheWindowManager->winGetWindowFromId( NULL, id );
DEBUG_ASSERTCRASH( m_radarWindow, ("Radar::newMap - Unable to find radar game window\n") );
if (TheWindowManager != NULL)
{
m_radarWindow = TheWindowManager->winGetWindowFromId( NULL, id );
DEBUG_ASSERTCRASH( m_radarWindow, ("Radar::newMap - Unable to find radar game window\n") );
}

// reset all the data in the radar
reset();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,8 @@ void ControlBar::reset( void )
//-------------------------------------------------------------------------------------------------
void ControlBar::update( void )
{
if (TheGlobalData->m_headless)
return;
getStarImage();
updateRadarAttackGlow();
if(m_controlBarSchemeManager)
Expand Down Expand Up @@ -1652,6 +1654,8 @@ const Image *ControlBar::getStarImage(void )
else
m_lastFlashedAtPointValue = ThePlayerList->getLocalPlayer()->getSciencePurchasePoints();

if (TheWindowManager == NULL)
return NULL;
GameWindow *win= TheWindowManager->winGetWindowFromId( NULL, TheNameKeyGenerator->nameToKey( "ControlBar.wnd:ButtonGeneral" ) );
if(!win)
return NULL;
Expand Down Expand Up @@ -2141,6 +2145,12 @@ void ControlBar::switchToContext( ControlBarContext context, Drawable *draw )
// save a pointer for the currently selected drawable
m_currentSelectedDrawable = draw;

if (TheWindowManager == NULL)
{
m_currContext = context;
return;
}

if (IsInGameChatActive() == FALSE && TheGameLogic && !TheGameLogic->isInShellGame()) {
TheWindowManager->winSetFocus( NULL );
}
Expand Down Expand Up @@ -2593,6 +2603,8 @@ void ControlBar::setPortraitByImage( const Image *image )
//-------------------------------------------------------------------------------------------------
void ControlBar::setPortraitByObject( Object *obj )
{
if (TheWindowManager == NULL)
return;

if( obj )
{
Expand Down Expand Up @@ -2754,7 +2766,8 @@ void ControlBar::setControlBarSchemeByPlayer(Player *p)
{
if(m_controlBarSchemeManager)
m_controlBarSchemeManager->setControlBarSchemeByPlayer(p);

if (TheWindowManager == NULL)
return;
static NameKeyType buttonPlaceBeaconID = NAMEKEY( "ControlBar.wnd:ButtonPlaceBeacon" );
static NameKeyType buttonIdleWorkerID = NAMEKEY("ControlBar.wnd:ButtonIdleWorker");
static NameKeyType buttonGeneralID = NAMEKEY("ControlBar.wnd:ButtonGeneral");
Expand Down Expand Up @@ -2961,7 +2974,7 @@ void ControlBar::showPurchaseScience( void )

void ControlBar::hidePurchaseScience( void )
{
if(m_contextParent[ CP_PURCHASE_SCIENCE ]->winIsHidden())
if(m_contextParent[ CP_PURCHASE_SCIENCE ] == NULL || m_contextParent[ CP_PURCHASE_SCIENCE ]->winIsHidden())
return;

if( m_contextParent[ CP_PURCHASE_SCIENCE ] )
Expand All @@ -2986,6 +2999,8 @@ void ControlBar::hidePurchaseScience( void )

void ControlBar::togglePurchaseScience( void )
{
if (m_contextParent[ CP_PURCHASE_SCIENCE ] == NULL)
return;
if(m_contextParent[ CP_PURCHASE_SCIENCE ]->winIsHidden())
showPurchaseScience();
else
Expand Down Expand Up @@ -3244,7 +3259,8 @@ void ControlBar::initSpecialPowershortcutBar( Player *player)
if(!player || !pt|| !player->isLocalPlayer()
|| pt->getSpecialPowerShortcutButtonCount() == 0
|| pt->getSpecialPowerShortcutWinName().isEmpty()
|| !player->isPlayerActive())
|| !player->isPlayerActive()
|| TheWindowManager == NULL)
return;
m_currentlyUsedSpecialPowersButtons = pt->getSpecialPowerShortcutButtonCount();
AsciiString layoutName, tempName, windowName, parentName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,8 @@ void ControlBarSchemeManager::setControlBarSchemeByPlayerTemplate( const PlayerT
//-----------------------------------------------------------------------------
void ControlBarSchemeManager::setControlBarSchemeByPlayer(Player *p)
{
if (TheWindowManager == NULL)
return;
GameWindow *communicatorButton = TheWindowManager->winGetWindowFromId( NULL, NAMEKEY("ControlBar.wnd:PopupCommunicator") );
if (communicatorButton && TheControlBar)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ void ControlBar::deleteBuildTooltipLayout( void )
{
m_showBuildToolTipLayout = FALSE;
prevWindow= NULL;
m_buildToolTipLayout->hide(TRUE);
if (m_buildToolTipLayout) m_buildToolTipLayout->hide(TRUE);
// if(!m_buildToolTipLayout)
// return;
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ BriefingList* GetBriefingTextList(void)
//-------------------------------------------------------------------------------------------------
void UpdateDiplomacyBriefingText(AsciiString newText, Bool clear)
{
if (TheWindowManager == NULL)
return;
GameWindow *listboxSolo = TheWindowManager->winGetWindowFromId(theWindow, NAMEKEY("Diplomacy.wnd:ListboxSolo"));

if (clear)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ void HideInGameChat( Bool immediate )
chatTextEntry->winEnable(FALSE);
TheWindowManager->winSetFocus( NULL );
}
TheWindowManager->winSetFocus( NULL );
if (TheWindowManager) TheWindowManager->winSetFocus( NULL );
}

// ------------------------------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ void Shell::showShellMap(Bool useShellMap )
TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA );

// if the shell is active,we need a background
if(!m_isShellActive)
if(!m_isShellActive || TheWindowManager == NULL)
return;
if(!m_background)
m_background = TheWindowManager->winCreateLayout("Menus/BlankWindow.wnd");
Expand Down
58 changes: 33 additions & 25 deletions GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,13 @@ void GameClient::init( void )
TheDisplayStringManager->setName("TheDisplayStringManager");
}

// create the keyboard
TheKeyboard = createKeyboard();
TheKeyboard->init();
TheKeyboard->setName("TheKeyboard");
if (!TheGlobalData->m_headless)
{
// create the keyboard
TheKeyboard = createKeyboard();
TheKeyboard->init();
TheKeyboard->setName("TheKeyboard");
}

// allocate and load image collection for the GUI and just load the 256x256 ones for now
TheMappedImageCollection = MSGNEW("GameClientSubsystem") ImageCollection;
Expand Down Expand Up @@ -321,11 +324,14 @@ void GameClient::init( void )
if( TheFontLibrary )
TheFontLibrary->init();

// create the mouse
TheMouse = createMouse();
TheMouse->parseIni();
TheMouse->initCursorResources();
TheMouse->setName("TheMouse");
if (!TheGlobalData->m_headless)
{
// create the mouse
TheMouse = createMouse();
TheMouse->parseIni();
TheMouse->initCursorResources();
TheMouse->setName("TheMouse");
}

// instantiate the display
TheDisplay = createGameDisplay();
Expand All @@ -339,23 +345,25 @@ void GameClient::init( void )
TheHeaderTemplateManager->init();
}

// create the window manager
TheWindowManager = createWindowManager();
if( TheWindowManager )
if (!TheGlobalData->m_headless)
{
// create the window manager
TheWindowManager = createWindowManager();
if( TheWindowManager )
{

TheWindowManager->init();
TheWindowManager->setName("TheWindowManager");
// TheWindowManager->initTestGUI();
TheWindowManager->init();
TheWindowManager->setName("TheWindowManager");
// TheWindowManager->initTestGUI();

} // end if

// create the IME manager
TheIMEManager = CreateIMEManagerInterface();
if ( TheIMEManager )
{
TheIMEManager->init();
TheIMEManager->setName("TheIMEManager");
} // end if
// create the IME manager
TheIMEManager = CreateIMEManagerInterface();
if ( TheIMEManager )
{
TheIMEManager->init();
TheIMEManager->setName("TheIMEManager");
}
}

// create the shell
Expand Down Expand Up @@ -397,11 +405,10 @@ void GameClient::init( void )
TheRayEffects->setName("TheRayEffects");
}

TheMouse->init(); //finish initializing the mouse.

// set the limits of the mouse now that we've created the display and such
if( TheMouse )
{
TheMouse->init(); //finish initializing the mouse.
TheMouse->setPosition( 0, 0 );
TheMouse->setMouseLimits();
TheMouse->setName("TheMouse");
Expand Down Expand Up @@ -634,6 +641,7 @@ void GameClient::update( void )
}

// update the window system itself
if (TheWindowManager)
{
TheWindowManager->UPDATE();
}
Expand Down
Loading