-
Notifications
You must be signed in to change notification settings - Fork 105
refactor(network): replace manual size calculations with packed structs #1675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
refactor(network): replace manual size calculations with packed structs #1675
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot compile this branch. There are 700 linker errors.
GeneralsMD/Code/GameEngine/Source/GameNetwork/NetPacketStructs.h
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameNetwork/NetPacketStructs.h
Outdated
Show resolved
Hide resolved
You can still do this for the messages that are variable in length, just make the fixed portion of the message a struct. |
dbb01a8
to
9d9e804
Compare
9d9e804
to
db5c043
Compare
GeneralsMD/Code/GameEngine/Include/GameNetwork/NetPacketStructs.h
Outdated
Show resolved
Hide resolved
|
||
++msglen; // the NetPacketFieldTypes::Data | ||
msglen += sizeof(UnsignedByte); // string msglength | ||
UnsignedByte textmsglen = cmdMsg->getText().getLength(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side note: This truncates 4 bytes to 1 byte 👀
GeneralsMD/Code/GameEngine/Include/GameNetwork/NetPacketStructs.h
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Include/GameNetwork/NetPacketStructs.h
Outdated
Show resolved
Hide resolved
34ab595
to
ba6335d
Compare
960e7a4
to
ad27a6c
Compare
GeneralsMD/Code/GameEngine/Source/GameNetwork/NetCommandMsg.cpp
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Include/GameNetwork/NetPacketStructs.h
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Include/GameNetwork/NetPacketStructs.h
Outdated
Show resolved
Hide resolved
GeneralsMD/Code/GameEngine/Source/GameNetwork/NetCommandMsg.cpp
Outdated
Show resolved
Hide resolved
*/ | ||
size_t NetDisconnectChatCommandMsg::getByteCount() const | ||
{ | ||
return m_text.getByteCount(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does the reader recognize the length of this text? Surely this must be either null terminated or bundled with a count? I wonder if this is an original bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be addressed in another PR or here?
A potential fix:
{
return sizeof(UnsignedByte) + m_text.getByteCount();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be addressed separately, and it needs to consider retail compatibility.
I suspect it works in original game because message buffer is zero initialized and therefore string is zero terminated when nothing comes after it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can add a TheSuperHackers @todo ...
comment here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what you did now:
struct NetPacketDisconnectChatCommandHeader {
NetPacketCommandTypeField commandType;
NetPacketRelayField relay;
NetPacketPlayerIdField playerId;
NetPacketDataFieldHeader dataHeader;
UnsignedByte textLength;
};
You put the textLength into the header struct.
It is a bit confusing that it is detached from the string data.
Perhaps move it? Same in the other struct.
GeneralsMD/Code/GameEngine/Source/GameNetwork/NetCommandMsg.cpp
Outdated
Show resolved
Hide resolved
*/ | ||
size_t NetChatCommandMsg::getByteCount() const | ||
{ | ||
return m_text.getByteCount() + sizeof(m_playerMask); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at this again, I think we need to make another pass on this.
NetChatCommandMsg inherits from NetCommandMsg. All these messages inherit from it.
So what we can do is add
size_t NetCommandMsg::getByteCount() const
{
return sizeof(PackedNetCommandMsg);
}
It can then be combined with its descendent classes:
size_t NetChatCommandMsg::getByteCount() const
{
return NetCommandMsg::getByteCount() + ...;
}
Alternatively the Packed structs can also inherit. Not sure what the best approach is because of the variable length data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I went with packed structs inheritance, let me know what you think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It still is confusing. We have 25 messages that inherit the base message:
class NetGameCommandMsg : public NetCommandMsg
class NetAckBothCommandMsg : public NetCommandMsg
class NetAckStage1CommandMsg : public NetCommandMsg
class NetAckStage2CommandMsg : public NetCommandMsg
class NetFrameCommandMsg : public NetCommandMsg
class NetPlayerLeaveCommandMsg : public NetCommandMsg
class NetRunAheadMetricsCommandMsg : public NetCommandMsg
class NetRunAheadCommandMsg : public NetCommandMsg
class NetDestroyPlayerCommandMsg : public NetCommandMsg
class NetKeepAliveCommandMsg : public NetCommandMsg
class NetDisconnectKeepAliveCommandMsg : public NetCommandMsg
class NetDisconnectPlayerCommandMsg : public NetCommandMsg
class NetPacketRouterQueryCommandMsg : public NetCommandMsg
class NetPacketRouterAckCommandMsg : public NetCommandMsg
class NetDisconnectChatCommandMsg : public NetCommandMsg
class NetChatCommandMsg : public NetCommandMsg
class NetDisconnectVoteCommandMsg : public NetCommandMsg
class NetProgressCommandMsg: public NetCommandMsg
class NetWrapperCommandMsg : public NetCommandMsg
class NetFileCommandMsg : public NetCommandMsg
class NetFileAnnounceCommandMsg : public NetCommandMsg
class NetFileProgressCommandMsg : public NetCommandMsg
class NetDisconnectFrameCommandMsg : public NetCommandMsg
class NetDisconnectScreenOffCommandMsg : public NetCommandMsg
class NetFrameResendRequestCommandMsg : public NetCommandMsg
Are not all messages using all the fields of the base message? It would have been nice if this could be constructed with consistency. But maybe that is not the case. What deviation patterns do we have?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pattern is:
Variable data -> getByteCount() function
Fixed data -> sizeof(struct) directly
We've got 5 variable-length classes using getByteCount() with packed struct inheritance.
and 20 fixed-size classes using sizeof(struct) directly
The 5 that need custom size calculations are:
NetGameCommandMsg
NetChatCommandMsg
NetDisconnectChatCommandMsg
NetFileCommandMsg
NetFileAnnounceCommandMsg
These packets have dynamic content (text, file data, game arguments), so they use custom size calculations. They use a header + variable data pattern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The confusion I see here is that the packed structs have different inheritance patterns than the non packed structs. It would be nice if these were consistent, but that seems not possible because the first few data fields are not always the same, for whatever reason.
I am inclined to say if we cannot make the packed ones use inheritance consistently, then make none inherited - for consistency.
But what we could do is add getByteCount()
functions to all non packed structs, and then make them static
where they simply return sizeof(PackedStructXYZ)
.
Also, I think we better name the functions getPackedByteCount()
.
And then in next changes we add functions toPackedStruct
and fromPackedStruct
to convert between packed and non-packed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I added getByteCount() functions to all of them, and used static where appropriate, and renamed the functions getPackedByteCount, in separate commits.
…ments more consistent
…dType type, and add struct constructors
…ta in getByteCount()
…th packed structs
…e getByteCount() functions
586fafb
to
07b592f
Compare
…es for consistent polymorphic interface
Created NetPacketStructs.h with packed struct definitions for network packets
Replaced 9 manual size calculation functions with sizeof(struct) calls
In the next PR we could use these structs for actual serialization, replacing manual memcpy calls
TODO:
Replicate in Generals