Skip to content

Commit bca25b9

Browse files
authored
feat: QoL: Byte Packing of integers [MTT-4924] (#2276)
Adds byte packing for most integer fields in the SDK (a few, such as hashes, are left un-packed because they're likely to be large integers and not benefit - and may possibly actually be harmed - by variable-length encoding). Integers in `RPC`s and `NetworkVariable`s are also packed properly. No functionality has been added to allow a value to be sent unpacked in `RPC`s or `NetworkVariable`s in cases where packing may be detrimental, but this can be achieved as follows: ```csharp public struct UnpackedInt32 : INetworkSerializeByMemcpy { int Value; } public NetworkVariable<UnpackedInt32> MyVariable; ```` In addition to byte packing most integer fields, this commit updates `WriteValueBitPacked()` to be able to handle the full range of its type (now using an extra byte - and thus ending up making the representation larger instead of smaller - for values at the extreme ends of the size), at the cost of using one extra bit to encode the size of the type.
1 parent 23be561 commit bca25b9

28 files changed

+968
-824
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ Additional documentation and release notes are available at [Multiplayer Documen
1313
- Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285)
1414
- Added `public string DisconnectReason` getter to `NetworkManager` and `string Reason` to `ConnectionApprovalResponse`. Allows connection approval to communicate back a reason. Also added `public void DisconnectClient(ulong clientId, string reason)` allowing setting a disconnection reason, when explicitly disconnecting a client.
1515

16+
### Changed
17+
18+
- Optimized bandwidth usage by encoding most integer fields using variable-length encoding. (#2276)
19+
1620
### Fixed
1721

1822
- Fixed issue where the host would receive more than one event completed notification when loading or unloading a scene only when no clients were connected. (#2292)

com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs

Lines changed: 195 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,19 @@ private bool IsMemcpyableType(TypeReference type)
139139
return false;
140140
}
141141

142+
private bool IsSpecialCaseType(TypeReference type)
143+
{
144+
foreach (var supportedType in SpecialCaseTypes)
145+
{
146+
if (type.FullName == supportedType.FullName)
147+
{
148+
return true;
149+
}
150+
}
151+
152+
return false;
153+
}
154+
142155
private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
143156
{
144157
foreach (var typeDefinition in assembly.MainModule.Types)
@@ -153,6 +166,11 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
153166

154167
foreach (var type in m_WrappedNetworkVariableTypes)
155168
{
169+
if (IsSpecialCaseType(type))
170+
{
171+
continue;
172+
}
173+
156174
// If a serializable type isn't found, FallbackSerializer will be used automatically, which will
157175
// call into UserNetworkVariableSerialization, giving the user a chance to define their own serializaiton
158176
// for types that aren't in our official supported types list.
@@ -257,6 +275,20 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
257275
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef;
258276
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef;
259277

278+
private MethodReference m_BytePacker_WriteValueBitPacked_Short_MethodRef;
279+
private MethodReference m_BytePacker_WriteValueBitPacked_UShort_MethodRef;
280+
private MethodReference m_BytePacker_WriteValueBitPacked_Int_MethodRef;
281+
private MethodReference m_BytePacker_WriteValueBitPacked_UInt_MethodRef;
282+
private MethodReference m_BytePacker_WriteValueBitPacked_Long_MethodRef;
283+
private MethodReference m_BytePacker_WriteValueBitPacked_ULong_MethodRef;
284+
285+
private MethodReference m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef;
286+
private MethodReference m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef;
287+
private MethodReference m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef;
288+
private MethodReference m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef;
289+
private MethodReference m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef;
290+
private MethodReference m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef;
291+
260292
private TypeReference m_FastBufferWriter_TypeRef;
261293
private readonly Dictionary<string, MethodReference> m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary<string, MethodReference>();
262294
private readonly List<MethodReference> m_FastBufferWriter_ExtensionMethodRefs = new List<MethodReference>();
@@ -276,12 +308,13 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
276308
typeof(decimal),
277309
typeof(double),
278310
typeof(float),
279-
typeof(int),
311+
// the following types have special handling
312+
/*typeof(int),
280313
typeof(uint),
281314
typeof(long),
282315
typeof(ulong),
283316
typeof(short),
284-
typeof(ushort),
317+
typeof(ushort),*/
285318
typeof(Vector2),
286319
typeof(Vector3),
287320
typeof(Vector2Int),
@@ -293,6 +326,16 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
293326
typeof(Ray),
294327
typeof(Ray2D)
295328
};
329+
internal static readonly Type[] SpecialCaseTypes = new[]
330+
{
331+
// the following types have special handling
332+
typeof(int),
333+
typeof(uint),
334+
typeof(long),
335+
typeof(ulong),
336+
typeof(short),
337+
typeof(ushort),
338+
};
296339

297340
private const string k_Debug_LogError = nameof(Debug.LogError);
298341
private const string k_NetworkManager_LocalClientId = nameof(NetworkManager.LocalClientId);
@@ -343,6 +386,8 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
343386
TypeDefinition fastBufferWriterTypeDef = null;
344387
TypeDefinition fastBufferReaderTypeDef = null;
345388
TypeDefinition networkVariableSerializationTypesTypeDef = null;
389+
TypeDefinition bytePackerTypeDef = null;
390+
TypeDefinition byteUnpackerTypeDef = null;
346391
foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes())
347392
{
348393
if (networkManagerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager))
@@ -398,6 +443,18 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
398443
networkVariableSerializationTypesTypeDef = netcodeTypeDef;
399444
continue;
400445
}
446+
447+
if (bytePackerTypeDef == null && netcodeTypeDef.Name == nameof(BytePacker))
448+
{
449+
bytePackerTypeDef = netcodeTypeDef;
450+
continue;
451+
}
452+
453+
if (byteUnpackerTypeDef == null && netcodeTypeDef.Name == nameof(ByteUnpacker))
454+
{
455+
byteUnpackerTypeDef = netcodeTypeDef;
456+
continue;
457+
}
401458
}
402459

403460
foreach (var methodDef in debugTypeDef.Methods)
@@ -652,6 +709,82 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
652709
}
653710
}
654711

712+
foreach (var method in bytePackerTypeDef.Methods)
713+
{
714+
if (!method.IsStatic)
715+
{
716+
continue;
717+
}
718+
719+
switch (method.Name)
720+
{
721+
case nameof(BytePacker.WriteValueBitPacked):
722+
if (method.Parameters[1].ParameterType.FullName == typeof(short).FullName)
723+
{
724+
m_BytePacker_WriteValueBitPacked_Short_MethodRef = m_MainModule.ImportReference(method);
725+
}
726+
else if (method.Parameters[1].ParameterType.FullName == typeof(ushort).FullName)
727+
{
728+
m_BytePacker_WriteValueBitPacked_UShort_MethodRef = m_MainModule.ImportReference(method);
729+
}
730+
else if (method.Parameters[1].ParameterType.FullName == typeof(int).FullName)
731+
{
732+
m_BytePacker_WriteValueBitPacked_Int_MethodRef = m_MainModule.ImportReference(method);
733+
}
734+
else if (method.Parameters[1].ParameterType.FullName == typeof(uint).FullName)
735+
{
736+
m_BytePacker_WriteValueBitPacked_UInt_MethodRef = m_MainModule.ImportReference(method);
737+
}
738+
else if (method.Parameters[1].ParameterType.FullName == typeof(long).FullName)
739+
{
740+
m_BytePacker_WriteValueBitPacked_Long_MethodRef = m_MainModule.ImportReference(method);
741+
}
742+
else if (method.Parameters[1].ParameterType.FullName == typeof(ulong).FullName)
743+
{
744+
m_BytePacker_WriteValueBitPacked_ULong_MethodRef = m_MainModule.ImportReference(method);
745+
}
746+
break;
747+
}
748+
}
749+
750+
foreach (var method in byteUnpackerTypeDef.Methods)
751+
{
752+
if (!method.IsStatic)
753+
{
754+
continue;
755+
}
756+
757+
switch (method.Name)
758+
{
759+
case nameof(ByteUnpacker.ReadValueBitPacked):
760+
if (method.Parameters[1].ParameterType.FullName == typeof(short).MakeByRefType().FullName)
761+
{
762+
m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef = m_MainModule.ImportReference(method);
763+
}
764+
else if (method.Parameters[1].ParameterType.FullName == typeof(ushort).MakeByRefType().FullName)
765+
{
766+
m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef = m_MainModule.ImportReference(method);
767+
}
768+
else if (method.Parameters[1].ParameterType.FullName == typeof(int).MakeByRefType().FullName)
769+
{
770+
m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef = m_MainModule.ImportReference(method);
771+
}
772+
else if (method.Parameters[1].ParameterType.FullName == typeof(uint).MakeByRefType().FullName)
773+
{
774+
m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef = m_MainModule.ImportReference(method);
775+
}
776+
else if (method.Parameters[1].ParameterType.FullName == typeof(long).MakeByRefType().FullName)
777+
{
778+
m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef = m_MainModule.ImportReference(method);
779+
}
780+
else if (method.Parameters[1].ParameterType.FullName == typeof(ulong).MakeByRefType().FullName)
781+
{
782+
m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef = m_MainModule.ImportReference(method);
783+
}
784+
break;
785+
}
786+
}
787+
655788
return true;
656789
}
657790

@@ -1008,6 +1141,36 @@ private MethodReference GetFastBufferWriterWriteMethod(string name, TypeReferenc
10081141

10091142
private bool GetWriteMethodForParameter(TypeReference paramType, out MethodReference methodRef)
10101143
{
1144+
if (paramType.FullName == typeof(short).FullName)
1145+
{
1146+
methodRef = m_BytePacker_WriteValueBitPacked_Short_MethodRef;
1147+
return true;
1148+
}
1149+
if (paramType.FullName == typeof(ushort).FullName)
1150+
{
1151+
methodRef = m_BytePacker_WriteValueBitPacked_UShort_MethodRef;
1152+
return true;
1153+
}
1154+
if (paramType.FullName == typeof(int).FullName)
1155+
{
1156+
methodRef = m_BytePacker_WriteValueBitPacked_Int_MethodRef;
1157+
return true;
1158+
}
1159+
if (paramType.FullName == typeof(uint).FullName)
1160+
{
1161+
methodRef = m_BytePacker_WriteValueBitPacked_UInt_MethodRef;
1162+
return true;
1163+
}
1164+
if (paramType.FullName == typeof(long).FullName)
1165+
{
1166+
methodRef = m_BytePacker_WriteValueBitPacked_Long_MethodRef;
1167+
return true;
1168+
}
1169+
if (paramType.FullName == typeof(ulong).FullName)
1170+
{
1171+
methodRef = m_BytePacker_WriteValueBitPacked_ULong_MethodRef;
1172+
return true;
1173+
}
10111174
var assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName;
10121175
var foundMethodRef = m_FastBufferWriter_WriteValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef);
10131176

@@ -1154,6 +1317,36 @@ private MethodReference GetFastBufferReaderReadMethod(string name, TypeReference
11541317

11551318
private bool GetReadMethodForParameter(TypeReference paramType, out MethodReference methodRef)
11561319
{
1320+
if (paramType.FullName == typeof(short).FullName)
1321+
{
1322+
methodRef = m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef;
1323+
return true;
1324+
}
1325+
if (paramType.FullName == typeof(ushort).FullName)
1326+
{
1327+
methodRef = m_ByteUnpacker_ReadValueBitPacked_UShort_MethodRef;
1328+
return true;
1329+
}
1330+
if (paramType.FullName == typeof(int).FullName)
1331+
{
1332+
methodRef = m_ByteUnpacker_ReadValueBitPacked_Int_MethodRef;
1333+
return true;
1334+
}
1335+
if (paramType.FullName == typeof(uint).FullName)
1336+
{
1337+
methodRef = m_ByteUnpacker_ReadValueBitPacked_UInt_MethodRef;
1338+
return true;
1339+
}
1340+
if (paramType.FullName == typeof(long).FullName)
1341+
{
1342+
methodRef = m_ByteUnpacker_ReadValueBitPacked_Long_MethodRef;
1343+
return true;
1344+
}
1345+
if (paramType.FullName == typeof(ulong).FullName)
1346+
{
1347+
methodRef = m_ByteUnpacker_ReadValueBitPacked_ULong_MethodRef;
1348+
return true;
1349+
}
11571350
var assemblyQualifiedName = paramType.FullName + ", " + paramType.Resolve().Module.Assembly.FullName;
11581351

11591352
var foundMethodRef = m_FastBufferReader_ReadValue_MethodRefs.TryGetValue(assemblyQualifiedName, out methodRef);

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ internal enum __RpcExecStage
2121
Client = 2
2222
}
2323

24+
2425
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
2526
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
2627

@@ -776,6 +777,11 @@ internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClie
776777
if (canClientRead)
777778
{
778779
var writePos = writer.Position;
780+
// Note: This value can't be packed because we don't know how large it will be in advance
781+
// we reserve space for it, then write the data, then come back and fill in the space
782+
// to pack here, we'd have to write data to a temporary buffer and copy it in - which
783+
// isn't worth possibly saving one byte if and only if the data is less than 63 bytes long...
784+
// The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent.
779785
writer.WriteValueSafe((ushort)0);
780786
var startPos = writer.Position;
781787
NetworkVariableFields[j].WriteField(writer);

com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,14 +2185,11 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe
21852185
// Generate a SceneObject for the player object to spawn
21862186
var sceneObject = new NetworkObject.SceneObject
21872187
{
2188-
Header = new NetworkObject.SceneObject.HeaderData
2189-
{
2190-
IsPlayerObject = true,
2191-
OwnerClientId = ownerClientId,
2192-
IsSceneObject = false,
2193-
HasTransform = true,
2194-
Hash = playerPrefabHash,
2195-
},
2188+
OwnerClientId = ownerClientId,
2189+
IsPlayerObject = true,
2190+
IsSceneObject = false,
2191+
HasTransform = true,
2192+
Hash = playerPrefabHash,
21962193
TargetClientId = ownerClientId,
21972194
Transform = new NetworkObject.SceneObject.TransformData
21982195
{
@@ -2309,11 +2306,11 @@ internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash)
23092306
{
23102307
ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key)
23112308
};
2312-
message.ObjectInfo.Header.Hash = playerPrefabHash;
2313-
message.ObjectInfo.Header.IsSceneObject = false;
2314-
message.ObjectInfo.Header.HasParent = false;
2315-
message.ObjectInfo.Header.IsPlayerObject = true;
2316-
message.ObjectInfo.Header.OwnerClientId = clientId;
2309+
message.ObjectInfo.Hash = playerPrefabHash;
2310+
message.ObjectInfo.IsSceneObject = false;
2311+
message.ObjectInfo.HasParent = false;
2312+
message.ObjectInfo.IsPlayerObject = true;
2313+
message.ObjectInfo.OwnerClientId = clientId;
23172314
var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key);
23182315
NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size);
23192316
}

0 commit comments

Comments
 (0)