using System; using System.IO; using MLAPI.Connection; using MLAPI.Logging; using MLAPI.SceneManagement; using MLAPI.Serialization.Pooled; using MLAPI.Spawning; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using System.Collections.Generic; using MLAPI.Configuration; using MLAPI.Messaging.Buffering; using MLAPI.Profiling; using MLAPI.Serialization; using Unity.Profiling; namespace MLAPI.Messaging { internal static class InternalMessageHandler { #if DEVELOPMENT_BUILD || UNITY_EDITOR private static ProfilerMarker s_HandleConnectionRequest = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleConnectionRequest)}"); private static ProfilerMarker s_HandleConnectionApproved = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleConnectionApproved)}"); private static ProfilerMarker s_HandleAddObject = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleAddObject)}"); private static ProfilerMarker s_HandleDestroyObject = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleDestroyObject)}"); private static ProfilerMarker s_HandleSwitchScene = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleSwitchScene)}"); private static ProfilerMarker s_HandleClientSwitchSceneCompleted = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleClientSwitchSceneCompleted)}"); private static ProfilerMarker s_HandleChangeOwner = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleChangeOwner)}"); private static ProfilerMarker s_HandleAddObjects = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleAddObjects)}"); private static ProfilerMarker s_HandleDestroyObjects = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleDestroyObjects)}"); private static ProfilerMarker s_HandleTimeSync = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleTimeSync)}"); private static ProfilerMarker s_HandleNetworkVariableDelta = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleNetworkVariableDelta)}"); private static ProfilerMarker s_HandleNetworkVariableUpdate = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleNetworkVariableUpdate)}"); private static ProfilerMarker s_HandleUnnamedMessage = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleUnnamedMessage)}"); private static ProfilerMarker s_HandleNamedMessage = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleNamedMessage)}"); private static ProfilerMarker s_HandleNetworkLog = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleNetworkLog)}"); private static ProfilerMarker s_RpcReceiveQueueItemServerRpc = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(RpcReceiveQueueItem)}.{nameof(RpcQueueContainer.QueueItemType.ServerRpc)}"); private static ProfilerMarker s_RpcReceiveQueueItemClientRpc = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(RpcReceiveQueueItem)}.{nameof(RpcQueueContainer.QueueItemType.ClientRpc)}"); #endif internal static void HandleConnectionRequest(ulong clientId, Stream stream) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleConnectionRequest.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { ulong configHash = reader.ReadUInt64Packed(); if (!NetworkManager.Singleton.NetworkConfig.CompareConfig(configHash)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"{nameof(NetworkConfig)} mismatch. The configuration between the server and client does not match"); } NetworkManager.Singleton.DisconnectClient(clientId); return; } if (NetworkManager.Singleton.NetworkConfig.ConnectionApproval) { byte[] connectionBuffer = reader.ReadByteArray(); NetworkManager.Singleton.InvokeConnectionApproval(connectionBuffer, clientId, (createPlayerObject, playerPrefabHash, approved, position, rotation) => { NetworkManager.Singleton.HandleApproval(clientId, createPlayerObject, playerPrefabHash, approved, position, rotation); }); } else { NetworkManager.Singleton.HandleApproval(clientId, NetworkManager.Singleton.NetworkConfig.CreatePlayerPrefab, null, true, null, null); } } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleConnectionRequest.End(); #endif } internal static void HandleConnectionApproved(ulong clientId, Stream stream, float receiveTime) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleConnectionApproved.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { NetworkManager.Singleton.LocalClientId = reader.ReadUInt64Packed(); uint sceneIndex = 0; Guid sceneSwitchProgressGuid = new Guid(); if (NetworkManager.Singleton.NetworkConfig.EnableSceneManagement) { sceneIndex = reader.ReadUInt32Packed(); sceneSwitchProgressGuid = new Guid(reader.ReadByteArray()); } bool sceneSwitch = NetworkManager.Singleton.NetworkConfig.EnableSceneManagement && NetworkSceneManager.HasSceneMismatch(sceneIndex); float netTime = reader.ReadSinglePacked(); NetworkManager.Singleton.UpdateNetworkTime(clientId, netTime, receiveTime, true); NetworkManager.Singleton.ConnectedClients.Add(NetworkManager.Singleton.LocalClientId, new NetworkClient { ClientId = NetworkManager.Singleton.LocalClientId }); void DelayedSpawnAction(Stream continuationStream) { using (var continuationReader = PooledNetworkReader.Get(continuationStream)) { if (!NetworkManager.Singleton.NetworkConfig.EnableSceneManagement || NetworkManager.Singleton.NetworkConfig.UsePrefabSync) { NetworkSpawnManager.DestroySceneObjects(); } else { NetworkSpawnManager.ClientCollectSoftSyncSceneObjectSweep(null); } uint objectCount = continuationReader.ReadUInt32Packed(); for (int i = 0; i < objectCount; i++) { bool isPlayerObject = continuationReader.ReadBool(); ulong networkId = continuationReader.ReadUInt64Packed(); ulong ownerId = continuationReader.ReadUInt64Packed(); bool hasParent = continuationReader.ReadBool(); ulong? parentNetworkId = null; if (hasParent) { parentNetworkId = continuationReader.ReadUInt64Packed(); } ulong prefabHash; ulong instanceId; bool softSync; if (!NetworkManager.Singleton.NetworkConfig.EnableSceneManagement || NetworkManager.Singleton.NetworkConfig.UsePrefabSync) { softSync = false; instanceId = 0; prefabHash = continuationReader.ReadUInt64Packed(); } else { softSync = continuationReader.ReadBool(); if (softSync) { instanceId = continuationReader.ReadUInt64Packed(); prefabHash = 0; } else { prefabHash = continuationReader.ReadUInt64Packed(); instanceId = 0; } } Vector3? pos = null; Quaternion? rot = null; if (continuationReader.ReadBool()) { pos = new Vector3(continuationReader.ReadSinglePacked(), continuationReader.ReadSinglePacked(), continuationReader.ReadSinglePacked()); rot = Quaternion.Euler(continuationReader.ReadSinglePacked(), continuationReader.ReadSinglePacked(), continuationReader.ReadSinglePacked()); } var networkObject = NetworkSpawnManager.CreateLocalNetworkObject(softSync, instanceId, prefabHash, parentNetworkId, pos, rot); NetworkSpawnManager.SpawnNetworkObjectLocally(networkObject, networkId, softSync, isPlayerObject, ownerId, continuationStream, false, 0, true, false); Queue bufferQueue = BufferManager.ConsumeBuffersForNetworkId(networkId); // Apply buffered messages if (bufferQueue != null) { while (bufferQueue.Count > 0) { BufferManager.BufferedMessage message = bufferQueue.Dequeue(); NetworkManager.Singleton.HandleIncomingData(message.SenderClientId, message.NetworkChannel, new ArraySegment(message.NetworkBuffer.GetBuffer(), (int)message.NetworkBuffer.Position, (int)message.NetworkBuffer.Length), message.ReceiveTime, false); BufferManager.RecycleConsumedBufferedMessage(message); } } } NetworkSpawnManager.CleanDiffedSceneObjects(); NetworkManager.Singleton.IsConnectedClient = true; NetworkManager.Singleton.InvokeOnClientConnectedCallback(NetworkManager.Singleton.LocalClientId); } } if (sceneSwitch) { UnityAction onSceneLoaded = null; var continuationBuffer = new NetworkBuffer(); continuationBuffer.CopyUnreadFrom(stream); continuationBuffer.Position = 0; void OnSceneLoadComplete() { SceneManager.activeSceneChanged -= onSceneLoaded; NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad = false; DelayedSpawnAction(continuationBuffer); } onSceneLoaded = (oldScene, newScene) => { OnSceneLoadComplete(); }; SceneManager.activeSceneChanged += onSceneLoaded; NetworkSceneManager.OnFirstSceneSwitchSync(sceneIndex, sceneSwitchProgressGuid); } else { DelayedSpawnAction(stream); } } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleConnectionApproved.End(); #endif } internal static void HandleAddObject(ulong clientId, Stream stream) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleAddObject.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { bool isPlayerObject = reader.ReadBool(); ulong networkId = reader.ReadUInt64Packed(); ulong ownerId = reader.ReadUInt64Packed(); bool hasParent = reader.ReadBool(); ulong? parentNetworkId = null; if (hasParent) { parentNetworkId = reader.ReadUInt64Packed(); } ulong prefabHash; ulong instanceId; bool softSync; if (!NetworkManager.Singleton.NetworkConfig.EnableSceneManagement || NetworkManager.Singleton.NetworkConfig.UsePrefabSync) { softSync = false; instanceId = 0; prefabHash = reader.ReadUInt64Packed(); } else { softSync = reader.ReadBool(); if (softSync) { instanceId = reader.ReadUInt64Packed(); prefabHash = 0; } else { prefabHash = reader.ReadUInt64Packed(); instanceId = 0; } } Vector3? pos = null; Quaternion? rot = null; if (reader.ReadBool()) { pos = new Vector3(reader.ReadSinglePacked(), reader.ReadSinglePacked(), reader.ReadSinglePacked()); rot = Quaternion.Euler(reader.ReadSinglePacked(), reader.ReadSinglePacked(), reader.ReadSinglePacked()); } bool hasPayload = reader.ReadBool(); int payLoadLength = hasPayload ? reader.ReadInt32Packed() : 0; var networkObject = NetworkSpawnManager.CreateLocalNetworkObject(softSync, instanceId, prefabHash, parentNetworkId, pos, rot); NetworkSpawnManager.SpawnNetworkObjectLocally(networkObject, networkId, softSync, isPlayerObject, ownerId, stream, hasPayload, payLoadLength, true, false); Queue bufferQueue = BufferManager.ConsumeBuffersForNetworkId(networkId); // Apply buffered messages if (bufferQueue != null) { while (bufferQueue.Count > 0) { BufferManager.BufferedMessage message = bufferQueue.Dequeue(); NetworkManager.Singleton.HandleIncomingData(message.SenderClientId, message.NetworkChannel, new ArraySegment(message.NetworkBuffer.GetBuffer(), (int)message.NetworkBuffer.Position, (int)message.NetworkBuffer.Length), message.ReceiveTime, false); BufferManager.RecycleConsumedBufferedMessage(message); } } } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleAddObject.End(); #endif } internal static void HandleDestroyObject(ulong clientId, Stream stream) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleDestroyObject.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { ulong networkId = reader.ReadUInt64Packed(); NetworkSpawnManager.OnDestroyObject(networkId, true); } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleDestroyObject.End(); #endif } internal static void HandleSwitchScene(ulong clientId, Stream stream) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleSwitchScene.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { uint sceneIndex = reader.ReadUInt32Packed(); Guid switchSceneGuid = new Guid(reader.ReadByteArray()); var objectBuffer = new NetworkBuffer(); objectBuffer.CopyUnreadFrom(stream); objectBuffer.Position = 0; NetworkSceneManager.OnSceneSwitch(sceneIndex, switchSceneGuid, objectBuffer); } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleSwitchScene.End(); #endif } internal static void HandleClientSwitchSceneCompleted(ulong clientId, Stream stream) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleClientSwitchSceneCompleted.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { NetworkSceneManager.OnClientSwitchSceneCompleted(clientId, new Guid(reader.ReadByteArray())); } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleClientSwitchSceneCompleted.End(); #endif } internal static void HandleChangeOwner(ulong clientId, Stream stream) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleChangeOwner.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { ulong networkId = reader.ReadUInt64Packed(); ulong ownerClientId = reader.ReadUInt64Packed(); if (NetworkSpawnManager.SpawnedObjects[networkId].OwnerClientId == NetworkManager.Singleton.LocalClientId) { //We are current owner. NetworkSpawnManager.SpawnedObjects[networkId].InvokeBehaviourOnLostOwnership(); } if (ownerClientId == NetworkManager.Singleton.LocalClientId) { //We are new owner. NetworkSpawnManager.SpawnedObjects[networkId].InvokeBehaviourOnGainedOwnership(); } NetworkSpawnManager.SpawnedObjects[networkId].OwnerClientId = ownerClientId; } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleChangeOwner.End(); #endif } internal static void HandleAddObjects(ulong clientId, Stream stream) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleAddObjects.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { ushort objectCount = reader.ReadUInt16Packed(); for (int i = 0; i < objectCount; i++) { HandleAddObject(clientId, stream); } } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleAddObjects.End(); #endif } internal static void HandleDestroyObjects(ulong clientId, Stream stream) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleDestroyObjects.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { ushort objectCount = reader.ReadUInt16Packed(); for (int i = 0; i < objectCount; i++) { HandleDestroyObject(clientId, stream); } } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleDestroyObjects.End(); #endif } internal static void HandleTimeSync(ulong clientId, Stream stream, float receiveTime) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleTimeSync.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { float netTime = reader.ReadSinglePacked(); NetworkManager.Singleton.UpdateNetworkTime(clientId, netTime, receiveTime); } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleTimeSync.End(); #endif } internal static void HandleNetworkVariableDelta(ulong clientId, Stream stream, Action bufferCallback, PreBufferPreset bufferPreset) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleNetworkVariableDelta.Begin(); #endif if (!NetworkManager.Singleton.NetworkConfig.EnableNetworkVariable) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_DELTA)} received but {nameof(NetworkConfig.EnableNetworkVariable)} is false"); } return; } using (var reader = PooledNetworkReader.Get(stream)) { ulong networkObjectId = reader.ReadUInt64Packed(); ushort networkBehaviourIndex = reader.ReadUInt16Packed(); if (NetworkSpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) { NetworkBehaviour instance = NetworkSpawnManager.SpawnedObjects[networkObjectId].GetNetworkBehaviourAtOrderIndex(networkBehaviourIndex); if (instance == null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_DELTA)} message received for a non-existent behaviour. {nameof(networkObjectId)}: {networkObjectId}, {nameof(networkBehaviourIndex)}: {networkBehaviourIndex}"); } } else { NetworkBehaviour.HandleNetworkVariableDeltas(instance.NetworkVariableFields, stream, clientId, instance); } } else if (NetworkManager.Singleton.IsServer || !NetworkManager.Singleton.NetworkConfig.EnableMessageBuffering) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_DELTA)} message received for a non-existent object with {nameof(networkObjectId)}: {networkObjectId}. This delta was lost."); } } else { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_DELTA)} message received for a non-existent object with {nameof(networkObjectId)}: {networkObjectId}. This delta will be buffered and might be recovered."); } bufferCallback(networkObjectId, bufferPreset); } } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleNetworkVariableDelta.End(); #endif } internal static void HandleNetworkVariableUpdate(ulong clientId, Stream stream, Action bufferCallback, PreBufferPreset bufferPreset) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleNetworkVariableUpdate.Begin(); #endif if (!NetworkManager.Singleton.NetworkConfig.EnableNetworkVariable) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_UPDATE)} update received but {nameof(NetworkConfig.EnableNetworkVariable)} is false"); } return; } using (var reader = PooledNetworkReader.Get(stream)) { ulong networkObjectId = reader.ReadUInt64Packed(); ushort networkBehaviourIndex = reader.ReadUInt16Packed(); if (NetworkSpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) { var networkBehaviour = NetworkSpawnManager.SpawnedObjects[networkObjectId].GetNetworkBehaviourAtOrderIndex(networkBehaviourIndex); if (networkBehaviour == null) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_UPDATE)} message received for a non-existent behaviour. {nameof(networkObjectId)}: {networkObjectId}, {nameof(networkBehaviourIndex)}: {networkBehaviourIndex}"); } } else { NetworkBehaviour.HandleNetworkVariableUpdate(networkBehaviour.NetworkVariableFields, stream, clientId, networkBehaviour); } } else if (NetworkManager.Singleton.IsServer || !NetworkManager.Singleton.NetworkConfig.EnableMessageBuffering) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_UPDATE)} message received for a non-existent object with {nameof(networkObjectId)}: {networkObjectId}. This delta was lost."); } } else { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_UPDATE)} message received for a non-existent object with {nameof(networkObjectId)}: {networkObjectId}. This delta will be buffered and might be recovered."); } bufferCallback(networkObjectId, bufferPreset); } } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleNetworkVariableUpdate.End(); #endif } /// /// Converts the stream to a PerformanceQueueItem and adds it to the receive queue /// /// /// /// internal static void RpcReceiveQueueItem(ulong clientId, Stream stream, float receiveTime, RpcQueueContainer.QueueItemType queueItemType) { if (NetworkManager.Singleton.IsServer && clientId == NetworkManager.Singleton.ServerClientId) { return; } ProfilerStatManager.RpcsRcvd.Record(); PerformanceDataManager.Increment(ProfilerConstants.RpcReceived); #if DEVELOPMENT_BUILD || UNITY_EDITOR switch (queueItemType) { case RpcQueueContainer.QueueItemType.ServerRpc: s_RpcReceiveQueueItemServerRpc.Begin(); break; case RpcQueueContainer.QueueItemType.ClientRpc: s_RpcReceiveQueueItemClientRpc.Begin(); break; } #endif var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer; rpcQueueContainer.AddQueueItemToInboundFrame(queueItemType, receiveTime, clientId, (NetworkBuffer)stream); #if DEVELOPMENT_BUILD || UNITY_EDITOR switch (queueItemType) { case RpcQueueContainer.QueueItemType.ServerRpc: s_RpcReceiveQueueItemServerRpc.End(); break; case RpcQueueContainer.QueueItemType.ClientRpc: s_RpcReceiveQueueItemClientRpc.End(); break; } #endif } internal static void HandleUnnamedMessage(ulong clientId, Stream stream) { PerformanceDataManager.Increment(ProfilerConstants.UnnamedMessageReceived); ProfilerStatManager.UnnamedMessage.Record(); #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleUnnamedMessage.Begin(); #endif CustomMessagingManager.InvokeUnnamedMessage(clientId, stream); #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleUnnamedMessage.End(); #endif } internal static void HandleNamedMessage(ulong clientId, Stream stream) { PerformanceDataManager.Increment(ProfilerConstants.NamedMessageReceived); ProfilerStatManager.NamedMessage.Record(); #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleNamedMessage.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { ulong hash = reader.ReadUInt64Packed(); CustomMessagingManager.InvokeNamedMessage(hash, clientId, stream); } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleNamedMessage.End(); #endif } internal static void HandleNetworkLog(ulong clientId, Stream stream) { #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleNetworkLog.Begin(); #endif using (var reader = PooledNetworkReader.Get(stream)) { NetworkLog.LogType logType = (NetworkLog.LogType)reader.ReadByte(); string message = reader.ReadStringPacked(); switch (logType) { case NetworkLog.LogType.Info: NetworkLog.LogInfoServerLocal(message, clientId); break; case NetworkLog.LogType.Warning: NetworkLog.LogWarningServerLocal(message, clientId); break; case NetworkLog.LogType.Error: NetworkLog.LogErrorServerLocal(message, clientId); break; } } #if DEVELOPMENT_BUILD || UNITY_EDITOR s_HandleNetworkLog.End(); #endif } } }