perf(gamemessage): Optimize argument list of GameMessage by replacing linked list with vector#2700
Conversation
…rs#2630) Simpler and cleaner control. Improves loop performance significantly via cache hits and brings random index access down to O(1)
Only affects parts that relied on the linked list functionality of the GameMessageArguments
|
| Filename | Overview |
|---|---|
| Generals/Code/GameEngine/Include/Common/MessageStream.h | Removes m_next/m_argList/m_argTail/m_argCount; adds std::vector<const GameMessageArgument*> and getArguments() accessor with correct const-correctness. Inline getArgumentCount() adds a DEBUG_ASSERTCRASH guard. |
| Generals/Code/GameEngine/Source/Common/MessageStream.cpp | Constructor, destructor, getArgument(), getArgumentDataType(), and allocArg() all correctly updated to use vector semantics; const_cast used appropriately in destructor. |
| GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h | Mirror of Generals changes; same vector refactoring applied correctly for Zero Hour expansion code path. |
| GeneralsMD/Code/GameEngine/Source/Common/MessageStream.cpp | Mirror of Generals .cpp changes; all linked-list traversal replaced with index-based vector access. |
| Core/GameEngine/Include/GameNetwork/NetCommandMsg.h | m_numArgs, m_argSize, m_argList (ptr), m_argTail removed; replaced with std::vector<const GameMessageArgument*>. Forward declaration of GameMessageArgument added. std::vector is available transitively via NetworkDefs.h → MessageStream.h → STLTypedefs.h. |
| Core/GameEngine/Source/GameNetwork/NetCommandMsg.cpp | Constructor, copy-constructor, destructor, addArgument(), and constructGameMessage() all cleanly updated; reserve() call added when copying args from a GameMessage. |
| Generals/Code/GameEngine/Source/Common/Recorder.cpp | Switches from getArgumentCount()/getArgumentDataType()/getArgument() loop to getArguments() vector range-for, simplifying the write path. |
| GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp | Same Recorder simplification as the Generals path. |
Reviews (7): Last reviewed commit: "fix: remove inconsistent todo leftover c..." | Re-trigger Greptile
… unnecessary test before uint conversion (TheSuperHackers#2630)
- Return GameMessageArguments as const - Style fixes (remove blank space, indent and space out too packed methods) - Remove vector includes; already included from precompiled header
|
Good effort. Generals fails to compile for now, but it's probably most convenient to wait for xezon's feedback before you replicate the changes from ZH to Gen. |
|
Is Caballs review addressed? |
…erHackers#2630) (cherry picked from commit 0c8438a88926ff2a2797854f9f17d94ce6daf177)
|
Addressed now @xezon Also included the mirrored changes into Generals/ side |
There was a problem hiding this comment.
Generals is out-of-sync with Zero Hour.
There was a problem hiding this comment.
I find it much easier to replicate the changes to Generals with a script. It tends to work quite well.
@echo off
FOR /F "delims=" %%b IN ('git merge-base --fork-point main') DO git diff %%b > changes.patch
git apply -p2 --directory=Generals --reject --whitespace=fix changes.patch
del changes.patch
pause
You can save this as a batch script and execute it in the project directory.
There was a problem hiding this comment.
This is very handy script, thank you 🍻 Ran the remaining inconsistencies with this script just in case, should be fully in sync now
There was a problem hiding this comment.
Looks like you missed this one, though. Perhaps worth a try to revert all Generals changes, and do everything with the script?
There was a problem hiding this comment.
Wanted to avoid that, basically did it to get the proper patch file already though. Then again, I was hasty and made yet another commit for that single comment. If there is still some issue, or you feel it would be better to do with reverts and fully with script - I'll do that next
There was a problem hiding this comment.
The comments are out of sync between Gen and ZH. Please revert all changes in the Generals code and replicate them in one go with the script.
xezon
left a comment
There was a problem hiding this comment.
I recommend to replicate to Generals not before approval.
| deleteInstance(arg); | ||
| arg = m_argList; | ||
| for (size_t i = 0; i < m_argList.size(); ++i) { | ||
| deleteInstance(const_cast<GameMessageArgument*>(m_argList[i])); |
There was a problem hiding this comment.
Instead of const cast, make the vector element non-const.
| @@ -127,21 +123,10 @@ NetGameCommandMsg::~NetGameCommandMsg() { | |||
| */ | |||
| void NetGameCommandMsg::addArgument(const GameMessageArgumentDataType type, GameMessageArgumentType arg) | |||
There was a problem hiding this comment.
Since this function is small and only called once, you can also inline it in the constructor, which reduces code complexity.
| addArgument(msg->getArgumentDataType(i), *(msg->getArgument(i))); | ||
|
|
||
| const std::vector<const GameMessageArgument*>& args = msg->getArguments(); | ||
| m_argList.reserve(args.size()); |
There was a problem hiding this comment.
If you inline code of addArgument function here, then this can do m_argList.resize(args.size()), which is even better.
There was a problem hiding this comment.
Because vec[i] = value will do a bit less work than vec.push_back(value)
| nextArg = arg->m_next; | ||
| deleteInstance(arg); | ||
| for( size_t i = 0; i < m_argList.size(); ++i ) { | ||
| deleteInstance(const_cast<GameMessageArgument*>(m_argList[i])); |
There was a problem hiding this comment.
Instead of const cast, make the vector element non-const.
| ); | ||
| return static_cast<UnsignedByte>(m_argList.size()); | ||
| } | ||
| const std::vector<const GameMessageArgument*>& getArguments() const { return m_argList ; } |
There was a problem hiding this comment.
Instead of giving this function that returns internal data structure, use established getArgumentCount() and getArgument() functions at call sites.
| // TheSuperHackers @refactor RiQQ 12/05/2026 Return argument count from vector size | ||
| /// @todo If a GameMessage needs more than 255 arguments, it needs to be split up into multiple GameMessage's. | ||
| UnsignedByte getArgumentCount() const { | ||
| DEBUG_ASSERTCRASH( |
There was a problem hiding this comment.
This assert looks misplaced in this function. One would expect this assert at the time of argument creation, not when querying the size of it.
|
|
||
| Type getType() const { return m_type; } ///< Return the message type | ||
| UnsignedByte getArgumentCount() const { return m_argCount; } ///< Return the number of arguments for this msg | ||
| // TheSuperHackers @refactor RiQQ 12/05/2026 Return argument count from vector size |
| UnsignedByte m_argCount; ///< The number of arguments of this message | ||
|
|
||
| GameMessageArgument *m_argList, *m_argTail; ///< This message's arguments | ||
| // TheSuperHackers @refactor RiQQ 12/05/2026 Replaced linked list with std::vector for argument storage |
There was a problem hiding this comment.
I think this comment can be removed because it is not useful for the next reader.
| protected: | ||
| Int m_numArgs; | ||
| Int m_argSize; | ||
| // TheSuperHackers @refactor RiQQ 11/05/2026 Replaced linked list with std::vector for argument storage |
There was a problem hiding this comment.
I think this comment can be removed because it is not useful for the next reader.
| UnsignedByte getArgumentCount() const { | ||
| DEBUG_ASSERTCRASH( | ||
| m_argList.size() <= 255, | ||
| ("GameMessage has more than 255 arguments") |
There was a problem hiding this comment.
"If a GameMessage needs more than 255 arguments, it needs to be split up into multiple GameMessage's."
Fixes #2630
GameMessageandNetGameCommandMsgstored their arguments in a linked list (m_argList/m_argTail/m_argCount/m_next). Since arguments are only ever appended and iterated in order, this is replaced with astd::vector, removing the manual pointer threading and simplifying all related code. ADEBUG_ASSERTCRASHis added to guard the existing 255-argument limit.AI usage was minimal. All code has been reviewed and tested to best of my capability.
TODO: