fix(object): Save and load game object list in correct order#2161
Conversation
…nal save order Co-Authored-By: Claude Opus 4.5 <[email protected]>
Greptile Overview
|
| Filename | Overview |
|---|---|
| Generals/Code/GameEngine/Include/GameLogic/Object.h | Added accessor methods for previous object pointer and friend setters for list manipulation during save/load reversal |
| GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h | Added accessor methods for previous object pointer and friend setters for list manipulation during save/load reversal |
| Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | Saves objects in reverse order (newest first) and reverses object list after loading old saves to maintain correct order |
| GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | Saves objects in reverse order (newest first) and reverses object list after loading old saves to maintain correct order |
Sequence Diagram
sequenceDiagram
participant Game
participant GameLogic
participant Xfer
participant ObjectList
alt XFER_SAVE (New Version 10/11)
Game->>GameLogic: xfer(XFER_SAVE)
GameLogic->>ObjectList: Find last object via getNextObject()
loop For each object (newest to oldest)
GameLogic->>ObjectList: Traverse backwards via getPrevObject()
GameLogic->>Xfer: Write object data (newest first)
end
else XFER_LOAD (Old Save version <= 9/10)
Game->>GameLogic: xfer(XFER_LOAD)
loop For each saved object (oldest to newest)
Xfer->>GameLogic: Read object data
GameLogic->>ObjectList: Create object via prependToList()
Note over ObjectList: Prepending reverses order
end
GameLogic->>ObjectList: Reverse entire list
loop For each object
ObjectList->>ObjectList: Swap next/prev pointers
end
Note over ObjectList: Correct order restored
else XFER_LOAD (New Save version >= 10/11)
Game->>GameLogic: xfer(XFER_LOAD)
loop For each saved object (newest to oldest)
Xfer->>GameLogic: Read object data
GameLogic->>ObjectList: Create object via prependToList()
Note over ObjectList: Order already correct
end
end
|
Nice. You'd be also able to test this by saving a game with two Command Centers, then pressing H and expecting to navigate to the same Command Center before and after loading a save. |
That's not related to this change, though the underlying issue is the same. You can uncomment this code to get the same behavior for GeneralsGameCode/GeneralsMD/Code/GameEngine/Source/Common/RTS/Team.cpp Lines 2742 to 2743 in a4482e0 |
|
Could reverse iterating during saving also fix this? |
I think so. That wouldn't work with already existing save games, though. Also, I think it requires changes to multiple functions. |
|
This is a good find.
I support this. Fixing the order of objects after creation is alright, but creating the objects in the correct order to begin with will be superior, because that will respect the right ordering even during creation, which hopefully is logically irrelevant, but technically more correct. |
|
The code actually get simpler if you do the work when saving. This will only work properly for new save games. -> Object *lastObj = nullptr;
for (obj = getFirstObject(); obj; obj = obj->getNextObject())
{
lastObj = obj;
}
for( obj = lastObj; obj; obj = obj->getPrevObject() )New Object* getPrevObject() { return m_prev; }
const Object* getPrevObject() const { return m_prev; } |
|
Good damn single linked list 😄 |
|
Does it make sense to increase the xfer version for this? It would give us a way to distinguish new save games with inverse object list if we ever needed to. |
|
Yes can do. Is not strictly necessary but fair to do as you have described. |
|
I'd be inclined to do both, reverse the list on load if the xfer version is retail, save with correct order with new version and load as is if version is same or greater than new version. |
|
|
||
| // version | ||
| const XferVersion currentVersion = 9; | ||
| const XferVersion currentVersion = 10; |
There was a problem hiding this comment.
I assume RETAIL_COMPATIBLE_XFER_SAVE is not strictly necessary? Retail game can still load these save games correctly?
There was a problem hiding this comment.
I think if an old game tries to load a new save it will just fail gracefully. I don't think this will be an issue - if someone asks a friend with TSH to create a .sav and share it with them maybe? But why would that ever happen? If we need to be able to load new saves on old game, we could use RETAIL_COMPATIBLE_XFER_SAVE
There was a problem hiding this comment.
So I think the way it would work is that version 10 isn't known by retail and isn't checked so it will load the list the same way it always did which will be the new correct order for saves saved after this version, the old incorrect order for save from before this. This new version checks on load so the order loaded will be correct for builds after this PR is merged no matter what version wrote the save.
Tested locally, looks good |
Summary
Test plan
Todo: