Skip to content

fix(particlesys): Simplify ParticleSystemManagerDummy setup#2740

Open
xezon wants to merge 1 commit into
TheSuperHackers:mainfrom
xezon:xezon/fix-particlesystemmanagerdummy
Open

fix(particlesys): Simplify ParticleSystemManagerDummy setup#2740
xezon wants to merge 1 commit into
TheSuperHackers:mainfrom
xezon:xezon/fix-particlesystemmanagerdummy

Conversation

@xezon
Copy link
Copy Markdown

@xezon xezon commented May 21, 2026

This change simplifies the ParticleSystemManagerDummy setup. It no longer creates and updates particle systems in headless mode with the dummy implementation. With RETAIL_COMPATIBLE_CRC it still INI parses the particle system templates to make sure the Logic CRC is not affected.

TODO

  • Mass test Replay simulations
  • Replicate in Generals

@xezon xezon added Enhancement Is new feature or request Debug Is mostly debug functionality ThisProject The issue was introduced by this project, or this task is specific to this project labels May 21, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 21, 2026

Greptile Summary

This PR simplifies ParticleSystemManagerDummy so that headless/replay mode no longer creates, tracks, or updates particle systems at all. Under RETAIL_COMPATIBLE_CRC, INI template loading is preserved to keep the logic CRC stable, while createParticleSystem is short-circuited to a static no-op sentinel.

  • isDummy() is added as a virtual escape hatch so call-sites in FXList.cpp and BeaconClientUpdate.cpp can suppress DEBUG_ASSERTCRASH failures that are expected when templates are not loaded.
  • updateHeadless() is removed from GameClient along with its per-replay-frame call; the dummy's update() override is now the authoritative suppression point.
  • Under RETAIL_COMPATIBLE_CRC, createParticleSystem returns a process-lifetime static StaticParticleSystem (ID = INVALID_PARTICLE_SYSTEM_ID) so tracked-list bookkeeping and the base reset() remain safe.

Confidence Score: 4/5

Safe to merge after replay mass-testing confirms CRC parity; the static singleton design is intentional and the manager's bookkeeping invariants hold.

The core bookkeeping invariants are preserved: StaticParticleSystem uses INVALID_PARTICLE_SYSTEM_ID (0) so it is never registered in m_allParticleSystemList, meaning the inherited reset() path in RETAIL_COMPATIBLE_CRC mode is safe. The one design question is the shared mutable singleton returned to every createParticleSystem caller — harmless in single-threaded headless mode today, but undocumented. The PR's own TODO flags that replay mass-testing is still pending, which is the right gate for this change.

Core/GameEngine/Include/GameClient/ParticleSys.h — specifically the RETAIL_COMPATIBLE_CRC branch of createParticleSystem and the absence of reset() / init() overrides in that mode.

Important Files Changed

Filename Overview
Core/GameEngine/Include/GameClient/ParticleSys.h Adds isDummy() virtual method, makes createParticleSystem virtual, and expands ParticleSystemManagerDummy with conditional init/reset/update overrides and a RETAIL_COMPATIBLE_CRC static-singleton path for createParticleSystem; ID=0 (INVALID_PARTICLE_SYSTEM_ID) is intentionally used so the singleton is never tracked by the manager's internal lists.
Core/GameEngine/Source/Common/ReplaySimulation.cpp Removes the updateHeadless() call from the replay loop; no longer needed since the dummy manager's update() is now a no-op and createParticleSystem no longer adds systems to tracked lists.
Core/GameEngine/Source/GameClient/Drawable/Update/BeaconClientUpdate.cpp Guards two DEBUG_ASSERTCRASH calls with isDummy() to suppress expected template-not-found failures in headless mode.
Core/GameEngine/Source/GameClient/FXList.cpp Guards DEBUG_ASSERTCRASH for missing particle template with isDummy() check, consistent with BeaconClientUpdate change.
GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h Removes the updateHeadless() declaration, now that the dummy manager itself suppresses particle work.
GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp Removes the updateHeadless() implementation; the detailed explanatory comment is gone with it.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
Core/GameEngine/Include/GameClient/ParticleSys.h:873-884
**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.

Reviews (1): Last reviewed commit: "fix(particlesys): Simplify ParticleSyste..." | Re-trigger Greptile

Comment on lines +873 to +884
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
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

@xezon xezon added Gen Relates to Generals ZH Relates to Zero Hour labels May 22, 2026
struct StaticParticleSystem : public ParticleSystem
{
StaticParticleSystem(const StaticParticleSystemTemplate *sysTemplate)
: ParticleSystem(sysTemplate, ParticleSystemID(0), TRUE) {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Perhaps set createSlaves here to FALSE

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 Gen Relates to Generals ThisProject The issue was introduced by this project, or this task is specific to this project ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants