com.unity.netcode.gameobjects@1.0.1

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.1] - 2022-08-23

### Changed

- Changed version to 1.0.1. (#2131)
- Updated dependency on `com.unity.transport` to 1.2.0. (#2129)
- When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081)
- Preformance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects

### Fixed

- Fixed an issue where reading/writing more than 8 bits at a time with BitReader/BitWriter would write/read from the wrong place, returning and incorrect result. (#2130)
- Fixed issue with the internal `NetworkTransformState.m_Bitset` flag not getting cleared upon the next tick advancement. (#2110)
- Fixed interpolation issue with `NetworkTransform.Teleport`. (#2110)
- Fixed issue where the authoritative side was interpolating its transform. (#2110)
- Fixed Owner-written NetworkVariable infinitely write themselves (#2109)
- Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099)
- Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097)
- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096)
- Fixed an issue where destroying a NetworkBehaviour would not deregister it from the parent NetworkObject, leading to exceptions when the parent was later destroyed. (#2091)
- Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086)
- Fixed `NetworkAnimator` synchronizing transitions twice due to it detecting the change in animation state once a transition is started by a trigger. (#2084)
- Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076)
- Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074)
- Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074)
- Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067)
- Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062)
This commit is contained in:
Unity Technologies
2022-08-23 00:00:00 +00:00
parent 18ffd5fdc8
commit e15bd056c5
58 changed files with 2192 additions and 1676 deletions

View File

@@ -331,7 +331,8 @@ namespace Unity.Netcode
// in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?)
// or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages
// per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages.
if (m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress))
// We must check IsSpawned, otherwise a warning will be logged under certain valid conditions (see OnDestroy)
if (IsSpawned && m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
@@ -582,9 +583,11 @@ namespace Unity.Netcode
{
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
}
MarkVariablesDirty(false);
}
internal void VariableUpdate(ulong targetClientId)
internal void PreVariableUpdate()
{
if (!m_VarInit)
{
@@ -592,6 +595,10 @@ namespace Unity.Netcode
}
PreNetworkVariableWrite();
}
internal void VariableUpdate(ulong targetClientId)
{
NetworkVariableUpdate(targetClientId, NetworkBehaviourId);
}
@@ -663,11 +670,11 @@ namespace Unity.Netcode
return false;
}
internal void MarkVariablesDirty()
internal void MarkVariablesDirty(bool dirty)
{
for (int j = 0; j < NetworkVariableFields.Count; j++)
{
NetworkVariableFields[j].SetDirty(true);
NetworkVariableFields[j].SetDirty(dirty);
}
}
@@ -759,6 +766,14 @@ namespace Unity.Netcode
/// </summary>
public virtual void OnDestroy()
{
if (NetworkObject != null && NetworkObject.IsSpawned && IsSpawned)
{
// If the associated NetworkObject is still spawned then this
// NetworkBehaviour will be removed from the NetworkObject's
// ChildNetworkBehaviours list.
NetworkObject.OnNetworkBehaviourDestroyed(this);
}
// this seems odd to do here, but in fact especially in tests we can find ourselves
// here without having called InitializedVariables, which causes problems if any
// of those variables use native containers (e.g. NetworkList) as they won't be
@@ -770,6 +785,7 @@ namespace Unity.Netcode
InitializeVariables();
}
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
NetworkVariableFields[i].Dispose();

View File

@@ -8,12 +8,17 @@ namespace Unity.Netcode
/// </summary>
public class NetworkBehaviourUpdater
{
private HashSet<NetworkObject> m_Touched = new HashSet<NetworkObject>();
private HashSet<NetworkObject> m_DirtyNetworkObjects = new HashSet<NetworkObject>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}");
#endif
internal void AddForUpdate(NetworkObject networkObject)
{
m_DirtyNetworkObjects.Add(networkObject);
}
internal void NetworkBehaviourUpdate(NetworkManager networkManager)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
@@ -23,57 +28,59 @@ namespace Unity.Netcode
{
if (networkManager.IsServer)
{
m_Touched.Clear();
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
foreach (var dirtyObj in m_DirtyNetworkObjects)
{
var client = networkManager.ConnectedClientsList[i];
var spawnedObjs = networkManager.SpawnManager.SpawnedObjectsList;
m_Touched.UnionWith(spawnedObjs);
foreach (var sobj in spawnedObjs)
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
{
if (sobj.IsNetworkVisibleTo(client.ClientId))
dirtyObj.ChildNetworkBehaviours[k].PreVariableUpdate();
}
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
{
var client = networkManager.ConnectedClientsList[i];
if (networkManager.IsHost && client.ClientId == networkManager.LocalClientId)
{
continue;
}
if (dirtyObj.IsNetworkVisibleTo(client.ClientId))
{
// Sync just the variables for just the objects this client sees
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
dirtyObj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
}
}
}
}
// Now, reset all the no-longer-dirty variables
foreach (var sobj in m_Touched)
{
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
}
}
}
else
{
// when client updates the server, it tells it about all its objects
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList)
foreach (var sobj in m_DirtyNetworkObjects)
{
if (sobj.IsOwner)
{
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].PreVariableUpdate();
}
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.ServerClientId);
}
}
}
// Now, reset all the no-longer-dirty variables
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList)
}
// Now, reset all the no-longer-dirty variables
foreach (var dirtyobj in m_DirtyNetworkObjects)
{
for (int k = 0; k < dirtyobj.ChildNetworkBehaviours.Count; k++)
{
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
}
dirtyobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
}
}
m_DirtyNetworkObjects.Clear();
}
finally
{

View File

@@ -54,7 +54,12 @@ namespace Unity.Netcode
return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.gameObject.name}\"";
}
internal NetworkBehaviourUpdater BehaviourUpdater { get; private set; }
internal NetworkBehaviourUpdater BehaviourUpdater { get; set; }
internal void MarkNetworkObjectDirty(NetworkObject networkObject)
{
BehaviourUpdater.AddForUpdate(networkObject);
}
internal MessagingSystem MessagingSystem { get; private set; }
@@ -1384,6 +1389,19 @@ namespace Unity.Netcode
}
IsConnectedClient = false;
// We need to clean up NetworkObjects before we reset the IsServer
// and IsClient properties. This provides consistency of these two
// property values for NetworkObjects that are still spawned when
// the shutdown cycle begins.
if (SpawnManager != null)
{
SpawnManager.DespawnAndDestroyNetworkObjects();
SpawnManager.ServerResetShudownStateForSceneObjects();
SpawnManager = null;
}
IsServer = false;
IsClient = false;
@@ -1406,14 +1424,6 @@ namespace Unity.Netcode
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
}
if (SpawnManager != null)
{
SpawnManager.DespawnAndDestroyNetworkObjects();
SpawnManager.ServerResetShudownStateForSceneObjects();
SpawnManager = null;
}
if (DeferredMessageManager != null)
{
DeferredMessageManager.CleanupAllTriggers();
@@ -2060,6 +2070,20 @@ namespace Unity.Netcode
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
{
if (MessagingSystem.MessageTypes[index] != null)
{
var orderingMessage = new OrderingMessage
{
Order = index,
Hash = XXHash.Hash32(MessagingSystem.MessageTypes[index].FullName)
};
SendMessage(ref orderingMessage, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
}
}
// If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
if (!NetworkConfig.EnableSceneManagement)
{

View File

@@ -235,9 +235,19 @@ namespace Unity.Netcode
}
/// <summary>
/// Shows a previously hidden <see cref="NetworkObject"/> to a client
/// Makes the previously hidden <see cref="NetworkObject"/> "netcode visible" to the targeted client.
/// </summary>
/// <param name="clientId">The client to show the <see cref="NetworkObject"/> to</param>
/// <remarks>
/// Usage: Use to start sending updates for a previously hidden <see cref="NetworkObject"/> to the targeted client.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be instantiated and spawned on the targeted client side.<br />
/// In-Scene Placed: The instantiated but despawned <see cref="NetworkObject"/>s will be spawned on the targeted client side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkShow(ulong)"/><br />
/// <see cref="NetworkHide(ulong)"/> or <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="clientId">The targeted client</param>
public void NetworkShow(ulong clientId)
{
if (!IsSpawned)
@@ -260,11 +270,22 @@ namespace Unity.Netcode
NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this);
}
/// <summary>
/// Shows a list of previously hidden <see cref="NetworkObject"/>s to a client
/// Makes a list of previously hidden <see cref="NetworkObject"/>s "netcode visible" for the client specified.
/// </summary>
/// <param name="networkObjects">The <see cref="NetworkObject"/>s to show</param>
/// <param name="clientId">The client to show the objects to</param>
/// <remarks>
/// Usage: Use to start sending updates for previously hidden <see cref="NetworkObject"/>s to the targeted client.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be instantiated and spawned on the targeted client's side.<br />
/// In-Scene Placed: Already instantiated but despawned <see cref="NetworkObject"/>s will be spawned on the targeted client's side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkShow(ulong)"/><br />
/// <see cref="NetworkHide(ulong)"/> or <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="networkObjects">The objects to become "netcode visible" to the targeted client</param>
/// <param name="clientId">The targeted client</param>
public static void NetworkShow(List<NetworkObject> networkObjects, ulong clientId)
{
if (networkObjects == null || networkObjects.Count == 0)
@@ -305,9 +326,19 @@ namespace Unity.Netcode
}
/// <summary>
/// Hides a object from a specific client
/// Hides the <see cref="NetworkObject"/> from the targeted client.
/// </summary>
/// <param name="clientId">The client to hide the object for</param>
/// <remarks>
/// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for a currently visible <see cref="NetworkObject"/>.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
/// <see cref="NetworkShow(ulong)"/> or <see cref="NetworkShow(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="clientId">The targeted client</param>
public void NetworkHide(ulong clientId)
{
if (!IsSpawned)
@@ -335,7 +366,7 @@ namespace Unity.Netcode
var message = new DestroyObjectMessage
{
NetworkObjectId = NetworkObjectId,
DestroyGameObject = true
DestroyGameObject = !IsSceneObject.Value
};
// Send destroy call
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
@@ -343,10 +374,20 @@ namespace Unity.Netcode
}
/// <summary>
/// Hides a list of objects from a client
/// Hides a list of <see cref="NetworkObject"/>s from the targeted client.
/// </summary>
/// <param name="networkObjects">The objects to hide</param>
/// <param name="clientId">The client to hide the objects from</param>
/// <remarks>
/// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for the currently visible <see cref="NetworkObject"/>s.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkHide(ulong)"/><br />
/// <see cref="NetworkShow(ulong)"/> or <see cref="NetworkShow(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="networkObjects">The <see cref="NetworkObject"/>s that will become "netcode invisible" to the targeted client</param>
/// <param name="clientId">The targeted client</param>
public static void NetworkHide(List<NetworkObject> networkObjects, ulong clientId)
{
if (networkObjects == null || networkObjects.Count == 0)
@@ -455,8 +496,8 @@ namespace Unity.Netcode
/// <summary>
/// Spawns a <see cref="NetworkObject"/> across the network and makes it the player object for the given client
/// </summary>
/// <param name="clientId">The clientId whos player object this is</param>
/// <param name="destroyWithScene">Should the object be destroyd when the scene is changed</param>
/// <param name="clientId">The clientId who's player object this is</param>
/// <param name="destroyWithScene">Should the object be destroyed when the scene is changed</param>
public void SpawnAsPlayerObject(ulong clientId, bool destroyWithScene = false)
{
SpawnInternal(destroyWithScene, clientId, true);
@@ -512,7 +553,14 @@ namespace Unity.Netcode
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
ChildNetworkBehaviours[i].InternalOnGainedOwnership();
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{
ChildNetworkBehaviours[i].InternalOnGainedOwnership();
}
else
{
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!");
}
}
}
@@ -697,7 +745,7 @@ namespace Unity.Netcode
// For instance, if we're spawning NetworkObject 5 and its parent is 10, what should happen if we do not have 10 yet?
// let's say 10 is on the way to be replicated in a few frames and we could fix that parent-child relationship later.
//
// If you couldn't find your parent, we put you into OrphanChildren set and everytime we spawn another NetworkObject locally due to replication,
// If you couldn't find your parent, we put you into OrphanChildren set and every time we spawn another NetworkObject locally due to replication,
// we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one.
internal static HashSet<NetworkObject> OrphanChildren = new HashSet<NetworkObject>();
@@ -764,7 +812,14 @@ namespace Unity.Netcode
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
ChildNetworkBehaviours[i].InternalOnNetworkSpawn();
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{
ChildNetworkBehaviours[i].InternalOnNetworkSpawn();
}
else
{
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during spawn!");
}
}
}
@@ -813,12 +868,12 @@ namespace Unity.Netcode
}
}
internal void MarkVariablesDirty()
internal void MarkVariablesDirty(bool dirty)
{
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
var behavior = ChildNetworkBehaviours[i];
behavior.MarkVariablesDirty();
behavior.MarkVariablesDirty(dirty);
}
}
@@ -1164,5 +1219,21 @@ namespace Unity.Netcode
return GlobalObjectIdHash;
}
/// <summary>
/// Removes a NetworkBehaviour from the ChildNetworkBehaviours list when destroyed
/// while the NetworkObject is still spawned.
/// </summary>
internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour)
{
if (networkBehaviour.IsSpawned && IsSpawned)
{
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)");
}
ChildNetworkBehaviours.Remove(networkBehaviour);
}
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Upon connecting, the host sends a series of OrderingMessage to the client so that it can make sure both sides
/// have the same message types in the same positions in
/// - MessagingSystem.m_MessageHandlers
/// - MessagingSystem.m_ReverseTypeMap
/// even if one side has extra messages (compilation, version, patch, or platform differences, etc...)
///
/// The ConnectionRequestedMessage, ConnectionApprovedMessage and OrderingMessage are prioritized at the beginning
/// of the mapping, to guarantee they can be exchanged before the two sides share their ordering
/// The sorting used in also stable so that even if MessageType names share hashes, it will work most of the time
/// </summary>
internal struct OrderingMessage : INetworkMessage
{
public int Order;
public uint Hash;
public void Serialize(FastBufferWriter writer)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
{
throw new OverflowException($"Not enough space in the buffer to write {nameof(OrderingMessage)}");
}
writer.WriteValue(Order);
writer.WriteValue(Hash);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
{
throw new OverflowException($"Not enough data in the buffer to read {nameof(OrderingMessage)}");
}
reader.ReadValue(out Order);
reader.ReadValue(out Hash);
return true;
}
public void Handle(ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).MessagingSystem.ReorderMessage(Order, Hash);
}
}
}

View File

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

View File

@@ -8,6 +8,11 @@ using UnityEngine;
namespace Unity.Netcode
{
internal class HandlerNotRegisteredException : SystemException
{
public HandlerNotRegisteredException() { }
public HandlerNotRegisteredException(string issue) : base(issue) { }
}
internal class InvalidMessageStructureException : SystemException
{
@@ -44,8 +49,9 @@ namespace Unity.Netcode
private NativeList<ReceiveQueueItem> m_IncomingMessageQueue = new NativeList<ReceiveQueueItem>(16, Allocator.Persistent);
private MessageHandler[] m_MessageHandlers = new MessageHandler[255];
private Type[] m_ReverseTypeMap = new Type[255];
// These array will grow as we need more message handlers. 4 is just a starting size.
private MessageHandler[] m_MessageHandlers = new MessageHandler[4];
private Type[] m_ReverseTypeMap = new Type[4];
private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
@@ -59,6 +65,7 @@ namespace Unity.Netcode
internal Type[] MessageTypes => m_ReverseTypeMap;
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
internal uint MessageHandlerCount => m_HighMessageType;
internal uint GetMessageType(Type t)
@@ -75,6 +82,35 @@ namespace Unity.Netcode
public MessageHandler Handler;
}
internal List<MessageWithHandler> PrioritizeMessageOrder(List<MessageWithHandler> allowedTypes)
{
var prioritizedTypes = new List<MessageWithHandler>();
// first pass puts the priority message in the first indices
// Those are the messages that must be delivered in order to allow re-ordering the others later
foreach (var t in allowedTypes)
{
if (t.MessageType.FullName == "Unity.Netcode.ConnectionRequestMessage" ||
t.MessageType.FullName == "Unity.Netcode.ConnectionApprovedMessage" ||
t.MessageType.FullName == "Unity.Netcode.OrderingMessage")
{
prioritizedTypes.Add(t);
}
}
foreach (var t in allowedTypes)
{
if (t.MessageType.FullName != "Unity.Netcode.ConnectionRequestMessage" &&
t.MessageType.FullName != "Unity.Netcode.ConnectionApprovedMessage" &&
t.MessageType.FullName != "Unity.Netcode.OrderingMessage")
{
prioritizedTypes.Add(t);
}
}
return prioritizedTypes;
}
public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvider provider = null)
{
try
@@ -89,6 +125,7 @@ namespace Unity.Netcode
var allowedTypes = provider.GetMessages();
allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName));
allowedTypes = PrioritizeMessageOrder(allowedTypes);
foreach (var type in allowedTypes)
{
RegisterMessageType(type);
@@ -143,6 +180,13 @@ namespace Unity.Netcode
private void RegisterMessageType(MessageWithHandler messageWithHandler)
{
// if we are out of space, perform amortized linear growth
if (m_HighMessageType == m_MessageHandlers.Length)
{
Array.Resize(ref m_MessageHandlers, 2 * m_MessageHandlers.Length);
Array.Resize(ref m_ReverseTypeMap, 2 * m_ReverseTypeMap.Length);
}
m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler;
m_ReverseTypeMap[m_HighMessageType] = messageWithHandler.MessageType;
m_MessageTypes[messageWithHandler.MessageType] = m_HighMessageType++;
@@ -226,6 +270,70 @@ namespace Unity.Netcode
return true;
}
// Moves the handler for the type having hash `targetHash` to the `desiredOrder` position, in the handler list
// This allows the server to tell the client which id it is using for which message and make sure the right
// message is used when deserializing.
internal void ReorderMessage(int desiredOrder, uint targetHash)
{
if (desiredOrder < 0)
{
throw new ArgumentException("ReorderMessage desiredOrder must be positive");
}
if (desiredOrder < m_ReverseTypeMap.Length &&
XXHash.Hash32(m_ReverseTypeMap[desiredOrder].FullName) == targetHash)
{
// matching positions and hashes. All good.
return;
}
Debug.Log($"Unexpected hash for {desiredOrder}");
// Since the message at `desiredOrder` is not the expected one,
// insert an empty placeholder and move the messages down
var typesAsList = new List<Type>(m_ReverseTypeMap);
typesAsList.Insert(desiredOrder, null);
var handlersAsList = new List<MessageHandler>(m_MessageHandlers);
handlersAsList.Insert(desiredOrder, null);
// we added a dummy message, bump the end up
m_HighMessageType++;
// Here, we rely on the server telling us about all messages, in order.
// So, we know the handlers before desiredOrder are correct.
// We start at desiredOrder to not shift them when we insert.
int position = desiredOrder;
bool found = false;
while (position < typesAsList.Count)
{
if (typesAsList[position] != null &&
XXHash.Hash32(typesAsList[position].FullName) == targetHash)
{
found = true;
break;
}
position++;
}
if (found)
{
// Copy the handler and type to the right index
typesAsList[desiredOrder] = typesAsList[position];
handlersAsList[desiredOrder] = handlersAsList[position];
typesAsList.RemoveAt(position);
handlersAsList.RemoveAt(position);
// we removed a copy after moving a message, reduce the high message index
m_HighMessageType--;
}
m_ReverseTypeMap = typesAsList.ToArray();
m_MessageHandlers = handlersAsList.ToArray();
}
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
{
if (header.MessageType >= m_HighMessageType)
@@ -259,18 +367,29 @@ namespace Unity.Netcode
var handler = m_MessageHandlers[header.MessageType];
using (reader)
{
// No user-land message handler exceptions should escape the receive loop.
// If an exception is throw, the message is ignored.
// Example use case: A bad message is received that can't be deserialized and throws
// an OverflowException because it specifies a length greater than the number of bytes in it
// for some dynamic-length value.
try
// This will also log an exception is if the server knows about a message type the client doesn't know
// about. In this case the handler will be null. It is still an issue the user must deal with: If the
// two connecting builds know about different messages, the server should not send a message to a client
// that doesn't know about it
if (handler == null)
{
handler.Invoke(reader, ref context, this);
Debug.LogException(new HandlerNotRegisteredException(header.MessageType.ToString()));
}
catch (Exception e)
else
{
Debug.LogException(e);
// No user-land message handler exceptions should escape the receive loop.
// If an exception is throw, the message is ignored.
// Example use case: A bad message is received that can't be deserialized and throws
// an OverflowException because it specifies a length greater than the number of bytes in it
// for some dynamic-length value.
try
{
handler.Invoke(reader, ref context, this);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)

View File

@@ -63,6 +63,11 @@ namespace Unity.Netcode
return base.IsDirty() || m_DirtyEvents.Length > 0;
}
internal void MarkNetworkObjectDirty()
{
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
}
/// <inheritdoc />
public override void WriteDelta(FastBufferWriter writer)
{
@@ -122,10 +127,26 @@ namespace Unity.Netcode
/// <inheritdoc />
public override void WriteField(FastBufferWriter writer)
{
writer.WriteValueSafe((ushort)m_ListAtLastReset.Length);
for (int i = 0; i < m_ListAtLastReset.Length; i++)
// The listAtLastReset mechanism was put in place to deal with duplicate adds
// upon initial spawn. However, it causes issues with in-scene placed objects
// due to difference in spawn order. In order to address this, we pick the right
// list based on the type of object.
bool isSceneObject = m_NetworkBehaviour.NetworkObject.IsSceneObject != false;
if (isSceneObject)
{
NetworkVariableSerialization<T>.Write(writer, ref m_ListAtLastReset.ElementAt(i));
writer.WriteValueSafe((ushort)m_ListAtLastReset.Length);
for (int i = 0; i < m_ListAtLastReset.Length; i++)
{
NetworkVariableSerialization<T>.Write(writer, ref m_ListAtLastReset.ElementAt(i));
}
}
else
{
writer.WriteValueSafe((ushort)m_List.Length);
for (int i = 0; i < m_List.Length; i++)
{
NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i));
}
}
}
@@ -173,6 +194,7 @@ namespace Unity.Netcode
Index = m_List.Length - 1,
Value = m_List[m_List.Length - 1]
});
MarkNetworkObjectDirty();
}
}
break;
@@ -180,8 +202,16 @@ namespace Unity.Netcode
{
reader.ReadValueSafe(out int index);
NetworkVariableSerialization<T>.Read(reader, out T value);
m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = value;
if (index < m_List.Length)
{
m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = value;
}
else
{
m_List.Add(value);
}
if (OnListChanged != null)
{
@@ -201,6 +231,7 @@ namespace Unity.Netcode
Index = index,
Value = m_List[index]
});
MarkNetworkObjectDirty();
}
}
break;
@@ -233,6 +264,7 @@ namespace Unity.Netcode
Index = index,
Value = value
});
MarkNetworkObjectDirty();
}
}
break;
@@ -260,6 +292,7 @@ namespace Unity.Netcode
Index = index,
Value = value
});
MarkNetworkObjectDirty();
}
}
break;
@@ -295,6 +328,7 @@ namespace Unity.Netcode
Value = value,
PreviousValue = previousValue
});
MarkNetworkObjectDirty();
}
}
break;
@@ -317,6 +351,7 @@ namespace Unity.Netcode
{
Type = eventType
});
MarkNetworkObjectDirty();
}
}
break;
@@ -403,8 +438,15 @@ namespace Unity.Netcode
/// <inheritdoc />
public void Insert(int index, T item)
{
m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = item;
if (index < m_List.Length)
{
m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = item;
}
else
{
m_List.Add(item);
}
var listEvent = new NetworkListEvent<T>()
{
@@ -436,13 +478,15 @@ namespace Unity.Netcode
get => m_List[index];
set
{
var previousValue = m_List[index];
m_List[index] = value;
var listEvent = new NetworkListEvent<T>()
{
Type = NetworkListEvent<T>.EventType.Value,
Index = index,
Value = value
Value = value,
PreviousValue = previousValue
};
HandleAddListEvent(listEvent);
@@ -452,6 +496,7 @@ namespace Unity.Netcode
private void HandleAddListEvent(NetworkListEvent<T> listEvent)
{
m_DirtyEvents.Add(listEvent);
MarkNetworkObjectDirty();
OnListChanged?.Invoke(listEvent);
}

View File

@@ -87,7 +87,7 @@ namespace Unity.Netcode
/// <param name="value">the new value of type `T` to be set/></param>
private protected void Set(T value)
{
m_IsDirty = true;
SetDirty(true);
T previousValue = m_InternalValue;
m_InternalValue = value;
OnValueChanged?.Invoke(previousValue, m_InternalValue);
@@ -119,7 +119,7 @@ namespace Unity.Netcode
if (keepDirtyDelta)
{
m_IsDirty = true;
SetDirty(true);
}
OnValueChanged?.Invoke(previousValue, m_InternalValue);

View File

@@ -54,7 +54,7 @@ namespace Unity.Netcode
/// The <see cref="m_IsDirty"/> property is used to determine if the
/// value of the `NetworkVariable` has changed.
/// </summary>
private protected bool m_IsDirty;
private bool m_IsDirty;
/// <summary>
/// Gets or sets the name of the network variable's instance
@@ -79,6 +79,10 @@ namespace Unity.Netcode
public virtual void SetDirty(bool isDirty)
{
m_IsDirty = isDirty;
if (m_IsDirty && m_NetworkBehaviour != null)
{
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
}
}
/// <summary>
@@ -111,7 +115,7 @@ namespace Unity.Netcode
case NetworkVariableReadPermission.Everyone:
return true;
case NetworkVariableReadPermission.Owner:
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId;
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId || NetworkManager.ServerClientId == clientId;
}
}

View File

@@ -21,6 +21,8 @@ namespace Unity.Netcode
private const int k_BitsPerByte = 8;
private int BytePosition => m_BitPosition >> 3;
/// <summary>
/// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary.
/// </summary>
@@ -98,11 +100,6 @@ namespace Unity.Netcode
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!");
}
if (bitCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!");
}
int checkPos = (int)(m_BitPosition + bitCount);
if (checkPos > m_AllowedBitwiseReadMark)
{
@@ -165,7 +162,7 @@ namespace Unity.Netcode
#endif
int offset = m_BitPosition & 7;
int pos = m_BitPosition >> 3;
int pos = BytePosition;
bit = (m_BufferPointer[pos] & (1 << offset)) != 0;
++m_BitPosition;
}
@@ -175,7 +172,7 @@ namespace Unity.Netcode
{
var val = new T();
byte* ptr = ((byte*)&val) + offsetBytes;
byte* bufferPointer = m_BufferPointer + m_Position;
byte* bufferPointer = m_BufferPointer + BytePosition;
UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead);
m_BitPosition += bytesToRead * k_BitsPerByte;

View File

@@ -29,6 +29,8 @@ namespace Unity.Netcode
get => (m_BitPosition & 7) == 0;
}
private int BytePosition => m_BitPosition >> 3;
internal unsafe BitWriter(FastBufferWriter writer)
{
m_Writer = writer;
@@ -181,7 +183,7 @@ namespace Unity.Netcode
#endif
int offset = m_BitPosition & 7;
int pos = m_BitPosition >> 3;
int pos = BytePosition;
++m_BitPosition;
m_BufferPointer[pos] = (byte)(bit ? (m_BufferPointer[pos] & ~(1 << offset)) | (1 << offset) : (m_BufferPointer[pos] & ~(1 << offset)));
}
@@ -190,7 +192,7 @@ namespace Unity.Netcode
private unsafe void WritePartialValue<T>(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged
{
byte* ptr = ((byte*)&value) + offsetBytes;
byte* bufferPointer = m_BufferPointer + m_Position;
byte* bufferPointer = m_BufferPointer + BytePosition;
UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite);
m_BitPosition += bytesToWrite * k_BitsPerByte;

View File

@@ -576,7 +576,7 @@ namespace Unity.Netcode
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
networkObject.MarkVariablesDirty();
networkObject.MarkVariablesDirty(true);
}
internal ulong? GetSpawnParentId(NetworkObject networkObject)

View File

@@ -158,11 +158,11 @@ namespace Unity.Netcode.Transports.UTP
set => m_MaxPacketQueueSize = value;
}
[Tooltip("The maximum size of a payload that can be handled by the transport.")]
[Tooltip("The maximum size of an unreliable payload that can be handled by the transport.")]
[SerializeField]
private int m_MaxPayloadSize = InitialMaxPayloadSize;
/// <summary>The maximum size of a payload that can be handled by the transport.</summary>
/// <summary>The maximum size of an unreliable payload that can be handled by the transport.</summary>
public int MaxPayloadSize
{
get => m_MaxPayloadSize;
@@ -1148,14 +1148,14 @@ namespace Unity.Netcode.Transports.UTP
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public override void Send(ulong clientId, ArraySegment<byte> payload, NetworkDelivery networkDelivery)
{
if (payload.Count > m_MaxPayloadSize)
var pipeline = SelectSendPipeline(networkDelivery);
if (pipeline != m_ReliableSequencedPipeline && payload.Count > m_MaxPayloadSize)
{
Debug.LogError($"Payload of size {payload.Count} larger than configured 'Max Payload Size' ({m_MaxPayloadSize}).");
Debug.LogError($"Unreliable payload of size {payload.Count} larger than configured 'Max Payload Size' ({m_MaxPayloadSize}).");
return;
}
var pipeline = SelectSendPipeline(networkDelivery);
var sendTarget = new SendTarget(clientId, pipeline);
if (!m_SendQueue.TryGetValue(sendTarget, out var queue))
{
@@ -1285,10 +1285,10 @@ namespace Unity.Netcode.Transports.UTP
SendBatchedMessages(kvp.Key, kvp.Value);
}
// The above flush only puts the message in UTP internal buffers, need the flush send
// job to execute to actually get things out on the wire. This will also ensure any
// disconnect messages are sent out.
m_Driver.ScheduleFlushSend(default).Complete();
// The above flush only puts the message in UTP internal buffers, need an update to
// actually get the messages on the wire. (Normally a flush send would be sufficient,
// but there might be disconnect messages and those require an update call.)
m_Driver.ScheduleUpdate().Complete();
DisposeInternals();
@@ -1325,10 +1325,8 @@ namespace Unity.Netcode.Transports.UTP
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7
NetworkPipelineStageCollection.RegisterPipelineStage(new NetworkMetricsPipelineStage());
#endif
var maxFrameTimeMS = 0;
#if UNITY_EDITOR || DEVELOPMENT_BUILD
maxFrameTimeMS = 100;
ConfigureSimulator();
#endif
@@ -1336,8 +1334,7 @@ namespace Unity.Netcode.Transports.UTP
maxConnectAttempts: transport.m_MaxConnectAttempts,
connectTimeoutMS: transport.m_ConnectTimeoutMS,
disconnectTimeoutMS: transport.m_DisconnectTimeoutMS,
heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS,
maxFrameTimeMS: maxFrameTimeMS);
heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS);
driver = NetworkDriver.Create(m_NetworkSettings);