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:
Unity Technologies
2022-04-01 00:00:00 +00:00
parent 5b4aaa8b59
commit 60e2dabef4
123 changed files with 5751 additions and 3419 deletions

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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)
{

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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 youll have children popping at the wrong location not having their parents global position to root them
// - and then theyll 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

View File

@@ -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;
}
}
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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