Skip to content

feature(headless): Add GhostObjectManagerDummy for headless mode#2244

Open
bobtista wants to merge 6 commits intoTheSuperHackers:mainfrom
bobtista:bobtista/ghost-object-manager-dummy
Open

feature(headless): Add GhostObjectManagerDummy for headless mode#2244
bobtista wants to merge 6 commits intoTheSuperHackers:mainfrom
bobtista:bobtista/ghost-object-manager-dummy

Conversation

@bobtista
Copy link

@bobtista bobtista commented Feb 2, 2026

Summary

  • Adds GhostObjectManagerDummy class that provides no-op implementations for headless mode
  • Factory in W3DGameLogic returns dummy when m_headless is true
  • Intentionally does NOT override crc/xfer/loadPostProcess to maintain save compatibility

Related

Split from #2139 per @xezon's suggestion to review each dummy class separately.

@greptile-apps
Copy link

greptile-apps bot commented Feb 2, 2026

Greptile Summary

This PR introduces GhostObjectManagerDummy, a no-op subclass of GhostObjectManager, and wires it into the W3DGameLogic factory so that headless-mode builds skip all W3D snapshot/scene management for fogged objects. The factory signature is widened to accept a bool headless parameter (defaulting to false for backward compatibility), and TheGlobalData->m_headless is forwarded from GameLogic::init(). The PR is mirrored identically across both Generals/ and GeneralsMD/.

Key points:

  • GhostObjectManagerDummy deliberately does not override crc/xfer/loadPostProcess, inheriting the lightweight base-class serialisation path (only m_localPlayer is xferred), which correctly maintains save-game compatibility.
  • The base GhostObjectManager itself already has no-op implementations of all the overridden methods, so the dummy is structurally equivalent to directly instantiating the base class; the explicit subclass is a reasonable choice for documenting intent and providing a named type for the headless branch.
  • Both W3DGhostObject.cpp files remove the W3DDisplay::m_3DScene != nullptr guard in W3DRenderObjectSnapshot::addToScene(). While safe by design (the dummy prevents W3DGhostObject from being instantiated in headless mode), this is an unmentioned behavioural change that removes a defensive null check and deserves an explanatory comment or assertion.

Confidence Score: 4/5

  • Safe to merge with minor follow-up recommended on the removed null guard in W3DGhostObject.cpp.
  • The core headless dummy pattern is correct, well-scoped, and mirrored cleanly across both game directories. The only concern is the unexplained removal of the W3DDisplay::m_3DScene null check in addToScene(), which changes a graceful-fail into a potential crash if the invariant is ever violated in non-headless mode.
  • Generals/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DGhostObject.cpp and its GeneralsMD counterpart — specifically the removed null guard in W3DRenderObjectSnapshot::addToScene().

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Include/GameLogic/GhostObject.h Adds GhostObjectManagerDummy as a clean no-op subclass of GhostObjectManager for headless mode; intentionally omits crc/xfer/loadPostProcess overrides to inherit the lightweight base-class serialisation path.
Generals/Code/GameEngineDevice/Include/W3DDevice/GameLogic/W3DGameLogic.h Factory override correctly selects GhostObjectManagerDummy or W3DGhostObjectManager based on the headless flag; static_cast is retained for VC6 compatibility as confirmed by a prior reviewer thread.
Generals/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DGhostObject.cpp Removes the W3DDisplay::m_3DScene != nullptr null-guard in W3DRenderObjectSnapshot::addToScene() without explanation; while safe under the new dummy design, it eliminates a defensive check and silently changes failure mode from a graceful no-op to a null dereference crash.
Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Correctly threads TheGlobalData->m_headless into the factory call; base-class implementation ignores the parameter and always returns a plain GhostObjectManager, which is the correct non-W3D behaviour.
GeneralsMD/Code/GameEngine/Include/GameLogic/GhostObject.h Mirror of the Generals change; identical GhostObjectManagerDummy class added with the correct "Zero Hour" header.
GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DGhostObject.cpp Same null-guard removal as the Generals counterpart; same concern about removing defensive programming without explanation.

Sequence Diagram

sequenceDiagram
    participant GL as GameLogic::init()
    participant F as createGhostObjectManager(headless)
    participant D as GhostObjectManagerDummy
    participant W as W3DGhostObjectManager
    participant PM as PartitionManager

    GL->>F: createGhostObjectManager(m_headless)
    alt headless == true
        F->>D: NEW GhostObjectManagerDummy
        D-->>GL: TheGhostObjectManager (no-op)
        PM->>D: addGhostObject(obj, pd)
        D-->>PM: nullptr (no-op)
    else headless == false
        F->>W: NEW W3DGhostObjectManager
        W-->>GL: TheGhostObjectManager (full W3D)
        PM->>W: addGhostObject(obj, pd)
        W-->>PM: W3DGhostObject*
    end
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: Generals/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DGhostObject.cpp
Line: 156-161

Comment:
**Defensive null guard removed without explanation**

The prior check `W3DDisplay::m_3DScene != nullptr &&` ensured `Add_Render_Object` would never be called on a null scene. Although headless mode now uses `GhostObjectManagerDummy` (preventing `W3DGhostObject` instances from being created there), this code path can still be exercised in non-headless mode. If `m_3DScene` is ever null under non-headless conditions (e.g. during teardown, an abnormal initialisation sequence, or future refactors) the dereference on line 159 will crash rather than return `false`.

Consider adding a `DEBUG_ASSERTCRASH` for the null case to preserve the crash-on-invalid invariant in debug builds while making the intent explicit:

```suggestion
	if (!m_robj->Is_In_Scene())
	{
		DEBUG_ASSERTCRASH(W3DDisplay::m_3DScene != nullptr, ("W3DRenderObjectSnapshot::addToScene - m_3DScene is null"));
		W3DDisplay::m_3DScene->Add_Render_Object(m_robj);
		return true;
	}
```

The same change applies to `GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameLogic/W3DGhostObject.cpp:158`.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: ecf1ecf

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

virtual TerrainLogic *createTerrainLogic( void ) { return NEW W3DTerrainLogic; };
virtual GhostObjectManager *createGhostObjectManager(void) { return NEW W3DGhostObjectManager; }
// TheSuperHackers @feature bobtista 19/01/2026 Use dummy for headless mode
virtual GhostObjectManager *createGhostObjectManager(void) { return TheGlobalData->m_headless ? static_cast<GhostObjectManager*>(NEW GhostObjectManagerDummy) : NEW W3DGhostObjectManager; }
Copy link

Choose a reason for hiding this comment

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

Unnecessary static_cast<GhostObjectManager*> since GhostObjectManagerDummy already inherits from GhostObjectManager

Suggested change
virtual GhostObjectManager *createGhostObjectManager(void) { return TheGlobalData->m_headless ? static_cast<GhostObjectManager*>(NEW GhostObjectManagerDummy) : NEW W3DGhostObjectManager; }
virtual GhostObjectManager *createGhostObjectManager(void) { return TheGlobalData->m_headless ? NEW GhostObjectManagerDummy : NEW W3DGhostObjectManager; }
Prompt To Fix With AI
This is a comment left during a code review.
Path: Generals/Code/GameEngineDevice/Include/W3DDevice/GameLogic/W3DGameLogic.h
Line: 64:64

Comment:
Unnecessary `static_cast<GhostObjectManager*>` since `GhostObjectManagerDummy` already inherits from `GhostObjectManager`

```suggestion
	virtual GhostObjectManager *createGhostObjectManager(void) { return TheGlobalData->m_headless ? NEW GhostObjectManagerDummy : NEW W3DGhostObjectManager; }
```

How can I resolve this? If you propose a fix, please make it concise.

@bobtista bobtista force-pushed the bobtista/ghost-object-manager-dummy branch from 4d12a95 to f02b950 Compare February 24, 2026 23:51
virtual TerrainLogic *createTerrainLogic() { return NEW W3DTerrainLogic; };
virtual GhostObjectManager *createGhostObjectManager() { return NEW W3DGhostObjectManager; }
// TheSuperHackers @feature bobtista 19/01/2026 Use dummy for headless mode
virtual GhostObjectManager *createGhostObjectManager() { return TheGlobalData->m_headless ? static_cast<GhostObjectManager*>(NEW GhostObjectManagerDummy) : NEW W3DGhostObjectManager; }
Copy link

Choose a reason for hiding this comment

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

I suggest make the variant a Bool argument. Then the GlobalData include can be removed again.

virtual GhostObject *addGhostObject(Object *object, PartitionData *pd) { return nullptr; }
virtual void removeGhostObject(GhostObject *mod) {}
virtual void updateOrphanedObjects(int *playerIndexList, int playerIndexCount) {}
virtual void releasePartitionData(void) {}
Copy link

Choose a reason for hiding this comment

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

override

Several times.

@xezon xezon added Enhancement Is new feature or request Debug Is mostly debug functionality labels Mar 10, 2026
…bool arg in GeneralsMD GhostObjectManagerDummy
…bool arg in Generals GhostObjectManagerDummy
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants