performance(Drawable): Adjusts Turret Positioning, Recoil and Muzzle for Model Draw to Update Only when Necessary - #2046
Conversation
Skyaero42
left a comment
There was a problem hiding this comment.
Any indication on the performance gain?
This may need extensive testing for mismatching.
| //------------------------------------------------------------------------------------------------- | ||
| void W3DModelDraw::setNeedUpdateTurretPositioning(Bool set) | ||
| { | ||
| m_needUpdateTurretPosition = set; // A simple function with the dangers of an atomic bomb, misuse and it'll cause desync |
| if( xfer->getXferMode() == XFER_LOAD && m_subObjectVec.empty() == FALSE ) | ||
| updateSubObjects(); | ||
|
|
||
| #if !RETAIL_COMPATIBLE_CRC |
There was a problem hiding this comment.
Does this require a xfer version change?
I am not sure, but the devs does mention it being not a major problem. Line handleClientRecoil function are majority just For Loop Checks, each time checks with for loops based on how many Maximum Weapons are in game, (In this case, 3), and checks for each Weapon Slot whether there are any recoil to handle. If it does, it handles the Model Bone Recoil and handles their Muzzle from showing. Every Barrel Bone present will require checks. handleClientTurretPositioning however, is O(n) based. It calls for all Turrets for each Weapon Slot to update Every Frame regardless if there are any changes. For each turret in the runtime it calls for AIUpdateInterface to get their Turret Pitch and Angle, then Transforms the Matrix up to 2 Times (One for Angle, One for Pitch). This is called every frame. The changes just add a go/stop variable at the beginning of both functions, (m_doHandleRecoil, and m_needUpdateTurretPosition) to inform W3DModelDraw to allow the calling of the respective functions. |
|
I think this can be simplified by removing the changes to xfer and instead forcing a recompute right after you load the savegame, like this (do test it): diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
@@ void Object::loadPostProcess()
if( m_xferContainedByID != INVALID_ID )
m_containedBy = TheGameLogic->findObjectByID(m_xferContainedByID);
else
m_containedBy = NULL;
+
+ setNeedUpdateTurretPositioning(TRUE);diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/GeneralsMD/Code/GameEngineDevice/
Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp
@@ void W3DModelDraw::loadPostProcess( void )
// extend base class
DrawModule::loadPostProcess();
+
+ m_needUpdateTurretPosition = TRUE;
+ m_doHandleRecoil = TRUE;This may change the CRC right after loading a savegame but I don't see how that would be a problem |
This change seems logical, tested on my end from loading a retail compatible save and it seems fine. I'll adjust and update my commit. |
Caball009
left a comment
There was a problem hiding this comment.
I don't think the turret positioning implementation is right.
I built a couple of firebases. I moved the camera somewhere else and then back to the firebases and the turrets snap to a certain angle. It looks like a purely visual thing, though, because it didn't cause a mismatch with a client that didn't have PR.
|
Alright, thanks for reporting. Pushed an update, it should fix it. Edit: Nevermind, the issue still persist, just less noticable. I'm looking into it. |
3ea78f3 to
3e96ab1
Compare
|
| Filename | Overview |
|---|---|
| Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp | Core of the optimization: adds dual-flag early-exit to handleClientTurretPositioning (one trailing frame to settle bones) and a reset-then-accumulate pattern for m_doHandleRecoil in handleClientRecoil. The recoil logic correctly covers RECOIL_START/RECOIL (via fall-through) and SETTLE states. Two style violations (same-line if bodies) in the new code. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/AI/TurretAI.cpp | Inserts setNeedUpdateTurretPositioning calls in every state that causes bone movement: setTurretTargetObject, setTurretTargetPosition, recenterTurret, setTurretEnabled, TurretAIAimTurretState, TurretAIRecenterTurretState, and TurretAIIdleScanState. One style rule violation (same-line if in setTurretEnabled). |
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp | Adds m_turretNeedPositioning (initialized FALSE) with a change-detection gate so the drawable is only notified when the boolean actually flips, avoiding redundant per-frame virtual dispatch through the draw module chain. |
| GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp | Adds setNeedUpdateTurretPositioning that fans out to all draw modules via ObjectDrawInterface, guarded by the null-check on di. Straightforward delegation, no issues. |
| GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h | Adds setNeedUpdateTurretPositioning as a pure virtual on ObjectDrawInterface. Only W3DModelDraw inherits this interface, so no other concrete class is broken. |
| Core/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h | Adds the three new member booleans and the virtual override declaration. Clean header change. |
| GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h | Adds m_turretNeedPositioning member and setNeedUpdateTurretPositioning declaration. Aligned with surrounding member declarations. |
| GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h | Adds setNeedUpdateTurretPositioning declaration. Clean header change, no issues. |
Sequence Diagram
sequenceDiagram
participant TAI as TurretAI State Machine
participant Obj as Object
participant Drw as Drawable
participant MDraw as W3DModelDraw
Note over TAI,MDraw: Turret starts moving (e.g. new target)
TAI->>Obj: setNeedUpdateTurretPositioning(TRUE)
Obj->>Obj: m_turretNeedPositioning FALSE→TRUE (changed)
Obj->>Drw: setNeedUpdateTurretPositioning(TRUE)
Drw->>MDraw: setNeedUpdateTurretPositioning(TRUE)
MDraw->>MDraw: "m_needUpdateTurretPosition=true, m_lastNeedUpdateTurretPosition=true"
loop Each client frame (turret moving)
MDraw->>MDraw: "handleClientTurretPositioning()<br/>m_need=true → runs update"
end
Note over TAI,MDraw: Turret reaches target angle
TAI->>Obj: setNeedUpdateTurretPositioning(FALSE)
Obj->>Obj: m_turretNeedPositioning TRUE→FALSE (changed)
Obj->>Drw: setNeedUpdateTurretPositioning(FALSE)
Drw->>MDraw: setNeedUpdateTurretPositioning(FALSE)
MDraw->>MDraw: "m_needUpdateTurretPosition=false<br/>(m_last stays true)"
MDraw->>MDraw: "handleClientTurretPositioning()<br/>m_need=false, m_last=true → runs trailing update<br/>sets m_last=false"
MDraw->>MDraw: "handleClientTurretPositioning()<br/>m_need=false, m_last=false → early return ✓"
Note over TAI,MDraw: Weapon fires — recoil path
TAI->>MDraw: handleWeaponFireFX()
MDraw->>MDraw: "m_doHandleRecoil=TRUE, state=RECOIL_START"
loop Each frame while barrel active
MDraw->>MDraw: "handleClientRecoil()<br/>reset m_doHandleRecoil=FALSE<br/>barrel in RECOIL→sets TRUE<br/>barrel in SETTLE→sets TRUE"
end
MDraw->>MDraw: "handleClientRecoil()<br/>SETTLE→IDLE: m_doHandleRecoil stays FALSE<br/>Next frame: early return ✓"
Reviews (5): Last reviewed commit: "Updated handleRecoil" | Re-trigger Greptile
| if(!m_needUpdateTurretPosition && !m_lastNeedUpdateTurretPosition) | ||
| return; | ||
|
|
||
| m_lastNeedUpdateTurretPosition = m_needUpdateTurretPosition; |
There was a problem hiding this comment.
Single Object flag spans all turret slots
m_needUpdateTurretPosition is one Bool for the entire object, but handleClientTurretPositioning loops over MAX_TURRETS. Any path that clears the flag (e.g. a two-turret unit where RecenterTurretState finishes for slot 1) suppresses updates for every other turret slot. A per-slot flag array, or a reference-counted active-turret counter, would prevent one turret's quiescence from silencing a sibling that is still rotating.
Prompt To Fix With AI
This is a comment left during a code review.
Path: GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp
Line: 2426-2429
Comment:
**Single Object flag spans all turret slots**
`m_needUpdateTurretPosition` is one `Bool` for the entire object, but `handleClientTurretPositioning` loops over `MAX_TURRETS`. Any path that clears the flag (e.g. a two-turret unit where `RecenterTurretState` finishes for slot 1) suppresses updates for every other turret slot. A per-slot flag array, or a reference-counted active-turret counter, would prevent one turret's quiescence from silencing a sibling that is still rotating.
How can I resolve this? If you propose a fix, please make it concise.
Ignore the above, the issue is linked towards my GameClient Drawable Iteration Issue in another PR. That being said, without that Iteration feature even before the fix attempt, I cannot replicate the bug by moving the camera either by scrolling or clicking from the minimap. @Caball009 , able to send a video on replicating it? |
|
Here you go. firebase_turret_angle_snap.mp4 |
|
Thank you, I have reattempted the fix as TurretAI's function expands beyond AIUpdate. I have also tested the fix on the current updated version of the SH Repo. Should be fine now. |
|
Nice, I'll try to make some time to test it later. Can you rebase this branch? |
955c9e1 to
b402a9b
Compare
It's done. Sorry for the wait. |
…Update when Necessary
…a on Client's side
b402a9b to
ab42a7e
Compare
Skyaero42
left a comment
There was a problem hiding this comment.
It's done. Sorry for the wait.
It's not. Issue still persist. Please test your changes before submitting them
Sorry about that. It should be fixed now. |
| { | ||
| const W3DModelDrawModuleData* d = getW3DModelDrawModuleData(); | ||
| if (!(m_curState->m_validStuff & ModelConditionInfo::BARRELS_VALID)) | ||
| if (!(m_curState->m_validStuff & ModelConditionInfo::BARRELS_VALID) || !m_doHandleRecoil) |
There was a problem hiding this comment.
Needs a null guard for m_curState
ae2357e to
0815b77
Compare
|
I think the current solution can be bug prone and hard to maintain because if you forget to set the update flag at some point then you can break visuals in a suble way. The extra complexity can be worth it if the performance benefit is there but we don’t have raw numbers yet. Perhaps use the Tracy profiler to get measurements of the |
Yeah, it would be hard if someone were to modify the direction of the code and building upon existing ones, I could see it becoming an issue towards oversight. Though if it could be further simplified... One of the troublesome solution is to use define headers to separate the features, but it could further complicate things. Though the profiler will be very useful to identify whether the tradeoffs would be worth it for this and #2044 |
Modifies the GameLogic to tell handleClientTurretPositioning() and handleClientRecoil() function in W3DModelDraw to only update when Necessary.