bugfix(contain): Ensure container exists when checking if a specific rider is free to exit#2513
bugfix(contain): Ensure container exists when checking if a specific rider is free to exit#2513Stubbjax wants to merge 3 commits intoTheSuperHackers:mainfrom
Conversation
|
| Filename | Overview |
|---|---|
| Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp | Adds isEffectivelyDead() guard in AIExitState::update() under !RETAIL_COMPATIBLE_CRC to short-circuit exit logic when the container is already dead |
| GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp | Mirror of the Generals change — identical isEffectivelyDead() guard added to the Zero Hour copy of AIExitState::update() |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[AIExitState::update] --> B{goal != nullptr?}
B -- No --> Z[STATE_FAILURE]
B -- Yes --> C{goalAI->getAiFreeToExit == WAIT_TO_EXIT?}
C -- Yes --> Y[STATE_CONTINUE]
C -- No --> D{goalExitInterface == nullptr?}
D -- Yes --> Z
D -- No --> E{goal->isEffectivelyDead?\n#if !RETAIL_COMPATIBLE_CRC}
E -- Yes --> Z
E -- No --> F{goalExitInterface->isExitBusy?}
F -- Yes --> Y
F -- No --> G[reserveDoorForExit]
G --> H{exitDoor == DOOR_NONE_AVAILABLE?}
H -- Yes --> Z
H -- No --> I[exitObjectViaDoor]
I --> J{state changed?}
J -- Yes --> Y
J -- No --> K[STATE_SUCCESS]
Reviews (3): Last reviewed commit: "refactor: Convert fix into a standalone ..." | Re-trigger Greptile
Caball009
left a comment
There was a problem hiding this comment.
If this can lead to a crash, the PR should include a crash reproduction.
Why? |
That strikes me as an odd question. The PR is labeled as a crash fix. The PR description and the surrounding code suggest that this shouldn't be a |
|
Yes it is concerning that this function is called on an object that is not a rider. The assert in this function makes clear that this should not happen. Can you check why it happens and if it can be fixed higher up? Even after this fix it would still be a bug, because the assert can be hit. |
I ask because I've only ever seen this to be a suggestion rather than a requirement. A developer might not be in a position to spend hours replicating whatever potentially obscure or convoluted scenario leads to a crash. It could take days or even weeks to figure out. Do we let users keep crashing until that happens? When it comes to live software, plugging the hole is typically the priority; understanding how and why can come later. |
Thankfully it was an easy 3-minute reproduction in this case: Just have a vehicle in the middle of evacuation when it dies. A full Assault Outpost is the best unit for the job. Simply evacuate it and then have it die before all troops have exited. |
The PR description didn't say that this was a crash hotfix, and even if it did, TSH typically doesn't move so quickly that those sort of PRs are merged fast enough. The GeneralsOnline devs put out their own hotfix an hour after the creation of this PR, so there's no longer a sense of urgency:
Thank you, that was helpful. I have dismissed my review, but I do think we should check why this happens at all. FWIW I was able to reproduce this issue with an Attack Outpost but not a Troop Crawler. I created a replay for VC6, but it also works with VS22: crash_containedby.zip Probably not surprising, if Here's the callstack if needed. |
|
Can we stop the evacuation sequence when the container dies? |
| // are not free to exit. | ||
| DEBUG_ASSERTCRASH(specificObject->getContainedBy(), ("rider must be contained")); | ||
| if (specificObject->getContainedBy()->isDisabledByType(DISABLED_HELD)) | ||
| if (specificObject->getContainedBy() && specificObject->getContainedBy()->isDisabledByType(DISABLED_HELD)) |
There was a problem hiding this comment.
Is it worth storing the result from getContainedBy() so we don't call it two or three times here?
|
Needs more investigation to prevent non-existing riders exiting. |
The occupants now abort the |
The pull desc needs updating. |
| if( goalExitInterface == nullptr ) | ||
| #else | ||
| // TheSuperHackers @bugfix Stubbjax 03/05/2026 Stop trying to exit if the container is dead, as we are already ejected. | ||
| if (goalExitInterface == nullptr || goal->isEffectivelyDead()) |
There was a problem hiding this comment.
Better write like so:
#if !RETAIL_COMPATIBLE_CRC
if (goal->isEffectivelyDead())
return STATE_FAILURE;
#endif| if( goalExitInterface == nullptr ) | ||
| #else | ||
| // TheSuperHackers @bugfix Stubbjax 03/05/2026 Stop trying to exit if the container is dead, as we are already ejected. | ||
| if (goalExitInterface == nullptr || goal->isEffectivelyDead()) |
There was a problem hiding this comment.
Now the next question is, why do the exited passengers still have an AI State Machine for exiting the container? I wonder if another piece of the puzzle here is to cancel the AIExitState before passengers are ejected from the dead host container. I do not know if that is possible. There are functions such as: AIStateMachine::clear, AIStateMachine::resetToDefaultState to play with.
There was a problem hiding this comment.
Now the next question is, why do the exited passengers still have an AI State Machine for exiting the container? I wonder if another piece of the puzzle here is to cancel the AIExitState before passengers are ejected from the dead host container. I do not know if that is possible. There are functions such as:
AIStateMachine::clear,AIStateMachine::resetToDefaultStateto play with.
I don't believe that is currently possible or even desirable; the container should generally not be aware of the internal AI states of its contents. An object could maybe notify its state machine of its destruction, but a destroyed container should not be telling its contents what state to take or how to behave - I'd expect that to be a decision made by the states themselves.
There was a problem hiding this comment.
Yes that can be solved by adding a OnContainerDestroyed to passenger objects and then call that on container destruction.
Occupants of a container now abort their
AIExitStateif their container dies. As a result, occupants will no longer attempt to reserve or exit doors on destroyed containers, which could potentially result in accessing invalid states of the original container object.