com.unity.netcode.gameobjects@1.0.0-pre.7
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.7] - 2022-04-01 ### Added - Added editor only check prior to entering into play mode if the currently open and active scene is in the build list and if not displays a dialog box asking the user if they would like to automatically add it prior to entering into play mode. (#1828) - Added `UnityTransport` implementation and `com.unity.transport` package dependency (#1823) - Added `NetworkVariableWritePermission` to `NetworkVariableBase` and implemented `Owner` client writable netvars. (#1762) - `UnityTransport` settings can now be set programmatically. (#1845) - `FastBufferWriter` and Reader IsInitialized property. (#1859) ### Changed - Updated `UnityTransport` dependency on `com.unity.transport` to 1.0.0 (#1849) ### Removed - Removed `SnapshotSystem` (#1852) - Removed `com.unity.modules.animation`, `com.unity.modules.physics` and `com.unity.modules.physics2d` dependencies from the package (#1812) - Removed `com.unity.collections` dependency from the package (#1849) ### Fixed - Fixed in-scene placed NetworkObjects not being found/ignored after a client disconnects and then reconnects. (#1850) - Fixed issue where `UnityTransport` send queues were not flushed when calling `DisconnectLocalClient` or `DisconnectRemoteClient`. (#1847) - Fixed NetworkBehaviour dependency verification check for an existing NetworkObject not searching from root parent transform relative GameObject. (#1841) - Fixed issue where entries were not being removed from the NetworkSpawnManager.OwnershipToObjectsTable. (#1838) - Fixed ClientRpcs would always send to all connected clients by default as opposed to only sending to the NetworkObject's Observers list by default. (#1836) - Fixed clarity for NetworkSceneManager client side notification when it receives a scene hash value that does not exist in its local hash table. (#1828) - Fixed client throws a key not found exception when it times out using UNet or UTP. (#1821) - Fixed network variable updates are no longer limited to 32,768 bytes when NetworkConfig.EnsureNetworkVariableLengthSafety is enabled. The limits are now determined by what the transport can send in a message. (#1811) - Fixed in-scene NetworkObjects get destroyed if a client fails to connect and shuts down the NetworkManager. (#1809) - Fixed user never being notified in the editor that a NetworkBehaviour requires a NetworkObject to function properly. (#1808) - Fixed PlayerObjects and dynamically spawned NetworkObjects not being added to the NetworkClient's OwnedObjects (#1801) - Fixed issue where NetworkManager would continue starting even if the NetworkTransport selected failed. (#1780) - Fixed issue when spawning new player if an already existing player exists it does not remove IsPlayer from the previous player (#1779) - Fixed lack of notification that NetworkManager and NetworkObject cannot be added to the same GameObject with in-editor notifications (#1777) - Fixed parenting warning printing for false positives (#1855)
This commit is contained in:
@@ -1,388 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct IndexAllocatorEntry
|
||||
{
|
||||
internal int Pos; // Position where the memory of this slot is
|
||||
internal int Length; // Length of the memory allocated to this slot
|
||||
internal int Next; // Next and Prev define the order of the slots in the buffer
|
||||
internal int Prev;
|
||||
internal bool Free; // Whether this is a free slot
|
||||
}
|
||||
|
||||
internal class IndexAllocator
|
||||
{
|
||||
private const int k_NotSet = -1;
|
||||
private readonly int m_MaxSlot; // Maximum number of sections (free or not) in the buffer
|
||||
private readonly int m_BufferSize; // Size of the buffer we allocated into
|
||||
private int m_LastSlot = 0; // Last allocated slot
|
||||
private IndexAllocatorEntry[] m_Slots; // Array of slots
|
||||
private int[] m_IndexToSlot; // Mapping from the client's index to the slot index
|
||||
|
||||
internal IndexAllocator(int bufferSize, int maxSlot)
|
||||
{
|
||||
m_MaxSlot = maxSlot;
|
||||
m_BufferSize = bufferSize;
|
||||
m_Slots = new IndexAllocatorEntry[m_MaxSlot];
|
||||
m_IndexToSlot = new int[m_MaxSlot];
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset this IndexAllocator to an empty one, with the same sized buffer and slots
|
||||
/// </summary>
|
||||
internal void Reset()
|
||||
{
|
||||
// todo: could be made faster, for example by having a last index
|
||||
// and not needing valid stuff past it
|
||||
for (int i = 0; i < m_MaxSlot; i++)
|
||||
{
|
||||
m_Slots[i].Free = true;
|
||||
m_Slots[i].Next = i + 1;
|
||||
m_Slots[i].Prev = i - 1;
|
||||
m_Slots[i].Pos = m_BufferSize;
|
||||
m_Slots[i].Length = 0;
|
||||
|
||||
m_IndexToSlot[i] = k_NotSet;
|
||||
}
|
||||
|
||||
m_Slots[0].Pos = 0;
|
||||
m_Slots[0].Length = m_BufferSize;
|
||||
m_Slots[0].Prev = k_NotSet;
|
||||
m_Slots[m_MaxSlot - 1].Next = k_NotSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the amount of memory used
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the amount of memory used, starting at 0, ending after the last used slot
|
||||
/// </returns>
|
||||
internal int Range
|
||||
{
|
||||
get
|
||||
{
|
||||
// when the whole buffer is free, m_LastSlot points to an empty slot
|
||||
if (m_Slots[m_LastSlot].Free)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// otherwise return the end of the last slot used
|
||||
return m_Slots[m_LastSlot].Pos + m_Slots[m_LastSlot].Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocate a slot with "size" position, for index "index"
|
||||
/// </summary>
|
||||
/// <param name="index">The client index to identify this. Used in Deallocate to identify which slot</param>
|
||||
/// <param name="size">The size required. </param>
|
||||
/// <param name="pos">Returns the position to use in the buffer </param>
|
||||
/// <returns>
|
||||
/// true if successful, false is there isn't enough memory available or no slots are large enough
|
||||
/// </returns>
|
||||
internal bool Allocate(int index, int size, out int pos)
|
||||
{
|
||||
pos = 0;
|
||||
// size must be positive, index must be within range
|
||||
if (size < 0 || index < 0 || index >= m_MaxSlot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// refuse allocation if the index is already in use
|
||||
if (m_IndexToSlot[index] != k_NotSet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: this is the slowest part
|
||||
// improvement 1: list of free blocks (minor)
|
||||
// improvement 2: heap of free blocks
|
||||
for (int i = 0; i < m_MaxSlot; i++)
|
||||
{
|
||||
if (m_Slots[i].Free && m_Slots[i].Length >= size)
|
||||
{
|
||||
m_IndexToSlot[index] = i;
|
||||
|
||||
int leftOver = m_Slots[i].Length - size;
|
||||
int next = m_Slots[i].Next;
|
||||
if (m_Slots[next].Free)
|
||||
{
|
||||
m_Slots[next].Pos -= leftOver;
|
||||
m_Slots[next].Length += leftOver;
|
||||
}
|
||||
else
|
||||
{
|
||||
int add = MoveSlotAfter(i);
|
||||
|
||||
m_Slots[add].Pos = m_Slots[i].Pos + size;
|
||||
m_Slots[add].Length = m_Slots[i].Length - size;
|
||||
}
|
||||
|
||||
m_Slots[i].Free = false;
|
||||
m_Slots[i].Length = size;
|
||||
|
||||
pos = m_Slots[i].Pos;
|
||||
|
||||
// if we allocate past the current range, we are the last slot
|
||||
if (m_Slots[i].Pos + m_Slots[i].Length > Range)
|
||||
{
|
||||
m_LastSlot = i;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deallocate a slot
|
||||
/// </summary>
|
||||
/// <param name="index">The client index to identify this. Same index used in Allocate </param>
|
||||
/// <returns>
|
||||
/// true if successful, false is there isn't an allocated slot at this index
|
||||
/// </returns>
|
||||
internal bool Deallocate(int index)
|
||||
{
|
||||
// size must be positive, index must be within range
|
||||
if (index < 0 || index >= m_MaxSlot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int slot = m_IndexToSlot[index];
|
||||
|
||||
if (slot == k_NotSet)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[slot].Free)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Slots[slot].Free = true;
|
||||
|
||||
int prev = m_Slots[slot].Prev;
|
||||
int next = m_Slots[slot].Next;
|
||||
|
||||
// if previous slot was free, merge and grow
|
||||
if (prev != k_NotSet && m_Slots[prev].Free)
|
||||
{
|
||||
m_Slots[prev].Length += m_Slots[slot].Length;
|
||||
m_Slots[slot].Length = 0;
|
||||
|
||||
// if the slot we're merging was the last one, the last one is now the one we merged with
|
||||
if (slot == m_LastSlot)
|
||||
{
|
||||
m_LastSlot = prev;
|
||||
}
|
||||
|
||||
// todo: verify what this does on full or nearly full cases
|
||||
MoveSlotToEnd(slot);
|
||||
slot = prev;
|
||||
}
|
||||
|
||||
next = m_Slots[slot].Next;
|
||||
|
||||
// merge with next slot if it is free
|
||||
if (next != k_NotSet && m_Slots[next].Free)
|
||||
{
|
||||
m_Slots[slot].Length += m_Slots[next].Length;
|
||||
m_Slots[next].Length = 0;
|
||||
MoveSlotToEnd(next);
|
||||
}
|
||||
|
||||
// if we just deallocate the last one, we need to move last back
|
||||
if (slot == m_LastSlot)
|
||||
{
|
||||
m_LastSlot = m_Slots[m_LastSlot].Prev;
|
||||
// if there's nothing allocated anymore, use 0
|
||||
if (m_LastSlot == k_NotSet)
|
||||
{
|
||||
m_LastSlot = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// mark the index as available
|
||||
m_IndexToSlot[index] = k_NotSet;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Take a slot at the end and link it to go just after "slot".
|
||||
// Used when allocating part of a slot and we need an entry for the rest
|
||||
// Returns the slot that was picked
|
||||
private int MoveSlotAfter(int slot)
|
||||
{
|
||||
int ret = m_Slots[m_MaxSlot - 1].Prev;
|
||||
int p0 = m_Slots[ret].Prev;
|
||||
|
||||
m_Slots[p0].Next = m_MaxSlot - 1;
|
||||
m_Slots[m_MaxSlot - 1].Prev = p0;
|
||||
|
||||
int p1 = m_Slots[slot].Next;
|
||||
m_Slots[slot].Next = ret;
|
||||
m_Slots[p1].Prev = ret;
|
||||
|
||||
m_Slots[ret].Prev = slot;
|
||||
m_Slots[ret].Next = p1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Move the slot "slot" to the end of the list.
|
||||
// Used when merging two slots, that gives us an extra entry at the end
|
||||
private void MoveSlotToEnd(int slot)
|
||||
{
|
||||
// if we're already there
|
||||
if (m_Slots[slot].Next == k_NotSet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int prev = m_Slots[slot].Prev;
|
||||
int next = m_Slots[slot].Next;
|
||||
|
||||
m_Slots[prev].Next = next;
|
||||
if (next != k_NotSet)
|
||||
{
|
||||
m_Slots[next].Prev = prev;
|
||||
}
|
||||
|
||||
int p0 = m_Slots[m_MaxSlot - 1].Prev;
|
||||
|
||||
m_Slots[p0].Next = slot;
|
||||
m_Slots[slot].Next = m_MaxSlot - 1;
|
||||
|
||||
m_Slots[m_MaxSlot - 1].Prev = slot;
|
||||
m_Slots[slot].Prev = p0;
|
||||
|
||||
m_Slots[slot].Pos = m_BufferSize;
|
||||
}
|
||||
|
||||
// runs a bunch of consistency check on the Allocator
|
||||
internal bool Verify()
|
||||
{
|
||||
int pos = k_NotSet;
|
||||
int count = 0;
|
||||
int total = 0;
|
||||
int endPos = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int prev = pos;
|
||||
if (pos != k_NotSet)
|
||||
{
|
||||
pos = m_Slots[pos].Next;
|
||||
if (pos == k_NotSet)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Prev != prev)
|
||||
{
|
||||
// the previous is not correct
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Length < 0)
|
||||
{
|
||||
// Length should be positive
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prev != k_NotSet && m_Slots[prev].Free && m_Slots[pos].Free && m_Slots[pos].Length > 0)
|
||||
{
|
||||
// should not have two consecutive free slots
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Slots[pos].Pos != total)
|
||||
{
|
||||
// slots should all line up nicely
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_Slots[pos].Free)
|
||||
{
|
||||
endPos = m_Slots[pos].Pos + m_Slots[pos].Length;
|
||||
}
|
||||
|
||||
total += m_Slots[pos].Length;
|
||||
count++;
|
||||
|
||||
} while (pos != k_NotSet);
|
||||
|
||||
if (count != m_MaxSlot)
|
||||
{
|
||||
// some slots were lost
|
||||
return false;
|
||||
}
|
||||
|
||||
if (total != m_BufferSize)
|
||||
{
|
||||
// total buffer should be accounted for
|
||||
return false;
|
||||
}
|
||||
|
||||
if (endPos != Range)
|
||||
{
|
||||
// end position should match reported end position
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Debug display the allocator structure
|
||||
internal void DebugDisplay()
|
||||
{
|
||||
string logMessage = "IndexAllocator structure\n";
|
||||
|
||||
bool[] seen = new bool[m_MaxSlot];
|
||||
|
||||
int pos = 0;
|
||||
int count = 0;
|
||||
bool prevEmpty = false;
|
||||
do
|
||||
{
|
||||
seen[pos] = true;
|
||||
count++;
|
||||
if (m_Slots[pos].Length == 0 && prevEmpty)
|
||||
{
|
||||
// don't display repetitive empty slots
|
||||
}
|
||||
else
|
||||
{
|
||||
logMessage += string.Format("{0}:{1}, {2} ({3}) \n", m_Slots[pos].Pos, m_Slots[pos].Length,
|
||||
m_Slots[pos].Free ? "Free" : "Used", pos);
|
||||
if (m_Slots[pos].Length == 0)
|
||||
{
|
||||
prevEmpty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
pos = m_Slots[pos].Next;
|
||||
} while (pos != k_NotSet && !seen[pos]);
|
||||
|
||||
logMessage += string.Format("{0} Total entries\n", count);
|
||||
|
||||
Debug.Log(logMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd9e1475e8c8e4a6d935fe2409e3bd26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -158,37 +158,57 @@ namespace Unity.Netcode
|
||||
// We check to see if we need to shortcut for the case where we are the host/server and we can send a clientRPC
|
||||
// to ourself. Sadly we have to figure that out from the list of clientIds :(
|
||||
bool shouldSendToHost = false;
|
||||
|
||||
if (clientRpcParams.Send.TargetClientIds != null)
|
||||
{
|
||||
foreach (var clientId in clientRpcParams.Send.TargetClientIds)
|
||||
foreach (var targetClientId in clientRpcParams.Send.TargetClientIds)
|
||||
{
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
if (targetClientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
shouldSendToHost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check to make sure we are sending to only observers, if not log an error.
|
||||
if (NetworkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId))
|
||||
{
|
||||
NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId));
|
||||
}
|
||||
}
|
||||
|
||||
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds);
|
||||
}
|
||||
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
|
||||
{
|
||||
foreach (var clientId in clientRpcParams.Send.TargetClientIdsNativeArray)
|
||||
foreach (var targetClientId in clientRpcParams.Send.TargetClientIdsNativeArray)
|
||||
{
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
if (targetClientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
shouldSendToHost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check to make sure we are sending to only observers, if not log an error.
|
||||
if (NetworkManager.LogLevel >= LogLevel.Error && !NetworkObject.Observers.Contains(targetClientId))
|
||||
{
|
||||
NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId));
|
||||
}
|
||||
}
|
||||
|
||||
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldSendToHost = IsHost;
|
||||
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ConnectedClientsIds);
|
||||
var observerEnumerator = NetworkObject.Observers.GetEnumerator();
|
||||
while (observerEnumerator.MoveNext())
|
||||
{
|
||||
// Skip over the host
|
||||
if (IsHost && observerEnumerator.Current == NetworkManager.LocalClientId)
|
||||
{
|
||||
shouldSendToHost = true;
|
||||
continue;
|
||||
}
|
||||
rpcWriteSize = NetworkManager.MessagingSystem.SendMessage(ref clientRpcMessage, networkDelivery, observerEnumerator.Current);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are a server/host then we just no op and send to ourself
|
||||
@@ -228,6 +248,12 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
internal string GenerateObserverErrorMessage(ClientRpcParams clientRpcParams, ulong targetClientId)
|
||||
{
|
||||
var containerNameHoldingId = clientRpcParams.Send.TargetClientIds != null ? nameof(ClientRpcParams.Send.TargetClientIds) : nameof(ClientRpcParams.Send.TargetClientIdsNativeArray);
|
||||
return $"Sending ClientRpc to non-observer! {containerNameHoldingId} contains clientId {targetClientId} that is not an observer!";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
@@ -235,42 +261,42 @@ namespace Unity.Netcode
|
||||
public NetworkManager NetworkManager => NetworkObject.NetworkManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is the the personal clients player object
|
||||
/// If a NetworkObject is assigned, it will return whether or not this NetworkObject
|
||||
/// is the local player object. If no NetworkObject is assigned it will always return false.
|
||||
/// </summary>
|
||||
public bool IsLocalPlayer => NetworkObject.IsLocalPlayer;
|
||||
public bool IsLocalPlayer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is owned by the local player or if the object is the local player object
|
||||
/// </summary>
|
||||
public bool IsOwner => NetworkObject.IsOwner;
|
||||
public bool IsOwner { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as server
|
||||
/// </summary>
|
||||
protected bool IsServer => IsRunning && NetworkManager.IsServer;
|
||||
protected bool IsServer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as client
|
||||
/// </summary>
|
||||
protected bool IsClient => IsRunning && NetworkManager.IsClient;
|
||||
protected bool IsClient { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as Host, I.E Server and Client
|
||||
/// </summary>
|
||||
protected bool IsHost => IsRunning && NetworkManager.IsHost;
|
||||
|
||||
private bool IsRunning => NetworkManager && NetworkManager.IsListening;
|
||||
protected bool IsHost { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not the object has a owner
|
||||
/// </summary>
|
||||
public bool IsOwnedByServer => NetworkObject.IsOwnedByServer;
|
||||
public bool IsOwnedByServer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine if it is safe to access NetworkObject and NetworkManager from within a NetworkBehaviour component
|
||||
/// Primarily useful when checking NetworkObject/NetworkManager properties within FixedUpate
|
||||
/// </summary>
|
||||
public bool IsSpawned => HasNetworkObject ? NetworkObject.IsSpawned : false;
|
||||
public bool IsSpawned { get; internal set; }
|
||||
|
||||
internal bool IsBehaviourEditable()
|
||||
{
|
||||
@@ -327,12 +353,12 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour
|
||||
/// </summary>
|
||||
public ulong NetworkObjectId => NetworkObject.NetworkObjectId;
|
||||
public ulong NetworkObjectId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject
|
||||
/// </summary>
|
||||
public ushort NetworkBehaviourId => NetworkObject.GetNetworkBehaviourOrderIndex(this);
|
||||
public ushort NetworkBehaviourId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Internally caches the Id of this behaviour in a NetworkObject. Makes look-up faster
|
||||
@@ -352,7 +378,47 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets the ClientId that owns the NetworkObject
|
||||
/// </summary>
|
||||
public ulong OwnerClientId => NetworkObject.OwnerClientId;
|
||||
public ulong OwnerClientId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates properties with network session related
|
||||
/// dependencies such as a NetworkObject's spawned
|
||||
/// state or NetworkManager's session state.
|
||||
/// </summary>
|
||||
internal void UpdateNetworkProperties()
|
||||
{
|
||||
// Set NetworkObject dependent properties
|
||||
if (NetworkObject != null)
|
||||
{
|
||||
// Set identification related properties
|
||||
NetworkObjectId = NetworkObject.NetworkObjectId;
|
||||
IsLocalPlayer = NetworkObject.IsLocalPlayer;
|
||||
|
||||
// This is "OK" because GetNetworkBehaviourOrderIndex uses the order of
|
||||
// NetworkObject.ChildNetworkBehaviours which is set once when first
|
||||
// accessed.
|
||||
NetworkBehaviourId = NetworkObject.GetNetworkBehaviourOrderIndex(this);
|
||||
|
||||
// Set ownership related properties
|
||||
IsOwnedByServer = NetworkObject.IsOwnedByServer;
|
||||
IsOwner = NetworkObject.IsOwner;
|
||||
OwnerClientId = NetworkObject.OwnerClientId;
|
||||
|
||||
// Set NetworkManager dependent properties
|
||||
if (NetworkManager != null)
|
||||
{
|
||||
IsHost = NetworkManager.IsListening && NetworkManager.IsHost;
|
||||
IsClient = NetworkManager.IsListening && NetworkManager.IsClient;
|
||||
IsServer = NetworkManager.IsListening && NetworkManager.IsServer;
|
||||
}
|
||||
}
|
||||
else // Shouldn't happen, but if so then set the properties to their default value;
|
||||
{
|
||||
OwnerClientId = NetworkObjectId = default;
|
||||
IsOwnedByServer = IsOwner = IsHost = IsClient = IsServer = default;
|
||||
NetworkBehaviourId = default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup.
|
||||
@@ -366,21 +432,41 @@ namespace Unity.Netcode
|
||||
|
||||
internal void InternalOnNetworkSpawn()
|
||||
{
|
||||
IsSpawned = true;
|
||||
InitializeVariables();
|
||||
UpdateNetworkProperties();
|
||||
OnNetworkSpawn();
|
||||
}
|
||||
|
||||
internal void InternalOnNetworkDespawn() { }
|
||||
internal void InternalOnNetworkDespawn()
|
||||
{
|
||||
IsSpawned = false;
|
||||
UpdateNetworkProperties();
|
||||
OnNetworkDespawn();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the local client gains ownership of this object
|
||||
/// </summary>
|
||||
public virtual void OnGainedOwnership() { }
|
||||
|
||||
internal void InternalOnGainedOwnership()
|
||||
{
|
||||
UpdateNetworkProperties();
|
||||
OnGainedOwnership();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when we loose ownership of this object
|
||||
/// </summary>
|
||||
public virtual void OnLostOwnership() { }
|
||||
|
||||
internal void InternalOnLostOwnership()
|
||||
{
|
||||
UpdateNetworkProperties();
|
||||
OnLostOwnership();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed
|
||||
/// </summary>
|
||||
@@ -433,12 +519,10 @@ namespace Unity.Netcode
|
||||
|
||||
m_VarInit = true;
|
||||
|
||||
FieldInfo[] sortedFields = GetFieldInfoForType(GetType());
|
||||
|
||||
var sortedFields = GetFieldInfoForType(GetType());
|
||||
for (int i = 0; i < sortedFields.Length; i++)
|
||||
{
|
||||
Type fieldType = sortedFields[i].FieldType;
|
||||
|
||||
var fieldType = sortedFields[i].FieldType;
|
||||
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
|
||||
{
|
||||
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this);
|
||||
@@ -499,7 +583,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal void VariableUpdate(ulong clientId)
|
||||
internal void VariableUpdate(ulong targetClientId)
|
||||
{
|
||||
if (!m_VarInit)
|
||||
{
|
||||
@@ -507,67 +591,58 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
PreNetworkVariableWrite();
|
||||
NetworkVariableUpdate(clientId, NetworkBehaviourId);
|
||||
NetworkVariableUpdate(targetClientId, NetworkBehaviourId);
|
||||
}
|
||||
|
||||
internal readonly List<int> NetworkVariableIndexesToReset = new List<int>();
|
||||
internal readonly HashSet<int> NetworkVariableIndexesToResetSet = new HashSet<int>();
|
||||
|
||||
private void NetworkVariableUpdate(ulong clientId, int behaviourIndex)
|
||||
private void NetworkVariableUpdate(ulong targetClientId, int behaviourIndex)
|
||||
{
|
||||
if (!CouldHaveDirtyNetworkVariables())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotDelta)
|
||||
for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
|
||||
{
|
||||
var shouldSend = false;
|
||||
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
||||
{
|
||||
NetworkManager.SnapshotSystem.Store(NetworkObjectId, behaviourIndex, k, NetworkVariableFields[k]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!NetworkManager.NetworkConfig.UseSnapshotDelta)
|
||||
{
|
||||
for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
|
||||
{
|
||||
var shouldSend = false;
|
||||
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
||||
var networkVariable = NetworkVariableFields[k];
|
||||
if (networkVariable.IsDirty() && networkVariable.CanClientRead(targetClientId))
|
||||
{
|
||||
if (NetworkVariableFields[k].ShouldWrite(clientId, IsServer))
|
||||
shouldSend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSend)
|
||||
{
|
||||
var message = new NetworkVariableDeltaMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
|
||||
NetworkBehaviour = this,
|
||||
TargetClientId = targetClientId,
|
||||
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
|
||||
};
|
||||
// TODO: Serialization is where the IsDirty flag gets changed.
|
||||
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
|
||||
// we still have to actually serialize the message even though we're not sending it, otherwise
|
||||
// the dirty flag doesn't change properly. These two pieces should be decoupled at some point
|
||||
// so we don't have to do this serialization work if we're not going to use the result.
|
||||
if (IsServer && targetClientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
|
||||
using (tmpWriter)
|
||||
{
|
||||
shouldSend = true;
|
||||
message.Serialize(tmpWriter);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSend)
|
||||
else
|
||||
{
|
||||
var message = new NetworkVariableDeltaMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId,
|
||||
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
|
||||
NetworkBehaviour = this,
|
||||
ClientId = clientId,
|
||||
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
|
||||
};
|
||||
// TODO: Serialization is where the IsDirty flag gets changed.
|
||||
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
|
||||
// we still have to actually serialize the message even though we're not sending it, otherwise
|
||||
// the dirty flag doesn't change properly. These two pieces should be decoupled at some point
|
||||
// so we don't have to do this serialization work if we're not going to use the result.
|
||||
if (IsServer && clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
|
||||
using (tmpWriter)
|
||||
{
|
||||
message.Serialize(tmpWriter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], clientId);
|
||||
}
|
||||
NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], targetClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -595,7 +670,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong clientId)
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
|
||||
{
|
||||
if (NetworkVariableFields.Count == 0)
|
||||
{
|
||||
@@ -604,7 +679,7 @@ namespace Unity.Netcode
|
||||
|
||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||
{
|
||||
bool canClientRead = NetworkVariableFields[j].CanClientRead(clientId);
|
||||
bool canClientRead = NetworkVariableFields[j].CanClientRead(targetClientId);
|
||||
|
||||
if (canClientRead)
|
||||
{
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].VariableUpdate(networkManager.ServerClientId);
|
||||
sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.ServerClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Unity.Multiplayer.Tools;
|
||||
#endif
|
||||
using Unity.Profiling;
|
||||
using UnityEngine.SceneManagement;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace Unity.Netcode
|
||||
@@ -45,7 +46,7 @@ namespace Unity.Netcode
|
||||
private static ProfilerMarker s_TransportDisconnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportDisconnect");
|
||||
#endif
|
||||
|
||||
private const double k_TimeSyncFrequency = 1.0d; // sync every second, TODO will be removed once timesync is done via snapshots
|
||||
private const double k_TimeSyncFrequency = 1.0d; // sync every second
|
||||
private const float k_DefaultBufferSizeSec = 0.05f; // todo talk with UX/Product, find good default value for this
|
||||
|
||||
internal static string PrefabDebugHelper(NetworkPrefab networkPrefab)
|
||||
@@ -53,7 +54,6 @@ namespace Unity.Netcode
|
||||
return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.gameObject.name}\"";
|
||||
}
|
||||
|
||||
internal SnapshotSystem SnapshotSystem { get; private set; }
|
||||
internal NetworkBehaviourUpdater BehaviourUpdater { get; private set; }
|
||||
|
||||
internal MessagingSystem MessagingSystem { get; private set; }
|
||||
@@ -125,13 +125,11 @@ namespace Unity.Netcode
|
||||
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
|
||||
{
|
||||
if (m_NetworkManager.PendingClients.TryGetValue(senderId, out PendingClient client) &&
|
||||
(client.ConnectionState == PendingClient.State.PendingApproval ||
|
||||
(client.ConnectionState == PendingClient.State.PendingConnection &&
|
||||
messageType != typeof(ConnectionRequestMessage))))
|
||||
(client.ConnectionState == PendingClient.State.PendingApproval || (client.ConnectionState == PendingClient.State.PendingConnection && messageType != typeof(ConnectionRequestMessage))))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId.ToString()} before it has been accepted");
|
||||
NetworkLog.LogWarning($"Message received from {nameof(senderId)}={senderId} before it has been accepted");
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -229,14 +227,12 @@ namespace Unity.Netcode
|
||||
|
||||
public NetworkSceneManager SceneManager { get; private set; }
|
||||
|
||||
public readonly ulong ServerClientId = 0;
|
||||
public const ulong ServerClientId = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the networkId of the server
|
||||
/// </summary>
|
||||
private ulong m_ServerTransportId => NetworkConfig.NetworkTransport?.ServerClientId ??
|
||||
throw new NullReferenceException(
|
||||
$"The transport in the active {nameof(NetworkConfig)} is null");
|
||||
private ulong m_ServerTransportId => NetworkConfig.NetworkTransport?.ServerClientId ?? throw new NullReferenceException($"The transport in the active {nameof(NetworkConfig)} is null");
|
||||
|
||||
/// <summary>
|
||||
/// Returns ServerClientId if IsServer or LocalClientId if not
|
||||
@@ -367,16 +363,14 @@ namespace Unity.Netcode
|
||||
/// <param name="approved">Whether or not the client was approved</param>
|
||||
/// <param name="position">The position to spawn the client at. If null, the prefab position is used.</param>
|
||||
/// <param name="rotation">The rotation to spawn the client with. If null, the prefab position is used.</param>
|
||||
public delegate void ConnectionApprovedDelegate(bool createPlayerObject, uint? playerPrefabHash, bool approved,
|
||||
Vector3? position, Quaternion? rotation);
|
||||
public delegate void ConnectionApprovedDelegate(bool createPlayerObject, uint? playerPrefabHash, bool approved, Vector3? position, Quaternion? rotation);
|
||||
|
||||
/// <summary>
|
||||
/// The callback to invoke during connection approval
|
||||
/// </summary>
|
||||
public event Action<byte[], ulong, ConnectionApprovedDelegate> ConnectionApprovalCallback = null;
|
||||
|
||||
internal void InvokeConnectionApproval(byte[] payload, ulong clientId, ConnectionApprovedDelegate action) =>
|
||||
ConnectionApprovalCallback?.Invoke(payload, clientId, action);
|
||||
internal void InvokeConnectionApproval(byte[] payload, ulong clientId, ConnectionApprovedDelegate action) => ConnectionApprovalCallback?.Invoke(payload, clientId, action);
|
||||
|
||||
/// <summary>
|
||||
/// The current NetworkConfig
|
||||
@@ -565,13 +559,6 @@ namespace Unity.Netcode
|
||||
|
||||
NetworkConfig.NetworkTransport.NetworkMetrics = NetworkMetrics;
|
||||
|
||||
//This 'if' should never enter
|
||||
if (SnapshotSystem != null)
|
||||
{
|
||||
SnapshotSystem.Dispose();
|
||||
SnapshotSystem = null;
|
||||
}
|
||||
|
||||
if (server)
|
||||
{
|
||||
NetworkTimeSystem = NetworkTimeSystem.ServerTimeSystem();
|
||||
@@ -584,8 +571,6 @@ 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
|
||||
@@ -800,44 +785,35 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo("StartServer()");
|
||||
NetworkLog.LogInfo(nameof(StartServer));
|
||||
}
|
||||
|
||||
if (IsServer || IsClient)
|
||||
if (!CanStart(StartType.Server))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning("Cannot start server while an instance is already running");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NetworkConfig.ConnectionApproval)
|
||||
{
|
||||
if (ConnectionApprovalCallback == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning(
|
||||
"No ConnectionApproval callback defined. Connection approval will timeout");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Initialize(true);
|
||||
|
||||
var result = NetworkConfig.NetworkTransport.StartServer();
|
||||
// If we failed to start then shutdown and notify user that the transport failed to start
|
||||
if (NetworkConfig.NetworkTransport.StartServer())
|
||||
{
|
||||
IsServer = true;
|
||||
IsClient = false;
|
||||
IsListening = true;
|
||||
|
||||
IsServer = true;
|
||||
IsClient = false;
|
||||
IsListening = true;
|
||||
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||
|
||||
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||
OnServerStarted?.Invoke();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
OnServerStarted?.Invoke();
|
||||
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -850,26 +826,26 @@ namespace Unity.Netcode
|
||||
NetworkLog.LogInfo(nameof(StartClient));
|
||||
}
|
||||
|
||||
if (IsServer || IsClient)
|
||||
if (!CanStart(StartType.Client))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning("Cannot start client while an instance is already running");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Initialize(false);
|
||||
MessagingSystem.ClientConnected(ServerClientId);
|
||||
|
||||
var result = NetworkConfig.NetworkTransport.StartClient();
|
||||
if (!NetworkConfig.NetworkTransport.StartClient())
|
||||
{
|
||||
Debug.LogError($"Client is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
IsServer = false;
|
||||
IsClient = true;
|
||||
IsListening = true;
|
||||
|
||||
return result;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -882,31 +858,21 @@ namespace Unity.Netcode
|
||||
NetworkLog.LogInfo(nameof(StartHost));
|
||||
}
|
||||
|
||||
if (IsServer || IsClient)
|
||||
if (!CanStart(StartType.Host))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning("Cannot start host while an instance is already running");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NetworkConfig.ConnectionApproval)
|
||||
{
|
||||
if (ConnectionApprovalCallback == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning(
|
||||
"No ConnectionApproval callback defined. Connection approval will timeout");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Initialize(true);
|
||||
|
||||
var result = NetworkConfig.NetworkTransport.StartServer();
|
||||
// If we failed to start then shutdown and notify user that the transport failed to start
|
||||
if (!NetworkConfig.NetworkTransport.StartServer())
|
||||
{
|
||||
Debug.LogError($"Server is shutting down due to network transport start failure of {NetworkConfig.NetworkTransport.GetType().Name}!");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
MessagingSystem.ClientConnected(ServerClientId);
|
||||
LocalClientId = ServerClientId;
|
||||
NetworkMetrics.SetConnectionId(LocalClientId);
|
||||
@@ -942,7 +908,53 @@ namespace Unity.Netcode
|
||||
|
||||
OnServerStarted?.Invoke();
|
||||
|
||||
return result;
|
||||
return true;
|
||||
}
|
||||
|
||||
private enum StartType
|
||||
{
|
||||
Server,
|
||||
Host,
|
||||
Client
|
||||
}
|
||||
|
||||
private bool CanStart(StartType type)
|
||||
{
|
||||
if (IsListening)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning("Cannot start " + type + " while an instance is already running");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NetworkConfig.ConnectionApproval)
|
||||
{
|
||||
if (ConnectionApprovalCallback == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning(
|
||||
"No ConnectionApproval callback defined. Connection approval will timeout");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ConnectionApprovalCallback != null)
|
||||
{
|
||||
if (!NetworkConfig.ConnectionApproval)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning(
|
||||
"A ConnectionApproval callback is defined but ConnectionApproval is disabled. In order to use ConnectionApproval it has to be explicitly enabled ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetSingleton()
|
||||
@@ -1014,6 +1026,7 @@ namespace Unity.Netcode
|
||||
internal interface INetworkManagerHelper
|
||||
{
|
||||
bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false);
|
||||
void CheckAndNotifyUserNetworkObjectRemoved(NetworkManager networkManager, bool editorTest = false);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1133,12 +1146,6 @@ namespace Unity.Netcode
|
||||
|
||||
this.UnregisterAllNetworkUpdates();
|
||||
|
||||
if (SnapshotSystem != null)
|
||||
{
|
||||
SnapshotSystem.Dispose();
|
||||
SnapshotSystem = null;
|
||||
}
|
||||
|
||||
if (NetworkTickSystem != null)
|
||||
{
|
||||
NetworkTickSystem.Tick -= OnNetworkManagerTick;
|
||||
@@ -1274,7 +1281,11 @@ namespace Unity.Netcode
|
||||
if (!m_ShuttingDown || !m_StopProcessingMessages)
|
||||
{
|
||||
MessagingSystem.ProcessSendQueues();
|
||||
NetworkMetrics.UpdateNetworkObjectsCount(SpawnManager.SpawnedObjects.Count);
|
||||
NetworkMetrics.UpdateConnectionsCount((IsServer) ? ConnectedClients.Count : 1);
|
||||
NetworkMetrics.DispatchFrame();
|
||||
|
||||
NetworkObject.VerifyParentingStatus();
|
||||
}
|
||||
SpawnManager.CleanupStaleTriggers();
|
||||
|
||||
@@ -1288,7 +1299,6 @@ namespace Unity.Netcode
|
||||
/// This function runs once whenever the local tick is incremented and is responsible for the following (in order):
|
||||
/// - collect commands/inputs and send them to the server (TBD)
|
||||
/// - call NetworkFixedUpdate on all NetworkBehaviours in prediction/client authority mode
|
||||
/// - create a snapshot from resulting state
|
||||
/// </summary>
|
||||
private void OnNetworkManagerTick()
|
||||
{
|
||||
@@ -1419,18 +1429,15 @@ namespace Unity.Netcode
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_TransportDisconnect.Begin();
|
||||
#endif
|
||||
clientId = TransportIdToClientId(clientId);
|
||||
|
||||
OnClientDisconnectCallback?.Invoke(clientId);
|
||||
|
||||
m_TransportIdToClientIdMap.Remove(transportId);
|
||||
m_ClientIdToTransportIdMap.Remove(clientId);
|
||||
clientId = TransportIdCleanUp(clientId, transportId);
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo($"Disconnect Event From {clientId}");
|
||||
}
|
||||
|
||||
OnClientDisconnectCallback?.Invoke(clientId);
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
OnClientDisconnectFromServer(clientId);
|
||||
@@ -1446,6 +1453,31 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles cleaning up the transport id/client id tables after
|
||||
/// receiving a disconnect event from transport
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ulong TransportIdCleanUp(ulong clientId, ulong transportId)
|
||||
{
|
||||
// This check is for clients that attempted to connect but failed.
|
||||
// When this happens, the client will not have an entry within the
|
||||
// m_TransportIdToClientIdMap or m_ClientIdToTransportIdMap lookup
|
||||
// tables so we exit early and just return 0 to be used for the
|
||||
// disconnect event.
|
||||
if (!IsServer && !m_TransportIdToClientIdMap.ContainsKey(clientId))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
clientId = TransportIdToClientId(clientId);
|
||||
|
||||
m_TransportIdToClientIdMap.Remove(transportId);
|
||||
m_ClientIdToTransportIdMap.Remove(clientId);
|
||||
|
||||
return clientId;
|
||||
}
|
||||
|
||||
internal unsafe int SendMessage<TMessageType, TClientIdListType>(ref TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds)
|
||||
where TMessageType : INetworkMessage
|
||||
where TClientIdListType : IReadOnlyList<ulong>
|
||||
@@ -1591,32 +1623,45 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = networkClient.OwnedObjects.Count - 1; i >= 0; i--)
|
||||
// Get the NetworkObjects owned by the disconnected client
|
||||
var clientOwnedObjects = SpawnManager.GetClientOwnedObjects(clientId);
|
||||
if (clientOwnedObjects == null)
|
||||
{
|
||||
var ownedObject = networkClient.OwnedObjects[i];
|
||||
if (ownedObject != null)
|
||||
// This could happen if a client is never assigned a player object and is disconnected
|
||||
// Only log this in verbose/developer mode
|
||||
if (LogLevel == LogLevel.Developer)
|
||||
{
|
||||
if (!ownedObject.DontDestroyWithOwner)
|
||||
NetworkLog.LogWarning($"ClientID {clientId} disconnected with (0) zero owned objects! Was a player prefab not assigned?");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle changing ownership and prefab handlers
|
||||
for (int i = clientOwnedObjects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var ownedObject = clientOwnedObjects[i];
|
||||
if (ownedObject != null)
|
||||
{
|
||||
if (PrefabHandler.ContainsHandler(ConnectedClients[clientId].OwnedObjects[i]
|
||||
.GlobalObjectIdHash))
|
||||
if (!ownedObject.DontDestroyWithOwner)
|
||||
{
|
||||
PrefabHandler.HandleNetworkPrefabDestroy(ConnectedClients[clientId].OwnedObjects[i]);
|
||||
if (PrefabHandler.ContainsHandler(clientOwnedObjects[i].GlobalObjectIdHash))
|
||||
{
|
||||
PrefabHandler.HandleNetworkPrefabDestroy(clientOwnedObjects[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(ownedObject.gameObject);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(ownedObject.gameObject);
|
||||
ownedObject.RemoveOwnership();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ownedObject.RemoveOwnership();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Could(should?) be replaced with more memory per client, by storing the visibility
|
||||
|
||||
foreach (var sobj in SpawnManager.SpawnedObjectsList)
|
||||
{
|
||||
sobj.Observers.Remove(clientId);
|
||||
@@ -1764,7 +1809,7 @@ namespace Unity.Netcode
|
||||
|
||||
var message = new CreateObjectMessage
|
||||
{
|
||||
ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key, false)
|
||||
ObjectInfo = ConnectedClients[clientId].PlayerObject.GetMessageSceneObject(clientPair.Key)
|
||||
};
|
||||
message.ObjectInfo.Header.Hash = playerPrefabHash;
|
||||
message.ObjectInfo.Header.IsSceneObject = false;
|
||||
|
||||
@@ -54,8 +54,6 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
internal NetworkManager NetworkManagerOwner;
|
||||
|
||||
private ulong m_NetworkObjectId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique Id of this object that is synced across the network
|
||||
/// </summary>
|
||||
@@ -64,33 +62,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Gets the ClientId of the owner of this NetworkObject
|
||||
/// </summary>
|
||||
public ulong OwnerClientId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OwnerClientIdInternal == null)
|
||||
{
|
||||
return NetworkManager != null ? NetworkManager.ServerClientId : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OwnerClientIdInternal.Value;
|
||||
}
|
||||
}
|
||||
internal set
|
||||
{
|
||||
if (NetworkManager != null && value == NetworkManager.ServerClientId)
|
||||
{
|
||||
OwnerClientIdInternal = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
OwnerClientIdInternal = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal ulong? OwnerClientIdInternal = null;
|
||||
public ulong OwnerClientId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, the object will always be replicated as root on clients and the parent will be ignored.
|
||||
@@ -234,11 +206,6 @@ namespace Unity.Netcode
|
||||
throw new VisibilityChangeException("The object is already visible");
|
||||
}
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
||||
{
|
||||
SnapshotSpawn(clientId);
|
||||
}
|
||||
|
||||
Observers.Add(clientId);
|
||||
|
||||
NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this);
|
||||
@@ -314,23 +281,15 @@ namespace Unity.Netcode
|
||||
throw new VisibilityChangeException("Cannot hide an object from the server");
|
||||
}
|
||||
|
||||
|
||||
Observers.Remove(clientId);
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
SnapshotDespawn(clientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = NetworkObjectId
|
||||
};
|
||||
// Send destroy call
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
|
||||
}
|
||||
NetworkObjectId = NetworkObjectId
|
||||
};
|
||||
// Send destroy call
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -345,14 +304,14 @@ namespace Unity.Netcode
|
||||
throw new ArgumentNullException("At least one " + nameof(NetworkObject) + " has to be provided");
|
||||
}
|
||||
|
||||
NetworkManager networkManager = networkObjects[0].NetworkManager;
|
||||
var networkManager = networkObjects[0].NetworkManager;
|
||||
|
||||
if (!networkManager.IsServer)
|
||||
{
|
||||
throw new NotServerException("Only server can change visibility");
|
||||
}
|
||||
|
||||
if (clientId == networkManager.ServerClientId)
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
throw new VisibilityChangeException("Cannot hide an object from the server");
|
||||
}
|
||||
@@ -384,84 +343,21 @@ namespace Unity.Netcode
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned
|
||||
&& (IsSceneObject == null || (IsSceneObject != null && IsSceneObject.Value != true)))
|
||||
if (NetworkManager != null && NetworkManager.IsListening && NetworkManager.IsServer == false && IsSpawned &&
|
||||
(IsSceneObject == null || (IsSceneObject != null && IsSceneObject.Value != true)))
|
||||
{
|
||||
throw new NotServerException($"Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead.");
|
||||
}
|
||||
|
||||
if (NetworkManager != null && NetworkManager.SpawnManager != null && NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
if (NetworkManager != null && NetworkManager.SpawnManager != null &&
|
||||
NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
{
|
||||
NetworkManager.SpawnManager.OnDespawnObject(networkObject, false);
|
||||
}
|
||||
}
|
||||
|
||||
private SnapshotDespawnCommand GetDespawnCommand()
|
||||
{
|
||||
var command = new SnapshotDespawnCommand();
|
||||
command.NetworkObjectId = NetworkObjectId;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private SnapshotSpawnCommand GetSpawnCommand()
|
||||
{
|
||||
var command = new SnapshotSpawnCommand();
|
||||
command.NetworkObjectId = NetworkObjectId;
|
||||
command.OwnerClientId = OwnerClientId;
|
||||
command.IsPlayerObject = IsPlayerObject;
|
||||
command.IsSceneObject = (IsSceneObject == null) || IsSceneObject.Value;
|
||||
|
||||
ulong? parent = NetworkManager.SpawnManager.GetSpawnParentId(this);
|
||||
if (parent != null)
|
||||
{
|
||||
command.ParentNetworkId = parent.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// write own network id, when no parents. todo: optimize this.
|
||||
command.ParentNetworkId = command.NetworkObjectId;
|
||||
}
|
||||
|
||||
command.GlobalObjectIdHash = HostCheckForGlobalObjectIdHashOverride();
|
||||
// todo: check if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(clientId)) for any clientId
|
||||
command.ObjectPosition = transform.position;
|
||||
command.ObjectRotation = transform.rotation;
|
||||
command.ObjectScale = transform.localScale;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private void SnapshotSpawn()
|
||||
{
|
||||
var command = GetSpawnCommand();
|
||||
NetworkManager.SnapshotSystem.Spawn(command);
|
||||
}
|
||||
|
||||
private void SnapshotSpawn(ulong clientId)
|
||||
{
|
||||
var command = GetSpawnCommand();
|
||||
command.TargetClientIds = new List<ulong>();
|
||||
command.TargetClientIds.Add(clientId);
|
||||
NetworkManager.SnapshotSystem.Spawn(command);
|
||||
}
|
||||
|
||||
internal void SnapshotDespawn()
|
||||
{
|
||||
var command = GetDespawnCommand();
|
||||
NetworkManager.SnapshotSystem.Despawn(command);
|
||||
}
|
||||
|
||||
internal void SnapshotDespawn(ulong clientId)
|
||||
{
|
||||
var command = GetDespawnCommand();
|
||||
command.TargetClientIds = new List<ulong>();
|
||||
command.TargetClientIds.Add(clientId);
|
||||
NetworkManager.SnapshotSystem.Despawn(command);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SpawnInternal(bool destroyWithScene, ulong? ownerClientId, bool playerObject)
|
||||
private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject)
|
||||
{
|
||||
if (!NetworkManager.IsListening)
|
||||
{
|
||||
@@ -475,12 +371,6 @@ namespace Unity.Netcode
|
||||
|
||||
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(this, NetworkManager.SpawnManager.GetNetworkObjectId(), false, playerObject, ownerClientId, destroyWithScene);
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
||||
{
|
||||
SnapshotSpawn();
|
||||
}
|
||||
|
||||
ulong ownerId = ownerClientId != null ? ownerClientId.Value : NetworkManager.ServerClientId;
|
||||
for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
if (Observers.Contains(NetworkManager.ConnectedClientsList[i].ClientId))
|
||||
@@ -496,7 +386,7 @@ namespace Unity.Netcode
|
||||
/// <param name="destroyWithScene">Should the object be destroyed when the scene is changed</param>
|
||||
public void Spawn(bool destroyWithScene = false)
|
||||
{
|
||||
SpawnInternal(destroyWithScene, null, false);
|
||||
SpawnInternal(destroyWithScene, NetworkManager.ServerClientId, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -547,17 +437,29 @@ namespace Unity.Netcode
|
||||
|
||||
internal void InvokeBehaviourOnLostOwnership()
|
||||
{
|
||||
// Server already handles this earlier, hosts should ignore, all clients should update
|
||||
if (!NetworkManager.IsServer)
|
||||
{
|
||||
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].OnLostOwnership();
|
||||
ChildNetworkBehaviours[i].InternalOnLostOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourOnGainedOwnership()
|
||||
{
|
||||
// Server already handles this earlier, hosts should ignore and only client owners should update
|
||||
if (!NetworkManager.IsServer && NetworkManager.LocalClientId == OwnerClientId)
|
||||
{
|
||||
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].OnGainedOwnership();
|
||||
ChildNetworkBehaviours[i].InternalOnGainedOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,13 +658,7 @@ namespace Unity.Netcode
|
||||
|
||||
if (!NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value))
|
||||
{
|
||||
if (OrphanChildren.Add(this))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkObject)} ({name}) cannot find its parent, added to {nameof(OrphanChildren)} set");
|
||||
}
|
||||
}
|
||||
OrphanChildren.Add(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -793,19 +689,21 @@ namespace Unity.Netcode
|
||||
|
||||
internal void InvokeBehaviourNetworkSpawn()
|
||||
{
|
||||
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId);
|
||||
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalOnNetworkSpawn();
|
||||
ChildNetworkBehaviours[i].OnNetworkSpawn();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourNetworkDespawn()
|
||||
{
|
||||
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true);
|
||||
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalOnNetworkDespawn();
|
||||
ChildNetworkBehaviours[i].OnNetworkDespawn();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -834,13 +732,13 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong clientId)
|
||||
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong targetClientId)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
var behavior = ChildNetworkBehaviours[i];
|
||||
behavior.InitializeVariables();
|
||||
behavior.WriteNetworkVariableData(writer, clientId);
|
||||
behavior.WriteNetworkVariableData(writer, targetClientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -853,6 +751,27 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
// NGO currently guarantees that the client will receive spawn data for all objects in one network tick.
|
||||
// Children may arrive before their parents; when they do they are stored in OrphanedChildren and then
|
||||
// resolved when their parents arrived. Because we don't send a partial list of spawns (yet), something
|
||||
// has gone wrong if by the end of an update we still have unresolved orphans
|
||||
//
|
||||
|
||||
// if and when we have different systems for where it is expected that orphans survive across ticks,
|
||||
// then this warning will remind us that we need to revamp the system because then we can no longer simply
|
||||
// spawn the orphan without its parent (at least, not when its transform is set to local coords mode)
|
||||
// - because then you’ll have children popping at the wrong location not having their parent’s global position to root them
|
||||
// - and then they’ll pop to the correct location after they get the parent, and that would be not good
|
||||
internal static void VerifyParentingStatus()
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
if (OrphanChildren.Count > 0)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkObject)} ({OrphanChildren.Count}) children not resolved to parents by the end of frame");
|
||||
}
|
||||
}
|
||||
}
|
||||
internal void SetNetworkVariableData(FastBufferReader reader)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
@@ -918,7 +837,6 @@ namespace Unity.Netcode
|
||||
public bool IsSceneObject;
|
||||
public bool HasTransform;
|
||||
public bool IsReparented;
|
||||
public bool HasNetworkVariables;
|
||||
}
|
||||
|
||||
public HeaderData Header;
|
||||
@@ -979,10 +897,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
if (Header.HasNetworkVariables)
|
||||
{
|
||||
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
|
||||
}
|
||||
OwnerObject.WriteNetworkVariableData(writer, TargetClientId);
|
||||
}
|
||||
|
||||
public unsafe void Deserialize(FastBufferReader reader)
|
||||
@@ -1022,7 +937,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
internal SceneObject GetMessageSceneObject(ulong targetClientId, bool includeNetworkVariableData = true)
|
||||
internal SceneObject GetMessageSceneObject(ulong targetClientId)
|
||||
{
|
||||
var obj = new SceneObject
|
||||
{
|
||||
@@ -1033,7 +948,6 @@ namespace Unity.Netcode
|
||||
OwnerClientId = OwnerClientId,
|
||||
IsSceneObject = IsSceneObject ?? true,
|
||||
Hash = HostCheckForGlobalObjectIdHashOverride(),
|
||||
HasNetworkVariables = includeNetworkVariableData
|
||||
},
|
||||
OwnerObject = this,
|
||||
TargetClientId = targetClientId
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal class ConnectionRtt
|
||||
{
|
||||
private double[] m_RttSendTimes; // times at which packet were sent for RTT computations
|
||||
private int[] m_SendSequence; // tick, or other key, at which packets were sent (to allow matching)
|
||||
private double[] m_MeasuredLatencies; // measured latencies (ring buffer)
|
||||
private int m_LatenciesBegin = 0; // ring buffer begin
|
||||
private int m_LatenciesEnd = 0; // ring buffer end
|
||||
|
||||
/// <summary>
|
||||
/// Round-trip-time data
|
||||
/// </summary>
|
||||
public struct Rtt
|
||||
{
|
||||
public double BestSec; // best RTT
|
||||
public double AverageSec; // average RTT
|
||||
public double WorstSec; // worst RTT
|
||||
public double LastSec; // latest ack'ed RTT
|
||||
public int SampleCount; // number of contributing samples
|
||||
}
|
||||
|
||||
public ConnectionRtt()
|
||||
{
|
||||
m_RttSendTimes = new double[NetworkConfig.RttWindowSize];
|
||||
m_SendSequence = new int[NetworkConfig.RttWindowSize];
|
||||
m_MeasuredLatencies = new double[NetworkConfig.RttWindowSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Round-trip-time computation for this client
|
||||
/// </summary>
|
||||
public Rtt GetRtt()
|
||||
{
|
||||
var ret = new Rtt();
|
||||
var index = m_LatenciesBegin;
|
||||
double total = 0.0;
|
||||
ret.BestSec = m_MeasuredLatencies[m_LatenciesBegin];
|
||||
ret.WorstSec = m_MeasuredLatencies[m_LatenciesBegin];
|
||||
|
||||
while (index != m_LatenciesEnd)
|
||||
{
|
||||
total += m_MeasuredLatencies[index];
|
||||
ret.SampleCount++;
|
||||
ret.BestSec = Math.Min(ret.BestSec, m_MeasuredLatencies[index]);
|
||||
ret.WorstSec = Math.Max(ret.WorstSec, m_MeasuredLatencies[index]);
|
||||
index = (index + 1) % NetworkConfig.RttAverageSamples;
|
||||
}
|
||||
|
||||
if (ret.SampleCount != 0)
|
||||
{
|
||||
ret.AverageSec = total / ret.SampleCount;
|
||||
// the latest RTT is one before m_LatenciesEnd
|
||||
ret.LastSec = m_MeasuredLatencies[(m_LatenciesEnd + (NetworkConfig.RttWindowSize - 1)) % NetworkConfig.RttWindowSize];
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.AverageSec = 0;
|
||||
ret.BestSec = 0;
|
||||
ret.WorstSec = 0;
|
||||
ret.SampleCount = 0;
|
||||
ret.LastSec = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal void NotifySend(int sequence, double timeSec)
|
||||
{
|
||||
m_RttSendTimes[sequence % NetworkConfig.RttWindowSize] = timeSec;
|
||||
m_SendSequence[sequence % NetworkConfig.RttWindowSize] = sequence;
|
||||
}
|
||||
|
||||
internal void NotifyAck(int sequence, double timeSec)
|
||||
{
|
||||
// if the same slot was not used by a later send
|
||||
if (m_SendSequence[sequence % NetworkConfig.RttWindowSize] == sequence)
|
||||
{
|
||||
double latency = timeSec - m_RttSendTimes[sequence % NetworkConfig.RttWindowSize];
|
||||
|
||||
m_MeasuredLatencies[m_LatenciesEnd] = latency;
|
||||
m_LatenciesEnd = (m_LatenciesEnd + 1) % NetworkConfig.RttAverageSamples;
|
||||
|
||||
if (m_LatenciesEnd == m_LatenciesBegin)
|
||||
{
|
||||
m_LatenciesBegin = (m_LatenciesBegin + 1) % NetworkConfig.RttAverageSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69c3c1c5a885d4aed99ee2e1fa40f763
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c275febadb27c4d18b41218e3353b84b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user