com.unity.netcode.gameobjects@1.0.0-pre.4

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.0.0-pre.4] - 2021-01-04

### Added

- Added `com.unity.modules.physics` and `com.unity.modules.physics2d` package dependencies (#1565)

### Removed

- Removed `com.unity.modules.ai` package dependency (#1565)
- Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398)

### Fixed
- Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354)
- Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393)
- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379)
- Fixed a runtime error when sending an array of an INetworkSerializable type that's implemented as a struct (#1402)
- NetworkConfig will no longer throw an OverflowException in GetConfig() when ForceSamePrefabs is enabled and the number of prefabs causes the config blob size to exceed 1300 bytes. (#1385)
- Fixed NetworkVariable not calling NetworkSerialize on INetworkSerializable types (#1383)
- Fixed NullReferenceException on ImportReferences call in NetworkBehaviourILPP (#1434)
- Fixed NetworkObjects not being despawned before they are destroyed during shutdown for client, host, and server instances. (#1390)
- Fixed KeyNotFound exception when removing ownership of a newly spawned NetworkObject that is already owned by the server. (#1500)
- Fixed NetworkManager.LocalClient not being set when starting as a host. (#1511)
- Fixed a few memory leak cases when shutting down NetworkManager during Incoming Message Queue processing. (#1323)

### Changed
- The SDK no longer limits message size to 64k. (The transport may still impose its own limits, but the SDK no longer does.) (#1384)
- Updated com.unity.collections to 1.1.0 (#1451)
This commit is contained in:
Unity Technologies
2021-01-04 00:00:00 +00:00
parent f5664b4cc1
commit 36d07fad5e
59 changed files with 1585 additions and 681 deletions

View File

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

View File

@@ -1,77 +0,0 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Queue with a fixed size
/// </summary>
/// <typeparam name="T">The type of the queue</typeparam>
public sealed class FixedQueue<T>
{
private readonly T[] m_Queue;
private int m_QueueCount = 0;
private int m_QueueStart;
/// <summary>
/// The amount of enqueued objects
/// </summary>
public int Count => m_QueueCount;
/// <summary>
/// Gets the element at a given virtual index
/// </summary>
/// <param name="index">The virtual index to get the item from</param>
/// <returns>The element at the virtual index</returns>
public T this[int index] => m_Queue[(m_QueueStart + index) % m_Queue.Length];
/// <summary>
/// Creates a new FixedQueue with a given size
/// </summary>
/// <param name="maxSize">The size of the queue</param>
public FixedQueue(int maxSize)
{
m_Queue = new T[maxSize];
m_QueueStart = 0;
}
/// <summary>
/// Enqueues an object
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public bool Enqueue(T t)
{
m_Queue[(m_QueueStart + m_QueueCount) % m_Queue.Length] = t;
if (++m_QueueCount > m_Queue.Length)
{
--m_QueueCount;
return true;
}
return false;
}
/// <summary>
/// Dequeues an object
/// </summary>
/// <returns></returns>
public T Dequeue()
{
if (--m_QueueCount == -1)
{
throw new IndexOutOfRangeException("Cannot dequeue empty queue!");
}
T res = m_Queue[m_QueueStart];
m_QueueStart = (m_QueueStart + 1) % m_Queue.Length;
return res;
}
/// <summary>
/// Gets the element at a given virtual index
/// </summary>
/// <param name="index">The virtual index to get the item from</param>
/// <returns>The element at the virtual index</returns>
public T ElementAt(int index) => m_Queue[(m_QueueStart + index) % m_Queue.Length];
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: a8514b4eca0c7044d9b92faf9407ec93
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -141,15 +141,15 @@ namespace Unity.Netcode
/// <summary>
/// Whether or not to enable Snapshot System for variable updates. Not supported in this version.
/// </summary>
public bool UseSnapshotDelta { get; } = false;
public bool UseSnapshotDelta { get; internal set; } = false;
/// <summary>
/// Whether or not to enable Snapshot System for spawn and despawn commands. Not supported in this version.
/// </summary>
public bool UseSnapshotSpawn { get; } = false;
public bool UseSnapshotSpawn { get; internal set; } = false;
/// <summary>
/// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
/// </summary>
public int SnapshotMaxSpawnUsage { get; } = 1200;
public int SnapshotMaxSpawnUsage { get; } = 1000;
public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one)
public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets)
@@ -224,7 +224,7 @@ namespace Unity.Netcode
return m_ConfigHash.Value;
}
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, int.MaxValue);
using (writer)
{
writer.WriteValueSafe(ProtocolVersion);

View File

@@ -5,6 +5,6 @@ namespace Unity.Netcode
/// </summary>
internal static class NetworkConstants
{
internal const string PROTOCOL_VERSION = "14.0.0";
internal const string PROTOCOL_VERSION = "15.0.0";
}
}

View File

@@ -82,7 +82,8 @@ namespace Unity.Netcode
SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message.
// Passing false to canDefer prevents it being accessed.
Header = new MessageHeader()
Header = new MessageHeader(),
SerializedHeaderSize = 0,
};
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
rpcMessageSize = tempBuffer.Length;
@@ -188,7 +189,8 @@ namespace Unity.Netcode
SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message.
// Passing false to canDefer prevents it being accessed.
Header = new MessageHeader()
Header = new MessageHeader(),
SerializedHeaderSize = 0,
};
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
messageSize = tempBuffer.Length;
@@ -282,9 +284,13 @@ namespace Unity.Netcode
m_NetworkObject = GetComponentInParent<NetworkObject>();
}
if (m_NetworkObject == null && NetworkLog.CurrentLogLevel <= LogLevel.Normal)
if (m_NetworkObject == null || NetworkManager.Singleton == null ||
(NetworkManager.Singleton != null && !NetworkManager.Singleton.ShutdownInProgress))
{
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
if (NetworkLog.CurrentLogLevel < LogLevel.Normal)
{
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
}
}
return m_NetworkObject;

View File

@@ -73,6 +73,9 @@ namespace Unity.Netcode
}
}
private bool m_ShuttingDown;
private bool m_StopProcessingMessages;
private class NetworkManagerHooks : INetworkHooks
{
private NetworkManager m_NetworkManager;
@@ -116,7 +119,7 @@ namespace Unity.Netcode
public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery)
{
return true;
return !m_NetworkManager.m_StopProcessingMessages;
}
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
@@ -134,7 +137,7 @@ namespace Unity.Netcode
return false;
}
return true;
return !m_NetworkManager.m_StopProcessingMessages;
}
}
@@ -333,6 +336,9 @@ namespace Unity.Netcode
/// </summary>
public bool IsConnectedClient { get; internal set; }
public bool ShutdownInProgress { get { return m_ShuttingDown; } }
/// <summary>
/// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
/// </summary>
@@ -345,8 +351,6 @@ namespace Unity.Netcode
/// </summary>
public event Action<ulong> OnClientDisconnectCallback = null;
internal void InvokeOnClientDisconnectCallback(ulong clientId) => OnClientDisconnectCallback?.Invoke(clientId);
/// <summary>
/// The callback to invoke once the server is ready
/// </summary>
@@ -556,8 +560,6 @@ namespace Unity.Netcode
SnapshotSystem = null;
}
SnapshotSystem = new SnapshotSystem(this);
if (server)
{
NetworkTimeSystem = NetworkTimeSystem.ServerTimeSystem();
@@ -570,6 +572,8 @@ namespace Unity.Netcode
NetworkTickSystem = new NetworkTickSystem(NetworkConfig.TickRate, 0, 0);
NetworkTickSystem.Tick += OnNetworkManagerTick;
SnapshotSystem = new SnapshotSystem(this, NetworkConfig, NetworkTickSystem);
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
// This is used to remove entries not needed or invalid
@@ -976,7 +980,7 @@ namespace Unity.Netcode
// Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit
private void OnDestroy()
{
Shutdown();
ShutdownInternal();
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
@@ -996,13 +1000,30 @@ namespace Unity.Netcode
/// Globally shuts down the library.
/// Disconnects clients if connected and stops server if running.
/// </summary>
public void Shutdown()
/// <param name="discardMessageQueue">
/// If false, any messages that are currently in the incoming queue will be handled,
/// and any messages in the outgoing queue will be sent, before the shutdown is processed.
/// If true, NetworkManager will shut down immediately, and any unprocessed or unsent messages
/// will be discarded.
/// </param>
public void Shutdown(bool discardMessageQueue = false)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo(nameof(Shutdown));
}
m_ShuttingDown = true;
m_StopProcessingMessages = discardMessageQueue;
}
internal void ShutdownInternal()
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo(nameof(ShutdownInternal));
}
if (IsServer)
{
// make sure all messages are flushed before transport disconnect clients
@@ -1075,11 +1096,15 @@ namespace Unity.Netcode
MessagingSystem = null;
}
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
if (NetworkConfig?.NetworkTransport != null)
{
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
}
if (SpawnManager != null)
{
SpawnManager.DestroyNonSceneObjects();
SpawnManager.CleanupAllTriggers();
SpawnManager.DespawnAndDestroyNetworkObjects();
SpawnManager.ServerResetShudownStateForSceneObjects();
SpawnManager = null;
@@ -1114,6 +1139,8 @@ namespace Unity.Netcode
m_TransportIdToClientIdMap.Clear();
IsListening = false;
m_ShuttingDown = false;
m_StopProcessingMessages = false;
}
// INetworkUpdateSystem
@@ -1167,6 +1194,11 @@ namespace Unity.Netcode
return;
}
if (m_ShuttingDown && m_StopProcessingMessages)
{
return;
}
// Only update RTT here, server time is updated by time sync messages
var reset = NetworkTimeSystem.Advance(Time.deltaTime);
if (reset)
@@ -1183,9 +1215,18 @@ namespace Unity.Netcode
private void OnNetworkPostLateUpdate()
{
MessagingSystem.ProcessSendQueues();
NetworkMetrics.DispatchFrame();
if (!m_ShuttingDown || !m_StopProcessingMessages)
{
MessagingSystem.ProcessSendQueues();
NetworkMetrics.DispatchFrame();
}
SpawnManager.CleanupStaleTriggers();
if (m_ShuttingDown)
{
ShutdownInternal();
}
}
/// <summary>
@@ -1312,6 +1353,8 @@ namespace Unity.Netcode
#endif
clientId = TransportIdToClientId(clientId);
OnClientDisconnectCallback?.Invoke(clientId);
m_TransportIdToClientIdMap.Remove(transportId);
m_ClientIdToTransportIdMap.Remove(clientId);
@@ -1328,9 +1371,6 @@ namespace Unity.Netcode
{
Shutdown();
}
OnClientDisconnectCallback?.Invoke(clientId);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportDisconnect.End();
#endif
@@ -1596,6 +1636,7 @@ namespace Unity.Netcode
}
else // Server just adds itself as an observer to all spawned NetworkObjects
{
LocalClient = client;
SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
InvokeOnClientConnectedCallback(ownerClientId);
}

View File

@@ -62,20 +62,63 @@ namespace Unity.Netcode
internal int TimesWritten;
}
internal class ClientData
{
internal struct SentSpawn // this struct also stores Despawns, not just Spawns
{
internal ulong SequenceNumber;
internal ulong ObjectId;
internal int Tick;
}
internal ushort SequenceNumber = 0; // the next sequence number to use for this client
internal ushort LastReceivedSequence = 0; // the last sequence number received by this client
internal ushort ReceivedSequenceMask = 0; // bitmask of the messages before the last one that we received.
internal int NextSpawnIndex = 0; // index of the last spawn sent. Used to cycle through spawns (LRU scheme)
internal int NextDespawnIndex = 0; // same as above, but for despawns.
// by objectId
// which spawns and despawns did this connection ack'ed ?
internal Dictionary<ulong, int> SpawnAck = new Dictionary<ulong, int>();
// list of spawn and despawns commands we sent, with sequence number
// need to manage acknowledgements
internal List<SentSpawn> SentSpawns = new List<SentSpawn>();
}
internal delegate int MockSendMessage(in SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId);
internal delegate int MockSpawnObject(SnapshotSpawnCommand spawnCommand);
internal delegate int MockDespawnObject(SnapshotDespawnCommand despawnCommand);
// A table of NetworkVariables that constitutes a Snapshot.
// Stores serialized NetworkVariables
// todo --M1--
// The Snapshot will change for M1b with memory management, instead of just FreeMemoryPosition, there will be data structure
// around available buffer, etc.
internal class Snapshot
internal class SnapshotSystem : INetworkUpdateSystem, IDisposable
{
// todo --M1-- functionality to grow these will be needed in a later milestone
private const int k_MaxVariables = 2000;
private int m_MaxSpawns = 100;
private int m_MaxDespawns = 100;
internal int SpawnsBufferCount { get; private set; } = 100;
internal int DespawnsBufferCount { get; private set; } = 100;
private const int k_BufferSize = 30000;
private NetworkManager m_NetworkManager = default;
// by clientId
private Dictionary<ulong, ClientData> m_ClientData = new Dictionary<ulong, ClientData>();
private Dictionary<ulong, ConnectionRtt> m_ConnectionRtts = new Dictionary<ulong, ConnectionRtt>();
private bool m_UseSnapshotDelta;
private bool m_UseSnapshotSpawn;
private int m_SnapshotMaxSpawnUsage;
private NetworkTickSystem m_NetworkTickSystem;
private int m_CurrentTick = NetworkTickSystem.NoTick;
internal byte[] MainBuffer = new byte[k_BufferSize]; // buffer holding a snapshot in memory
internal byte[] RecvBuffer = new byte[k_BufferSize]; // buffer holding the received snapshot message
@@ -90,23 +133,17 @@ namespace Unity.Netcode
internal SnapshotDespawnCommand[] Despawns;
internal int NumDespawns = 0;
internal NetworkManager NetworkManager;
// indexed by ObjectId
internal Dictionary<ulong, int> TickAppliedSpawn = new Dictionary<ulong, int>();
internal Dictionary<ulong, int> TickAppliedDespawn = new Dictionary<ulong, int>();
/// <summary>
/// Constructor
/// Allocated a MemoryStream to be reused for this Snapshot
/// </summary>
internal Snapshot()
{
// we ask for twice as many slots because there could end up being one free spot between each pair of slot used
Allocator = new IndexAllocator(k_BufferSize, k_MaxVariables * 2);
Spawns = new SnapshotSpawnCommand[m_MaxSpawns];
Despawns = new SnapshotDespawnCommand[m_MaxDespawns];
}
internal bool IsServer { get; set; }
internal bool IsConnectedClient { get; set; }
internal ulong ServerClientId { get; set; }
internal List<ulong> ConnectedClientsId { get; } = new List<ulong>();
internal MockSendMessage MockSendMessage { get; set; }
internal MockSpawnObject MockSpawnObject { get; set; }
internal MockDespawnObject MockDespawnObject { get; set; }
internal void Clear()
{
@@ -156,15 +193,15 @@ namespace Unity.Netcode
List<ulong> clientList;
clientList = new List<ulong>();
if (!NetworkManager.IsServer)
if (!IsServer)
{
clientList.Add(NetworkManager.ServerClientId);
clientList.Add(m_NetworkManager.ServerClientId);
}
else
{
foreach (var clientId in NetworkManager.ConnectedClientsIds)
foreach (var clientId in ConnectedClientsId)
{
if (clientId != NetworkManager.ServerClientId)
if (clientId != m_NetworkManager.ServerClientId)
{
clientList.Add(clientId);
}
@@ -176,14 +213,14 @@ namespace Unity.Netcode
internal void AddSpawn(SnapshotSpawnCommand command)
{
if (NumSpawns >= m_MaxSpawns)
if (NumSpawns >= SpawnsBufferCount)
{
Array.Resize(ref Spawns, 2 * m_MaxSpawns);
m_MaxSpawns = m_MaxSpawns * 2;
Array.Resize(ref Spawns, 2 * SpawnsBufferCount);
SpawnsBufferCount = SpawnsBufferCount * 2;
// Debug.Log($"[JEFF] spawn size is now {m_MaxSpawns}");
}
if (NumSpawns < m_MaxSpawns)
if (NumSpawns < SpawnsBufferCount)
{
if (command.TargetClientIds == default)
{
@@ -208,14 +245,14 @@ namespace Unity.Netcode
internal void AddDespawn(SnapshotDespawnCommand command)
{
if (NumDespawns >= m_MaxDespawns)
if (NumDespawns >= DespawnsBufferCount)
{
Array.Resize(ref Despawns, 2 * m_MaxDespawns);
m_MaxDespawns = m_MaxDespawns * 2;
Array.Resize(ref Despawns, 2 * DespawnsBufferCount);
DespawnsBufferCount = DespawnsBufferCount * 2;
// Debug.Log($"[JEFF] despawn size is now {m_MaxDespawns}");
}
if (NumDespawns < m_MaxDespawns)
if (NumDespawns < DespawnsBufferCount)
{
if (command.TargetClientIds == default)
{
@@ -229,6 +266,17 @@ namespace Unity.Netcode
}
}
internal void ReduceBufferUsage()
{
var count = Math.Max(1, NumDespawns);
Array.Resize(ref Despawns, count);
DespawnsBufferCount = count;
count = Math.Max(1, NumSpawns);
Array.Resize(ref Spawns, count);
SpawnsBufferCount = count;
}
internal ClientData.SentSpawn GetSpawnData(in ClientData clientData, in SnapshotSpawnCommand spawn, out SnapshotDataMessage.SpawnData data)
{
// remember which spawn we sent this connection with which sequence number
@@ -417,7 +465,54 @@ namespace Unity.Netcode
}
}
internal void ReadSpawns(in SnapshotDataMessage message)
internal void SpawnObject(SnapshotSpawnCommand spawnCommand, ulong srcClientId)
{
if (m_NetworkManager)
{
NetworkObject networkObject;
if (spawnCommand.ParentNetworkId == spawnCommand.NetworkObjectId)
{
networkObject = m_NetworkManager.SpawnManager.CreateLocalNetworkObject(false,
spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, null, spawnCommand.ObjectPosition,
spawnCommand.ObjectRotation);
}
else
{
networkObject = m_NetworkManager.SpawnManager.CreateLocalNetworkObject(false,
spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, spawnCommand.ParentNetworkId, spawnCommand.ObjectPosition,
spawnCommand.ObjectRotation);
}
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId,
true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
//todo: discuss with tools how to report shared bytes
m_NetworkManager.NetworkMetrics.TrackObjectSpawnReceived(srcClientId, networkObject, 8);
}
else
{
MockSpawnObject(spawnCommand);
}
}
internal void DespawnObject(SnapshotDespawnCommand despawnCommand, ulong srcClientId)
{
if (m_NetworkManager)
{
m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(despawnCommand.NetworkObjectId,
out NetworkObject networkObject);
m_NetworkManager.SpawnManager.OnDespawnObject(networkObject, true);
//todo: discuss with tools how to report shared bytes
m_NetworkManager.NetworkMetrics.TrackObjectDestroyReceived(srcClientId, networkObject, 8);
}
else
{
MockDespawnObject(despawnCommand);
}
}
internal void ReadSpawns(in SnapshotDataMessage message, ulong srcClientId)
{
SnapshotSpawnCommand spawnCommand;
SnapshotDespawnCommand despawnCommand;
@@ -436,16 +531,7 @@ namespace Unity.Netcode
// Debug.Log($"[Spawn] {spawnCommand.NetworkObjectId} {spawnCommand.TickWritten}");
if (spawnCommand.ParentNetworkId == spawnCommand.NetworkObjectId)
{
var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(false, spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, null, spawnCommand.ObjectPosition, spawnCommand.ObjectRotation);
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
}
else
{
var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(false, spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, spawnCommand.ParentNetworkId, spawnCommand.ObjectPosition, spawnCommand.ObjectRotation);
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
}
SpawnObject(spawnCommand, srcClientId);
}
for (var i = 0; i < message.Despawns.Length; i++)
{
@@ -461,10 +547,7 @@ namespace Unity.Netcode
// Debug.Log($"[DeSpawn] {despawnCommand.NetworkObjectId} {despawnCommand.TickWritten}");
NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(despawnCommand.NetworkObjectId,
out NetworkObject networkObject);
NetworkManager.SpawnManager.OnDespawnObject(networkObject, true);
DespawnObject(despawnCommand, srcClientId);
}
}
@@ -569,7 +652,7 @@ namespace Unity.Netcode
/// <param name="key">The key to search for</param>
private NetworkVariableBase FindNetworkVar(VariableKey key)
{
var spawnedObjects = NetworkManager.SpawnManager.SpawnedObjects;
var spawnedObjects = m_NetworkManager.SpawnManager.SpawnedObjects;
if (spawnedObjects.ContainsKey(key.NetworkObjectId))
{
@@ -580,61 +663,49 @@ namespace Unity.Netcode
return null;
}
}
internal class ClientData
{
internal struct SentSpawn // this struct also stores Despawns, not just Spawns
{
internal ulong SequenceNumber;
internal ulong ObjectId;
internal int Tick;
}
internal ushort SequenceNumber = 0; // the next sequence number to use for this client
internal ushort LastReceivedSequence = 0; // the last sequence number received by this client
internal ushort ReceivedSequenceMask = 0; // bitmask of the messages before the last one that we received.
internal int NextSpawnIndex = 0; // index of the last spawn sent. Used to cycle through spawns (LRU scheme)
internal int NextDespawnIndex = 0; // same as above, but for despawns.
// by objectId
// which spawns and despawns did this connection ack'ed ?
internal Dictionary<ulong, int> SpawnAck = new Dictionary<ulong, int>();
// list of spawn and despawns commands we sent, with sequence number
// need to manage acknowledgements
internal List<SentSpawn> SentSpawns = new List<SentSpawn>();
}
internal class SnapshotSystem : INetworkUpdateSystem, IDisposable
{
// temporary, debugging sentinels
internal const ushort SentinelBefore = 0x4246;
internal const ushort SentinelAfter = 0x89CE;
private NetworkManager m_NetworkManager = default;
private Snapshot m_Snapshot = default;
// by clientId
private Dictionary<ulong, ClientData> m_ClientData = new Dictionary<ulong, ClientData>();
private Dictionary<ulong, ConnectionRtt> m_ConnectionRtts = new Dictionary<ulong, ConnectionRtt>();
private int m_CurrentTick = NetworkTickSystem.NoTick;
/// <summary>
/// Constructor
/// </summary>
/// Registers the snapshot system for early updates, keeps reference to the NetworkManager
internal SnapshotSystem(NetworkManager networkManager)
internal SnapshotSystem(NetworkManager networkManager, NetworkConfig config, NetworkTickSystem networkTickSystem)
{
m_Snapshot = new Snapshot();
m_NetworkManager = networkManager;
m_Snapshot.NetworkManager = networkManager;
m_NetworkTickSystem = networkTickSystem;
m_UseSnapshotDelta = config.UseSnapshotDelta;
m_UseSnapshotSpawn = config.UseSnapshotSpawn;
m_SnapshotMaxSpawnUsage = config.SnapshotMaxSpawnUsage;
UpdateClientServerData();
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
// we ask for twice as many slots because there could end up being one free spot between each pair of slot used
Allocator = new IndexAllocator(k_BufferSize, k_MaxVariables * 2);
Spawns = new SnapshotSpawnCommand[SpawnsBufferCount];
Despawns = new SnapshotDespawnCommand[DespawnsBufferCount];
}
// since we don't want to access the NetworkManager directly, we refresh those values on Update
internal void UpdateClientServerData()
{
if (m_NetworkManager)
{
IsServer = m_NetworkManager.IsServer;
IsConnectedClient = m_NetworkManager.IsConnectedClient;
ServerClientId = m_NetworkManager.ServerClientId;
// todo: This is extremely inefficient. What is the efficient and idiomatic way ?
ConnectedClientsId.Clear();
if (IsServer)
{
foreach (var id in m_NetworkManager.ConnectedClientsIds)
{
ConnectedClientsId.Add(id);
}
}
}
}
internal ConnectionRtt GetConnectionRtt(ulong clientId)
@@ -658,34 +729,36 @@ namespace Unity.Netcode
public void NetworkUpdate(NetworkUpdateStage updateStage)
{
if (!m_NetworkManager.NetworkConfig.UseSnapshotDelta && !m_NetworkManager.NetworkConfig.UseSnapshotSpawn)
if (!m_UseSnapshotDelta && !m_UseSnapshotSpawn)
{
return;
}
if (updateStage == NetworkUpdateStage.EarlyUpdate)
{
var tick = m_NetworkManager.NetworkTickSystem.LocalTime.Tick;
UpdateClientServerData();
var tick = m_NetworkTickSystem.LocalTime.Tick;
if (tick != m_CurrentTick)
{
m_CurrentTick = tick;
if (m_NetworkManager.IsServer)
if (IsServer)
{
for (int i = 0; i < m_NetworkManager.ConnectedClientsList.Count; i++)
for (int i = 0; i < ConnectedClientsId.Count; i++)
{
var clientId = m_NetworkManager.ConnectedClientsList[i].ClientId;
var clientId = ConnectedClientsId[i];
// don't send to ourselves
if (clientId != m_NetworkManager.ServerClientId)
if (clientId != ServerClientId)
{
SendSnapshot(clientId);
}
}
}
else if (m_NetworkManager.IsConnectedClient)
else if (IsConnectedClient)
{
SendSnapshot(m_NetworkManager.ServerClientId);
SendSnapshot(ServerClientId);
}
}
@@ -721,12 +794,12 @@ namespace Unity.Netcode
{
CurrentTick = m_CurrentTick,
Sequence = sequence,
Range = (ushort)m_Snapshot.Allocator.Range,
Range = (ushort)Allocator.Range,
// todo --M1--
// this sends the whole buffer
// we'll need to build a per-client list
SendMainBuffer = m_Snapshot.MainBuffer,
SendMainBuffer = MainBuffer,
Ack = new SnapshotDataMessage.AckData
{
@@ -740,7 +813,14 @@ namespace Unity.Netcode
WriteIndex(ref message);
WriteSpawns(ref message, clientId);
m_NetworkManager.SendMessage(message, NetworkDelivery.Unreliable, clientId);
if (m_NetworkManager)
{
m_NetworkManager.SendMessage(message, NetworkDelivery.Unreliable, clientId);
}
else
{
MockSendMessage(message, NetworkDelivery.Unreliable, clientId);
}
m_ClientData[clientId].LastReceivedSequence = 0;
@@ -791,51 +871,51 @@ namespace Unity.Netcode
ClientData clientData = m_ClientData[clientId];
// this is needed because spawns being removed may have reduce the size below LRU position
if (m_Snapshot.NumSpawns > 0)
if (NumSpawns > 0)
{
clientData.NextSpawnIndex %= m_Snapshot.NumSpawns;
clientData.NextSpawnIndex %= NumSpawns;
}
else
{
clientData.NextSpawnIndex = 0;
}
if (m_Snapshot.NumDespawns > 0)
if (NumDespawns > 0)
{
clientData.NextDespawnIndex %= m_Snapshot.NumDespawns;
clientData.NextDespawnIndex %= NumDespawns;
}
else
{
clientData.NextDespawnIndex = 0;
}
message.Spawns = new NativeList<SnapshotDataMessage.SpawnData>(m_Snapshot.NumSpawns, Allocator.TempJob);
message.Despawns = new NativeList<SnapshotDataMessage.DespawnData>(m_Snapshot.NumDespawns, Allocator.TempJob);
message.Spawns = new NativeList<SnapshotDataMessage.SpawnData>(NumSpawns, Collections.Allocator.TempJob);
message.Despawns = new NativeList<SnapshotDataMessage.DespawnData>(NumDespawns, Collections.Allocator.TempJob);
var spawnUsage = 0;
for (var j = 0; j < m_Snapshot.NumSpawns && !overSize; j++)
for (var j = 0; j < NumSpawns && !overSize; j++)
{
var index = clientData.NextSpawnIndex;
// todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns
if (m_Snapshot.Spawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteSpawn(m_Snapshot.Spawns[index])*/)
if (Spawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteSpawn(Spawns[index])*/)
{
spawnUsage += FastBufferWriter.GetWriteSize<SnapshotDataMessage.SpawnData>();
// limit spawn sizes, compare current pos to very first position we wrote to
if (spawnUsage > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage)
if (spawnUsage > m_SnapshotMaxSpawnUsage)
{
overSize = true;
break;
}
var sentSpawn = m_Snapshot.GetSpawnData(clientData, in m_Snapshot.Spawns[index], out var spawn);
var sentSpawn = GetSpawnData(clientData, in Spawns[index], out var spawn);
message.Spawns.Add(spawn);
m_Snapshot.Spawns[index].TimesWritten++;
Spawns[index].TimesWritten++;
clientData.SentSpawns.Add(sentSpawn);
spawnWritten++;
}
clientData.NextSpawnIndex = (clientData.NextSpawnIndex + 1) % m_Snapshot.NumSpawns;
clientData.NextSpawnIndex = (clientData.NextSpawnIndex + 1) % NumSpawns;
}
// even though we might have a spawn we could not fit, it's possible despawns will fit (they're smaller)
@@ -846,28 +926,28 @@ namespace Unity.Netcode
// As-is it is overly restrictive but allows us to go forward without the spawn/despawn dependency check
// overSize = false;
for (var j = 0; j < m_Snapshot.NumDespawns && !overSize; j++)
for (var j = 0; j < NumDespawns && !overSize; j++)
{
var index = clientData.NextDespawnIndex;
// todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns
if (m_Snapshot.Despawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteDespawn(m_Snapshot.Despawns[index])*/)
if (Despawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteDespawn(Despawns[index])*/)
{
spawnUsage += FastBufferWriter.GetWriteSize<SnapshotDataMessage.DespawnData>();
// limit spawn sizes, compare current pos to very first position we wrote to
if (spawnUsage > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage)
if (spawnUsage > m_SnapshotMaxSpawnUsage)
{
overSize = true;
break;
}
var sentDespawn = m_Snapshot.GetDespawnData(clientData, in m_Snapshot.Despawns[index], out var despawn);
var sentDespawn = GetDespawnData(clientData, in Despawns[index], out var despawn);
message.Despawns.Add(despawn);
m_Snapshot.Despawns[index].TimesWritten++;
Despawns[index].TimesWritten++;
clientData.SentSpawns.Add(sentDespawn);
despawnWritten++;
}
clientData.NextDespawnIndex = (clientData.NextDespawnIndex + 1) % m_Snapshot.NumDespawns;
clientData.NextDespawnIndex = (clientData.NextDespawnIndex + 1) % NumDespawns;
}
}
@@ -877,10 +957,10 @@ namespace Unity.Netcode
/// <param name="message">The message to write the index to</param>
private void WriteIndex(ref SnapshotDataMessage message)
{
message.Entries = new NativeList<SnapshotDataMessage.EntryData>(m_Snapshot.LastEntry, Allocator.TempJob);
for (var i = 0; i < m_Snapshot.LastEntry; i++)
message.Entries = new NativeList<SnapshotDataMessage.EntryData>(LastEntry, Collections.Allocator.TempJob);
for (var i = 0; i < LastEntry; i++)
{
var entryMeta = m_Snapshot.Entries[i];
var entryMeta = Entries[i];
var entry = entryMeta.Key;
message.Entries.Add(new SnapshotDataMessage.EntryData
{
@@ -897,7 +977,7 @@ namespace Unity.Netcode
internal void Spawn(SnapshotSpawnCommand command)
{
command.TickWritten = m_CurrentTick;
m_Snapshot.AddSpawn(command);
AddSpawn(command);
// Debug.Log($"[Spawn] {command.NetworkObjectId} {command.TickWritten}");
}
@@ -905,7 +985,7 @@ namespace Unity.Netcode
internal void Despawn(SnapshotDespawnCommand command)
{
command.TickWritten = m_CurrentTick;
m_Snapshot.AddDespawn(command);
AddDespawn(command);
// Debug.Log($"[DeSpawn] {command.NetworkObjectId} {command.TickWritten}");
}
@@ -922,35 +1002,35 @@ namespace Unity.Netcode
k.NetworkObjectId = networkObjectId;
k.BehaviourIndex = (ushort)behaviourIndex;
k.VariableIndex = (ushort)variableIndex;
k.TickWritten = m_NetworkManager.NetworkTickSystem.LocalTime.Tick;
k.TickWritten = m_NetworkTickSystem.LocalTime.Tick;
int pos = m_Snapshot.Find(k);
int pos = Find(k);
if (pos == Entry.NotFound)
{
pos = m_Snapshot.AddEntry(k);
pos = AddEntry(k);
}
m_Snapshot.Entries[pos].Key.TickWritten = k.TickWritten;
Entries[pos].Key.TickWritten = k.TickWritten;
WriteVariableToSnapshot(m_Snapshot, networkVariable, pos);
WriteVariable(networkVariable, pos);
}
private unsafe void WriteVariableToSnapshot(Snapshot snapshot, NetworkVariableBase networkVariable, int index)
private unsafe void WriteVariable(NetworkVariableBase networkVariable, int index)
{
// write var into buffer, possibly adjusting entry's position and Length
var varBuffer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
var varBuffer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Collections.Allocator.Temp);
using (varBuffer)
{
networkVariable.WriteDelta(varBuffer);
if (varBuffer.Length > snapshot.Entries[index].Length)
if (varBuffer.Length > Entries[index].Length)
{
// allocate this Entry's buffer
snapshot.AllocateEntry(ref snapshot.Entries[index], index, (int)varBuffer.Length);
AllocateEntry(ref Entries[index], index, (int)varBuffer.Length);
}
fixed (byte* buffer = snapshot.MainBuffer)
fixed (byte* buffer = MainBuffer)
{
UnsafeUtility.MemCpy(buffer + snapshot.Entries[index].Position, varBuffer.GetUnsafePtr(), varBuffer.Length);
UnsafeUtility.MemCpy(buffer + Entries[index].Position, varBuffer.GetUnsafePtr(), varBuffer.Length);
}
}
}
@@ -1009,10 +1089,10 @@ namespace Unity.Netcode
// without this, we incur extra retransmit, not a catastrophic failure
}
m_Snapshot.ReadBuffer(message);
m_Snapshot.ReadIndex(message);
m_Snapshot.ReadAcks(clientId, m_ClientData[clientId], message, GetConnectionRtt(clientId));
m_Snapshot.ReadSpawns(message);
ReadBuffer(message);
ReadIndex(message);
ReadAcks(clientId, m_ClientData[clientId], message, GetConnectionRtt(clientId));
ReadSpawns(message, clientId);
}
// todo --M1--
@@ -1024,14 +1104,14 @@ namespace Unity.Netcode
table += $"We're clientId {m_NetworkManager.LocalClientId}\n";
table += "=== Variables ===\n";
for (int i = 0; i < m_Snapshot.LastEntry; i++)
for (int i = 0; i < LastEntry; i++)
{
table += string.Format("NetworkVariable {0}:{1}:{2} written {5}, range [{3}, {4}] ", m_Snapshot.Entries[i].Key.NetworkObjectId, m_Snapshot.Entries[i].Key.BehaviourIndex,
m_Snapshot.Entries[i].Key.VariableIndex, m_Snapshot.Entries[i].Position, m_Snapshot.Entries[i].Position + m_Snapshot.Entries[i].Length, m_Snapshot.Entries[i].Key.TickWritten);
table += string.Format("NetworkVariable {0}:{1}:{2} written {5}, range [{3}, {4}] ", Entries[i].Key.NetworkObjectId, Entries[i].Key.BehaviourIndex,
Entries[i].Key.VariableIndex, Entries[i].Position, Entries[i].Position + Entries[i].Length, Entries[i].Key.TickWritten);
for (int j = 0; j < m_Snapshot.Entries[i].Length && j < 4; j++)
for (int j = 0; j < Entries[i].Length && j < 4; j++)
{
table += m_Snapshot.MainBuffer[m_Snapshot.Entries[i].Position + j].ToString("X2") + " ";
table += MainBuffer[Entries[i].Position + j].ToString("X2") + " ";
}
table += "\n";
@@ -1039,14 +1119,14 @@ namespace Unity.Netcode
table += "=== Spawns ===\n";
for (int i = 0; i < m_Snapshot.NumSpawns; i++)
for (int i = 0; i < NumSpawns; i++)
{
string targets = "";
foreach (var target in m_Snapshot.Spawns[i].TargetClientIds)
foreach (var target in Spawns[i].TargetClientIds)
{
targets += target.ToString() + ", ";
}
table += $"Spawn Object Id {m_Snapshot.Spawns[i].NetworkObjectId}, Tick {m_Snapshot.Spawns[i].TickWritten}, Target {targets}\n";
table += $"Spawn Object Id {Spawns[i].NetworkObjectId}, Tick {Spawns[i].TickWritten}, Target {targets}\n";
}
table += $"=== RTTs ===\n";

View File

@@ -28,7 +28,7 @@ namespace Unity.Netcode
/// </summary>
public event UnnamedMessageDelegate OnUnnamedMessage;
internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader)
internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader, int serializedHeaderSize)
{
if (OnUnnamedMessage != null)
{
@@ -40,7 +40,7 @@ namespace Unity.Netcode
((UnnamedMessageDelegate)handler).Invoke(clientId, reader);
}
}
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + serializedHeaderSize);
}
/// <summary>
@@ -115,9 +115,9 @@ namespace Unity.Netcode
private Dictionary<ulong, string> m_MessageHandlerNameLookup32 = new Dictionary<ulong, string>();
private Dictionary<ulong, string> m_MessageHandlerNameLookup64 = new Dictionary<ulong, string>();
internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader)
internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader, int serializedHeaderSize)
{
var bytesCount = reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>();
var bytesCount = reader.Length + serializedHeaderSize;
if (m_NetworkManager == null)
{

View File

@@ -11,11 +11,12 @@ namespace Unity.Netcode
/// unchanged - if new messages are added or messages are removed, MessageType assignments may be
/// calculated differently.
/// </summary>
public byte MessageType;
public uint MessageType;
/// <summary>
/// The total size of the message, NOT including the header.
/// Stored as a uint to avoid zig-zag encoding, but capped at int.MaxValue.
/// </summary>
public ushort MessageSize;
public uint MessageSize;
}
}

View File

@@ -16,7 +16,7 @@ namespace Unity.Netcode
var message = new NamedMessage();
reader.ReadValueSafe(out message.Hash);
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(message.Hash, context.SenderId, reader);
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(message.Hash, context.SenderId, reader, context.SerializedHeaderSize);
}
}
}

View File

@@ -104,7 +104,6 @@ namespace Unity.Netcode
public static unsafe void Receive(FastBufferReader reader, in NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
var message = new SnapshotDataMessage();
if (!reader.TryBeginRead(
FastBufferWriter.GetWriteSize(message.CurrentTick) +
@@ -142,20 +141,32 @@ namespace Unity.Netcode
using (message.Spawns)
using (message.Despawns)
{
message.Handle(context.SenderId, networkManager);
message.Handle(context.SenderId, context.SystemOwner);
}
}
public void Handle(ulong senderId, NetworkManager networkManager)
public void Handle(ulong senderId, object systemOwner)
{
// todo: temporary hack around bug
if (!networkManager.IsServer)
if (systemOwner is NetworkManager)
{
senderId = networkManager.ServerClientId;
}
var networkManager = (NetworkManager)systemOwner;
var snapshotSystem = networkManager.SnapshotSystem;
snapshotSystem.HandleSnapshot(senderId, this);
// todo: temporary hack around bug
if (!networkManager.IsServer)
{
senderId = networkManager.ServerClientId;
}
var snapshotSystem = networkManager.SnapshotSystem;
snapshotSystem.HandleSnapshot(senderId, this);
}
else
{
var ownerData = (Tuple<SnapshotSystem, ulong>)systemOwner;
var snapshotSystem = ownerData.Item1;
snapshotSystem.HandleSnapshot(ownerData.Item2, this);
return;
}
}
}
}

View File

@@ -11,7 +11,7 @@ namespace Unity.Netcode
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, reader);
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, reader, context.SerializedHeaderSize);
}
}
}

View File

@@ -23,6 +23,7 @@ namespace Unity.Netcode
public MessageHeader Header;
public ulong SenderId;
public float Timestamp;
public int MessageHeaderSerializedSize;
}
private struct SendQueueItem
@@ -46,27 +47,27 @@ namespace Unity.Netcode
private MessageHandler[] m_MessageHandlers = new MessageHandler[255];
private Type[] m_ReverseTypeMap = new Type[255];
private Dictionary<Type, byte> m_MessageTypes = new Dictionary<Type, byte>();
private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
private List<INetworkHooks> m_Hooks = new List<INetworkHooks>();
private byte m_HighMessageType;
private uint m_HighMessageType;
private object m_Owner;
private IMessageSender m_MessageSender;
private bool m_Disposed;
internal Type[] MessageTypes => m_ReverseTypeMap;
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
internal int MessageHandlerCount => m_HighMessageType;
internal uint MessageHandlerCount => m_HighMessageType;
internal byte GetMessageType(Type t)
internal uint GetMessageType(Type t)
{
return m_MessageTypes[t];
}
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
public const int FRAGMENTED_MESSAGE_MAX_SIZE = 64000;
public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue;
internal struct MessageWithHandler
{
@@ -100,7 +101,7 @@ namespace Unity.Netcode
}
}
public void Dispose()
public unsafe void Dispose()
{
if (m_Disposed)
{
@@ -113,6 +114,14 @@ namespace Unity.Netcode
{
CleanupDisconnectedClient(kvp.Key);
}
for (var queueIndex = 0; queueIndex < m_IncomingMessageQueue.Length; ++queueIndex)
{
// Avoid copies...
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(queueIndex);
item.Reader.Dispose();
}
m_IncomingMessageQueue.Dispose();
m_Disposed = true;
}
@@ -141,7 +150,7 @@ namespace Unity.Netcode
fixed (byte* nativeData = data.Array)
{
var batchReader =
new FastBufferReader(nativeData, Allocator.None, data.Count, data.Offset);
new FastBufferReader(nativeData + data.Offset, Allocator.None, data.Count);
if (!batchReader.TryBeginRead(sizeof(BatchHeader)))
{
NetworkLog.LogWarning("Received a packet too small to contain a BatchHeader. Ignoring it.");
@@ -157,14 +166,23 @@ namespace Unity.Netcode
for (var messageIdx = 0; messageIdx < batchHeader.BatchSize; ++messageIdx)
{
if (!batchReader.TryBeginRead(sizeof(MessageHeader)))
var messageHeader = new MessageHeader();
var position = batchReader.Position;
try
{
ByteUnpacker.ReadValueBitPacked(batchReader, out messageHeader.MessageType);
ByteUnpacker.ReadValueBitPacked(batchReader, out messageHeader.MessageSize);
}
catch (OverflowException)
{
NetworkLog.LogWarning("Received a batch that didn't have enough data for all of its batches, ending early!");
return;
throw;
}
batchReader.ReadValue(out MessageHeader messageHeader);
if (!batchReader.TryBeginRead(messageHeader.MessageSize))
var receivedHeaderSize = batchReader.Position - position;
if (!batchReader.TryBeginRead((int)messageHeader.MessageSize))
{
NetworkLog.LogWarning("Received a message that claimed a size larger than the packet, ending early!");
return;
@@ -177,9 +195,10 @@ namespace Unity.Netcode
// Copy the data for this message into a new FastBufferReader that owns that memory.
// We can't guarantee the memory in the ArraySegment stays valid because we don't own it,
// so we must move it to memory we do own.
Reader = new FastBufferReader(batchReader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, messageHeader.MessageSize)
Reader = new FastBufferReader(batchReader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, (int)messageHeader.MessageSize),
MessageHeaderSerializedSize = receivedHeaderSize,
});
batchReader.Seek(batchReader.Position + messageHeader.MessageSize);
batchReader.Seek(batchReader.Position + (int)messageHeader.MessageSize);
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
@@ -202,7 +221,7 @@ namespace Unity.Netcode
return true;
}
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp)
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
{
if (header.MessageType >= m_HighMessageType)
{
@@ -215,8 +234,10 @@ namespace Unity.Netcode
SystemOwner = m_Owner,
SenderId = senderId,
Timestamp = timestamp,
Header = header
Header = header,
SerializedHeaderSize = serializedHeaderSize,
};
var type = m_ReverseTypeMap[header.MessageType];
if (!CanReceive(senderId, type))
{
@@ -228,6 +249,7 @@ namespace Unity.Netcode
{
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
}
var handler = m_MessageHandlers[header.MessageType];
using (reader)
{
@@ -253,11 +275,15 @@ namespace Unity.Netcode
internal unsafe void ProcessIncomingMessageQueue()
{
for (var i = 0; i < m_IncomingMessageQueue.Length; ++i)
for (var index = 0; index < m_IncomingMessageQueue.Length; ++index)
{
// Avoid copies...
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(i);
HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp);
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(index);
HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp, item.MessageHeaderSerializedSize);
if (m_Disposed)
{
return;
}
}
m_IncomingMessageQueue.Clear();
@@ -316,64 +342,68 @@ namespace Unity.Netcode
}
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
using (tmpSerializer)
using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
message.Serialize(tmpSerializer);
using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp);
var header = new MessageHeader
{
message.Serialize(tmpSerializer);
MessageSize = (ushort)tmpSerializer.Length,
MessageType = m_MessageTypes[typeof(TMessageType)],
};
BytePacker.WriteValueBitPacked(headerSerializer, header.MessageType);
BytePacker.WriteValueBitPacked(headerSerializer, header.MessageSize);
for (var i = 0; i < clientIds.Count; ++i)
for (var i = 0; i < clientIds.Count; ++i)
{
var clientId = clientIds[i];
if (!CanSend(clientId, typeof(TMessageType), delivery))
{
var clientId = clientIds[i];
continue;
}
if (!CanSend(clientId, typeof(TMessageType), delivery))
{
continue;
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeSendMessage(clientId, typeof(TMessageType), delivery);
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeSendMessage(clientId, typeof(TMessageType), delivery);
}
var sendQueueItem = m_SendQueues[clientId];
if (sendQueueItem.Length == 0)
var sendQueueItem = m_SendQueues[clientId];
if (sendQueueItem.Length == 0)
{
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
maxSize));
sendQueueItem.GetUnsafeList()->ElementAt(0).Writer.Seek(sizeof(BatchHeader));
}
else
{
ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
if (lastQueueItem.NetworkDelivery != delivery ||
lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position
< tmpSerializer.Length + headerSerializer.Length)
{
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
maxSize));
sendQueueItem.GetUnsafeList()->ElementAt(0).Writer.Seek(sizeof(BatchHeader));
}
else
{
ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
if (lastQueueItem.NetworkDelivery != delivery ||
lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position
< tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>())
{
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
maxSize));
sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader));
}
}
ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
var header = new MessageHeader
{
MessageSize = (ushort)tmpSerializer.Length,
MessageType = m_MessageTypes[typeof(TMessageType)],
};
writeQueueItem.Writer.WriteValue(header);
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
writeQueueItem.BatchHeader.BatchSize++;
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader));
}
}
return tmpSerializer.Length + FastBufferWriter.GetWriteSize<MessageHeader>();
ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length);
writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
writeQueueItem.BatchHeader.BatchSize++;
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + headerSerializer.Length);
}
}
return tmpSerializer.Length + headerSerializer.Length;
}
private struct PointerListWrapper<T> : IReadOnlyList<T>
@@ -461,16 +491,16 @@ namespace Unity.Netcode
try
{
m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer);
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
}
}
finally
{
queueItem.Writer.Dispose();
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
}
}
sendQueueItem.Clear();
}

View File

@@ -25,5 +25,10 @@ namespace Unity.Netcode
/// The header data that was sent with the message
/// </summary>
public MessageHeader Header;
/// <summary>
/// The actual serialized size of the header when packed into the buffer
/// </summary>
public int SerializedHeaderSize;
}
}

View File

@@ -1,12 +0,0 @@
using System.IO;
namespace Unity.Netcode
{
public static class StreamExtensions
{
public static long SafeGetLengthOrDefault(this Stream stream)
{
return stream.CanSeek ? stream.Length : 0;
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 61dd9b1558f6d7c46ad323b2c2c03c29
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -9,6 +9,55 @@ namespace Unity.Netcode
[Serializable]
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
{
// Functions that know how to serialize INetworkSerializable
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, ref TForMethod value)
where TForMethod : INetworkSerializable, new()
{
writer.WriteNetworkSerializable(value);
}
internal static void ReadNetworkSerializable<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : INetworkSerializable, new()
{
reader.ReadNetworkSerializable(out value);
}
// Functions that serialize other types
private static void WriteValue<TForMethod>(FastBufferWriter writer, ref TForMethod value) where TForMethod : unmanaged
{
writer.WriteValueSafe(value);
}
private static void ReadValue<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged
{
reader.ReadValueSafe(out value);
}
internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, ref TForMethod value);
internal delegate void ReadDelegate<TForMethod>(FastBufferReader reader, out TForMethod value);
// These static delegates provide the right implementation for writing and reading a particular network variable
// type.
//
// For most types, these default to WriteValue() and ReadValue(), which perform simple memcpy operations.
//
// INetworkSerializableILPP will generate startup code that will set it to WriteNetworkSerializable()
// and ReadNetworkSerializable() for INetworkSerializable types, which will call NetworkSerialize().
//
// In the future we may be able to use this to provide packing implementations for floats and integers to
// optimize bandwidth usage.
//
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
// side, but it gets the best achievable user experience and performance.
internal static WriteDelegate<T> Write = WriteValue;
internal static ReadDelegate<T> Read = ReadValue;
/// <summary>
/// Delegate type for value changed event
/// </summary>
@@ -106,7 +155,7 @@ namespace Unity.Netcode
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
{
T previousValue = m_InternalValue;
reader.ReadValueSafe(out m_InternalValue);
Read(reader, out m_InternalValue);
if (keepDirtyDelta)
{
@@ -119,13 +168,13 @@ namespace Unity.Netcode
/// <inheritdoc />
public override void ReadField(FastBufferReader reader)
{
reader.ReadValueSafe(out m_InternalValue);
Read(reader, out m_InternalValue);
}
/// <inheritdoc />
public override void WriteField(FastBufferWriter writer)
{
writer.WriteValueSafe(m_InternalValue);
Write(writer, ref m_InternalValue);
}
}
}

View File

@@ -0,0 +1,22 @@
namespace Unity.Netcode
{
public class NetworkVariableHelper
{
// This is called by ILPP during module initialization for all unmanaged INetworkSerializable types
// This sets up NetworkVariable so that it properly calls NetworkSerialize() when wrapping an INetworkSerializable value
//
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
// side, but it gets the best achievable user experience and performance.
//
// RuntimeAccessModifiersILPP will make this `public`
internal static void InitializeDelegates<T>() where T : unmanaged, INetworkSerializable
{
NetworkVariable<T>.Write = NetworkVariable<T>.WriteNetworkSerializable;
NetworkVariable<T>.Read = NetworkVariable<T>.ReadNetworkSerializable;
}
}
}

View File

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

View File

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

View File

@@ -1,31 +0,0 @@
using System;
namespace Unity.Netcode
{
internal static class TypeExtensions
{
internal static bool HasInterface(this Type type, Type interfaceType)
{
var ifaces = type.GetInterfaces();
for (int i = 0; i < ifaces.Length; i++)
{
if (ifaces[i] == interfaceType)
{
return true;
}
}
return false;
}
internal static bool IsNullable(this Type type)
{
if (!type.IsValueType)
{
return true; // ref-type
}
return Nullable.GetUnderlyingType(type) != null;
}
}
}

View File

@@ -1028,8 +1028,8 @@ namespace Unity.Netcode
// despawned that no longer exists
SendSceneEventData(sceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != m_NetworkManager.ServerClientId).ToArray());
//Second, server sets itself as having finished unloading
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
//Only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId);
}
@@ -1344,8 +1344,8 @@ namespace Unity.Netcode
OnLoadComplete?.Invoke(m_NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode);
//Second, set the server as having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
//Second, only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId);
}
@@ -1830,21 +1830,18 @@ namespace Unity.Netcode
/// Moves all NetworkObjects that don't have the <see cref="NetworkObject.DestroyWithScene"/> set to
/// the "Do not destroy on load" scene.
/// </summary>
private void MoveObjectsToDontDestroyOnLoad()
internal void MoveObjectsToDontDestroyOnLoad()
{
// Move ALL NetworkObjects to the temp scene
// Move ALL NetworkObjects marked to persist scene transitions into the DDOL scene
var objectsToKeep = new HashSet<NetworkObject>(m_NetworkManager.SpawnManager.SpawnedObjectsList);
foreach (var sobj in objectsToKeep)
{
if (!sobj.DestroyWithScene || (sobj.IsSceneObject != null && sobj.IsSceneObject.Value && sobj.gameObject.scene == DontDestroyOnLoadScene))
if (!sobj.DestroyWithScene || sobj.gameObject.scene == DontDestroyOnLoadScene)
{
// Only move objects with no parent as child objects will follow
if (sobj.gameObject.transform.parent == null)
// Only move dynamically spawned network objects with no parent as child objects will follow
if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
{
UnityEngine.Object.DontDestroyOnLoad(sobj.gameObject);
// Since we are doing a scene transition, disable the GameObject until the next scene is loaded
sobj.gameObject.SetActive(false);
}
}
else if (m_NetworkManager.IsServer)
@@ -1907,24 +1904,22 @@ namespace Unity.Netcode
/// Moves all spawned NetworkObjects (from do not destroy on load) to the scene specified
/// </summary>
/// <param name="scene">scene to move the NetworkObjects to</param>
private void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene)
internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene)
{
// Move ALL NetworkObjects to the temp scene
var objectsToKeep = m_NetworkManager.SpawnManager.SpawnedObjectsList;
foreach (var sobj in objectsToKeep)
{
if (sobj.gameObject.scene == DontDestroyOnLoadScene && (sobj.IsSceneObject == null || sobj.IsSceneObject.Value))
// If it is in the DDOL then
if (sobj.gameObject.scene == DontDestroyOnLoadScene)
{
continue;
}
// Only move objects with no parent as child objects will follow
if (sobj.gameObject.transform.parent == null)
{
// set it back to active at this point
sobj.gameObject.SetActive(true);
SceneManager.MoveGameObjectToScene(sobj.gameObject, scene);
// only move dynamically spawned network objects, with no parent as child objects will follow,
// back into the currently active scene
if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
{
SceneManager.MoveGameObjectToScene(sobj.gameObject, scene);
}
}
}
}

View File

@@ -189,7 +189,7 @@ namespace Unity.Netcode
var newSize = Math.Min(desiredSize, Handle->MaxCapacity);
byte* newBuffer = (byte*)UnsafeUtility.Malloc(newSize, UnsafeUtility.AlignOf<byte>(), Handle->Allocator);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
UnsafeUtility.MemSet(newBuffer, 0, sizeof(WriterHandle) + newSize);
UnsafeUtility.MemSet(newBuffer, 0, newSize);
#endif
UnsafeUtility.MemCpy(newBuffer, Handle->BufferPointer, Length);
if (Handle->BufferGrew)
@@ -428,7 +428,7 @@ namespace Unity.Netcode
/// <param name="count"></param>
/// <param name="offset"></param>
/// <typeparam name="T"></typeparam>
public void WriteNetworkSerializable<T>(INetworkSerializable[] array, int count = -1, int offset = 0) where T : INetworkSerializable
public void WriteNetworkSerializable<T>(T[] array, int count = -1, int offset = 0) where T : INetworkSerializable
{
int sizeInTs = count != -1 ? count : array.Length - offset;
WriteValueSafe(sizeInTs);

View File

@@ -27,6 +27,7 @@ namespace Unity.Netcode
public MessageHeader Header;
public ulong SenderId;
public float Timestamp;
public int SerializedHeaderSize;
}
private struct TriggerInfo
{
@@ -117,7 +118,8 @@ namespace Unity.Netcode
Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length),
Header = context.Header,
Timestamp = context.Timestamp,
SenderId = context.SenderId
SenderId = context.SenderId,
SerializedHeaderSize = context.SerializedHeaderSize
});
}
@@ -154,6 +156,24 @@ namespace Unity.Netcode
m_Triggers.Remove(staleKeys[i]);
}
}
/// <summary>
/// Cleans up any trigger that's existed for more than a second.
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
/// </summary>
internal void CleanupAllTriggers()
{
foreach (var kvp in m_Triggers)
{
foreach (var data in kvp.Value.TriggerData)
{
data.Reader.Dispose();
}
kvp.Value.TriggerData.Dispose();
}
m_Triggers.Clear();
}
internal void RemoveOwnership(NetworkObject networkObject)
{
@@ -167,28 +187,44 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is not spawned");
}
for (int i = NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects.Count - 1;
i > -1;
i--)
// If we made it here then we are the server and if the server is determined to already be the owner
// then ignore the RemoveOwnership invocation.
if (networkObject.OwnerClientId == NetworkManager.ServerClientId)
{
if (NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects[i] == networkObject)
{
NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects.RemoveAt(i);
}
return;
}
networkObject.OwnerClientIdInternal = null;
var message = new ChangeOwnershipMessage
// Make sure the connected client entry exists before trying to remove ownership.
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
{
NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId
};
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
{
if (networkClient.OwnedObjects[i] == networkObject)
{
networkClient.OwnedObjects.RemoveAt(i);
}
}
foreach (var client in NetworkManager.ConnectedClients)
networkObject.OwnerClientIdInternal = null;
var message = new ChangeOwnershipMessage
{
NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId
};
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
foreach (var client in NetworkManager.ConnectedClients)
{
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
}
}
else
{
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"No connected clients prior to removing ownership for {networkObject.name}. Make sure you are not initializing or shutting down when removing ownership.");
}
}
}
@@ -207,7 +243,7 @@ namespace Unity.Netcode
return NetworkManager.ConnectedClients.TryGetValue(clientId, out networkClient);
}
if (clientId == NetworkManager.LocalClient.ClientId)
if (NetworkManager.LocalClient != null && clientId == NetworkManager.LocalClient.ClientId)
{
networkClient = NetworkManager.LocalClient;
return true;
@@ -483,7 +519,7 @@ namespace Unity.Netcode
foreach (var trigger in triggerInfo.TriggerData)
{
// Reader will be disposed within HandleMessage
NetworkManager.MessagingSystem.HandleMessage(trigger.Header, trigger.Reader, trigger.SenderId, trigger.Timestamp);
NetworkManager.MessagingSystem.HandleMessage(trigger.Header, trigger.Reader, trigger.SenderId, trigger.Timestamp, trigger.SerializedHeaderSize);
}
triggerInfo.TriggerData.Dispose();
@@ -580,7 +616,7 @@ namespace Unity.Netcode
}
}
internal void DestroyNonSceneObjects()
internal void DespawnAndDestroyNetworkObjects()
{
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
@@ -588,17 +624,25 @@ namespace Unity.Netcode
{
if (networkObjects[i].NetworkManager == NetworkManager)
{
if (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value == false)
if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i]))
{
if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i]))
{
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
OnDespawnObject(networkObjects[i], false);
}
else
{
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
}
OnDespawnObject(networkObjects[i], false);
// Leave destruction up to the handler
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
}
else if (networkObjects[i].IsSpawned)
{
// If it is an in-scene placed NetworkObject then just despawn
// and let it be destroyed when the scene is unloaded. Otherwise,
// despawn and destroy it.
var shouldDestroy = !(networkObjects[i].IsSceneObject != null
&& networkObjects[i].IsSceneObject.Value);
OnDespawnObject(networkObjects[i], shouldDestroy);
}
else
{
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
}
}
}
@@ -668,17 +712,21 @@ namespace Unity.Netcode
return;
}
// Move child NetworkObjects to the root when parent NetworkObject is destroyed
foreach (var spawnedNetObj in SpawnedObjectsList)
// If we are shutting down the NetworkManager, then ignore resetting the parent
if (!NetworkManager.ShutdownInProgress)
{
var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting();
if (isReparented && latestParent == networkObject.NetworkObjectId)
// Move child NetworkObjects to the root when parent NetworkObject is destroyed
foreach (var spawnedNetObj in SpawnedObjectsList)
{
spawnedNetObj.gameObject.transform.parent = null;
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting();
if (isReparented && latestParent == networkObject.NetworkObjectId)
{
NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed");
spawnedNetObj.gameObject.transform.parent = null;
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed");
}
}
}
}