diff --git a/CHANGELOG.md b/CHANGELOG.md index 19aec06..b7462ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). -## [1.0.0-pre.2] - 2020-12-20 +## [1.0.0-pre.3] - 2021-10-22 + +### Added + +- ResetTrigger function to NetworkAnimator (#1327) + +### Fixed + +- Overflow exception when syncing Animator state. (#1327) +- Added `try`/`catch` around RPC calls, preventing exception from causing further RPC calls to fail (#1329) +- Fixed an issue where ServerClientId and LocalClientId could have the same value, causing potential confusion, and also fixed an issue with the UNet where the server could be identified with two different values, one of which might be the same as LocalClientId, and the other of which would not.(#1368) +- IL2CPP would not properly compile (#1359) + +## [1.0.0-pre.2] - 2021-10-19 + ### Added @@ -16,7 +30,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Updated label for `1.0.0-pre.1` changelog section -## [1.0.0-pre.1] - 2020-12-20 +## [1.0.0-pre.1] - 2021-10-19 ### Added diff --git a/Components/NetworkAnimator.cs b/Components/NetworkAnimator.cs index 7116b4e..a30e3a0 100644 --- a/Components/NetworkAnimator.cs +++ b/Components/NetworkAnimator.cs @@ -1,5 +1,6 @@ using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; + using UnityEngine; namespace Unity.Netcode.Components @@ -38,11 +39,12 @@ namespace Unity.Netcode.Components internal struct AnimationTriggerMessage : INetworkSerializable { public int Hash; + public bool Reset; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { serializer.SerializeValue(ref Hash); - + serializer.SerializeValue(ref Reset); } } @@ -286,6 +288,12 @@ namespace Unity.Netcode.Components return false; } + /* $AS TODO: Right now we are not checking for changed values this is because + the read side of this function doesn't have similar logic which would cause + an overflow read because it doesn't know if the value is there or not. So + there needs to be logic to track which indexes changed in order for there + to be proper value change checking. Will revist in 1.1.0. + */ private unsafe bool WriteParameters(FastBufferWriter writer, bool autoSend) { if (m_CachedAnimatorParameters == null) @@ -308,12 +316,8 @@ namespace Unity.Netcode.Components var valueInt = m_Animator.GetInteger(hash); fixed (void* value = cacheValue.Value) { - var oldValue = UnsafeUtility.AsRef(value); - if (valueInt != oldValue) - { - UnsafeUtility.WriteArrayElement(value, 0, valueInt); - BytePacker.WriteValuePacked(writer, (uint)valueInt); - } + UnsafeUtility.WriteArrayElement(value, 0, valueInt); + BytePacker.WriteValuePacked(writer, (uint)valueInt); } } else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool) @@ -321,12 +325,8 @@ namespace Unity.Netcode.Components var valueBool = m_Animator.GetBool(hash); fixed (void* value = cacheValue.Value) { - var oldValue = UnsafeUtility.AsRef(value); - if (valueBool != oldValue) - { - UnsafeUtility.WriteArrayElement(value, 0, valueBool); - writer.WriteValueSafe(valueBool); - } + UnsafeUtility.WriteArrayElement(value, 0, valueBool); + writer.WriteValueSafe(valueBool); } } else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat) @@ -334,13 +334,9 @@ namespace Unity.Netcode.Components var valueFloat = m_Animator.GetFloat(hash); fixed (void* value = cacheValue.Value) { - var oldValue = UnsafeUtility.AsRef(value); - if (valueFloat != oldValue) - { - UnsafeUtility.WriteArrayElement(value, 0, valueFloat); - writer.WriteValueSafe(valueFloat); - } + UnsafeUtility.WriteArrayElement(value, 0, valueFloat); + writer.WriteValueSafe(valueFloat); } } } @@ -432,7 +428,14 @@ namespace Unity.Netcode.Components [ClientRpc] private void SendAnimTriggerClientRpc(AnimationTriggerMessage animSnapshot, ClientRpcParams clientRpcParams = default) { - m_Animator.SetTrigger(animSnapshot.Hash); + if (animSnapshot.Reset) + { + m_Animator.ResetTrigger(animSnapshot.Hash); + } + else + { + m_Animator.SetTrigger(animSnapshot.Hash); + } } public void SetTrigger(string triggerName) @@ -440,15 +443,26 @@ namespace Unity.Netcode.Components SetTrigger(Animator.StringToHash(triggerName)); } - public void SetTrigger(int hash) + public void SetTrigger(int hash, bool reset = false) { var animMsg = new AnimationTriggerMessage(); animMsg.Hash = hash; + animMsg.Reset = reset; if (IsServer) { SendAnimTriggerClientRpc(animMsg); } } + + public void ResetTrigger(string triggerName) + { + ResetTrigger(Animator.StringToHash(triggerName)); + } + + public void ResetTrigger(int hash) + { + SetTrigger(hash, true); + } } } diff --git a/Editor/CodeGen/NetworkBehaviourILPP.cs b/Editor/CodeGen/NetworkBehaviourILPP.cs index 3b4b6a1..d49d48c 100644 --- a/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -1127,6 +1127,11 @@ namespace Unity.Netcode.Editor.CodeGen nhandler.Parameters.Add(new ParameterDefinition("rpcParams", ParameterAttributes.None, m_RpcParams_TypeRef)); var processor = nhandler.Body.GetILProcessor(); + + // begin Try/Catch + var tryStart = processor.Create(OpCodes.Nop); + processor.Append(tryStart); + var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; var requireOwnership = true; // default value MUST be = `ServerRpcAttribute.RequireOwnership` foreach (var attrField in rpcAttribute.Fields) @@ -1303,7 +1308,54 @@ namespace Unity.Netcode.Editor.CodeGen processor.Emit(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.None); processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef); + // pull in the Exception Module + var exception = m_MainModule.ImportReference(typeof(Exception)); + + // Get Exception.ToString() + var exp = m_MainModule.ImportReference(typeof(Exception).GetMethod("ToString", new Type[] { })); + + // Get String.Format (This is equivalent to an interpolated string) + var stringFormat = m_MainModule.ImportReference(typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) })); + + nhandler.Body.Variables.Add(new VariableDefinition(exception)); + int exceptionVariableIndex = nhandler.Body.Variables.Count - 1; + + //try ends/catch begins + var catchEnds = processor.Create(OpCodes.Nop); + processor.Emit(OpCodes.Leave, catchEnds); + + // Load the Exception onto the stack + var catchStarts = processor.Create(OpCodes.Stloc, exceptionVariableIndex); + processor.Append(catchStarts); + + // Load string for the error log that will be shown + processor.Emit(OpCodes.Ldstr, $"Unhandled RPC Exception:\n {{0}}"); + processor.Emit(OpCodes.Ldloc, exceptionVariableIndex); + processor.Emit(OpCodes.Callvirt, exp); + processor.Emit(OpCodes.Call, stringFormat); + + // Call Debug.LogError + processor.Emit(OpCodes.Call, m_Debug_LogError_MethodRef); + + // reset NetworkBehaviour.__rpc_exec_stage = __RpcExecStage.None; + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.None); + processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef); + + // catch ends + processor.Append(catchEnds); + + processor.Body.ExceptionHandlers.Add(new ExceptionHandler(ExceptionHandlerType.Catch) + { + CatchType = exception, + TryStart = tryStart, + TryEnd = catchStarts, + HandlerStart = catchStarts, + HandlerEnd = catchEnds + }); + processor.Emit(OpCodes.Ret); + return nhandler; } } diff --git a/Runtime/Core/NetworkManager.cs b/Runtime/Core/NetworkManager.cs index 249d3b4..fe26534 100644 --- a/Runtime/Core/NetworkManager.cs +++ b/Runtime/Core/NetworkManager.cs @@ -149,14 +149,9 @@ namespace Unity.Netcode public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData) { + var sendBuffer = batchData.ToTempByteArray(); - var length = batchData.Length; - //TODO: Transport needs to have a way to send it data without copying and allocating here. - var bytes = batchData.ToArray(); - var sendBuffer = new ArraySegment(bytes, 0, length); - - m_NetworkManager.NetworkConfig.NetworkTransport.Send(clientId, sendBuffer, delivery); - + m_NetworkManager.NetworkConfig.NetworkTransport.Send(m_NetworkManager.ClientIdToTransportId(clientId), sendBuffer, delivery); } } @@ -228,10 +223,12 @@ namespace Unity.Netcode public NetworkSceneManager SceneManager { get; private set; } + public readonly ulong ServerClientId = 0; + /// /// Gets the networkId of the server /// - public ulong ServerClientId => NetworkConfig.NetworkTransport?.ServerClientId ?? + private ulong m_ServerTransportId => NetworkConfig.NetworkTransport?.ServerClientId ?? throw new NullReferenceException( $"The transport in the active {nameof(NetworkConfig)} is null"); @@ -248,6 +245,10 @@ namespace Unity.Netcode private Dictionary m_ConnectedClients = new Dictionary(); + private ulong m_NextClientId = 1; + private Dictionary m_ClientIdToTransportIdMap = new Dictionary(); + private Dictionary m_TransportIdToClientIdMap = new Dictionary(); + private List m_ConnectedClientsList = new List(); private List m_ConnectedClientIds = new List(); @@ -985,6 +986,12 @@ namespace Unity.Netcode } } + private void DisconnectRemoteClient(ulong clientId) + { + var transportId = ClientIdToTransportId(clientId); + NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId); + } + /// /// Globally shuts down the library. /// Disconnects clients if connected and stops server if running. @@ -1019,7 +1026,7 @@ namespace Unity.Netcode continue; } - NetworkConfig.NetworkTransport.DisconnectRemoteClient(pair.Key); + DisconnectRemoteClient(pair.Key); } } @@ -1033,7 +1040,7 @@ namespace Unity.Netcode continue; } - NetworkConfig.NetworkTransport.DisconnectRemoteClient(pair.Key); + DisconnectRemoteClient(pair.Key); } } } @@ -1103,6 +1110,9 @@ namespace Unity.Netcode NetworkConfig?.NetworkTransport?.Shutdown(); } + m_ClientIdToTransportIdMap.Clear(); + m_TransportIdToClientIdMap.Clear(); + IsListening = false; } @@ -1229,14 +1239,30 @@ namespace Unity.Netcode } } + private ulong TransportIdToClientId(ulong transportId) + { + return transportId == m_ServerTransportId ? ServerClientId : m_TransportIdToClientIdMap[transportId]; + } + + private ulong ClientIdToTransportId(ulong clientId) + { + return clientId == ServerClientId ? m_ServerTransportId : m_ClientIdToTransportIdMap[clientId]; + } + private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, ArraySegment payload, float receiveTime) { + var transportId = clientId; switch (networkEvent) { case NetworkEvent.Connect: #if DEVELOPMENT_BUILD || UNITY_EDITOR s_TransportConnect.Begin(); #endif + + clientId = m_NextClientId++; + m_ClientIdToTransportIdMap[clientId] = transportId; + m_TransportIdToClientIdMap[transportId] = clientId; + MessagingSystem.ClientConnected(clientId); if (IsServer) { @@ -1275,6 +1301,8 @@ namespace Unity.Netcode NetworkLog.LogInfo($"Incoming Data From {clientId}: {payload.Count} bytes"); } + clientId = TransportIdToClientId(clientId); + HandleIncomingData(clientId, payload, receiveTime); break; } @@ -1282,6 +1310,10 @@ namespace Unity.Netcode #if DEVELOPMENT_BUILD || UNITY_EDITOR s_TransportDisconnect.Begin(); #endif + clientId = TransportIdToClientId(clientId); + + m_TransportIdToClientIdMap.Remove(transportId); + m_ClientIdToTransportIdMap.Remove(clientId); if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { @@ -1405,8 +1437,7 @@ namespace Unity.Netcode } OnClientDisconnectFromServer(clientId); - - NetworkConfig.NetworkTransport.DisconnectRemoteClient(clientId); + DisconnectRemoteClient(clientId); } private void OnClientDisconnectFromServer(ulong clientId) @@ -1580,7 +1611,7 @@ namespace Unity.Netcode else { PendingClients.Remove(ownerClientId); - NetworkConfig.NetworkTransport.DisconnectRemoteClient(ownerClientId); + DisconnectRemoteClient(ownerClientId); } } diff --git a/Runtime/Messaging/CustomMessageManager.cs b/Runtime/Messaging/CustomMessageManager.cs index f84a379..0b4e9a0 100644 --- a/Runtime/Messaging/CustomMessageManager.cs +++ b/Runtime/Messaging/CustomMessageManager.cs @@ -40,7 +40,7 @@ namespace Unity.Netcode ((UnnamedMessageDelegate)handler).Invoke(clientId, reader); } } - m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length); + m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + FastBufferWriter.GetWriteSize()); } /// @@ -53,7 +53,6 @@ namespace Unity.Netcode SendUnnamedMessage(m_NetworkManager.ConnectedClientsIds, messageBuffer, networkDelivery); } - /// /// Sends unnamed message to a list of clients /// @@ -118,7 +117,7 @@ namespace Unity.Netcode internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader) { - var bytesCount = reader.Length; + var bytesCount = reader.Length + FastBufferWriter.GetWriteSize(); if (m_NetworkManager == null) { diff --git a/Runtime/Messaging/MessagingSystem.cs b/Runtime/Messaging/MessagingSystem.cs index ab6b438..41d27a6 100644 --- a/Runtime/Messaging/MessagingSystem.cs +++ b/Runtime/Messaging/MessagingSystem.cs @@ -226,7 +226,7 @@ namespace Unity.Netcode for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) { - m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length); + m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize()); } var handler = m_MessageHandlers[header.MessageType]; using (reader) @@ -247,7 +247,7 @@ namespace Unity.Netcode } for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) { - m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length); + m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize()); } } @@ -310,8 +310,13 @@ namespace Unity.Netcode where TMessageType : INetworkMessage where TClientIdListType : IReadOnlyList { + if (clientIds.Count == 0) + { + return 0; + } + var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE; - var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(MessageHeader), Allocator.Temp, maxSize - sizeof(MessageHeader)); + var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize()); using (tmpSerializer) { message.Serialize(tmpSerializer); @@ -342,7 +347,7 @@ namespace Unity.Netcode ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1); if (lastQueueItem.NetworkDelivery != delivery || lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position - < tmpSerializer.Length + sizeof(MessageHeader)) + < tmpSerializer.Length + FastBufferWriter.GetWriteSize()) { sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob, maxSize)); @@ -351,7 +356,7 @@ namespace Unity.Netcode } ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1); - writeQueueItem.Writer.TryBeginWrite(sizeof(MessageHeader) + tmpSerializer.Length); + writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + FastBufferWriter.GetWriteSize()); var header = new MessageHeader { MessageSize = (ushort)tmpSerializer.Length, @@ -363,11 +368,11 @@ namespace Unity.Netcode writeQueueItem.BatchHeader.BatchSize++; for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) { - m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + sizeof(MessageHeader)); + m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + FastBufferWriter.GetWriteSize()); } } - return tmpSerializer.Length; + return tmpSerializer.Length + FastBufferWriter.GetWriteSize(); } } diff --git a/Runtime/SceneManagement/NetworkSceneManager.cs b/Runtime/SceneManagement/NetworkSceneManager.cs index 5cb3fc2..889b1f8 100644 --- a/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/Runtime/SceneManagement/NetworkSceneManager.cs @@ -8,17 +8,22 @@ using UnityEngine.SceneManagement; namespace Unity.Netcode { /// - /// Used for local notifications of various scene events. - /// The of delegate type uses this class to provide - /// scene event status/state. + /// Used for local notifications of various scene events. The of + /// delegate type uses this class to provide + /// scene event status.
+ /// Note: This is only when is enabled.
+ /// See also:
+ /// ///
public class SceneEvent { /// - /// The returned by + /// The returned by
/// This is set for the following s: - /// - /// + /// + /// + /// + /// ///
public AsyncOperation AsyncOperation; @@ -28,71 +33,92 @@ namespace Unity.Netcode public SceneEventType SceneEventType; /// - /// If applicable, this reflects the type of scene loading or unloading that is occurring. + /// If applicable, this reflects the type of scene loading or unloading that is occurring.
/// This is set for the following s: - /// - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// + /// + /// ///
public LoadSceneMode LoadSceneMode; /// - /// This will be set to the scene name that the event pertains to. + /// This will be set to the scene name that the event pertains to.
/// This is set for the following s: - /// - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// + /// + /// ///
public string SceneName; /// - /// When a scene is loaded, the Scene structure is returned. + /// When a scene is loaded, the Scene structure is returned.
/// This is set for the following s: - /// + /// + /// + /// ///
public Scene Scene; /// - /// Events that always set the to the local client identifier, - /// are initiated (and processed locally) by the server-host, and sent to all clients - /// to be processed: - /// - /// - /// - /// - /// - /// Events that always set the to the local client identifier, + /// The client identifier can vary depending upon the following conditions:
+ /// + /// s that always set the + /// to the local client identifier, are initiated (and processed locally) by the + /// server-host, and sent to all clients to be processed.
+ /// + /// + /// + /// + /// + /// + ///
+ /// Events that always set the to the local client identifier, /// are initiated (and processed locally) by a client or server-host, and if initiated /// by a client will always be sent to and processed on the server-host: - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// + /// /// Events that always set the to the ServerId: - /// - /// + /// + /// + /// + /// + /// + ///
///
public ulong ClientId; /// - /// List of clients that completed a loading or unloading event + /// List of clients that completed a loading or unloading event.
/// This is set for the following s: - /// - /// + /// + /// + /// + /// ///
public List ClientsThatCompleted; /// - /// List of clients that timed out during a loading or unloading event + /// List of clients that timed out during a loading or unloading event.
/// This is set for the following s: - /// - /// + /// + /// + /// + /// ///
public List ClientsThatTimedOut; } @@ -122,34 +148,37 @@ namespace Unity.Netcode #endif /// - /// The delegate callback definition for scene event notifications - /// For more details review over and + /// The delegate callback definition for scene event notifications.
+ /// See also:
+ ///
+ /// ///
/// public delegate void SceneEventDelegate(SceneEvent sceneEvent); /// - /// Event that will notify the local client or server of all scene events that take place - /// For more details review over , , and - /// Subscribe to this event to receive all notifications - /// - /// Alternate Single Event Type Notification Registration Options + /// Subscribe to this event to receive all notifications.
+ /// For more details review over and .
+ /// Alternate Single Event Type Notification Registration Options
/// To receive only a specific event type notification or a limited set of notifications you can alternately subscribe to - /// each notification type individually via the following events: - /// -- Invoked only when a event is being processed - /// -- Invoked only when an event is being processed - /// -- Invoked only when a event is being processed - /// -- Invoked only when a event is being processed - /// -- Invoked only when an event is being processed - /// -- Invoked only when a event is being processed - /// -- Invoked only when an event is being processed - /// -- Invoked only when a event is being processed + /// each notification type individually via the following events:
+ /// + /// Invoked only when a event is being processed + /// Invoked only when an event is being processed + /// Invoked only when a event is being processed + /// Invoked only when a event is being processed + /// Invoked only when an event is being processed + /// Invoked only when a event is being processed + /// Invoked only when an event is being processed + /// Invoked only when a event is being processed + /// ///
public event SceneEventDelegate OnSceneEvent; /// - /// Delegate declaration for the OnLoad event - /// View for more information + /// Delegate declaration for the OnLoad event.
+ /// See also:
+ /// for more information ///
/// the client that is processing this event (the server will receive all of these events for every client and itself) /// name of the scene being processed @@ -158,8 +187,9 @@ namespace Unity.Netcode public delegate void OnLoadDelegateHandler(ulong clientId, string sceneName, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation); /// - /// Delegate declaration for the OnUnload event - /// View for more information + /// Delegate declaration for the OnUnload event.
+ /// See also:
+ /// for more information ///
/// the client that is processing this event (the server will receive all of these events for every client and itself) /// name of the scene being processed @@ -167,16 +197,18 @@ namespace Unity.Netcode public delegate void OnUnloadDelegateHandler(ulong clientId, string sceneName, AsyncOperation asyncOperation); /// - /// Delegate declaration for the OnSynchronize event - /// View for more information + /// Delegate declaration for the OnSynchronize event.
+ /// See also:
+ /// for more information ///
/// the client that is processing this event (the server will receive all of these events for every client and itself) public delegate void OnSynchronizeDelegateHandler(ulong clientId); /// - /// Delegate declaration for the OnLoadEventCompleted and OnUnloadEventCompleted events - /// View for more information - /// View for more information + /// Delegate declaration for the OnLoadEventCompleted and OnUnloadEventCompleted events.
+ /// See also:
+ ///
+ /// ///
/// scene pertaining to this event /// of the associated event completed @@ -185,8 +217,9 @@ namespace Unity.Netcode public delegate void OnEventCompletedDelegateHandler(string sceneName, LoadSceneMode loadSceneMode, List clientsCompleted, List clientsTimedOut); /// - /// Delegate declaration for the OnLoadComplete event - /// View for more information + /// Delegate declaration for the OnLoadComplete event.
+ /// See also:
+ /// for more information ///
/// the client that is processing this event (the server will receive all of these events for every client and itself) /// the scene name pertaining to this event @@ -194,38 +227,40 @@ namespace Unity.Netcode public delegate void OnLoadCompleteDelegateHandler(ulong clientId, string sceneName, LoadSceneMode loadSceneMode); /// - /// Delegate declaration for the OnUnloadComplete event - /// View for more information + /// Delegate declaration for the OnUnloadComplete event.
+ /// See also:
+ /// for more information ///
/// the client that is processing this event (the server will receive all of these events for every client and itself) /// the scene name pertaining to this event public delegate void OnUnloadCompleteDelegateHandler(ulong clientId, string sceneName); /// - /// Delegate declaration for the OnSynchronizeComplete event - /// View for more information + /// Delegate declaration for the OnSynchronizeComplete event.
+ /// See also:
+ /// for more information ///
/// the client that completed this event public delegate void OnSynchronizeCompleteDelegateHandler(ulong clientId); /// - /// Invoked when a event is started by the server - /// The server and client(s) will receive this notification + /// Invoked when a event is started by the server.
+ /// Note: The server and connected client(s) will always receive this notification. ///
public event OnLoadDelegateHandler OnLoad; /// - /// Invoked when a event is started by the server - /// The server and client(s) will receive this notification + /// Invoked when a event is started by the server.
+ /// Note: The server and connected client(s) will always receive this notification. ///
public event OnUnloadDelegateHandler OnUnload; /// /// Invoked when a event is started by the server /// after a client is approved for connection in order to synchronize the client with the currently loaded - /// scenes and NetworkObjects. This event signifies the beginning of the synchronization event. - /// The server and client will receive this notification - /// Note: this event is generated on a per newly connected and approved client basis + /// scenes and NetworkObjects. This event signifies the beginning of the synchronization event.
+ /// Note: The server and connected client(s) will always receive this notification. + /// This event is generated on a per newly connected and approved client basis. ///
public event OnSynchronizeDelegateHandler OnSynchronize; @@ -233,8 +268,8 @@ namespace Unity.Netcode /// Invoked when a event is generated by the server. /// This event signifies the end of an existing event as it pertains /// to all clients connected when the event was started. This event signifies that all clients (and server) have - /// finished the event. - /// Note: this is useful to know when all clients have loaded the same scene (single or additive mode) + /// finished the event.
+ /// Note: this is useful to know when all clients have loaded the same scene (single or additive mode) ///
public event OnEventCompletedDelegateHandler OnLoadEventCompleted; @@ -242,32 +277,31 @@ namespace Unity.Netcode /// Invoked when a event is generated by the server. /// This event signifies the end of an existing event as it pertains /// to all clients connected when the event was started. This event signifies that all clients (and server) have - /// finished the event. - /// Note: this is useful to know when all clients have unloaded a specific scene. The will - /// always be for this event + /// finished the event.
+ /// Note: this is useful to know when all clients have unloaded a specific scene. The will + /// always be for this event. ///
public event OnEventCompletedDelegateHandler OnUnloadEventCompleted; /// - /// Invoked when a event is generated by a client or server. - /// The server receives this message from all clients (including itself). - /// Each client receives their own notification sent to the server. + /// Invoked when a event is generated by a client or server.
+ /// Note: The server receives this message from all clients (including itself). + /// Each client receives their own notification sent to the server. ///
public event OnLoadCompleteDelegateHandler OnLoadComplete; /// - /// Invoked when a event is generated by a client or server. - /// The server receives this message from all clients (including itself). - /// Each client receives their own notification sent to the server. + /// Invoked when a event is generated by a client or server.
+ /// Note: The server receives this message from all clients (including itself). + /// Each client receives their own notification sent to the server. ///
public event OnUnloadCompleteDelegateHandler OnUnloadComplete; /// - /// Invoked when a event is generated by a client. - /// The server receives this message from the client, but will never generate this event for itself. - /// Each client receives their own notification sent to the server. - /// Note: This is useful to know that a client has completed the entire connection sequence, loaded all scenes, and - /// synchronized all NetworkObjects. + /// Invoked when a event is generated by a client.
+ /// Note: The server receives this message from the client, but will never generate this event for itself. + /// Each client receives their own notification sent to the server. This is useful to know that a client has + /// completed the entire connection sequence, loaded all scenes, and synchronized all NetworkObjects. ///
public event OnSynchronizeCompleteDelegateHandler OnSynchronizeComplete; @@ -284,9 +318,9 @@ namespace Unity.Netcode /// /// Delegate handler defined by that is invoked before the - /// server or client loads a scene during an active netcode game session. - /// Client Side: In order for clients to be notified of this condition you must assign the delegate handler. - /// Server Side: will return . + /// server or client loads a scene during an active netcode game session.
+ /// Client Side: In order for clients to be notified of this condition you must assign the delegate handler.
+ /// Server Side: will return . ///
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading; @@ -353,11 +387,10 @@ namespace Unity.Netcode internal Scene DontDestroyOnLoadScene; /// - /// LoadSceneMode.Single: All currently loaded scenes on the client will be unloaded and + /// LoadSceneMode.Single: All currently loaded scenes on the client will be unloaded and /// the server's currently active scene will be loaded in single mode on the client - /// unless it was already loaded. - /// - /// LoadSceneMode.Additive: All currently loaded scenes are left as they are and any newly loaded + /// unless it was already loaded.
+ /// LoadSceneMode.Additive: All currently loaded scenes are left as they are and any newly loaded /// scenes will be loaded additively. Users need to determine which scenes are valid to load via the /// method. ///
@@ -506,12 +539,11 @@ namespace Unity.Netcode } /// - /// This will change how clients are initially synchronized. - /// LoadSceneMode.Single: All currently loaded scenes on the client will be unloaded and + /// This will change how clients are initially synchronized.
+ /// LoadSceneMode.Single: All currently loaded scenes on the client will be unloaded and /// the server's currently active scene will be loaded in single mode on the client - /// unless it was already loaded. - /// - /// LoadSceneMode.Additive: All currently loaded scenes are left as they are and any newly loaded + /// unless it was already loaded.
+ /// LoadSceneMode.Additive: All currently loaded scenes are left as they are and any newly loaded /// scenes will be loaded additively. Users need to determine which scenes are valid to load via the /// method. ///
@@ -858,7 +890,7 @@ namespace Unity.Netcode } /// - /// Server Side: + /// Server Side: /// Unloads an additively loaded scene. If you want to unload a mode loaded scene load another scene. /// When applicable, the is delivered within the via the /// @@ -918,7 +950,7 @@ namespace Unity.Netcode } /// - /// Client Side: + /// Client Side: /// Handles scene events. /// private void OnClientUnloadScene(uint sceneEventId) @@ -1056,7 +1088,7 @@ namespace Unity.Netcode } /// - /// Server side: + /// Server side: /// Loads the scene name in either additive or single loading mode. /// When applicable, the is delivered within the via /// diff --git a/Runtime/SceneManagement/SceneEventData.cs b/Runtime/SceneManagement/SceneEventData.cs index 02c3ba7..1d80d2f 100644 --- a/Runtime/SceneManagement/SceneEventData.cs +++ b/Runtime/SceneManagement/SceneEventData.cs @@ -8,84 +8,86 @@ using UnityEngine.SceneManagement; namespace Unity.Netcode { /// - /// The different types of scene events communicated between a server and client. - /// Used by for messages - /// Note: This is only when is enabled - /// See also: + /// The different types of scene events communicated between a server and client.
+ /// Used by for messages.
+ /// Note: This is only when is enabled.
+ /// See also:
+ /// ///
public enum SceneEventType : byte { /// - /// Load a scene - /// Invocation: Server Side - /// Message Flow: Server to client - /// Event Notification: Both server and client are notified a load scene event started + /// Load a scene
+ /// Invocation: Server Side
+ /// Message Flow: Server to client
+ /// Event Notification: Both server and client are notified a load scene event started ///
Load, /// - /// Unload a scene - /// Invocation: Server Side - /// Message Flow: Server to client - /// Event Notification: Both server and client are notified an unload scene event started + /// Unload a scene
+ /// Invocation: Server Side
+ /// Message Flow: Server to client
+ /// Event Notification: Both server and client are notified an unload scene event started. ///
Unload, /// - /// Synchronize current game session state for approved clients - /// Invocation: Server Side - /// Message Flow: Server to client - /// Event Notification: Server and Client receives a local notification (server receives the ClientId being synchronized) + /// Synchronizes current game session state for newly approved clients
+ /// Invocation: Server Side
+ /// Message Flow: Server to client
+ /// Event Notification: Server and Client receives a local notification (server receives the ClientId being synchronized). ///
Synchronize, /// - /// Game session re-synchronization of NetworkObjects that were destroyed during a event - /// Invocation: Server Side - /// Message Flow: Server to client - /// Event Notification: Both server and client receive a local notification - /// Note: This will be removed once snapshot and buffered messages are finalized as it will no longer be needed at that point + /// Game session re-synchronization of NetworkObjects that were destroyed during a event
+ /// Invocation: Server Side
+ /// Message Flow: Server to client
+ /// Event Notification: Both server and client receive a local notification
+ /// Note: This will be removed once snapshot and buffered messages are finalized as it will no longer be needed at that point. ///
ReSynchronize, /// - /// All clients have finished loading a scene - /// Invocation: Server Side - /// Message Flow: Server to Client - /// Event Notification: Both server and client receive a local notification containing the clients that finished - /// as well as the clients that timed out (if any). + /// All clients have finished loading a scene
+ /// Invocation: Server Side
+ /// Message Flow: Server to Client
+ /// Event Notification: Both server and client receive a local notification containing the clients that finished + /// as well as the clients that timed out(if any). ///
LoadEventCompleted, /// - /// All clients have unloaded a scene - /// Invocation: Server Side - /// Message Flow: Server to Client - /// Event Notification: Both server and client receive a local notification containing the clients that finished - /// as well as the clients that timed out (if any). + /// All clients have unloaded a scene
+ /// Invocation: Server Side
+ /// Message Flow: Server to Client
+ /// Event Notification: Both server and client receive a local notification containing the clients that finished + /// as well as the clients that timed out(if any). ///
UnloadEventCompleted, /// - /// A client has finished loading a scene - /// Invocation: Client Side - /// Message Flow: Client to Server - /// Event Notification: Both server and client receive a local notification + /// A client has finished loading a scene
+ /// Invocation: Client Side
+ /// Message Flow: Client to Server
+ /// Event Notification: Both server and client receive a local notification. ///
LoadComplete, /// - /// A client has finished unloading a scene - /// Invocation: Client Side - /// Message Flow: Client to Server - /// Event Notification: Both server and client receive a local notification + /// A client has finished unloading a scene
+ /// Invocation: Client Side
+ /// Message Flow: Client to Server
+ /// Event Notification: Both server and client receive a local notification. ///
UnloadComplete, /// - /// A client has finished synchronizing from a event - /// Invocation: Client Side - /// Message Flow: Client to Server - /// Event Notification: Both server and client receive a local notification + /// A client has finished synchronizing from a event
+ /// Invocation: Client Side
+ /// Message Flow: Client to Server
+ /// Event Notification: Both server and client receive a local notification. ///
SynchronizeComplete, } /// /// Used by for messages - /// Note: This is only when is enabled + /// Note: This is only when is enabled.
+ /// See also: ///
internal class SceneEventData : IDisposable { diff --git a/Runtime/SceneManagement/SceneEventProgress.cs b/Runtime/SceneManagement/SceneEventProgress.cs index 12143a3..8e71eb5 100644 --- a/Runtime/SceneManagement/SceneEventProgress.cs +++ b/Runtime/SceneManagement/SceneEventProgress.cs @@ -9,10 +9,10 @@ namespace Unity.Netcode { /// /// Used by to determine if a server invoked scene event has started. - /// The returned status is stored in the property. - /// Note: This was formally known as SwitchSceneProgress which contained the . + /// The returned status is stored in the property.
+ /// Note: This was formally known as SwitchSceneProgress which contained the . /// All s are now delivered by the event handler - /// via the parameter. + /// via the parameter. ///
public enum SceneEventProgressStatus { @@ -21,31 +21,30 @@ namespace Unity.Netcode /// None, /// - /// The scene event was successfully started + /// The scene event was successfully started. /// Started, /// - /// Returned if you try to unload a scene that was not yet loaded + /// Returned if you try to unload a scene that was not yet loaded. /// SceneNotLoaded, /// - /// Returned if you try to start a new scene event before a previous one is finished + /// Returned if you try to start a new scene event before a previous one is finished. /// SceneEventInProgress, /// /// Returned if the scene name used with - /// or is invalid + /// or is invalid. /// InvalidSceneName, /// /// Server side: Returned if the delegate handler returns false - /// (i.e. scene is considered not valid/safe to load) + /// (i.e. scene is considered not valid/safe to load). /// SceneFailedVerification, /// - /// This is used for internal error notifications. - /// If you receive this event then it is most likely due to a bug. - /// If you receive this event repeatedly, then please open a GitHub issue with steps to replicate + /// This is used for internal error notifications.
+ /// If you receive this event then it is most likely due to a bug (please open a GitHub issue with steps to replicate).
///
InternalNetcodeError, } diff --git a/Runtime/Serialization/FastBufferWriter.cs b/Runtime/Serialization/FastBufferWriter.cs index 2f636ff..0d906b0 100644 --- a/Runtime/Serialization/FastBufferWriter.cs +++ b/Runtime/Serialization/FastBufferWriter.cs @@ -24,6 +24,8 @@ namespace Unity.Netcode internal readonly unsafe WriterHandle* Handle; + private static byte[] s_ByteArrayCache = new byte[65535]; + /// /// The current write position /// @@ -78,6 +80,10 @@ namespace Unity.Netcode /// Maximum size the buffer can grow to. If less than size, buffer cannot grow. public unsafe FastBufferWriter(int size, Allocator allocator, int maxSize = -1) { + // Allocating both the Handle struct and the buffer in a single allocation - sizeof(WriterHandle) + size + // The buffer for the initial allocation is the next block of memory after the handle itself. + // If the buffer grows, a new buffer will be allocated and the handle pointer pointed at the new location... + // The original buffer won't be deallocated until the writer is destroyed since it's part of the handle allocation. Handle = (WriterHandle*)UnsafeUtility.Malloc(sizeof(WriterHandle) + size, UnsafeUtility.AlignOf(), allocator); #if DEVELOPMENT_BUILD || UNITY_EDITOR UnsafeUtility.MemSet(Handle, 0, sizeof(WriterHandle) + size); @@ -349,6 +355,29 @@ namespace Unity.Netcode return ret; } + /// + /// Uses a static cached array to create an array segment with no allocations. + /// This array can only be used until the next time ToTempByteArray() is called on ANY FastBufferWriter, + /// as the cached buffer is shared by all of them and will be overwritten. + /// As such, this should be used with care. + /// + /// + internal unsafe ArraySegment ToTempByteArray() + { + var length = Length; + if (length > s_ByteArrayCache.Length) + { + return new ArraySegment(ToArray(), 0, length); + } + + fixed (byte* b = s_ByteArrayCache) + { + UnsafeUtility.MemCpy(b, Handle->BufferPointer, length); + } + + return new ArraySegment(s_ByteArrayCache, 0, length); + } + /// /// Gets a direct pointer to the underlying buffer /// diff --git a/Tests/Runtime/Metrics/MessagingMetricsTests.cs b/Tests/Runtime/Metrics/MessagingMetricsTests.cs index fe21b45..675e08c 100644 --- a/Tests/Runtime/Metrics/MessagingMetricsTests.cs +++ b/Tests/Runtime/Metrics/MessagingMetricsTests.cs @@ -14,9 +14,9 @@ namespace Unity.Netcode.RuntimeTests.Metrics { public class MessagingMetricsTests : DualClientMetricTestBase { - const uint MessageNameHashSize = 8; - - const uint MessageOverhead = MessageNameHashSize; + private const uint k_MessageNameHashSize = 8; + private static readonly int k_NamedMessageOverhead = (int)k_MessageNameHashSize + FastBufferWriter.GetWriteSize(); + private static readonly int k_UnnamedMessageOverhead = FastBufferWriter.GetWriteSize(); protected override int NbClients => 2; @@ -111,7 +111,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics var namedMessageSent = namedMessageSentMetricValues.First(); Assert.AreEqual(messageName.ToString(), namedMessageSent.Name); Assert.AreEqual(FirstClient.LocalClientId, namedMessageSent.Connection.Id); - Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead, namedMessageSent.BytesCount); + Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + k_NamedMessageOverhead, namedMessageSent.BytesCount); } [UnityTest] @@ -132,7 +132,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics var namedMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); Assert.AreEqual(2, namedMessageSentMetricValues.Count); Assert.That(namedMessageSentMetricValues.Select(x => x.Name), Has.All.EqualTo(messageName.ToString())); - Assert.That(namedMessageSentMetricValues.Select(x => x.BytesCount), Has.All.EqualTo(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead)); + Assert.That(namedMessageSentMetricValues.Select(x => x.BytesCount), Has.All.EqualTo(FastBufferWriter.GetWriteSize(messageName) + k_NamedMessageOverhead)); } [UnityTest] @@ -181,7 +181,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics var namedMessageReceived = namedMessageReceivedValues.First(); Assert.AreEqual(messageName.ToString(), namedMessageReceived.Name); Assert.AreEqual(Server.LocalClientId, namedMessageReceived.Connection.Id); - Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead, namedMessageReceived.BytesCount); + Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + k_NamedMessageOverhead, namedMessageReceived.BytesCount); } [UnityTest] @@ -205,7 +205,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics var unnamedMessageSent = unnamedMessageSentMetricValues.First(); Assert.AreEqual(FirstClient.LocalClientId, unnamedMessageSent.Connection.Id); - Assert.AreEqual(FastBufferWriter.GetWriteSize(message), unnamedMessageSent.BytesCount); + Assert.AreEqual(FastBufferWriter.GetWriteSize(message) + k_UnnamedMessageOverhead, unnamedMessageSent.BytesCount); } [UnityTest] @@ -225,7 +225,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics var unnamedMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); Assert.AreEqual(2, unnamedMessageSentMetricValues.Count); - Assert.That(unnamedMessageSentMetricValues.Select(x => x.BytesCount), Has.All.EqualTo(FastBufferWriter.GetWriteSize(message))); + Assert.That(unnamedMessageSentMetricValues.Select(x => x.BytesCount), Has.All.EqualTo(FastBufferWriter.GetWriteSize(message) + k_UnnamedMessageOverhead)); var clientIds = unnamedMessageSentMetricValues.Select(x => x.Connection.Id).ToList(); Assert.Contains(FirstClient.LocalClientId, clientIds); @@ -268,7 +268,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics var unnamedMessageReceived = unnamedMessageReceivedValues.First(); Assert.AreEqual(Server.LocalClientId, unnamedMessageReceived.Connection.Id); - Assert.AreEqual(FastBufferWriter.GetWriteSize(message), unnamedMessageReceived.BytesCount); + Assert.AreEqual(FastBufferWriter.GetWriteSize(message) + k_UnnamedMessageOverhead, unnamedMessageReceived.BytesCount); } } } diff --git a/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs b/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs index 8d9a096..31151d2 100644 --- a/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs +++ b/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs @@ -58,7 +58,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics var ownershipChangeSent = metricValues.First(); Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeSent.NetworkId.NetworkId); Assert.AreEqual(Server.LocalClientId, ownershipChangeSent.Connection.Id); - Assert.AreEqual(FastBufferWriter.GetWriteSize(), ownershipChangeSent.BytesCount); + Assert.AreEqual(FastBufferWriter.GetWriteSize() + FastBufferWriter.GetWriteSize(), ownershipChangeSent.BytesCount); } [UnityTest] diff --git a/Tests/Runtime/Metrics/ServerLogsMetricTests.cs b/Tests/Runtime/Metrics/ServerLogsMetricTests.cs index 79a6e4c..6471cbf 100644 --- a/Tests/Runtime/Metrics/ServerLogsMetricTests.cs +++ b/Tests/Runtime/Metrics/ServerLogsMetricTests.cs @@ -11,6 +11,9 @@ namespace Unity.Netcode.RuntimeTests.Metrics { internal class ServerLogsMetricTests : SingleClientMetricTestBase { + private static readonly int k_ServerLogSentMessageOverhead = 2 + FastBufferWriter.GetWriteSize(); + private static readonly int k_ServerLogReceivedMessageOverhead = 2; + [UnityTest] public IEnumerator TrackServerLogSentMetric() { @@ -27,7 +30,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics var sentMetric = sentMetrics.First(); Assert.AreEqual(Server.LocalClientId, sentMetric.Connection.Id); Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)sentMetric.LogLevel); - Assert.AreEqual(message.Length + 2, sentMetric.BytesCount); + Assert.AreEqual(message.Length + k_ServerLogSentMessageOverhead, sentMetric.BytesCount); } [UnityTest] @@ -46,7 +49,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics var receivedMetric = receivedMetrics.First(); Assert.AreEqual(Client.LocalClientId, receivedMetric.Connection.Id); Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)receivedMetric.LogLevel); - Assert.AreEqual(message.Length + 2, receivedMetric.BytesCount); + Assert.AreEqual(message.Length + k_ServerLogReceivedMessageOverhead, receivedMetric.BytesCount); } } } diff --git a/package.json b/package.json index 646ab45..a29572d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "1.0.0-pre.2", + "version": "1.0.0-pre.3", "unity": "2020.3", "dependencies": { "com.unity.modules.ai": "1.0.0", @@ -11,12 +11,12 @@ "com.unity.collections": "1.0.0-pre.5" }, "upmCi": { - "footprint": "f3acafb35c17cf3cb48042bf9655c4ada00c34ae" + "footprint": "883b3567bbb5155b7d06ef3c2cac755efa58a235" }, "repository": { "url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git", "type": "git", - "revision": "bcef5b992c5414707ff48c95a48a113fd0e09ad3" + "revision": "3e4df72dadeea8bd622da2824e30541910c79d3d" }, "samples": [ {