// note: original HLAPI HandleBytes function handled >1 message in a while loop, but this wasn't necessary // anymore because NetworkServer/NetworkClient Update both use while loops to handle >1 data events per // frame already. // -> in other words, we always receive 1 message per Receive call, never two. // -> can be tested easily with a 1000ms send delay and then logging amount received in while loops here // and in NetworkServer/Client Update. HandleBytes already takes exactly one. /// <summary> /// This virtual function allows custom network connection classes to process data from the network before it is passed to the application. /// </summary> /// <param name="buffer">The data recieved.</param> public virtual void TransportReceive(ArraySegment <byte> buffer) { #if MIRROR_PROFILING // -1 for now since we don't know the incoming segment channel NetworkProfiler.RecordTraffic(NetworkDirection.Incoming, -1, buffer.Count); #endif // unpack message NetworkReader reader = new NetworkReader(buffer); if (MessagePacker.UnpackMessage(reader, out int msgType)) { // logging if (logNetworkMessages) { Debug.Log("ConnectionRecv con:" + connectionId + " msgType:" + msgType + " content:" + BitConverter.ToString(buffer.Array, buffer.Offset, buffer.Count)); } // try to invoke the handler for that message if (InvokeHandler(msgType, reader)) { lastMessageTime = Time.time; } } else { Debug.LogError("Closed connection: " + connectionId + ". Invalid message header."); Disconnect(); } }
protected void SendRPCInternal(Type invokeClass, string rpcName, NetworkWriter writer, int channelId) { // this was in Weaver before if (!NetworkServer.active) { Debug.LogError("RPC Function " + rpcName + " called on Client."); return; } // This cannot use NetworkServer.active, as that is not specific to this object. if (!isServer) { Debug.LogWarning("ClientRpc " + rpcName + " called on un-spawned object: " + name); return; } // construct the message RpcMessage message = new RpcMessage { netId = netId, componentIndex = ComponentIndex, functionHash = GetMethodHash(invokeClass, rpcName), // type+func so Inventory.RpcUse != Equipment.RpcUse payload = writer.ToArraySegment() // segment to avoid reader allocations }; NetworkServer.SendToReady(netIdentity, message, channelId); #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Outgoing, typeof(RpcMessage), $"{invokeClass.GetType()}.{rpcName}", 1); #endif }
/// <summary> /// Signal that the client connection is ready to enter the game. /// <para>This could be for example when a client enters an ongoing game and has finished loading the current scene. The server should respond to the SYSTEM_READY event with an appropriate handler which instantiates the players object for example.</para> /// </summary> /// <param name="conn">The client connection which is ready.</param> /// <returns>True if succcessful</returns> public static bool Ready(NetworkConnection conn) { if (ready) { Debug.LogError("A connection has already been set as ready. There can only be one."); return(false); } if (LogFilter.Debug) { Debug.Log("ClientScene.Ready() called with connection [" + conn + "]"); } #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Outgoing, typeof(ReadyMessage), null, 1); #endif if (conn != null) { conn.Send(new ReadyMessage()); ready = true; readyConnection = conn; readyConnection.isReady = true; return(true); } Debug.LogError("Ready() called with invalid connection object: conn=null"); return(false); }
internal static void OnLocalClientObjectDestroy(NetworkConnection _, ObjectDestroyMessage msg) { if (LogFilter.Debug) { Debug.Log("ClientScene.OnLocalObjectObjDestroy netId:" + msg.netId); } #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Incoming, typeof(ObjectDestroyMessage), msg.netId.ToString(), 1); #endif NetworkIdentity.spawned.Remove(msg.netId); }
internal static void OnObjectSpawnStarted(NetworkConnection _, ObjectSpawnStartedMessage msg) { if (LogFilter.Debug) { Debug.Log("SpawnStarted"); } #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Incoming, typeof(ObjectSpawnStartedMessage), null, 1); #endif PrepareToSpawnSceneObjects(); isSpawnFinished = false; }
internal static void OnSpawnSceneObject(NetworkConnection _, SpawnSceneObjectMessage msg) { if (LogFilter.Debug) { Debug.Log("Client spawn scene handler instantiating [netId:" + msg.netId + " sceneId:" + msg.sceneId + " pos:" + msg.position); } // owner? if (msg.owner) { OnSpawnMessageForOwner(msg.netId); } if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null) { // this object already exists (was in the scene) localObject.Reset(); ApplySpawnPayload(localObject, msg.position, msg.rotation, msg.scale, msg.payload, msg.netId); #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Incoming, typeof(SpawnSceneObjectMessage), localObject.gameObject.name, 1); #endif return; } NetworkIdentity spawnedId = SpawnSceneObject(msg.sceneId); if (spawnedId == null) { Debug.LogError("Spawn scene object not found for " + msg.sceneId.ToString("X") + " SpawnableObjects.Count=" + spawnableObjects.Count); // dump the whole spawnable objects dict for easier debugging if (LogFilter.Debug) { foreach (KeyValuePair <ulong, NetworkIdentity> kvp in spawnableObjects) { Debug.Log("Spawnable: SceneId=" + kvp.Key + " name=" + kvp.Value.name); } } return; } if (LogFilter.Debug) { Debug.Log("Client spawn for [netId:" + msg.netId + "] [sceneId:" + msg.sceneId + "] obj:" + spawnedId.gameObject.name); } #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Incoming, typeof(SpawnSceneObjectMessage), spawnedId.gameObject.name, 1); #endif spawnedId.Reset(); spawnedId.pendingOwner = msg.owner; ApplySpawnPayload(spawnedId, msg.position, msg.rotation, msg.scale, msg.payload, msg.netId); }
// NetworkIdentity.UNetStaticUpdate is called from UnityEngine while LLAPI network is active. // If we want TCP then we need to call it manually. Probably best from NetworkManager, although this means that we can't use NetworkServer/NetworkClient without a NetworkManager invoking Update anymore. /// <summary> /// virtual so that inheriting classes' LateUpdate() can call base.LateUpdate() too /// </summary> public virtual void LateUpdate() { // call it while the NetworkManager exists. // -> we don't only call while Client/Server.Connected, because then we would stop if disconnected and the // NetworkClient wouldn't receive the last Disconnect event, result in all kinds of issues NetworkServer.Update(); NetworkClient.Update(); UpdateScene(); #if MIRROR_PROFILING NetworkProfiler.Tick(Time.time); #endif }
internal static void OnLocalClientObjectHide(NetworkConnection _, ObjectHideMessage msg) { if (LogFilter.Debug) { Debug.Log("ClientScene::OnLocalObjectObjHide netId:" + msg.netId); } #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Incoming, typeof(ObjectHideMessage), msg.netId.ToString(), 1); #endif if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null) { localObject.OnSetLocalVisibility(false); } }
/// <summary> /// This virtual function allows custom network connection classes to process data send by the application before it goes to the network transport layer. /// </summary> /// <param name="channelId">Channel to send data on.</param> /// <param name="bytes">Data to send.</param> /// <returns></returns> public virtual bool TransportSend(int channelId, byte[] bytes) { #if MIRROR_PROFILING NetworkProfiler.RecordTraffic(NetworkDirection.Outgoing, channelId, bytes.Length); #endif if (Transport.activeTransport.ClientConnected()) { return(Transport.activeTransport.ClientSend(channelId, bytes)); } else if (Transport.activeTransport.ServerActive()) { return(Transport.activeTransport.ServerSend(connectionId, channelId, bytes)); } return(false); }
internal static void OnClientAuthority(NetworkConnection _, ClientAuthorityMessage msg) { if (LogFilter.Debug) { Debug.Log("ClientScene.OnClientAuthority for netId: " + msg.netId); } if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity identity)) { #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Incoming, typeof(ClientAuthorityMessage), identity.gameObject.name, 1); #endif identity.HandleClientAuthority(msg.authority); } }
ulong DirtyObjectBits() { ulong dirtyObjects = 0; for (int i = 0; i < syncObjects.Count; i++) { SyncObject syncObject = syncObjects[i]; if (syncObject.IsDirty) { dirtyObjects |= 1UL << i; #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Outgoing, typeof(UpdateVarsMessage), syncObject.GetType().Name, 1); #endif } } return(dirtyObjects); }
internal static void OnObjectSpawnFinished(NetworkConnection _, ObjectSpawnFinishedMessage msg) { if (LogFilter.Debug) { Debug.Log("SpawnFinished"); } NetworkProfiler.RecordMessage(NetworkDirection.Incoming, typeof(ObjectSpawnFinishedMessage), null, 1); // paul: Initialize the objects in the same order as they were initialized // in the server. This is important if spawned objects // use data from scene objects foreach (NetworkIdentity identity in NetworkIdentity.spawned.Values.OrderBy(uv => uv.netId)) { if (!identity.isClient) { identity.OnStartClient(); CheckForOwner(identity); } } isSpawnFinished = true; }
protected void SendEventInternal(Type invokeClass, string eventName, NetworkWriter writer, int channelId) { if (!NetworkServer.active) { Debug.LogWarning("SendEvent no server?"); return; } // construct the message SyncEventMessage message = new SyncEventMessage { netId = netId, componentIndex = ComponentIndex, functionHash = GetMethodHash(invokeClass, eventName), // type+func so Inventory.RpcUse != Equipment.RpcUse payload = writer.ToArraySegment() // segment to avoid reader allocations }; NetworkServer.SendToReady(netIdentity, message, channelId); #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Outgoing, typeof(SyncEventMessage), $"{invokeClass.GetType()}.{eventName}", 1); #endif }
static void DestroyObject(uint netId) { if (LogFilter.Debug) { Debug.Log("ClientScene.OnObjDestroy netId:" + netId); } if (NetworkIdentity.spawned.TryGetValue(netId, out NetworkIdentity localObject) && localObject != null) { NetworkProfiler.RecordMessage(NetworkDirection.Incoming, typeof(ObjectDestroyMessage), netId.ToString(), 1); localObject.OnNetworkDestroy(); if (!InvokeUnSpawnHandler(localObject.assetId, localObject.gameObject)) { // default handling if (localObject.sceneId == 0) { Object.Destroy(localObject.gameObject); } else { // scene object.. disable it in scene instead of destroying localObject.gameObject.SetActive(false); spawnableObjects[localObject.sceneId] = localObject; } } NetworkIdentity.spawned.Remove(netId); localObject.MarkForReset(); } else { if (LogFilter.Debug) { Debug.LogWarning("Did not find target for destroy message for " + netId); } } }
// this is called from message handler for Owner message internal static void InternalAddPlayer(NetworkIdentity identity) { if (LogFilter.Debug) { Debug.LogWarning("ClientScene.InternalAddPlayer"); } #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Incoming, typeof(AddPlayerMessage), identity.gameObject.name, 1); #endif // NOTE: It can be "normal" when changing scenes for the player to be destroyed and recreated. // But, the player structures are not cleaned up, we'll just replace the old player localPlayer = identity; if (readyConnection != null) { readyConnection.playerController = identity; } else { Debug.LogWarning("No ready connection found for setting player controller during InternalAddPlayer"); } }
/// <summary> /// Removes the player from the game. /// </summary> /// <returns>True if succcessful</returns> public static bool RemovePlayer() { if (LogFilter.Debug) { Debug.Log("ClientScene.RemovePlayer() called with connection [" + readyConnection + "]"); } if (readyConnection.playerController != null) { readyConnection.Send(new RemovePlayerMessage()); #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Incoming, typeof(RemovePlayerMessage), readyConnection.playerController.gameObject.name, 1); #endif Object.Destroy(readyConnection.playerController.gameObject); readyConnection.playerController = null; localPlayer = null; return(true); } return(false); }
protected void SendCommandInternal(Type invokeClass, string cmdName, NetworkWriter writer, int channelId) { // this was in Weaver before // NOTE: we could remove this later to allow calling Cmds on Server // to avoid Wrapper functions. a lot of people requested this. if (!NetworkClient.active) { Debug.LogError("Command Function " + cmdName + " called on server without an active client."); return; } // local players can always send commands, regardless of authority, other objects must have authority. if (!(isLocalPlayer || hasAuthority)) { Debug.LogWarning("Trying to send command for object without authority."); return; } if (ClientScene.readyConnection == null) { Debug.LogError("Send command attempted with no client running [client=" + connectionToServer + "]."); return; } // construct the message CommandMessage message = new CommandMessage { netId = netId, componentIndex = ComponentIndex, functionHash = GetMethodHash(invokeClass, cmdName), // type+func so Inventory.RpcUse != Equipment.RpcUse payload = writer.ToArraySegment() // segment to avoid reader allocations }; ClientScene.readyConnection.Send(message, channelId); #if MIRROR_PROFILING NetworkProfiler.RecordMessage(NetworkDirection.Outgoing, typeof(CommandMessage), $"{invokeClass.GetType()}.{cmdName}", 1); #endif }