[Add] FishNet
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.CodeGenerating;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Utility.Performance;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishNet.Managing.Server
|
||||
{
|
||||
public struct ClientConnectionChangeBroadcast : IBroadcast
|
||||
{
|
||||
public bool Connected;
|
||||
public int Id;
|
||||
}
|
||||
|
||||
[UseGlobalCustomSerializer]
|
||||
public struct ConnectedClientsBroadcast : IBroadcast
|
||||
{
|
||||
public List<int> Values;
|
||||
}
|
||||
|
||||
internal static class ConnectedClientsBroadcastSerializers
|
||||
{
|
||||
public static void WriteConnectedClientsBroadcast(this Writer writer, ConnectedClientsBroadcast value)
|
||||
{
|
||||
writer.WriteList(value.Values);
|
||||
}
|
||||
|
||||
public static ConnectedClientsBroadcast ReadConnectedClientsBroadcast(this Reader reader)
|
||||
{
|
||||
List<int> cache = CollectionCaches<int>.RetrieveList();
|
||||
reader.ReadList(ref cache);
|
||||
return new()
|
||||
{
|
||||
Values = cache
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a16b83a545be8f8488795783a0fc8648
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Server/ClientConnectionBroadcast.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c150c74713eeaeb47a387a0f34529ad4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,92 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Server.Editing
|
||||
{
|
||||
[CustomEditor(typeof(ServerManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class ServerManagerEditor : Editor
|
||||
{
|
||||
private SerializedProperty _authenticator;
|
||||
private SerializedProperty _remoteClientTimeout;
|
||||
private SerializedProperty _remoteClientTimeoutDuration;
|
||||
private SerializedProperty _syncTypeRate;
|
||||
private SerializedProperty SpawnPacking;
|
||||
private SerializedProperty _changeFrameRate;
|
||||
private SerializedProperty _frameRate;
|
||||
private SerializedProperty _shareIds;
|
||||
private SerializedProperty _startOnHeadless;
|
||||
private SerializedProperty _allowPredictedSpawning;
|
||||
private SerializedProperty _reservedObjectIds;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_authenticator = serializedObject.FindProperty(nameof(_authenticator));
|
||||
_remoteClientTimeout = serializedObject.FindProperty(nameof(_remoteClientTimeout));
|
||||
_remoteClientTimeoutDuration = serializedObject.FindProperty(nameof(_remoteClientTimeoutDuration));
|
||||
_syncTypeRate = serializedObject.FindProperty(nameof(_syncTypeRate));
|
||||
SpawnPacking = serializedObject.FindProperty(nameof(SpawnPacking));
|
||||
_changeFrameRate = serializedObject.FindProperty(nameof(_changeFrameRate));
|
||||
_frameRate = serializedObject.FindProperty(nameof(_frameRate));
|
||||
_shareIds = serializedObject.FindProperty(nameof(_shareIds));
|
||||
_startOnHeadless = serializedObject.FindProperty(nameof(_startOnHeadless));
|
||||
_allowPredictedSpawning = serializedObject.FindProperty(nameof(_allowPredictedSpawning));
|
||||
_reservedObjectIds = serializedObject.FindProperty(nameof(_reservedObjectIds));
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((ServerManager)target), typeof(ServerManager), false);
|
||||
GUI.enabled = true;
|
||||
|
||||
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_syncTypeRate);
|
||||
EditorGUILayout.PropertyField(SpawnPacking);
|
||||
EditorGUILayout.PropertyField(_changeFrameRate);
|
||||
if (_changeFrameRate.boolValue)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_frameRate);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.PropertyField(_startOnHeadless);
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField("Connections", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_remoteClientTimeout);
|
||||
if ((RemoteTimeoutType)_remoteClientTimeout.intValue != RemoteTimeoutType.Disabled)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_remoteClientTimeoutDuration, new GUIContent("Timeout"));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.PropertyField(_shareIds);
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField("Security", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_authenticator);
|
||||
|
||||
EditorGUILayout.PropertyField(_allowPredictedSpawning);
|
||||
if (_allowPredictedSpawning.boolValue == true)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_reservedObjectIds);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUILayout.Space();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da6ea97e6b868974e8ac139fe545e986
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Server/Editor/ServerManagerEditor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace FishNet.Managing.Server
|
||||
{
|
||||
public enum KickReason : short
|
||||
{
|
||||
/// <summary>
|
||||
/// No reason was specified.
|
||||
/// </summary>
|
||||
Unset = 0,
|
||||
/// <summary>
|
||||
/// Client performed an action which could only be done if trying to exploit the server.
|
||||
/// </summary>
|
||||
ExploitAttempt = 1,
|
||||
/// <summary>
|
||||
/// Data received from the client could not be parsed. This rarely indicates an attack.
|
||||
/// </summary>
|
||||
MalformedData = 2,
|
||||
/// <summary>
|
||||
/// Client sent more data than should be able to.
|
||||
/// </summary>
|
||||
ExploitExcessiveData = 3,
|
||||
/// <summary>
|
||||
/// Client has sent a large amount of data several times in a row. This may not be an attack but there is no way to know with certainty.
|
||||
/// </summary>
|
||||
ExcessiveData = 4,
|
||||
/// <summary>
|
||||
/// A problem occurred with the server where the only option was to kick the client. This rarely indicates an exploit attempt.
|
||||
/// </summary>
|
||||
UnexpectedProblem = 5,
|
||||
/// <summary>
|
||||
/// Client is behaving unusually, such as providing multiple invalid states. This may not be an attack but there is no way to know with certainty.
|
||||
/// </summary>
|
||||
UnusualActivity = 6
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9289a823878c5d1408e7106e6ed5d866
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Server/KickReasons.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 624750768480fa840b40449a42006488
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,486 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Object;
|
||||
using FishNet.Observing;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Server
|
||||
{
|
||||
public partial class ServerObjects : ManagedObjects
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Cache filled with objects which observers are being updated.
|
||||
/// This is primarily used to invoke events after all observers are updated, rather than as each is updated.
|
||||
/// </summary>
|
||||
private List<NetworkObject> _observerChangedObjectsCache = new(100);
|
||||
/// <summary>
|
||||
/// NetworkObservers which require regularly iteration.
|
||||
/// </summary>
|
||||
private List<NetworkObject> _timedNetworkObservers = new();
|
||||
/// <summary>
|
||||
/// Index in TimedNetworkObservers to start on next cycle.
|
||||
/// </summary>
|
||||
private int _nextTimedObserversIndex;
|
||||
/// <summary>
|
||||
/// Used to write spawns for everyone. This writer will exclude owner only information.
|
||||
/// </summary>
|
||||
private PooledWriter _writer = new();
|
||||
/// <summary>
|
||||
/// Indexes within TimedNetworkObservers which are unset.
|
||||
/// </summary>
|
||||
private Queue<int> _emptiedTimedIndexes = new();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Called when MonoBehaviours call Update.
|
||||
/// </summary>
|
||||
private void Observers_OnUpdate()
|
||||
{
|
||||
UpdateTimedObservers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Progressively updates NetworkObservers with timed conditions.
|
||||
/// </summary>
|
||||
private void UpdateTimedObservers()
|
||||
{
|
||||
if (!NetworkManager.IsServerStarted)
|
||||
return;
|
||||
// No point in updating if the timemanager isn't going to tick this frame.
|
||||
if (!NetworkManager.TimeManager.FrameTicked)
|
||||
return;
|
||||
int networkObserversCount = _timedNetworkObservers.Count;
|
||||
if (networkObserversCount == 0)
|
||||
return;
|
||||
|
||||
/* Try to iterate all timed observers every half a second.
|
||||
* This value will increase as there's more observers or timed conditions. */
|
||||
float timeMultiplier = 1f + (NetworkManager.ServerManager.Clients.Count * 0.005f + _timedNetworkObservers.Count * 0.0005f);
|
||||
// Check cap this way for readability.
|
||||
float completionTime = Mathf.Min(0.5f * timeMultiplier, NetworkManager.ObserverManager.MaximumTimedObserversDuration);
|
||||
uint completionTicks = NetworkManager.TimeManager.TimeToTicks(completionTime, TickRounding.RoundUp);
|
||||
/* Iterations will be the number of objects
|
||||
* to iterate to be have completed all objects by
|
||||
* the end of completionTicks. */
|
||||
int iterations = Mathf.CeilToInt((float)networkObserversCount / completionTicks);
|
||||
if (iterations > _timedNetworkObservers.Count)
|
||||
iterations = _timedNetworkObservers.Count;
|
||||
|
||||
List<NetworkConnection> connCache = RetrieveAuthenticatedConnections();
|
||||
// Build nob cache.
|
||||
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
if (_nextTimedObserversIndex >= _timedNetworkObservers.Count)
|
||||
_nextTimedObserversIndex = 0;
|
||||
|
||||
NetworkObject nob = _timedNetworkObservers[_nextTimedObserversIndex++];
|
||||
if (nob != null)
|
||||
nobCache.Add(nob);
|
||||
}
|
||||
|
||||
RebuildObservers(nobCache, connCache, true);
|
||||
|
||||
CollectionCaches<NetworkConnection>.Store(connCache);
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a networkObserver component should be updated regularly. This is done automatically.
|
||||
/// </summary>
|
||||
/// <param name = "networkObject">NetworkObject to be updated.</param>
|
||||
public void AddTimedNetworkObserver(NetworkObject networkObject)
|
||||
{
|
||||
if (_emptiedTimedIndexes.TryDequeue(out int index))
|
||||
_timedNetworkObservers[index] = networkObject;
|
||||
else
|
||||
_timedNetworkObservers.Add(networkObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a networkObserver component no longer needs to be updated regularly. This is done automatically.
|
||||
/// </summary>
|
||||
/// <param name = "networkObject">NetworkObject to be updated.</param>
|
||||
public void RemoveTimedNetworkObserver(NetworkObject networkObject)
|
||||
{
|
||||
int index = _timedNetworkObservers.IndexOf(networkObject);
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
_emptiedTimedIndexes.Enqueue(index);
|
||||
_timedNetworkObservers[index] = null;
|
||||
|
||||
// If there's a decent amount missing then rebuild the collection.
|
||||
if (_emptiedTimedIndexes.Count > 20)
|
||||
{
|
||||
List<NetworkObject> newLst = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
foreach (NetworkObject nob in _timedNetworkObservers)
|
||||
{
|
||||
if (nob == null)
|
||||
continue;
|
||||
|
||||
newLst.Add(nob);
|
||||
}
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(_timedNetworkObservers);
|
||||
_timedNetworkObservers = newLst;
|
||||
_emptiedTimedIndexes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all NetworkConnections which are authenticated.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private List<NetworkConnection> RetrieveAuthenticatedConnections()
|
||||
{
|
||||
List<NetworkConnection> cache = CollectionCaches<NetworkConnection>.RetrieveList();
|
||||
foreach (NetworkConnection item in NetworkManager.ServerManager.Clients.Values)
|
||||
{
|
||||
if (item.IsAuthenticated)
|
||||
cache.Add(item);
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all spawned objects with root objects first.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private List<NetworkObject> RetrieveOrderedSpawnedObjects()
|
||||
{
|
||||
List<NetworkObject> spawnedCache = GetSpawnedNetworkObjects();
|
||||
|
||||
List<NetworkObject> sortedCache = SortRootAndNestedByInitializeOrder(spawnedCache);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(spawnedCache);
|
||||
|
||||
return sortedCache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns spawned NetworkObjects as a list.
|
||||
/// Collection returned is a new cache and should be disposed of properly.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private List<NetworkObject> GetSpawnedNetworkObjects()
|
||||
{
|
||||
List<NetworkObject> cache = Spawned.ValuesToList(useCache: true);
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts a collection of NetworkObjects root and nested by initialize order.
|
||||
/// Collection returned is a new cache and should be disposed of properly.
|
||||
/// </summary>
|
||||
internal List<NetworkObject> SortRootAndNestedByInitializeOrder(List<NetworkObject> nobs)
|
||||
{
|
||||
List<NetworkObject> sortedRootCache = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
|
||||
// First order root objects.
|
||||
foreach (NetworkObject item in nobs)
|
||||
{
|
||||
if (item.IsNested)
|
||||
continue;
|
||||
|
||||
sortedRootCache.AddOrdered(item);
|
||||
}
|
||||
|
||||
/* After all root are ordered check
|
||||
* their nested. Order nested in segments
|
||||
* of each root then insert after the root.
|
||||
* This must be performed after all roots are ordered. */
|
||||
|
||||
// This holds the results of all values.
|
||||
List<NetworkObject> sortedRootAndNestedCache = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
|
||||
// Cache for sorting nested.
|
||||
List<NetworkObject> sortedNestedCache = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
|
||||
foreach (NetworkObject item in sortedRootCache)
|
||||
{
|
||||
/* Remove recursive and only check Initialized and Runtime. Once iterated
|
||||
* check each added entry again using Initialized and Recursive. */
|
||||
List<NetworkObject> nested = item.GetNetworkObjects(GetNetworkObjectOption.AllNestedRecursive);
|
||||
|
||||
foreach (NetworkObject nestedItem in nested)
|
||||
{
|
||||
/* If entry has a runtime nested that differs from initialized nested
|
||||
* then it k*/
|
||||
if (sortedNestedCache.Contains(nestedItem))
|
||||
Debug.LogError($"Nested cache already contains item [{nestedItem.name}]. Source [{item.name}]. Please report this error.");
|
||||
else
|
||||
sortedNestedCache.AddOrdered(nestedItem);
|
||||
}
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nested);
|
||||
|
||||
/* Once all nested are sorted then can be added to the
|
||||
* sorted root and nested cache. */
|
||||
sortedRootAndNestedCache.Add(item);
|
||||
sortedRootAndNestedCache.AddRange(sortedNestedCache);
|
||||
|
||||
// Reset cache.
|
||||
sortedNestedCache.Clear();
|
||||
}
|
||||
|
||||
// Store temp caches.
|
||||
CollectionCaches<NetworkObject>.Store(sortedRootCache);
|
||||
CollectionCaches<NetworkObject>.Store(sortedNestedCache);
|
||||
|
||||
return sortedRootAndNestedCache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a connection from observers without synchronizing changes.
|
||||
/// </summary>
|
||||
/// <param name = "connection"></param>
|
||||
private void RemoveFromObserversWithoutSynchronization(NetworkConnection connection)
|
||||
{
|
||||
List<NetworkObject> observerChangedObjectsCache = _observerChangedObjectsCache;
|
||||
foreach (NetworkObject nob in Spawned.Values)
|
||||
{
|
||||
if (nob.RemoveObserver(connection))
|
||||
observerChangedObjectsCache.Add(nob);
|
||||
}
|
||||
|
||||
// Invoke despawn callbacks on nobs.
|
||||
for (int i = 0; i < observerChangedObjectsCache.Count; i++)
|
||||
observerChangedObjectsCache[i].InvokeOnServerDespawn(connection);
|
||||
observerChangedObjectsCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all NetworkObjects for all connections.
|
||||
/// </summary>
|
||||
public void RebuildObservers(bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = RetrieveOrderedSpawnedObjects();
|
||||
List<NetworkConnection> connCache = RetrieveAuthenticatedConnections();
|
||||
|
||||
RebuildObservers(nobCache, connCache, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
CollectionCaches<NetworkConnection>.Store(connCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for all connections for a NetworkObject.
|
||||
/// </summary>
|
||||
public void RebuildObservers(NetworkObject nob, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList(nob);
|
||||
List<NetworkConnection> connCache = RetrieveAuthenticatedConnections();
|
||||
|
||||
RebuildObservers(nobCache, connCache, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
CollectionCaches<NetworkConnection>.Store(connCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all NetworkObjects for a connection.
|
||||
/// </summary>
|
||||
public void RebuildObservers(NetworkConnection connection, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = RetrieveOrderedSpawnedObjects();
|
||||
List<NetworkConnection> connCache = CollectionCaches<NetworkConnection>.RetrieveList(connection);
|
||||
|
||||
RebuildObservers(nobCache, connCache, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
CollectionCaches<NetworkConnection>.Store(connCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects.
|
||||
/// </summary>
|
||||
public void RebuildObservers(IList<NetworkObject> nobs, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkConnection> conns = RetrieveAuthenticatedConnections();
|
||||
|
||||
RebuildObservers(nobs, conns, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkConnection>.Store(conns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all objects for connections.
|
||||
/// </summary>
|
||||
public void RebuildObservers(IList<NetworkConnection> connections, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = RetrieveOrderedSpawnedObjects();
|
||||
|
||||
RebuildObservers(nobCache, connections, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects for connections.
|
||||
/// </summary>
|
||||
public void RebuildObservers(IList<NetworkObject> nobs, NetworkConnection conn, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkConnection> connCache = CollectionCaches<NetworkConnection>.RetrieveList(conn);
|
||||
|
||||
RebuildObservers(nobs, connCache, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkConnection>.Store(connCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for connections on NetworkObject.
|
||||
/// </summary>
|
||||
public void RebuildObservers(NetworkObject networkObject, IList<NetworkConnection> connections, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList(networkObject);
|
||||
|
||||
RebuildObservers(nobCache, connections, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects for connections.
|
||||
/// </summary>
|
||||
public void RebuildObservers(IList<NetworkObject> nobs, IList<NetworkConnection> conns, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
NetworkConnection nc;
|
||||
|
||||
int connsCount = conns.Count;
|
||||
for (int i = 0; i < connsCount; i++)
|
||||
{
|
||||
nobCache.Clear();
|
||||
|
||||
nc = conns[i];
|
||||
int nobsCount = nobs.Count;
|
||||
for (int z = 0; z < nobsCount; z++)
|
||||
RebuildObservers(nobs[z], nc, nobCache, timedOnly);
|
||||
|
||||
// Send if change.
|
||||
if (_writer.Length > 0)
|
||||
{
|
||||
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, _writer.GetArraySegment(), nc);
|
||||
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (NetworkTrafficStatistics != null)
|
||||
NetworkTrafficStatistics.AddOutboundPacketIdData(PacketId.BulkSpawnOrDespawn, string.Empty, _writer.Length, gameObject: null, asServer: true);
|
||||
#endif
|
||||
|
||||
_writer.Clear();
|
||||
|
||||
foreach (NetworkObject n in nobCache)
|
||||
n.OnSpawnServer(nc);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for a connection on NetworkObject.
|
||||
/// </summary>
|
||||
public void RebuildObservers(NetworkObject nob, NetworkConnection conn, bool timedOnly = false)
|
||||
{
|
||||
if (ApplicationState.IsQuitting())
|
||||
return;
|
||||
_writer.Clear();
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
PacketId trafficPacketId;
|
||||
#endif
|
||||
conn.UpdateHashGridPositions(!timedOnly);
|
||||
// If observer state changed then write changes.
|
||||
ObserverStateChange osc = nob.RebuildObservers(conn, timedOnly);
|
||||
if (osc == ObserverStateChange.Added)
|
||||
{
|
||||
WriteSpawn(nob, _writer, conn);
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
trafficPacketId = PacketId.ObjectSpawn;
|
||||
#endif
|
||||
}
|
||||
else if (osc == ObserverStateChange.Removed)
|
||||
{
|
||||
nob.InvokeOnServerDespawn(conn);
|
||||
WriteDespawn(nob, nob.GetDefaultDespawnType(), _writer);
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
trafficPacketId = PacketId.ObjectDespawn;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (NetworkTrafficStatistics != null)
|
||||
NetworkTrafficStatistics.AddOutboundPacketIdData(trafficPacketId, string.Empty, _writer.Length, gameObject: null, asServer: true);
|
||||
#endif
|
||||
|
||||
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, _writer.GetArraySegment(), conn);
|
||||
|
||||
/* If spawning then also invoke server
|
||||
* start events, such as buffer last
|
||||
* and onspawnserver. */
|
||||
if (osc == ObserverStateChange.Added)
|
||||
nob.OnSpawnServer(conn);
|
||||
|
||||
_writer.Clear();
|
||||
|
||||
/* If there is change then also rebuild recursive networkObjects. */
|
||||
foreach (NetworkBehaviour item in nob.RuntimeChildNetworkBehaviours)
|
||||
RebuildObservers(item.NetworkObject, conn, timedOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for a connection on NetworkObject.
|
||||
/// </summary>
|
||||
internal void RebuildObservers(NetworkObject nob, NetworkConnection conn, List<NetworkObject> addedNobs, bool timedOnly = false)
|
||||
{
|
||||
if (ApplicationState.IsQuitting())
|
||||
return;
|
||||
|
||||
/* When not using a timed rebuild such as this connections must have
|
||||
* hashgrid data rebuilt immediately. */
|
||||
conn.UpdateHashGridPositions(!timedOnly);
|
||||
|
||||
// If observer state changed then write changes.
|
||||
ObserverStateChange osc = nob.RebuildObservers(conn, timedOnly);
|
||||
if (osc == ObserverStateChange.Added)
|
||||
{
|
||||
WriteSpawn(nob, _writer, conn);
|
||||
addedNobs.Add(nob);
|
||||
}
|
||||
else if (osc == ObserverStateChange.Removed)
|
||||
{
|
||||
nob.InvokeOnServerDespawn(conn);
|
||||
WriteDespawn(nob, nob.GetDefaultDespawnType(), _writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* If there is change then also rebuild on any runtime children.
|
||||
* This is to ensure runtime children have visibility updated
|
||||
* in relation to parent.
|
||||
*
|
||||
* If here there is change. */
|
||||
foreach (NetworkBehaviour item in nob.RuntimeChildNetworkBehaviours)
|
||||
RebuildObservers(item.NetworkObject, conn, addedNobs, timedOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02d93fa4a653dd64da0bb338b82f4740
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.Observers.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,39 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Managing.Utility;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FishNet.Managing.Server
|
||||
{
|
||||
public partial class ServerObjects : ManagedObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses a ServerRpc.
|
||||
/// </summary>
|
||||
internal void ParseServerRpc(PooledReader reader, NetworkConnection conn, Channel channel)
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, out string rpcInformation, out uint expectedReadAmount);
|
||||
#endif
|
||||
int readerStartAfterDebug = reader.Position;
|
||||
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.ServerRpc, reader, channel);
|
||||
|
||||
if (nb != null)
|
||||
nb.ReadServerRpc(readerStartAfterDebug, fromRpcLink: false, hash: 0, reader, conn, channel);
|
||||
else
|
||||
SkipDataLength((ushort)PacketId.ServerRpc, reader, dataLength);
|
||||
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader, startReaderRemaining, rpcInformation, expectedReadAmount, channel);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cb6cef520a4ff44bb8c4814e566c5ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.Parsing.cs
|
||||
uploadId: 866910
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5e7f3005cbc7924f99819311c58651a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,400 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Broadcast.Helping;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Utility;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Transporting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FishNet.Managing.Transporting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Server
|
||||
{
|
||||
public sealed partial class ServerManager : MonoBehaviour
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Handler for registered broadcasts.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ushort, BroadcastHandlerBase> _broadcastHandlers = new();
|
||||
/// <summary>
|
||||
/// Connections which can be broadcasted to after having excluded removed.
|
||||
/// </summary>
|
||||
private HashSet<NetworkConnection> _connectionsWithoutExclusionsCache = new();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Registers a method to call when a Broadcast arrives.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast being registered.</typeparam>
|
||||
/// <param name = "handler">Method to call.</param>
|
||||
/// <param name = "requireAuthentication">True if the client must be authenticated for the method to call.</param>
|
||||
public void RegisterBroadcast<T>(Action<NetworkConnection, T, Channel> handler, bool requireAuthentication = true) where T : struct, IBroadcast
|
||||
{
|
||||
if (handler == null)
|
||||
{
|
||||
NetworkManager.LogError($"Broadcast cannot be registered because handler is null. This may occur when trying to register to objects which require initialization, such as events.");
|
||||
return;
|
||||
}
|
||||
|
||||
ushort key = BroadcastExtensions.GetKey<T>();
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
NetworkManager.SetBroadcastName<T>(key);
|
||||
#endif
|
||||
|
||||
// Create new IBroadcastHandler if needed.
|
||||
BroadcastHandlerBase bhs;
|
||||
if (!_broadcastHandlers.TryGetValueIL2CPP(key, out bhs))
|
||||
{
|
||||
bhs = new ClientBroadcastHandler<T>(requireAuthentication);
|
||||
_broadcastHandlers.Add(key, bhs);
|
||||
}
|
||||
// Register handler to IBroadcastHandler.
|
||||
bhs.RegisterHandler(handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a method call from a Broadcast type.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast being unregistered.</typeparam>
|
||||
/// <param name = "handler">Method to unregister.</param>
|
||||
public void UnregisterBroadcast<T>(Action<NetworkConnection, T, Channel> handler) where T : struct, IBroadcast
|
||||
{
|
||||
ushort key = BroadcastExtensions.GetKey<T>();
|
||||
if (_broadcastHandlers.TryGetValueIL2CPP(key, out BroadcastHandlerBase bhs))
|
||||
bhs.UnregisterHandler(handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a received broadcast.
|
||||
/// </summary>
|
||||
private void ParseBroadcast(PooledReader reader, NetworkConnection conn, Channel channel)
|
||||
{
|
||||
int readerPositionAfterDebug = reader.Position;
|
||||
|
||||
ushort key = reader.ReadUInt16();
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.Broadcast, reader, channel);
|
||||
|
||||
// try to invoke the handler for that message
|
||||
if (_broadcastHandlers.TryGetValueIL2CPP(key, out BroadcastHandlerBase bhs))
|
||||
{
|
||||
if (bhs.RequireAuthentication && !conn.IsAuthenticated)
|
||||
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a broadcast which requires authentication, but client was not authenticated. Client has been disconnected.");
|
||||
else
|
||||
bhs.InvokeHandlers(conn, reader, channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Skip(dataLength);
|
||||
}
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
{
|
||||
string broadcastName = NetworkManager.GetBroadcastName(key);
|
||||
_networkTrafficStatistics.AddInboundPacketIdData(PacketId.Broadcast, broadcastName, reader.Position - readerPositionAfterDebug + TransportManager.PACKETID_LENGTH,gameObject: null, asServer: true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to a connection.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "connection">Connection to send to.</param>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the client must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
public void Broadcast<T>(NetworkConnection connection, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (!Started)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
|
||||
return;
|
||||
}
|
||||
if (requireAuthenticated && !connection.IsAuthenticated)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot send broadcast to client because they are not authenticated.");
|
||||
return;
|
||||
}
|
||||
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
BroadcastsSerializers.WriteBroadcast(NetworkManager, writer, message, ref channel);
|
||||
ArraySegment<byte> segment = writer.GetArraySegment();
|
||||
|
||||
AddOutboundNetworkTraffic<T>(segment.Count);
|
||||
|
||||
NetworkManager.TransportManager.SendToClient((byte)channel, segment, connection);
|
||||
writer.Store();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to connections.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "connections">Connections to send to.</param>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
public void Broadcast<T>(HashSet<NetworkConnection> connections, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (!Started)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool failedAuthentication = false;
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
BroadcastsSerializers.WriteBroadcast(NetworkManager, writer, message, ref channel);
|
||||
ArraySegment<byte> segment = writer.GetArraySegment();
|
||||
|
||||
int sentBytes = 0;
|
||||
int segmentCount = segment.Count;
|
||||
|
||||
foreach (NetworkConnection conn in connections)
|
||||
{
|
||||
if (requireAuthenticated && !conn.IsAuthenticated)
|
||||
{
|
||||
failedAuthentication = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.TransportManager.SendToClient((byte)channel, segment, conn);
|
||||
sentBytes += segmentCount;
|
||||
}
|
||||
}
|
||||
|
||||
writer.Store();
|
||||
|
||||
AddOutboundNetworkTraffic<T>(sentBytes);
|
||||
|
||||
if (failedAuthentication)
|
||||
NetworkManager.LogWarning($"One or more broadcast did not send to a client because they were not authenticated.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to connections except excluded.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "connections">Connections to send to.</param>
|
||||
/// <param name = "excludedConnection">Connection to exclude.</param>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
public void BroadcastExcept<T>(HashSet<NetworkConnection> connections, NetworkConnection excludedConnection, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (!Started)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fast exit if no exclusions.
|
||||
if (excludedConnection == null || !excludedConnection.IsValid)
|
||||
{
|
||||
Broadcast(connections, message, requireAuthenticated, channel);
|
||||
return;
|
||||
}
|
||||
|
||||
connections.Remove(excludedConnection);
|
||||
Broadcast(connections, message, requireAuthenticated, channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to connections except excluded.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "connections">Connections to send to.</param>
|
||||
/// <param name = "excludedConnections">Connections to exclude.</param>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
public void BroadcastExcept<T>(HashSet<NetworkConnection> connections, HashSet<NetworkConnection> excludedConnections, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (!Started)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fast exit if no exclusions.
|
||||
if (excludedConnections == null || excludedConnections.Count == 0)
|
||||
{
|
||||
Broadcast(connections, message, requireAuthenticated, channel);
|
||||
return;
|
||||
}
|
||||
|
||||
/* I'm not sure if the hashset API such as intersect generates
|
||||
* GC or not but I'm betting doing remove locally is faster, or
|
||||
* just as fast. */
|
||||
foreach (NetworkConnection ec in excludedConnections)
|
||||
connections.Remove(ec);
|
||||
|
||||
Broadcast(connections, message, requireAuthenticated, channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to all connections except excluded.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "excludedConnection">Connection to exclude.</param>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
public void BroadcastExcept<T>(NetworkConnection excludedConnection, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (!Started)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fast exit if there are no excluded.
|
||||
if (excludedConnection == null || !excludedConnection.IsValid)
|
||||
{
|
||||
Broadcast(message, requireAuthenticated, channel);
|
||||
return;
|
||||
}
|
||||
|
||||
_connectionsWithoutExclusionsCache.Clear();
|
||||
/* It will be faster to fill the entire list then
|
||||
* remove vs checking if each connection is contained within excluded. */
|
||||
foreach (NetworkConnection c in Clients.Values)
|
||||
_connectionsWithoutExclusionsCache.Add(c);
|
||||
//Remove
|
||||
_connectionsWithoutExclusionsCache.Remove(excludedConnection);
|
||||
|
||||
Broadcast(_connectionsWithoutExclusionsCache, message, requireAuthenticated, channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to all connections except excluded.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "excludedConnections">Connections to send to.</param>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
public void BroadcastExcept<T>(HashSet<NetworkConnection> excludedConnections, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (!Started)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
//Fast exit if there are no excluded.
|
||||
if (excludedConnections == null || excludedConnections.Count == 0)
|
||||
{
|
||||
Broadcast(message, requireAuthenticated, channel);
|
||||
return;
|
||||
}
|
||||
|
||||
_connectionsWithoutExclusionsCache.Clear();
|
||||
/* It will be faster to fill the entire list then
|
||||
* remove vs checking if each connection is contained within excluded. */
|
||||
foreach (NetworkConnection c in Clients.Values)
|
||||
_connectionsWithoutExclusionsCache.Add(c);
|
||||
//Remove
|
||||
foreach (NetworkConnection c in excludedConnections)
|
||||
_connectionsWithoutExclusionsCache.Remove(c);
|
||||
|
||||
Broadcast(_connectionsWithoutExclusionsCache, message, requireAuthenticated, channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to observers.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "networkObject">NetworkObject to use Observers from.</param>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
public void Broadcast<T>(NetworkObject networkObject, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (networkObject == null)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot send broadcast because networkObject is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
Broadcast(networkObject.Observers, message, requireAuthenticated, channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to all clients.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of broadcast to send.</typeparam>
|
||||
/// <param name = "message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name = "requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
|
||||
/// <param name = "channel">Channel to send on.</param>
|
||||
public void Broadcast<T>(T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (!Started)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool failedAuthentication = false;
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
BroadcastsSerializers.WriteBroadcast(NetworkManager, writer, message, ref channel);
|
||||
ArraySegment<byte> segment = writer.GetArraySegment();
|
||||
|
||||
int sentBytes = 0;
|
||||
int segmentCount = segment.Count;
|
||||
|
||||
foreach (NetworkConnection conn in Clients.Values)
|
||||
{
|
||||
//
|
||||
if (requireAuthenticated && !conn.IsAuthenticated)
|
||||
{
|
||||
failedAuthentication = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.TransportManager.SendToClient((byte)channel, segment, conn);
|
||||
sentBytes += segmentCount;
|
||||
}
|
||||
}
|
||||
|
||||
AddOutboundNetworkTraffic<T>(sentBytes);
|
||||
|
||||
writer.Store();
|
||||
|
||||
if (failedAuthentication)
|
||||
NetworkManager.LogWarning($"One or more broadcast did not send to a client because they were not authenticated.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds data for an outbound broadcast.
|
||||
/// </summary>
|
||||
private void AddOutboundNetworkTraffic<T>(int bytes) where T : struct, IBroadcast
|
||||
{
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
{
|
||||
if (bytes <= 0)
|
||||
return;
|
||||
|
||||
ushort key = BroadcastExtensions.GetKey<T>();
|
||||
string broadcastName = NetworkManager.GetBroadcastName(key);
|
||||
|
||||
/* Do not include packetId length -- its written in the 'WriteBroadcast' method. */
|
||||
_networkTrafficStatistics.AddOutboundPacketIdData(PacketId.Broadcast, broadcastName, bytes, gameObject: null, asServer: true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cd9dcd58556e27449ce5cb0d70611cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Server/ServerManager.Broadcast.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,223 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Transporting.Multipass;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Server
|
||||
{
|
||||
public sealed partial class ServerManager : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Called when a client is removed from the server using Kick. This is invoked before the client is disconnected.
|
||||
/// NetworkConnection when available, clientId, and KickReason are provided.
|
||||
/// </summary>
|
||||
public event Action<NetworkConnection, int, KickReason> OnClientKick;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Stores a cache and returns a boolean result.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool StoreTransportCacheAndReturn(List<Transport> cache, bool returnedValue)
|
||||
{
|
||||
CollectionCaches<Transport>.Store(cache);
|
||||
return returnedValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if all server sockets have a local connection state of stopped.
|
||||
/// </summary>
|
||||
public bool AreAllServersStopped()
|
||||
{
|
||||
List<Transport> transports = NetworkManager.TransportManager.GetAllTransports(includeMultipass: false);
|
||||
|
||||
foreach (Transport t in transports)
|
||||
{
|
||||
if (t.GetConnectionState(server: true) != LocalConnectionState.Stopped)
|
||||
return StoreTransportCacheAndReturn(transports, returnedValue: false);
|
||||
}
|
||||
|
||||
return StoreTransportCacheAndReturn(transports, returnedValue: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if only one server is started.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsOnlyOneServerStarted()
|
||||
{
|
||||
List<Transport> transports = NetworkManager.TransportManager.GetAllTransports(includeMultipass: false);
|
||||
|
||||
int startedCount = 0;
|
||||
|
||||
foreach (Transport t in transports)
|
||||
{
|
||||
if (t.GetConnectionState(true) == LocalConnectionState.Started)
|
||||
startedCount++;
|
||||
}
|
||||
|
||||
return StoreTransportCacheAndReturn(transports, startedCount == 1);
|
||||
}
|
||||
|
||||
[Obsolete("Use IsOnlyOneServerStarted().")]
|
||||
public bool OneServerStarted() => IsOnlyOneServerStarted();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if any server socket is in the started state.
|
||||
/// </summary>
|
||||
/// <param name = "excludedTransport">When set the transport will be ignored. This value is only used with Multipass.</param>
|
||||
public bool IsAnyServerStarted(Transport excludedTransport)
|
||||
{
|
||||
List<Transport> transports = NetworkManager.TransportManager.GetAllTransports(includeMultipass: false);
|
||||
|
||||
foreach (Transport t in transports)
|
||||
{
|
||||
if (t == excludedTransport)
|
||||
continue;
|
||||
// Another transport is started, no need to load start scenes again.
|
||||
if (t.GetConnectionState(true) == LocalConnectionState.Started)
|
||||
return StoreTransportCacheAndReturn(transports, returnedValue: true);
|
||||
}
|
||||
|
||||
return StoreTransportCacheAndReturn(transports, returnedValue: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if any server socket is in the started state.
|
||||
/// </summary>
|
||||
/// <param name = "excludedIndex">When set the transport on this index will be ignored. This value is only used with Multipass.</param>
|
||||
public bool IsAnyServerStarted(int excludedIndex = TransportConsts.UNSET_TRANSPORT_INDEX)
|
||||
{
|
||||
Transport excludedTransport = null;
|
||||
if (excludedIndex != TransportConsts.UNSET_TRANSPORT_INDEX)
|
||||
{
|
||||
if (NetworkManager.TransportManager.Transport is Multipass mp)
|
||||
excludedTransport = mp.GetTransport(excludedIndex);
|
||||
}
|
||||
|
||||
return IsAnyServerStarted(excludedTransport);
|
||||
}
|
||||
|
||||
[Obsolete("Use IsAnyServerStarted.")]
|
||||
public bool AnyServerStarted(int excludedIndex = TransportConsts.UNSET_TRANSPORT_INDEX) => IsAnyServerStarted(excludedIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name = "go">GameObject instance to spawn.</param>
|
||||
/// <param name = "ownerConnection">Connection to give ownership to.</param>
|
||||
public void Spawn(GameObject go, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
if (go == null)
|
||||
{
|
||||
NetworkManager.LogWarning($"GameObject cannot be spawned because it is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkObject nob = go.GetComponent<NetworkObject>();
|
||||
Spawn(nob, ownerConnection, scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name = "nob">MetworkObject instance to spawn.</param>
|
||||
/// <param name = "ownerConnection">Connection to give ownership to.</param>
|
||||
public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
if (!nob.GetIsSpawnable())
|
||||
{
|
||||
NetworkManager.LogWarning($"NetworkObject {nob} cannot be spawned because it is not marked as spawnable.");
|
||||
return;
|
||||
}
|
||||
Objects.Spawn(nob, ownerConnection, scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns an object over the network. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name = "go">GameObject instance to despawn.</param>
|
||||
/// <param name = "cacheOnDespawnOverride">Overrides the default DisableOnDespawn value for this single despawn. Scene objects will never be destroyed.</param>
|
||||
public void Despawn(GameObject go, DespawnType? despawnType = null)
|
||||
{
|
||||
if (go == null)
|
||||
{
|
||||
NetworkManager.LogWarning($"GameObject cannot be despawned because it is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkObject nob = go.GetComponent<NetworkObject>();
|
||||
Despawn(nob, despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns an object over the network. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name = "networkObject">NetworkObject instance to despawn.</param>
|
||||
/// <param name = "despawnType">Despawn override type.</param>
|
||||
public void Despawn(NetworkObject networkObject, DespawnType? despawnType = null)
|
||||
{
|
||||
DespawnType resolvedDespawnType = !despawnType.HasValue ? networkObject.GetDefaultDespawnType() : despawnType.Value;
|
||||
|
||||
Objects.Despawn(networkObject, resolvedDespawnType, asServer: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a connection immediately while invoking OnClientKick.
|
||||
/// </summary>
|
||||
/// <param name = "conn">Client to kick.</param>
|
||||
/// <param name = "kickReason">Reason client is being kicked.</param>
|
||||
/// <param name = "loggingType">How to print logging as.</param>
|
||||
/// <param name = "log">Optional message to be debug logged.</param>
|
||||
public void Kick(NetworkConnection conn, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
|
||||
{
|
||||
if (!conn.IsValid)
|
||||
return;
|
||||
|
||||
OnClientKick?.Invoke(conn, conn.ClientId, kickReason);
|
||||
if (conn.IsActive)
|
||||
conn.Disconnect(true);
|
||||
|
||||
if (!string.IsNullOrEmpty(log))
|
||||
NetworkManager.Log(loggingType, log);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a connection immediately while invoking OnClientKick.
|
||||
/// </summary>
|
||||
/// <param name = "clientId">ClientId to kick.</param>
|
||||
/// <param name = "kickReason">Reason client is being kicked.</param>
|
||||
/// <param name = "loggingType">How to print logging as.</param>
|
||||
/// <param name = "log">Optional message to be debug logged.</param>
|
||||
public void Kick(int clientId, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
|
||||
{
|
||||
OnClientKick?.Invoke(null, clientId, kickReason);
|
||||
NetworkManager.TransportManager.Transport.StopConnection(clientId, true);
|
||||
if (!string.IsNullOrEmpty(log))
|
||||
NetworkManager.Log(loggingType, log);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kicks a connection immediately while invoking OnClientKick.
|
||||
/// </summary>
|
||||
/// <param name = "conn">Client to kick.</param>
|
||||
/// <param name = "reader">Reader to clear before kicking.</param>
|
||||
/// <param name = "kickReason">Reason client is being kicked.</param>
|
||||
/// <param name = "loggingType">How to print logging as.</param>
|
||||
/// <param name = "log">Optional message to be debug logged.</param>
|
||||
public void Kick(NetworkConnection conn, Reader reader, KickReason kickReason, LoggingType loggingType = LoggingType.Common, string log = "")
|
||||
{
|
||||
reader.Clear();
|
||||
Kick(conn, kickReason, loggingType, log);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2a07d9984be21648bc714ea03bd0d70
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Server/ServerManager.QOL.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,67 @@
|
||||
using FishNet.Object;
|
||||
using FishNet.Transporting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Server
|
||||
{
|
||||
public sealed partial class ServerManager : MonoBehaviour
|
||||
{
|
||||
#region Internal
|
||||
/// <summary>
|
||||
/// Current RPCLinks.
|
||||
/// </summary>
|
||||
internal Dictionary<ushort, RpcLink> RpcLinks = new();
|
||||
/// <summary>
|
||||
/// RPCLink indexes which can be used.
|
||||
/// </summary>
|
||||
private Queue<ushort> _availableRpcLinkIndexes = new();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes RPC Links for NetworkBehaviours.
|
||||
/// </summary>
|
||||
private void InitializeRpcLinks()
|
||||
{
|
||||
ushort startingLink = NetworkManager.StartingRpcLinkIndex;
|
||||
for (ushort i = ushort.MaxValue; i >= startingLink; i--)
|
||||
_availableRpcLinkIndexes.Enqueue(i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the next RPC Link to use.
|
||||
/// </summary>
|
||||
/// <returns>True if a link was available and set.</returns>
|
||||
internal bool GetRpcLink(out ushort value)
|
||||
{
|
||||
if (_availableRpcLinkIndexes.Count > 0)
|
||||
{
|
||||
value = _availableRpcLinkIndexes.Dequeue();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets data to RpcLinks for linkIndex.
|
||||
/// </summary>
|
||||
internal void SetRpcLink(ushort linkIndex, RpcLink data)
|
||||
{
|
||||
RpcLinks[linkIndex] = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns RPCLinks to availableRpcLinkIndexes.
|
||||
/// </summary>
|
||||
internal void StoreRpcLinks(Dictionary<uint, RpcLinkType> links)
|
||||
{
|
||||
foreach (RpcLinkType rlt in links.Values)
|
||||
_availableRpcLinkIndexes.Enqueue(rlt.LinkPacketId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9f0a4620d06f5c41b01f20af3f90634
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Server/ServerManager.RpcLinks.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,993 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Authenticating;
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Debugging;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Predicting;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using FishNet.Utility.Performance;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using FishNet.Managing.Statistic;
|
||||
using Unity.Profiling;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for server data and actions.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("FishNet/Manager/ServerManager")]
|
||||
public sealed partial class ServerManager : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Called after the local server connection state changes.
|
||||
/// </summary>
|
||||
public event Action<ServerConnectionStateArgs> OnServerConnectionState;
|
||||
/// <summary>
|
||||
/// Called when authenticator has concluded a result for a connection. Boolean is true if authentication passed, false if failed.
|
||||
/// </summary>
|
||||
public event Action<NetworkConnection, bool> OnAuthenticationResult;
|
||||
/// <summary>
|
||||
/// Called when a remote client state changes with the server.
|
||||
/// </summary>
|
||||
public event Action<NetworkConnection, RemoteConnectionStateArgs> OnRemoteConnectionState;
|
||||
/// <summary>
|
||||
/// True if the server connection has started.
|
||||
/// </summary>
|
||||
public bool Started { get; private set; }
|
||||
/// <summary>
|
||||
/// Handling and information for objects on the server.
|
||||
/// </summary>
|
||||
public ServerObjects Objects { get; private set; }
|
||||
/// <summary>
|
||||
/// Authenticated and non-authenticated connected clients.
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
public Dictionary<int, NetworkConnection> Clients = new();
|
||||
/// <summary>
|
||||
/// Clients dictionary as a list, containing only values.
|
||||
/// </summary>
|
||||
private List<NetworkConnection> _clientsList = new();
|
||||
/// <summary>
|
||||
/// NetworkManager for server.
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
public NetworkManager NetworkManager { get; private set; }
|
||||
#endregion
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// Gets the Authenticator for this manager.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Authenticator GetAuthenticator() => _authenticator;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Authenticator for this manager, and initializes it.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void SetAuthenticator(Authenticator value)
|
||||
{
|
||||
_authenticator = value;
|
||||
InitializeAuthenticator();
|
||||
}
|
||||
|
||||
[Tooltip("Authenticator for this ServerManager. May be null if not using authentication.")]
|
||||
[SerializeField]
|
||||
private Authenticator _authenticator;
|
||||
/// <summary>
|
||||
/// What platforms to enable remote client timeout.
|
||||
/// </summary>
|
||||
[Tooltip("What platforms to enable remote client timeout.")]
|
||||
[SerializeField]
|
||||
private RemoteTimeoutType _remoteClientTimeout = RemoteTimeoutType.Development;
|
||||
/// <summary>
|
||||
/// How long in seconds client must go without sending any packets before getting disconnected. This is independent of any transport settings.
|
||||
/// </summary>
|
||||
[Tooltip("How long in seconds a client must go without sending any packets before getting disconnected. This is independent of any transport settings.")]
|
||||
[Range(1, MAXIMUM_REMOTE_CLIENT_TIMEOUT_DURATION)]
|
||||
[SerializeField]
|
||||
private ushort _remoteClientTimeoutDuration = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Sets timeout settings. Can be used at runtime.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void SetRemoteClientTimeout(RemoteTimeoutType timeoutType, ushort duration)
|
||||
{
|
||||
_remoteClientTimeout = timeoutType;
|
||||
duration = (ushort)Mathf.Clamp(duration, 1, MAXIMUM_REMOTE_CLIENT_TIMEOUT_DURATION);
|
||||
_remoteClientTimeoutDuration = duration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True to allow clients to use predicted spawning. While true, each NetworkObject you wish this feature to apply towards must have a PredictedSpawn component.
|
||||
/// Predicted spawns can have custom validation on the server.
|
||||
/// </summary>
|
||||
internal bool GetAllowPredictedSpawning() => _allowPredictedSpawning;
|
||||
|
||||
[Tooltip("True to allow clients to use predicted spawning. While true, each NetworkObject you wish this feature to apply towards must have a PredictedSpawn component. Predicted spawns can have custom validation on the server.")]
|
||||
[SerializeField]
|
||||
private bool _allowPredictedSpawning = false;
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[Tooltip("Maximum number of Ids to reserve on clients for predicted spawning. Higher values will allow clients to send more predicted spawns per second but may reduce availability of ObjectIds with high player counts.")]
|
||||
[Range(1, MAXIMUM_RESERVED_OBJECT_IDS)]
|
||||
[SerializeField]
|
||||
private ushort _reservedObjectIds = 15;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of Ids to reserve on clients for predicted spawning. Higher values will allow clients to send more predicted spawns per second but may reduce availability of ObjectIds with high player counts.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal ushort GetReservedObjectIds() => _reservedObjectIds;
|
||||
|
||||
/// <summary>
|
||||
/// Default send rate for SyncTypes. A value of 0f will send changed values every tick.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal float GetSyncTypeRate() => _syncTypeRate;
|
||||
|
||||
[Tooltip("Default send rate for SyncTypes. A value of 0f will send changed values every tick.")]
|
||||
[Range(0f, 60f)]
|
||||
[SerializeField]
|
||||
private float _syncTypeRate = 0.1f;
|
||||
/// <summary>
|
||||
/// How to pack object spawns.
|
||||
/// </summary>
|
||||
[Tooltip("How to pack object spawns.")]
|
||||
[SerializeField]
|
||||
internal TransformPackingData SpawnPacking = new()
|
||||
{
|
||||
Position = AutoPackType.Unpacked,
|
||||
Rotation = AutoPackType.PackedLess,
|
||||
Scale = AutoPackType.PackedLess
|
||||
};
|
||||
/// <summary>
|
||||
/// True to automatically set the frame rate when the client connects.
|
||||
/// </summary>
|
||||
[Tooltip("True to automatically set the frame rate when the client connects.")]
|
||||
[SerializeField]
|
||||
private bool _changeFrameRate = true;
|
||||
/// <summary>
|
||||
/// Maximum frame rate the server may run at. When as host this value runs at whichever is higher between client and server.
|
||||
/// </summary>
|
||||
internal ushort FrameRate => _changeFrameRate ? _frameRate : (ushort)0;
|
||||
[Tooltip("Maximum frame rate the server may run at. When as host this value runs at whichever is higher between client and server.")]
|
||||
[Range(1, NetworkManager.MAXIMUM_FRAMERATE)]
|
||||
[SerializeField]
|
||||
private ushort _frameRate = NetworkManager.MAXIMUM_FRAMERATE;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum frame rate the server may run at. Calling this method will enable ChangeFrameRate.
|
||||
/// </summary>
|
||||
/// <param name = "value">New value.</param>
|
||||
public void SetFrameRate(ushort value)
|
||||
{
|
||||
_frameRate = (ushort)Mathf.Clamp(value, 0, NetworkManager.MAXIMUM_FRAMERATE);
|
||||
_changeFrameRate = true;
|
||||
if (NetworkManager != null)
|
||||
NetworkManager.UpdateFramerate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True to share the Ids of clients and the objects they own with other clients. No sensitive information is shared.
|
||||
/// </summary>
|
||||
public bool ShareIds => _shareIds;
|
||||
[Tooltip("True to share the Ids of clients and the objects they own with other clients. No sensitive information is shared.")]
|
||||
[SerializeField]
|
||||
private bool _shareIds = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets StartOnHeadless value.
|
||||
/// </summary>
|
||||
public bool GetStartOnHeadless() => _startOnHeadless;
|
||||
|
||||
/// <summary>
|
||||
/// Sets StartOnHeadless value.
|
||||
/// </summary>
|
||||
/// <param name = "value">New value to use.</param>
|
||||
public void SetStartOnHeadless(bool value) => _startOnHeadless = value;
|
||||
|
||||
[Tooltip("True to automatically start the server connection when running as headless.")]
|
||||
[SerializeField]
|
||||
private bool _startOnHeadless = true;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// The last index checked to see if a client has not sent a packet in awhile.
|
||||
/// </summary>
|
||||
private int _nextClientTimeoutCheckIndex;
|
||||
/// <summary>
|
||||
/// Next time a timeout check can be performed.
|
||||
/// </summary>
|
||||
private float _nextTimeoutCheckTime;
|
||||
/// <summary>
|
||||
/// Used to read splits.
|
||||
/// </summary>
|
||||
private SplitReader _splitReader = new();
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
private NetworkTrafficStatistics _networkTrafficStatistics;
|
||||
#if DEVELOPMENT
|
||||
/// <summary>
|
||||
/// Logs data about parser to help debug.
|
||||
/// </summary>
|
||||
private PacketIdHistory _packetIdHistory = new();
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region Private Profiler Markers
|
||||
private static readonly ProfilerMarker _pm_OnPostTick = new("ServerManager.TimeManager_OnPostTick()");
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Maximum value the remote client timeout can be set to.
|
||||
/// </summary>
|
||||
public const ushort MAXIMUM_REMOTE_CLIENT_TIMEOUT_DURATION = 1500;
|
||||
/// <summary>
|
||||
/// Maximum number of reserved object Ids allowed for predicted spawning.
|
||||
/// </summary>
|
||||
private const int MAXIMUM_RESERVED_OBJECT_IDS = 100; // QUICK-TEST Increase this to 5000, save, within ServerManager set maximum reserved Ids to max.
|
||||
#endregion
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Objects?.SubscribeToSceneLoaded(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this script for use.
|
||||
/// </summary>
|
||||
/// <param name = "manager"></param>
|
||||
internal void InitializeOnce_Internal(NetworkManager manager)
|
||||
{
|
||||
NetworkManager = manager;
|
||||
Objects = new(manager);
|
||||
Objects.SubscribeToSceneLoaded(true);
|
||||
InitializeRpcLinks();
|
||||
// Unsubscribe first incase already subscribed.
|
||||
SubscribeToTransport(false);
|
||||
SubscribeToTransport(true);
|
||||
NetworkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState;
|
||||
NetworkManager.SceneManager.OnClientLoadedStartScenes += SceneManager_OnClientLoadedStartScenes;
|
||||
NetworkManager.TimeManager.OnPostTick += TimeManager_OnPostTick;
|
||||
|
||||
NetworkManager.StatisticsManager.TryGetNetworkTrafficStatistics(out _networkTrafficStatistics);
|
||||
|
||||
if (_authenticator == null)
|
||||
_authenticator = GetComponent<Authenticator>();
|
||||
if (_authenticator != null)
|
||||
InitializeAuthenticator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the authenticator to this manager.
|
||||
/// </summary>
|
||||
private void InitializeAuthenticator()
|
||||
{
|
||||
Authenticator auth = GetAuthenticator();
|
||||
if (auth == null || auth.Initialized)
|
||||
return;
|
||||
if (NetworkManager == null)
|
||||
return;
|
||||
|
||||
auth.InitializeOnce(NetworkManager);
|
||||
auth.OnAuthenticationResult += _authenticator_OnAuthenticationResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the server if configured to for headless.
|
||||
/// </summary>
|
||||
internal void StartForHeadless()
|
||||
{
|
||||
if (GetStartOnHeadless())
|
||||
{
|
||||
// Wrapping logic in check instead of everything so _startOnHeadless doesnt warn as unused in editor.
|
||||
#if UNITY_SERVER && !UNITY_EDITOR
|
||||
StartConnection();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the local server connection.
|
||||
/// </summary>
|
||||
/// <param name = "sendDisconnectMessage">True to send a disconnect message to all clients first.</param>
|
||||
public bool StopConnection(bool sendDisconnectMessage)
|
||||
{
|
||||
if (sendDisconnectMessage)
|
||||
SendDisconnectMessages(Clients.Values.ToList(), true);
|
||||
|
||||
// Return stop connection result.
|
||||
return NetworkManager.TransportManager.Transport.StopConnection(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a disconnect messge to connectionIds.
|
||||
/// This does not iterate outgoing automatically.
|
||||
/// </summary>
|
||||
/// <param name = "connectionIds"></param>
|
||||
public void SendDisconnectMessages(int[] connectionIds)
|
||||
{
|
||||
List<NetworkConnection> conns = new();
|
||||
foreach (int item in connectionIds)
|
||||
{
|
||||
if (Clients.TryGetValueIL2CPP(item, out NetworkConnection c))
|
||||
conns.Add(c);
|
||||
}
|
||||
|
||||
if (conns.Count > 0)
|
||||
SendDisconnectMessages(conns, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a disconnect message to all clients and optionally immediately iterates outgoing.
|
||||
/// </summary>
|
||||
public void SendDisconnectMessages(List<NetworkConnection> conns, bool iterate)
|
||||
{
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
writer.WritePacketIdUnpacked(PacketId.Disconnect);
|
||||
ArraySegment<byte> segment = writer.GetArraySegment();
|
||||
// Send segment to each client, authenticated or not.
|
||||
foreach (NetworkConnection c in conns)
|
||||
c.SendToClient((byte)Channel.Reliable, segment);
|
||||
//Recycle writer.
|
||||
writer.Store();
|
||||
|
||||
if (iterate)
|
||||
NetworkManager.TransportManager.IterateOutgoing(asServer: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the local server connection.
|
||||
/// </summary>
|
||||
public bool StartConnection()
|
||||
{
|
||||
return NetworkManager.TransportManager.Transport.StartConnection(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the local server using port.
|
||||
/// </summary>
|
||||
/// <param name = "port">Port to start on.</param>
|
||||
/// <returns></returns>
|
||||
public bool StartConnection(ushort port)
|
||||
{
|
||||
Transport t = NetworkManager.TransportManager.Transport;
|
||||
t.SetPort(port);
|
||||
return t.StartConnection(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to timeout client connections.
|
||||
/// </summary>
|
||||
private void CheckClientTimeout()
|
||||
{
|
||||
if (_remoteClientTimeout == RemoteTimeoutType.Disabled)
|
||||
return;
|
||||
#if DEVELOPMENT
|
||||
//If development but not set to development return.
|
||||
if (_remoteClientTimeout != RemoteTimeoutType.Development)
|
||||
return;
|
||||
#endif
|
||||
//Wait two timing intervals to give packets a chance to come through.
|
||||
if (NetworkManager.SceneManager.IsIteratingQueue(2f))
|
||||
return;
|
||||
|
||||
float unscaledTime = Time.unscaledTime;
|
||||
if (unscaledTime < _nextTimeoutCheckTime)
|
||||
return;
|
||||
//Check for timeouts every 200ms.
|
||||
const float TIMEOUT_CHECK_FREQUENCY = 0.2f;
|
||||
_nextTimeoutCheckTime = unscaledTime + TIMEOUT_CHECK_FREQUENCY;
|
||||
//No clients.
|
||||
int clientsCount = Clients.Count;
|
||||
if (clientsCount == 0)
|
||||
return;
|
||||
|
||||
/* If here can do checks. */
|
||||
//If to reset index.
|
||||
if (_nextClientTimeoutCheckIndex >= clientsCount)
|
||||
_nextClientTimeoutCheckIndex = 0;
|
||||
|
||||
//Number of ticks passed for client to be timed out.
|
||||
uint requiredTicks = NetworkManager.TimeManager.TimeToTicks((double)_remoteClientTimeoutDuration, TickRounding.RoundUp);
|
||||
|
||||
const float FULL_CHECK_TIME = 2f;
|
||||
/* Number of times this is expected to run every 2 seconds.
|
||||
* Iterations will try to complete the entire client collection
|
||||
* over these 2 seconds. */
|
||||
int checkCount = Mathf.CeilToInt(FULL_CHECK_TIME / TIMEOUT_CHECK_FREQUENCY);
|
||||
int targetIterations = Mathf.Max(clientsCount / checkCount, 1);
|
||||
|
||||
uint localTick = NetworkManager.TimeManager.LocalTick;
|
||||
for (int i = 0; i < targetIterations; i++)
|
||||
{
|
||||
if (_nextClientTimeoutCheckIndex >= _clientsList.Count)
|
||||
_nextClientTimeoutCheckIndex = 0;
|
||||
|
||||
NetworkConnection item = _clientsList[_nextClientTimeoutCheckIndex];
|
||||
uint clientLocalTick = item.PacketTick.LocalTick;
|
||||
/* If client tick has not been set yet then use the tick
|
||||
* when they connected to the server. */
|
||||
if (clientLocalTick == 0)
|
||||
clientLocalTick = item.ServerConnectionTick;
|
||||
|
||||
uint difference = localTick - clientLocalTick;
|
||||
//Client has timed out.
|
||||
if (difference >= requiredTicks)
|
||||
item.Kick(KickReason.UnexpectedProblem, LoggingType.Common, $"{item.ToString()} has timed out. You can modify this feature on the ServerManager component.");
|
||||
|
||||
_nextClientTimeoutCheckIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the TimeManager calls OnPostTick.
|
||||
/// </summary>
|
||||
private void TimeManager_OnPostTick()
|
||||
{
|
||||
using (_pm_OnPostTick.Auto())
|
||||
CheckClientTimeout();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the local client connection state changes.
|
||||
/// </summary>
|
||||
private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj)
|
||||
{
|
||||
/* If client is doing anything but started destroy pending.
|
||||
* Pending is only used for host mode. */
|
||||
if (obj.ConnectionState != LocalConnectionState.Started)
|
||||
Objects.DestroyPending();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a client loads initial scenes after connecting.
|
||||
/// </summary>
|
||||
private void SceneManager_OnClientLoadedStartScenes(NetworkConnection conn, bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
Objects.RebuildObservers(conn);
|
||||
/* If connection is host then renderers must be hidden
|
||||
* for all objects not visible to the host. The observer system
|
||||
* does handle this but only after an initial state is set.
|
||||
* If the clientHost joins without observation of an object
|
||||
* then the initial state will never be set. */
|
||||
if (conn.IsLocalClient)
|
||||
{
|
||||
foreach (NetworkObject nob in Objects.Spawned.Values)
|
||||
{
|
||||
if (!nob.Observers.Contains(conn))
|
||||
nob.SetRenderersVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes subscription status to transport.
|
||||
/// </summary>
|
||||
/// <param name = "subscribe"></param>
|
||||
private void SubscribeToTransport(bool subscribe)
|
||||
{
|
||||
if (NetworkManager == null || NetworkManager.TransportManager == null || NetworkManager.TransportManager.Transport == null)
|
||||
return;
|
||||
|
||||
if (subscribe)
|
||||
{
|
||||
NetworkManager.TransportManager.Transport.OnServerReceivedData += Transport_OnServerReceivedData;
|
||||
NetworkManager.TransportManager.Transport.OnServerConnectionState += Transport_OnServerConnectionState;
|
||||
NetworkManager.TransportManager.Transport.OnRemoteConnectionState += Transport_OnRemoteConnectionState;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.TransportManager.Transport.OnServerReceivedData -= Transport_OnServerReceivedData;
|
||||
NetworkManager.TransportManager.Transport.OnServerConnectionState -= Transport_OnServerConnectionState;
|
||||
NetworkManager.TransportManager.Transport.OnRemoteConnectionState -= Transport_OnRemoteConnectionState;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when authenticator has concluded a result for a connection. Boolean is true if authentication passed, false if failed.
|
||||
/// Server listens for this event automatically.
|
||||
/// </summary>
|
||||
private void _authenticator_OnAuthenticationResult(NetworkConnection conn, bool authenticated)
|
||||
{
|
||||
if (!authenticated)
|
||||
conn.Disconnect(false);
|
||||
else
|
||||
ClientAuthenticated(conn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a connection state changes for the local server.
|
||||
/// </summary>
|
||||
private void Transport_OnServerConnectionState(ServerConnectionStateArgs args)
|
||||
{
|
||||
/* Let the client manager know the server state is changing first.
|
||||
* This gives the client an opportunity to clean-up or prepare
|
||||
* before the server completes it's actions. */
|
||||
Started = IsAnyServerStarted();
|
||||
NetworkManager.ClientManager.Objects.OnServerConnectionState(args);
|
||||
//If no servers are started then reset data.
|
||||
if (!Started)
|
||||
{
|
||||
MatchCondition.StoreCollections(NetworkManager);
|
||||
//Despawn without synchronizing network objects.
|
||||
Objects.DespawnWithoutSynchronization(recursive: true, asServer: true);
|
||||
//Clear all clients.
|
||||
Clients.Clear();
|
||||
//Clients as list.
|
||||
_clientsList.Clear();
|
||||
}
|
||||
Objects.OnServerConnectionState(args);
|
||||
|
||||
LocalConnectionState state = args.ConnectionState;
|
||||
|
||||
if (NetworkManager.CanLog(LoggingType.Common))
|
||||
{
|
||||
Transport t = NetworkManager.TransportManager.GetTransport(args.TransportIndex);
|
||||
string tName = t == null ? "Unknown" : t.GetType().Name;
|
||||
string socketInformation = string.Empty;
|
||||
if (state == LocalConnectionState.Starting)
|
||||
socketInformation = $" Listening on port {t.GetPort()}.";
|
||||
NetworkManager.Log($"Local server is {state.ToString().ToLower()} for {tName}.{socketInformation}");
|
||||
}
|
||||
|
||||
NetworkManager.UpdateFramerate();
|
||||
OnServerConnectionState?.Invoke(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to make sure the client is on the same version.
|
||||
/// This is to help developers make sure their builds are on the same FishNet version.
|
||||
/// </summary>
|
||||
private void ParseVersion(PooledReader reader, NetworkConnection conn, int transportId)
|
||||
{
|
||||
//Cannot be authenticated if havent sent version yet. This is a duplicate version send, likely exploit attempt.
|
||||
if (conn.HasSentVersion)
|
||||
{
|
||||
conn.Kick(reader, KickReason.ExploitAttempt, LoggingType.Common, $"Connection {conn.ToString()} has sent their FishNet version after being authenticated; this is not possible under normal conditions.");
|
||||
return;
|
||||
}
|
||||
|
||||
conn.HasSentVersion = true;
|
||||
string version = reader.ReadStringAllocated();
|
||||
//Version match.
|
||||
if (version == NetworkManager.FISHNET_VERSION)
|
||||
{
|
||||
/* Send to client if server is in development build or not.
|
||||
* This is to allow the client to utilize some features/information
|
||||
* received from the server only when it's in dev mode. */
|
||||
bool isDevelopmentBuild;
|
||||
#if DEVELOPMENT
|
||||
isDevelopmentBuild = true;
|
||||
#else
|
||||
isDevelopmentBuild = false;
|
||||
#endif
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
writer.WritePacketIdUnpacked(PacketId.Version);
|
||||
writer.WriteBoolean(isDevelopmentBuild);
|
||||
conn.SendToClient((byte)Channel.Reliable, writer.GetArraySegment());
|
||||
WriterPool.Store(writer);
|
||||
|
||||
/* If there is an authenticator
|
||||
* and the transport is not a local transport. */
|
||||
Authenticator auth = GetAuthenticator();
|
||||
if (auth != null)
|
||||
auth.OnRemoteConnection(conn);
|
||||
else
|
||||
ClientAuthenticated(conn);
|
||||
}
|
||||
else
|
||||
{
|
||||
conn.Kick(reader, KickReason.UnexpectedProblem, LoggingType.Warning, $"Connection {conn.ToString()} has been kicked for being on FishNet version {version}. Server version is {NetworkManager.FISHNET_VERSION}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a connection state changes for a remote client.
|
||||
/// </summary>
|
||||
private void Transport_OnRemoteConnectionState(RemoteConnectionStateArgs args)
|
||||
{
|
||||
//Sanity check to make sure transports are following proper types/ranges.
|
||||
int id = args.ConnectionId;
|
||||
if (id < 0 || id > NetworkConnection.MAXIMUM_CLIENTID_VALUE)
|
||||
{
|
||||
Kick(args.ConnectionId, KickReason.UnexpectedProblem, LoggingType.Error, $"The transport you are using supplied an invalid connection Id of {id}. Connection Id values must range between 0 and {NetworkConnection.MAXIMUM_CLIENTID_VALUE}. The client has been disconnected.");
|
||||
return;
|
||||
}
|
||||
//Valid Id.
|
||||
else
|
||||
{
|
||||
//If started then add to authenticated clients.
|
||||
if (args.ConnectionState == RemoteConnectionState.Started)
|
||||
{
|
||||
NetworkManager.Log($"Remote connection started for Id {id}.");
|
||||
NetworkConnection conn = new(NetworkManager, id, args.TransportIndex, true);
|
||||
Clients.Add(args.ConnectionId, conn);
|
||||
_clientsList.Add(conn);
|
||||
OnRemoteConnectionState?.Invoke(conn, args);
|
||||
|
||||
//Do nothing else until the client sends it's version.
|
||||
}
|
||||
//If stopping.
|
||||
else if (args.ConnectionState == RemoteConnectionState.Stopped)
|
||||
{
|
||||
/* If client's connection is found then clean
|
||||
* them up from server. */
|
||||
if (Clients.TryGetValueIL2CPP(id, out NetworkConnection conn))
|
||||
{
|
||||
conn.SetDisconnecting(true);
|
||||
OnRemoteConnectionState?.Invoke(conn, args);
|
||||
Clients.Remove(id);
|
||||
_clientsList.Remove(conn);
|
||||
Objects.ClientDisconnected(conn);
|
||||
BroadcastClientConnectionChange(false, conn);
|
||||
//Return predictedObjectIds.
|
||||
Queue<int> pqId = conn.PredictedObjectIds;
|
||||
while (pqId.Count > 0)
|
||||
Objects.CacheObjectId(pqId.Dequeue());
|
||||
|
||||
conn.ResetState();
|
||||
NetworkManager.Log($"Remote connection stopped for Id {id}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends client their connectionId.
|
||||
/// </summary>
|
||||
/// <param name = "connectionid"></param>
|
||||
private void SendAuthenticated(NetworkConnection conn)
|
||||
{
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
writer.WritePacketIdUnpacked(PacketId.Authenticated);
|
||||
writer.WriteNetworkConnection(conn);
|
||||
/* If predicted spawning is enabled then also send
|
||||
* reserved objectIds. */
|
||||
|
||||
PredictionManager pm = NetworkManager.PredictionManager;
|
||||
if (GetAllowPredictedSpawning())
|
||||
{
|
||||
int count = Mathf.Min(Objects.GetObjectIdCache().Count, GetReservedObjectIds());
|
||||
if (count > MAXIMUM_RESERVED_OBJECT_IDS)
|
||||
count = MAXIMUM_RESERVED_OBJECT_IDS;
|
||||
|
||||
List<int> ids = CollectionCaches<int>.RetrieveList();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (Objects.GetNextNetworkObjectId(out int nId))
|
||||
ids.Add(nId);
|
||||
}
|
||||
|
||||
writer.WriteSignedPackedWhole(ids.Count);
|
||||
foreach (int id in ids)
|
||||
{
|
||||
writer.WriteNetworkObjectId(id);
|
||||
conn.PredictedObjectIds.Enqueue(id);
|
||||
}
|
||||
|
||||
CollectionCaches<int>.Store(ids);
|
||||
}
|
||||
|
||||
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, writer.GetArraySegment(), conn);
|
||||
writer.Store();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the server socket receives data.
|
||||
/// </summary>
|
||||
private void Transport_OnServerReceivedData(ServerReceivedDataArgs args)
|
||||
{
|
||||
ParseReceived(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the server receives data.
|
||||
/// </summary>
|
||||
/// <param name = "args"></param>
|
||||
private void ParseReceived(ServerReceivedDataArgs args)
|
||||
{
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.PacketBundleReceived(asServer: true);
|
||||
#endif
|
||||
|
||||
//Not from a valid connection. Should not be possible.
|
||||
if (args.ConnectionId < 0)
|
||||
return;
|
||||
|
||||
ArraySegment<byte> segment;
|
||||
if (NetworkManager.TransportManager.HasIntermediateLayer)
|
||||
segment = NetworkManager.TransportManager.ProcessIntermediateIncoming(args.Data, false);
|
||||
else
|
||||
segment = args.Data;
|
||||
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddInboundSocketData((ulong)segment.Count, asServer: true);
|
||||
|
||||
if (segment.Count <= TransportManager.UNPACKED_TICK_LENGTH)
|
||||
return;
|
||||
|
||||
//FishNet internally splits packets so nothing should ever arrive over MTU.
|
||||
int channelMtu = NetworkManager.TransportManager.GetMTU(args.TransportIndex, (byte)args.Channel);
|
||||
//If over MTU kick client immediately.
|
||||
if (segment.Count > channelMtu)
|
||||
{
|
||||
ExceededMTUKick();
|
||||
return;
|
||||
}
|
||||
|
||||
TimeManager timeManager = NetworkManager.TimeManager;
|
||||
|
||||
PacketId packetId = PacketId.Unset;
|
||||
PooledReader reader = null;
|
||||
#if !DEVELOPMENT
|
||||
try
|
||||
{
|
||||
#endif
|
||||
Reader.DataSource dataSource = Reader.DataSource.Client;
|
||||
reader = ReaderPool.Retrieve(segment, NetworkManager, dataSource);
|
||||
uint tick = reader.ReadTickUnpacked();
|
||||
timeManager.LastPacketTick.Update(tick);
|
||||
/* This is a special condition where a message may arrive split.
|
||||
* When this occurs buffer each packet until all packets are
|
||||
* received. */
|
||||
if (reader.PeekPacketId() == PacketId.Split)
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
NetworkManager.PacketIdHistory.ReceivedPacket(PacketId.Split, packetFromServer: false);
|
||||
#endif
|
||||
//Skip packetId.
|
||||
reader.ReadPacketId();
|
||||
|
||||
int expectedMessages;
|
||||
_splitReader.GetHeader(reader, out expectedMessages);
|
||||
//If here split message is to be read into splitReader.
|
||||
_splitReader.Write(tick, reader, expectedMessages);
|
||||
|
||||
/* If fullMessage returns 0 count then the split
|
||||
* has not written fully yet. Otherwise, if there is
|
||||
* data within then reinitialize reader with the
|
||||
* full message. */
|
||||
ArraySegment<byte> fullMessage = _splitReader.GetFullMessage();
|
||||
if (fullMessage.Count == 0)
|
||||
return;
|
||||
|
||||
/* If here then all data has been received.
|
||||
* It's possible the client could have exceeded
|
||||
* maximum MTU but not the maximum number of splits.
|
||||
* This is because the length of each split
|
||||
* is not written, so we don't know how much data of the
|
||||
* final message actually belonged to the split vs
|
||||
* unrelated data added afterwards. We're going to cut
|
||||
* the client some slack in this situation for the sake
|
||||
* of keeping things simple. */
|
||||
reader.Initialize(fullMessage, NetworkManager, dataSource);
|
||||
}
|
||||
|
||||
//Parse reader.
|
||||
while (reader.Remaining > 0)
|
||||
{
|
||||
packetId = reader.ReadPacketId();
|
||||
#if DEVELOPMENT
|
||||
NetworkManager.PacketIdHistory.ReceivedPacket(packetId, packetFromServer: false);
|
||||
#endif
|
||||
NetworkConnection conn;
|
||||
|
||||
/* Connection isn't available. This should never happen.
|
||||
* Force an immediate disconnect. */
|
||||
if (!Clients.TryGetValueIL2CPP(args.ConnectionId, out conn))
|
||||
{
|
||||
Kick(args.ConnectionId, KickReason.UnexpectedProblem, LoggingType.Error, $"ConnectionId {args.ConnectionId} not found within Clients. Connection will be kicked immediately.");
|
||||
return;
|
||||
}
|
||||
conn.LocalTick.Update(timeManager, tick, EstimatedTick.OldTickOption.Discard);
|
||||
conn.PacketTick.Update(timeManager, tick, EstimatedTick.OldTickOption.SetLastRemoteTick);
|
||||
/* If connection isn't authenticated and isn't a broadcast
|
||||
* then disconnect client. If a broadcast then process
|
||||
* normally; client may still become disconnected if the broadcast
|
||||
* does not allow to be called while not authenticated. */
|
||||
if (!conn.IsAuthenticated && packetId != PacketId.Version && packetId != PacketId.Broadcast)
|
||||
{
|
||||
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent packetId {packetId} without being authenticated. Connection will be kicked immediately.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (packetId == PacketId.Replicate)
|
||||
{
|
||||
Objects.ParseReplicateRpc(reader, conn, args.Channel);
|
||||
}
|
||||
else if (packetId == PacketId.ServerRpc)
|
||||
{
|
||||
Objects.ParseServerRpc(reader, conn, args.Channel);
|
||||
}
|
||||
else if (packetId == PacketId.ObjectSpawn)
|
||||
{
|
||||
if (!GetAllowPredictedSpawning())
|
||||
{
|
||||
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a predicted spawn while predicted spawning is not enabled. Connection will be kicked immediately.");
|
||||
return;
|
||||
}
|
||||
Objects.ReadSpawn(reader, conn);
|
||||
}
|
||||
else if (packetId == PacketId.ObjectDespawn)
|
||||
{
|
||||
if (!GetAllowPredictedSpawning())
|
||||
{
|
||||
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a predicted spawn while predicted spawning is not enabled. Connection will be kicked immediately.");
|
||||
return;
|
||||
}
|
||||
Objects.ReadDespawn(reader, conn);
|
||||
}
|
||||
else if (packetId == PacketId.Broadcast)
|
||||
{
|
||||
ParseBroadcast(reader, conn, args.Channel);
|
||||
}
|
||||
else if (packetId == PacketId.PingPong)
|
||||
{
|
||||
ParsePingPong(reader, conn);
|
||||
}
|
||||
else if (packetId == PacketId.Version)
|
||||
{
|
||||
ParseVersion(reader, conn, args.TransportIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
NetworkManager.LogError($"Server received an unhandled PacketId of {(ushort)packetId} on channel {args.Channel} from connectionId {args.ConnectionId}. Remaining data has been purged.");
|
||||
NetworkManager.LogError(NetworkManager.PacketIdHistory.GetReceivedPacketIds(packetsFromServer: false));
|
||||
#else
|
||||
NetworkManager.LogError($"Server received an unhandled PacketId of {(ushort)packetId} on channel {args.Channel} from connectionId {args.ConnectionId}. Connection will be kicked immediately.");
|
||||
NetworkManager.TransportManager.Transport.StopConnection(args.ConnectionId, true);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
#if !DEVELOPMENT
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Kick(args.ConnectionId, KickReason.MalformedData, LoggingType.Error, $"Server encountered an error while parsing data for packetId {packetId} from connectionId {args.ConnectionId}. Connection will be kicked immediately. Message: {e}.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader?.Store();
|
||||
}
|
||||
#else
|
||||
reader?.Store();
|
||||
#endif
|
||||
|
||||
//Kicks connection for exceeding MTU.
|
||||
void ExceededMTUKick()
|
||||
{
|
||||
Kick(args.ConnectionId, KickReason.ExploitExcessiveData, LoggingType.Common, $"ConnectionId {args.ConnectionId} sent a message larger than allowed amount. Connection will be kicked immediately.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a received PingPong.
|
||||
/// </summary>
|
||||
/// <param name = "reader"></param>
|
||||
/// <param name = "conn"></param>
|
||||
private void ParsePingPong(PooledReader reader, NetworkConnection conn)
|
||||
{
|
||||
int readerPositionAfterDebug = reader.Position;
|
||||
|
||||
/* //security limit how often clients can send pings.
|
||||
* have clients use a stopwatch rather than frame time
|
||||
* for checks to ensure it's not possible to send
|
||||
* excessively should their game stutter then catch back up. */
|
||||
uint clientTick = reader.ReadTickUnpacked();
|
||||
if (conn.CanPingPong())
|
||||
NetworkManager.TimeManager.SendPong(conn, clientTick);
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddInboundPacketIdData(PacketId.PingPong, string.Empty, reader.Position - readerPositionAfterDebug + TransportManager.PACKETID_LENGTH, gameObject: null, asServer: false);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a remote client authenticates with the server.
|
||||
/// </summary>
|
||||
/// <param name = "connectionId"></param>
|
||||
private void ClientAuthenticated(NetworkConnection connection)
|
||||
{
|
||||
/* Immediately send connectionId to client. Some transports
|
||||
* don't give clients their remoteId, therefor it has to be sent
|
||||
* by the ServerManager. This packet is very simple and can be built
|
||||
* on the spot. */
|
||||
connection.ConnectionAuthenticated();
|
||||
/* Send client Ids before telling the client
|
||||
* they are authenticated. This is important because when the client becomes
|
||||
* authenticated they set their LocalConnection using Clients field in ClientManager,
|
||||
* which is set after getting Ids. */
|
||||
BroadcastClientConnectionChange(true, connection);
|
||||
SendAuthenticated(connection);
|
||||
|
||||
OnAuthenticationResult?.Invoke(connection, true);
|
||||
NetworkManager.SceneManager.OnClientAuthenticated(connection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a client connection state change to owner and other clients if applicable.
|
||||
/// </summary>
|
||||
private void BroadcastClientConnectionChange(bool connected, NetworkConnection conn)
|
||||
{
|
||||
//Only send if the connection was authenticated.
|
||||
if (!conn.IsAuthenticated)
|
||||
return;
|
||||
//If sharing Ids then send all connected client Ids first if is a connected state.
|
||||
if (ShareIds)
|
||||
{
|
||||
/* Send a broadcast to all authenticated clients with the clientId
|
||||
* that just connected. The conn client will also get this. */
|
||||
ClientConnectionChangeBroadcast changeMsg = new()
|
||||
{
|
||||
Connected = connected,
|
||||
Id = conn.ClientId
|
||||
};
|
||||
foreach (NetworkConnection c in Clients.Values)
|
||||
{
|
||||
if (c.IsAuthenticated)
|
||||
Broadcast(c, changeMsg);
|
||||
}
|
||||
|
||||
/* If state is connected then the conn client
|
||||
* must also receive all currently connected client ids. */
|
||||
if (connected)
|
||||
{
|
||||
//Send already connected clients to the connection that just joined.
|
||||
List<int> cache = CollectionCaches<int>.RetrieveList();
|
||||
foreach (int key in Clients.Keys)
|
||||
cache.Add(key);
|
||||
|
||||
ConnectedClientsBroadcast allMsg = new()
|
||||
{
|
||||
Values = cache
|
||||
};
|
||||
conn.Broadcast(allMsg);
|
||||
CollectionCaches<int>.Store(cache);
|
||||
}
|
||||
}
|
||||
//If not sharing Ids then only send ConnectionChange to conn.
|
||||
else
|
||||
{
|
||||
if (connected)
|
||||
{
|
||||
/* Send broadcast only to the client which just disconnected.
|
||||
* Only send if connecting. If the client is disconnected there's no reason
|
||||
* to send them a disconnect msg. */
|
||||
ClientConnectionChangeBroadcast changeMsg = new()
|
||||
{
|
||||
Connected = connected,
|
||||
Id = conn.ClientId
|
||||
};
|
||||
Broadcast(conn, changeMsg, true, Channel.Reliable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68828c85278210948b9d50a8db3aab74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Server/ServerManager.cs
|
||||
uploadId: 866910
|
||||
Reference in New Issue
Block a user