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

Conversation

helmutbuhler
Copy link

@helmutbuhler helmutbuhler commented Apr 11, 2025

This PR adds a commandline option -headless which runs the game without any graphics.
This is necessary to implement a silent replay checker (see issue #565). Note that I have that replay checker already implemented, but I will push that in another PR. This is just preparation for that.

Note that right now, if you pass in -headless, the game just runs the Shellmap. Since it's headless, you won't be able to see or do anything. But if you run it in Debug, you can enjoy looking at the log file getting bigger.

Headless mode is implemented here by essentially only running GameLogic, and not the GameClient. Unfortunately, GameLogic depends on the GameClient in some small hacky ways, which makes this a non-trivial task. See also this comment I added:

// 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

As it is now, this PR sets all the client components as listed above to NULL and adds all the appropriate NULL-checks (which are sadly quite a few).
For TheRadar I added a dummy implementation to avoid the NULL-checks in the many callers (this doesn't make much sense in the other components though).

If we decide to fix these separation issues properly in the future, we will be able to revert many of the changes in this PR in the future.

Alternatively, we can also decide to just hide the window in headless mode and initialize DX without using it. Then almost all the changes in this PR become obsolete as well. The question is then whether this will still run in CI.

I would prefer to keep the changes in this PR and to clean up these separation issues properly long-term. Having logic and client properly separated can help in the future when we decide to increase the framerate of the client, or if we want to implement a headless server. It's also nice to automatically test that the logic works without the client.

TODO

  • Replicate in Generals

…th logging enabled (if you define RELEASE_DEBUG_LOGGING in Debug.h)
…in GameLogic::startNewGame to improve CRC Logging
…e with -SaveDebugCRCPerFrame.

Also remove Bool dumped (wasn't really used and was in the way for this new code)
…d and not intended because it spams the crclogging with mostly useless data.
…ewGame instead of GameLogic::prepareNewGame so it works when loading a game.
# Conflicts:
#	GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
@helmutbuhler
Copy link
Author

Alright, I just pushed another update.

I tested headless mode with about 1400 various replays and they all work as expected now. I had to fix a few more issues:

  • mss32.dll was crashing in rare and random situations when multiple instances were running. I added AudioManagerDummy to fix this
  • GameWindowManagerDummy had a bug and was causing some memory corruption. I fixed that.
  • Maps with bridges depend on m_bridgeBuffer in TheTerrainRenderObject, so I include that now in headless mode without the attached vertexbuffers.

I also added MouseDummy to eliminate some more NULL-checks. All issues should be solved now. Thanks again to @jaapdeheer for helping me fix some of them :)

@@ -655,13 +658,17 @@ Bool W3DTerrainVisual::load( AsciiString filename )
it = NULL;
}
// add our terrain render object to the scene
W3DDisplay::m_3DScene->Add_Render_Object( m_terrainRenderObject );
if (W3DDisplay::m_3DScene != NULL)
Copy link

Choose a reason for hiding this comment

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

For the final PR with the replay simulation, I won't call GameClient::update at all, so don't worry :) I just wanted to make this PR testable and added some checks in the rendering to make it work. I can remove those checks in the next PR.

How will this look? Which null tests will be gone?

@xezon xezon added this to the Important features milestone May 18, 2025
@helmutbuhler
Copy link
Author

helmutbuhler commented May 18, 2025

How will this look? Which null tests will be gone?

I just pushed an update where I commented the checks that will be gone in the Replay Simulation PR

@xezon
Copy link

xezon commented May 18, 2025

Do you need help replicating this to Generals?

@helmutbuhler
Copy link
Author

Do you need help replicating this to Generals?

I got the diff with:
git diff 291382d374e15fabd86b393324badaa79f9bf5ce..6bb77c0f09b49f73a4ee869d8d833c0ac54ffb8b > changes.patch

But there are quite a few conflicts with Generals. (Doesn't surprise me)
So I suggest we do that in a separate PR

@helmutbuhler
Copy link
Author

But I can give it a try and will call for help when I get stuck

@xezon
Copy link

xezon commented May 18, 2025

I can help you with it.

@xezon
Copy link

xezon commented May 19, 2025

I think the "Merge main" commits make this really hard to replicate to Generals with that method. I will do it by hand tomorrow.

@helmutbuhler
Copy link
Author

I can do it, I just want to wait until this is approved because I don't want to sync two PRs if more changes are requested.

The merge commits are not the problem, it's that there are differences between Gen and ZH where this PR does changes. You can get the diff with the merge base with:

git diff $(git merge-base main branch)..branch

or use the git command I posted above.

@helmutbuhler
Copy link
Author

Finally merged, thanks everyone! PR for the changes in Generals is here: #899

@helmutbuhler helmutbuhler merged commit c25825e into TheSuperHackers:main May 22, 2025
18 checks passed
@xezon xezon added Debug Is mostly debug functionality Enhancement Is new feature or request and removed Enhancement Is new feature or request labels May 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Debug Is mostly debug functionality Enhancement Is new feature or request Major Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants