Conversation
Greptile SummaryThis PR decouples particle logic updates from render updates. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (14): Last reviewed commit: "tweak(particlesys): Decouple Particles r..." | Re-trigger Greptile |
|
I presume this PR fixes #2467. |
| psys->attachToObject(building); | ||
| Drawable *drawable = building->getDrawable(); | ||
| psys->attachToObject(object); | ||
| Drawable *drawable = object->getDrawable(); |
There was a problem hiding this comment.
This rename was just a side quest from when looking around particle things.
| ParticleSystemInfo::WindMotion windMotion = m_system->getWindMotion(); | ||
| // monitor lifetime | ||
| if (m_lifetimeLeft && --m_lifetimeLeft == 0) | ||
| return false; |
There was a problem hiding this comment.
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.
b94750d to
b86a504
Compare
|
The plane trails do not fade out gracefully. That needs looking into. |
|
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. |
b86a504 to
278f319
Compare
|
I will break the dummy particle manager changes off of this pull. |
|
ParticleSystemManagerDummy changes split off to #2740 |
|
This still needs work. Plane trail issue I have locally fixed but there was another issue that I already forgot. Maybe need to watch old videos. Edit: I remember it was plane exhaust and low frame rate (USA 02 mission intro) |
Would have liked this PR in draft then, so I didn't spend time reviewing it. |
|
@greptileai Particle definition: |
|
The root cause is the interaction between the decoupled logic/render updates, the initial black color of JetLenzflare particles, and the Why it happens: JetLenzflare uses With the decoupled design, At logic = 30 FPS, render = 25 FPS (or lower), the timing is:
The if (frameCount == 0)
{
// Pass one full logic frame before trying to update and delete this potentially now
// invisible particle, because the later render update may fade it in and make it visible.
return true;
}But it only protects the creation frame. It does not protect against the case where render FPS < logic FPS and no The existing todo in the code already captures this: // TheSuperHackers @todo This check is shady for particles that fade in first. A more robust logic would be good.
if (isInvisible())
return false;A robust fix would need to track whether at least one |
|
@greptileai |
|
You're right, I was wrong on both counts — the logic step can't run faster than render in this engine's design, and the Looking more carefully at the decoupling for other potential causes: one thing that stands out is that Could the issue be related to particle position or alpha/color values going out of expected range due to this rate/accumulation split, rather than the lifetime check? If you can describe what specific visual artifact you're seeing, I can narrow down the analysis. |
|
I found the hidden particles at sub 30 fps are an explicit feature in //
// Check if particle is below priorities we allow for this FPS or if it being skipped because
// all particles are being skipped (excluding special fps independent particles at
// getMinDynamicParticleSkipPriority())
//
if( priority < TheGameLODManager->getMinDynamicParticlePriority() ||
(priority < TheGameLODManager->getMinDynamicParticleSkipPriority() &&
TheGameLODManager->isParticleSkipped()) )
return nullptr; |
|
Brother @greptileai, please list potential reasons why this change could have introduced lagging-behind particles on fast moving objects or drawables. It is not observable with all particles, but is 100% with JetLenzflare in a cutscene, that runs at 22 fps. The effect(s): |
|
Here are the potential root causes, ranked by likelihood: 1. Spawn position lag due to update-order change (most likely) In the old code, Now This was literally the reason Lorenzen & Wilczynski originally moved particle update into 2. Bone/animated transform vs. logical object position The particle system is attached to a drawable whose bone positions are resolved during the W3D client pipeline, not during game logic. 3.
4. Multiple logic steps between render frames are not compensated At 22fps render / 30fps logic, there will be render frames where 1 logic step fires and others where 2 fire before the next 5.
In short: The most actionable cause is #1 — particles now spawn with one client-update-cycle of position lag that the original design deliberately avoided by running |
439c4c9 to
6143a7a
Compare
4c0cbb0 to
07c95d1
Compare
|
Fixup commits squashed |
|
Any final words? |
|
To use Codex here, create an environment for this repo. |
|
@codex Review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 07c95d1142
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…rticle::update() into additional functions (#2709)
…icle::draw() (#2709)
07c95d1 to
eb2c0db
Compare
|
I have reworked the decoupling implementation again. All the complicated math has been removed from the draw update and the particle rate updates are back in the logic update and are applied with time scale in the draw update. The advantage of this approach is that the rate integration is now exact to what it did originally and there is no frame rate dependent deviation. Is easier to understand and maintain. |
eb2c0db to
a5d8ea7
Compare
|
Comments polished and fixup commits merged down. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review. WalkthroughThe change separates particle logic updates from render-time integration. ChangesParticle render-phase separation
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~60 minutes Change: Bug fix Sequence Diagram(s)sequenceDiagram
participant FramePacer
participant GameClient
participant ParticleSystemManager
participant W3DDisplay
FramePacer->>GameClient: provide logic-frame phase
GameClient->>ParticleSystemManager: update when game logic advances
W3DDisplay->>ParticleSystemManager: draw with render phase
ParticleSystemManager->>ParticleSystemManager: complete prior phase and draw systems
Merge Risk: ⚪ Minimal · up to No concrete merge-blocking particle regression remains from the reviewed changes. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: 36569756-2404-4f78-ad04-47303a365511
📒 Files selected for processing (11)
Core/GameEngine/Include/Common/FramePacer.hCore/GameEngine/Include/GameClient/ParticleSys.hCore/GameEngine/Source/Common/FramePacer.cppCore/GameEngine/Source/GameClient/System/ParticleSys.cppCore/Libraries/Include/Lib/BaseType.hGenerals/Code/GameEngine/Source/GameClient/GameClient.cppGenerals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cppGenerals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cppGeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cppGeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cppGeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
| DEBUG_ASSERTCRASH(m_colorTargetKey > 0 && m_alphaTargetKey > 0, | ||
| ("Particle::isInvisible - The color and alpha frame keys are expected to be larger than zero")); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Relax the assert; target key zero is a valid state.
In the non-PRESERVE_RETAIL_PARTICLES build, the constructor sets m_alphaTargetKey = 0 and m_colorTargetKey = 0 (lines 349 and 368). update() advances a target key only when frameCount > m_alphaKey[m_alphaTargetKey].frame (lines 489 and 506). If the first alpha or color keyframe has a nonzero frame, the target key stays 0 until that frame passes, and update() still calls isInvisible() for every frameCount >= 1 (line 520). The assert then fires in debug builds for legitimate INI data, for example JetLenzflareExhaust-style systems whose first key starts later than frame 0. The non-assert branches already handle target key 0 correctly, because m_alphaKey[0].frame != 0 makes the particle count as still transitioning.
🛠️ Proposed fix
- DEBUG_ASSERTCRASH(m_colorTargetKey > 0 && m_alphaTargetKey > 0,
- ("Particle::isInvisible - The color and alpha frame keys are expected to be larger than zero"));
+#if PRESERVE_RETAIL_PARTICLES
+ // The retail initialization targets the second key, so the target keys are never zero here.
+ DEBUG_ASSERTCRASH(m_colorTargetKey > 0 && m_alphaTargetKey > 0,
+ ("Particle::isInvisible - The color and alpha frame keys are expected to be larger than zero"));
+#endif📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| DEBUG_ASSERTCRASH(m_colorTargetKey > 0 && m_alphaTargetKey > 0, | |
| ("Particle::isInvisible - The color and alpha frame keys are expected to be larger than zero")); | |
| #if PRESERVE_RETAIL_PARTICLES | |
| // The retail initialization targets the second key, so the target keys are never zero here. | |
| DEBUG_ASSERTCRASH(m_colorTargetKey > 0 && m_alphaTargetKey > 0, | |
| ("Particle::isInvisible - The color and alpha frame keys are expected to be larger than zero")); | |
| #endif |
| } | ||
| #endif | ||
|
|
||
| validateAlphaKeyframes(m_alphaKey, ARRAY_SIZE(m_alphaKey)); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash
# Find every declaration/definition of validateAlphaKeyframes and its parameter types.
rg -n -C6 'validateAlphaKeyframes' --type=cpp --type=cc --type=h
# Confirm the RandomKeyframe and Keyframe definitions.
rg -n -C6 'struct RandomKeyframe|^struct Keyframe' --type=hRepository: TheSuperHackers/GeneralsGameCode
Length of output: 3239
🏁 Script executed:
#!/bin/bash
sed -n '185,240p' Core/GameEngine/Source/GameClient/ParticleSys.cpp
sed -n '1178,1202p' Core/GameEngine/Source/GameClient/ParticleSys.cpp
sed -n '65,90p' Core/GameEngine/Include/GameClient/ParticleSys.h
sed -n '300,340p' Core/GameEngine/Include/GameClient/ParticleSys.hRepository: TheSuperHackers/GeneralsGameCode
Length of output: 2927
🏁 Script executed:
#!/bin/bash
git ls-files | grep -iE '(^|/)ParticleSys\.cpp$|(^|/)ParticleSys\.h$'Repository: TheSuperHackers/GeneralsGameCode
Length of output: 276
🏁 Script executed:
#!/bin/bash
rg -n -C8 'validateAlphaKeyframes' Core/GameEngine/Source/GameClient/System/ParticleSys.cpp Core/GameEngine/Include/GameClient/ParticleSys.h
sed -n '1184,1200p' Core/GameEngine/Source/GameClient/System/ParticleSys.cpp
sed -n '190,215p' Core/GameEngine/Source/GameClient/System/ParticleSys.cpp
sed -n '70,83p' Core/GameEngine/Include/GameClient/ParticleSys.h
sed -n '318,330p' Core/GameEngine/Include/GameClient/ParticleSys.hRepository: TheSuperHackers/GeneralsGameCode
Length of output: 9308
Pass a compatible keyframe type to validateAlphaKeyframes. validateAlphaKeyframes accepts Keyframe*, but ParticleSystemInfo::m_alphaKey is RandomKeyframe[MAX_KEYFRAMES]. The call at ParticleSys.cpp:1193 therefore does not compile. Do not fix this with a cast, because the function accesses frame using the Keyframe layout. Add an overload or template that accepts ParticleSystemInfo::RandomKeyframe*.
a5d8ea7 to
1df79ff
Compare

Merge with Rebase
This change decouples the Particles render update from the logic step.
Split into 6 commits for ease of understanding and review.
TODO
Initialize particle templates with RETAIL_COMPATIBLE_CRC