Skip to content

tweak(particlesys): Decouple Particles render update from logic step#2709

Open
xezon wants to merge 8 commits into
TheSuperHackers:mainfrom
xezon:xezon/decouple-particle-update
Open

tweak(particlesys): Decouple Particles render update from logic step#2709
xezon wants to merge 8 commits into
TheSuperHackers:mainfrom
xezon:xezon/decouple-particle-update

Conversation

@xezon
Copy link
Copy Markdown

@xezon xezon commented May 14, 2026

Merge with Rebase

This change decouples the Particles render update from the logic step.

Split into 7 commits for ease of understanding and review.

TODO

  • Replicate in Generals
  • Add pull ids to commit titles
  • Investigate plane trail particles
  • Initialize particle templates with RETAIL_COMPATIBLE_CRC

@xezon xezon added Enhancement Is new feature or request Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Rendering Is Rendering related labels May 14, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 14, 2026

Greptile Summary

This PR decouples particle physics integration (velocity, position, color, alpha) from the fixed logic step and moves it into a new ParticleSystem::draw(Real timeScale) render-path call, while ParticleSystem::update() is demoted to keyframe advancement and lifetime management only. The logic update is now driven from GameLogic::update() instead of W3DDisplay, and ParticleSystemManagerDummy is extended with empty draw()/update() overrides and a createParticleSystem stub that returns a well-guarded sentinel (ID = INVALID_PARTICLE_SYSTEM_ID) for headless replay.

  • Introduces frame-rate–independent damping helpers (scaleDamping / scaleAccelDamping) using pow(damping, timeScale) and the correct geometric series formula; the PRESERVE_RETAIL_PARTICLES flag preserves original keyframe timing to avoid known green-flame visual glitches.
  • m_pos/m_lastPos on both Particle and ParticleSystem are renamed to m_logicalPos/m_lastLogicalPos, particle m_lastPos is dropped and xfer versioned accordingly, and timestamps are moved from client-frame to logic-frame counters.
  • GameClient::update() retains a dead, empty block with a commented-out draw() call after the real call was moved to W3DDisplay and the set-up was moved to GameLogic.

Confidence Score: 5/5

The decoupling is architecturally sound and the change is safe to merge.

The frame-rate-independent damping math is correct, the dummy manager ID=0 sentinel prevents phantom registration, keyframe delay flag preserves retail visual behavior, and xfer versioning is properly bumped for the removed m_lastPos field.

No files require special attention; the empty block in GameClient.cpp around line 1261 is a minor cleanup item.

Important Files Changed

Filename Overview
Core/GameEngine/Source/GameClient/System/ParticleSys.cpp Core of the PR: split into update() (keyframes/lifetime) and draw(timeScale) (physics integration with frame-rate-independent damping). New helper functions scaleDamping/scaleAccelDamping are mathematically correct.
Core/GameEngine/Include/GameClient/ParticleSys.h Adds draw()/updateWindMotion()/updateTransform() family of methods; drops m_lastPos from Particle. ParticleSystemManagerDummy extended with guarded createParticleSystem stub (ID=0 sentinel, never registered in manager map).
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Moves setLocalPlayerIndex + ParticleSystemManager::update() call here from GameClient, correctly preceding the logic-step particle update.
GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp Removes updateHeadless() and the old update block from the client path. Leaves a dead-code empty block with a commented-out draw() call.
GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp Single-line change: replaces legacy update() with new draw() in the render path.
Core/Libraries/Include/Lib/BaseType.h Adds compound assignment and binary operators for RGBColor. All binary operators return by value, fixing the dangling-reference issue from a previous review.

Sequence Diagram

sequenceDiagram
    participant GL as GameLogic::update()
    participant PSM_U as ParticleSystemManager::update()
    participant PS_U as ParticleSystem::update()
    participant W3D as W3DDisplay (render path)
    participant PSM_D as ParticleSystemManager::draw()
    participant PS_D as ParticleSystem::draw(timeScale)

    Note over GL,PS_D: New decoupled flow (this PR)

    GL->>PSM_U: setLocalPlayerIndex + update()
    PSM_U->>PS_U: update() — keyframes, lifetime, isInvisible
    PS_U-->>PSM_U: alive / dead

    W3D->>PSM_D: draw() — render path
    PSM_D->>PS_D: applyForce(gravity) + draw(timeScale)
    PS_D-->>PSM_D: physics integrated (vel, pos, color, alpha)
Loading
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
GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp:1261-1267
**Dead code block left behind**

The `setLocalPlayerIndex` + `update()` call was moved to `GameLogic::update()` and the draw call is now in `W3DDisplay`, leaving an empty block with only a commented-out line. The surrounding braces and comment have no functional effect and should be removed to keep the call site clean.

Reviews (4): Last reviewed commit: "fixup! fix(particlesys): Simplify Partic..." | Re-trigger Greptile

Comment thread Core/Libraries/Include/Lib/BaseType.h Outdated
@Caball009
Copy link
Copy Markdown

Caball009 commented May 14, 2026

I presume this PR fixes #2467.

psys->attachToObject(building);
Drawable *drawable = building->getDrawable();
psys->attachToObject(object);
Drawable *drawable = object->getDrawable();
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.

This rename was just a side quest from when looking around particle things.

// vel += accel;
// vel *= damping;
//
static inline Real scaleAccelDamping(Real damping, Real timeScale)
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.

This function was crafted by prompting Chat Gippy many times. I do not understand exactly why it works but it works.

ParticleSystemInfo::WindMotion windMotion = m_system->getWindMotion();
// monitor lifetime
if (m_lifetimeLeft && --m_lifetimeLeft == 0)
return false;
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.

I moved the lifetime check from the bottom of the update to the very top, because there is no reason to go through all the trouble of updating the particle when the lifetime hits zero anyway. So this is a very minor performance improvement maybe.

@xezon
Copy link
Copy Markdown
Author

xezon commented May 17, 2026

The plane trails do not fade out gracefully. That needs looking into.

@Caball009
Copy link
Copy Markdown

Caball009 commented May 17, 2026

The changes in this PR don't seem remotely retail compatible. Like many other replays, this PR makes GR1 mismatch with headless mode for me. The mismatch happens at frame 14227, but only with headless mode. It looks like the CI replay checker is silently broken.


RGBColor& operator+=(const RGBColor& c)
{
red += c.red;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can this cause colors to go out of bound?

same for the other operations

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.

Yes it can. The caller needs to clamp then.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

wouldn't it be preferable that the operator deals with the clamp? Makes it less error-prone

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.

I dont think so. Because it is up to the color user what to do with it. For example the user can normalize the color, or average it like it does in W3DRadar:

// set the color to an average of the colors read
color.red = sampleColor.red / (Real)samples;
color.green = sampleColor.green / (Real)samples;
color.blue = sampleColor.blue / (Real)samples;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

oof, this is such a poor design. It takes away the meaning of red, green and blue, which always should have a value between 0 and 255 (even as intermediate result). If we would want an average of colors, it would be better to have a static RGBColor AverageColor(RGBColor[] colors) method that does this, rather then have color implementations in other classes. Otherwise it makes it error prone and fails to adhere to srp.

This is an issue well beyond this PR, so I'll leave this as resolved.

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 can think of this class like a Vector. Vectors are also manipulated in various ways, for example for creating a unit vector we do normalize. I think that is why they made it 3x float to begin with, instead of just 4x byte.

@xezon xezon force-pushed the xezon/decouple-particle-update branch from b86a504 to 278f319 Compare May 20, 2026 20:02
@xezon
Copy link
Copy Markdown
Author

xezon commented May 21, 2026

I will break the dummy particle manager changes off of this pull.

@xezon
Copy link
Copy Markdown
Author

xezon commented May 21, 2026

ParticleSystemManagerDummy changes split off to #2740

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Is new feature or request Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker Rendering Is Rendering related ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants