using FishNet.Component.Observing;
using FishNet.Documenting;
using FishNet.Managing;
using FishNet.Managing.Timing;
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using System;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using static FishNet.Managing.Timing.EstimatedTick;
namespace FishNet.Connection
{
public static class NetworkConnectionExtensions
{
///
/// True if this connection is valid. An invalid connection indicates no client is set for this reference.
/// Null references can be used with this method.
///
public static bool IsValid(this NetworkConnection c)
{
if (c == null)
return false;
else
return c.IsValid;
}
}
///
/// A container for a connected client used to perform actions on and gather information for the declared client.
///
public partial class NetworkConnection : IResettable, IEquatable
{
#region Internal.
///
/// Tick when Disconnecting was set.
///
internal uint DisconnectingTick { get; private set; }
///
/// ObjectIds to use for predicted spawning.
///
internal Queue PredictedObjectIds = new();
///
/// True if the client has sent the same version that the server is on.
///
internal bool HasSentVersion;
///
/// LocalTick of the server when this connection was established. This value is not set for clients.
///
internal uint ServerConnectionTick;
#endregion
#region Public.
///
/// Called after this connection has loaded start scenes. Boolean will be true if asServer. Available to this connection and server.
///
public event Action OnLoadedStartScenes;
///
/// Called after connection gains ownership of an object, and after the object has been added to Objects. Available to this connection and server.
///
public event Action OnObjectAdded;
///
/// Called after connection loses ownership of an object, and after the object has been removed from Objects. Available to this connection and server.
///
public event Action OnObjectRemoved;
///
/// NetworkManager managing this class.
///
public NetworkManager NetworkManager { get; private set; }
///
/// True if connection has loaded start scenes. Available to this connection and server.
///
public bool LoadedStartScenes() => (_loadedStartScenesAsServer || _loadedStartScenesAsClient);
///
///
///
public bool LoadedStartScenes(bool asServer)
{
if (asServer)
return _loadedStartScenesAsServer;
else
return _loadedStartScenesAsClient;
}
///
/// TransportIndex this connection is on.
/// For security reasons this value will be unset on clients if this is not their connection.
/// This is not yet used.
///
public int TransportIndex { get; private set; } = -1;
///
/// True if this connection is authenticated. Only available to server.
///
public bool IsAuthenticated { get; private set; }
[Obsolete("Use IsAuthenticated.")] //Remove in V5
public bool Authenticated
{
get => IsAuthenticated;
set => IsAuthenticated = value;
}
///
/// True if this connection IsValid and not Disconnecting.
///
public bool IsActive => (ClientId >= 0 && !Disconnecting);
///
/// True if this connection is valid. An invalid connection indicates no client is set for this reference.
///
public bool IsValid => (ClientId >= 0);
///
/// Unique Id for this connection.
///
public int ClientId = -1;
///
/// Objects owned by this connection. Available to this connection and server.
///
public HashSet Objects = new();
///
/// The first object within Objects.
///
public NetworkObject FirstObject { get; private set; }
///
/// Sets a custom FirstObject. This connection must be owner of the specified object.
///
///
public void SetFirstObject(NetworkObject nob)
{
//Invalid object.
if (!Objects.Contains(nob))
{
string errMessage = $"FirstObject for {ClientId} cannot be set to {nob.name} as it's not within Objects for this connection.";
NetworkManager.LogError(errMessage);
return;
}
FirstObject = nob;
}
///
/// Scenes this connection is in. Available to this connection and server.
///
public HashSet Scenes { get; private set; } = new();
///
/// True if this connection is being disconnected. Only available to server.
///
public bool Disconnecting { get; private set; }
///
/// Custom data associated with this connection which may be modified by the user.
/// The value of this field are not synchronized over the network.
///
public object CustomData = null;
///
/// Tick of the last packet received from this connection which was not out of order.
/// This value is only available on the server.
///
public EstimatedTick PacketTick { get; private set; } = new();
///
/// Approximate local tick as it is on this connection.
/// This also contains the last set value for local and remote.
///
public EstimatedTick LocalTick { get; private set; } = new();
#endregion
#region Private.
///
/// True if loaded start scenes as server.
///
private bool _loadedStartScenesAsServer;
///
/// True if loaded start scenes as client.
///
private bool _loadedStartScenesAsClient;
#endregion
#region Const.
///
/// Value used when ClientId has not been set.
///
public const int UNSET_CLIENTID_VALUE = -1;
///
/// Maximum value a ClientId can be.
///
public const int MAXIMUM_CLIENTID_VALUE = int.MaxValue;
///
/// Maximum value a ClientId can be excluding simulated value.
///
public const int MAXIMUM_CLIENTID_WITHOUT_SIMULATED_VALUE = (int.MaxValue - 1);
///
/// Value to use as a ClientId when simulating a local client without actually using a socket.
///
public const int SIMULATED_CLIENTID_VALUE = int.MaxValue;
///
/// Number of bytes to reserve for a connectionId if writing the value uncompressed.
///
public const int CLIENTID_UNCOMPRESSED_RESERVE_LENGTH = 4;
#endregion
#region Comparers.
public override bool Equals(object obj)
{
if (obj is NetworkConnection nc)
return (nc.ClientId == this.ClientId);
else
return false;
}
public bool Equals(NetworkConnection nc)
{
if (nc is null)
return false;
//If either is -1 Id.
if (this.ClientId == NetworkConnection.UNSET_CLIENTID_VALUE || nc.ClientId == NetworkConnection.UNSET_CLIENTID_VALUE)
return false;
//Same object.
if (System.Object.ReferenceEquals(this, nc))
return true;
return (this.ClientId == nc.ClientId);
}
public override int GetHashCode()
{
return ClientId;
}
public static bool operator ==(NetworkConnection a, NetworkConnection b)
{
if (a is null && b is null)
return true;
if (a is null && !(b is null))
return false;
return (b == null) ? a.Equals(b) : b.Equals(a);
}
public static bool operator !=(NetworkConnection a, NetworkConnection b)
{
return !(a == b);
}
#endregion
[APIExclude]
public NetworkConnection() { }
[APIExclude]
public NetworkConnection(NetworkManager manager, int clientId, int transportIndex, bool asServer)
{
Initialize(manager, clientId, transportIndex, asServer);
}
///
/// Outputs data about this connection as a string.
///
///
public override string ToString()
{
int clientId = ClientId;
string ip = (NetworkManager != null) ? NetworkManager.TransportManager.Transport.GetConnectionAddress(clientId) : "Unset";
return $"Id [{ClientId}] Address [{ip}]";
}
///
/// Initializes this for use.
///
private void Initialize(NetworkManager nm, int clientId, int transportIndex, bool asServer)
{
NetworkManager = nm;
LocalTick.Initialize(nm.TimeManager);
PacketTick.Initialize(nm.TimeManager);
if (asServer)
ServerConnectionTick = nm.TimeManager.LocalTick;
TransportIndex = transportIndex;
ClientId = clientId;
/* Set PacketTick to current values so
* that timeouts and other things around
* first packet do not occur due to an unset value. */
PacketTick.Update(nm.TimeManager, 0, OldTickOption.SetLastRemoteTick);
Observers_Initialize(nm);
Prediction_Initialize(nm, asServer);
//Only the server uses the ping and buffer.
if (asServer)
{
InitializeBuffer();
InitializePing();
}
}
///
/// Sets Disconnecting boolean for this connection.
///
internal void SetDisconnecting(bool value)
{
Disconnecting = value;
if (Disconnecting)
DisconnectingTick = NetworkManager.TimeManager.LocalTick;
}
///
/// Disconnects this connection.
///
/// True to disconnect immediately. False to send any pending data first.
public void Disconnect(bool immediately)
{
if (!IsValid)
{
NetworkManager.LogWarning($"Disconnect called on an invalid connection.");
return;
}
if (Disconnecting)
{
NetworkManager.LogWarning($"ClientId {ClientId} is already disconnecting.");
return;
}
SetDisconnecting(true);
//If immediately then force disconnect through transport.
if (immediately)
NetworkManager.TransportManager.Transport.StopConnection(ClientId, true);
//Otherwise mark dirty so server will push out any pending information, and then disconnect.
else
ServerDirty();
}
///
/// Returns if just loaded start scenes and sets them as loaded if not.
///
///
internal bool SetLoadedStartScenes(bool asServer)
{
bool loadedToCheck = (asServer) ? _loadedStartScenesAsServer : _loadedStartScenesAsClient;
//Result becomes true if not yet loaded start scenes.
bool result = !loadedToCheck;
if (asServer)
_loadedStartScenesAsServer = true;
else
_loadedStartScenesAsClient = true;
OnLoadedStartScenes?.Invoke(this, asServer);
return result;
}
///
/// Sets connection as authenticated.
///
internal void ConnectionAuthenticated()
{
IsAuthenticated = true;
}
///
/// Adds to Objects owned by this connection.
///
///
internal void AddObject(NetworkObject nob)
{
if (!IsValid)
return;
Objects.Add(nob);
//If adding the first object then set new FirstObject.
if (Objects.Count == 1)
SetFirstObject();
OnObjectAdded?.Invoke(nob);
}
///
/// Removes from Objects owned by this connection.
///
///
internal void RemoveObject(NetworkObject nob)
{
if (!IsValid)
{
ClearObjects();
return;
}
Objects.Remove(nob);
//If removing the first object then set a new one.
if (nob == FirstObject)
SetFirstObject();
OnObjectRemoved?.Invoke(nob);
}
///
/// Clears all Objects.
///
private void ClearObjects()
{
Objects.Clear();
FirstObject = null;
}
///
/// Sets FirstObject using the first element in Objects.
///
private void SetFirstObject()
{
if (Objects.Count == 0)
{
FirstObject = null;
}
else
{
foreach (NetworkObject nob in Objects)
{
FirstObject = nob;
Observers_FirstObjectChanged();
break;
}
}
}
///
/// Adds a scene to this connections Scenes.
///
///
///
internal bool AddToScene(Scene scene)
{
return Scenes.Add(scene);
}
///
/// Removes a scene to this connections Scenes.
///
///
///
internal bool RemoveFromScene(Scene scene)
{
return Scenes.Remove(scene);
}
///
/// Resets all states for re-use.
///
public void ResetState()
{
MatchCondition.RemoveFromMatchesWithoutRebuild(this, NetworkManager);
foreach (PacketBundle p in _toClientBundles)
p.Dispose();
_toClientBundles.Clear();
ServerConnectionTick = 0;
PacketTick.Reset();
LocalTick.Reset();
TransportIndex = -1;
ClientId = -1;
ClearObjects();
IsAuthenticated = false;
HasSentVersion = false;
NetworkManager = null;
_loadedStartScenesAsClient = false;
_loadedStartScenesAsServer = false;
SetDisconnecting(false);
Scenes.Clear();
PredictedObjectIds.Clear();
ResetPingPong();
Observers_Reset();
Prediction_Reset();
}
public void InitializeState() { }
}
}