feature(headless): Add GhostObjectManagerDummy for headless mode#2244
feature(headless): Add GhostObjectManagerDummy for headless mode#2244bobtista wants to merge 6 commits intoTheSuperHackers:mainfrom
Conversation
|
| 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
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
| 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; } |
There was a problem hiding this comment.
Unnecessary static_cast<GhostObjectManager*> since GhostObjectManagerDummy already inherits from GhostObjectManager
| 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.
GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameLogic/W3DGameLogic.h
Outdated
Show resolved
Hide resolved
4d12a95 to
f02b950
Compare
| 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; } |
There was a problem hiding this comment.
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) {} |
…bool arg in GeneralsMD GhostObjectManagerDummy
…bool arg in Generals GhostObjectManagerDummy
Summary
Related
Split from #2139 per @xezon's suggestion to review each dummy class separately.