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:
@@ -11,4 +11,3 @@ using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")]
|
||||
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]
|
||||
|
||||
|
||||
@@ -138,19 +138,6 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public bool EnableNetworkLogs = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable Snapshot System for variable updates. Not supported in this version.
|
||||
/// </summary>
|
||||
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; internal set; } = false;
|
||||
/// <summary>
|
||||
/// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
|
||||
/// </summary>
|
||||
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)
|
||||
/// <summary>
|
||||
|
||||
@@ -20,6 +20,17 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// The NetworkObject's owned by this Client
|
||||
/// </summary>
|
||||
public readonly List<NetworkObject> OwnedObjects = new List<NetworkObject>();
|
||||
public List<NetworkObject> OwnedObjects
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PlayerObject != null && PlayerObject.NetworkManager != null && PlayerObject.NetworkManager.IsListening)
|
||||
{
|
||||
return PlayerObject.NetworkManager.SpawnManager.GetClientOwnedObjects(ClientId);
|
||||
}
|
||||
|
||||
return new List<NetworkObject>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,9 +14,9 @@ namespace Unity.Netcode
|
||||
public static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel;
|
||||
|
||||
// internal logging
|
||||
internal static void LogInfo(string message) => Debug.Log($"[Netcode] {message}");
|
||||
internal static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}");
|
||||
internal static void LogError(string message) => Debug.LogError($"[Netcode] {message}");
|
||||
public static void LogInfo(string message) => Debug.Log($"[Netcode] {message}");
|
||||
public static void LogWarning(string message) => Debug.LogWarning($"[Netcode] {message}");
|
||||
public static void LogError(string message) => Debug.LogError($"[Netcode] {message}");
|
||||
|
||||
/// <summary>
|
||||
/// Logs an info log locally and on the server if possible.
|
||||
@@ -62,9 +62,9 @@ namespace Unity.Netcode
|
||||
LogType = logType,
|
||||
Message = message
|
||||
};
|
||||
var size = NetworkManager.Singleton.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.Singleton.ServerClientId);
|
||||
var size = NetworkManager.Singleton.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.ServerClientId);
|
||||
|
||||
NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.Singleton.ServerClientId, (uint)logType, size);
|
||||
NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.ServerClientId, (uint)logType, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,24 +29,33 @@ namespace Unity.Netcode
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
|
||||
if (networkObject.OwnerClientId == networkManager.LocalClientId)
|
||||
{
|
||||
//We are current owner.
|
||||
networkObject.InvokeBehaviourOnLostOwnership();
|
||||
}
|
||||
var originalOwner = networkObject.OwnerClientId;
|
||||
|
||||
networkObject.OwnerClientId = OwnerClientId;
|
||||
|
||||
// We are current owner.
|
||||
if (originalOwner == networkManager.LocalClientId)
|
||||
{
|
||||
networkObject.InvokeBehaviourOnLostOwnership();
|
||||
}
|
||||
|
||||
// We are new owner.
|
||||
if (OwnerClientId == networkManager.LocalClientId)
|
||||
{
|
||||
//We are new owner.
|
||||
networkObject.InvokeBehaviourOnGainedOwnership();
|
||||
}
|
||||
|
||||
// For all other clients that are neither the former or current owner, update the behaviours' properties
|
||||
if (OwnerClientId != networkManager.LocalClientId && originalOwner != networkManager.LocalClientId)
|
||||
{
|
||||
for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
networkObject.ChildNetworkBehaviours[i].UpdateNetworkProperties();
|
||||
}
|
||||
}
|
||||
|
||||
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Unity.Netcode
|
||||
public ushort NetworkBehaviourIndex;
|
||||
|
||||
public HashSet<int> DeliveryMappedNetworkVariableIndex;
|
||||
public ulong ClientId;
|
||||
public ulong TargetClientId;
|
||||
public NetworkBehaviour NetworkBehaviour;
|
||||
|
||||
private FastBufferReader m_ReceivedNetworkVariableData;
|
||||
@@ -31,9 +31,9 @@ namespace Unity.Netcode
|
||||
writer.WriteValue(NetworkObjectId);
|
||||
writer.WriteValue(NetworkBehaviourIndex);
|
||||
|
||||
for (int k = 0; k < NetworkBehaviour.NetworkVariableFields.Count; k++)
|
||||
for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++)
|
||||
{
|
||||
if (!DeliveryMappedNetworkVariableIndex.Contains(k))
|
||||
if (!DeliveryMappedNetworkVariableIndex.Contains(i))
|
||||
{
|
||||
// This var does not belong to the currently iterating delivery group.
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
@@ -48,15 +48,17 @@ namespace Unity.Netcode
|
||||
continue;
|
||||
}
|
||||
|
||||
// if I'm dirty AND a client, write (server always has all permissions)
|
||||
// if I'm dirty AND the server AND the client can read me, send.
|
||||
bool shouldWrite = NetworkBehaviour.NetworkVariableFields[k].ShouldWrite(ClientId, NetworkBehaviour.NetworkManager.IsServer);
|
||||
var startingSize = writer.Length;
|
||||
var networkVariable = NetworkBehaviour.NetworkVariableFields[i];
|
||||
var shouldWrite = networkVariable.IsDirty() &&
|
||||
networkVariable.CanClientRead(TargetClientId) &&
|
||||
(NetworkBehaviour.NetworkManager.IsServer || networkVariable.CanClientWrite(NetworkBehaviour.NetworkManager.LocalClientId));
|
||||
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (!shouldWrite)
|
||||
{
|
||||
writer.WriteValueSafe((ushort)0);
|
||||
BytePacker.WriteValueBitPacked(writer, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -68,34 +70,34 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, short.MaxValue);
|
||||
NetworkBehaviour.NetworkVariableFields[k].WriteDelta(tmpWriter);
|
||||
var tempWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
|
||||
NetworkBehaviour.NetworkVariableFields[i].WriteDelta(tempWriter);
|
||||
BytePacker.WriteValueBitPacked(writer, tempWriter.Length);
|
||||
|
||||
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize<ushort>() + tmpWriter.Length))
|
||||
if (!writer.TryBeginWrite(tempWriter.Length))
|
||||
{
|
||||
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
|
||||
}
|
||||
|
||||
writer.WriteValue((ushort)tmpWriter.Length);
|
||||
tmpWriter.CopyTo(writer);
|
||||
tempWriter.CopyTo(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkBehaviour.NetworkVariableFields[k].WriteDelta(writer);
|
||||
networkVariable.WriteDelta(writer);
|
||||
}
|
||||
|
||||
if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(k))
|
||||
if (!NetworkBehaviour.NetworkVariableIndexesToResetSet.Contains(i))
|
||||
{
|
||||
NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(k);
|
||||
NetworkBehaviour.NetworkVariableIndexesToReset.Add(k);
|
||||
NetworkBehaviour.NetworkVariableIndexesToResetSet.Add(i);
|
||||
NetworkBehaviour.NetworkVariableIndexesToReset.Add(i);
|
||||
}
|
||||
|
||||
NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
|
||||
ClientId,
|
||||
TargetClientId,
|
||||
NetworkBehaviour.NetworkObject,
|
||||
NetworkBehaviour.NetworkVariableFields[k].Name,
|
||||
networkVariable.Name,
|
||||
NetworkBehaviour.__getTypeName(),
|
||||
writer.Length);
|
||||
writer.Length - startingSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,9 +123,9 @@ namespace Unity.Netcode
|
||||
|
||||
if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject))
|
||||
{
|
||||
NetworkBehaviour behaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
|
||||
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
|
||||
|
||||
if (behaviour == null)
|
||||
if (networkBehaviour == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
@@ -132,13 +134,12 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++)
|
||||
for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++)
|
||||
{
|
||||
ushort varSize = 0;
|
||||
|
||||
int varSize = 0;
|
||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
m_ReceivedNetworkVariableData.ReadValueSafe(out varSize);
|
||||
ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize);
|
||||
|
||||
if (varSize == 0)
|
||||
{
|
||||
@@ -154,15 +155,17 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
if (networkManager.IsServer)
|
||||
var networkVariable = networkBehaviour.NetworkVariableFields[i];
|
||||
|
||||
if (networkManager.IsServer && !networkVariable.CanClientWrite(context.SenderId))
|
||||
{
|
||||
// we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server
|
||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]");
|
||||
NetworkLog.LogWarning($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
|
||||
}
|
||||
|
||||
m_ReceivedNetworkVariableData.Seek(m_ReceivedNetworkVariableData.Position + varSize);
|
||||
@@ -176,23 +179,23 @@ namespace Unity.Netcode
|
||||
//A dummy read COULD be added to the interface for this situation, but it's just being too nice.
|
||||
//This is after all a developer fault. A critical error should be fine.
|
||||
// - TwoTen
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]");
|
||||
NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
int readStartPos = m_ReceivedNetworkVariableData.Position;
|
||||
|
||||
behaviour.NetworkVariableFields[i].ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer);
|
||||
networkVariable.ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer);
|
||||
|
||||
networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived(
|
||||
context.SenderId,
|
||||
networkObject,
|
||||
behaviour.NetworkVariableFields[i].Name,
|
||||
behaviour.__getTypeName(),
|
||||
networkVariable.Name,
|
||||
networkBehaviour.__getTypeName(),
|
||||
context.MessageSize);
|
||||
|
||||
|
||||
@@ -202,7 +205,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var delta read too far. {m_ReceivedNetworkVariableData.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogWarning($"Var delta read too far. {m_ReceivedNetworkVariableData.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
}
|
||||
|
||||
m_ReceivedNetworkVariableData.Seek(readStartPos + varSize);
|
||||
@@ -211,7 +214,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var delta read too little. {(readStartPos + varSize) - m_ReceivedNetworkVariableData.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
|
||||
NetworkLog.LogWarning($"Var delta read too little. {readStartPos + varSize - m_ReceivedNetworkVariableData.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||
}
|
||||
|
||||
m_ReceivedNetworkVariableData.Seek(readStartPos + varSize);
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal struct SnapshotDataMessage : INetworkMessage
|
||||
{
|
||||
public int CurrentTick;
|
||||
public ushort Sequence;
|
||||
|
||||
public ushort Range;
|
||||
|
||||
public byte[] SendMainBuffer;
|
||||
public NativeArray<byte> ReceiveMainBuffer;
|
||||
|
||||
public struct AckData
|
||||
{
|
||||
public ushort LastReceivedSequence;
|
||||
public ushort ReceivedSequenceMask;
|
||||
}
|
||||
|
||||
public AckData Ack;
|
||||
|
||||
public struct EntryData
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public ushort BehaviourIndex;
|
||||
public ushort VariableIndex;
|
||||
public int TickWritten;
|
||||
public ushort Position;
|
||||
public ushort Length;
|
||||
}
|
||||
|
||||
public NativeList<EntryData> Entries;
|
||||
|
||||
public struct SpawnData
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public uint Hash;
|
||||
public bool IsSceneObject;
|
||||
|
||||
public bool IsPlayerObject;
|
||||
public ulong OwnerClientId;
|
||||
public ulong ParentNetworkId;
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public Vector3 Scale;
|
||||
|
||||
public int TickWritten;
|
||||
}
|
||||
|
||||
public NativeList<SpawnData> Spawns;
|
||||
|
||||
public struct DespawnData
|
||||
{
|
||||
public ulong NetworkObjectId;
|
||||
public int TickWritten;
|
||||
}
|
||||
|
||||
public NativeList<DespawnData> Despawns;
|
||||
|
||||
public unsafe void Serialize(FastBufferWriter writer)
|
||||
{
|
||||
if (!writer.TryBeginWrite(
|
||||
FastBufferWriter.GetWriteSize(CurrentTick) +
|
||||
FastBufferWriter.GetWriteSize(Sequence) +
|
||||
FastBufferWriter.GetWriteSize(Range) + Range +
|
||||
FastBufferWriter.GetWriteSize(Ack) +
|
||||
FastBufferWriter.GetWriteSize<ushort>() +
|
||||
Entries.Length * sizeof(EntryData) +
|
||||
FastBufferWriter.GetWriteSize<ushort>() +
|
||||
Spawns.Length * sizeof(SpawnData) +
|
||||
FastBufferWriter.GetWriteSize<ushort>() +
|
||||
Despawns.Length * sizeof(DespawnData)
|
||||
))
|
||||
{
|
||||
throw new OverflowException($"Not enough space to serialize {nameof(SnapshotDataMessage)}");
|
||||
}
|
||||
writer.WriteValue(CurrentTick);
|
||||
writer.WriteValue(Sequence);
|
||||
|
||||
writer.WriteValue(Range);
|
||||
writer.WriteBytes(SendMainBuffer, Range);
|
||||
writer.WriteValue(Ack);
|
||||
|
||||
writer.WriteValue((ushort)Entries.Length);
|
||||
writer.WriteBytes((byte*)Entries.GetUnsafePtr(), Entries.Length * sizeof(EntryData));
|
||||
|
||||
writer.WriteValue((ushort)Spawns.Length);
|
||||
writer.WriteBytes((byte*)Spawns.GetUnsafePtr(), Spawns.Length * sizeof(SpawnData));
|
||||
|
||||
writer.WriteValue((ushort)Despawns.Length);
|
||||
writer.WriteBytes((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData));
|
||||
}
|
||||
|
||||
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context)
|
||||
{
|
||||
if (!reader.TryBeginRead(
|
||||
FastBufferWriter.GetWriteSize(CurrentTick) +
|
||||
FastBufferWriter.GetWriteSize(Sequence) +
|
||||
FastBufferWriter.GetWriteSize(Range)
|
||||
))
|
||||
{
|
||||
throw new OverflowException($"Not enough space to deserialize {nameof(SnapshotDataMessage)}");
|
||||
}
|
||||
reader.ReadValue(out CurrentTick);
|
||||
reader.ReadValue(out Sequence);
|
||||
|
||||
reader.ReadValue(out Range);
|
||||
ReceiveMainBuffer = new NativeArray<byte>(Range, Allocator.Temp);
|
||||
reader.ReadBytesSafe((byte*)ReceiveMainBuffer.GetUnsafePtr(), Range);
|
||||
reader.ReadValueSafe(out Ack);
|
||||
|
||||
reader.ReadValueSafe(out ushort length);
|
||||
Entries = new NativeList<EntryData>(length, Allocator.Temp) { Length = length };
|
||||
reader.ReadBytesSafe((byte*)Entries.GetUnsafePtr(), Entries.Length * sizeof(EntryData));
|
||||
|
||||
reader.ReadValueSafe(out length);
|
||||
Spawns = new NativeList<SpawnData>(length, Allocator.Temp) { Length = length };
|
||||
reader.ReadBytesSafe((byte*)Spawns.GetUnsafePtr(), Spawns.Length * sizeof(SpawnData));
|
||||
|
||||
reader.ReadValueSafe(out length);
|
||||
Despawns = new NativeList<DespawnData>(length, Allocator.Temp) { Length = length };
|
||||
reader.ReadBytesSafe((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
using (ReceiveMainBuffer)
|
||||
using (Entries)
|
||||
using (Spawns)
|
||||
using (Despawns)
|
||||
{
|
||||
var systemOwner = context.SystemOwner;
|
||||
var senderId = context.SenderId;
|
||||
if (systemOwner is NetworkManager networkManager)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
|
||||
public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue;
|
||||
public const int FRAGMENTED_MESSAGE_MAX_SIZE = BytePacker.BitPackedIntMax;
|
||||
|
||||
internal struct MessageWithHandler
|
||||
{
|
||||
|
||||
@@ -87,7 +87,13 @@ namespace Unity.Netcode
|
||||
|
||||
void TrackPacketReceived(uint packetCount);
|
||||
|
||||
void TrackRttToServer(int rtt);
|
||||
void UpdateRttToServer(int rtt);
|
||||
|
||||
void UpdateNetworkObjectsCount(int count);
|
||||
|
||||
void UpdateConnectionsCount(int count);
|
||||
|
||||
void UpdatePacketLoss(float packetLoss);
|
||||
|
||||
void DispatchFrame();
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Unity.Netcode
|
||||
private readonly EventMetric<SceneEventMetric> m_SceneEventSentEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventSent.Id);
|
||||
private readonly EventMetric<SceneEventMetric> m_SceneEventReceivedEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventReceived.Id);
|
||||
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
private readonly Counter m_PacketSentCounter = new Counter(NetworkMetricTypes.PacketsSent.Id)
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
@@ -79,6 +79,15 @@ namespace Unity.Netcode
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
};
|
||||
private readonly Gauge m_NetworkObjectsGauge = new Gauge(NetworkMetricTypes.NetworkObjects.Id)
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
};
|
||||
private readonly Gauge m_ConnectionsGauge = new Gauge(NetworkMetricTypes.ConnectedClients.Id)
|
||||
{
|
||||
ShouldResetOnDispatch = true,
|
||||
};
|
||||
private readonly Gauge m_PacketLossGauge = new Gauge(NetworkMetricTypes.PacketLoss.Id);
|
||||
#endif
|
||||
|
||||
private ulong m_NumberOfMetricsThisFrame;
|
||||
@@ -97,9 +106,12 @@ namespace Unity.Netcode
|
||||
.WithMetricEvents(m_RpcSentEvent, m_RpcReceivedEvent)
|
||||
.WithMetricEvents(m_ServerLogSentEvent, m_ServerLogReceivedEvent)
|
||||
.WithMetricEvents(m_SceneEventSentEvent, m_SceneEventReceivedEvent)
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
.WithCounters(m_PacketSentCounter, m_PacketReceivedCounter)
|
||||
.WithGauges(m_RttToServerGauge)
|
||||
.WithGauges(m_NetworkObjectsGauge)
|
||||
.WithGauges(m_ConnectionsGauge)
|
||||
.WithGauges(m_PacketLossGauge)
|
||||
#endif
|
||||
.Build();
|
||||
|
||||
@@ -428,7 +440,7 @@ namespace Unity.Netcode
|
||||
|
||||
public void TrackPacketSent(uint packetCount)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
@@ -441,7 +453,7 @@ namespace Unity.Netcode
|
||||
|
||||
public void TrackPacketReceived(uint packetCount)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
@@ -452,15 +464,51 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
public void TrackRttToServer(int rtt)
|
||||
public void UpdateRttToServer(int rttMilliseconds)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var rttSeconds = rttMilliseconds * 1e-3;
|
||||
m_RttToServerGauge.Set(rttSeconds);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UpdateNetworkObjectsCount(int count)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_RttToServerGauge.Set(rtt);
|
||||
m_NetworkObjectsGauge.Set(count);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UpdateConnectionsCount(int count)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_ConnectionsGauge.Set(count);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void UpdatePacketLoss(float packetLoss)
|
||||
{
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
if (!CanSendMetrics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_PacketLossGauge.Set(packetLoss);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,19 @@ namespace Unity.Netcode
|
||||
{
|
||||
}
|
||||
|
||||
public void TrackRttToServer(int rtt)
|
||||
public void UpdateRttToServer(int rtt)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateNetworkObjectsCount(int count)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdateConnectionsCount(int count)
|
||||
{
|
||||
}
|
||||
|
||||
public void UpdatePacketLoss(float packetLoss)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -24,17 +24,12 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public event OnListChangedDelegate OnListChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with the default value and settings
|
||||
/// </summary>
|
||||
public NetworkList() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with the default value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission to use for the NetworkList</param>
|
||||
/// <param name="values">The initial value to use for the NetworkList</param>
|
||||
public NetworkList(NetworkVariableReadPermission readPerm, IEnumerable<T> values) : base(readPerm)
|
||||
public NetworkList(IEnumerable<T> values = default,
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
: base(readPerm, writePerm)
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
@@ -42,19 +37,6 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with a custom value and the default settings
|
||||
/// </summary>
|
||||
/// <param name="values">The initial value to use for the NetworkList</param>
|
||||
public NetworkList(IEnumerable<T> values)
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
m_List.Add(value);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ResetDirty()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
@@ -22,7 +24,8 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
// Functions that serialize other types
|
||||
private static void WriteValue<TForMethod>(FastBufferWriter writer, in TForMethod value) where TForMethod : unmanaged
|
||||
private static void WriteValue<TForMethod>(FastBufferWriter writer, in TForMethod value)
|
||||
where TForMethod : unmanaged
|
||||
{
|
||||
writer.WriteValueSafe(value);
|
||||
}
|
||||
@@ -37,16 +40,13 @@ namespace Unity.Netcode
|
||||
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
// 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,
|
||||
@@ -69,38 +69,11 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public OnValueChangedDelegate OnValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with the default value and custom read permission
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission for the NetworkVariable</param>
|
||||
|
||||
public NetworkVariable()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with the default value and custom read permission
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission for the NetworkVariable</param>
|
||||
public NetworkVariable(NetworkVariableReadPermission readPerm) : base(readPerm)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with a custom value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="readPerm">The read permission for the NetworkVariable</param>
|
||||
/// <param name="value">The initial value to use for the NetworkVariable</param>
|
||||
public NetworkVariable(NetworkVariableReadPermission readPerm, T value) : base(readPerm)
|
||||
{
|
||||
m_InternalValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with a custom value and the default read permission
|
||||
/// </summary>
|
||||
/// <param name="value">The initial value to use for the NetworkVariable</param>
|
||||
public NetworkVariable(T value)
|
||||
public NetworkVariable(T value = default,
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
: base(readPerm, writePerm)
|
||||
{
|
||||
m_InternalValue = value;
|
||||
}
|
||||
@@ -116,19 +89,36 @@ namespace Unity.Netcode
|
||||
get => m_InternalValue;
|
||||
set
|
||||
{
|
||||
// this could be improved. The Networking Manager is not always initialized here
|
||||
// Good place to decouple network manager from the network variable
|
||||
|
||||
// Also, note this is not really very water-tight, if you are running as a host
|
||||
// we cannot tell if a NetworkVariable write is happening inside client-ish code
|
||||
if (m_NetworkBehaviour && (m_NetworkBehaviour.NetworkManager.IsClient && !m_NetworkBehaviour.NetworkManager.IsHost))
|
||||
// Compare bitwise
|
||||
if (ValueEquals(ref m_InternalValue, ref value))
|
||||
{
|
||||
throw new InvalidOperationException("Client can't write to NetworkVariables");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_NetworkBehaviour && !CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
|
||||
{
|
||||
throw new InvalidOperationException("Client is not allowed to write to this NetworkVariable");
|
||||
}
|
||||
|
||||
Set(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Compares two values of the same unmanaged type by underlying memory
|
||||
// Ignoring any overriden value checks
|
||||
// Size is fixed
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe bool ValueEquals(ref T a, ref T b)
|
||||
{
|
||||
// get unmanaged pointers
|
||||
var aptr = UnsafeUtility.AddressOf(ref a);
|
||||
var bptr = UnsafeUtility.AddressOf(ref b);
|
||||
|
||||
// compare addresses
|
||||
return UnsafeUtility.MemCmp(aptr, bptr, sizeof(T)) == 0;
|
||||
}
|
||||
|
||||
|
||||
private protected void Set(T value)
|
||||
{
|
||||
m_IsDirty = true;
|
||||
@@ -146,7 +136,6 @@ namespace Unity.Netcode
|
||||
WriteField(writer);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads value from the reader and applies it
|
||||
/// </summary>
|
||||
|
||||
@@ -19,9 +19,15 @@ namespace Unity.Netcode
|
||||
m_NetworkBehaviour = networkBehaviour;
|
||||
}
|
||||
|
||||
protected NetworkVariableBase(NetworkVariableReadPermission readPermIn = NetworkVariableReadPermission.Everyone)
|
||||
public const NetworkVariableReadPermission DefaultReadPerm = NetworkVariableReadPermission.Everyone;
|
||||
public const NetworkVariableWritePermission DefaultWritePerm = NetworkVariableWritePermission.Server;
|
||||
|
||||
protected NetworkVariableBase(
|
||||
NetworkVariableReadPermission readPerm = DefaultReadPerm,
|
||||
NetworkVariableWritePermission writePerm = DefaultWritePerm)
|
||||
{
|
||||
ReadPerm = readPermIn;
|
||||
ReadPerm = readPerm;
|
||||
WritePerm = writePerm;
|
||||
}
|
||||
|
||||
private protected bool m_IsDirty;
|
||||
@@ -37,6 +43,8 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public readonly NetworkVariableReadPermission ReadPerm;
|
||||
|
||||
public readonly NetworkVariableWritePermission WritePerm;
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether or not the variable needs to be delta synced
|
||||
/// </summary>
|
||||
@@ -62,26 +70,28 @@ namespace Unity.Netcode
|
||||
return m_IsDirty;
|
||||
}
|
||||
|
||||
public virtual bool ShouldWrite(ulong clientId, bool isServer)
|
||||
{
|
||||
return IsDirty() && isServer && CanClientRead(clientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not a specific client can read to the varaible
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId of the remote client</param>
|
||||
/// <returns>Whether or not the client can read to the variable</returns>
|
||||
public bool CanClientRead(ulong clientId)
|
||||
{
|
||||
switch (ReadPerm)
|
||||
{
|
||||
default:
|
||||
case NetworkVariableReadPermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariableReadPermission.OwnerOnly:
|
||||
return m_NetworkBehaviour.OwnerClientId == clientId;
|
||||
case NetworkVariableReadPermission.Owner:
|
||||
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanClientWrite(ulong clientId)
|
||||
{
|
||||
switch (WritePerm)
|
||||
{
|
||||
default:
|
||||
case NetworkVariableWritePermission.Server:
|
||||
return clientId == NetworkManager.ServerClientId;
|
||||
case NetworkVariableWritePermission.Owner:
|
||||
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -107,7 +117,6 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
/// <param name="reader">The stream to read the delta from</param>
|
||||
/// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param>
|
||||
|
||||
public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta);
|
||||
|
||||
public virtual void Dispose()
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Permission type
|
||||
/// </summary>
|
||||
public enum NetworkVariableReadPermission
|
||||
{
|
||||
/// <summary>
|
||||
/// Everyone
|
||||
/// </summary>
|
||||
Everyone,
|
||||
Owner,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Owner-ownly
|
||||
/// </summary>
|
||||
OwnerOnly,
|
||||
public enum NetworkVariableWritePermission
|
||||
{
|
||||
Server,
|
||||
Owner
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Unity.Netcode
|
||||
private const NetworkDelivery k_DeliveryType = NetworkDelivery.ReliableFragmentedSequenced;
|
||||
internal const int InvalidSceneNameOrPath = -1;
|
||||
|
||||
// Used to be able to turn re-synchronization off for future snapshot development purposes.
|
||||
// Used to be able to turn re-synchronization off
|
||||
internal static bool DisableReSynchronization;
|
||||
|
||||
/// <summary>
|
||||
@@ -488,8 +488,18 @@ namespace Unity.Netcode
|
||||
var scenePath = SceneUtility.GetScenePathByBuildIndex(i);
|
||||
var hash = XXHash.Hash32(scenePath);
|
||||
var buildIndex = SceneUtility.GetBuildIndexByScenePath(scenePath);
|
||||
HashToBuildIndex.Add(hash, buildIndex);
|
||||
BuildIndexToHash.Add(buildIndex, hash);
|
||||
|
||||
// In the rare-case scenario where a programmatically generated build has duplicate
|
||||
// scene entries, we will log an error and skip the entry
|
||||
if (!HashToBuildIndex.ContainsKey(hash))
|
||||
{
|
||||
HashToBuildIndex.Add(hash, buildIndex);
|
||||
BuildIndexToHash.Add(buildIndex, hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"{nameof(NetworkSceneManager)} is skipping duplicate scene path entry {scenePath}. Make sure your scenes in build list does not contain duplicates!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,7 +530,8 @@ namespace Unity.Netcode
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Scene Hash {sceneHash} does not exist in the {nameof(HashToBuildIndex)} table!");
|
||||
throw new Exception($"Scene Hash {sceneHash} does not exist in the {nameof(HashToBuildIndex)} table! Verify that all scenes requiring" +
|
||||
$" server to client synchronization are in the scenes in build list.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,7 +887,7 @@ namespace Unity.Netcode
|
||||
{
|
||||
SceneEventType = sceneEventProgress.SceneEventType,
|
||||
SceneName = SceneNameFromHash(sceneEventProgress.SceneHash),
|
||||
ClientId = m_NetworkManager.ServerClientId,
|
||||
ClientId = NetworkManager.ServerClientId,
|
||||
LoadSceneMode = sceneEventProgress.LoadSceneMode,
|
||||
ClientsThatCompleted = sceneEventProgress.DoneClients,
|
||||
ClientsThatTimedOut = m_NetworkManager.ConnectedClients.Keys.Except(sceneEventProgress.DoneClients).ToList(),
|
||||
@@ -947,10 +958,10 @@ namespace Unity.Netcode
|
||||
SceneEventType = sceneEventData.SceneEventType,
|
||||
LoadSceneMode = sceneEventData.LoadSceneMode,
|
||||
SceneName = sceneName,
|
||||
ClientId = m_NetworkManager.ServerClientId // Server can only invoke this
|
||||
ClientId = NetworkManager.ServerClientId // Server can only invoke this
|
||||
});
|
||||
|
||||
OnUnload?.Invoke(m_NetworkManager.ServerClientId, sceneName, sceneUnload);
|
||||
OnUnload?.Invoke(NetworkManager.ServerClientId, sceneName, sceneUnload);
|
||||
|
||||
//Return the status
|
||||
return sceneEventProgress.Status;
|
||||
@@ -1017,12 +1028,12 @@ namespace Unity.Netcode
|
||||
// Server sends the unload scene notification after unloading because it will despawn all scene relative in-scene NetworkObjects
|
||||
// If we send this event to all clients before the server is finished unloading they will get warning about an object being
|
||||
// despawned that no longer exists
|
||||
SendSceneEventData(sceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != m_NetworkManager.ServerClientId).ToArray());
|
||||
SendSceneEventData(sceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray());
|
||||
|
||||
//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);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1035,7 +1046,7 @@ namespace Unity.Netcode
|
||||
SceneEventType = sceneEventData.SceneEventType,
|
||||
LoadSceneMode = sceneEventData.LoadSceneMode,
|
||||
SceneName = SceneNameFromHash(sceneEventData.SceneHash),
|
||||
ClientId = m_NetworkManager.IsServer ? m_NetworkManager.ServerClientId : m_NetworkManager.LocalClientId
|
||||
ClientId = m_NetworkManager.IsServer ? NetworkManager.ServerClientId : m_NetworkManager.LocalClientId
|
||||
});
|
||||
|
||||
OnUnloadComplete?.Invoke(m_NetworkManager.LocalClientId, SceneNameFromHash(sceneEventData.SceneHash));
|
||||
@@ -1043,7 +1054,7 @@ namespace Unity.Netcode
|
||||
// Clients send a notification back to the server they have completed the unload scene event
|
||||
if (!m_NetworkManager.IsServer)
|
||||
{
|
||||
SendSceneEventData(sceneEventId, new ulong[] { m_NetworkManager.ServerClientId });
|
||||
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
||||
}
|
||||
|
||||
EndSceneEvent(sceneEventId);
|
||||
@@ -1079,7 +1090,7 @@ namespace Unity.Netcode
|
||||
SceneEventType = SceneEventType.Unload,
|
||||
SceneName = keyHandleEntry.Value.name,
|
||||
LoadSceneMode = LoadSceneMode.Additive, // The only scenes unloaded are scenes that were additively loaded
|
||||
ClientId = m_NetworkManager.ServerClientId
|
||||
ClientId = NetworkManager.ServerClientId
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1147,10 +1158,10 @@ namespace Unity.Netcode
|
||||
SceneEventType = sceneEventData.SceneEventType,
|
||||
LoadSceneMode = sceneEventData.LoadSceneMode,
|
||||
SceneName = sceneName,
|
||||
ClientId = m_NetworkManager.ServerClientId
|
||||
ClientId = NetworkManager.ServerClientId
|
||||
});
|
||||
|
||||
OnLoad?.Invoke(m_NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad);
|
||||
OnLoad?.Invoke(NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, sceneLoad);
|
||||
|
||||
//Return our scene progress instance
|
||||
return sceneEventProgress.Status;
|
||||
@@ -1187,7 +1198,6 @@ namespace Unity.Netcode
|
||||
// When it is set: Just before starting the asynchronous loading call
|
||||
// When it is unset: After the scene has loaded, the PopulateScenePlacedObjects is called, and all NetworkObjects in the do
|
||||
// not destroy temporary scene are moved into the active scene
|
||||
// TODO: When Snapshot scene spawning is enabled this needs to be removed.
|
||||
if (sceneEventData.LoadSceneMode == LoadSceneMode.Single)
|
||||
{
|
||||
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
|
||||
@@ -1278,7 +1288,9 @@ namespace Unity.Netcode
|
||||
{
|
||||
if (!keyValuePairBySceneHandle.Value.IsPlayerObject)
|
||||
{
|
||||
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value, m_NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, null, true);
|
||||
// All in-scene placed NetworkObjects default to being owned by the server
|
||||
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(keyValuePairBySceneHandle.Value,
|
||||
m_NetworkManager.SpawnManager.GetNetworkObjectId(), true, false, NetworkManager.ServerClientId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1290,7 +1302,7 @@ namespace Unity.Netcode
|
||||
for (int j = 0; j < m_NetworkManager.ConnectedClientsList.Count; j++)
|
||||
{
|
||||
var clientId = m_NetworkManager.ConnectedClientsList[j].ClientId;
|
||||
if (clientId != m_NetworkManager.ServerClientId)
|
||||
if (clientId != NetworkManager.ServerClientId)
|
||||
{
|
||||
sceneEventData.TargetClientId = clientId;
|
||||
var message = new SceneEventMessage
|
||||
@@ -1309,16 +1321,16 @@ namespace Unity.Netcode
|
||||
SceneEventType = SceneEventType.LoadComplete,
|
||||
LoadSceneMode = sceneEventData.LoadSceneMode,
|
||||
SceneName = SceneNameFromHash(sceneEventData.SceneHash),
|
||||
ClientId = m_NetworkManager.ServerClientId,
|
||||
ClientId = NetworkManager.ServerClientId,
|
||||
Scene = scene,
|
||||
});
|
||||
|
||||
OnLoadComplete?.Invoke(m_NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode);
|
||||
OnLoadComplete?.Invoke(NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode);
|
||||
|
||||
//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);
|
||||
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(NetworkManager.ServerClientId);
|
||||
}
|
||||
EndSceneEvent(sceneEventId);
|
||||
}
|
||||
@@ -1333,7 +1345,7 @@ namespace Unity.Netcode
|
||||
sceneEventData.DeserializeScenePlacedObjects();
|
||||
|
||||
sceneEventData.SceneEventType = SceneEventType.LoadComplete;
|
||||
SendSceneEventData(sceneEventId, new ulong[] { m_NetworkManager.ServerClientId });
|
||||
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
||||
m_IsSceneEventActive = false;
|
||||
|
||||
// Notify local client that the scene was loaded
|
||||
@@ -1544,9 +1556,9 @@ namespace Unity.Netcode
|
||||
{
|
||||
EventData = responseSceneEventData
|
||||
};
|
||||
var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, m_NetworkManager.ServerClientId);
|
||||
var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, NetworkManager.ServerClientId);
|
||||
|
||||
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(m_NetworkManager.ServerClientId, (uint)responseSceneEventData.SceneEventType, sceneName, size);
|
||||
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(NetworkManager.ServerClientId, (uint)responseSceneEventData.SceneEventType, sceneName, size);
|
||||
|
||||
EndSceneEvent(responseSceneEventData.SceneEventId);
|
||||
|
||||
@@ -1600,7 +1612,7 @@ namespace Unity.Netcode
|
||||
sceneEventData.SynchronizeSceneNetworkObjects(m_NetworkManager);
|
||||
|
||||
sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete;
|
||||
SendSceneEventData(sceneEventId, new ulong[] { m_NetworkManager.ServerClientId });
|
||||
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
||||
|
||||
// All scenes are synchronized, let the server know we are done synchronizing
|
||||
m_NetworkManager.IsConnectedClient = true;
|
||||
@@ -1627,7 +1639,7 @@ namespace Unity.Netcode
|
||||
OnSceneEvent?.Invoke(new SceneEvent()
|
||||
{
|
||||
SceneEventType = sceneEventData.SceneEventType,
|
||||
ClientId = m_NetworkManager.ServerClientId, // Server sent this to client
|
||||
ClientId = NetworkManager.ServerClientId, // Server sent this to client
|
||||
});
|
||||
|
||||
EndSceneEvent(sceneEventId);
|
||||
@@ -1642,7 +1654,7 @@ namespace Unity.Netcode
|
||||
SceneEventType = sceneEventData.SceneEventType,
|
||||
LoadSceneMode = sceneEventData.LoadSceneMode,
|
||||
SceneName = SceneNameFromHash(sceneEventData.SceneHash),
|
||||
ClientId = m_NetworkManager.ServerClientId,
|
||||
ClientId = NetworkManager.ServerClientId,
|
||||
ClientsThatCompleted = sceneEventData.ClientsCompleted,
|
||||
ClientsThatTimedOut = sceneEventData.ClientsTimedOut,
|
||||
});
|
||||
@@ -1734,8 +1746,6 @@ namespace Unity.Netcode
|
||||
// NetworkObjects
|
||||
m_NetworkManager.InvokeOnClientConnectedCallback(clientId);
|
||||
|
||||
// TODO: This check and associated code can be removed once we determine all
|
||||
// snapshot destroy messages are being updated until the server receives ACKs
|
||||
if (sceneEventData.ClientNeedsReSynchronization() && !DisableReSynchronization)
|
||||
{
|
||||
sceneEventData.SceneEventType = SceneEventType.ReSynchronize;
|
||||
@@ -1830,7 +1840,7 @@ namespace Unity.Netcode
|
||||
/// Using the local scene relative Scene.handle as a sub-key to the root dictionary allows us to
|
||||
/// distinguish between duplicate in-scene placed NetworkObjects
|
||||
/// </summary>
|
||||
private void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePlacedObjects = true)
|
||||
internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePlacedObjects = true)
|
||||
{
|
||||
if (clearScenePlacedObjects)
|
||||
{
|
||||
@@ -1845,25 +1855,26 @@ namespace Unity.Netcode
|
||||
// at the end of scene loading we use this list to soft synchronize all in-scene placed NetworkObjects
|
||||
foreach (var networkObjectInstance in networkObjects)
|
||||
{
|
||||
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (additive scenes)
|
||||
if (networkObjectInstance.IsSceneObject == null && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy &&
|
||||
networkObjectInstance.gameObject.scene.handle == sceneToFilterBy.handle)
|
||||
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
|
||||
var sceneHandle = networkObjectInstance.gameObject.scene.handle;
|
||||
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes)
|
||||
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy &&
|
||||
sceneHandle == sceneToFilterBy.handle)
|
||||
{
|
||||
if (!ScenePlacedObjects.ContainsKey(networkObjectInstance.GlobalObjectIdHash))
|
||||
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||
{
|
||||
ScenePlacedObjects.Add(networkObjectInstance.GlobalObjectIdHash, new Dictionary<int, NetworkObject>());
|
||||
ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
|
||||
}
|
||||
|
||||
if (!ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash].ContainsKey(networkObjectInstance.gameObject.scene.handle))
|
||||
if (!ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneHandle))
|
||||
{
|
||||
ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash].Add(networkObjectInstance.gameObject.scene.handle, networkObjectInstance);
|
||||
ScenePlacedObjects[globalObjectIdHash].Add(sceneHandle, networkObjectInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
var exitingEntryName = ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash][networkObjectInstance.gameObject.scene.handle] != null ?
|
||||
ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash][networkObjectInstance.gameObject.scene.handle].name : "Null Entry";
|
||||
var exitingEntryName = ScenePlacedObjects[globalObjectIdHash][sceneHandle] != null ? ScenePlacedObjects[globalObjectIdHash][sceneHandle].name : "Null Entry";
|
||||
throw new Exception($"{networkObjectInstance.name} tried to registered with {nameof(ScenePlacedObjects)} which already contains " +
|
||||
$"the same {nameof(NetworkObject.GlobalObjectIdHash)} value {networkObjectInstance.GlobalObjectIdHash} for {exitingEntryName}!");
|
||||
$"the same {nameof(NetworkObject.GlobalObjectIdHash)} value {globalObjectIdHash} for {exitingEntryName}!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ namespace Unity.Netcode
|
||||
/// <b>Invocation:</b> Server Side<br/>
|
||||
/// <b>Message Flow:</b> Server to client<br/>
|
||||
/// <b>Event Notification:</b> Both server and client receive a local notification<br/>
|
||||
/// <em>Note: This will be removed once snapshot and buffered messages are finalized as it will no longer be needed at that point.</em>
|
||||
/// </summary>
|
||||
ReSynchronize,
|
||||
/// <summary>
|
||||
@@ -592,9 +591,6 @@ namespace Unity.Netcode
|
||||
networkObject.IsSpawned = false;
|
||||
if (m_NetworkManager.PrefabHandler.ContainsHandler(networkObject))
|
||||
{
|
||||
// Since this is the client side and we have missed the delete message, until the Snapshot system is in place for spawn and despawn handling
|
||||
// we have to remove this from the list of spawned objects manually or when a NetworkObjectId is recycled the client will throw an error
|
||||
// about the id already being assigned.
|
||||
if (m_NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId))
|
||||
{
|
||||
m_NetworkManager.SpawnManager.SpawnedObjects.Remove(networkObjectId);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Unity.Netcode
|
||||
public static class BytePacker
|
||||
{
|
||||
#if UNITY_NETCODE_DEBUG_NO_PACKING
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValuePacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
|
||||
#else
|
||||
@@ -277,10 +277,21 @@ namespace Unity.Netcode
|
||||
|
||||
|
||||
#if UNITY_NETCODE_DEBUG_NO_PACKING
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteValueBitPacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
|
||||
#else
|
||||
|
||||
public const ushort BitPackedUshortMax = (1 << 15) - 1;
|
||||
public const short BitPackedShortMax = (1 << 14) - 1;
|
||||
public const short BitPackedShortMin = -(1 << 14);
|
||||
public const uint BitPackedUintMax = (1 << 30) - 1;
|
||||
public const int BitPackedIntMax = (1 << 29) - 1;
|
||||
public const int BitPackedIntMin = -(1 << 29);
|
||||
public const ulong BitPackedULongMax = (1L << 61) - 1;
|
||||
public const long BitPackedLongMax = (1L << 60) - 1;
|
||||
public const long BitPackedLongMin = -(1L << 60);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 14-bit signed short to the buffer in a bit-encoded packed format.
|
||||
/// The first bit indicates whether the value is 1 byte or 2.
|
||||
@@ -307,7 +318,7 @@ namespace Unity.Netcode
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, ushort value)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (value >= 0b1000_0000_0000_0000)
|
||||
if (value >= BitPackedUshortMax)
|
||||
{
|
||||
throw new ArgumentException("BitPacked ushorts must be <= 15 bits");
|
||||
}
|
||||
@@ -356,7 +367,7 @@ namespace Unity.Netcode
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, uint value)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (value >= 0b0100_0000_0000_0000_0000_0000_0000_0000)
|
||||
if (value > BitPackedUintMax)
|
||||
{
|
||||
throw new ArgumentException("BitPacked uints must be <= 30 bits");
|
||||
}
|
||||
@@ -396,7 +407,7 @@ namespace Unity.Netcode
|
||||
public static void WriteValueBitPacked(FastBufferWriter writer, ulong value)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
if (value >= 0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000)
|
||||
if (value > BitPackedULongMax)
|
||||
{
|
||||
throw new ArgumentException("BitPacked ulongs must be <= 61 bits");
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
internal readonly unsafe ReaderHandle* Handle;
|
||||
internal unsafe ReaderHandle* Handle;
|
||||
|
||||
/// <summary>
|
||||
/// Get the current read position
|
||||
@@ -39,6 +39,11 @@ namespace Unity.Netcode
|
||||
get => Handle->Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the reader has been initialized and a handle allocated.
|
||||
/// </summary>
|
||||
public unsafe bool IsInitialized => Handle != null;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal unsafe void CommitBitwiseReads(int amount)
|
||||
{
|
||||
@@ -196,6 +201,7 @@ namespace Unity.Netcode
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
UnsafeUtility.Free(Handle, Handle->Allocator);
|
||||
Handle = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Unity.Netcode
|
||||
#endif
|
||||
}
|
||||
|
||||
internal readonly unsafe WriterHandle* Handle;
|
||||
internal unsafe WriterHandle* Handle;
|
||||
|
||||
private static byte[] s_ByteArrayCache = new byte[65535];
|
||||
|
||||
@@ -62,6 +62,11 @@ namespace Unity.Netcode
|
||||
get => Handle->Position > Handle->Length ? Handle->Position : Handle->Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the writer has been initialized and a handle allocated.
|
||||
/// </summary>
|
||||
public unsafe bool IsInitialized => Handle != null;
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal unsafe void CommitBitwiseWrites(int amount)
|
||||
@@ -111,6 +116,7 @@ namespace Unity.Netcode
|
||||
UnsafeUtility.Free(Handle->BufferPointer, Handle->Allocator);
|
||||
}
|
||||
UnsafeUtility.Free(Handle, Handle->Allocator);
|
||||
Handle = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -207,7 +213,7 @@ namespace Unity.Netcode
|
||||
/// When you know you will be writing multiple fields back-to-back and you know the total size,
|
||||
/// you can call TryBeginWrite() once on the total size, and then follow it with calls to
|
||||
/// WriteValue() instead of WriteValueSafe() for faster serialization.
|
||||
///
|
||||
///
|
||||
/// Unsafe write operations will throw OverflowException in editor and development builds if you
|
||||
/// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown
|
||||
/// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
|
||||
@@ -253,7 +259,7 @@ namespace Unity.Netcode
|
||||
/// When you know you will be writing multiple fields back-to-back and you know the total size,
|
||||
/// you can call TryBeginWrite() once on the total size, and then follow it with calls to
|
||||
/// WriteValue() instead of WriteValueSafe() for faster serialization.
|
||||
///
|
||||
///
|
||||
/// Unsafe write operations will throw OverflowException in editor and development builds if you
|
||||
/// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown
|
||||
/// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
|
||||
|
||||
@@ -21,6 +21,122 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public readonly HashSet<NetworkObject> SpawnedObjectsList = new HashSet<NetworkObject>();
|
||||
|
||||
/// <summary>
|
||||
/// Use to get all NetworkObjects owned by a client
|
||||
/// Ownership to Objects Table Format:
|
||||
/// [ClientId][NetworkObjectId][NetworkObject]
|
||||
/// Server: Keeps track of all clients' ownership
|
||||
/// Client: Keeps track of only its ownership
|
||||
/// </summary>
|
||||
public readonly Dictionary<ulong, Dictionary<ulong, NetworkObject>> OwnershipToObjectsTable = new Dictionary<ulong, Dictionary<ulong, NetworkObject>>();
|
||||
|
||||
/// <summary>
|
||||
/// Object to Ownership Table:
|
||||
/// [NetworkObjectId][ClientId]
|
||||
/// Used internally to find the client Id that currently owns
|
||||
/// the NetworkObject
|
||||
/// </summary>
|
||||
private Dictionary<ulong, ulong> m_ObjectToOwnershipTable = new Dictionary<ulong, ulong>();
|
||||
|
||||
/// <summary>
|
||||
/// Used to update a NetworkObject's ownership
|
||||
/// </summary>
|
||||
internal void UpdateOwnershipTable(NetworkObject networkObject, ulong newOwner, bool isRemoving = false)
|
||||
{
|
||||
var previousOwner = newOwner;
|
||||
|
||||
// Use internal lookup table to see if the NetworkObject has a previous owner
|
||||
if (m_ObjectToOwnershipTable.ContainsKey(networkObject.NetworkObjectId))
|
||||
{
|
||||
// Keep track of the previous owner's ClientId
|
||||
previousOwner = m_ObjectToOwnershipTable[networkObject.NetworkObjectId];
|
||||
|
||||
// We are either despawning (remove) or changing ownership (assign)
|
||||
if (isRemoving)
|
||||
{
|
||||
m_ObjectToOwnershipTable.Remove(networkObject.NetworkObjectId);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ObjectToOwnershipTable[networkObject.NetworkObjectId] = newOwner;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, just add a new lookup entry
|
||||
m_ObjectToOwnershipTable.Add(networkObject.NetworkObjectId, newOwner);
|
||||
}
|
||||
|
||||
// Check to see if we had a previous owner
|
||||
if (previousOwner != newOwner && OwnershipToObjectsTable.ContainsKey(previousOwner))
|
||||
{
|
||||
// Before updating the previous owner, assure this entry exists
|
||||
if (OwnershipToObjectsTable[previousOwner].ContainsKey(networkObject.NetworkObjectId))
|
||||
{
|
||||
// Remove the previous owner's entry
|
||||
OwnershipToObjectsTable[previousOwner].Remove(networkObject.NetworkObjectId);
|
||||
|
||||
// Server or Host alway invokes the lost ownership notification locally
|
||||
if (NetworkManager.IsServer)
|
||||
{
|
||||
networkObject.InvokeBehaviourOnLostOwnership();
|
||||
}
|
||||
|
||||
// If we are removing the entry (i.e. despawning or client lost ownership)
|
||||
if (isRemoving)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Really, as long as UpdateOwnershipTable is invoked when ownership is gained or lost this should never happen
|
||||
throw new Exception($"Client-ID {previousOwner} had a partial {nameof(m_ObjectToOwnershipTable)} entry! Potentially corrupted {nameof(OwnershipToObjectsTable)}?");
|
||||
}
|
||||
}
|
||||
|
||||
// If the owner doesn't have an entry then create one
|
||||
if (!OwnershipToObjectsTable.ContainsKey(newOwner))
|
||||
{
|
||||
OwnershipToObjectsTable.Add(newOwner, new Dictionary<ulong, NetworkObject>());
|
||||
}
|
||||
|
||||
// Sanity check to make sure we don't already have this entry (we shouldn't)
|
||||
if (!OwnershipToObjectsTable[newOwner].ContainsKey(networkObject.NetworkObjectId))
|
||||
{
|
||||
// Add the new ownership entry
|
||||
OwnershipToObjectsTable[newOwner].Add(networkObject.NetworkObjectId, networkObject);
|
||||
|
||||
// Server or Host always invokes the gained ownership notification locally
|
||||
if (NetworkManager.IsServer)
|
||||
{
|
||||
networkObject.InvokeBehaviourOnGainedOwnership();
|
||||
}
|
||||
}
|
||||
else if (isRemoving)
|
||||
{
|
||||
OwnershipToObjectsTable[previousOwner].Remove(networkObject.NetworkObjectId);
|
||||
}
|
||||
else if (NetworkManager.LogLevel == LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogWarning($"Setting ownership twice? Client-ID {previousOwner} already owns NetworkObject ID {networkObject.NetworkObjectId}!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all NetworkObjects that belong to a client.
|
||||
/// </summary>
|
||||
/// <param name="clientId">the client's id <see cref="NetworkManager.LocalClientId"/></param>
|
||||
public List<NetworkObject> GetClientOwnedObjects(ulong clientId)
|
||||
{
|
||||
if (!OwnershipToObjectsTable.ContainsKey(clientId))
|
||||
{
|
||||
OwnershipToObjectsTable.Add(clientId, new Dictionary<ulong, NetworkObject>());
|
||||
}
|
||||
return OwnershipToObjectsTable[clientId].Values.ToList();
|
||||
}
|
||||
|
||||
|
||||
private struct TriggerData
|
||||
{
|
||||
public FastBufferReader Reader;
|
||||
@@ -96,8 +212,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Defers processing of a message until the moment a specific networkObjectId is spawned.
|
||||
/// This is to handle situations where an RPC or other object-specific message arrives before the spawn does,
|
||||
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed, or with
|
||||
/// snapshot spawns enabled where the spawn is sent unreliably and not until the end of the frame.
|
||||
/// either due to it being requested in OnNetworkSpawn before the spawn call has been executed
|
||||
///
|
||||
/// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed
|
||||
/// without the requested object ID being spawned, the triggers for it are automatically deleted.
|
||||
@@ -194,37 +309,21 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the connected client entry exists before trying to remove ownership.
|
||||
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
|
||||
// Server removes the entry and takes over ownership before notifying
|
||||
UpdateOwnershipTable(networkObject, NetworkManager.ServerClientId, true);
|
||||
|
||||
networkObject.OwnerClientId = NetworkManager.ServerClientId;
|
||||
|
||||
var message = new ChangeOwnershipMessage
|
||||
{
|
||||
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
|
||||
{
|
||||
if (networkClient.OwnedObjects[i] == networkObject)
|
||||
{
|
||||
networkClient.OwnedObjects.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
OwnerClientId = networkObject.OwnerClientId
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
|
||||
|
||||
networkObject.OwnerClientIdInternal = null;
|
||||
|
||||
var message = new ChangeOwnershipMessage
|
||||
{
|
||||
NetworkObjectId = networkObject.NetworkObjectId,
|
||||
OwnerClientId = networkObject.OwnerClientId
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
|
||||
|
||||
foreach (var client in NetworkManager.ConnectedClients)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
||||
}
|
||||
}
|
||||
else
|
||||
foreach (var client in NetworkManager.ConnectedClients)
|
||||
{
|
||||
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.");
|
||||
}
|
||||
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,25 +364,10 @@ namespace Unity.Netcode
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
|
||||
{
|
||||
for (int i = networkClient.OwnedObjects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (networkClient.OwnedObjects[i] == networkObject)
|
||||
{
|
||||
networkClient.OwnedObjects.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
networkClient.OwnedObjects.Add(networkObject);
|
||||
}
|
||||
|
||||
networkObject.OwnerClientId = clientId;
|
||||
|
||||
if (TryGetNetworkClient(clientId, out NetworkClient newNetworkClient))
|
||||
{
|
||||
newNetworkClient.OwnedObjects.Add(networkObject);
|
||||
}
|
||||
// Server adds entries for all client ownership
|
||||
UpdateOwnershipTable(networkObject, networkObject.OwnerClientId);
|
||||
|
||||
var message = new ChangeOwnershipMessage
|
||||
{
|
||||
@@ -414,7 +498,7 @@ namespace Unity.Netcode
|
||||
}
|
||||
|
||||
// Ran on both server and client
|
||||
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, bool destroyWithScene)
|
||||
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
|
||||
{
|
||||
if (networkObject == null)
|
||||
{
|
||||
@@ -452,15 +536,12 @@ namespace Unity.Netcode
|
||||
throw new SpawnStateException("Object is already spawned");
|
||||
}
|
||||
|
||||
if (sceneObject.Header.HasNetworkVariables)
|
||||
{
|
||||
networkObject.SetNetworkVariableData(variableData);
|
||||
}
|
||||
networkObject.SetNetworkVariableData(variableData);
|
||||
|
||||
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.Header.NetworkObjectId, sceneObject.Header.IsSceneObject, sceneObject.Header.IsPlayerObject, sceneObject.Header.OwnerClientId, destroyWithScene);
|
||||
}
|
||||
|
||||
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong? ownerClientId, bool destroyWithScene)
|
||||
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
|
||||
{
|
||||
if (SpawnedObjects.ContainsKey(networkId))
|
||||
{
|
||||
@@ -471,38 +552,45 @@ namespace Unity.Netcode
|
||||
// this initialization really should be at the bottom of the function
|
||||
networkObject.IsSpawned = true;
|
||||
|
||||
// this initialization really should be at the top of this function. If and when we break the
|
||||
// this initialization really should be at the top of this function. If and when we break the
|
||||
// NetworkVariable dependency on NetworkBehaviour, this otherwise creates problems because
|
||||
// SetNetworkVariableData above calls InitializeVariables, and the 'baked out' data isn't ready there;
|
||||
// the current design banks on getting the network behaviour set and then only reading from it
|
||||
// after the below initialization code. However cowardice compels me to hold off on moving this until
|
||||
// that commit
|
||||
// the current design banks on getting the network behaviour set and then only reading from it after the
|
||||
// below initialization code. However cowardice compels me to hold off on moving this until that commit
|
||||
networkObject.IsSceneObject = sceneObject;
|
||||
networkObject.NetworkObjectId = networkId;
|
||||
|
||||
networkObject.DestroyWithScene = sceneObject || destroyWithScene;
|
||||
|
||||
networkObject.OwnerClientIdInternal = ownerClientId;
|
||||
networkObject.OwnerClientId = ownerClientId;
|
||||
|
||||
networkObject.IsPlayerObject = playerObject;
|
||||
|
||||
SpawnedObjects.Add(networkObject.NetworkObjectId, networkObject);
|
||||
SpawnedObjectsList.Add(networkObject);
|
||||
|
||||
if (ownerClientId != null)
|
||||
if (NetworkManager.IsServer)
|
||||
{
|
||||
if (NetworkManager.IsServer)
|
||||
if (playerObject)
|
||||
{
|
||||
if (playerObject)
|
||||
// If there was an already existing player object for this player, then mark it as no longer
|
||||
// a player object.
|
||||
if (NetworkManager.ConnectedClients[ownerClientId].PlayerObject != null)
|
||||
{
|
||||
NetworkManager.ConnectedClients[ownerClientId.Value].PlayerObject = networkObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.ConnectedClients[ownerClientId.Value].OwnedObjects.Add(networkObject);
|
||||
NetworkManager.ConnectedClients[ownerClientId].PlayerObject.IsPlayerObject = false;
|
||||
}
|
||||
NetworkManager.ConnectedClients[ownerClientId].PlayerObject = networkObject;
|
||||
}
|
||||
else if (playerObject && ownerClientId.Value == NetworkManager.LocalClientId)
|
||||
}
|
||||
else if (ownerClientId == NetworkManager.LocalClientId)
|
||||
{
|
||||
if (playerObject)
|
||||
{
|
||||
// If there was an already existing player object for this player, then mark it as no longer a player object.
|
||||
if (NetworkManager.LocalClient.PlayerObject != null)
|
||||
{
|
||||
NetworkManager.LocalClient.PlayerObject.IsPlayerObject = false;
|
||||
}
|
||||
NetworkManager.LocalClient.PlayerObject = networkObject;
|
||||
}
|
||||
}
|
||||
@@ -549,25 +637,21 @@ namespace Unity.Netcode
|
||||
|
||||
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
|
||||
{
|
||||
if (!NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
||||
//Currently, if this is called and the clientId (destination) is the server's client Id, this case will be checked
|
||||
// within the below Send function. To avoid unwarranted allocation of a PooledNetworkBuffer placing this check here. [NSS]
|
||||
if (NetworkManager.IsServer && clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
//Currently, if this is called and the clientId (destination) is the server's client Id, this case
|
||||
//will be checked within the below Send function. To avoid unwarranted allocation of a PooledNetworkBuffer
|
||||
//placing this check here. [NSS]
|
||||
if (NetworkManager.IsServer && clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var message = new CreateObjectMessage
|
||||
{
|
||||
ObjectInfo = networkObject.GetMessageSceneObject(clientId)
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
|
||||
|
||||
networkObject.MarkVariablesDirty();
|
||||
return;
|
||||
}
|
||||
|
||||
var message = new CreateObjectMessage
|
||||
{
|
||||
ObjectInfo = networkObject.GetMessageSceneObject(clientId)
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
|
||||
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
|
||||
|
||||
networkObject.MarkVariablesDirty();
|
||||
}
|
||||
|
||||
internal ulong? GetSpawnParentId(NetworkObject networkObject)
|
||||
@@ -605,14 +689,12 @@ namespace Unity.Netcode
|
||||
// Makes scene objects ready to be reused
|
||||
internal void ServerResetShudownStateForSceneObjects()
|
||||
{
|
||||
foreach (var sobj in SpawnedObjectsList)
|
||||
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSceneObject != null && c.IsSceneObject == true);
|
||||
foreach (var sobj in networkObjects)
|
||||
{
|
||||
if ((sobj.IsSceneObject != null && sobj.IsSceneObject == true) || sobj.DestroyWithScene)
|
||||
{
|
||||
sobj.IsSpawned = false;
|
||||
sobj.DestroyWithScene = false;
|
||||
sobj.IsSceneObject = null;
|
||||
}
|
||||
sobj.IsSpawned = false;
|
||||
sobj.DestroyWithScene = false;
|
||||
sobj.IsSceneObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,14 +735,12 @@ namespace Unity.Netcode
|
||||
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);
|
||||
// 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
|
||||
else if (networkObjects[i].IsSceneObject != null && !networkObjects[i].IsSceneObject.Value)
|
||||
{
|
||||
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
|
||||
}
|
||||
@@ -711,9 +791,10 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var networkObject in networkObjectsToSpawn)
|
||||
{
|
||||
SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, null, true);
|
||||
SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, networkObject.OwnerClientId, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,18 +838,6 @@ namespace Unity.Netcode
|
||||
}
|
||||
}
|
||||
|
||||
if (!networkObject.IsOwnedByServer && !networkObject.IsPlayerObject && TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
|
||||
{
|
||||
//Someone owns it.
|
||||
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
|
||||
{
|
||||
if (networkClient.OwnedObjects[i].NetworkObjectId == networkObject.NetworkObjectId)
|
||||
{
|
||||
networkClient.OwnedObjects.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
networkObject.InvokeBehaviourNetworkDespawn();
|
||||
|
||||
if (NetworkManager != null && NetworkManager.IsServer)
|
||||
@@ -782,38 +851,31 @@ namespace Unity.Netcode
|
||||
});
|
||||
}
|
||||
|
||||
if (NetworkManager.NetworkConfig.UseSnapshotSpawn)
|
||||
if (networkObject != null)
|
||||
{
|
||||
networkObject.SnapshotDespawn();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (networkObject != null)
|
||||
// As long as we have any remaining clients, then notify of the object being destroy.
|
||||
if (NetworkManager.ConnectedClientsList.Count > 0)
|
||||
{
|
||||
// As long as we have any remaining clients, then notify of the object being destroy.
|
||||
if (NetworkManager.ConnectedClientsList.Count > 0)
|
||||
m_TargetClientIds.Clear();
|
||||
|
||||
// We keep only the client for which the object is visible
|
||||
// as the other clients have them already despawned
|
||||
foreach (var clientId in NetworkManager.ConnectedClientsIds)
|
||||
{
|
||||
m_TargetClientIds.Clear();
|
||||
|
||||
// We keep only the client for which the object is visible
|
||||
// as the other clients have them already despawned
|
||||
foreach (var clientId in NetworkManager.ConnectedClientsIds)
|
||||
if (networkObject.IsNetworkVisibleTo(clientId))
|
||||
{
|
||||
if (networkObject.IsNetworkVisibleTo(clientId))
|
||||
{
|
||||
m_TargetClientIds.Add(clientId);
|
||||
}
|
||||
m_TargetClientIds.Add(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = networkObject.NetworkObjectId
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
|
||||
foreach (var targetClientId in m_TargetClientIds)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size);
|
||||
}
|
||||
var message = new DestroyObjectMessage
|
||||
{
|
||||
NetworkObjectId = networkObject.NetworkObjectId
|
||||
};
|
||||
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
|
||||
foreach (var targetClientId in m_TargetClientIds)
|
||||
{
|
||||
NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
#if UNITY_UNET_PRESENT
|
||||
using System;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Unity.Netcode.Transports.UNET
|
||||
{
|
||||
/// <summary>
|
||||
/// A transport channel used by the netcode
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class UNetChannel
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the channel
|
||||
/// </summary>
|
||||
#if UNITY_EDITOR
|
||||
[ReadOnly]
|
||||
#endif
|
||||
public byte Id;
|
||||
|
||||
/// <summary>
|
||||
/// The type of channel
|
||||
/// </summary>
|
||||
public QosType Type;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private class ReadOnlyAttribute : PropertyAttribute { }
|
||||
|
||||
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
|
||||
private class ReadOnlyDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
// Saving previous GUI enabled value
|
||||
var previousGUIState = GUI.enabled;
|
||||
|
||||
// Disabling edit for property
|
||||
GUI.enabled = false;
|
||||
|
||||
// Drawing Property
|
||||
EditorGUI.PropertyField(position, property, label);
|
||||
|
||||
// Setting old GUI enabled value
|
||||
GUI.enabled = previousGUIState;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e864534da30ef604992c0ed33c75d3c6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Runtime/Transports/UTP.meta
Normal file
8
Runtime/Transports/UTP.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81887adf6d9ca40c9b70728b7018b6f5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
96
Runtime/Transports/UTP/BatchedReceiveQueue.cs
Normal file
96
Runtime/Transports/UTP/BatchedReceiveQueue.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using Unity.Networking.Transport;
|
||||
|
||||
namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
/// <summary>Queue for batched messages received through UTP.</summary>
|
||||
/// <remarks>This is meant as a companion to <see cref="BatchedSendQueue"/>.</remarks>
|
||||
internal class BatchedReceiveQueue
|
||||
{
|
||||
private byte[] m_Data;
|
||||
private int m_Offset;
|
||||
private int m_Length;
|
||||
|
||||
public bool IsEmpty => m_Length <= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new receive queue from a <see cref="DataStreamReader"/> returned by
|
||||
/// <see cref="NetworkDriver"/> when popping a data event.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="DataStreamReader"/> to construct from.</param>
|
||||
public BatchedReceiveQueue(DataStreamReader reader)
|
||||
{
|
||||
m_Data = new byte[reader.Length];
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = m_Data)
|
||||
{
|
||||
reader.ReadBytes(dataPtr, reader.Length);
|
||||
}
|
||||
}
|
||||
|
||||
m_Offset = 0;
|
||||
m_Length = reader.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Push the entire data from a <see cref="DataStreamReader"/> (as returned by popping an
|
||||
/// event from a <see cref="NetworkDriver">) to the queue.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="DataStreamReader"/> to push the data of.</param>
|
||||
public void PushReader(DataStreamReader reader)
|
||||
{
|
||||
// Resize the array and copy the existing data to the beginning if there's not enough
|
||||
// room to copy the reader's data at the end of the existing data.
|
||||
var available = m_Data.Length - (m_Offset + m_Length);
|
||||
if (available < reader.Length)
|
||||
{
|
||||
if (m_Length > 0)
|
||||
{
|
||||
Array.Copy(m_Data, m_Offset, m_Data, 0, m_Length);
|
||||
}
|
||||
|
||||
m_Offset = 0;
|
||||
|
||||
while (m_Data.Length - m_Length < reader.Length)
|
||||
{
|
||||
Array.Resize(ref m_Data, m_Data.Length * 2);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* dataPtr = m_Data)
|
||||
{
|
||||
reader.ReadBytes(dataPtr + m_Offset + m_Length, reader.Length);
|
||||
}
|
||||
}
|
||||
|
||||
m_Length += reader.Length;
|
||||
}
|
||||
|
||||
/// <summary>Pop the next full message in the queue.</summary>
|
||||
/// <returns>The message, or the default value if no more full messages.</returns>
|
||||
public ArraySegment<byte> PopMessage()
|
||||
{
|
||||
if (m_Length < sizeof(int))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var messageLength = BitConverter.ToInt32(m_Data, m_Offset);
|
||||
|
||||
if (m_Length - sizeof(int) < messageLength)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var data = new ArraySegment<byte>(m_Data, m_Offset + sizeof(int), messageLength);
|
||||
|
||||
m_Offset += sizeof(int) + messageLength;
|
||||
m_Length -= sizeof(int) + messageLength;
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c275febadb27c4d18b41218e3353b84b
|
||||
guid: e9ead10b891184bd5b8f2650fd66a5b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
233
Runtime/Transports/UTP/BatchedSendQueue.cs
Normal file
233
Runtime/Transports/UTP/BatchedSendQueue.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Networking.Transport;
|
||||
|
||||
namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
/// <summary>Queue for batched messages meant to be sent through UTP.</summary>
|
||||
/// <remarks>
|
||||
/// Messages should be pushed on the queue with <see cref="PushMessage"/>. To send batched
|
||||
/// messages, call <see cref="FillWriter"> with the <see cref="DataStreamWriter"/> obtained from
|
||||
/// <see cref="NetworkDriver.BeginSend"/>. This will fill the writer with as many messages as
|
||||
/// possible. If the send is successful, call <see cref="Consume"/> to remove the data from the
|
||||
/// queue.
|
||||
///
|
||||
/// This is meant as a companion to <see cref="BatchedReceiveQueue"/>, which should be used to
|
||||
/// read messages sent with this queue.
|
||||
/// </remarks>
|
||||
internal struct BatchedSendQueue : IDisposable
|
||||
{
|
||||
private NativeArray<byte> m_Data;
|
||||
private NativeArray<int> m_HeadTailIndices;
|
||||
|
||||
/// <summary>Overhead that is added to each message in the queue.</summary>
|
||||
public const int PerMessageOverhead = sizeof(int);
|
||||
|
||||
// Indices into m_HeadTailIndicies.
|
||||
private const int k_HeadInternalIndex = 0;
|
||||
private const int k_TailInternalIndex = 1;
|
||||
|
||||
/// <summary>Index of the first byte of the oldest data in the queue.</summary>
|
||||
private int HeadIndex
|
||||
{
|
||||
get { return m_HeadTailIndices[k_HeadInternalIndex]; }
|
||||
set { m_HeadTailIndices[k_HeadInternalIndex] = value; }
|
||||
}
|
||||
|
||||
/// <summary>Index one past the last byte of the most recent data in the queue.</summary>
|
||||
private int TailIndex
|
||||
{
|
||||
get { return m_HeadTailIndices[k_TailInternalIndex]; }
|
||||
set { m_HeadTailIndices[k_TailInternalIndex] = value; }
|
||||
}
|
||||
|
||||
public int Length => TailIndex - HeadIndex;
|
||||
|
||||
public bool IsEmpty => HeadIndex == TailIndex;
|
||||
|
||||
public bool IsCreated => m_Data.IsCreated;
|
||||
|
||||
/// <summary>Construct a new empty send queue.</summary>
|
||||
/// <param name="capacity">Maximum capacity of the send queue.</param>
|
||||
public BatchedSendQueue(int capacity)
|
||||
{
|
||||
m_Data = new NativeArray<byte>(capacity, Allocator.Persistent);
|
||||
m_HeadTailIndices = new NativeArray<int>(2, Allocator.Persistent);
|
||||
|
||||
HeadIndex = 0;
|
||||
TailIndex = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsCreated)
|
||||
{
|
||||
m_Data.Dispose();
|
||||
m_HeadTailIndices.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Append data at the tail of the queue. No safety checks.</summary>
|
||||
private void AppendDataAtTail(ArraySegment<byte> data)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var writer = new DataStreamWriter((byte*)m_Data.GetUnsafePtr() + TailIndex, m_Data.Length - TailIndex);
|
||||
|
||||
writer.WriteInt(data.Count);
|
||||
|
||||
fixed (byte* dataPtr = data.Array)
|
||||
{
|
||||
writer.WriteBytes(dataPtr + data.Offset, data.Count);
|
||||
}
|
||||
}
|
||||
|
||||
TailIndex += sizeof(int) + data.Count;
|
||||
}
|
||||
|
||||
/// <summary>Append a new message to the queue.</summary>
|
||||
/// <param name="message">Message to append to the queue.</param>
|
||||
/// <returns>
|
||||
/// Whether the message was appended successfully. The only way it can fail is if there's
|
||||
/// no more room in the queue. On failure, nothing is written to the queue.
|
||||
/// </returns>
|
||||
public bool PushMessage(ArraySegment<byte> message)
|
||||
{
|
||||
if (!IsCreated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there's enough room after the current tail index.
|
||||
if (m_Data.Length - TailIndex >= sizeof(int) + message.Count)
|
||||
{
|
||||
AppendDataAtTail(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if there would be enough room if we moved data at the beginning of m_Data.
|
||||
if (m_Data.Length - TailIndex + HeadIndex >= sizeof(int) + message.Count)
|
||||
{
|
||||
// Move the data back at the beginning of m_Data.
|
||||
unsafe
|
||||
{
|
||||
UnsafeUtility.MemMove(m_Data.GetUnsafePtr(), (byte*)m_Data.GetUnsafePtr() + HeadIndex, Length);
|
||||
}
|
||||
|
||||
TailIndex = Length;
|
||||
HeadIndex = 0;
|
||||
|
||||
AppendDataAtTail(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill as much of a <see cref="DataStreamWriter"/> as possible with data from the head of
|
||||
/// the queue. Only full messages (and their length) are written to the writer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does NOT actually consume anything from the queue. That is, calling this method
|
||||
/// does not reduce the length of the queue. Callers are expected to call
|
||||
/// <see cref="Consume"/> with the value returned by this method afterwards if the data can
|
||||
/// be safely removed from the queue (e.g. if it was sent successfully).
|
||||
///
|
||||
/// This method should not be used together with <see cref="FillWriterWithBytes"> since this
|
||||
/// could lead to a corrupted queue.
|
||||
/// </remarks>
|
||||
/// <param name="writer">The <see cref="DataStreamWriter"/> to write to.</param>
|
||||
/// <returns>How many bytes were written to the writer.</returns>
|
||||
public int FillWriterWithMessages(ref DataStreamWriter writer)
|
||||
{
|
||||
if (!IsCreated || Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
var reader = new DataStreamReader((byte*)m_Data.GetUnsafePtr() + HeadIndex, Length);
|
||||
|
||||
var writerAvailable = writer.Capacity;
|
||||
var readerOffset = 0;
|
||||
|
||||
while (readerOffset < Length)
|
||||
{
|
||||
reader.SeekSet(readerOffset);
|
||||
var messageLength = reader.ReadInt();
|
||||
|
||||
if (writerAvailable < sizeof(int) + messageLength)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteInt(messageLength);
|
||||
|
||||
var messageOffset = HeadIndex + reader.GetBytesRead();
|
||||
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + messageOffset, messageLength);
|
||||
|
||||
writerAvailable -= sizeof(int) + messageLength;
|
||||
readerOffset += sizeof(int) + messageLength;
|
||||
}
|
||||
}
|
||||
|
||||
return writer.Capacity - writerAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill the given <see cref="DataStreamWriter"/> with as many bytes from the queue as
|
||||
/// possible, disregarding message boundaries.
|
||||
/// </summary>
|
||||
///<remarks>
|
||||
/// This does NOT actually consume anything from the queue. That is, calling this method
|
||||
/// does not reduce the length of the queue. Callers are expected to call
|
||||
/// <see cref="Consume"/> with the value returned by this method afterwards if the data can
|
||||
/// be safely removed from the queue (e.g. if it was sent successfully).
|
||||
///
|
||||
/// This method should not be used together with <see cref="FillWriterWithMessages"/> since
|
||||
/// this could lead to reading messages from a corrupted queue.
|
||||
/// </remarks>
|
||||
/// <param name="writer">The <see cref="DataStreamWriter"/> to write to.</param>
|
||||
/// <returns>How many bytes were written to the writer.</returns>
|
||||
public int FillWriterWithBytes(ref DataStreamWriter writer)
|
||||
{
|
||||
if (!IsCreated || Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var copyLength = Math.Min(writer.Capacity, Length);
|
||||
|
||||
unsafe
|
||||
{
|
||||
writer.WriteBytes((byte*)m_Data.GetUnsafePtr() + HeadIndex, copyLength);
|
||||
}
|
||||
|
||||
return copyLength;
|
||||
}
|
||||
|
||||
/// <summary>Consume a number of bytes from the head of the queue.</summary>
|
||||
/// <remarks>
|
||||
/// This should only be called with a size that matches the last value returned by
|
||||
/// <see cref="FillWriter"/>. Anything else will result in a corrupted queue.
|
||||
/// </remarks>
|
||||
/// <param name="size">Number of bytes to consume from the queue.</param>
|
||||
public void Consume(int size)
|
||||
{
|
||||
if (size >= Length)
|
||||
{
|
||||
HeadIndex = 0;
|
||||
TailIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
HeadIndex += size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd9e1475e8c8e4a6d935fe2409e3bd26
|
||||
guid: ddf8f97f695d740f297dc42242b76b8c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
8
Runtime/Transports/UTP/NetworkMetricsContext.cs
Normal file
8
Runtime/Transports/UTP/NetworkMetricsContext.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
public struct NetworkMetricsContext
|
||||
{
|
||||
public uint PacketSentCount;
|
||||
public uint PacketReceivedCount;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cf75026c2ab86646aac16b39d7259ad
|
||||
guid: adb0270501ff1421896ce15cc75bd56a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
70
Runtime/Transports/UTP/NetworkMetricsPipelineStage.cs
Normal file
70
Runtime/Transports/UTP/NetworkMetricsPipelineStage.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
#if MULTIPLAYER_TOOLS
|
||||
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
|
||||
using AOT;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Networking.Transport;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Transports.UTP
|
||||
{
|
||||
[BurstCompile]
|
||||
internal unsafe struct NetworkMetricsPipelineStage : INetworkPipelineStage
|
||||
{
|
||||
static TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate> ReceiveFunction = new TransportFunctionPointer<NetworkPipelineStage.ReceiveDelegate>(Receive);
|
||||
static TransportFunctionPointer<NetworkPipelineStage.SendDelegate> SendFunction = new TransportFunctionPointer<NetworkPipelineStage.SendDelegate>(Send);
|
||||
static TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate> InitializeConnectionFunction = new TransportFunctionPointer<NetworkPipelineStage.InitializeConnectionDelegate>(InitializeConnection);
|
||||
|
||||
public NetworkPipelineStage StaticInitialize(byte* staticInstanceBuffer,
|
||||
int staticInstanceBufferLength,
|
||||
NetworkSettings settings)
|
||||
{
|
||||
return new NetworkPipelineStage(
|
||||
ReceiveFunction,
|
||||
SendFunction,
|
||||
InitializeConnectionFunction,
|
||||
ReceiveCapacity: 0,
|
||||
SendCapacity: 0,
|
||||
HeaderCapacity: 0,
|
||||
SharedStateCapacity: UnsafeUtility.SizeOf<NetworkMetricsContext>());
|
||||
}
|
||||
|
||||
public int StaticSize => 0;
|
||||
|
||||
[BurstCompile(DisableDirectCall = true)]
|
||||
[MonoPInvokeCallback(typeof(NetworkPipelineStage.ReceiveDelegate))]
|
||||
private static void Receive(ref NetworkPipelineContext networkPipelineContext,
|
||||
ref InboundRecvBuffer inboundReceiveBuffer,
|
||||
ref NetworkPipelineStage.Requests requests,
|
||||
int systemHeaderSize)
|
||||
{
|
||||
var networkMetricContext = (NetworkMetricsContext*)networkPipelineContext.internalSharedProcessBuffer;
|
||||
networkMetricContext->PacketReceivedCount++;
|
||||
}
|
||||
|
||||
[BurstCompile(DisableDirectCall = true)]
|
||||
[MonoPInvokeCallback(typeof(NetworkPipelineStage.SendDelegate))]
|
||||
private static int Send(ref NetworkPipelineContext networkPipelineContext,
|
||||
ref InboundSendBuffer inboundSendBuffer,
|
||||
ref NetworkPipelineStage.Requests requests,
|
||||
int systemHeaderSize)
|
||||
{
|
||||
var networkMetricContext = (NetworkMetricsContext*)networkPipelineContext.internalSharedProcessBuffer;
|
||||
networkMetricContext->PacketSentCount++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
[BurstCompile(DisableDirectCall = true)]
|
||||
[MonoPInvokeCallback(typeof(NetworkPipelineStage.InitializeConnectionDelegate))]
|
||||
private static void InitializeConnection(byte* staticInstanceBuffer, int staticInstanceBufferLength,
|
||||
byte* sendProcessBuffer, int sendProcessBufferLength, byte* receiveProcessBuffer, int receiveProcessBufferLength,
|
||||
byte* sharedProcessBuffer, int sharedProcessBufferLength)
|
||||
{
|
||||
var networkMetricContext = (NetworkMetricsContext*)sharedProcessBuffer;
|
||||
networkMetricContext->PacketSentCount = 0;
|
||||
networkMetricContext->PacketReceivedCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69c3c1c5a885d4aed99ee2e1fa40f763
|
||||
guid: 52b1ce9f83ce049c59327064bf70cee8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
1222
Runtime/Transports/UTP/UnityTransport.cs
Normal file
1222
Runtime/Transports/UTP/UnityTransport.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Runtime/Transports/UTP/UnityTransport.cs.meta
Normal file
11
Runtime/Transports/UTP/UnityTransport.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6960e84d07fb87f47956e7a81d71c4e6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -10,7 +10,9 @@
|
||||
"Unity.Multiplayer.Tools.NetStats",
|
||||
"Unity.Multiplayer.Tools.NetStatsReporting",
|
||||
"Unity.Multiplayer.Tools.NetworkSolutionInterface",
|
||||
"Unity.Collections"
|
||||
"Unity.Networking.Transport",
|
||||
"Unity.Collections",
|
||||
"Unity.Burst"
|
||||
],
|
||||
"allowUnsafeCode": true,
|
||||
"versionDefines": [
|
||||
@@ -26,8 +28,8 @@
|
||||
},
|
||||
{
|
||||
"name": "com.unity.multiplayer.tools",
|
||||
"expression": "1.0.0-pre.4",
|
||||
"define": "MULTIPLAYER_TOOLS_1_0_0_PRE_4"
|
||||
"expression": "1.0.0-pre.7",
|
||||
"define": "MULTIPLAYER_TOOLS_1_0_0_PRE_7"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user