fix(particlesys): Simplify ParticleSystemManagerDummy setup#2740
Conversation
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Include/GameClient/ParticleSys.h | Core change: adds isDummy() virtual method, makes createParticleSystem virtual, and reworks ParticleSystemManagerDummy with conditional RETAIL_COMPATIBLE_CRC logic — well-structured and safe. |
| Core/GameEngine/Source/Common/ReplaySimulation.cpp | Removes the now-unnecessary updateHeadless() call from the replay simulation loop; correct given that the dummy manager's update() is now a no-op. |
| Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp | Suppresses two DEBUG_ASSERTCRASH calls when isDummy() is true, preventing spurious asserts in non-RETAIL_COMPATIBLE_CRC headless mode where no templates are loaded. |
| Core/GameEngine/Source/GameClient/FXList.cpp | Same isDummy() guard pattern as BeaconClientUpdate.cpp — assert is suppressed when in dummy mode and template lookup returns null. |
| GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h | Removes the updateHeadless() declaration — clean deletion matching the GameClient.cpp removal. |
| GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp | Removes the updateHeadless() implementation that previously called TheParticleSystemManager->update() on each replay frame — no longer needed. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[ReplaySimulation loop] -->|before PR| B[TheGameClient->updateHeadless]
B --> C[TheParticleSystemManager->update real update on base class]
A -->|after PR| D[No updateHeadless call]
E[createParticleSystem called] --> F{isDummy?}
F -->|No| G[Real ParticleSystemManager creates tracked ParticleSystem]
F -->|Yes - RETAIL_COMPATIBLE_CRC| H[Return static dummySystem ID=INVALID not registered]
F -->|Yes - no RETAIL_COMPATIBLE_CRC| I[Return nullptr]
J[DEBUG_ASSERTCRASH] --> K{isDummy OR template found?}
K -->|No| L[Assert fires]
K -->|Yes| M[Continue silently]
Reviews (2): Last reviewed commit: "Pass false" | Re-trigger Greptile
| virtual ParticleSystem *createParticleSystem(const ParticleSystemTemplate *sysTemplate, Bool createSlaves = TRUE) override | ||
| { | ||
| #if RETAIL_COMPATIBLE_CRC | ||
| if (sysTemplate == nullptr) | ||
| return nullptr; | ||
| static StaticParticleSystemTemplate dummyTemplate; | ||
| static StaticParticleSystem dummySystem(&dummyTemplate); | ||
| return &dummySystem; | ||
| #else | ||
| return nullptr; | ||
| #endif | ||
| } |
There was a problem hiding this comment.
Shared mutable static singleton returned to callers
createParticleSystem returns &dummySystem — a single static object shared by every caller. Code in FXList.cpp calls setLocalTransform, rotateLocalTransformX/Y/Z, and setPosition on the returned pointer inside a loop where each iteration overwrites the previous iteration's state on the same object. BeaconClientUpdate.cpp calls attachToDrawable and tintAllColors on it, and stores system->getSystemID() which always equals INVALID_PARTICLE_SYSTEM_ID (0) — causing the beacon to re-enter the creation path on every subsequent call.
This is benign in single-threaded headless mode since update() is a no-op and nothing is rendered, but a brief comment explaining that the returned pointer is a non-owning, non-unique token (and that callers must not delete or track it by ID) would prevent future misuse when this class is re-used.
Prompt To Fix With AI
This is a comment left during a code review.
Path: Core/GameEngine/Include/GameClient/ParticleSys.h
Line: 873-884
Comment:
**Shared mutable static singleton returned to callers**
`createParticleSystem` returns `&dummySystem` — a single static object shared by every caller. Code in `FXList.cpp` calls `setLocalTransform`, `rotateLocalTransformX/Y/Z`, and `setPosition` on the returned pointer inside a loop where each iteration overwrites the previous iteration's state on the same object. `BeaconClientUpdate.cpp` calls `attachToDrawable` and `tintAllColors` on it, and stores `system->getSystemID()` which always equals `INVALID_PARTICLE_SYSTEM_ID (0)` — causing the beacon to re-enter the creation path on every subsequent call.
This is benign in single-threaded headless mode since `update()` is a no-op and nothing is rendered, but a brief comment explaining that the returned pointer is a non-owning, non-unique token (and that callers must not delete or track it by ID) would prevent future misuse when this class is re-used.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
We expect single threaded use right now.
We could add thread_local for non VC6 builds to protect future multithreaded users, but it is useless because ParticleSystemManager::createParticleSystem is also not thread safe.
| return nullptr; | ||
| static StaticParticleSystemTemplate dummyTemplate; | ||
| static StaticParticleSystem dummySystem(&dummyTemplate); | ||
| return &dummySystem; |
There was a problem hiding this comment.
After #2742 is merged, we should be able to remove this path, because particle systems will no longer be needed for correct CRC.
|
I tested this against ~2500 (short) replays and saw no mismatches because of this change. I can check again after #2742 is merged just to be sure. |
| // Must not overload init to keep loading the particle system templates, | ||
| // which are unfortunately needed to preserve the correct logic crc. |
There was a problem hiding this comment.
This is going to change after #2742, right?
There was a problem hiding this comment.
Loading particle system templates will still be required under RETAIL CRC because calls to logic random value is conditioned by existence of some templates.
This change simplifies the ParticleSystemManagerDummy setup. It no longer creates and updates particle systems in headless mode with the dummy implementation. With
RETAIL_COMPATIBLE_CRCit still INI parses the particle system templates to make sure the Logic CRC is not affected.TODO