Skip to content

Commit e0358ea

Browse files
authored
Introduce typed packets (#728)
* Introduce typed packets Packets are required to be structs to help minimize any overhead * Typed Server_PlayerList packet * Typed {Client,Server}_Cursor packet * Typed {Client,Server}_Command packet * Typed {Client,Server}_PingLocation packet * Add packet serialization round trip tests * Add binary snapshot tests for packet serialization * Ensure packets are fully consumed
1 parent 89f4425 commit e0358ea

30 files changed

+1070
-333
lines changed

Source/Client/Networking/State/ClientPlayingState.cs

Lines changed: 52 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
using System;
2+
using System.Collections.Generic;
13
using Ionic.Zlib;
24
using Multiplayer.Common;
5+
using Multiplayer.Common.Networking.Packet;
36
using RimWorld;
4-
using RimWorld.Planet;
5-
using System;
6-
using System.Collections.Generic;
77
using UnityEngine;
88
using Verse;
99

@@ -17,88 +17,78 @@ public class ClientPlayingState(ConnectionBase connection) : ClientBaseState(con
1717
[PacketHandler(Packets.Server_TimeControl)]
1818
public new void HandleTimeControl(ByteReader data) => base.HandleTimeControl(data);
1919

20-
[PacketHandler(Packets.Server_Command)]
21-
public void HandleCommand(ByteReader data)
20+
[TypedPacketHandler]
21+
public void HandleCommand(ServerCommandPacket packet)
2222
{
23-
ScheduledCommand cmd = ScheduledCommand.Deserialize(data);
24-
Session.ScheduleCommand(cmd);
25-
23+
Session.ScheduleCommand(packet.ToCommand());
2624
Multiplayer.session.receivedCmds++;
2725
Multiplayer.session.ProcessTimeControl();
2826
}
2927

30-
[PacketHandler(Packets.Server_PlayerList)]
31-
public void HandlePlayerList(ByteReader data)
28+
[TypedPacketHandler]
29+
public void HandlePlayerList(ServerPlayerListPacket packet)
3230
{
33-
var action = data.ReadEnum<PlayerListAction>();
34-
if (action == PlayerListAction.Add)
31+
if (packet.action == PlayerListAction.Add)
3532
{
36-
var info = PlayerInfo.Read(data);
37-
if (!Multiplayer.session.players.Contains(info))
38-
{
39-
ServerLog.Log($"PlayerList: Adding player {info.id}:{info.username}");
40-
Multiplayer.session.players.Add(info);
41-
}
42-
else
33+
foreach (var info in packet.players)
4334
{
44-
ServerLog.Error($"PlayerList: Adding player {info.id}:{info.username} - player already exists");
35+
if (!Multiplayer.session.players.Any(p => p.id == info.id || p.username == info.username))
36+
{
37+
ServerLog.Log($"PlayerList: Adding player {info.id}:{info.username}");
38+
Multiplayer.session.players.Add(PlayerInfo.FromNet(info));
39+
}
40+
else
41+
{
42+
ServerLog.Error($"PlayerList: Adding player {info.id}:{info.username} - player already exists");
43+
}
4544
}
4645
}
47-
else if (action == PlayerListAction.Remove)
46+
else if (packet.action == PlayerListAction.Remove)
4847
{
49-
int id = data.ReadInt32();
50-
ServerLog.Log($"PlayerList: Removing player with id {id}");
51-
var matches = Multiplayer.session.players.RemoveAll(p => p.id == id);
48+
ServerLog.Log($"PlayerList: Removing player with id {packet.playerId}");
49+
var matches = Multiplayer.session.players.RemoveAll(p => p.id == packet.playerId);
5250
if (matches > 1)
5351
{
54-
ServerLog.Error($"PlayerList: Removing player with id {id} -- occurred {matches} times. This should not happen");
52+
ServerLog.Error($"PlayerList: Removing player with id {packet.playerId} -- occurred {matches} times. This should not happen");
5553
}
5654
}
57-
else if (action == PlayerListAction.List)
55+
else if (packet.action == PlayerListAction.List)
5856
{
59-
int count = data.ReadInt32();
60-
ServerLog.Log($"PlayerList: Received player list with {count} entries");
57+
ServerLog.Log($"PlayerList: Received player list with {packet.players.Length} entries");
6158

6259
Multiplayer.session.players.Clear();
63-
for (int i = 0; i < count; i++)
60+
foreach (var info in packet.players)
6461
{
65-
var info = PlayerInfo.Read(data);
6662
ServerLog.Log($"PlayerList: Adding player from list {info.id}:{info.username}");
67-
Multiplayer.session.players.Add(info);
63+
Multiplayer.session.players.Add(PlayerInfo.FromNet(info));
6864
}
6965
}
70-
else if (action == PlayerListAction.Latencies)
66+
else if (packet.action == PlayerListAction.Latencies)
7167
{
72-
int count = data.ReadInt32();
73-
74-
for (int i = 0; i < count; i++)
68+
foreach (var latency in packet.latencies)
7569
{
76-
var id = data.ReadInt32();
77-
var player = Multiplayer.session.GetPlayerInfo(id);
70+
var player = Multiplayer.session.GetPlayerInfo(latency.playerId);
7871
if (player == null)
7972
{
80-
ServerLog.Log($"PlayerList: Received latency info for unknown player with id {id}");
73+
ServerLog.Log($"PlayerList: Received latency info for unknown player with id {latency.playerId}");
8174
continue;
8275
}
83-
player.latency = data.ReadInt32();
84-
player.ticksBehind = data.ReadInt32();
85-
player.simulating = data.ReadBool();
86-
player.frameTime = data.ReadFloat();
76+
player.latency = latency.latency;
77+
player.ticksBehind = latency.ticksBehind;
78+
player.simulating = latency.simulating;
79+
player.frameTime = latency.frameTime;
8780
}
8881
}
89-
else if (action == PlayerListAction.Status)
82+
else if (packet.action == PlayerListAction.Status)
9083
{
91-
var id = data.ReadInt32();
92-
var status = data.ReadEnum<PlayerStatus>();
93-
var player = Multiplayer.session.GetPlayerInfo(id);
94-
84+
var player = Multiplayer.session.GetPlayerInfo(packet.playerId);
9585
if (player == null)
9686
{
97-
ServerLog.Log($"PlayerList: Received player status ({status}) for unknown player with id {id}");
87+
ServerLog.Log($"PlayerList: Received player status ({packet.status}) for unknown player with id {packet.playerId}");
9888
}
9989
else
10090
{
101-
player.status = status;
91+
player.status = packet.status;
10292
}
10393
}
10494
}
@@ -110,44 +100,26 @@ public void HandleChat(ByteReader data)
110100
Multiplayer.session.AddMsg(msg);
111101
}
112102

113-
[PacketHandler(Packets.Server_Cursor)]
114-
public void HandleCursor(ByteReader data)
103+
[TypedPacketHandler]
104+
public void HandleCursor(ServerCursorPacket packet)
115105
{
116-
int playerId = data.ReadInt32();
117-
var player = Multiplayer.session.GetPlayerInfo(playerId);
106+
var player = Multiplayer.session.GetPlayerInfo(packet.playerId);
118107
if (player == null) return;
119108

120-
byte seq = data.ReadByte();
121-
if (seq < player.cursorSeq && player.cursorSeq - seq < 128) return;
122-
123-
byte map = data.ReadByte();
124-
player.map = map;
125-
126-
if (map == byte.MaxValue) return;
109+
var data = packet.data;
110+
if (data.seq < player.cursorSeq && player.cursorSeq - data.seq < 128) return;
127111

128-
byte icon = data.ReadByte();
129-
float x = data.ReadShort() / 10f;
130-
float z = data.ReadShort() / 10f;
112+
player.map = data.map;
113+
if (data.map == byte.MaxValue) return;
131114

132-
player.cursorSeq = seq;
115+
player.cursorSeq = data.seq;
133116
player.lastCursor = player.cursor;
134117
player.lastDelta = Multiplayer.clock.ElapsedMillisDouble() - player.updatedAt;
135-
player.cursor = new Vector3(x, 0, z);
118+
player.cursor = new Vector3(data.x, 0, data.z);
136119
player.updatedAt = Multiplayer.clock.ElapsedMillisDouble();
137-
player.cursorIcon = icon;
120+
player.cursorIcon = data.icon;
138121

139-
short dragXRaw = data.ReadShort();
140-
if (dragXRaw != -1)
141-
{
142-
float dragX = dragXRaw / 10f;
143-
float dragZ = data.ReadShort() / 10f;
144-
145-
player.dragStart = new Vector3(dragX, 0, dragZ);
146-
}
147-
else
148-
{
149-
player.dragStart = PlayerInfo.Invalid;
150-
}
122+
player.dragStart = data.HasDrag ? new Vector3(data.dragX, 0, data.dragZ) : PlayerInfo.Invalid;
151123
}
152124

153125
[PacketHandler(Packets.Server_Selected)]
@@ -171,16 +143,8 @@ public void HandleSelected(ByteReader data)
171143
player.selectedThings.Remove(remove[i]);
172144
}
173145

174-
[PacketHandler(Packets.Server_PingLocation)]
175-
public void HandlePing(ByteReader data)
176-
{
177-
int player = data.ReadInt32();
178-
int map = data.ReadInt32();
179-
PlanetTile planetTile = new(data.ReadInt32(), data.ReadInt32());
180-
var loc = new Vector3(data.ReadFloat(), data.ReadFloat(), data.ReadFloat());
181-
182-
Session.locationPings.ReceivePing(player, map, planetTile, loc);
183-
}
146+
[TypedPacketHandler]
147+
public void HandlePing(ServerPingLocPacket packet) => Session.locationPings.ReceivePing(packet);
184148

185149
[PacketHandler(Packets.Server_MapResponse)]
186150
public void HandleMapResponse(ByteReader data)

Source/Client/Session/PlayerInfo.cs

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Multiplayer.Common;
33
using System.Collections.Generic;
44
using System.Linq;
5+
using Multiplayer.Common.Networking.Packet;
56
using UnityEngine;
67
using Verse;
78

@@ -60,34 +61,23 @@ private PlayerInfo(int id, string username, int latency, PlayerType type)
6061
this.type = type;
6162
}
6263

63-
public static PlayerInfo Read(ByteReader data)
64+
public static PlayerInfo FromNet(ServerPlayerListPacket.PlayerInfo info)
6465
{
65-
int id = data.ReadInt32();
66-
string username = data.ReadString();
67-
int latency = data.ReadInt32();
68-
var type = data.ReadEnum<PlayerType>();
69-
var status = data.ReadEnum<PlayerStatus>();
70-
71-
var steamId = data.ReadULong();
72-
var steamName = data.ReadString();
73-
74-
var ticksBehind = data.ReadInt32();
75-
var simulating = data.ReadBool();
66+
var color = new Color(info.r / 255f, info.g / 255f, info.b / 255f);
67+
return new PlayerInfo(id: info.id, username: info.username, latency: info.latency, type: info.type)
68+
{
69+
status = info.status,
7670

77-
var color = new Color(data.ReadByte() / 255f, data.ReadByte() / 255f, data.ReadByte() / 255f);
71+
steamId = info.steamId,
72+
steamPersonaName = info.steamPersonaName,
7873

79-
int factionId = data.ReadInt32();
74+
ticksBehind = info.ticksBehind,
75+
simulating = info.simulating,
8076

81-
return new PlayerInfo(id, username, latency, type)
82-
{
83-
status = status,
84-
steamId = steamId,
85-
steamPersonaName = steamName,
8677
color = color,
8778
selectionBracketMaterial = MaterialPool.MatFrom("UI/Overlays/SelectionBracket", ShaderDatabase.MetaOverlay, color * new Color(1, 1, 1, 0.5f)),
88-
ticksBehind = ticksBehind,
89-
simulating = simulating,
90-
factionId = factionId
79+
80+
factionId = info.factionId,
9181
};
9282
}
9383
}

Source/Client/UI/LocationPings.cs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Collections.Generic;
22
using Multiplayer.Client.Util;
3-
using Multiplayer.Common;
3+
using Multiplayer.Common.Networking.Packet;
44
using RimWorld;
55
using RimWorld.Planet;
66
using UnityEngine;
@@ -60,27 +60,25 @@ private static bool KeyDown(KeyCode? keyNullable)
6060

6161
private void PingLocation(int map, PlanetTile tile, Vector3 loc)
6262
{
63-
var writer = new ByteWriter();
64-
writer.WriteInt32(map);
65-
writer.WriteInt32(tile.tileId);
66-
writer.WriteInt32(tile.layerId);
67-
writer.WriteFloat(loc.x);
68-
writer.WriteFloat(loc.y);
69-
writer.WriteFloat(loc.z);
70-
Multiplayer.Client.Send(Packets.Client_PingLocation, writer.ToArray());
71-
63+
Multiplayer.Client.Send(new ClientPingLocPacket(map, tile.tileId, tile.layerId, loc.x, loc.y, loc.z));
7264
SoundDefOf.TinyBell.PlayOneShotOnCamera();
7365
}
7466

75-
public void ReceivePing(int player, int map, PlanetTile tile, Vector3 loc)
67+
public void ReceivePing(ServerPingLocPacket packet)
7668
{
7769
if (!Multiplayer.settings.enablePings) return;
7870

79-
pings.RemoveAll(p => p.player == player);
80-
pings.Add(new PingInfo { player = player, mapId = map, planetTile = tile, mapLoc = loc });
71+
var data = packet.data;
72+
pings.RemoveAll(p => p.player == packet.playerId);
73+
pings.Add(new PingInfo {
74+
player = packet.playerId,
75+
mapId = data.mapId,
76+
planetTile = new PlanetTile(data.planetTileId, data.planetTileLayer),
77+
mapLoc = new Vector3(data.x, data.y, data.z)
78+
});
8179
alertHidden = false;
8280

83-
if (player != Multiplayer.session.playerId)
81+
if (packet.playerId != Multiplayer.session.playerId)
8482
SoundDefOf.TinyBell.PlayOneShotOnCamera();
8583
}
8684
}

Source/Client/UI/PlayerCursors.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using System.Linq;
33
using Multiplayer.Common;
4+
using Multiplayer.Common.Networking.Packet;
45
using RimWorld.Planet;
56
using UnityEngine;
67
using Verse;
@@ -33,31 +34,30 @@ public void SendVisuals()
3334

3435
private void SendCursor()
3536
{
36-
var writer = new ByteWriter();
37-
writer.WriteByte(cursorSeq++);
37+
var packet = new ClientCursorPacket(cursorSeq++);
3838

3939
if (Find.CurrentMap != null && !WorldRendererUtility.WorldSelected)
4040
{
41-
writer.WriteByte((byte)Find.CurrentMap.Index);
41+
packet.map = (byte)Find.CurrentMap.Index;
4242

4343
var icon = Find.MapUI?.designatorManager?.SelectedDesignator?.icon;
4444
int iconId = icon == null ? 0 :
4545
!MultiplayerData.icons.Contains(icon) ? 0 : MultiplayerData.icons.IndexOf(icon);
46-
writer.WriteByte((byte)iconId);
46+
packet.icon = (byte)iconId;
4747

48-
writer.WriteVectorXZ(UI.MouseMapPosition());
48+
var mapPosition = UI.MouseMapPosition();
49+
packet.x = mapPosition.x;
50+
packet.z = mapPosition.z;
4951

5052
if (Find.Selector.dragBox.IsValidAndActive)
51-
writer.WriteVectorXZ(Find.Selector.dragBox.start);
52-
else
53-
writer.WriteShort(-1);
54-
}
55-
else
56-
{
57-
writer.WriteByte(byte.MaxValue);
53+
{
54+
var dragVec = Find.Selector.dragBox.start;
55+
packet.dragX = dragVec.x;
56+
packet.dragZ = dragVec.z;
57+
}
5858
}
5959

60-
Multiplayer.Client.Send(Packets.Client_Cursor, writer.ToArray(), reliable: false);
60+
Multiplayer.Client.Send(packet, reliable: false);
6161
}
6262

6363
private void SendSelected()

0 commit comments

Comments
 (0)