using System;
using System.Collections.Generic;
using UnityEngine;
namespace Mirror
{
public enum ConnectState
{
None,
Connecting,
Connected,
Disconnected
}
///
/// This is a network client class used by the networking system. It contains a NetworkConnection that is used to connect to a network server.
/// The NetworkClient handle connection state, messages handlers, and connection configuration. There can be many NetworkClient instances in a process at a time, but only one that is connected to a game server (NetworkServer) that uses spawned objects.
/// NetworkClient has an internal update function where it handles events from the transport layer. This includes asynchronous connect events, disconnect events and incoming data from a server.
/// The NetworkManager has a NetworkClient instance that it uses for games that it starts, but the NetworkClient may be used by itself.
///
public static class NetworkClient
{
static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkClient));
///
/// The registered network message handlers.
///
static readonly Dictionary handlers = new Dictionary();
///
/// The NetworkConnection object this client is using.
///
public static NetworkConnection connection { get; internal set; }
internal static ConnectState connectState = ConnectState.None;
///
/// The IP address of the server that this client is connected to.
/// This will be empty if the client has not connected yet.
///
public static string serverIp => connection.address;
///
/// active is true while a client is connecting/connected
/// (= while the network is active)
///
public static bool active => connectState == ConnectState.Connecting || connectState == ConnectState.Connected;
///
/// This gives the current connection status of the client.
///
public static bool isConnected => connectState == ConnectState.Connected;
///
/// NetworkClient can connect to local server in host mode too
///
public static bool isLocalClient => connection is ULocalConnectionToServer;
///
/// Connect client to a NetworkServer instance.
///
///
public static void Connect(string address)
{
if (logger.LogEnabled()) logger.Log("Client Connect: " + address);
logger.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkClient.Connect, If you are calling Connect manually then make sure to set 'Transport.activeTransport' first");
RegisterSystemHandlers(false);
Transport.activeTransport.enabled = true;
InitializeTransportHandlers();
connectState = ConnectState.Connecting;
Transport.activeTransport.ClientConnect(address);
// setup all the handlers
connection = new NetworkConnectionToServer();
connection.SetHandlers(handlers);
}
///
/// Connect client to a NetworkServer instance.
///
/// Address of the server to connect to
public static void Connect(Uri uri)
{
if (logger.LogEnabled()) logger.Log("Client Connect: " + uri);
logger.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkClient.Connect, If you are calling Connect manually then make sure to set 'Transport.activeTransport' first");
RegisterSystemHandlers(false);
Transport.activeTransport.enabled = true;
InitializeTransportHandlers();
connectState = ConnectState.Connecting;
Transport.activeTransport.ClientConnect(uri);
// setup all the handlers
connection = new NetworkConnectionToServer();
connection.SetHandlers(handlers);
}
public static void ConnectHost()
{
logger.Log("Client Connect Host to Server");
RegisterSystemHandlers(true);
connectState = ConnectState.Connected;
// create local connection objects and connect them
ULocalConnectionToServer connectionToServer = new ULocalConnectionToServer();
ULocalConnectionToClient connectionToClient = new ULocalConnectionToClient();
connectionToServer.connectionToClient = connectionToClient;
connectionToClient.connectionToServer = connectionToServer;
connection = connectionToServer;
connection.SetHandlers(handlers);
// create server connection to local client
NetworkServer.SetLocalConnection(connectionToClient);
}
///
/// connect host mode
///
public static void ConnectLocalServer()
{
NetworkServer.OnConnected(NetworkServer.localConnection);
NetworkServer.localConnection.Send(new ConnectMessage());
}
///
/// disconnect host mode. this is needed to call DisconnectMessage for
/// the host client too.
///
public static void DisconnectLocalServer()
{
// only if host connection is running
if (NetworkServer.localConnection != null)
{
// TODO ConnectLocalServer manually sends a ConnectMessage to the
// local connection. should we send a DisconnectMessage here too?
// (if we do then we get an Unknown Message ID log)
//NetworkServer.localConnection.Send(new DisconnectMessage());
NetworkServer.OnDisconnected(NetworkServer.localConnection.connectionId);
}
}
static void InitializeTransportHandlers()
{
Transport.activeTransport.OnClientConnected.AddListener(OnConnected);
Transport.activeTransport.OnClientDataReceived.AddListener(OnDataReceived);
Transport.activeTransport.OnClientDisconnected.AddListener(OnDisconnected);
Transport.activeTransport.OnClientError.AddListener(OnError);
}
static void OnError(Exception exception)
{
logger.LogException(exception);
}
static void OnDisconnected()
{
connectState = ConnectState.Disconnected;
ClientScene.HandleClientDisconnect(connection);
connection?.InvokeHandler(new DisconnectMessage(), -1);
}
internal static void OnDataReceived(ArraySegment data, int channelId)
{
if (connection != null)
{
connection.TransportReceive(data, channelId);
}
else logger.LogError("Skipped Data message handling because connection is null.");
}
static void OnConnected()
{
if (connection != null)
{
// reset network time stats
NetworkTime.Reset();
// the handler may want to send messages to the client
// thus we should set the connected state before calling the handler
connectState = ConnectState.Connected;
NetworkTime.UpdateClient();
connection.InvokeHandler(new ConnectMessage(), -1);
}
else logger.LogError("Skipped Connect message handling because connection is null.");
}
///
/// Disconnect from server.
/// The disconnect message will be invoked.
///
public static void Disconnect()
{
connectState = ConnectState.Disconnected;
ClientScene.HandleClientDisconnect(connection);
// local or remote connection?
if (isLocalClient)
{
if (isConnected)
{
NetworkServer.localConnection.Send(new DisconnectMessage());
}
NetworkServer.RemoveLocalConnection();
}
else
{
if (connection != null)
{
connection.Disconnect();
connection = null;
RemoveTransportHandlers();
}
}
}
static void RemoveTransportHandlers()
{
// so that we don't register them more than once
Transport.activeTransport.OnClientConnected.RemoveListener(OnConnected);
Transport.activeTransport.OnClientDataReceived.RemoveListener(OnDataReceived);
Transport.activeTransport.OnClientDisconnected.RemoveListener(OnDisconnected);
Transport.activeTransport.OnClientError.RemoveListener(OnError);
}
///
/// This sends a network message with a message Id to the server. This message is sent on channel zero, which by default is the reliable channel.
/// The message must be an instance of a class derived from MessageBase.
/// The message id passed to Send() is used to identify the handler function to invoke on the server when the message is received.
///
/// The message type to unregister.
///
///
public static void Send(T message, int channelId = Channels.DefaultReliable) where T : NetworkMessage
{
if (connection != null)
{
if (connectState == ConnectState.Connected)
{
connection.Send(message, channelId);
}
else logger.LogError("NetworkClient Send when not connected to a server");
}
else logger.LogError("NetworkClient Send with no connection");
}
public static void Update()
{
// local connection?
if (connection is ULocalConnectionToServer localConnection)
{
localConnection.Update();
}
// remote connection?
else
{
// only update things while connected
if (active && connectState == ConnectState.Connected)
{
NetworkTime.UpdateClient();
}
}
}
internal static void RegisterSystemHandlers(bool hostMode)
{
// host mode client / regular client react to some messages differently.
// but we still need to add handlers for all of them to avoid
// 'message id not found' errors.
if (hostMode)
{
RegisterHandler(ClientScene.OnHostClientObjectDestroy);
RegisterHandler(ClientScene.OnHostClientObjectHide);
RegisterHandler((conn, msg) => { }, false);
RegisterHandler(ClientScene.OnHostClientSpawn);
// host mode doesn't need spawning
RegisterHandler((conn, msg) => { });
// host mode doesn't need spawning
RegisterHandler((conn, msg) => { });
RegisterHandler((conn, msg) => { });
}
else
{
RegisterHandler(ClientScene.OnObjectDestroy);
RegisterHandler(ClientScene.OnObjectHide);
RegisterHandler(NetworkTime.OnClientPong, false);
RegisterHandler(ClientScene.OnSpawn);
RegisterHandler(ClientScene.OnObjectSpawnStarted);
RegisterHandler(ClientScene.OnObjectSpawnFinished);
RegisterHandler(ClientScene.OnUpdateVarsMessage);
}
RegisterHandler(ClientScene.OnRPCMessage);
}
///
/// Register a handler for a particular message type.
/// There are several system message types which you can add handlers for. You can also add your own message types.
///
/// Message type
/// Function handler which will be invoked when this message type is received.
/// True if the message requires an authenticated connection
public static void RegisterHandler(Action handler, bool requireAuthentication = true) where T : NetworkMessage
{
int msgType = MessagePacker.GetId();
if (handlers.ContainsKey(msgType))
{
logger.LogWarning($"NetworkClient.RegisterHandler replacing handler for {typeof(T).FullName}, id={msgType}. If replacement is intentional, use ReplaceHandler instead to avoid this warning.");
}
handlers[msgType] = MessagePacker.MessageHandler(handler, requireAuthentication);
}
///
/// Register a handler for a particular message type.
/// There are several system message types which you can add handlers for. You can also add your own message types.
///
/// Message type
/// Function handler which will be invoked when this message type is received.
/// True if the message requires an authenticated connection
public static void RegisterHandler(Action handler, bool requireAuthentication = true) where T : NetworkMessage
{
RegisterHandler((NetworkConnection _, T value) => { handler(value); }, requireAuthentication);
}
///
/// Replaces a handler for a particular message type.
/// See also RegisterHandler(T)(Action(NetworkConnection, T), bool)
///
/// Message type
/// Function handler which will be invoked when this message type is received.
/// True if the message requires an authenticated connection
public static void ReplaceHandler(Action handler, bool requireAuthentication = true) where T : NetworkMessage
{
int msgType = MessagePacker.GetId();
handlers[msgType] = MessagePacker.MessageHandler(handler, requireAuthentication);
}
///
/// Replaces a handler for a particular message type.
/// See also RegisterHandler(T)(Action(NetworkConnection, T), bool)
///
/// Message type
/// Function handler which will be invoked when this message type is received.
/// True if the message requires an authenticated connection
public static void ReplaceHandler(Action handler, bool requireAuthentication = true) where T : NetworkMessage
{
ReplaceHandler((NetworkConnection _, T value) => { handler(value); }, requireAuthentication);
}
///
/// Unregisters a network message handler.
///
/// The message type to unregister.
public static bool UnregisterHandler() where T : NetworkMessage
{
// use int to minimize collisions
int msgType = MessagePacker.GetId();
return handlers.Remove(msgType);
}
///
/// Shut down a client.
/// This should be done when a client is no longer going to be used.
///
public static void Shutdown()
{
logger.Log("Shutting down client.");
ClientScene.Shutdown();
connectState = ConnectState.None;
handlers.Clear();
// disconnect the client connection.
// we do NOT call Transport.Shutdown, because someone only called
// NetworkClient.Shutdown. we can't assume that the server is
// supposed to be shut down too!
Transport.activeTransport.ClientDisconnect();
}
}
}