Creating Packets¶
Packets are the units of data sent over the network. MafiaNet provides two ways to create packets: using structures or using BitStreams.
Message IDs¶
Every packet starts with a message ID byte. MafiaNet reserves IDs 0-134 for internal use. Your custom messages should start at ID_USER_PACKET_ENUM:
// In a shared header file
enum GameMessages {
ID_PLAYER_POSITION = ID_USER_PACKET_ENUM,
ID_PLAYER_SHOOT,
ID_CHAT_MESSAGE,
// ... more message types
};
Using Structs¶
The simplest way to create packets:
#pragma pack(push, 1) // Ensure no padding
struct PlayerPositionPacket {
unsigned char messageId; // Must be first
float x, y, z;
float yaw, pitch;
};
#pragma pack(pop)
// Sending
PlayerPositionPacket packet;
packet.messageId = ID_PLAYER_POSITION;
packet.x = player.x;
packet.y = player.y;
packet.z = player.z;
packet.yaw = player.yaw;
packet.pitch = player.pitch;
peer->Send((char*)&packet, sizeof(packet),
HIGH_PRIORITY, RELIABLE_ORDERED, 0, address, false);
// Receiving
PlayerPositionPacket* received = (PlayerPositionPacket*)packet->data;
player.x = received->x;
player.y = received->y;
// ...
Warning
Struct-based packets don’t handle endian differences between platforms. Use BitStream for cross-platform compatibility.
Using BitStream¶
More flexible and handles endianness:
// Sending
MafiaNet::BitStream bs;
bs.Write((MafiaNet::MessageID)ID_PLAYER_POSITION);
bs.Write(player.x);
bs.Write(player.y);
bs.Write(player.z);
bs.Write(player.yaw);
bs.Write(player.pitch);
peer->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, address, false);
// Receiving
MafiaNet::BitStream bsIn(packet->data, packet->length, false);
bsIn.IgnoreBytes(1); // Skip message ID
float x, y, z, yaw, pitch;
bsIn.Read(x);
bsIn.Read(y);
bsIn.Read(z);
bsIn.Read(yaw);
bsIn.Read(pitch);
Timestamps¶
To synchronize timing across the network, use timestamps:
// Sending
MafiaNet::BitStream bs;
bs.Write((MafiaNet::MessageID)ID_TIMESTAMP);
bs.Write(MafiaNet::GetTime()); // Current time
bs.Write((MafiaNet::MessageID)ID_PLAYER_POSITION);
bs.Write(player.x);
// ...
// Receiving
MafiaNet::BitStream bsIn(packet->data, packet->length, false);
MafiaNet::MessageID msgId;
bsIn.Read(msgId);
if (msgId == ID_TIMESTAMP) {
MafiaNet::Time timestamp;
bsIn.Read(timestamp);
bsIn.Read(msgId); // Read actual message ID
// timestamp is now in your local time
MafiaNet::Time packetAge = MafiaNet::GetTime() - timestamp;
}
Note
ID_TIMESTAMP must be the first byte. MafiaNet automatically converts the timestamp to the receiver’s local time.
Compression Techniques¶
Use less bandwidth with compression:
// For integers that are usually small
bs.WriteCompressed(health); // 100 uses ~1 byte, not 4
// For normalized vectors (length = 1)
bs.WriteNormVector(dirX, dirY, dirZ);
// For quaternions
bs.WriteNormQuat(qw, qx, qy, qz);
// For floats with limited precision needs
bs.WriteFloat16(value, minValue, maxValue);
// For integers within a known range
bs.WriteBitsFromIntegerRange(value, minValue, maxValue);
Strings¶
// Using RakString (more efficient)
MafiaNet::RakString str = "Hello, World!";
bs.Write(str);
// Or C-strings
bs.Write("Hello, World!");
// Reading
MafiaNet::RakString received;
bsIn.Read(received);
Variable-Length Data¶
For arrays or variable-length data:
// Write count first, then elements
bs.Write((uint16_t)items.size());
for (const auto& item : items) {
bs.Write(item);
}
// Reading
uint16_t count;
bsIn.Read(count);
for (uint16_t i = 0; i < count; i++) {
ItemType item;
bsIn.Read(item);
items.push_back(item);
}
Best Practices¶
Always check read success:
if (!bsIn.Read(value)) { // Packet was truncated/corrupted return; }
Use smallest types possible:
uint8_tfor small values,uint16_tfor medium.Consider delta compression: Send only what changed since last update.
Group related data: Send position and velocity together rather than separately.
Use compression:
WriteCompressed()and specialized methods save bandwidth.
See Also¶
BitStreams - Detailed BitStream documentation
Sending Packets - How to send packets
Timestamping - Timestamp synchronization