diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index a69827ede5..4f2058e5d4 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -15,6 +15,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed +- Improved performance of the NetworkVariable. (#3683) - Improved performance around the NetworkBehaviour component. (#3687) ### Deprecated diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index 0be9500c9f..11fe2e2a7e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -78,8 +78,8 @@ public void Serialize(FastBufferWriter writer, int targetVersion) throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); } - var obj = NetworkBehaviour.NetworkObject; - var networkManager = obj.NetworkManagerOwner; + var networkObject = NetworkBehaviour.NetworkObject; + var networkManager = networkObject.NetworkManagerOwner; var typeName = NetworkBehaviour.__getTypeName(); var nonFragmentedMessageMaxSize = networkManager.MessageManager.NonFragmentedMessageMaxSize; var fragmentedMessageMaxSize = networkManager.MessageManager.FragmentedMessageMaxSize; @@ -117,7 +117,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) if (shouldWrite) { WriteNetworkVariable(ref writer, ref networkVariable, ensureNetworkVariableLengthSafety, nonFragmentedMessageMaxSize, fragmentedMessageMaxSize); - networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(TargetClientId, obj, networkVariable.Name, typeName, writer.Length - startingSize); + networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(TargetClientId, networkObject, networkVariable.Name, typeName, writer.Length - startingSize); } } return; @@ -146,12 +146,12 @@ public void Serialize(FastBufferWriter writer, int targetVersion) var shouldWrite = networkVariable.IsDirty() && networkVariable.CanClientRead(TargetClientId) && (networkManager.IsServer || - (networkVariable.CanWrite && networkVariable.CanSend())); + (networkVariable.CanWrite() && networkVariable.CanSend())); // Prevent the server from writing to the client that owns a given NetworkVariable // Allowing the write would send an old value to the client and cause jitter if (networkVariable.WritePerm == NetworkVariableWritePermission.Owner && - networkVariable.OwnerClientId() == TargetClientId) + networkObject.OwnerClientId == TargetClientId) { shouldWrite = false; } @@ -159,9 +159,8 @@ public void Serialize(FastBufferWriter writer, int targetVersion) // The object containing the behaviour we're about to process is about to be shown to this client // As a result, the client will get the fully serialized NetworkVariable and would be confused by // an extraneous delta - if (networkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(TargetClientId) && - networkManager.SpawnManager.ObjectsToShowToClient[TargetClientId] - .Contains(obj)) + if (networkManager.SpawnManager.ObjectsToShowToClient.TryGetValue(TargetClientId, out var objectsToShow) + && objectsToShow.Contains(networkObject)) { shouldWrite = false; } @@ -181,7 +180,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) if (shouldWrite) { WriteNetworkVariable(ref writer, ref networkVariable, ensureNetworkVariableLengthSafety, nonFragmentedMessageMaxSize, fragmentedMessageMaxSize); - networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(TargetClientId, obj, networkVariable.Name, typeName, writer.Length - startingSize); + networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(TargetClientId, networkObject, networkVariable.Name, typeName, writer.Length - startingSize); } } } @@ -205,203 +204,201 @@ public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; - if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject)) + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject)) { - var ensureNetworkVariableLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety; - var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex); - var isServerAndDeltaForwarding = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery && networkManager.IsServer; - var markNetworkVariableDirty = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery ? false : networkManager.IsServer; - m_UpdatedNetworkVariables = new List(); + // DANGO-TODO: Fix me! + // When a client-spawned NetworkObject is despawned by the owner client, the owner client will still get messages for deltas and cause this to + // log a warning. The issue is primarily how NetworkVariables handle updating and will require some additional re-factoring. + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context, k_Name); + return; + } + + var ensureNetworkVariableLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety; + var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex); + var isServerAndDeltaForwarding = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery && networkManager.IsServer; + var markNetworkVariableDirty = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery ? false : networkManager.IsServer; + m_UpdatedNetworkVariables = new List(); + + if (networkBehaviour == null) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"Network variable delta message received for a non-existent behaviour. {nameof(NetworkObjectId)}: {NetworkObjectId}, {nameof(NetworkBehaviourIndex)}: {NetworkBehaviourIndex}"); + } + return; + } + + // (For client-server) As opposed to worrying about adding additional processing on the server to send NetworkVariable + // updates at the end of the frame, we now track all NetworkVariable state updates, per client, that need to be forwarded + // to the client. This creates a list of all remaining connected clients that could have updates applied. + if (isServerAndDeltaForwarding) + { + m_ForwardUpdates = new Dictionary>(); + foreach (var clientId in networkManager.ConnectionManager.ConnectedClientIds) + { + if (clientId == context.SenderId || clientId == networkManager.LocalClientId || !networkObject.Observers.Contains(clientId)) + { + continue; + } + m_ForwardUpdates.Add(clientId, new List()); + } + } - if (networkBehaviour == null) + // Update NetworkVariable Fields + for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++) + { + int expectedBytesToRead = 0; + var networkVariable = networkBehaviour.NetworkVariableFields[i]; + + if (ensureNetworkVariableLengthSafety) { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out expectedBytesToRead); + if (expectedBytesToRead == 0) { - NetworkLog.LogWarning($"Network variable delta message received for a non-existent behaviour. {nameof(NetworkObjectId)}: {NetworkObjectId}, {nameof(NetworkBehaviourIndex)}: {NetworkBehaviourIndex}"); + continue; } } else { - // (For client-server) As opposed to worrying about adding additional processing on the server to send NetworkVariable - // updates at the end of the frame, we now track all NetworkVariable state updates, per client, that need to be forwarded - // to the client. This creates a list of all remaining connected clients that could have updates applied. - if (isServerAndDeltaForwarding) + m_ReceivedNetworkVariableData.ReadValueSafe(out bool deltaExists); + if (!deltaExists) { - m_ForwardUpdates = new Dictionary>(); - foreach (var clientId in networkManager.ConnectionManager.ConnectedClientIds) - { - if (clientId == context.SenderId || clientId == networkManager.LocalClientId || !networkObject.Observers.Contains(clientId)) - { - continue; - } - m_ForwardUpdates.Add(clientId, new List()); - } + continue; } + } - // Update NetworkVariable Fields - for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; 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 (ensureNetworkVariableLengthSafety) { - int expectedBytesToRead = 0; - var networkVariable = networkBehaviour.NetworkVariableFields[i]; - - if (ensureNetworkVariableLengthSafety) + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { - ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out expectedBytesToRead); - if (expectedBytesToRead == 0) - { - continue; - } + 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}]"); } - else - { - m_ReceivedNetworkVariableData.ReadValueSafe(out bool deltaExists); - if (!deltaExists) - { - continue; - } - } - - 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 (ensureNetworkVariableLengthSafety) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - 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 + expectedBytesToRead); - continue; - } - //This client wrote somewhere they are not allowed. This is critical - //We can't just skip this field. Because we don't actually know how to dummy read - //That is, we don't know how many bytes to skip. Because the interface doesn't have a - //Read that gives us the value. Only a Read that applies the value straight away - //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.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(networkBehaviour)} - VariableIndex: {i}"); - NetworkLog.LogError($"[{networkVariable.GetType().Name}]"); - } - return; - } - var readStartPos = m_ReceivedNetworkVariableData.Position; + m_ReceivedNetworkVariableData.Seek(m_ReceivedNetworkVariableData.Position + expectedBytesToRead); + continue; + } - if (ensureNetworkVariableLengthSafety) - { - var remainingBufferSize = m_ReceivedNetworkVariableData.Length - readStartPos; - if (expectedBytesToRead > remainingBufferSize) - { - UnityEngine.Debug.LogError($"[{networkBehaviour.name}][Delta State Read Error] Expecting to read {expectedBytesToRead} but only {remainingBufferSize} remains!"); - return; - } - } + //This client wrote somewhere they are not allowed. This is critical + //We can't just skip this field. Because we don't actually know how to dummy read + //That is, we don't know how many bytes to skip. Because the interface doesn't have a + //Read that gives us the value. Only a Read that applies the value straight away + //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.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(networkBehaviour)} - VariableIndex: {i}"); + NetworkLog.LogError($"[{networkVariable.GetType().Name}]"); + } + return; + } + var readStartPos = m_ReceivedNetworkVariableData.Position; - // Added a try catch here to assure any failure will only fail on this one message and not disrupt the stack - try - { - // Read the delta - networkVariable.ReadDelta(m_ReceivedNetworkVariableData, markNetworkVariableDirty); + if (ensureNetworkVariableLengthSafety) + { + var remainingBufferSize = m_ReceivedNetworkVariableData.Length - readStartPos; + if (expectedBytesToRead > remainingBufferSize) + { + UnityEngine.Debug.LogError($"[{networkBehaviour.name}][Delta State Read Error] Expecting to read {expectedBytesToRead} but only {remainingBufferSize} remains!"); + return; + } + } - // Add the NetworkVariable field index so we can invoke the PostDeltaRead - m_UpdatedNetworkVariables.Add(i); - } - catch (Exception ex) - { - UnityEngine.Debug.LogException(ex); - return; - } + // Added a try catch here to assure any failure will only fail on this one message and not disrupt the stack + try + { + // Read the delta + networkVariable.ReadDelta(m_ReceivedNetworkVariableData, markNetworkVariableDirty); - if (ensureNetworkVariableLengthSafety) - { - var totalBytesRead = m_ReceivedNetworkVariableData.Position - readStartPos; - if (totalBytesRead != expectedBytesToRead) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"[{nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}][Delta State Read] NetworkVariable read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes!"); - } - m_ReceivedNetworkVariableData.Seek(readStartPos + expectedBytesToRead); - } - } + // Add the NetworkVariable field index so we can invoke the PostDeltaRead + m_UpdatedNetworkVariables.Add(i); + } + catch (Exception ex) + { + UnityEngine.Debug.LogException(ex); + return; + } - // (For client-server) As opposed to worrying about adding additional processing on the server to send NetworkVariable - // updates at the end of the frame, we now track all NetworkVariable state updates, per client, that need to be forwarded - // to the client. This happens once the server is finished processing all state updates for this message. - if (isServerAndDeltaForwarding) + if (ensureNetworkVariableLengthSafety) + { + var totalBytesRead = m_ReceivedNetworkVariableData.Position - readStartPos; + if (totalBytesRead != expectedBytesToRead) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { - foreach (var forwardEntry in m_ForwardUpdates) - { - // Only track things that the client can read - if (networkVariable.CanClientRead(forwardEntry.Key)) - { - // If the object is about to be shown to the client then don't send an update as it will - // send a full update when shown. - if (networkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(forwardEntry.Key) && - networkManager.SpawnManager.ObjectsToShowToClient[forwardEntry.Key] - .Contains(networkObject)) - { - continue; - } - forwardEntry.Value.Add(i); - } - } + NetworkLog.LogWarning($"[{nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}][Delta State Read] NetworkVariable read {totalBytesRead} bytes but was expected to read {expectedBytesToRead} bytes!"); } - - networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived( - context.SenderId, - networkObject, - networkVariable.Name, - networkBehaviour.__getTypeName(), - context.MessageSize); + m_ReceivedNetworkVariableData.Seek(readStartPos + expectedBytesToRead); } + } - // If we are using the version of this message that includes network delivery, then - // forward this update to all connected clients (other than the sender and the server). - if (isServerAndDeltaForwarding) + // (For client-server) As opposed to worrying about adding additional processing on the server to send NetworkVariable + // updates at the end of the frame, we now track all NetworkVariable state updates, per client, that need to be forwarded + // to the client. This happens once the server is finished processing all state updates for this message. + if (isServerAndDeltaForwarding) + { + foreach (var forwardEntry in m_ForwardUpdates) { - var message = new NetworkVariableDeltaMessage() - { - NetworkBehaviour = networkBehaviour, - NetworkBehaviourIndex = NetworkBehaviourIndex, - NetworkObjectId = NetworkObjectId, - m_ForwardingMessage = true, - m_ForwardUpdates = m_ForwardUpdates, - }; - - foreach (var forwardEntry in m_ForwardUpdates) + // Only track things that the client can read + if (networkVariable.CanClientRead(forwardEntry.Key)) { - // Only forward updates to any client that has visibility to the state updates included in this message - if (forwardEntry.Value.Count > 0) + // If the object is about to be shown to the client then don't send an update as it will + // send a full update when shown. + if (networkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(forwardEntry.Key) && + networkManager.SpawnManager.ObjectsToShowToClient[forwardEntry.Key] + .Contains(networkObject)) { - message.TargetClientId = forwardEntry.Key; - networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery, forwardEntry.Key); + continue; } + forwardEntry.Value.Add(i); } } + } + + networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived( + context.SenderId, + networkObject, + networkVariable.Name, + networkBehaviour.__getTypeName(), + context.MessageSize); + } - // This should be always invoked (client & server) to assure the previous values are set - // !! IMPORTANT ORDER OF OPERATIONS !! (Has to happen after forwarding deltas) - // When a server forwards delta updates to connected clients, it needs to preserve the previous value - // until it is done serializing all valid NetworkVariable field deltas (relative to each client). This - // is invoked after it is done forwarding the deltas. - foreach (var fieldIndex in m_UpdatedNetworkVariables) + // If we are using the version of this message that includes network delivery, then + // forward this update to all connected clients (other than the sender and the server). + if (isServerAndDeltaForwarding) + { + var message = new NetworkVariableDeltaMessage() + { + NetworkBehaviour = networkBehaviour, + NetworkBehaviourIndex = NetworkBehaviourIndex, + NetworkObjectId = NetworkObjectId, + m_ForwardingMessage = true, + m_ForwardUpdates = m_ForwardUpdates, + }; + + foreach (var forwardEntry in m_ForwardUpdates) + { + // Only forward updates to any client that has visibility to the state updates included in this message + if (forwardEntry.Value.Count > 0) { - networkBehaviour.NetworkVariableFields[fieldIndex].PostDeltaRead(); + message.TargetClientId = forwardEntry.Key; + networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery, forwardEntry.Key); } } } - else + + // This should be always invoked (client & server) to assure the previous values are set + // !! IMPORTANT ORDER OF OPERATIONS !! (Has to happen after forwarding deltas) + // When a server forwards delta updates to connected clients, it needs to preserve the previous value + // until it is done serializing all valid NetworkVariable field deltas (relative to each client). This + // is invoked after it is done forwarding the deltas. + foreach (var fieldIndex in m_UpdatedNetworkVariables) { - // DANGO-TODO: Fix me! - // When a client-spawned NetworkObject is despawned by the owner client, the owner client will still get messages for deltas and cause this to - // log a warning. The issue is primarily how NetworkVariables handle updating and will require some additional re-factoring. - networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, m_ReceivedNetworkVariableData, ref context, k_Name); + networkBehaviour.NetworkVariableFields[fieldIndex].PostDeltaRead(); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs index 2e14bccf9c..36e0199a5a 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs @@ -217,7 +217,7 @@ public void Anticipate(T value) m_LastAnticipationCounter = m_NetworkBehaviour.NetworkManager.AnticipationSystem.AnticipationCounter; m_AnticipatedValue = value; NetworkVariableSerialization.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue); - if (CanWrite) + if (CanWrite()) { AuthoritativeValue = value; } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index c4b6a0e803..18322ee332 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -429,7 +429,7 @@ public IEnumerator GetEnumerator() public void Add(T item) { // check write permissions - if (CannotWrite) + if (CannotWrite()) { LogWritePermissionError(); return; @@ -456,7 +456,7 @@ public void Add(T item) public void Clear() { // check write permissions - if (CannotWrite) + if (CannotWrite()) { LogWritePermissionError(); return; @@ -494,7 +494,7 @@ public bool Contains(T item) public bool Remove(T item) { // check write permissions - if (CannotWrite) + if (CannotWrite()) { LogWritePermissionError(); return false; @@ -543,7 +543,7 @@ public int IndexOf(T item) public void Insert(int index, T item) { // check write permissions - if (CannotWrite) + if (CannotWrite()) { LogWritePermissionError(); return; @@ -579,7 +579,7 @@ public void Insert(int index, T item) public void RemoveAt(int index) { // check write permissions - if (CannotWrite) + if (CannotWrite()) { throw new InvalidOperationException("Client is not allowed to write to this NetworkList"); } @@ -613,7 +613,7 @@ public void RemoveAt(int index) public void Set(int index, T value, bool forceUpdate = false) { // check write permissions - if (CannotWrite) + if (CannotWrite()) { LogWritePermissionError(); return; diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index b34b3cfeb8..e8604a66aa 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -89,7 +89,7 @@ public NetworkVariable(T value = default, /// the value to reset the NetworkVariable to (if none specified it resets to the default) public void Reset(T value = default) { - if (m_NetworkBehaviour == null || m_NetworkBehaviour != null && !m_NetworkBehaviour.NetworkObject.IsSpawned) + if (m_NetworkBehaviour == null || m_NetworkObject == null || !m_NetworkObject.IsSpawned) { m_InternalValue = value; NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_LastInternalValue); @@ -148,7 +148,7 @@ public virtual T Value get => m_InternalValue; set { - if (CannotWrite) + if (CannotWrite()) { LogWritePermissionError(); return; @@ -182,7 +182,7 @@ public bool CheckDirtyState(bool forceCheck = false) var isDirty = base.IsDirty(); // A client without permissions invoking this method should only check to assure the current value is equal to the last known current value - if (CannotWrite) + if (CannotWrite()) { // If modifications are detected, then revert back to the last known current value if (!NetworkVariableSerialization.AreEqual(ref m_InternalValue, ref m_LastInternalValue)) @@ -266,7 +266,7 @@ public override bool IsDirty() { // If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert // to the original collection value prior to applying updates (primarily for collections). - if (!NetworkUpdaterCheck && CannotWrite && !NetworkVariableSerialization.AreEqual(ref m_InternalValue, ref m_LastInternalValue)) + if (!NetworkUpdaterCheck && CannotWrite() && !NetworkVariableSerialization.AreEqual(ref m_InternalValue, ref m_LastInternalValue)) { NetworkVariableSerialization.Duplicate(m_LastInternalValue, ref m_InternalValue); return true; @@ -328,7 +328,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { // If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert // to the original collection value prior to applying updates (primarily for collections). - if (CannotWrite && !NetworkVariableSerialization.AreEqual(ref m_LastInternalValue, ref m_InternalValue)) + if (CannotWrite() && !NetworkVariableSerialization.AreEqual(ref m_LastInternalValue, ref m_InternalValue)) { NetworkVariableSerialization.Duplicate(m_LastInternalValue, ref m_InternalValue); } @@ -370,7 +370,7 @@ public override void ReadField(FastBufferReader reader) { // If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert // to the original collection value prior to applying updates (primarily for collections). - if (CannotWrite && !NetworkVariableSerialization.AreEqual(ref m_LastInternalValue, ref m_InternalValue)) + if (CannotWrite() && !NetworkVariableSerialization.AreEqual(ref m_LastInternalValue, ref m_InternalValue)) { NetworkVariableSerialization.Duplicate(m_LastInternalValue, ref m_InternalValue); } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index 8761496d2a..fa93c04937 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using UnityEngine; namespace Unity.Netcode @@ -42,7 +43,11 @@ public abstract class NetworkVariableBase : IDisposable /// private protected NetworkBehaviour m_NetworkBehaviour; - private NetworkManager m_InternalNetworkManager; + private protected NetworkManager m_NetworkManager; + + private protected NetworkObject m_NetworkObject; + + private bool m_UseServerTime; // Determines if this NetworkVariable has been "initialized" to prevent initializing more than once which can happen when first // instantiated and spawned. If this NetworkVariable instance is on an in-scene placed NetworkObject =or= a pooled NetworkObject @@ -60,8 +65,6 @@ internal void LogWritePermissionError() Debug.LogError(GetWritePermissionError()); } - private protected NetworkManager m_NetworkManager => m_InternalNetworkManager; - /// /// Gets the NetworkBehaviour instance associated with this network variable /// @@ -108,23 +111,27 @@ public void Initialize(NetworkBehaviour networkBehaviour) return; } - if (!m_NetworkBehaviour.NetworkObject.NetworkManagerOwner) + m_NetworkObject = m_NetworkBehaviour.NetworkObject; + + if (!m_NetworkObject.NetworkManagerOwner) { // Exit early if there has yet to be a NetworkManagerOwner assigned // to the NetworkObject. This is ok because Initialize is invoked // multiple times until it is considered "initialized". return; } - m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject.NetworkManagerOwner; + m_NetworkManager = m_NetworkObject.NetworkManagerOwner; + + m_UseServerTime = m_NetworkManager.CMBServiceConnection || !m_NetworkManager.IsServer; // When in distributed authority mode, there is no such thing as server write permissions - InternalWritePerm = m_InternalNetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm; + InternalWritePerm = m_NetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm; OnInitialize(); // Some unit tests don't operate with a running NetworkManager. // Only update the last time if there is a NetworkTimeSystem. - if (m_InternalNetworkManager.NetworkTimeSystem != null) + if (m_NetworkManager.NetworkTimeSystem != null) { // Update our last sent time relative to when this was initialized UpdateLastSentTime(); @@ -132,7 +139,7 @@ public void Initialize(NetworkBehaviour networkBehaviour) // At this point, this instance is considered initialized HasBeenInitialized = true; } - else if (m_InternalNetworkManager.LogLevel == LogLevel.Developer) + else if (m_NetworkManager.LogLevel == LogLevel.Developer) { Debug.LogWarning($"[{m_NetworkBehaviour.name}][{m_NetworkBehaviour.GetType().Name}][{GetType().Name}][Initialize] {nameof(NetworkManager)} has no {nameof(NetworkTimeSystem)} assigned!"); } @@ -251,7 +258,7 @@ public virtual void SetDirty(bool isDirty) internal bool CanSend() { // When connected to a service or not the server, always use the synchronized server time as opposed to the local time - var time = m_InternalNetworkManager.CMBServiceConnection || !m_InternalNetworkManager.IsServer ? m_NetworkBehaviour.NetworkManager.ServerTime.Time : m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime; + var time = m_UseServerTime ? m_NetworkManager.ServerTime.Time : m_NetworkManager.NetworkTimeSystem.LocalTime; var timeSinceLastUpdate = time - LastUpdateSent; return ( @@ -267,7 +274,7 @@ internal bool CanSend() internal void UpdateLastSentTime() { // When connected to a service or not the server, always use the synchronized server time as opposed to the local time - LastUpdateSent = m_InternalNetworkManager.CMBServiceConnection || !m_InternalNetworkManager.IsServer ? m_NetworkBehaviour.NetworkManager.ServerTime.Time : m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime; + LastUpdateSent = m_UseServerTime ? m_NetworkManager.ServerTime.Time : m_NetworkManager.NetworkTimeSystem.LocalTime; } internal static bool IgnoreInitializeWarning; @@ -286,9 +293,9 @@ protected void MarkNetworkBehaviourDirty() } return; } - if (m_NetworkBehaviour.NetworkManager.ShutdownInProgress) + if (m_NetworkManager.ShutdownInProgress) { - if (m_NetworkBehaviour.NetworkManager.LogLevel <= LogLevel.Developer) + if (m_NetworkManager.LogLevel <= LogLevel.Developer) { Debug.LogWarning($"NetworkVariable is written to during the NetworkManager shutdown! " + "Are you modifying a NetworkVariable within a NetworkBehaviour.OnDestroy or NetworkBehaviour.OnDespawn method?"); @@ -296,9 +303,9 @@ protected void MarkNetworkBehaviourDirty() return; } - if (!m_NetworkBehaviour.NetworkManager.IsListening) + if (!m_NetworkManager.IsListening) { - if (m_NetworkBehaviour.NetworkManager.LogLevel <= LogLevel.Developer) + if (m_NetworkManager.LogLevel <= LogLevel.Developer) { Debug.LogWarning($"NetworkVariable is written to after the NetworkManager has already shutdown! " + "Are you modifying a NetworkVariable within a NetworkBehaviour.OnDestroy or NetworkBehaviour.OnDespawn method?"); @@ -306,7 +313,7 @@ protected void MarkNetworkBehaviourDirty() return; } - m_NetworkBehaviour.NetworkManager.BehaviourUpdater?.AddForUpdate(m_NetworkBehaviour.NetworkObject); + m_NetworkManager.BehaviourUpdater?.AddForUpdate(m_NetworkObject); } /// @@ -345,7 +352,7 @@ public bool CanClientRead(ulong clientId) } // When in distributed authority mode, everyone can read (but only the owner can write) - if (m_NetworkManager != null && m_NetworkManager.DistributedAuthorityMode) + if (m_NetworkManager.DistributedAuthorityMode) { return true; } @@ -355,7 +362,7 @@ public bool CanClientRead(ulong clientId) case NetworkVariableReadPermission.Everyone: return true; case NetworkVariableReadPermission.Owner: - return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId || NetworkManager.ServerClientId == clientId; + return clientId == m_NetworkObject.OwnerClientId || NetworkManager.ServerClientId == clientId; } } @@ -377,26 +384,26 @@ public bool CanClientWrite(ulong clientId) case NetworkVariableWritePermission.Server: return clientId == NetworkManager.ServerClientId; case NetworkVariableWritePermission.Owner: - return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId; + return clientId == m_NetworkObject.OwnerClientId; } } /// /// Returns true if the current can write to this variable; otherwise false. /// - internal bool CanWrite => m_NetworkManager && CanClientWrite(m_NetworkManager.LocalClientId); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool CanWrite() + { + return m_NetworkManager && CanClientWrite(m_NetworkManager.LocalClientId); + } /// /// Returns false if the current can write to this variable; otherwise true. /// - internal bool CannotWrite => m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId); - - /// - /// Returns the ClientId of the owning client - /// - internal ulong OwnerClientId() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool CannotWrite() { - return m_NetworkBehaviour.NetworkObject.OwnerClientId; + return m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId); } /// @@ -467,7 +474,10 @@ internal virtual void WriteFieldSynchronization(FastBufferWriter writer) /// public virtual void Dispose() { - m_InternalNetworkManager = null; + HasBeenInitialized = false; + m_NetworkBehaviour = null; + m_NetworkObject = null; + m_NetworkManager = null; } } }