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

@@ -21,6 +21,7 @@ namespace Unity.Netcode
Client = 2
}
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
@@ -286,7 +287,18 @@ namespace Unity.Netcode
/// Gets the NetworkManager that owns this NetworkBehaviour instance
/// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized
/// </summary>
public NetworkManager NetworkManager => NetworkObject.NetworkManager;
public NetworkManager NetworkManager
{
get
{
if (NetworkObject?.NetworkManager != null)
{
return NetworkObject?.NetworkManager;
}
return NetworkManager.Singleton;
}
}
/// <summary>
/// If a NetworkObject is assigned, it will return whether or not this NetworkObject
@@ -335,23 +347,29 @@ namespace Unity.Netcode
m_NetworkObject.NetworkManager.IsServer;
}
/// <summary>
/// Gets the NetworkObject that owns this NetworkBehaviour instance
/// TODO: this needs an overhaul. It's expensive, it's ja little naive in how it looks for networkObject in
/// its parent and worst, it creates a puzzle if you are a NetworkBehaviour wanting to see if you're live or not
/// (e.g. editor code). All you want to do is find out if NetworkManager is null, but to do that you
/// need NetworkObject, but if you try and grab NetworkObject and NetworkManager isn't up you'll get
/// the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do
/// how NetworkObject works but it was close to the release and too risky to change
///
/// <summary>
/// Gets the NetworkObject that owns this NetworkBehaviour instance
/// </summary>
public NetworkObject NetworkObject
{
get
{
if (m_NetworkObject == null)
try
{
m_NetworkObject = GetComponentInParent<NetworkObject>();
if (m_NetworkObject == null)
{
m_NetworkObject = GetComponentInParent<NetworkObject>();
}
}
catch (Exception)
{
return null;
}
// ShutdownInProgress check:
@@ -712,7 +730,7 @@ namespace Unity.Netcode
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
using (tmpWriter)
{
message.Serialize(tmpWriter);
message.Serialize(tmpWriter, message.Version);
}
}
else
@@ -745,6 +763,14 @@ namespace Unity.Netcode
}
}
/// <summary>
/// Synchronizes by setting only the NetworkVariable field values that the client has permission to read.
/// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject)
/// </summary>
/// <remarks>
/// When NetworkConfig.EnsureNetworkVariableLengthSafety is enabled each NetworkVariable field will be preceded
/// by the number of bytes written for that specific field.
/// </remarks>
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
{
if (NetworkVariableFields.Count == 0)
@@ -754,27 +780,47 @@ namespace Unity.Netcode
for (int j = 0; j < NetworkVariableFields.Count; j++)
{
bool canClientRead = NetworkVariableFields[j].CanClientRead(targetClientId);
if (canClientRead)
if (NetworkVariableFields[j].CanClientRead(targetClientId))
{
var writePos = writer.Position;
writer.WriteValueSafe((ushort)0);
var startPos = writer.Position;
NetworkVariableFields[j].WriteField(writer);
var size = writer.Position - startPos;
writer.Seek(writePos);
writer.WriteValueSafe((ushort)size);
writer.Seek(startPos + size);
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
var writePos = writer.Position;
// Note: This value can't be packed because we don't know how large it will be in advance
// we reserve space for it, then write the data, then come back and fill in the space
// to pack here, we'd have to write data to a temporary buffer and copy it in - which
// isn't worth possibly saving one byte if and only if the data is less than 63 bytes long...
// The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent.
writer.WriteValueSafe((ushort)0);
var startPos = writer.Position;
NetworkVariableFields[j].WriteField(writer);
var size = writer.Position - startPos;
writer.Seek(writePos);
writer.WriteValueSafe((ushort)size);
writer.Seek(startPos + size);
}
else
{
NetworkVariableFields[j].WriteField(writer);
}
}
else
else // Only if EnsureNetworkVariableLengthSafety, otherwise just skip
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
writer.WriteValueSafe((ushort)0);
}
}
}
internal void SetNetworkVariableData(FastBufferReader reader)
/// <summary>
/// Synchronizes by setting only the NetworkVariable field values that the client has permission to read.
/// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject)
/// </summary>
/// <remarks>
/// When NetworkConfig.EnsureNetworkVariableLengthSafety is enabled each NetworkVariable field will be preceded
/// by the number of bytes written for that specific field.
/// </remarks>
internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
{
if (NetworkVariableFields.Count == 0)
{
@@ -783,13 +829,23 @@ namespace Unity.Netcode
for (int j = 0; j < NetworkVariableFields.Count; j++)
{
reader.ReadValueSafe(out ushort varSize);
if (varSize == 0)
var varSize = (ushort)0;
var readStartPos = 0;
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
reader.ReadValueSafe(out varSize);
if (varSize == 0)
{
continue;
}
readStartPos = reader.Position;
}
else // If the client cannot read this field, then skip it
if (!NetworkVariableFields[j].CanClientRead(clientId))
{
continue;
}
var readStartPos = reader.Position;
NetworkVariableFields[j].ReadField(reader);
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
@@ -826,6 +882,138 @@ namespace Unity.Netcode
return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null;
}
/// <summary>
/// Override this method if your derived NetworkBehaviour requires custom synchronization data.
/// Note: Use of this method is only for the initial client synchronization of NetworkBehaviours
/// and will increase the payload size for client synchronization and dynamically spawned
/// <see cref="NetworkObject"/>s.
/// </summary>
/// <remarks>
/// When serializing (writing) this will be invoked during the client synchronization period and
/// when spawning new NetworkObjects.
/// When deserializing (reading), this will be invoked prior to the NetworkBehaviour's associated
/// NetworkObject being spawned.
/// </remarks>
/// <param name="serializer">The serializer to use to read and write the data.</param>
/// <typeparam name="T">
/// Either BufferSerializerReader or BufferSerializerWriter, depending whether the serializer
/// is in read mode or write mode.
/// </typeparam>
protected virtual void OnSynchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
{
}
/// <summary>
/// Internal method that determines if a NetworkBehaviour has additional synchronization data to
/// be synchronized when first instantiated prior to its associated NetworkObject being spawned.
/// </summary>
/// <remarks>
/// This includes try-catch blocks to recover from exceptions that might occur and continue to
/// synchronize any remaining NetworkBehaviours.
/// </remarks>
/// <returns>true if it wrote synchronization data and false if it did not</returns>
internal bool Synchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
{
if (serializer.IsWriter)
{
// Get the writer to handle seeking and determining how many bytes were written
var writer = serializer.GetFastBufferWriter();
// Save our position before we attempt to write anything so we can seek back to it (i.e. error occurs)
var positionBeforeWrite = writer.Position;
writer.WriteValueSafe(NetworkBehaviourId);
// Save our position where we will write the final size being written so we can skip over it in the
// event an exception occurs when deserializing.
var sizePosition = writer.Position;
writer.WriteValueSafe((ushort)0);
// Save our position before synchronizing to determine how much was written
var positionBeforeSynchronize = writer.Position;
var threwException = false;
try
{
OnSynchronize(ref serializer);
}
catch (Exception ex)
{
threwException = true;
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{name} threw an exception during synchronization serialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}");
}
}
}
var finalPosition = writer.Position;
// If we wrote nothing then skip writing anything for this NetworkBehaviour
if (finalPosition == positionBeforeSynchronize || threwException)
{
writer.Seek(positionBeforeWrite);
return false;
}
else
{
// Write the number of bytes serialized to handle exceptions on the deserialization side
var bytesWritten = finalPosition - positionBeforeSynchronize;
writer.Seek(sizePosition);
writer.WriteValueSafe((ushort)bytesWritten);
writer.Seek(finalPosition);
}
return true;
}
else
{
var reader = serializer.GetFastBufferReader();
// We will always read the expected byte count
reader.ReadValueSafe(out ushort expectedBytesToRead);
// Save our position before we begin synchronization deserialization
var positionBeforeSynchronize = reader.Position;
var synchronizationError = false;
try
{
// Invoke synchronization
OnSynchronize(ref serializer);
}
catch (Exception ex)
{
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{name} threw an exception during synchronization deserialization, this {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogError($"{ex.Message}\n {ex.StackTrace}");
}
}
synchronizationError = true;
}
var totalBytesRead = reader.Position - positionBeforeSynchronize;
if (totalBytesRead != expectedBytesToRead)
{
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{name} read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes during synchronization deserialization! This {nameof(NetworkBehaviour)} is being skipped and will not be synchronized!");
}
synchronizationError = true;
}
// Skip over the entry if deserialization fails
if (synchronizationError)
{
var skipToPosition = positionBeforeSynchronize + expectedBytesToRead;
reader.Seek(skipToPosition);
return false;
}
return true;
}
}
/// <summary>
/// Invoked when the <see cref="GameObject"/> the <see cref="NetworkBehaviour"/> is attached to.
/// NOTE: If you override this, you will want to always invoke this base class version of this

View File

@@ -86,6 +86,12 @@ namespace Unity.Netcode
private bool m_ShuttingDown;
private bool m_StopProcessingMessages;
/// <summary>
/// When disconnected from the server, the server may send a reason. If a reason was sent, this property will
/// tell client code what the reason was. It should be queried after the OnClientDisconnectCallback is called
/// </summary>
public string DisconnectReason { get; internal set; }
private class NetworkManagerHooks : INetworkHooks
{
private NetworkManager m_NetworkManager;
@@ -443,6 +449,12 @@ namespace Unity.Netcode
/// If the Approval decision cannot be made immediately, the client code can set Pending to true, keep a reference to the ConnectionApprovalResponse object and write to it later. Client code must exercise care to setting all the members to the value it wants before marking Pending to false, to indicate completion. If the field is set as Pending = true, we'll monitor the object until it gets set to not pending anymore and use the parameters then.
/// </summary>
public bool Pending;
/// <summary>
/// Optional reason. If Approved is false, this reason will be sent to the client so they know why they
/// were not approved.
/// </summary>
public string Reason;
}
/// <summary>
@@ -889,6 +901,7 @@ namespace Unity.Netcode
return;
}
DisconnectReason = string.Empty;
IsApproved = false;
ComponentFactory.SetDefaults();
@@ -1181,6 +1194,11 @@ namespace Unity.Netcode
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
// This assures that any in-scene placed NetworkObject is spawned and
// any associated NetworkBehaviours' netcode related properties are
// set prior to invoking OnClientConnected.
InvokeOnClientConnectedCallback(LocalClientId);
OnServerStarted?.Invoke();
return true;
@@ -1341,6 +1359,7 @@ namespace Unity.Netcode
private void DisconnectRemoteClient(ulong clientId)
{
var transportId = ClientIdToTransportId(clientId);
MessagingSystem.ProcessSendQueues();
NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
}
@@ -1421,7 +1440,7 @@ namespace Unity.Netcode
}
}
if (IsClient && IsConnectedClient)
if (IsClient && IsListening)
{
// Client only, send disconnect to server
NetworkConfig.NetworkTransport.DisconnectLocalClient();
@@ -1579,6 +1598,7 @@ namespace Unity.Netcode
} while (IsListening && networkEvent != NetworkEvent.Nothing);
MessagingSystem.ProcessIncomingMessageQueue();
MessagingSystem.CleanupDisconnectedClients();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportPoll.End();
@@ -1660,7 +1680,23 @@ namespace Unity.Netcode
ShouldSendConnectionData = NetworkConfig.ConnectionApproval,
ConnectionData = NetworkConfig.ConnectionData
};
message.MessageVersions = new NativeArray<MessageVersionData>(MessagingSystem.MessageHandlers.Length, Allocator.Temp);
for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
{
if (MessagingSystem.MessageTypes[index] != null)
{
var type = MessagingSystem.MessageTypes[index];
message.MessageVersions[index] = new MessageVersionData
{
Hash = XXHash.Hash32(type.FullName),
Version = MessagingSystem.GetLocalVersion(type)
};
}
}
SendMessage(ref message, NetworkDelivery.ReliableSequenced, ServerClientId);
message.MessageVersions.Dispose();
}
private IEnumerator ApprovalTimeout(ulong clientId)
@@ -1821,7 +1857,18 @@ namespace Unity.Netcode
NetworkLog.LogInfo($"Disconnect Event From {clientId}");
}
OnClientDisconnectCallback?.Invoke(clientId);
// Process the incoming message queue so that we get everything from the server disconnecting us
// or, if we are the server, so we got everything from that client.
MessagingSystem.ProcessIncomingMessageQueue();
try
{
OnClientDisconnectCallback?.Invoke(clientId);
}
catch (Exception exception)
{
Debug.LogException(exception);
}
if (IsServer)
{
@@ -1987,12 +2034,31 @@ namespace Unity.Netcode
/// </summary>
/// <param name="clientId">The ClientId to disconnect</param>
public void DisconnectClient(ulong clientId)
{
DisconnectClient(clientId, null);
}
/// <summary>
/// Disconnects the remote client.
/// </summary>
/// <param name="clientId">The ClientId to disconnect</param>
/// <param name="reason">Disconnection reason. If set, client will receive a DisconnectReasonMessage and have the
/// reason available in the NetworkManager.DisconnectReason property</param>
public void DisconnectClient(ulong clientId, string reason)
{
if (!IsServer)
{
throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead.");
}
if (!string.IsNullOrEmpty(reason))
{
var disconnectReason = new DisconnectReasonMessage();
disconnectReason.Reason = reason;
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId);
}
MessagingSystem.ProcessSendQueues();
OnClientDisconnectFromServer(clientId);
DisconnectRemoteClient(clientId);
}
@@ -2137,14 +2203,11 @@ namespace Unity.Netcode
// Generate a SceneObject for the player object to spawn
var sceneObject = new NetworkObject.SceneObject
{
Header = new NetworkObject.SceneObject.HeaderData
{
IsPlayerObject = true,
OwnerClientId = ownerClientId,
IsSceneObject = false,
HasTransform = true,
Hash = playerPrefabHash,
},
OwnerClientId = ownerClientId,
IsPlayerObject = true,
IsSceneObject = false,
HasTransform = true,
Hash = playerPrefabHash,
TargetClientId = ownerClientId,
Transform = new NetworkObject.SceneObject.TransformData
{
@@ -2184,22 +2247,23 @@ namespace Unity.Netcode
}
}
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
message.MessageVersions = new NativeArray<MessageVersionData>(MessagingSystem.MessageHandlers.Length, Allocator.Temp);
for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
{
if (MessagingSystem.MessageTypes[index] != null)
{
var orderingMessage = new OrderingMessage
var type = MessagingSystem.MessageTypes[index];
message.MessageVersions[index] = new MessageVersionData
{
Order = index,
Hash = XXHash.Hash32(MessagingSystem.MessageTypes[index].FullName)
Hash = XXHash.Hash32(type.FullName),
Version = MessagingSystem.GetLocalVersion(type)
};
SendMessage(ref orderingMessage, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
}
}
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
message.MessageVersions.Dispose();
// If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
if (!NetworkConfig.EnableSceneManagement)
{
@@ -2214,7 +2278,6 @@ namespace Unity.Netcode
{
LocalClient = client;
SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
InvokeOnClientConnectedCallback(ownerClientId);
}
if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkConfig.PlayerPrefab == null))
@@ -2227,6 +2290,15 @@ namespace Unity.Netcode
}
else
{
if (!string.IsNullOrEmpty(response.Reason))
{
var disconnectReason = new DisconnectReasonMessage();
disconnectReason.Reason = response.Reason;
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId);
MessagingSystem.ProcessSendQueues();
}
PendingClients.Remove(ownerClientId);
DisconnectRemoteClient(ownerClientId);
}
@@ -2253,11 +2325,11 @@ namespace Unity.Netcode
{
ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key)
};
message.ObjectInfo.Header.Hash = playerPrefabHash;
message.ObjectInfo.Header.IsSceneObject = false;
message.ObjectInfo.Header.HasParent = false;
message.ObjectInfo.Header.IsPlayerObject = true;
message.ObjectInfo.Header.OwnerClientId = clientId;
message.ObjectInfo.Hash = playerPrefabHash;
message.ObjectInfo.IsSceneObject = false;
message.ObjectInfo.HasParent = false;
message.ObjectInfo.IsPlayerObject = true;
message.ObjectInfo.OwnerClientId = clientId;
var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key);
NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size);
}

View File

@@ -830,6 +830,9 @@ namespace Unity.Netcode
// parent and then re-parents the child under a GameObject with a NetworkObject component attached.
if (parentNetworkObject == null)
{
// If we are parented under a GameObject, go ahead and mark the world position stays as false
// so clients synchronize their transform in local space. (only for in-scene placed NetworkObjects)
m_CachedWorldPositionStays = false;
return true;
}
else // If the parent still isn't spawned add this to the orphaned children and return false
@@ -841,8 +844,11 @@ namespace Unity.Netcode
else
{
// If we made it this far, go ahead and set the network parenting values
// with the default WorldPoisitonSays value
SetNetworkParenting(parentNetworkObject.NetworkObjectId, true);
// with the WorldPoisitonSays value set to false
// Note: Since in-scene placed NetworkObjects are parented in the scene
// the default "assumption" is that children are parenting local space
// relative.
SetNetworkParenting(parentNetworkObject.NetworkObjectId, false);
// Set the cached parent
m_CachedParent = parentNetworkObject.transform;
@@ -1002,13 +1008,17 @@ namespace Unity.Netcode
}
}
}
internal void SetNetworkVariableData(FastBufferReader reader)
/// <summary>
/// Only invoked during first synchronization of a NetworkObject (late join or newly spawned)
/// </summary>
internal void SetNetworkVariableData(FastBufferReader reader, ulong clientId)
{
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
var behaviour = ChildNetworkBehaviours[i];
behaviour.InitializeVariables();
behaviour.SetNetworkVariableData(reader);
behaviour.SetNetworkVariableData(reader, clientId);
}
}
@@ -1045,7 +1055,20 @@ namespace Unity.Netcode
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?");
NetworkLog.LogError($"{nameof(NetworkBehaviour)} index {index} was out of bounds for {name}. NetworkBehaviours must be the same, and in the same order, between server and client.");
}
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
var currentKnownChildren = new System.Text.StringBuilder();
currentKnownChildren.Append($"Known child {nameof(NetworkBehaviour)}s:");
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
var childNetworkBehaviour = ChildNetworkBehaviours[i];
currentKnownChildren.Append($" [{i}] {childNetworkBehaviour.__getTypeName()}");
currentKnownChildren.Append(i < ChildNetworkBehaviours.Count - 1 ? "," : ".");
}
NetworkLog.LogInfo(currentKnownChildren.ToString());
}
return null;
@@ -1056,19 +1079,43 @@ namespace Unity.Netcode
internal struct SceneObject
{
public struct HeaderData : INetworkSerializeByMemcpy
{
public ulong NetworkObjectId;
public ulong OwnerClientId;
public uint Hash;
private byte m_BitField;
public uint Hash;
public ulong NetworkObjectId;
public ulong OwnerClientId;
public bool IsPlayerObject;
public bool HasParent;
public bool IsSceneObject;
public bool HasTransform;
public bool IsPlayerObject
{
get => ByteUtility.GetBit(m_BitField, 0);
set => ByteUtility.SetBit(ref m_BitField, 0, value);
}
public bool HasParent
{
get => ByteUtility.GetBit(m_BitField, 1);
set => ByteUtility.SetBit(ref m_BitField, 1, value);
}
public bool IsSceneObject
{
get => ByteUtility.GetBit(m_BitField, 2);
set => ByteUtility.SetBit(ref m_BitField, 2, value);
}
public bool HasTransform
{
get => ByteUtility.GetBit(m_BitField, 3);
set => ByteUtility.SetBit(ref m_BitField, 3, value);
}
public HeaderData Header;
public bool IsLatestParentSet
{
get => ByteUtility.GetBit(m_BitField, 4);
set => ByteUtility.SetBit(ref m_BitField, 4, value);
}
public bool WorldPositionStays
{
get => ByteUtility.GetBit(m_BitField, 5);
set => ByteUtility.SetBit(ref m_BitField, 5, value);
}
//If(Metadata.HasParent)
public ulong ParentObjectId;
@@ -1084,7 +1131,6 @@ namespace Unity.Netcode
public TransformData Transform;
//If(Metadata.IsReparented)
public bool IsLatestParentSet;
//If(IsLatestParentSet)
public ulong? LatestParent;
@@ -1094,114 +1140,90 @@ namespace Unity.Netcode
public int NetworkSceneHandle;
public bool WorldPositionStays;
public unsafe void Serialize(FastBufferWriter writer)
public void Serialize(FastBufferWriter writer)
{
var writeSize = sizeof(HeaderData);
if (Header.HasParent)
writer.WriteValueSafe(m_BitField);
writer.WriteValueSafe(Hash);
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
if (HasParent)
{
writeSize += FastBufferWriter.GetWriteSize(ParentObjectId);
writeSize += FastBufferWriter.GetWriteSize(WorldPositionStays);
writeSize += FastBufferWriter.GetWriteSize(IsLatestParentSet);
writeSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0;
BytePacker.WriteValueBitPacked(writer, ParentObjectId);
if (IsLatestParentSet)
{
BytePacker.WriteValueBitPacked(writer, LatestParent.Value);
}
}
writeSize += Header.HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
writeSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
var writeSize = 0;
writeSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
writeSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
if (!writer.TryBeginWrite(writeSize))
{
throw new OverflowException("Could not serialize SceneObject: Out of buffer space.");
}
writer.WriteValue(Header);
if (Header.HasParent)
{
writer.WriteValue(ParentObjectId);
writer.WriteValue(WorldPositionStays);
writer.WriteValue(IsLatestParentSet);
if (IsLatestParentSet)
{
writer.WriteValue(LatestParent.Value);
}
}
if (Header.HasTransform)
if (HasTransform)
{
writer.WriteValue(Transform);
}
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
// This is only set on in-scene placed NetworkObjects to reduce the over-all packet
// sizes for dynamically spawned NetworkObjects.
if (Header.IsSceneObject)
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
// this to locate their local instance of the in-scene placed NetworkObject instance.
// Only written for in-scene placed NetworkObjects.
if (IsSceneObject)
{
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
}
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
// Synchronize NetworkVariables and NetworkBehaviours
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
OwnerObject.SynchronizeNetworkBehaviours(ref bufferSerializer, TargetClientId);
}
public unsafe void Deserialize(FastBufferReader reader)
public void Deserialize(FastBufferReader reader)
{
if (!reader.TryBeginRead(sizeof(HeaderData)))
reader.ReadValueSafe(out m_BitField);
reader.ReadValueSafe(out Hash);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
if (HasParent)
{
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
}
reader.ReadValue(out Header);
var readSize = 0;
if (Header.HasParent)
{
readSize += FastBufferWriter.GetWriteSize(ParentObjectId);
readSize += FastBufferWriter.GetWriteSize(WorldPositionStays);
readSize += FastBufferWriter.GetWriteSize(IsLatestParentSet);
// We need to read at this point in order to get the IsLatestParentSet value
if (!reader.TryBeginRead(readSize))
ByteUnpacker.ReadValueBitPacked(reader, out ParentObjectId);
if (IsLatestParentSet)
{
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
ByteUnpacker.ReadValueBitPacked(reader, out ulong latestParent);
LatestParent = latestParent;
}
// Read the initial parenting related properties
reader.ReadValue(out ParentObjectId);
reader.ReadValue(out WorldPositionStays);
reader.ReadValue(out IsLatestParentSet);
// Now calculate the remaining bytes to read
readSize = 0;
readSize += IsLatestParentSet ? FastBufferWriter.GetWriteSize<ulong>() : 0;
}
readSize += Header.HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
readSize += Header.IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
var readSize = 0;
readSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
readSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
// Try to begin reading the remaining bytes
if (!reader.TryBeginRead(readSize))
{
throw new OverflowException("Could not deserialize SceneObject: Out of buffer space.");
throw new OverflowException("Could not deserialize SceneObject: Reading past the end of the buffer");
}
if (IsLatestParentSet)
{
reader.ReadValueSafe(out ulong latestParent);
LatestParent = latestParent;
}
if (Header.HasTransform)
if (HasTransform)
{
reader.ReadValue(out Transform);
}
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
// NetworkSceneHandle and GlobalObjectIdHash. Since each loaded scene has a unique
// handle, it provides us with a unique and persistent "scene prefab asset" instance.
// Client-side NetworkSceneManagers use this to locate their local instance of the
// NetworkObject instance.
if (Header.IsSceneObject)
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
// this to locate their local instance of the in-scene placed NetworkObject instance.
// Only read for in-scene placed NetworkObjects
if (IsSceneObject)
{
reader.ReadValueSafe(out NetworkSceneHandle);
reader.ReadValue(out NetworkSceneHandle);
}
}
}
@@ -1214,18 +1236,87 @@ namespace Unity.Netcode
}
}
/// <summary>
/// Handles synchronizing NetworkVariables and custom synchronization data for NetworkBehaviours.
/// </summary>
/// <remarks>
/// This is where we determine how much data is written after the associated NetworkObject in order to recover
/// from a failed instantiated NetworkObject without completely disrupting client synchronization.
/// </remarks>
internal void SynchronizeNetworkBehaviours<T>(ref BufferSerializer<T> serializer, ulong targetClientId = 0) where T : IReaderWriter
{
if (serializer.IsWriter)
{
var writer = serializer.GetFastBufferWriter();
var positionBeforeSynchronizing = writer.Position;
writer.WriteValueSafe((ushort)0);
var sizeToSkipCalculationPosition = writer.Position;
// Synchronize NetworkVariables
WriteNetworkVariableData(writer, targetClientId);
// Reserve the NetworkBehaviour synchronization count position
var networkBehaviourCountPosition = writer.Position;
writer.WriteValueSafe((byte)0);
// Parse through all NetworkBehaviours and any that return true
// had additional synchronization data written.
// (See notes for reading/deserialization below)
var synchronizationCount = (byte)0;
foreach (var childBehaviour in ChildNetworkBehaviours)
{
if (childBehaviour.Synchronize(ref serializer))
{
synchronizationCount++;
}
}
var currentPosition = writer.Position;
// Write the total number of bytes written for NetworkVariable and NetworkBehaviour
// synchronization.
writer.Seek(positionBeforeSynchronizing);
// We want the size of everything after our size to skip calculation position
var size = (ushort)(currentPosition - sizeToSkipCalculationPosition);
writer.WriteValueSafe(size);
// Write the number of NetworkBehaviours synchronized
writer.Seek(networkBehaviourCountPosition);
writer.WriteValueSafe(synchronizationCount);
// seek back to the position after writing NetworkVariable and NetworkBehaviour
// synchronization data.
writer.Seek(currentPosition);
}
else
{
var reader = serializer.GetFastBufferReader();
reader.ReadValueSafe(out ushort sizeOfSynchronizationData);
var seekToEndOfSynchData = reader.Position + sizeOfSynchronizationData;
// Apply the network variable synchronization data
SetNetworkVariableData(reader, targetClientId);
// Read the number of NetworkBehaviours to synchronize
reader.ReadValueSafe(out byte numberSynchronized);
var networkBehaviourId = (ushort)0;
// If a NetworkBehaviour writes synchronization data, it will first
// write its NetworkBehaviourId so when deserializing the client-side
// can find the right NetworkBehaviour to deserialize the synchronization data.
for (int i = 0; i < numberSynchronized; i++)
{
serializer.SerializeValue(ref networkBehaviourId);
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
networkBehaviour.Synchronize(ref serializer);
}
}
}
internal SceneObject GetMessageSceneObject(ulong targetClientId)
{
var obj = new SceneObject
{
Header = new SceneObject.HeaderData
{
IsPlayerObject = IsPlayerObject,
NetworkObjectId = NetworkObjectId,
OwnerClientId = OwnerClientId,
IsSceneObject = IsSceneObject ?? true,
Hash = HostCheckForGlobalObjectIdHashOverride(),
},
NetworkObjectId = NetworkObjectId,
OwnerClientId = OwnerClientId,
IsPlayerObject = IsPlayerObject,
IsSceneObject = IsSceneObject ?? true,
Hash = HostCheckForGlobalObjectIdHashOverride(),
OwnerObject = this,
TargetClientId = targetClientId
};
@@ -1235,11 +1326,18 @@ namespace Unity.Netcode
if (!AlwaysReplicateAsRoot && transform.parent != null)
{
parentNetworkObject = transform.parent.GetComponent<NetworkObject>();
// In-scene placed NetworkObjects parented under GameObjects with no NetworkObject
// should set the has parent flag and preserve the world position stays value
if (parentNetworkObject == null && obj.IsSceneObject)
{
obj.HasParent = true;
obj.WorldPositionStays = m_CachedWorldPositionStays;
}
}
if (parentNetworkObject != null)
{
obj.Header.HasParent = true;
obj.HasParent = true;
obj.ParentObjectId = parentNetworkObject.NetworkObjectId;
obj.WorldPositionStays = m_CachedWorldPositionStays;
var latestParent = GetNetworkParenting();
@@ -1253,20 +1351,38 @@ namespace Unity.Netcode
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
{
obj.Header.HasTransform = true;
obj.HasTransform = true;
// We start with the default AutoObjectParentSync values to determine which transform space we will
// be synchronizing clients with.
var syncRotationPositionLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
var syncScaleLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
// If auto object synchronization is turned off
if (!AutoObjectParentSync)
{
// We always synchronize position and rotation world space relative
syncRotationPositionLocalSpaceRelative = false;
// Scale is special, it synchronizes local space relative if it has a
// parent since applying the world space scale under a parent with scale
// will result in the improper scale for the child
syncScaleLocalSpaceRelative = obj.HasParent;
}
obj.Transform = new SceneObject.TransformData
{
// If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space
// values as opposed world space values.
Position = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localPosition : transform.position,
Rotation = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localRotation : transform.rotation,
Position = syncRotationPositionLocalSpaceRelative ? transform.localPosition : transform.position,
Rotation = syncRotationPositionLocalSpaceRelative ? transform.localRotation : transform.rotation,
// We only use the lossyScale if the NetworkObject has a parent. Multi-generation nested children scales can
// impact the final scale of the child NetworkObject in question. The solution is to use the lossy scale
// which can be thought of as "world space scale".
// More information:
// https://docs.unity3d.com/ScriptReference/Transform-lossyScale.html
Scale = parentNetworkObject && !m_CachedWorldPositionStays ? transform.localScale : transform.lossyScale,
Scale = syncScaleLocalSpaceRelative ? transform.localScale : transform.lossyScale,
};
}
@@ -1278,10 +1394,10 @@ namespace Unity.Netcode
/// when the client is approved or during a scene transition
/// </summary>
/// <param name="sceneObject">Deserialized scene object data</param>
/// <param name="variableData">reader for the NetworkVariable data</param>
/// <param name="reader">FastBufferReader for the NetworkVariable data</param>
/// <param name="networkManager">NetworkManager instance</param>
/// <returns>optional to use NetworkObject deserialized</returns>
internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader variableData, NetworkManager networkManager)
internal static NetworkObject AddSceneObject(in SceneObject sceneObject, FastBufferReader reader, NetworkManager networkManager)
{
//Attempt to create a local NetworkObject
var networkObject = networkManager.SpawnManager.CreateLocalNetworkObject(sceneObject);
@@ -1289,18 +1405,36 @@ namespace Unity.Netcode
if (networkObject == null)
{
// Log the error that the NetworkObject failed to construct
Debug.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Header.Hash}.");
if (networkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"Failed to spawn {nameof(NetworkObject)} for Hash {sceneObject.Hash}.");
}
// If we failed to load this NetworkObject, then skip past the network variable data
variableData.ReadValueSafe(out ushort varSize);
variableData.Seek(variableData.Position + varSize);
try
{
// If we failed to load this NetworkObject, then skip past the Network Variable and (if any) synchronization data
reader.ReadValueSafe(out ushort networkBehaviourSynchronizationDataLength);
reader.Seek(reader.Position + networkBehaviourSynchronizationDataLength);
}
catch (Exception ex)
{
Debug.LogException(ex);
}
// We have nothing left to do here.
return null;
}
// This will get set again when the NetworkObject is spawned locally, but we set it here ahead of spawning
// in order to be able to determine which NetworkVariables the client will be allowed to read.
networkObject.OwnerClientId = sceneObject.OwnerClientId;
// Synchronize NetworkBehaviours
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
// Spawn the NetworkObject
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, variableData, false);
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, false);
return networkObject;
}

248
Runtime/Hashing/XXHash.cs Normal file
View File

@@ -0,0 +1,248 @@
using System;
using System.Text;
using System.Runtime.CompilerServices;
namespace Unity.Netcode
{
internal static class XXHash
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe uint Hash32(byte* input, int length, uint seed = 0)
{
unchecked
{
const uint prime1 = 2654435761u;
const uint prime2 = 2246822519u;
const uint prime3 = 3266489917u;
const uint prime4 = 0668265263u;
const uint prime5 = 0374761393u;
uint hash = seed + prime5;
if (length >= 16)
{
uint val0 = seed + prime1 + prime2;
uint val1 = seed + prime2;
uint val2 = seed + 0;
uint val3 = seed - prime1;
int count = length >> 4;
for (int i = 0; i < count; i++)
{
var pos0 = *(uint*)(input + 0);
var pos1 = *(uint*)(input + 4);
var pos2 = *(uint*)(input + 8);
var pos3 = *(uint*)(input + 12);
val0 += pos0 * prime2;
val0 = (val0 << 13) | (val0 >> (32 - 13));
val0 *= prime1;
val1 += pos1 * prime2;
val1 = (val1 << 13) | (val1 >> (32 - 13));
val1 *= prime1;
val2 += pos2 * prime2;
val2 = (val2 << 13) | (val2 >> (32 - 13));
val2 *= prime1;
val3 += pos3 * prime2;
val3 = (val3 << 13) | (val3 >> (32 - 13));
val3 *= prime1;
input += 16;
}
hash = ((val0 << 01) | (val0 >> (32 - 01))) +
((val1 << 07) | (val1 >> (32 - 07))) +
((val2 << 12) | (val2 >> (32 - 12))) +
((val3 << 18) | (val3 >> (32 - 18)));
}
hash += (uint)length;
length &= 15;
while (length >= 4)
{
hash += *(uint*)input * prime3;
hash = ((hash << 17) | (hash >> (32 - 17))) * prime4;
input += 4;
length -= 4;
}
while (length > 0)
{
hash += *input * prime5;
hash = ((hash << 11) | (hash >> (32 - 11))) * prime1;
++input;
--length;
}
hash ^= hash >> 15;
hash *= prime2;
hash ^= hash >> 13;
hash *= prime3;
hash ^= hash >> 16;
return hash;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ulong Hash64(byte* input, int length, uint seed = 0)
{
unchecked
{
const ulong prime1 = 11400714785074694791ul;
const ulong prime2 = 14029467366897019727ul;
const ulong prime3 = 01609587929392839161ul;
const ulong prime4 = 09650029242287828579ul;
const ulong prime5 = 02870177450012600261ul;
ulong hash = seed + prime5;
if (length >= 32)
{
ulong val0 = seed + prime1 + prime2;
ulong val1 = seed + prime2;
ulong val2 = seed + 0;
ulong val3 = seed - prime1;
int count = length >> 5;
for (int i = 0; i < count; i++)
{
var pos0 = *(ulong*)(input + 0);
var pos1 = *(ulong*)(input + 8);
var pos2 = *(ulong*)(input + 16);
var pos3 = *(ulong*)(input + 24);
val0 += pos0 * prime2;
val0 = (val0 << 31) | (val0 >> (64 - 31));
val0 *= prime1;
val1 += pos1 * prime2;
val1 = (val1 << 31) | (val1 >> (64 - 31));
val1 *= prime1;
val2 += pos2 * prime2;
val2 = (val2 << 31) | (val2 >> (64 - 31));
val2 *= prime1;
val3 += pos3 * prime2;
val3 = (val3 << 31) | (val3 >> (64 - 31));
val3 *= prime1;
input += 32;
}
hash = ((val0 << 01) | (val0 >> (64 - 01))) +
((val1 << 07) | (val1 >> (64 - 07))) +
((val2 << 12) | (val2 >> (64 - 12))) +
((val3 << 18) | (val3 >> (64 - 18)));
val0 *= prime2;
val0 = (val0 << 31) | (val0 >> (64 - 31));
val0 *= prime1;
hash ^= val0;
hash = hash * prime1 + prime4;
val1 *= prime2;
val1 = (val1 << 31) | (val1 >> (64 - 31));
val1 *= prime1;
hash ^= val1;
hash = hash * prime1 + prime4;
val2 *= prime2;
val2 = (val2 << 31) | (val2 >> (64 - 31));
val2 *= prime1;
hash ^= val2;
hash = hash * prime1 + prime4;
val3 *= prime2;
val3 = (val3 << 31) | (val3 >> (64 - 31));
val3 *= prime1;
hash ^= val3;
hash = hash * prime1 + prime4;
}
hash += (ulong)length;
length &= 31;
while (length >= 8)
{
ulong lane = *(ulong*)input * prime2;
lane = ((lane << 31) | (lane >> (64 - 31))) * prime1;
hash ^= lane;
hash = ((hash << 27) | (hash >> (64 - 27))) * prime1 + prime4;
input += 8;
length -= 8;
}
if (length >= 4)
{
hash ^= *(uint*)input * prime1;
hash = ((hash << 23) | (hash >> (64 - 23))) * prime2 + prime3;
input += 4;
length -= 4;
}
while (length > 0)
{
hash ^= *input * prime5;
hash = ((hash << 11) | (hash >> (64 - 11))) * prime1;
++input;
--length;
}
hash ^= hash >> 33;
hash *= prime2;
hash ^= hash >> 29;
hash *= prime3;
hash ^= hash >> 32;
return hash;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Hash32(this byte[] buffer)
{
int length = buffer.Length;
unsafe
{
fixed (byte* pointer = buffer)
{
return Hash32(pointer, length);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Hash32(this string text) => Hash32(Encoding.UTF8.GetBytes(text));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Hash32(this Type type) => Hash32(type.FullName);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Hash32<T>() => Hash32(typeof(T));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Hash64(this byte[] buffer)
{
int length = buffer.Length;
unsafe
{
fixed (byte* pointer = buffer)
{
return Hash64(pointer, length);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Hash64(this string text) => Hash64(Encoding.UTF8.GetBytes(text));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Hash64(this Type type) => Hash64(type.FullName);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Hash64<T>() => Hash64(typeof(T));
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b5aa7a49e9e694f148d810d34577546b
guid: c3077af091aa443acbdea9d3e97727b0
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 2c61e8fe9a68a486fbbc3128d233ded2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015, 2016 Sedat Kapanoglu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: cf89ecbf6f9954c8ea6d0848b1e79d87
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,318 +0,0 @@
// <copyright file="XXHash.cs" company="Sedat Kapanoglu">
// Copyright (c) 2015-2019 Sedat Kapanoglu
// MIT License (see LICENSE file for details)
// </copyright>
// @mfatihmar (Unity): Modified for Unity support
using System.Text;
using System.Runtime.CompilerServices;
namespace Unity.Netcode
{
/// <summary>
/// XXHash implementation.
/// </summary>
internal static class XXHash
{
private const ulong k_Prime64v1 = 11400714785074694791ul;
private const ulong k_Prime64v2 = 14029467366897019727ul;
private const ulong k_Prime64v3 = 1609587929392839161ul;
private const ulong k_Prime64v4 = 9650029242287828579ul;
private const ulong k_Prime64v5 = 2870177450012600261ul;
private const uint k_Prime32v1 = 2654435761u;
private const uint k_Prime32v2 = 2246822519u;
private const uint k_Prime32v3 = 3266489917u;
private const uint k_Prime32v4 = 668265263u;
private const uint k_Prime32v5 = 374761393u;
public static uint Hash32(string text) => Hash32(text, Encoding.UTF8);
public static uint Hash32(string text, Encoding encoding) => Hash32(encoding.GetBytes(text));
public static uint Hash32(byte[] buffer)
{
unsafe
{
fixed (byte* ptr = buffer)
{
return Hash32(ptr, buffer.Length);
}
}
}
/// <summary>
/// Generate a 32-bit xxHash value.
/// </summary>
/// <param name="buffer">Input buffer.</param>
/// <param name="bufferLength">Input buffer length.</param>
/// <param name="seed">Optional seed.</param>
/// <returns>32-bit hash value.</returns>
public static unsafe uint Hash32(byte* buffer, int bufferLength, uint seed = 0)
{
const int stripeLength = 16;
int len = bufferLength;
int remainingLen = len;
uint acc;
byte* pInput = buffer;
if (len >= stripeLength)
{
uint acc1 = seed + k_Prime32v1 + k_Prime32v2;
uint acc2 = seed + k_Prime32v2;
uint acc3 = seed;
uint acc4 = seed - k_Prime32v1;
do
{
acc = processStripe32(ref pInput, ref acc1, ref acc2, ref acc3, ref acc4);
remainingLen -= stripeLength;
} while (remainingLen >= stripeLength);
}
else
{
acc = seed + k_Prime32v5;
}
acc += (uint)len;
acc = processRemaining32(pInput, acc, remainingLen);
return avalanche32(acc);
}
public static ulong Hash64(string text) => Hash64(text, Encoding.UTF8);
public static ulong Hash64(string text, Encoding encoding) => Hash64(encoding.GetBytes(text));
public static ulong Hash64(byte[] buffer)
{
unsafe
{
fixed (byte* ptr = buffer)
{
return Hash64(ptr, buffer.Length);
}
}
}
/// <summary>
/// Generate a 64-bit xxHash value.
/// </summary>
/// <param name="buffer">Input buffer.</param>
/// <param name="bufferLength">Input buffer length.</param>
/// <param name="seed">Optional seed.</param>
/// <returns>Computed 64-bit hash value.</returns>
public static unsafe ulong Hash64(byte* buffer, int bufferLength, ulong seed = 0)
{
const int stripeLength = 32;
int len = bufferLength;
int remainingLen = len;
ulong acc;
byte* pInput = buffer;
if (len >= stripeLength)
{
ulong acc1 = seed + k_Prime64v1 + k_Prime64v2;
ulong acc2 = seed + k_Prime64v2;
ulong acc3 = seed;
ulong acc4 = seed - k_Prime64v1;
do
{
acc = processStripe64(ref pInput, ref acc1, ref acc2, ref acc3, ref acc4);
remainingLen -= stripeLength;
} while (remainingLen >= stripeLength);
}
else
{
acc = seed + k_Prime64v5;
}
acc += (ulong)len;
acc = processRemaining64(pInput, acc, remainingLen);
return avalanche64(acc);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe ulong processStripe64(
ref byte* pInput,
ref ulong acc1,
ref ulong acc2,
ref ulong acc3,
ref ulong acc4)
{
processLane64(ref acc1, ref pInput);
processLane64(ref acc2, ref pInput);
processLane64(ref acc3, ref pInput);
processLane64(ref acc4, ref pInput);
ulong acc = Bits.RotateLeft(acc1, 1)
+ Bits.RotateLeft(acc2, 7)
+ Bits.RotateLeft(acc3, 12)
+ Bits.RotateLeft(acc4, 18);
mergeAccumulator64(ref acc, acc1);
mergeAccumulator64(ref acc, acc2);
mergeAccumulator64(ref acc, acc3);
mergeAccumulator64(ref acc, acc4);
return acc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void processLane64(ref ulong accn, ref byte* pInput)
{
ulong lane = *(ulong*)pInput;
accn = round64(accn, lane);
pInput += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe ulong processRemaining64(
byte* pInput,
ulong acc,
int remainingLen)
{
for (ulong lane; remainingLen >= 8; remainingLen -= 8, pInput += 8)
{
lane = *(ulong*)pInput;
acc ^= round64(0, lane);
acc = Bits.RotateLeft(acc, 27) * k_Prime64v1;
acc += k_Prime64v4;
}
for (uint lane32; remainingLen >= 4; remainingLen -= 4, pInput += 4)
{
lane32 = *(uint*)pInput;
acc ^= lane32 * k_Prime64v1;
acc = Bits.RotateLeft(acc, 23) * k_Prime64v2;
acc += k_Prime64v3;
}
for (byte lane8; remainingLen >= 1; remainingLen--, pInput++)
{
lane8 = *pInput;
acc ^= lane8 * k_Prime64v5;
acc = Bits.RotateLeft(acc, 11) * k_Prime64v1;
}
return acc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong avalanche64(ulong acc)
{
acc ^= acc >> 33;
acc *= k_Prime64v2;
acc ^= acc >> 29;
acc *= k_Prime64v3;
acc ^= acc >> 32;
return acc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong round64(ulong accn, ulong lane)
{
accn += lane * k_Prime64v2;
return Bits.RotateLeft(accn, 31) * k_Prime64v1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void mergeAccumulator64(ref ulong acc, ulong accn)
{
acc ^= round64(0, accn);
acc *= k_Prime64v1;
acc += k_Prime64v4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe uint processStripe32(
ref byte* pInput,
ref uint acc1,
ref uint acc2,
ref uint acc3,
ref uint acc4)
{
processLane32(ref pInput, ref acc1);
processLane32(ref pInput, ref acc2);
processLane32(ref pInput, ref acc3);
processLane32(ref pInput, ref acc4);
return Bits.RotateLeft(acc1, 1)
+ Bits.RotateLeft(acc2, 7)
+ Bits.RotateLeft(acc3, 12)
+ Bits.RotateLeft(acc4, 18);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void processLane32(ref byte* pInput, ref uint accn)
{
uint lane = *(uint*)pInput;
accn = round32(accn, lane);
pInput += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe uint processRemaining32(
byte* pInput,
uint acc,
int remainingLen)
{
for (uint lane; remainingLen >= 4; remainingLen -= 4, pInput += 4)
{
lane = *(uint*)pInput;
acc += lane * k_Prime32v3;
acc = Bits.RotateLeft(acc, 17) * k_Prime32v4;
}
for (byte lane; remainingLen >= 1; remainingLen--, pInput++)
{
lane = *pInput;
acc += lane * k_Prime32v5;
acc = Bits.RotateLeft(acc, 11) * k_Prime32v1;
}
return acc;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint round32(uint accn, uint lane)
{
accn += lane * k_Prime32v2;
accn = Bits.RotateLeft(accn, 13);
accn *= k_Prime32v1;
return accn;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint avalanche32(uint acc)
{
acc ^= acc >> 15;
acc *= k_Prime32v2;
acc ^= acc >> 13;
acc *= k_Prime32v3;
acc ^= acc >> 16;
return acc;
}
/// <summary>
/// Bit operations.
/// </summary>
private static class Bits
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ulong RotateLeft(ulong value, int bits)
{
return (value << bits) | (value >> (64 - bits));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static uint RotateLeft(uint value, int bits)
{
return (value << bits) | (value >> (32 - bits));
}
}
}
}

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>

View File

@@ -413,7 +413,7 @@ namespace Unity.Netcode
/// <inheritdoc />
public bool Contains(T item)
{
int index = NativeArrayExtensions.IndexOf(m_List, item);
int index = m_List.IndexOf(item);
return index != -1;
}
@@ -426,7 +426,7 @@ namespace Unity.Netcode
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
}
int index = NativeArrayExtensions.IndexOf(m_List, item);
int index = m_List.IndexOf(item);
if (index == -1)
{
return false;

View File

@@ -1,6 +1,8 @@
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode
{
@@ -20,6 +22,96 @@ namespace Unity.Netcode
public void Read(FastBufferReader reader, ref T value);
}
/// <summary>
/// Packing serializer for shorts
/// </summary>
internal class ShortSerializer : INetworkVariableSerializer<short>
{
public void Write(FastBufferWriter writer, ref short value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref short value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
}
/// <summary>
/// Packing serializer for shorts
/// </summary>
internal class UshortSerializer : INetworkVariableSerializer<ushort>
{
public void Write(FastBufferWriter writer, ref ushort value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref ushort value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
}
/// <summary>
/// Packing serializer for ints
/// </summary>
internal class IntSerializer : INetworkVariableSerializer<int>
{
public void Write(FastBufferWriter writer, ref int value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref int value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
}
/// <summary>
/// Packing serializer for ints
/// </summary>
internal class UintSerializer : INetworkVariableSerializer<uint>
{
public void Write(FastBufferWriter writer, ref uint value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref uint value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
}
/// <summary>
/// Packing serializer for longs
/// </summary>
internal class LongSerializer : INetworkVariableSerializer<long>
{
public void Write(FastBufferWriter writer, ref long value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref long value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
}
/// <summary>
/// Packing serializer for longs
/// </summary>
internal class UlongSerializer : INetworkVariableSerializer<ulong>
{
public void Write(FastBufferWriter writer, ref ulong value)
{
BytePacker.WriteValueBitPacked(writer, value);
}
public void Read(FastBufferReader reader, ref ulong value)
{
ByteUnpacker.ReadValueBitPacked(reader, out value);
}
}
/// <summary>
/// Basic serializer for unmanaged types.
/// This covers primitives, built-in unity types, and IForceSerializeByMemcpy
@@ -188,6 +280,26 @@ namespace Unity.Netcode
/// </summary>
public static class NetworkVariableSerializationTypes
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
#if UNITY_EDITOR
[InitializeOnLoadMethod]
#endif
internal static void InitializeIntegerSerialization()
{
NetworkVariableSerialization<short>.Serializer = new ShortSerializer();
NetworkVariableSerialization<short>.AreEqual = NetworkVariableSerialization<short>.ValueEquals;
NetworkVariableSerialization<ushort>.Serializer = new UshortSerializer();
NetworkVariableSerialization<ushort>.AreEqual = NetworkVariableSerialization<ushort>.ValueEquals;
NetworkVariableSerialization<int>.Serializer = new IntSerializer();
NetworkVariableSerialization<int>.AreEqual = NetworkVariableSerialization<int>.ValueEquals;
NetworkVariableSerialization<uint>.Serializer = new UintSerializer();
NetworkVariableSerialization<uint>.AreEqual = NetworkVariableSerialization<uint>.ValueEquals;
NetworkVariableSerialization<long>.Serializer = new LongSerializer();
NetworkVariableSerialization<long>.AreEqual = NetworkVariableSerialization<long>.ValueEquals;
NetworkVariableSerialization<ulong>.Serializer = new UlongSerializer();
NetworkVariableSerialization<ulong>.AreEqual = NetworkVariableSerialization<ulong>.ValueEquals;
}
/// <summary>
/// Registeres an unmanaged type that will be serialized by a direct memcpy into a buffer
/// </summary>

View File

@@ -2018,7 +2018,11 @@ namespace Unity.Netcode
ScenePlacedObjects.Clear();
}
#if UNITY_2023_1_OR_NEWER
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID);
#else
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
#endif
// Just add every NetworkObject found that isn't already in the list
// With additive scenes, we can have multiple in-scene placed NetworkObjects with the same GlobalObjectIdHash value

View File

@@ -269,7 +269,12 @@ namespace Unity.Netcode
{
m_DespawnedInSceneObjectsSync.Clear();
// Find all active and non-active in-scene placed NetworkObjects
#if UNITY_2023_1_OR_NEWER
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) => c.NetworkManager == m_NetworkManager);
#else
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) => c.NetworkManager == m_NetworkManager);
#endif
foreach (var sobj in inSceneNetworkObjects)
{
if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned)
@@ -380,7 +385,7 @@ namespace Unity.Netcode
writer.WriteValueSafe(SceneEventType);
// Write the scene loading mode
writer.WriteValueSafe(LoadSceneMode);
writer.WriteValueSafe((byte)LoadSceneMode);
// Write the scene event progress Guid
if (SceneEventType != SceneEventType.Synchronize)
@@ -444,13 +449,13 @@ namespace Unity.Netcode
int totalBytes = 0;
// Write the number of NetworkObjects we are serializing
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync.Count);
writer.WriteValueSafe(m_NetworkObjectsSync.Count);
// Serialize all NetworkObjects that are spawned
for (var i = 0; i < m_NetworkObjectsSync.Count; ++i)
{
var noStart = writer.Position;
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
BytePacker.WriteValuePacked(writer, m_NetworkObjectsSync[i].GetSceneOriginHandle());
sceneObject.Serialize(writer);
var noStop = writer.Position;
totalBytes += (int)(noStop - noStart);
@@ -462,8 +467,8 @@ namespace Unity.Netcode
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
{
var noStart = writer.Position;
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
var noStop = writer.Position;
totalBytes += (int)(noStop - noStart);
}
@@ -497,8 +502,6 @@ namespace Unity.Netcode
{
if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId))
{
// Write our server relative scene handle for the NetworkObject being serialized
writer.WriteValueSafe(keyValuePairBySceneHandle.Key);
// Serialize the NetworkObject
var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId);
sceneObject.Serialize(writer);
@@ -512,8 +515,8 @@ namespace Unity.Netcode
// Write the scene handle and GlobalObjectIdHash value
for (var i = 0; i < m_DespawnedInSceneObjectsSync.Count; ++i)
{
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
BytePacker.WriteValuePacked(writer, m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
}
var tailPosition = writer.Position;
@@ -533,7 +536,8 @@ namespace Unity.Netcode
internal void Deserialize(FastBufferReader reader)
{
reader.ReadValueSafe(out SceneEventType);
reader.ReadValueSafe(out LoadSceneMode);
reader.ReadValueSafe(out byte loadSceneMode);
LoadSceneMode = (LoadSceneMode)loadSceneMode;
if (SceneEventType != SceneEventType.Synchronize)
{
@@ -624,13 +628,15 @@ namespace Unity.Netcode
for (ushort i = 0; i < newObjectsCount; i++)
{
InternalBuffer.ReadValueSafe(out int sceneHandle);
// Set our relative scene to the NetworkObject
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneHandle);
// Deserialize the NetworkObject
var sceneObject = new NetworkObject.SceneObject();
sceneObject.Deserialize(InternalBuffer);
if (sceneObject.IsSceneObject)
{
// Set our relative scene to the NetworkObject
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle);
}
NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
}
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
@@ -656,7 +662,11 @@ namespace Unity.Netcode
if (networkObjectsToRemove.Length > 0)
{
#if UNITY_2023_1_OR_NEWER
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(UnityEngine.FindObjectsSortMode.InstanceID);
#else
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
#endif
var networkObjectIdToNetworkObject = new Dictionary<ulong, NetworkObject>();
foreach (var networkObject in networkObjects)
{
@@ -771,8 +781,8 @@ namespace Unity.Netcode
for (int i = 0; i < despawnedObjectsCount; i++)
{
// We just need to get the scene
ByteUnpacker.ReadValuePacked(InternalBuffer, out int networkSceneHandle);
ByteUnpacker.ReadValuePacked(InternalBuffer, out uint globalObjectIdHash);
InternalBuffer.ReadValueSafe(out int networkSceneHandle);
InternalBuffer.ReadValueSafe(out uint globalObjectIdHash);
var sceneRelativeNetworkObjects = new Dictionary<uint, NetworkObject>();
if (!sceneCache.ContainsKey(networkSceneHandle))
{
@@ -784,8 +794,14 @@ namespace Unity.Netcode
var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle];
// Find all active and non-active in-scene placed NetworkObjects
#if UNITY_2023_1_OR_NEWER
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.InstanceID).Where((c) =>
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
#else
var inSceneNetworkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>(includeInactive: true).Where((c) =>
c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList();
#endif
foreach (var inSceneObject in inSceneNetworkObjects)
{
@@ -847,24 +863,26 @@ namespace Unity.Netcode
try
{
// Process all spawned NetworkObjects for this network session
ByteUnpacker.ReadValuePacked(InternalBuffer, out int newObjectsCount);
InternalBuffer.ReadValueSafe(out int newObjectsCount);
for (int i = 0; i < newObjectsCount; i++)
{
// We want to make sure for each NetworkObject we have the appropriate scene selected as the scene that is
// currently being synchronized. This assures in-scene placed NetworkObjects will use the right NetworkObject
// from the list of populated <see cref="NetworkSceneManager.ScenePlacedObjects"/>
ByteUnpacker.ReadValuePacked(InternalBuffer, out int handle);
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(handle);
var sceneObject = new NetworkObject.SceneObject();
sceneObject.Deserialize(InternalBuffer);
var spawnedNetworkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, networkManager);
if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject))
// If the sceneObject is in-scene placed, then set the scene being synchronized
if (sceneObject.IsSceneObject)
{
m_NetworkObjectsSync.Add(spawnedNetworkObject);
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle);
}
var spawnedNetworkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, networkManager);
// If we failed to deserialize the NetowrkObject then don't add null to the list
if (spawnedNetworkObject != null)
{
if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject))
{
m_NetworkObjectsSync.Add(spawnedNetworkObject);
}
}
}

View File

@@ -108,19 +108,37 @@ namespace Unity.Netcode
internal List<ulong> GetClientsWithStatus(bool completedSceneEvent)
{
var clients = new List<ulong>();
foreach (var clientStatus in ClientsProcessingSceneEvent)
if (completedSceneEvent)
{
if (clientStatus.Value == completedSceneEvent)
// If we are the host, then add the host-client to the list
// of clients that completed if the AsyncOperation is done.
if (m_NetworkManager.IsHost && m_AsyncOperation.isDone)
{
clients.Add(clientStatus.Key);
clients.Add(m_NetworkManager.LocalClientId);
}
// Add all clients that completed the scene event
foreach (var clientStatus in ClientsProcessingSceneEvent)
{
if (clientStatus.Value == completedSceneEvent)
{
clients.Add(clientStatus.Key);
}
}
}
// If we are getting the list of clients that have not completed the
// scene event, then add any clients that disconnected during this
// scene event.
if (!completedSceneEvent)
else
{
// If we are the host, then add the host-client to the list
// of clients that did not complete if the AsyncOperation is
// not done.
if (m_NetworkManager.IsHost && !m_AsyncOperation.isDone)
{
clients.Add(m_NetworkManager.LocalClientId);
}
// If we are getting the list of clients that have not completed the
// scene event, then add any clients that disconnected during this
// scene event.
clients.AddRange(ClientsThatDisconnected);
}
return clients;
@@ -138,6 +156,11 @@ namespace Unity.Netcode
// Track the clients that were connected when we started this event
foreach (var connectedClientId in networkManager.ConnectedClientsIds)
{
// Ignore the host client
if (NetworkManager.ServerClientId == connectedClientId)
{
continue;
}
ClientsProcessingSceneEvent.Add(connectedClientId, false);
}
@@ -218,7 +241,10 @@ namespace Unity.Netcode
}
// Return the local scene event's AsyncOperation status
return m_AsyncOperation.isDone;
// Note: Integration tests process scene loading through a queue
// and the AsyncOperation could not be assigned for several
// network tick periods. Return false if that is the case.
return m_AsyncOperation == null ? false : m_AsyncOperation.isDone;
}
/// <summary>

View File

@@ -50,7 +50,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, float value)
{
WriteUInt32Packed(writer, ToUint(value));
WriteValueBitPacked(writer, ToUint(value));
}
/// <summary>
@@ -61,7 +61,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, double value)
{
WriteUInt64Packed(writer, ToUlong(value));
WriteValueBitPacked(writer, ToUlong(value));
}
/// <summary>
@@ -98,7 +98,7 @@ namespace Unity.Netcode
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, short value) => WriteUInt32Packed(writer, (ushort)Arithmetic.ZigZagEncode(value));
public static void WriteValuePacked(FastBufferWriter writer, short value) => WriteValueBitPacked(writer, value);
/// <summary>
/// Write an unsigned short (UInt16) as a varint to the buffer.
@@ -109,7 +109,7 @@ namespace Unity.Netcode
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, ushort value) => WriteUInt32Packed(writer, value);
public static void WriteValuePacked(FastBufferWriter writer, ushort value) => WriteValueBitPacked(writer, value);
/// <summary>
/// Write a two-byte character as a varint to the buffer.
@@ -120,7 +120,7 @@ namespace Unity.Netcode
/// <param name="writer">The writer to write to</param>
/// <param name="c">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, char c) => WriteUInt32Packed(writer, c);
public static void WriteValuePacked(FastBufferWriter writer, char c) => WriteValueBitPacked(writer, c);
/// <summary>
/// Write a signed int (Int32) as a ZigZag encoded varint to the buffer.
@@ -128,7 +128,7 @@ namespace Unity.Netcode
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, int value) => WriteUInt32Packed(writer, (uint)Arithmetic.ZigZagEncode(value));
public static void WriteValuePacked(FastBufferWriter writer, int value) => WriteValueBitPacked(writer, value);
/// <summary>
/// Write an unsigned int (UInt32) to the buffer.
@@ -136,7 +136,7 @@ namespace Unity.Netcode
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, uint value) => WriteUInt32Packed(writer, value);
public static void WriteValuePacked(FastBufferWriter writer, uint value) => WriteValueBitPacked(writer, value);
/// <summary>
/// Write an unsigned long (UInt64) to the buffer.
@@ -144,7 +144,7 @@ namespace Unity.Netcode
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, ulong value) => WriteUInt64Packed(writer, value);
public static void WriteValuePacked(FastBufferWriter writer, ulong value) => WriteValueBitPacked(writer, value);
/// <summary>
/// Write a signed long (Int64) as a ZigZag encoded varint to the buffer.
@@ -152,7 +152,7 @@ namespace Unity.Netcode
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, long value) => WriteUInt64Packed(writer, Arithmetic.ZigZagEncode(value));
public static void WriteValuePacked(FastBufferWriter writer, long value) => WriteValueBitPacked(writer, value);
/// <summary>
/// Convenience method that writes two packed Vector3 from the ray to the buffer
@@ -282,231 +282,183 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueBitPacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
#else
/// <summary>
/// Maximum serializable value for a BitPacked ushort (minimum for unsigned is 0)
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const ushort BitPackedUshortMax = (1 << 15) - 1;
/// <summary>
/// Maximum serializable value for a BitPacked short
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const short BitPackedShortMax = (1 << 14) - 1;
/// <summary>
/// Minimum serializable value size for a BitPacked ushort
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const short BitPackedShortMin = -(1 << 14);
/// <summary>
/// Maximum serializable value for a BitPacked uint (minimum for unsigned is 0)
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const uint BitPackedUintMax = (1 << 30) - 1;
/// <summary>
/// Maximum serializable value for a BitPacked int
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const int BitPackedIntMax = (1 << 29) - 1;
/// <summary>
/// Minimum serializable value size for a BitPacked int
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const int BitPackedIntMin = -(1 << 29);
/// <summary>
/// Maximum serializable value for a BitPacked ulong (minimum for unsigned is 0)
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const ulong BitPackedULongMax = (1L << 61) - 1;
/// <summary>
/// Maximum serializable value for a BitPacked long
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const long BitPackedLongMax = (1L << 60) - 1;
/// <summary>
/// Minimum serializable value size for a BitPacked long
/// Obsolete value that no longer carries meaning. Do not use.
/// </summary>
public const long BitPackedLongMin = -(1L << 60);
/// <summary>
/// Writes a 14-bit signed short to the buffer in a bit-encoded packed format.
/// The first bit indicates whether the value is 1 byte or 2.
/// The sign bit takes up another bit.
/// That leaves 14 bits for the value.
/// A value greater than 2^14-1 or less than -2^14 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its two
/// most significant bits after zig-zag encoding.
/// Writes a 16-bit signed short to the buffer in a bit-encoded packed format.
/// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values
/// are still able to be compressed.
/// The first two bits indicate whether the value is 1, 2, or 3 bytes.
/// If the value uses 14 bits or less, the remaining 14 bits contain the value.
/// For performance, reasons, if the value is 15 bits or more, there will be six 0 bits, followed
/// by the original unmodified 16-bit value in the next 2 bytes.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, short value) => WriteValueBitPacked(writer, (ushort)Arithmetic.ZigZagEncode(value));
/// <summary>
/// Writes a 15-bit unsigned short to the buffer in a bit-encoded packed format.
/// The first bit indicates whether the value is 1 byte or 2.
/// That leaves 15 bits for the value.
/// A value greater than 2^15-1 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its
/// most significant bit.
/// Writes a 16-bit unsigned short to the buffer in a bit-encoded packed format.
/// The first two bits indicate whether the value is 1, 2, or 3 bytes.
/// If the value uses 14 bits or less, the remaining 14 bits contain the value.
/// For performance, reasons, if the value is 15 bits or more, there will be six 0 bits, followed
/// by the original unmodified 16-bit value in the next 2 bytes.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, ushort value)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (value >= BitPackedUshortMax)
if (value > (1 << 14) - 1)
{
throw new ArgumentException("BitPacked ushorts must be <= 15 bits");
}
#endif
if (value <= 0b0111_1111)
{
if (!writer.TryBeginWriteInternal(1))
if (!writer.TryBeginWriteInternal(3))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WriteByte((byte)(value << 1));
writer.WriteByte(3);
writer.WriteValue(value);
return;
}
if (!writer.TryBeginWriteInternal(2))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WriteValue((ushort)((value << 1) | 0b1));
}
/// <summary>
/// Writes a 29-bit signed int to the buffer in a bit-encoded packed format.
/// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes.
/// The sign bit takes up another bit.
/// That leaves 29 bits for the value.
/// A value greater than 2^29-1 or less than -2^29 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its three
/// most significant bits after zig-zag encoding.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, int value) => WriteValueBitPacked(writer, (uint)Arithmetic.ZigZagEncode(value));
/// <summary>
/// Writes a 30-bit unsigned int to the buffer in a bit-encoded packed format.
/// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes.
/// That leaves 30 bits for the value.
/// A value greater than 2^30-1 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its two
/// most significant bits.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, uint value)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (value > BitPackedUintMax)
{
throw new ArgumentException("BitPacked uints must be <= 30 bits");
}
#endif
value <<= 2;
var numBytes = BitCounter.GetUsedByteCount(value);
if (!writer.TryBeginWriteInternal(numBytes))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes);
writer.WritePartialValue(value | (ushort)(numBytes), numBytes);
}
/// <summary>
/// Writes a 60-bit signed long to the buffer in a bit-encoded packed format.
/// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes.
/// The sign bit takes up another bit.
/// That leaves 60 bits for the value.
/// A value greater than 2^60-1 or less than -2^60 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its four
/// most significant bits after zig-zag encoding.
/// Writes a 32-bit signed int to the buffer in a bit-encoded packed format.
/// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values
/// are still able to be compressed.
/// The first three bits indicate whether the value is 1, 2, 3, 4, or 5 bytes.
/// If the value uses 29 bits or less, the remaining 29 bits contain the value.
/// For performance, reasons, if the value is 30 bits or more, there will be five 0 bits, followed
/// by the original unmodified 32-bit value in the next 4 bytes.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, long value) => WriteValueBitPacked(writer, Arithmetic.ZigZagEncode(value));
public static void WriteValueBitPacked(FastBufferWriter writer, int value) => WriteValueBitPacked(writer, (uint)Arithmetic.ZigZagEncode(value));
/// <summary>
/// Writes a 61-bit unsigned long to the buffer in a bit-encoded packed format.
/// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes.
/// That leaves 31 bits for the value.
/// A value greater than 2^61-1 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its three
/// most significant bits.
/// Writes a 32-bit unsigned int to the buffer in a bit-encoded packed format.
/// The first three bits indicate whether the value is 1, 2, 3, 4, or 5 bytes.
/// If the value uses 29 bits or less, the remaining 29 bits contain the value.
/// For performance, reasons, if the value is 30 bits or more, there will be five 0 bits, followed
/// by the original unmodified 32-bit value in the next 4 bytes.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, ulong value)
public static void WriteValueBitPacked(FastBufferWriter writer, uint value)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (value > BitPackedULongMax)
if (value > (1 << 29) - 1)
{
throw new ArgumentException("BitPacked ulongs must be <= 61 bits");
if (!writer.TryBeginWriteInternal(5))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WriteByte(5);
writer.WriteValue(value);
return;
}
#endif
value <<= 3;
var numBytes = BitCounter.GetUsedByteCount(value);
if (!writer.TryBeginWriteInternal(numBytes))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes);
writer.WritePartialValue(value | (uint)(numBytes), numBytes);
}
/// <summary>
/// Writes a 64-bit signed long to the buffer in a bit-encoded packed format.
/// Zig-zag encoding is used to move the sign bit to the least significant bit, so that negative values
/// are still able to be compressed.
/// The first four bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, 8, or 9 bytes.
/// If the value uses 60 bits or less, the remaining 60 bits contain the value.
/// For performance, reasons, if the value is 61 bits or more, there will be four 0 bits, followed
/// by the original unmodified 64-bit value in the next 8 bytes.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, long value) => WriteValueBitPacked(writer, Arithmetic.ZigZagEncode(value));
/// <summary>
/// Writes a 64-bit unsigned long to the buffer in a bit-encoded packed format.
/// The first four bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, 8, or 9 bytes.
/// If the value uses 60 bits or less, the remaining 60 bits contain the value.
/// For performance, reasons, if the value is 61 bits or more, there will be four 0 bits, followed
/// by the original unmodified 64-bit value in the next 8 bytes.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, ulong value)
{
if (value > (1L << 60) - 1)
{
if (!writer.TryBeginWriteInternal(9))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WriteByte(9);
writer.WriteValue(value);
return;
}
value <<= 4;
var numBytes = BitCounter.GetUsedByteCount(value);
if (!writer.TryBeginWriteInternal(numBytes))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WritePartialValue(value | (uint)(numBytes), numBytes);
}
#endif
private static void WriteUInt64Packed(FastBufferWriter writer, ulong value)
{
if (value <= 240)
{
writer.WriteByteSafe((byte)value);
return;
}
if (value <= 2287)
{
writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241));
writer.WriteByteSafe((byte)(value - 240));
return;
}
var writeBytes = BitCounter.GetUsedByteCount(value);
if (!writer.TryBeginWriteInternal(writeBytes + 1))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WriteByte((byte)(247 + writeBytes));
writer.WritePartialValue(value, writeBytes);
}
// Looks like the same code as WriteUInt64Packed?
// It's actually different because it will call the more efficient 32-bit version
// of BytewiseUtility.GetUsedByteCount().
private static void WriteUInt32Packed(FastBufferWriter writer, uint value)
{
if (value <= 240)
{
writer.WriteByteSafe((byte)value);
return;
}
if (value <= 2287)
{
writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241));
writer.WriteByteSafe((byte)(value - 240));
return;
}
var writeBytes = BitCounter.GetUsedByteCount(value);
if (!writer.TryBeginWriteInternal(writeBytes + 1))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WriteByte((byte)(247 + writeBytes));
writer.WritePartialValue(value, writeBytes);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe uint ToUint<T>(T value) where T : unmanaged
{

View File

@@ -11,7 +11,6 @@ namespace Unity.Netcode
/// </summary>
public static class ByteUnpacker
{
#if UNITY_NETCODE_DEBUG_NO_PACKING
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -58,7 +57,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out float value)
{
ReadUInt32Packed(reader, out uint asUInt);
ReadValueBitPacked(reader, out uint asUInt);
value = ToSingle(asUInt);
}
@@ -70,7 +69,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out double value)
{
ReadUInt64Packed(reader, out ulong asULong);
ReadValueBitPacked(reader, out ulong asULong);
value = ToDouble(asULong);
}
@@ -109,11 +108,7 @@ namespace Unity.Netcode
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out short value)
{
ReadUInt32Packed(reader, out uint readValue);
value = (short)Arithmetic.ZigZagDecode(readValue);
}
public static void ReadValuePacked(FastBufferReader reader, out short value) => ReadValueBitPacked(reader, out value);
/// <summary>
/// Read an unsigned short (UInt16) as a varint from the stream.
@@ -121,11 +116,7 @@ namespace Unity.Netcode
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out ushort value)
{
ReadUInt32Packed(reader, out uint readValue);
value = (ushort)readValue;
}
public static void ReadValuePacked(FastBufferReader reader, out ushort value) => ReadValueBitPacked(reader, out value);
/// <summary>
/// Read a two-byte character as a varint from the stream.
@@ -135,7 +126,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out char c)
{
ReadUInt32Packed(reader, out uint readValue);
ReadValueBitPacked(reader, out ushort readValue);
c = (char)readValue;
}
@@ -145,11 +136,7 @@ namespace Unity.Netcode
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out int value)
{
ReadUInt32Packed(reader, out uint readValue);
value = (int)Arithmetic.ZigZagDecode(readValue);
}
public static void ReadValuePacked(FastBufferReader reader, out int value) => ReadValueBitPacked(reader, out value);
/// <summary>
/// Read an unsigned int (UInt32) from the stream.
@@ -157,7 +144,7 @@ namespace Unity.Netcode
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out uint value) => ReadUInt32Packed(reader, out value);
public static void ReadValuePacked(FastBufferReader reader, out uint value) => ReadValueBitPacked(reader, out value);
/// <summary>
/// Read an unsigned long (UInt64) from the stream.
@@ -165,7 +152,7 @@ namespace Unity.Netcode
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out ulong value) => ReadUInt64Packed(reader, out value);
public static void ReadValuePacked(FastBufferReader reader, out ulong value) => ReadValueBitPacked(reader, out value);
/// <summary>
/// Read a signed long (Int64) as a ZigZag encoded varint from the stream.
@@ -175,8 +162,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out long value)
{
ReadUInt64Packed(reader, out ulong readValue);
value = Arithmetic.ZigZagDecode(readValue);
ReadValueBitPacked(reader, out value);
}
/// <summary>
@@ -341,7 +327,9 @@ namespace Unity.Netcode
ushort returnValue = 0;
byte* ptr = ((byte*)&returnValue);
byte* data = reader.GetUnsafePtrAtCurrentPosition();
int numBytes = (data[0] & 0b1) + 1;
// Mask out the first two bits - they contain the total byte count
// (1, 2, or 3)
int numBytes = (data[0] & 0b11);
if (!reader.TryBeginReadInternal(numBytes))
{
throw new OverflowException("Reading past the end of the buffer");
@@ -350,17 +338,23 @@ namespace Unity.Netcode
switch (numBytes)
{
case 1:
*ptr = *data;
ptr[0] = data[0];
break;
case 2:
*ptr = *data;
*(ptr + 1) = *(data + 1);
ptr[0] = data[0];
ptr[1] = data[1];
break;
case 3:
// First byte contains no data, it's just a marker. The data is in the remaining two bytes.
ptr[0] = data[1];
ptr[1] = data[2];
value = returnValue;
return;
default:
throw new InvalidOperationException("Could not read bit-packed value: impossible byte count");
}
value = (ushort)(returnValue >> 1);
value = (ushort)(returnValue >> 2);
}
/// <summary>
@@ -386,7 +380,8 @@ namespace Unity.Netcode
uint returnValue = 0;
byte* ptr = ((byte*)&returnValue);
byte* data = reader.GetUnsafePtrAtCurrentPosition();
int numBytes = (data[0] & 0b11) + 1;
// Mask out the first three bits - they contain the total byte count (1-5)
int numBytes = (data[0] & 0b111);
if (!reader.TryBeginReadInternal(numBytes))
{
throw new OverflowException("Reading past the end of the buffer");
@@ -395,26 +390,34 @@ namespace Unity.Netcode
switch (numBytes)
{
case 1:
*ptr = *data;
ptr[0] = data[0];
break;
case 2:
*ptr = *data;
*(ptr + 1) = *(data + 1);
ptr[0] = data[0];
ptr[1] = data[1];
break;
case 3:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
ptr[0] = data[0];
ptr[1] = data[1];
ptr[2] = data[2];
break;
case 4:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
ptr[0] = data[0];
ptr[1] = data[1];
ptr[2] = data[2];
ptr[3] = data[3];
break;
case 5:
// First byte contains no data, it's just a marker. The data is in the remaining two bytes.
ptr[0] = data[1];
ptr[1] = data[2];
ptr[2] = data[3];
ptr[3] = data[4];
value = returnValue;
return;
}
value = returnValue >> 2;
value = returnValue >> 3;
}
/// <summary>
@@ -440,7 +443,8 @@ namespace Unity.Netcode
ulong returnValue = 0;
byte* ptr = ((byte*)&returnValue);
byte* data = reader.GetUnsafePtrAtCurrentPosition();
int numBytes = (data[0] & 0b111) + 1;
// Mask out the first four bits - they contain the total byte count (1-9)
int numBytes = (data[0] & 0b1111);
if (!reader.TryBeginReadInternal(numBytes))
{
throw new OverflowException("Reading past the end of the buffer");
@@ -449,109 +453,74 @@ namespace Unity.Netcode
switch (numBytes)
{
case 1:
*ptr = *data;
ptr[0] = data[0];
break;
case 2:
*ptr = *data;
*(ptr + 1) = *(data + 1);
ptr[0] = data[0];
ptr[1] = data[1];
break;
case 3:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
ptr[0] = data[0];
ptr[1] = data[1];
ptr[2] = data[2];
break;
case 4:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
ptr[0] = data[0];
ptr[1] = data[1];
ptr[2] = data[2];
ptr[3] = data[3];
break;
case 5:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
*(ptr + 4) = *(data + 4);
ptr[0] = data[0];
ptr[1] = data[1];
ptr[2] = data[2];
ptr[3] = data[3];
ptr[4] = data[4];
break;
case 6:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
*(ptr + 4) = *(data + 4);
*(ptr + 5) = *(data + 5);
ptr[0] = data[0];
ptr[1] = data[1];
ptr[2] = data[2];
ptr[3] = data[3];
ptr[4] = data[4];
ptr[5] = data[5];
break;
case 7:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
*(ptr + 4) = *(data + 4);
*(ptr + 5) = *(data + 5);
*(ptr + 6) = *(data + 6);
ptr[0] = data[0];
ptr[1] = data[1];
ptr[2] = data[2];
ptr[3] = data[3];
ptr[4] = data[4];
ptr[5] = data[5];
ptr[6] = data[6];
break;
case 8:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
*(ptr + 4) = *(data + 4);
*(ptr + 5) = *(data + 5);
*(ptr + 6) = *(data + 6);
*(ptr + 7) = *(data + 7);
ptr[0] = data[0];
ptr[1] = data[1];
ptr[2] = data[2];
ptr[3] = data[3];
ptr[4] = data[4];
ptr[5] = data[5];
ptr[6] = data[6];
ptr[7] = data[7];
break;
case 9:
// First byte contains no data, it's just a marker. The data is in the remaining two bytes.
ptr[0] = data[1];
ptr[1] = data[2];
ptr[2] = data[3];
ptr[3] = data[4];
ptr[4] = data[5];
ptr[5] = data[6];
ptr[6] = data[7];
ptr[7] = data[8];
value = returnValue;
return;
}
value = returnValue >> 3;
value = returnValue >> 4;
}
#endif
private static void ReadUInt64Packed(FastBufferReader reader, out ulong value)
{
reader.ReadByteSafe(out byte firstByte);
if (firstByte <= 240)
{
value = firstByte;
return;
}
if (firstByte <= 248)
{
reader.ReadByteSafe(out byte secondByte);
value = 240UL + ((firstByte - 241UL) << 8) + secondByte;
return;
}
var numBytes = firstByte - 247;
if (!reader.TryBeginReadInternal(numBytes))
{
throw new OverflowException("Reading past the end of the buffer");
}
reader.ReadPartialValue(out value, numBytes);
}
private static void ReadUInt32Packed(FastBufferReader reader, out uint value)
{
reader.ReadByteSafe(out byte firstByte);
if (firstByte <= 240)
{
value = firstByte;
return;
}
if (firstByte <= 248)
{
reader.ReadByteSafe(out byte secondByte);
value = 240U + ((firstByte - 241U) << 8) + secondByte;
return;
}
var numBytes = firstByte - 247;
if (!reader.TryBeginReadInternal(numBytes))
{
throw new OverflowException("Reading past the end of the buffer");
}
reader.ReadPartialValue(out value, numBytes);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe float ToSingle<T>(T value) where T : unmanaged

View File

@@ -0,0 +1,58 @@
using System.Runtime.CompilerServices;
namespace Unity.Netcode
{
internal class ByteUtility
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe byte ToByte(bool b) => *(byte*)&b;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool GetBit(byte bitField, ushort bitPosition)
{
return (bitField & (1 << bitPosition)) != 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void SetBit(ref byte bitField, ushort bitPosition, bool value)
{
bitField = (byte)((bitField & ~(1 << bitPosition)) | (ToByte(value) << bitPosition));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool GetBit(ushort bitField, ushort bitPosition)
{
return (bitField & (1 << bitPosition)) != 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void SetBit(ref ushort bitField, ushort bitPosition, bool value)
{
bitField = (ushort)((bitField & ~(1 << bitPosition)) | (ToByte(value) << bitPosition));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool GetBit(uint bitField, ushort bitPosition)
{
return (bitField & (1 << bitPosition)) != 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void SetBit(ref uint bitField, ushort bitPosition, bool value)
{
bitField = (uint)((bitField & ~(1 << bitPosition)) | ((uint)ToByte(value) << bitPosition));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool GetBit(ulong bitField, ushort bitPosition)
{
return (bitField & (ulong)(1 << bitPosition)) != 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void SetBit(ref ulong bitField, ushort bitPosition, bool value)
{
bitField = ((bitField & (ulong)~(1 << bitPosition)) | ((ulong)ToByte(value) << bitPosition));
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 25bb0dd7157c423b8cfe0ecf06e15ae5
timeCreated: 1666711082

View File

@@ -65,7 +65,7 @@ namespace Unity.Netcode
ReaderHandle* readerHandle = null;
if (copyAllocator == Allocator.None)
{
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), internalAllocator);
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle), UnsafeUtility.AlignOf<byte>(), internalAllocator);
readerHandle->BufferPointer = buffer;
readerHandle->Position = offset;
}

View File

@@ -270,6 +270,9 @@ namespace Unity.Netcode
networkObject.OwnerClientId = clientId;
networkObject.MarkVariablesDirty(true);
NetworkManager.BehaviourUpdater.AddForUpdate(networkObject);
// Server adds entries for all client ownership
UpdateOwnershipTable(networkObject, networkObject.OwnerClientId);
@@ -291,13 +294,13 @@ namespace Unity.Netcode
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)
{
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject)
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject)
{
if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Header.Hash))
if (NetworkManager.PrefabHandler.ContainsHandler(sceneObject.Hash))
{
return true;
}
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Header.Hash, out var networkPrefab))
if (NetworkManager.NetworkConfig.NetworkPrefabOverrideLinks.TryGetValue(sceneObject.Hash, out var networkPrefab))
{
switch (networkPrefab.Override)
{
@@ -312,7 +315,7 @@ namespace Unity.Netcode
return false;
}
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Header.Hash, sceneObject.NetworkSceneHandle);
var networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(sceneObject.Hash, sceneObject.NetworkSceneHandle);
return networkObject != null;
}
@@ -326,22 +329,22 @@ namespace Unity.Netcode
internal NetworkObject CreateLocalNetworkObject(NetworkObject.SceneObject sceneObject)
{
NetworkObject networkObject = null;
var globalObjectIdHash = sceneObject.Header.Hash;
var position = sceneObject.Header.HasTransform ? sceneObject.Transform.Position : default;
var rotation = sceneObject.Header.HasTransform ? sceneObject.Transform.Rotation : default;
var scale = sceneObject.Header.HasTransform ? sceneObject.Transform.Scale : default;
var parentNetworkId = sceneObject.Header.HasParent ? sceneObject.ParentObjectId : default;
var worldPositionStays = sceneObject.Header.HasParent ? sceneObject.WorldPositionStays : true;
var globalObjectIdHash = sceneObject.Hash;
var position = sceneObject.HasTransform ? sceneObject.Transform.Position : default;
var rotation = sceneObject.HasTransform ? sceneObject.Transform.Rotation : default;
var scale = sceneObject.HasTransform ? sceneObject.Transform.Scale : default;
var parentNetworkId = sceneObject.HasParent ? sceneObject.ParentObjectId : default;
var worldPositionStays = (!sceneObject.HasParent) || sceneObject.WorldPositionStays;
var isSpawnedByPrefabHandler = false;
// If scene management is disabled or the NetworkObject was dynamically spawned
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.Header.IsSceneObject)
if (!NetworkManager.NetworkConfig.EnableSceneManagement || !sceneObject.IsSceneObject)
{
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
{
// Let the handler spawn the NetworkObject
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.Header.OwnerClientId, position, rotation);
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, sceneObject.OwnerClientId, position, rotation);
networkObject.NetworkManagerOwner = NetworkManager;
isSpawnedByPrefabHandler = true;
}
@@ -402,12 +405,12 @@ namespace Unity.Netcode
if (networkObject != null)
{
// SPECIAL CASE:
// SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject)
// This is a special case scenario where a late joining client has joined and loaded one or
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
// synchronization information does not indicate the NetworkObject in question has a parent.
// Under this scenario, we want to remove the parent before spawning and setting the transform values.
if (sceneObject.Header.IsSceneObject && !sceneObject.Header.HasParent && networkObject.transform.parent != null)
if (sceneObject.IsSceneObject && !sceneObject.HasParent && networkObject.transform.parent != null)
{
// if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not
// include parenting, then we need to force the removal of that parent
@@ -421,9 +424,11 @@ namespace Unity.Netcode
// Set the transform unless we were spawned by a prefab handler
// Note: prefab handlers are provided the position and rotation
// but it is up to the user to set those values
if (sceneObject.Header.HasTransform && !isSpawnedByPrefabHandler)
if (sceneObject.HasTransform && !isSpawnedByPrefabHandler)
{
if (worldPositionStays)
// If world position stays is true or we have auto object parent synchronization disabled
// then we want to apply the position and rotation values world space relative
if (worldPositionStays || !networkObject.AutoObjectParentSync)
{
networkObject.transform.position = position;
networkObject.transform.rotation = rotation;
@@ -441,22 +446,31 @@ namespace Unity.Netcode
// the network prefab used to represent the player.
// Note: not doing this would set the player's scale to zero since
// that is the default value of Vector3.
if (!sceneObject.Header.IsPlayerObject)
if (!sceneObject.IsPlayerObject)
{
// Since scale is always applied to local space scale, we do the transform
// space logic during serialization such that it works out whether AutoObjectParentSync
// is enabled or not (see NetworkObject.SceneObject)
networkObject.transform.localScale = scale;
}
}
if (sceneObject.Header.HasParent)
if (sceneObject.HasParent)
{
// Go ahead and set network parenting properties
networkObject.SetNetworkParenting(parentNetworkId, worldPositionStays);
// Go ahead and set network parenting properties, if the latest parent is not set then pass in null
// (we always want to set worldPositionStays)
ulong? parentId = null;
if (sceneObject.IsLatestParentSet)
{
parentId = parentNetworkId;
}
networkObject.SetNetworkParenting(parentId, worldPositionStays);
}
// Dynamically spawned NetworkObjects that occur during a LoadSceneMode.Single load scene event are migrated into the DDOL
// until the scene is loaded. They are then migrated back into the newly loaded and currently active scene.
if (!sceneObject.Header.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
if (!sceneObject.IsSceneObject && NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad)
{
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
}
@@ -490,8 +504,7 @@ namespace Unity.Netcode
}
// Ran on both server and client
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject,
FastBufferReader variableData, bool destroyWithScene)
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, bool destroyWithScene)
{
if (networkObject == null)
{
@@ -503,9 +516,7 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is already spawned");
}
networkObject.SetNetworkVariableData(variableData);
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.Header.NetworkObjectId, sceneObject.Header.IsSceneObject, sceneObject.Header.IsPlayerObject, sceneObject.Header.OwnerClientId, destroyWithScene);
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.NetworkObjectId, sceneObject.IsSceneObject, sceneObject.IsPlayerObject, sceneObject.OwnerClientId, destroyWithScene);
}
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
@@ -647,7 +658,11 @@ namespace Unity.Netcode
// Makes scene objects ready to be reused
internal void ServerResetShudownStateForSceneObjects()
{
#if UNITY_2023_1_OR_NEWER
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID).Where((c) => c.IsSceneObject != null && c.IsSceneObject == true);
#else
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSceneObject != null && c.IsSceneObject == true);
#endif
foreach (var sobj in networkObjects)
{
sobj.IsSpawned = false;
@@ -678,7 +693,11 @@ namespace Unity.Netcode
internal void DespawnAndDestroyNetworkObjects()
{
#if UNITY_2023_1_OR_NEWER
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID);
#else
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
#endif
for (int i = 0; i < networkObjects.Length; i++)
{
@@ -708,7 +727,11 @@ namespace Unity.Netcode
internal void DestroySceneObjects()
{
#if UNITY_2023_1_OR_NEWER
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID);
#else
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
#endif
for (int i = 0; i < networkObjects.Length; i++)
{
@@ -735,7 +758,11 @@ namespace Unity.Netcode
internal void ServerSpawnSceneObjectsOnStartSweep()
{
#if UNITY_2023_1_OR_NEWER
var networkObjects = UnityEngine.Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID);
#else
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
#endif
var networkObjectsToSpawn = new List<NetworkObject>();
for (int i = 0; i < networkObjects.Length; i++)

View File

@@ -10,8 +10,10 @@ using System.Collections.Generic;
using UnityEngine;
using NetcodeNetworkEvent = Unity.Netcode.NetworkEvent;
using TransportNetworkEvent = Unity.Networking.Transport.NetworkEvent;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Collections;
using Unity.Jobs;
using Unity.Networking.Transport;
using Unity.Networking.Transport.Relay;
using Unity.Networking.Transport.Utilities;
@@ -51,50 +53,48 @@ namespace Unity.Netcode.Transports.UTP
/// </summary>
public static class ErrorUtilities
{
private const string k_NetworkSuccess = "Success";
private const string k_NetworkIdMismatch = "NetworkId is invalid, likely caused by stale connection {0}.";
private const string k_NetworkVersionMismatch = "NetworkVersion is invalid, likely caused by stale connection {0}.";
private const string k_NetworkStateMismatch = "Sending data while connecting on connection {0} is not allowed.";
private const string k_NetworkPacketOverflow = "Unable to allocate packet due to buffer overflow.";
private const string k_NetworkSendQueueFull = "Currently unable to queue packet as there is too many in-flight packets. This could be because the send queue size ('Max Send Queue Size') is too small.";
private const string k_NetworkHeaderInvalid = "Invalid Unity Transport Protocol header.";
private const string k_NetworkDriverParallelForErr = "The parallel network driver needs to process a single unique connection per job, processing a single connection multiple times in a parallel for is not supported.";
private const string k_NetworkSendHandleInvalid = "Invalid NetworkInterface Send Handle. Likely caused by pipeline send data corruption.";
private const string k_NetworkArgumentMismatch = "Invalid NetworkEndpoint Arguments.";
private static readonly FixedString128Bytes k_NetworkSuccess = "Success";
private static readonly FixedString128Bytes k_NetworkIdMismatch = "Invalid connection ID {0}.";
private static readonly FixedString128Bytes k_NetworkVersionMismatch = "Connection ID is invalid. Likely caused by sending on stale connection {0}.";
private static readonly FixedString128Bytes k_NetworkStateMismatch = "Connection state is invalid. Likely caused by sending on connection {0} which is stale or still connecting.";
private static readonly FixedString128Bytes k_NetworkPacketOverflow = "Packet is too large to be allocated by the transport.";
private static readonly FixedString128Bytes k_NetworkSendQueueFull = "Unable to queue packet in the transport. Likely caused by send queue size ('Max Send Queue Size') being too small.";
/// <summary>
/// Convert error code to human readable error message.
/// Convert a UTP error code to human-readable error message.
/// </summary>
/// <param name="error">Status code of the error</param>
/// <param name="connectionId">Subject connection ID of the error</param>
/// <returns>Human readable error message.</returns>
/// <param name="error">UTP error code.</param>
/// <param name="connectionId">ID of the connection on which the error occurred.</param>
/// <returns>Human-readable error message.</returns>
public static string ErrorToString(Networking.Transport.Error.StatusCode error, ulong connectionId)
{
switch (error)
return ErrorToString((int)error, connectionId);
}
internal static string ErrorToString(int error, ulong connectionId)
{
return ErrorToFixedString(error, connectionId).ToString();
}
internal static FixedString128Bytes ErrorToFixedString(int error, ulong connectionId)
{
switch ((Networking.Transport.Error.StatusCode)error)
{
case Networking.Transport.Error.StatusCode.Success:
return k_NetworkSuccess;
case Networking.Transport.Error.StatusCode.NetworkIdMismatch:
return string.Format(k_NetworkIdMismatch, connectionId);
return FixedString.Format(k_NetworkIdMismatch, connectionId);
case Networking.Transport.Error.StatusCode.NetworkVersionMismatch:
return string.Format(k_NetworkVersionMismatch, connectionId);
return FixedString.Format(k_NetworkVersionMismatch, connectionId);
case Networking.Transport.Error.StatusCode.NetworkStateMismatch:
return string.Format(k_NetworkStateMismatch, connectionId);
return FixedString.Format(k_NetworkStateMismatch, connectionId);
case Networking.Transport.Error.StatusCode.NetworkPacketOverflow:
return k_NetworkPacketOverflow;
case Networking.Transport.Error.StatusCode.NetworkSendQueueFull:
return k_NetworkSendQueueFull;
case Networking.Transport.Error.StatusCode.NetworkHeaderInvalid:
return k_NetworkHeaderInvalid;
case Networking.Transport.Error.StatusCode.NetworkDriverParallelForErr:
return k_NetworkDriverParallelForErr;
case Networking.Transport.Error.StatusCode.NetworkSendHandleInvalid:
return k_NetworkSendHandleInvalid;
case Networking.Transport.Error.StatusCode.NetworkArgumentMismatch:
return k_NetworkArgumentMismatch;
default:
return FixedString.Format("Unknown error code {0}.", error);
}
return $"Unknown ErrorCode {Enum.GetName(typeof(Networking.Transport.Error.StatusCode), error)}";
}
}
@@ -676,53 +676,72 @@ namespace Unity.Netcode.Transports.UTP
}
}
[BurstCompile]
private struct SendBatchedMessagesJob : IJob
{
public NetworkDriver.Concurrent Driver;
public SendTarget Target;
public BatchedSendQueue Queue;
public NetworkPipeline ReliablePipeline;
public void Execute()
{
var clientId = Target.ClientId;
var connection = ParseClientId(clientId);
var pipeline = Target.NetworkPipeline;
while (!Queue.IsEmpty)
{
var result = Driver.BeginSend(pipeline, connection, out var writer);
if (result != (int)Networking.Transport.Error.StatusCode.Success)
{
Debug.LogError($"Error sending message: {ErrorUtilities.ErrorToFixedString(result, clientId)}");
return;
}
// We don't attempt to send entire payloads over the reliable pipeline. Instead we
// fragment it manually. This is safe and easy to do since the reliable pipeline
// basically implements a stream, so as long as we separate the different messages
// in the stream (the send queue does that automatically) we are sure they'll be
// reassembled properly at the other end. This allows us to lift the limit of ~44KB
// on reliable payloads (because of the reliable window size).
var written = pipeline == ReliablePipeline ? Queue.FillWriterWithBytes(ref writer) : Queue.FillWriterWithMessages(ref writer);
result = Driver.EndSend(writer);
if (result == written)
{
// Batched message was sent successfully. Remove it from the queue.
Queue.Consume(written);
}
else
{
// Some error occured. If it's just the UTP queue being full, then don't log
// anything since that's okay (the unsent message(s) are still in the queue
// and we'll retry sending them later). Otherwise log the error and remove the
// message from the queue (we don't want to resend it again since we'll likely
// just get the same error again).
if (result != (int)Networking.Transport.Error.StatusCode.NetworkSendQueueFull)
{
Debug.LogError($"Error sending the message: {ErrorUtilities.ErrorToFixedString(result, clientId)}");
Queue.Consume(written);
}
return;
}
}
}
}
// Send as many batched messages from the queue as possible.
private void SendBatchedMessages(SendTarget sendTarget, BatchedSendQueue queue)
{
var clientId = sendTarget.ClientId;
var connection = ParseClientId(clientId);
var pipeline = sendTarget.NetworkPipeline;
while (!queue.IsEmpty)
new SendBatchedMessagesJob
{
var result = m_Driver.BeginSend(pipeline, connection, out var writer);
if (result != (int)Networking.Transport.Error.StatusCode.Success)
{
Debug.LogError("Error sending the message: " +
ErrorUtilities.ErrorToString((Networking.Transport.Error.StatusCode)result, clientId));
return;
}
// We don't attempt to send entire payloads over the reliable pipeline. Instead we
// fragment it manually. This is safe and easy to do since the reliable pipeline
// basically implements a stream, so as long as we separate the different messages
// in the stream (the send queue does that automatically) we are sure they'll be
// reassembled properly at the other end. This allows us to lift the limit of ~44KB
// on reliable payloads (because of the reliable window size).
var written = pipeline == m_ReliableSequencedPipeline ? queue.FillWriterWithBytes(ref writer) : queue.FillWriterWithMessages(ref writer);
result = m_Driver.EndSend(writer);
if (result == written)
{
// Batched message was sent successfully. Remove it from the queue.
queue.Consume(written);
}
else
{
// Some error occured. If it's just the UTP queue being full, then don't log
// anything since that's okay (the unsent message(s) are still in the queue
// and we'll retry sending the later). Otherwise log the error and remove the
// message from the queue (we don't want to resend it again since we'll likely
// just get the same error again).
if (result != (int)Networking.Transport.Error.StatusCode.NetworkSendQueueFull)
{
Debug.LogError("Error sending the message: " + ErrorUtilities.ErrorToString((Networking.Transport.Error.StatusCode)result, clientId));
queue.Consume(written);
}
return;
}
}
Driver = m_Driver.ToConcurrent(),
Target = sendTarget,
Queue = queue,
ReliablePipeline = m_ReliableSequencedPipeline
}.Run();
}
private bool AcceptConnection()
@@ -1441,7 +1460,7 @@ namespace Unity.Netcode.Transports.UTP
{
if (m_ProtocolType == ProtocolType.RelayUnityTransport)
{
if (m_RelayServerData.IsSecure != 0)
if (m_RelayServerData.IsSecure == 0)
{
// log an error because we have mismatched configuration
Debug.LogError("Mismatched security configuration, between Relay and local NetworkManager settings");
@@ -1452,36 +1471,30 @@ namespace Unity.Netcode.Transports.UTP
}
else
{
try
if (NetworkManager.IsServer)
{
if (NetworkManager.IsServer)
if (String.IsNullOrEmpty(m_ServerCertificate) || String.IsNullOrEmpty(m_ServerPrivateKey))
{
if (m_ServerCertificate.Length == 0 || m_ServerPrivateKey.Length == 0)
{
throw new Exception("In order to use encrypted communications, when hosting, you must set the server certificate and key.");
}
m_NetworkSettings.WithSecureServerParameters(m_ServerCertificate, m_ServerPrivateKey);
throw new Exception("In order to use encrypted communications, when hosting, you must set the server certificate and key.");
}
m_NetworkSettings.WithSecureServerParameters(m_ServerCertificate, m_ServerPrivateKey);
}
else
{
if (String.IsNullOrEmpty(m_ServerCommonName))
{
throw new Exception("In order to use encrypted communications, clients must set the server common name.");
}
else if (String.IsNullOrEmpty(m_ClientCaCertificate))
{
m_NetworkSettings.WithSecureClientParameters(m_ServerCommonName);
}
else
{
if (m_ServerCommonName.Length == 0)
{
throw new Exception("In order to use encrypted communications, clients must set the server common name.");
}
else if (m_ClientCaCertificate == null)
{
m_NetworkSettings.WithSecureClientParameters(m_ServerCommonName);
}
else
{
m_NetworkSettings.WithSecureClientParameters(m_ClientCaCertificate, m_ServerCommonName);
}
m_NetworkSettings.WithSecureClientParameters(m_ClientCaCertificate, m_ServerCommonName);
}
}
catch(Exception e)
{
Debug.LogException(e, this);
}
}
}
#endif