com.unity.netcode.gameobjects@1.2.0

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.2.0] - 2022-11-21

### Added

- Added protected method `NetworkBehaviour.OnSynchronize` which is invoked during the initial `NetworkObject` synchronization process. This provides users the ability to include custom serialization information that will be applied to the `NetworkBehaviour` prior to the `NetworkObject` being spawned. (#2298)
- Added support for different versions of the SDK to talk to each other in circumstances where changes permit it. Starting with this version and into future versions, patch versions should be compatible as long as the minor version is the same. (#2290)
- Added `NetworkObject` auto-add helper and Multiplayer Tools install reminder settings to Project Settings. (#2285)
- 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. (#2280)

### Changed

- Changed 3rd-party `XXHash` (32 & 64) implementation with an in-house reimplementation (#2310)
- When `NetworkConfig.EnsureNetworkVariableLengthSafety` is disabled `NetworkVariable` fields do not write the additional `ushort` size value (_which helps to reduce the total synchronization message size_), but when enabled it still writes the additional `ushort` value. (#2298)
- Optimized bandwidth usage by encoding most integer fields using variable-length encoding. (#2276)

### Fixed

- Fixed issue where `NetworkTransform` components nested under a parent with a `NetworkObject` component  (i.e. network prefab) would not have their associated `GameObject`'s transform synchronized. (#2298)
- Fixed issue where `NetworkObject`s that failed to instantiate could cause the entire synchronization pipeline to be disrupted/halted for a connecting client. (#2298)
- Fixed issue where in-scene placed `NetworkObject`s nested under a `GameObject` would be added to the orphaned children list causing continual console warning log messages. (#2298)
- Custom messages are now properly received by the local client when they're sent while running in host mode. (#2296)
- 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)
- Fixed an issue in `UnityTransport` where an error would be logged if the 'Use Encryption' flag was enabled with a Relay configuration that used a secure protocol. (#2289)
- Fixed issue where in-scene placed `NetworkObjects` were not honoring the `AutoObjectParentSync` property. (#2281)
- Fixed the issue where `NetworkManager.OnClientConnectedCallback` was being invoked before in-scene placed `NetworkObject`s had been spawned when starting `NetworkManager` as a host. (#2277)
- Creating a `FastBufferReader` with `Allocator.None` will not result in extra memory being allocated for the buffer (since it's owned externally in that scenario). (#2265)

### Removed

- Removed the `NetworkObject` auto-add and Multiplayer Tools install reminder settings from the Menu interface. (#2285)
This commit is contained in:
Unity Technologies
2022-11-21 00:00:00 +00:00
parent 1e7078c160
commit fe02ca682e
96 changed files with 4522 additions and 2088 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
namespace Unity.Netcode
{
@@ -68,9 +69,23 @@ namespace Unity.Netcode
if (clientIds == null)
{
throw new ArgumentNullException("You must pass in a valid clientId List");
throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List");
}
if (m_NetworkManager.IsHost)
{
for (var i = 0; i < clientIds.Count; ++i)
{
if (clientIds[i] == m_NetworkManager.LocalClientId)
{
InvokeUnnamedMessage(
m_NetworkManager.LocalClientId,
new FastBufferReader(messageBuffer, Allocator.None),
0
);
}
}
}
var message = new UnnamedMessage
{
SendData = messageBuffer
@@ -92,6 +107,18 @@ namespace Unity.Netcode
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
{
if (m_NetworkManager.IsHost)
{
if (clientId == m_NetworkManager.LocalClientId)
{
InvokeUnnamedMessage(
m_NetworkManager.LocalClientId,
new FastBufferReader(messageBuffer, Allocator.None),
0
);
return;
}
}
var message = new UnnamedMessage
{
SendData = messageBuffer
@@ -220,6 +247,20 @@ namespace Unity.Netcode
hash = XXHash.Hash64(messageName);
break;
}
if (m_NetworkManager.IsHost)
{
if (clientId == m_NetworkManager.LocalClientId)
{
InvokeNamedMessage(
hash,
m_NetworkManager.LocalClientId,
new FastBufferReader(messageStream, Allocator.None),
0
);
return;
}
}
var message = new NamedMessage
{
@@ -251,7 +292,7 @@ namespace Unity.Netcode
if (clientIds == null)
{
throw new ArgumentNullException("You must pass in a valid clientId List");
throw new ArgumentNullException(nameof(clientIds), "You must pass in a valid clientId List");
}
ulong hash = 0;
@@ -264,6 +305,21 @@ namespace Unity.Netcode
hash = XXHash.Hash64(messageName);
break;
}
if (m_NetworkManager.IsHost)
{
for (var i = 0; i < clientIds.Count; ++i)
{
if (clientIds[i] == m_NetworkManager.LocalClientId)
{
InvokeNamedMessage(
hash,
m_NetworkManager.LocalClientId,
new FastBufferReader(messageStream, Allocator.None),
0
);
}
}
}
var message = new NamedMessage
{
Hash = hash,

View File

@@ -0,0 +1,50 @@
namespace Unity.Netcode
{
internal struct DisconnectReasonMessage : INetworkMessage
{
public string Reason;
public int Version => 0;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
string reasonSent = Reason;
if (reasonSent == null)
{
reasonSent = string.Empty;
}
// Since we don't send a ConnectionApprovedMessage, the version for this message is encded with the message
// itself. However, note that we HAVE received a ConnectionRequestMessage, so we DO have a valid targetVersion
// on this side of things - we just have to make sure the receiving side knows what version we sent it,
// since whoever has the higher version number is responsible for versioning and they may be the one
// with the higher version number.
BytePacker.WriteValueBitPacked(writer, Version);
if (writer.TryBeginWrite(FastBufferWriter.GetWriteSize(reasonSent)))
{
writer.WriteValue(reasonSent);
}
else
{
writer.WriteValueSafe(string.Empty);
NetworkLog.LogWarning(
"Disconnect reason didn't fit. Disconnected without sending a reason. Consider shortening the reason string.");
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
// Since we don't get a ConnectionApprovedMessage, the version for this message is encded with the message
// itself. This will override what we got from MessagingSystem... which will always be 0 here.
ByteUnpacker.ReadValueBitPacked(reader, out receivedMessageVersion);
reader.ReadValueSafe(out Reason);
return true;
}
public void Handle(ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).DisconnectReason = Reason;
}
};
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3ada9e8fd5bf94b1f9a6a21531c8a3ee
guid: d7742516058394f96999464f3ea32c71
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -40,8 +40,9 @@ namespace Unity.Netcode
/// </summary>
internal interface INetworkMessage
{
void Serialize(FastBufferWriter writer);
bool Deserialize(FastBufferReader reader, ref NetworkContext context);
void Serialize(FastBufferWriter writer, int targetVersion);
bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion);
void Handle(ref NetworkContext context);
int Version { get; }
}
}

View File

@@ -2,22 +2,26 @@ namespace Unity.Netcode
{
internal struct ChangeOwnershipMessage : INetworkMessage, INetworkSerializeByMemcpy
{
public int Version => 0;
public ulong NetworkObjectId;
public ulong OwnerClientId;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteValueSafe(this);
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return false;
}
reader.ReadValueSafe(out this);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
namespace Unity.Netcode
{
internal struct ConnectionApprovedMessage : INetworkMessage
{
public int Version => 0;
public ulong OwnerClientId;
public int NetworkTick;
@@ -13,14 +15,26 @@ namespace Unity.Netcode
private FastBufferReader m_ReceivedSceneObjectData;
public void Serialize(FastBufferWriter writer)
public NativeArray<MessageVersionData> MessageVersions;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
if (!writer.TryBeginWrite(sizeof(ulong) + sizeof(int) + sizeof(int)))
// ============================================================
// BEGIN FORBIDDEN SEGMENT
// DO NOT CHANGE THIS HEADER. Everything added to this message
// must go AFTER the message version header.
// ============================================================
BytePacker.WriteValueBitPacked(writer, MessageVersions.Length);
foreach (var messageVersion in MessageVersions)
{
throw new OverflowException($"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}");
messageVersion.Serialize(writer);
}
writer.WriteValue(OwnerClientId);
writer.WriteValue(NetworkTick);
// ============================================================
// END FORBIDDEN SEGMENT
// ============================================================
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
BytePacker.WriteValueBitPacked(writer, NetworkTick);
uint sceneObjectCount = 0;
if (SpawnedObjectsList != null)
@@ -39,17 +53,19 @@ namespace Unity.Netcode
++sceneObjectCount;
}
}
writer.Seek(pos);
writer.WriteValue(sceneObjectCount);
// Can't pack this value because its space is reserved, so it needs to always use all the reserved space.
writer.WriteValueSafe(sceneObjectCount);
writer.Seek(writer.Length);
}
else
{
writer.WriteValue(sceneObjectCount);
writer.WriteValueSafe(sceneObjectCount);
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
@@ -57,13 +73,36 @@ namespace Unity.Netcode
return false;
}
if (!reader.TryBeginRead(sizeof(ulong) + sizeof(int) + sizeof(int)))
// ============================================================
// BEGIN FORBIDDEN SEGMENT
// DO NOT CHANGE THIS HEADER. Everything added to this message
// must go AFTER the message version header.
// ============================================================
ByteUnpacker.ReadValueBitPacked(reader, out int length);
var messageHashesInOrder = new NativeArray<uint>(length, Allocator.Temp);
for (var i = 0; i < length; ++i)
{
throw new OverflowException($"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}");
}
var messageVersion = new MessageVersionData();
messageVersion.Deserialize(reader);
networkManager.MessagingSystem.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version);
messageHashesInOrder[i] = messageVersion.Hash;
reader.ReadValue(out OwnerClientId);
reader.ReadValue(out NetworkTick);
// Update the received version since this message will always be passed version 0, due to the map not
// being initialized until just now.
var messageType = networkManager.MessagingSystem.GetMessageForHash(messageVersion.Hash);
if (messageType == typeof(ConnectionApprovedMessage))
{
receivedMessageVersion = messageVersion.Version;
}
}
networkManager.MessagingSystem.SetServerMessageOrder(messageHashesInOrder);
messageHashesInOrder.Dispose();
// ============================================================
// END FORBIDDEN SEGMENT
// ============================================================
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick);
m_ReceivedSceneObjectData = reader;
return true;
}
@@ -85,7 +124,7 @@ namespace Unity.Netcode
if (!networkManager.NetworkConfig.EnableSceneManagement)
{
networkManager.SpawnManager.DestroySceneObjects();
m_ReceivedSceneObjectData.ReadValue(out uint sceneObjectCount);
m_ReceivedSceneObjectData.ReadValueSafe(out uint sceneObjectCount);
// Deserializing NetworkVariable data is deferred from Receive() to Handle to avoid needing
// to create a list to hold the data. This is a breach of convention for performance reasons.

View File

@@ -1,15 +1,35 @@
using Unity.Collections;
namespace Unity.Netcode
{
internal struct ConnectionRequestMessage : INetworkMessage
{
public int Version => 0;
public ulong ConfigHash;
public byte[] ConnectionData;
public bool ShouldSendConnectionData;
public void Serialize(FastBufferWriter writer)
public NativeArray<MessageVersionData> MessageVersions;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
// ============================================================
// BEGIN FORBIDDEN SEGMENT
// DO NOT CHANGE THIS HEADER. Everything added to this message
// must go AFTER the message version header.
// ============================================================
BytePacker.WriteValueBitPacked(writer, MessageVersions.Length);
foreach (var messageVersion in MessageVersions)
{
messageVersion.Serialize(writer);
}
// ============================================================
// END FORBIDDEN SEGMENT
// ============================================================
if (ShouldSendConnectionData)
{
writer.WriteValueSafe(ConfigHash);
@@ -21,7 +41,7 @@ namespace Unity.Netcode
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsServer)
@@ -29,6 +49,30 @@ namespace Unity.Netcode
return false;
}
// ============================================================
// BEGIN FORBIDDEN SEGMENT
// DO NOT CHANGE THIS HEADER. Everything added to this message
// must go AFTER the message version header.
// ============================================================
ByteUnpacker.ReadValueBitPacked(reader, out int length);
for (var i = 0; i < length; ++i)
{
var messageVersion = new MessageVersionData();
messageVersion.Deserialize(reader);
networkManager.MessagingSystem.SetVersion(context.SenderId, messageVersion.Hash, messageVersion.Version);
// Update the received version since this message will always be passed version 0, due to the map not
// being initialized until just now.
var messageType = networkManager.MessagingSystem.GetMessageForHash(messageVersion.Hash);
if (messageType == typeof(ConnectionRequestMessage))
{
receivedMessageVersion = messageVersion.Version;
}
}
// ============================================================
// END FORBIDDEN SEGMENT
// ============================================================
if (networkManager.NetworkConfig.ConnectionApproval)
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash) + FastBufferWriter.GetWriteSize<int>()))

View File

@@ -2,15 +2,17 @@ namespace Unity.Netcode
{
internal struct CreateObjectMessage : INetworkMessage
{
public int Version => 0;
public NetworkObject.SceneObject ObjectInfo;
private FastBufferReader m_ReceivedNetworkVariableData;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
ObjectInfo.Serialize(writer);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
@@ -21,7 +23,7 @@ namespace Unity.Netcode
ObjectInfo.Deserialize(reader);
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
{
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Header.Hash, reader, ref context);
networkManager.DeferredMessageManager.DeferMessage(IDeferredMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context);
return false;
}
m_ReceivedNetworkVariableData = reader;

View File

@@ -2,15 +2,18 @@ namespace Unity.Netcode
{
internal struct DestroyObjectMessage : INetworkMessage, INetworkSerializeByMemcpy
{
public int Version => 0;
public ulong NetworkObjectId;
public bool DestroyGameObject;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteValueSafe(this);
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
writer.WriteValueSafe(DestroyGameObject);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
@@ -18,7 +21,8 @@ namespace Unity.Netcode
return false;
}
reader.ReadValueSafe(out this);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
reader.ReadValueSafe(out DestroyGameObject);
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
{

View File

@@ -0,0 +1,23 @@
namespace Unity.Netcode
{
/// <summary>
/// Conveys a version number on a remote node for the given message (identified by its hash)
/// </summary>
internal struct MessageVersionData
{
public uint Hash;
public int Version;
public void Serialize(FastBufferWriter writer)
{
writer.WriteValueSafe(Hash);
BytePacker.WriteValueBitPacked(writer, Version);
}
public void Deserialize(FastBufferReader reader)
{
reader.ReadValueSafe(out Hash);
ByteUnpacker.ReadValueBitPacked(reader, out Version);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 754d727b316b4263a2fa0d4c54fdad52
timeCreated: 1666895514

View File

@@ -2,18 +2,20 @@ namespace Unity.Netcode
{
internal struct NamedMessage : INetworkMessage
{
public int Version => 0;
public ulong Hash;
public FastBufferWriter SendData;
private FastBufferReader m_ReceiveData;
public unsafe void Serialize(FastBufferWriter writer)
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteValueSafe(Hash);
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
reader.ReadValueSafe(out Hash);
m_ReceiveData = reader;

View File

@@ -12,6 +12,8 @@ namespace Unity.Netcode
/// </summary>
internal struct NetworkVariableDeltaMessage : INetworkMessage
{
public int Version => 0;
public ulong NetworkObjectId;
public ushort NetworkBehaviourIndex;
@@ -21,15 +23,15 @@ namespace Unity.Netcode
private FastBufferReader m_ReceivedNetworkVariableData;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
{
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
}
writer.WriteValue(NetworkObjectId);
writer.WriteValue(NetworkBehaviourIndex);
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex);
for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++)
{
@@ -38,7 +40,7 @@ namespace Unity.Netcode
// This var does not belong to the currently iterating delivery group.
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
writer.WriteValueSafe((ushort)0);
BytePacker.WriteValueBitPacked(writer, (ushort)0);
}
else
{
@@ -66,7 +68,7 @@ namespace Unity.Netcode
{
if (!shouldWrite)
{
BytePacker.WriteValueBitPacked(writer, 0);
BytePacker.WriteValueBitPacked(writer, (ushort)0);
}
}
else
@@ -110,15 +112,10 @@ namespace Unity.Netcode
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
{
throw new OverflowException($"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}");
}
reader.ReadValue(out NetworkObjectId);
reader.ReadValue(out NetworkBehaviourIndex);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourIndex);
m_ReceivedNetworkVariableData = reader;

View File

@@ -1,50 +0,0 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Upon connecting, the host sends a series of OrderingMessage to the client so that it can make sure both sides
/// have the same message types in the same positions in
/// - MessagingSystem.m_MessageHandlers
/// - MessagingSystem.m_ReverseTypeMap
/// even if one side has extra messages (compilation, version, patch, or platform differences, etc...)
///
/// The ConnectionRequestedMessage, ConnectionApprovedMessage and OrderingMessage are prioritized at the beginning
/// of the mapping, to guarantee they can be exchanged before the two sides share their ordering
/// The sorting used in also stable so that even if MessageType names share hashes, it will work most of the time
/// </summary>
internal struct OrderingMessage : INetworkMessage
{
public int Order;
public uint Hash;
public void Serialize(FastBufferWriter writer)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
{
throw new OverflowException($"Not enough space in the buffer to write {nameof(OrderingMessage)}");
}
writer.WriteValue(Order);
writer.WriteValue(Hash);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
{
throw new OverflowException($"Not enough data in the buffer to read {nameof(OrderingMessage)}");
}
reader.ReadValue(out Order);
reader.ReadValue(out Hash);
return true;
}
public void Handle(ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).MessagingSystem.ReorderMessage(Order, Hash);
}
}
}

View File

@@ -4,18 +4,34 @@ namespace Unity.Netcode
{
internal struct ParentSyncMessage : INetworkMessage
{
public int Version => 0;
public ulong NetworkObjectId;
public bool WorldPositionStays;
private byte m_BitField;
public bool WorldPositionStays
{
get => ByteUtility.GetBit(m_BitField, 0);
set => ByteUtility.SetBit(ref m_BitField, 0, value);
}
//If(Metadata.IsReparented)
public bool IsLatestParentSet;
public bool IsLatestParentSet
{
get => ByteUtility.GetBit(m_BitField, 1);
set => ByteUtility.SetBit(ref m_BitField, 1, value);
}
//If(IsLatestParentSet)
public ulong? LatestParent;
// Is set when the parent should be removed (similar to IsReparented functionality but only for removing the parent)
public bool RemoveParent;
public bool RemoveParent
{
get => ByteUtility.GetBit(m_BitField, 2);
set => ByteUtility.SetBit(ref m_BitField, 2, value);
}
// These additional properties are used to synchronize clients with the current position,
// rotation, and scale after parenting/de-parenting (world/local space relative). This
@@ -25,18 +41,15 @@ namespace Unity.Netcode
public Quaternion Rotation;
public Vector3 Scale;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
BytePacker.WriteValuePacked(writer, NetworkObjectId);
writer.WriteValueSafe(RemoveParent);
writer.WriteValueSafe(WorldPositionStays);
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
writer.WriteValueSafe(m_BitField);
if (!RemoveParent)
{
writer.WriteValueSafe(IsLatestParentSet);
if (IsLatestParentSet)
{
BytePacker.WriteValueBitPacked(writer, (ulong)LatestParent);
BytePacker.WriteValueBitPacked(writer, LatestParent.Value);
}
}
@@ -46,7 +59,7 @@ namespace Unity.Netcode
writer.WriteValueSafe(Scale);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
@@ -54,13 +67,10 @@ namespace Unity.Netcode
return false;
}
ByteUnpacker.ReadValuePacked(reader, out NetworkObjectId);
reader.ReadValueSafe(out RemoveParent);
reader.ReadValueSafe(out WorldPositionStays);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
reader.ReadValueSafe(out m_BitField);
if (!RemoveParent)
{
reader.ReadValueSafe(out IsLatestParentSet);
if (IsLatestParentSet)
{
ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);

View File

@@ -8,24 +8,17 @@ namespace Unity.Netcode
{
public static unsafe void Serialize(ref FastBufferWriter writer, ref RpcMetadata metadata, ref FastBufferWriter payload)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize<RpcMetadata>() + payload.Length))
{
throw new OverflowException("Not enough space in the buffer to store RPC data.");
}
writer.WriteValue(metadata);
writer.WriteBytes(payload.GetUnsafePtr(), payload.Length);
BytePacker.WriteValueBitPacked(writer, metadata.NetworkObjectId);
BytePacker.WriteValueBitPacked(writer, metadata.NetworkBehaviourId);
BytePacker.WriteValueBitPacked(writer, metadata.NetworkRpcMethodId);
writer.WriteBytesSafe(payload.GetUnsafePtr(), payload.Length);
}
public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload)
{
int metadataSize = FastBufferWriter.GetWriteSize<RpcMetadata>();
if (!reader.TryBeginRead(metadataSize))
{
throw new InvalidOperationException("Not enough data in the buffer to read RPC meta.");
}
reader.ReadValue(out metadata);
ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkObjectId);
ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkBehaviourId);
ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkRpcMethodId);
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId))
@@ -46,7 +39,7 @@ namespace Unity.Netcode
return false;
}
payload = new FastBufferReader(reader.GetUnsafePtr() + metadataSize, Allocator.None, reader.Length - metadataSize);
payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
@@ -92,17 +85,19 @@ namespace Unity.Netcode
internal struct ServerRpcMessage : INetworkMessage
{
public int Version => 0;
public RpcMetadata Metadata;
public FastBufferWriter WriteBuffer;
public FastBufferReader ReadBuffer;
public unsafe void Serialize(FastBufferWriter writer)
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
{
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
}
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
}
@@ -125,17 +120,19 @@ namespace Unity.Netcode
internal struct ClientRpcMessage : INetworkMessage
{
public int Version => 0;
public RpcMetadata Metadata;
public FastBufferWriter WriteBuffer;
public FastBufferReader ReadBuffer;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
}

View File

@@ -4,16 +4,18 @@ namespace Unity.Netcode
// like most of the other messages when we have some more time and can come back and refactor this.
internal struct SceneEventMessage : INetworkMessage
{
public int Version => 0;
public SceneEventData EventData;
private FastBufferReader m_ReceivedData;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
EventData.Serialize(writer);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
m_ReceivedData = reader;
return true;

View File

@@ -2,6 +2,8 @@ namespace Unity.Netcode
{
internal struct ServerLogMessage : INetworkMessage
{
public int Version => 0;
public NetworkLog.LogType LogType;
// It'd be lovely to be able to replace this with FixedString or NativeArray...
// But it's not really practical. On the sending side, the user is likely to want
@@ -11,13 +13,13 @@ namespace Unity.Netcode
public string Message;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteValueSafe(LogType);
BytePacker.WriteValuePacked(writer, Message);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs)

View File

@@ -2,21 +2,23 @@ namespace Unity.Netcode
{
internal struct TimeSyncMessage : INetworkMessage, INetworkSerializeByMemcpy
{
public int Version => 0;
public int Tick;
public void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteValueSafe(this);
BytePacker.WriteValueBitPacked(writer, Tick);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return false;
}
reader.ReadValueSafe(out this);
ByteUnpacker.ReadValueBitPacked(reader, out Tick);
return true;
}

View File

@@ -2,15 +2,17 @@ namespace Unity.Netcode
{
internal struct UnnamedMessage : INetworkMessage
{
public int Version => 0;
public FastBufferWriter SendData;
private FastBufferReader m_ReceivedData;
public unsafe void Serialize(FastBufferWriter writer)
public unsafe void Serialize(FastBufferWriter writer, int targetVersion)
{
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
m_ReceivedData = reader;
return true;

View File

@@ -46,6 +46,7 @@ namespace Unity.Netcode
}
internal delegate void MessageHandler(FastBufferReader reader, ref NetworkContext context, MessagingSystem system);
internal delegate int VersionGetter();
private NativeList<ReceiveQueueItem> m_IncomingMessageQueue = new NativeList<ReceiveQueueItem>(16, Allocator.Persistent);
@@ -56,6 +57,11 @@ namespace Unity.Netcode
private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
// This is m_PerClientMessageVersion[clientId][messageType] = version
private Dictionary<ulong, Dictionary<Type, int>> m_PerClientMessageVersions = new Dictionary<ulong, Dictionary<Type, int>>();
private Dictionary<uint, Type> m_MessagesByHash = new Dictionary<uint, Type>();
private Dictionary<Type, int> m_LocalVersions = new Dictionary<Type, int>();
private List<INetworkHooks> m_Hooks = new List<INetworkHooks>();
private uint m_HighMessageType;
@@ -74,12 +80,13 @@ namespace Unity.Netcode
}
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
public const int FRAGMENTED_MESSAGE_MAX_SIZE = BytePacker.BitPackedIntMax;
public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue;
internal struct MessageWithHandler
{
public Type MessageType;
public MessageHandler Handler;
public VersionGetter GetVersion;
}
internal List<MessageWithHandler> PrioritizeMessageOrder(List<MessageWithHandler> allowedTypes)
@@ -90,9 +97,8 @@ namespace Unity.Netcode
// Those are the messages that must be delivered in order to allow re-ordering the others later
foreach (var t in allowedTypes)
{
if (t.MessageType.FullName == "Unity.Netcode.ConnectionRequestMessage" ||
t.MessageType.FullName == "Unity.Netcode.ConnectionApprovedMessage" ||
t.MessageType.FullName == "Unity.Netcode.OrderingMessage")
if (t.MessageType.FullName == typeof(ConnectionRequestMessage).FullName ||
t.MessageType.FullName == typeof(ConnectionApprovedMessage).FullName)
{
prioritizedTypes.Add(t);
}
@@ -100,9 +106,8 @@ namespace Unity.Netcode
foreach (var t in allowedTypes)
{
if (t.MessageType.FullName != "Unity.Netcode.ConnectionRequestMessage" &&
t.MessageType.FullName != "Unity.Netcode.ConnectionApprovedMessage" &&
t.MessageType.FullName != "Unity.Netcode.OrderingMessage")
if (t.MessageType.FullName != typeof(ConnectionRequestMessage).FullName &&
t.MessageType.FullName != typeof(ConnectionApprovedMessage).FullName)
{
prioritizedTypes.Add(t);
}
@@ -189,7 +194,14 @@ namespace Unity.Netcode
m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler;
m_ReverseTypeMap[m_HighMessageType] = messageWithHandler.MessageType;
m_MessagesByHash[XXHash.Hash32(messageWithHandler.MessageType.FullName)] = messageWithHandler.MessageType;
m_MessageTypes[messageWithHandler.MessageType] = m_HighMessageType++;
m_LocalVersions[messageWithHandler.MessageType] = messageWithHandler.GetVersion();
}
public int GetLocalVersion(Type messageType)
{
return m_LocalVersions[messageType];
}
internal void HandleIncomingData(ulong clientId, ArraySegment<byte> data, float receiveTime)
@@ -270,68 +282,53 @@ namespace Unity.Netcode
return true;
}
// Moves the handler for the type having hash `targetHash` to the `desiredOrder` position, in the handler list
// This allows the server to tell the client which id it is using for which message and make sure the right
// message is used when deserializing.
internal void ReorderMessage(int desiredOrder, uint targetHash)
internal Type GetMessageForHash(uint messageHash)
{
if (desiredOrder < 0)
if (!m_MessagesByHash.ContainsKey(messageHash))
{
throw new ArgumentException("ReorderMessage desiredOrder must be positive");
return null;
}
return m_MessagesByHash[messageHash];
}
if (desiredOrder < m_ReverseTypeMap.Length &&
XXHash.Hash32(m_ReverseTypeMap[desiredOrder].FullName) == targetHash)
internal void SetVersion(ulong clientId, uint messageHash, int version)
{
if (!m_MessagesByHash.ContainsKey(messageHash))
{
// matching positions and hashes. All good.
return;
}
var messageType = m_MessagesByHash[messageHash];
Debug.Log($"Unexpected hash for {desiredOrder}");
// Since the message at `desiredOrder` is not the expected one,
// insert an empty placeholder and move the messages down
var typesAsList = new List<Type>(m_ReverseTypeMap);
typesAsList.Insert(desiredOrder, null);
var handlersAsList = new List<MessageHandler>(m_MessageHandlers);
handlersAsList.Insert(desiredOrder, null);
// we added a dummy message, bump the end up
m_HighMessageType++;
// Here, we rely on the server telling us about all messages, in order.
// So, we know the handlers before desiredOrder are correct.
// We start at desiredOrder to not shift them when we insert.
int position = desiredOrder;
bool found = false;
while (position < typesAsList.Count)
if (!m_PerClientMessageVersions.ContainsKey(clientId))
{
if (typesAsList[position] != null &&
XXHash.Hash32(typesAsList[position].FullName) == targetHash)
m_PerClientMessageVersions[clientId] = new Dictionary<Type, int>();
}
m_PerClientMessageVersions[clientId][messageType] = version;
}
internal void SetServerMessageOrder(NativeArray<uint> messagesInIdOrder)
{
var oldHandlers = m_MessageHandlers;
var oldTypes = m_MessageTypes;
m_ReverseTypeMap = new Type[messagesInIdOrder.Length];
m_MessageHandlers = new MessageHandler[messagesInIdOrder.Length];
m_MessageTypes = new Dictionary<Type, uint>();
for (var i = 0; i < messagesInIdOrder.Length; ++i)
{
if (!m_MessagesByHash.ContainsKey(messagesInIdOrder[i]))
{
found = true;
break;
continue;
}
position++;
var messageType = m_MessagesByHash[messagesInIdOrder[i]];
var oldId = oldTypes[messageType];
var handler = oldHandlers[oldId];
var newId = (uint)i;
m_MessageTypes[messageType] = newId;
m_MessageHandlers[newId] = handler;
m_ReverseTypeMap[newId] = messageType;
}
if (found)
{
// Copy the handler and type to the right index
typesAsList[desiredOrder] = typesAsList[position];
handlersAsList[desiredOrder] = handlersAsList[position];
typesAsList.RemoveAt(position);
handlersAsList.RemoveAt(position);
// we removed a copy after moving a message, reduce the high message index
m_HighMessageType--;
}
m_ReverseTypeMap = typesAsList.ToArray();
m_MessageHandlers = handlersAsList.ToArray();
}
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
@@ -433,7 +430,7 @@ namespace Unity.Netcode
m_SendQueues.Remove(clientId);
}
private unsafe void CleanupDisconnectedClient(ulong clientId)
private void CleanupDisconnectedClient(ulong clientId)
{
var queue = m_SendQueues[clientId];
for (var i = 0; i < queue.Length; ++i)
@@ -444,10 +441,67 @@ namespace Unity.Netcode
queue.Dispose();
}
internal void CleanupDisconnectedClients()
{
var removeList = new NativeList<ulong>(Allocator.Temp);
foreach (var clientId in m_PerClientMessageVersions.Keys)
{
if (!m_SendQueues.ContainsKey(clientId))
{
removeList.Add(clientId);
}
}
foreach (var clientId in removeList)
{
m_PerClientMessageVersions.Remove(clientId);
}
}
public static int CreateMessageAndGetVersion<T>() where T : INetworkMessage, new()
{
return new T().Version;
}
internal int GetMessageVersion(Type type, ulong clientId, bool forReceive = false)
{
if (!m_PerClientMessageVersions.TryGetValue(clientId, out var versionMap))
{
if (forReceive)
{
Debug.LogWarning($"Trying to receive {type.Name} from client {clientId} which is not in a connected state.");
}
else
{
Debug.LogWarning($"Trying to send {type.Name} to client {clientId} which is not in a connected state.");
}
return -1;
}
if (!versionMap.TryGetValue(type, out var messageVersion))
{
return -1;
}
return messageVersion;
}
public static void ReceiveMessage<T>(FastBufferReader reader, ref NetworkContext context, MessagingSystem system) where T : INetworkMessage, new()
{
var message = new T();
if (message.Deserialize(reader, ref context))
var messageVersion = 0;
// Special cases because these are the messages that carry the version info - thus the version info isn't
// populated yet when we get these. The first part of these messages always has to be the version data
// and can't change.
if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage))
{
messageVersion = system.GetMessageVersion(typeof(T), context.SenderId, true);
if (messageVersion < 0)
{
return;
}
}
if (message.Deserialize(reader, ref context, messageVersion))
{
for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx)
{
@@ -485,16 +539,47 @@ namespace Unity.Netcode
return 0;
}
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
var largestSerializedSize = 0;
var sentMessageVersions = new NativeHashSet<int>(clientIds.Count, Allocator.Temp);
for (var i = 0; i < clientIds.Count; ++i)
{
var messageVersion = 0;
// Special case because this is the message that carries the version info - thus the version info isn't
// populated yet when we get this. The first part of this message always has to be the version data
// and can't change.
if (typeof(TMessageType) != typeof(ConnectionRequestMessage))
{
messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]);
if (messageVersion < 0)
{
// Client doesn't know this message exists, don't send it at all.
continue;
}
}
using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
if (sentMessageVersions.Contains(messageVersion))
{
continue;
}
message.Serialize(tmpSerializer);
sentMessageVersions.Add(messageVersion);
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds);
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
message.Serialize(tmpSerializer, messageVersion);
var size = SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds, messageVersion);
largestSerializedSize = size > largestSerializedSize ? size : largestSerializedSize;
}
sentMessageVersions.Dispose();
return largestSerializedSize;
}
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList<ulong> clientIds)
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList<ulong> clientIds, int messageVersionFilter)
where TMessageType : INetworkMessage
{
using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp);
@@ -509,6 +594,25 @@ namespace Unity.Netcode
for (var i = 0; i < clientIds.Count; ++i)
{
var messageVersion = 0;
// Special case because this is the message that carries the version info - thus the version info isn't
// populated yet when we get this. The first part of this message always has to be the version data
// and can't change.
if (typeof(TMessageType) != typeof(ConnectionRequestMessage))
{
messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]);
if (messageVersion < 0)
{
// Client doesn't know this message exists, don't send it at all.
continue;
}
if (messageVersion != messageVersionFilter)
{
continue;
}
}
var clientId = clientIds[i];
if (!CanSend(clientId, typeof(TMessageType), delivery))
@@ -559,8 +663,22 @@ namespace Unity.Netcode
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, ulong clientId)
where TMessageType : INetworkMessage
{
var messageVersion = 0;
// Special case because this is the message that carries the version info - thus the version info isn't
// populated yet when we get this. The first part of this message always has to be the version data
// and can't change.
if (typeof(TMessageType) != typeof(ConnectionRequestMessage))
{
messageVersion = GetMessageVersion(typeof(TMessageType), clientId);
if (messageVersion < 0)
{
// Client doesn't know this message exists, don't send it at all.
return 0;
}
}
ulong* clientIds = stackalloc ulong[] { clientId };
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, new PointerListWrapper<ulong>(clientIds, 1));
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, new PointerListWrapper<ulong>(clientIds, 1), messageVersion);
}
private struct PointerListWrapper<T> : IReadOnlyList<T>