mirror of
https://github.com/smyalygames/monopoly.git
synced 2025-05-18 22:34:30 +02:00
385 lines
17 KiB
C#
385 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace Mirror
|
|
{
|
|
public enum ConnectState
|
|
{
|
|
None,
|
|
Connecting,
|
|
Connected,
|
|
Disconnected
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is a network client class used by the networking system. It contains a NetworkConnection that is used to connect to a network server.
|
|
/// <para>The <see cref="NetworkClient">NetworkClient</see> handle connection state, messages handlers, and connection configuration. There can be many <see cref="NetworkClient">NetworkClient</see> instances in a process at a time, but only one that is connected to a game server (<see cref="NetworkServer">NetworkServer</see>) that uses spawned objects.</para>
|
|
/// <para><see cref="NetworkClient">NetworkClient</see> 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.</para>
|
|
/// <para>The <see cref="NetworkManager">NetworkManager</see> has a NetworkClient instance that it uses for games that it starts, but the NetworkClient may be used by itself.</para>
|
|
/// </summary>
|
|
public static class NetworkClient
|
|
{
|
|
static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkClient));
|
|
|
|
/// <summary>
|
|
/// The registered network message handlers.
|
|
/// </summary>
|
|
static readonly Dictionary<int, NetworkMessageDelegate> handlers = new Dictionary<int, NetworkMessageDelegate>();
|
|
|
|
/// <summary>
|
|
/// The NetworkConnection object this client is using.
|
|
/// </summary>
|
|
public static NetworkConnection connection { get; internal set; }
|
|
|
|
internal static ConnectState connectState = ConnectState.None;
|
|
|
|
/// <summary>
|
|
/// The IP address of the server that this client is connected to.
|
|
/// <para>This will be empty if the client has not connected yet.</para>
|
|
/// </summary>
|
|
public static string serverIp => connection.address;
|
|
|
|
/// <summary>
|
|
/// active is true while a client is connecting/connected
|
|
/// (= while the network is active)
|
|
/// </summary>
|
|
public static bool active => connectState == ConnectState.Connecting || connectState == ConnectState.Connected;
|
|
|
|
/// <summary>
|
|
/// This gives the current connection status of the client.
|
|
/// </summary>
|
|
public static bool isConnected => connectState == ConnectState.Connected;
|
|
|
|
/// <summary>
|
|
/// NetworkClient can connect to local server in host mode too
|
|
/// </summary>
|
|
public static bool isLocalClient => connection is ULocalConnectionToServer;
|
|
|
|
/// <summary>
|
|
/// Connect client to a NetworkServer instance.
|
|
/// </summary>
|
|
/// <param name="address"></param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connect client to a NetworkServer instance.
|
|
/// </summary>
|
|
/// <param name="uri">Address of the server to connect to</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// connect host mode
|
|
/// </summary>
|
|
public static void ConnectLocalServer()
|
|
{
|
|
NetworkServer.OnConnected(NetworkServer.localConnection);
|
|
NetworkServer.localConnection.Send(new ConnectMessage());
|
|
}
|
|
|
|
/// <summary>
|
|
/// disconnect host mode. this is needed to call DisconnectMessage for
|
|
/// the host client too.
|
|
/// </summary>
|
|
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<byte> 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.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect from server.
|
|
/// <para>The disconnect message will be invoked.</para>
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// <para>The message must be an instance of a class derived from MessageBase.</para>
|
|
/// <para>The message id passed to Send() is used to identify the handler function to invoke on the server when the message is received.</para>
|
|
/// </summary>
|
|
/// <typeparam name="T">The message type to unregister.</typeparam>
|
|
/// <param name="message"></param>
|
|
/// <param name="channelId"></param>
|
|
public static void Send<T>(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<ObjectDestroyMessage>(ClientScene.OnHostClientObjectDestroy);
|
|
RegisterHandler<ObjectHideMessage>(ClientScene.OnHostClientObjectHide);
|
|
RegisterHandler<NetworkPongMessage>((conn, msg) => { }, false);
|
|
RegisterHandler<SpawnMessage>(ClientScene.OnHostClientSpawn);
|
|
// host mode doesn't need spawning
|
|
RegisterHandler<ObjectSpawnStartedMessage>((conn, msg) => { });
|
|
// host mode doesn't need spawning
|
|
RegisterHandler<ObjectSpawnFinishedMessage>((conn, msg) => { });
|
|
RegisterHandler<UpdateVarsMessage>((conn, msg) => { });
|
|
}
|
|
else
|
|
{
|
|
RegisterHandler<ObjectDestroyMessage>(ClientScene.OnObjectDestroy);
|
|
RegisterHandler<ObjectHideMessage>(ClientScene.OnObjectHide);
|
|
RegisterHandler<NetworkPongMessage>(NetworkTime.OnClientPong, false);
|
|
RegisterHandler<SpawnMessage>(ClientScene.OnSpawn);
|
|
RegisterHandler<ObjectSpawnStartedMessage>(ClientScene.OnObjectSpawnStarted);
|
|
RegisterHandler<ObjectSpawnFinishedMessage>(ClientScene.OnObjectSpawnFinished);
|
|
RegisterHandler<UpdateVarsMessage>(ClientScene.OnUpdateVarsMessage);
|
|
}
|
|
RegisterHandler<RpcMessage>(ClientScene.OnRPCMessage);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a handler for a particular message type.
|
|
/// <para>There are several system message types which you can add handlers for. You can also add your own message types.</para>
|
|
/// </summary>
|
|
/// <typeparam name="T">Message type</typeparam>
|
|
/// <param name="handler">Function handler which will be invoked when this message type is received.</param>
|
|
/// <param name="requireAuthentication">True if the message requires an authenticated connection</param>
|
|
public static void RegisterHandler<T>(Action<NetworkConnection, T> handler, bool requireAuthentication = true) where T : NetworkMessage
|
|
{
|
|
int msgType = MessagePacker.GetId<T>();
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a handler for a particular message type.
|
|
/// <para>There are several system message types which you can add handlers for. You can also add your own message types.</para>
|
|
/// </summary>
|
|
/// <typeparam name="T">Message type</typeparam>
|
|
/// <param name="handler">Function handler which will be invoked when this message type is received.</param>
|
|
/// <param name="requireAuthentication">True if the message requires an authenticated connection</param>
|
|
public static void RegisterHandler<T>(Action<T> handler, bool requireAuthentication = true) where T : NetworkMessage
|
|
{
|
|
RegisterHandler((NetworkConnection _, T value) => { handler(value); }, requireAuthentication);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces a handler for a particular message type.
|
|
/// <para>See also <see cref="RegisterHandler{T}(Action{NetworkConnection, T}, bool)">RegisterHandler(T)(Action(NetworkConnection, T), bool)</see></para>
|
|
/// </summary>
|
|
/// <typeparam name="T">Message type</typeparam>
|
|
/// <param name="handler">Function handler which will be invoked when this message type is received.</param>
|
|
/// <param name="requireAuthentication">True if the message requires an authenticated connection</param>
|
|
public static void ReplaceHandler<T>(Action<NetworkConnection, T> handler, bool requireAuthentication = true) where T : NetworkMessage
|
|
{
|
|
int msgType = MessagePacker.GetId<T>();
|
|
handlers[msgType] = MessagePacker.MessageHandler(handler, requireAuthentication);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces a handler for a particular message type.
|
|
/// <para>See also <see cref="RegisterHandler{T}(Action{NetworkConnection, T}, bool)">RegisterHandler(T)(Action(NetworkConnection, T), bool)</see></para>
|
|
/// </summary>
|
|
/// <typeparam name="T">Message type</typeparam>
|
|
/// <param name="handler">Function handler which will be invoked when this message type is received.</param>
|
|
/// <param name="requireAuthentication">True if the message requires an authenticated connection</param>
|
|
public static void ReplaceHandler<T>(Action<T> handler, bool requireAuthentication = true) where T : NetworkMessage
|
|
{
|
|
ReplaceHandler((NetworkConnection _, T value) => { handler(value); }, requireAuthentication);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregisters a network message handler.
|
|
/// </summary>
|
|
/// <typeparam name="T">The message type to unregister.</typeparam>
|
|
public static bool UnregisterHandler<T>() where T : NetworkMessage
|
|
{
|
|
// use int to minimize collisions
|
|
int msgType = MessagePacker.GetId<T>();
|
|
return handlers.Remove(msgType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shut down a client.
|
|
/// <para>This should be done when a client is no longer going to be used.</para>
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
}
|