Moved a couple of folders and wrote some code

This commit is contained in:
Madhav Kapa
2023-06-01 11:21:49 -07:00
parent 0eea70ab4e
commit e8684391ca
1380 changed files with 2766 additions and 13987 deletions

View File

@ -0,0 +1,18 @@
using FishNet.Broadcast;
using FishNet.Utility.Performance;
namespace FishNet.Managing.Server
{
public struct ClientConnectionChangeBroadcast : IBroadcast
{
public bool Connected;
public int Id;
}
public struct ConnectedClientsBroadcast : IBroadcast
{
public ListCache<int> ListCache;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a16b83a545be8f8488795783a0fc8648
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eef52ab3fdb83f04592a1de5ccb741f5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,64 @@
#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 _syncTypeRate;
private SerializedProperty SpawnPacking;
private SerializedProperty _changeFrameRate;
private SerializedProperty _frameRate;
private SerializedProperty _shareIds;
private SerializedProperty _startOnHeadless;
private SerializedProperty _limitClientMTU;
protected virtual void OnEnable()
{
_authenticator = serializedObject.FindProperty(nameof(_authenticator));
_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));
_limitClientMTU = serializedObject.FindProperty(nameof(_limitClientMTU));
}
public override void OnInspectorGUI()
{
serializedObject.Update();
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((ServerManager)target), typeof(ServerManager), false);
GUI.enabled = true;
EditorGUILayout.PropertyField(_authenticator);
EditorGUILayout.PropertyField(_syncTypeRate);
EditorGUILayout.PropertyField(SpawnPacking);
EditorGUILayout.PropertyField(_changeFrameRate);
if (_changeFrameRate.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_frameRate);
EditorGUI.indentLevel--;
}
EditorGUILayout.PropertyField(_shareIds);
EditorGUILayout.PropertyField(_startOnHeadless);
EditorGUILayout.PropertyField(_limitClientMTU);
EditorGUILayout.Space();
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da6ea97e6b868974e8ac139fe545e986
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,36 @@
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,
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9289a823878c5d1408e7106e6ed5d866
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 272e053307699c84d9787bfd86d3529a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,522 @@
using FishNet.Connection;
using FishNet.Managing.Object;
using FishNet.Managing.Transporting;
using FishNet.Object;
using FishNet.Observing;
using FishNet.Serializing;
using FishNet.Transporting;
using FishNet.Utility.Performance;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
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 List<NetworkObject>(100);
/// <summary>
/// NetworkObservers which require regularly iteration.
/// </summary>
private List<NetworkObject> _timedNetworkObservers = new List<NetworkObject>();
/// <summary>
/// Index in TimedNetworkObservers to start on next cycle.
/// </summary>
private int _nextTimedObserversIndex;
#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 (!base.NetworkManager.IsServer)
return;
//No point in updating if the timemanager isn't going to tick this frame.
if (!base.NetworkManager.TimeManager.FrameTicked)
return;
int observersCount = _timedNetworkObservers.Count;
if (observersCount == 0)
return;
ServerManager serverManager = base.NetworkManager.ServerManager;
TransportManager transportManager = NetworkManager.TransportManager;
/* Try to iterate all timed observers every half a second.
* This value will increase as there's more observers. */
int completionTicks = (base.NetworkManager.TimeManager.TickRate * 2);
/* Multiply required ticks based on connection count and nob count. This will
* reduce how quickly observers update slightly but will drastically
* improve performance. */
float tickMultiplier = 1f + (float)(
(serverManager.Clients.Count * 0.005f) +
(_timedNetworkObservers.Count * 0.0005f)
);
/* Add an additional iteration to prevent
* 0 iterations */
int iterations = (observersCount / (int)(completionTicks * tickMultiplier)) + 1;
if (iterations > observersCount)
iterations = observersCount;
PooledWriter everyoneWriter = WriterPool.GetWriter();
PooledWriter ownerWriter = WriterPool.GetWriter();
//Index to perform a check on.
int observerIndex = 0;
foreach (NetworkConnection conn in serverManager.Clients.Values)
{
int cacheIndex = 0;
using (PooledWriter largeWriter = WriterPool.GetWriter())
{
//Reset index to start on for every connection.
observerIndex = 0;
/* Run the number of calculated iterations.
* This is spaced out over frames to prevent
* fps spikes. */
for (int i = 0; i < iterations; i++)
{
observerIndex = _nextTimedObserversIndex + i;
/* Compare actual collection size not cached value.
* This is incase collection is modified during runtime. */
if (observerIndex >= _timedNetworkObservers.Count)
observerIndex -= _timedNetworkObservers.Count;
/* If still out of bounds something whack is going on.
* Reset index and exit method. Let it sort itself out
* next iteration. */
if (observerIndex < 0 || observerIndex >= _timedNetworkObservers.Count)
{
_nextTimedObserversIndex = 0;
break;
}
NetworkObject nob = _timedNetworkObservers[observerIndex];
ObserverStateChange osc = nob.RebuildObservers(conn, true);
if (osc == ObserverStateChange.Added)
{
everyoneWriter.Reset();
ownerWriter.Reset();
base.WriteSpawn_Server(nob, conn, everyoneWriter, ownerWriter);
CacheObserverChange(nob, ref cacheIndex);
}
else if (osc == ObserverStateChange.Removed)
{
everyoneWriter.Reset();
WriteDespawn(nob, nob.GetDefaultDespawnType(), everyoneWriter);
}
else
{
continue;
}
/* Only use ownerWriter if an add, and if owner. Owner
* doesn't matter if not being added because no owner specific
* information would be included. */
PooledWriter writerToUse = (osc == ObserverStateChange.Added && nob.Owner == conn) ?
ownerWriter : everyoneWriter;
largeWriter.WriteArraySegment(writerToUse.GetArraySegment());
}
if (largeWriter.Length > 0)
{
transportManager.SendToClient(
(byte)Channel.Reliable,
largeWriter.GetArraySegment(), conn);
}
//Invoke spawn callbacks on nobs.
for (int i = 0; i < cacheIndex; i++)
_observerChangedObjectsCache[i].InvokePostOnServerStart(conn);
}
}
everyoneWriter.Dispose();
ownerWriter.Dispose();
_nextTimedObserversIndex = (observerIndex + 1);
}
/// <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)
{
_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)
{
_timedNetworkObservers.Remove(networkObject);
}
/// <summary>
/// Caches an observer change.
/// </summary>
/// <param name="cacheIndex"></param>
private void CacheObserverChange(NetworkObject nob, ref int cacheIndex)
{
/* If this spawn would exceed cache size then
* add instead of set value. */
if (_observerChangedObjectsCache.Count <= cacheIndex)
_observerChangedObjectsCache.Add(nob);
else
_observerChangedObjectsCache[cacheIndex] = nob;
cacheIndex++;
}
/// <summary>
/// Removes a connection from observers without synchronizing changes.
/// </summary>
/// <param name="connection"></param>
private void RemoveFromObserversWithoutSynchronization(NetworkConnection connection)
{
int cacheIndex = 0;
foreach (NetworkObject nob in Spawned.Values)
{
if (nob.RemoveObserver(connection))
CacheObserverChange(nob, ref cacheIndex);
}
//Invoke despawn callbacks on nobs.
for (int i = 0; i < cacheIndex; i++)
_observerChangedObjectsCache[i].InvokeOnServerDespawn(connection);
}
/// <summary>
/// Rebuilds observers on all NetworkObjects for all connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers()
{
ListCache<NetworkObject> nobCache = GetOrderedSpawnedObjects();
ListCache<NetworkConnection> connCache = ListCaches.GetNetworkConnectionCache();
foreach (NetworkConnection conn in base.NetworkManager.ServerManager.Clients.Values)
connCache.AddValue(conn);
RebuildObservers(nobCache, connCache);
ListCaches.StoreCache(nobCache);
ListCaches.StoreCache(connCache);
}
/// <summary>
/// Rebuilds observers on NetworkObjects.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(NetworkObject[] nobs)
{
int count = nobs.Length;
for (int i = 0; i < count; i++)
RebuildObservers(nobs[i]);
}
/// <summary>
/// Rebuilds observers on NetworkObjects.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(List<NetworkObject> nobs)
{
int count = nobs.Count;
for (int i = 0; i < count; i++)
RebuildObservers(nobs[i]);
}
/// <summary>
/// Rebuilds observers on NetworkObjects.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(ListCache<NetworkObject> nobs)
{
int count = nobs.Written;
List<NetworkObject> collection = nobs.Collection;
for (int i = 0; i < count; i++)
RebuildObservers(collection[i]);
}
/// <summary>
/// Rebuilds observers on NetworkObjects for connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(ListCache<NetworkObject> nobs, NetworkConnection conn)
{
RebuildObservers(nobs.Collection, conn, nobs.Written);
}
/// <summary>
/// Rebuilds observers on NetworkObjects for connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(ListCache<NetworkObject> nobs, ListCache<NetworkConnection> conns)
{
int count = nobs.Written;
List<NetworkObject> collection = nobs.Collection;
for (int i = 0; i < count; i++)
RebuildObservers(collection[i], conns);
}
/// <summary>
/// Rebuilds observers on all objects for a connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(ListCache<NetworkConnection> connections)
{
int count = connections.Written;
List<NetworkConnection> collection = connections.Collection;
for (int i = 0; i < count; i++)
RebuildObservers(collection[i]);
}
/// <summary>
/// Rebuilds observers on all objects for connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(NetworkConnection[] connections)
{
int count = connections.Length;
for (int i = 0; i < count; i++)
RebuildObservers(connections[i]);
}
/// <summary>
/// Rebuilds observers on all objects for connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(List<NetworkConnection> connections)
{
int count = connections.Count;
for (int i = 0; i < count; i++)
RebuildObservers(connections[i]);
}
/// <summary>
/// Rebuilds observers on all NetworkObjects for a connection.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(NetworkConnection connection)
{
ListCache<NetworkObject> cache = GetOrderedSpawnedObjects();
RebuildObservers(cache, connection);
ListCaches.StoreCache(cache);
}
/// <summary>
/// Gets all spawned objects with root objects first.
/// </summary>
/// <returns></returns>
private ListCache<NetworkObject> GetOrderedSpawnedObjects()
{
ListCache<NetworkObject> cache = ListCaches.GetNetworkObjectCache();
foreach (NetworkObject networkObject in Spawned.Values)
{
if (networkObject.IsNested)
continue;
//Add nob and children recursively.
AddChildNetworkObjects(networkObject);
}
void AddChildNetworkObjects(NetworkObject n)
{
cache.AddValue(n);
foreach (NetworkObject nob in n.ChildNetworkObjects)
AddChildNetworkObjects(nob);
}
return cache;
}
/// <summary>
/// Rebuilds observers for a connection on NetworkObjects.
/// </summary>
/// <param name="nobs">NetworkObjects to rebuild.</param>
/// <param name="connection">Connection to rebuild for.</param>
/// <param name="count">Number of iterations to perform collection. Entire collection is iterated when value is -1.</param>
public void RebuildObservers(IEnumerable<NetworkObject> nobs, NetworkConnection connection, int count = -1)
{
PooledWriter everyoneWriter = WriterPool.GetWriter();
PooledWriter ownerWriter = WriterPool.GetWriter();
//If there's no limit on how many can be written set count to the maximum.
if (count == -1)
count = int.MaxValue;
int iterations;
int observerCacheIndex;
using (PooledWriter largeWriter = WriterPool.GetWriter())
{
iterations = 0;
observerCacheIndex = 0;
foreach (NetworkObject n in nobs)
{
iterations++;
if (iterations > count)
break;
//If observer state changed then write changes.
ObserverStateChange osc = n.RebuildObservers(connection, false);
if (osc == ObserverStateChange.Added)
{
everyoneWriter.Reset();
ownerWriter.Reset();
base.WriteSpawn_Server(n, connection, everyoneWriter, ownerWriter);
CacheObserverChange(n, ref observerCacheIndex);
}
else if (osc == ObserverStateChange.Removed)
{
connection.LevelOfDetails.Remove(n);
everyoneWriter.Reset();
WriteDespawn(n, n.GetDefaultDespawnType(), everyoneWriter);
}
else
{
continue;
}
/* Only use ownerWriter if an add, and if owner. Owner //cleanup see if rebuild timed and this can be joined or reuse methods.
* doesn't matter if not being added because no owner specific
* information would be included. */
PooledWriter writerToUse = (osc == ObserverStateChange.Added && n.Owner == connection) ?
ownerWriter : everyoneWriter;
largeWriter.WriteArraySegment(writerToUse.GetArraySegment());
}
if (largeWriter.Length > 0)
{
NetworkManager.TransportManager.SendToClient(
(byte)Channel.Reliable,
largeWriter.GetArraySegment(), connection);
}
}
//Dispose of writers created in this method.
everyoneWriter.Dispose();
ownerWriter.Dispose();
//Invoke spawn callbacks on nobs.
for (int i = 0; i < observerCacheIndex; i++)
_observerChangedObjectsCache[i].InvokePostOnServerStart(connection);
}
/// <summary>
/// Rebuilds observers for connections on a NetworkObject.
/// </summary>
private void RebuildObservers(NetworkObject nob, ListCache<NetworkConnection> conns)
{
PooledWriter everyoneWriter = WriterPool.GetWriter();
PooledWriter ownerWriter = WriterPool.GetWriter();
int written = conns.Written;
for (int i = 0; i < written; i++)
{
NetworkConnection conn = conns.Collection[i];
everyoneWriter.Reset();
ownerWriter.Reset();
//If observer state changed then write changes.
ObserverStateChange osc = nob.RebuildObservers(conn, false);
if (osc == ObserverStateChange.Added)
{
base.WriteSpawn_Server(nob, conn, everyoneWriter, ownerWriter);
}
else if (osc == ObserverStateChange.Removed)
{
conn.LevelOfDetails.Remove(nob);
WriteDespawn(nob, nob.GetDefaultDespawnType(), everyoneWriter);
}
else
{
continue;
}
/* Only use ownerWriter if an add, and if owner. Owner
* doesn't matter if not being added because no owner specific
* information would be included. */
PooledWriter writerToUse = (osc == ObserverStateChange.Added && nob.Owner == conn) ?
ownerWriter : everyoneWriter;
if (writerToUse.Length > 0)
{
NetworkManager.TransportManager.SendToClient(
(byte)Channel.Reliable,
writerToUse.GetArraySegment(), conn);
//If a spawn is being sent.
if (osc == ObserverStateChange.Added)
nob.InvokePostOnServerStart(conn);
}
}
//Dispose of writers created in this method.
everyoneWriter.Dispose();
ownerWriter.Dispose();
}
/// <summary>
/// Rebuilds observers for all connections for a NetworkObject.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(NetworkObject nob)
{
ListCache<NetworkConnection> cache = ListCaches.GetNetworkConnectionCache();
foreach (NetworkConnection item in NetworkManager.ServerManager.Clients.Values)
cache.AddValue(item);
RebuildObservers(nob, cache);
ListCaches.StoreCache(cache);
}
/// <summary>
/// Rebuilds observers for a connection on NetworkObject.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RebuildObservers(NetworkObject nob, NetworkConnection conn)
{
ListCache<NetworkConnection> cache = ListCaches.GetNetworkConnectionCache();
cache.AddValue(conn);
RebuildObservers(nob, cache);
ListCaches.StoreCache(cache);
}
/// <summary>
/// Rebuilds observers for connections on NetworkObject.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(NetworkObject networkObject, NetworkConnection[] connections)
{
ListCache<NetworkConnection> cache = ListCaches.GetNetworkConnectionCache();
cache.AddValues(connections);
RebuildObservers(networkObject, cache);
ListCaches.StoreCache(cache);
}
/// <summary>
/// Rebuilds observers for connections on NetworkObject.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(NetworkObject networkObject, List<NetworkConnection> connections)
{
ListCache<NetworkConnection> cache = ListCaches.GetNetworkConnectionCache();
cache.AddValues(connections);
RebuildObservers(networkObject, cache);
ListCaches.StoreCache(cache);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02d93fa4a653dd64da0bb338b82f4740
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@
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 ReplicateRpc.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ParseReplicateRpc(PooledReader reader, NetworkConnection conn, Channel channel)
{
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
int dataLength = Packets.GetPacketLength((ushort)PacketId.ServerRpc, reader, channel);
if (nb != null)
nb.OnReplicateRpc(null, reader, conn, channel);
else
SkipDataLength((ushort)PacketId.ServerRpc, reader, dataLength);
}
/// <summary>
/// Parses a ServerRpc.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ParseServerRpc(PooledReader reader, NetworkConnection conn, Channel channel)
{
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
int dataLength = Packets.GetPacketLength((ushort)PacketId.ServerRpc, reader, channel);
if (nb != null)
nb.OnServerRpc(reader, conn, channel);
else
SkipDataLength((ushort)PacketId.ServerRpc, reader, dataLength);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3cb6cef520a4ff44bb8c4814e566c5ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,876 @@
using FishNet.Connection;
using FishNet.Managing.Logging;
using FishNet.Managing.Object;
using FishNet.Managing.Timing;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using FishNet.Utility;
using FishNet.Utility.Extension;
using FishNet.Utility.Performance;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace FishNet.Managing.Server
{
/// <summary>
/// Handles objects and information about objects for the server. See ManagedObjects for inherited options.
/// </summary>
public partial class ServerObjects : ManagedObjects
{
#region Public.
/// <summary>
/// Called right before client objects are destroyed when a client disconnects.
/// </summary>
public event Action<NetworkConnection> OnPreDestroyClientObjects;
#endregion
#region Internal.
/// <summary>
/// Collection of NetworkObjects recently despawned.
/// Key: objectId.
/// Value: despawn tick.
/// This is used primarily to track if client is sending messages for recently despawned objects.
/// Objects are automatically removed after RECENTLY_DESPAWNED_DURATION seconds.
/// </summary>
internal Dictionary<int, uint> RecentlyDespawnedIds = new Dictionary<int, uint>();
#endregion
#region Private.
/// <summary>
/// Cached ObjectIds which may be used when exceeding available ObjectIds.
/// </summary>
private Queue<int> _objectIdCache = new Queue<int>();
/// <summary>
/// Returns the ObjectId cache.
/// </summary>
/// <returns></returns>
internal Queue<int> GetObjectIdCache() => _objectIdCache;
/// <summary>
/// NetworkBehaviours which have dirty SyncVars.
/// </summary>
private List<NetworkBehaviour> _dirtySyncVarBehaviours = new List<NetworkBehaviour>(20);
/// <summary>
/// NetworkBehaviours which have dirty SyncObjects.
/// </summary>
private List<NetworkBehaviour> _dirtySyncObjectBehaviours = new List<NetworkBehaviour>(20);
/// <summary>
/// Objects which need to be destroyed next tick.
/// This is needed when running as host so host client will get any final messages for the object before they're destroyed.
/// </summary>
private Dictionary<int, NetworkObject> _pendingDestroy = new Dictionary<int, NetworkObject>();
/// <summary>
/// Scenes which were loaded that need to be setup.
/// </summary>
private List<(int, Scene)> _loadedScenes = new List<(int frame, Scene scene)>();
/// <summary>
/// Cache of spawning objects, used for recursively spawning nested NetworkObjects.
/// </summary>
private ListCache<NetworkObject> _spawnCache = new ListCache<NetworkObject>();
/// <summary>
/// True if one or more scenes are currently loading through the SceneManager.
/// </summary>
private bool _scenesLoading;
/// <summary>
/// Number of ticks which must pass to clear a recently despawned.
/// </summary>
private uint _cleanRecentlyDespawnedMaxTicks => base.NetworkManager.TimeManager.TimeToTicks(30d, TickRounding.RoundUp);
#endregion
internal ServerObjects(NetworkManager networkManager)
{
base.Initialize(networkManager);
networkManager.SceneManager.OnLoadStart += SceneManager_OnLoadStart;
networkManager.SceneManager.OnActiveSceneSetInternal += SceneManager_OnActiveSceneSet;
networkManager.TimeManager.OnUpdate += TimeManager_OnUpdate;
}
/// <summary>
/// Called when MonoBehaviours call Update.
/// </summary>
private void TimeManager_OnUpdate()
{
if (!base.NetworkManager.IsServer)
{
_scenesLoading = false;
_loadedScenes.Clear();
return;
}
CleanRecentlyDespawned();
if (!_scenesLoading)
IterateLoadedScenes(false);
Observers_OnUpdate();
}
#region Checking dirty SyncTypes.
/// <summary>
/// Iterates NetworkBehaviours with dirty SyncTypes.
/// </summary>
internal void WriteDirtySyncTypes()
{
/* Tells networkbehaviours to check their
* dirty synctypes. */
IterateCollection(_dirtySyncVarBehaviours, false);
IterateCollection(_dirtySyncObjectBehaviours, true);
void IterateCollection(List<NetworkBehaviour> collection, bool isSyncObject)
{
for (int i = 0; i < collection.Count; i++)
{
bool dirtyCleared = collection[i].WriteDirtySyncTypes(isSyncObject);
if (dirtyCleared)
{
collection.RemoveAt(i);
i--;
}
}
}
}
/// <summary>
/// Sets that a NetworkBehaviour has a dirty syncVars.
/// </summary>
/// <param name="nb"></param>
internal void SetDirtySyncType(NetworkBehaviour nb, bool isSyncObject)
{
if (isSyncObject)
_dirtySyncObjectBehaviours.Add(nb);
else
_dirtySyncVarBehaviours.Add(nb);
}
#endregion
#region Connection Handling.
/// <summary>
/// Called when the connection state changes for the local server.
/// </summary>
/// <param name="args"></param>
internal void OnServerConnectionState(ServerConnectionStateArgs args)
{
//If server just connected.
if (args.ConnectionState == LocalConnectionState.Started)
{
/* If there's no servers started besides the one
* that just started then build Ids and setup scene objects. */
if (base.NetworkManager.ServerManager.OneServerStarted())
{
BuildObjectIdCache();
SetupSceneObjects();
}
}
//Server in anything but started state.
else
{
//If no servers are started then reset.
if (!base.NetworkManager.ServerManager.AnyServerStarted())
{
base.DespawnWithoutSynchronization(true);
base.SceneObjects.Clear();
_objectIdCache.Clear();
base.NetworkManager.ClearClientsCollection(base.NetworkManager.ServerManager.Clients);
}
}
}
/// <summary>
/// Called when a client disconnects.
/// </summary>
/// <param name="connection"></param>
internal void ClientDisconnected(NetworkConnection connection)
{
RemoveFromObserversWithoutSynchronization(connection);
OnPreDestroyClientObjects?.Invoke(connection);
/* A cache is made because the Objects
* collection would end up modified during
* iteration from removing ownership and despawning. */
ListCache<NetworkObject> cache = ListCaches.GetNetworkObjectCache();
foreach (NetworkObject nob in connection.Objects)
cache.AddValue(nob);
int written = cache.Written;
List<NetworkObject> collection = cache.Collection;
for (int i = 0; i < written; i++)
{
/* Objects may already be deinitializing when a client disconnects
* because the root object could have been despawned first, and in result
* all child objects would have been recursively despawned.
*
* EG: object is:
* A (nob)
* B (nob)
*
* Both A and B are owned by the client so they will both be
* in collection. Should A despawn first B will recursively despawn
* from it. Then once that finishes and the next index of collection
* is run, which would B, the object B would have already been deinitialized. */
if (!collection[i].IsDeinitializing)
base.NetworkManager.ServerManager.Despawn(collection[i]);
}
ListCaches.StoreCache(cache);
}
#endregion
#region ObjectIds.
/// <summary>
/// Builds the ObjectId cache with all possible Ids.
/// </summary>
private void BuildObjectIdCache()
{
_objectIdCache.Clear();
/* Shuffle Ids to make it more difficult
* for clients to track spawned object
* count. */
List<int> shuffledCache = new List<int>();
//Ignore ushort.maxvalue as that indicates null.
for (int i = 0; i < (ushort.MaxValue - 1); i++)
shuffledCache.Add(i);
/* Only shuffle when NOT in editor and not
* development build.
* Debugging could be easier when Ids are ordered. */
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
shuffledCache.Shuffle();
#endif
//Add shuffled to objectIdCache.
//Build Id cache.
int cacheCount = shuffledCache.Count;
for (int i = 0; i < cacheCount; i++)
_objectIdCache.Enqueue(shuffledCache[i]);
}
/// <summary>
/// Caches a NetworkObject ObjectId.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CacheObjectId(NetworkObject nob)
{
if (nob.ObjectId != NetworkObject.UNSET_OBJECTID_VALUE)
CacheObjectId(nob.ObjectId);
}
/// <summary>
/// Adds an ObjectId to objectId cache.
/// </summary>
/// <param name="id"></param>
internal void CacheObjectId(int id)
{
_objectIdCache.Enqueue(id);
}
/// <summary>
/// Gets the next ObjectId to use for NetworkObjects.
/// </summary>
/// <returns></returns>
protected internal override int GetNextNetworkObjectId(bool errorCheck = true)
{
//Either something went wrong or user actually managed to spawn ~64K networked objects.
if (_objectIdCache.Count == 0)
{
base.NetworkManager.LogError($"No more available ObjectIds. How the heck did you manage to have {ushort.MaxValue} objects spawned at once?");
return -1;
}
else
{
return _objectIdCache.Dequeue();
}
}
#endregion
#region Initializing Objects In Scenes.
/// <summary>
/// Called when a scene load starts.
/// </summary>
private void SceneManager_OnLoadStart(Scened.SceneLoadStartEventArgs obj)
{
_scenesLoading = true;
}
/// <summary>
/// Called after the active scene has been scene, immediately after scene loads.
/// </summary>
private void SceneManager_OnActiveSceneSet()
{
_scenesLoading = false;
IterateLoadedScenes(true);
}
/// <summary>
/// Iterates loaded scenes and sets them up.
/// </summary>
/// <param name="ignoreFrameRestriction">True to ignore the frame restriction when iterating.</param>
internal void IterateLoadedScenes(bool ignoreFrameRestriction)
{
//Not started, clear loaded scenes.
if (!NetworkManager.ServerManager.Started)
_loadedScenes.Clear();
for (int i = 0; i < _loadedScenes.Count; i++)
{
(int frame, Scene scene) value = _loadedScenes[i];
if (ignoreFrameRestriction || (Time.frameCount > value.frame))
{
SetupSceneObjects(value.scene);
_loadedScenes.RemoveAt(i);
i--;
}
}
}
/// <summary>
/// Called when a scene loads on the server.
/// </summary>
/// <param name="s"></param>
/// <param name="arg1"></param>
protected internal override void SceneManager_sceneLoaded(Scene s, LoadSceneMode arg1)
{
base.SceneManager_sceneLoaded(s, arg1);
if (!NetworkManager.ServerManager.Started)
return;
//Add to loaded scenes so that they are setup next frame.
_loadedScenes.Add((Time.frameCount, s));
}
/// <summary>
/// Setup all NetworkObjects in scenes. Should only be called when server is active.
/// </summary>
protected internal void SetupSceneObjects()
{
for (int i = 0; i < SceneManager.sceneCount; i++)
SetupSceneObjects(SceneManager.GetSceneAt(i));
Scene ddolScene = DDOLFinder.GetDDOL().gameObject.scene;
if (ddolScene.isLoaded)
SetupSceneObjects(ddolScene);
}
/// <summary>
/// Setup NetworkObjects in a scene. Should only be called when server is active.
/// </summary>
/// <param name="s"></param>
private void SetupSceneObjects(Scene s)
{
ListCache<NetworkObject> nobs;
SceneFN.GetSceneNetworkObjects(s, false, out nobs);
bool isHost = base.NetworkManager.IsHost;
for (int i = 0; i < nobs.Written; i++)
{
NetworkObject nob = nobs.Collection[i];
//Only setup if a scene object and not initialzied.
if (nob.IsNetworked && nob.IsSceneObject && nob.IsDeinitializing)
{
base.UpdateNetworkBehavioursForSceneObject(nob, true);
base.AddToSceneObjects(nob);
/* If was active in the editor (before hitting play), or currently active
* then PreInitialize without synchronizing to clients. There is no reason
* to synchronize to clients because the scene just loaded on server,
* which means clients are not yet in the scene. */
if (nob.ActiveDuringEdit || nob.gameObject.activeInHierarchy)
{
//If not host then object doesn't need to be spawned until a client joins.
if (!isHost)
SetupWithoutSynchronization(nob);
//Otherwise spawn object so observers update for clientHost.
else
SpawnWithoutChecks(nob);
}
}
}
ListCaches.StoreCache(nobs);
}
/// <summary>
/// Performs setup on a NetworkObject without synchronizing the actions to clients.
/// </summary>
/// <param name="objectId">Override ObjectId to use.</param>
private void SetupWithoutSynchronization(NetworkObject nob, NetworkConnection ownerConnection = null, int? objectId = null)
{
if (nob.IsNetworked)
{
if (objectId == null)
objectId = GetNextNetworkObjectId();
nob.Preinitialize_Internal(NetworkManager, objectId.Value, ownerConnection, true);
base.AddToSpawned(nob, true);
nob.gameObject.SetActive(true);
nob.Initialize(true, true);
}
}
#endregion
#region Spawning.
/// <summary>
/// Spawns an object over the network.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Spawn(NetworkObject networkObject, NetworkConnection ownerConnection = null)
{
//Default as false, will change if needed.
bool predictedSpawn = false;
if (networkObject == null)
{
base.NetworkManager.LogError($"Specified networkObject is null.");
return;
}
if (!NetworkManager.ServerManager.Started)
{
//Neither server nor client are started.
if (!NetworkManager.ClientManager.Started)
{
base.NetworkManager.LogWarning("Cannot spawn object because server nor client are active.");
return;
}
//Server has predicted spawning disabled.
if (!NetworkManager.PredictionManager.GetAllowPredictedSpawning())
{
base.NetworkManager.LogWarning("Cannot spawn object because server is not active and predicted spawning is not enabled.");
return;
}
//Various predicted spawn checks.
if (!base.CanPredictedSpawn(networkObject, NetworkManager.ClientManager.Connection, ownerConnection, false))
return;
predictedSpawn = true;
}
if (!networkObject.gameObject.scene.IsValid())
{
base.NetworkManager.LogError($"{networkObject.name} is a prefab. You must instantiate the prefab first, then use Spawn on the instantiated copy.");
return;
}
if (ownerConnection != null && ownerConnection.IsActive && !ownerConnection.LoadedStartScenes(!predictedSpawn))
{
base.NetworkManager.LogWarning($"{networkObject.name} was spawned but it's recommended to not spawn objects for connections until they have loaded start scenes. You can be notified when a connection loads start scenes by using connection.OnLoadedStartScenes on the connection, or SceneManager.OnClientLoadStartScenes.");
}
if (networkObject.IsSpawned)
{
base.NetworkManager.LogWarning($"{networkObject.name} is already spawned.");
return;
}
if (networkObject.ParentNetworkObject != null && !networkObject.ParentNetworkObject.IsSpawned)
{
base.NetworkManager.LogError($"{networkObject.name} cannot be spawned because it has a parent NetworkObject {networkObject.ParentNetworkObject} which is not spawned.");
return;
}
if (predictedSpawn)
base.NetworkManager.ClientManager.Objects.PredictedSpawn(networkObject, ownerConnection);
else
SpawnWithoutChecks(networkObject, ownerConnection);
}
/// <summary>
/// Spawns networkObject without any checks.
/// </summary>
private void SpawnWithoutChecks(NetworkObject networkObject, NetworkConnection ownerConnection = null, int? objectId = null)
{
/* Setup locally without sending to clients.
* When observers are built for the network object
* during initialization spawn messages will
* be sent. */
networkObject.SetIsNetworked(true);
_spawnCache.AddValue(networkObject);
SetupWithoutSynchronization(networkObject, ownerConnection, objectId);
foreach (NetworkObject item in networkObject.ChildNetworkObjects)
{
/* Only spawn recursively if the nob state is unset.
* Unset indicates that the nob has not been */
if (item.gameObject.activeInHierarchy || item.State == NetworkObjectState.Spawned)
SpawnWithoutChecks(item, ownerConnection);
}
/* Copy to a new cache then reset _spawnCache
* just incase rebuilding observers would lead to
* more additions into _spawnCache. EG: rebuilding
* may result in additional objects being spawned
* for clients and if _spawnCache were not reset
* the same objects would be rebuilt again. This likely
* would not affect anything other than perf but who
* wants that. */
ListCache<NetworkObject> spawnCacheCopy = ListCaches.GetNetworkObjectCache();
spawnCacheCopy.AddValues(_spawnCache);
_spawnCache.Reset();
//Also rebuild observers for the object so it spawns for others.
RebuildObservers(spawnCacheCopy);
/* If also client then we need to make sure the object renderers have correct visibility.
* Set visibility based on if the observers contains the clientHost connection. */
if (NetworkManager.IsClient)
{
int count = spawnCacheCopy.Written;
List<NetworkObject> collection = spawnCacheCopy.Collection;
for (int i = 0; i < count; i++)
collection[i].SetRenderersVisible(networkObject.Observers.Contains(NetworkManager.ClientManager.Connection));
}
ListCaches.StoreCache(spawnCacheCopy);
}
/// <summary>
/// Reads a predicted spawn.
/// </summary>
internal void ReadPredictedSpawn(PooledReader reader, NetworkConnection conn)
{
sbyte initializeOrder;
ushort collectionId;
int prefabId;
int objectId = reader.ReadNetworkObjectForSpawn(out initializeOrder, out collectionId, out _);
//If objectId is not within predicted ids for conn.
if (!conn.PredictedObjectIds.Contains(objectId))
{
reader.Clear();
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {conn.ClientId} used predicted spawning with a non-reserved objectId of {objectId}.");
return;
}
NetworkConnection owner = reader.ReadNetworkConnection();
SpawnType st = (SpawnType)reader.ReadByte();
//Not used at the moment.
byte componentIndex = reader.ReadByte();
//Read transform values which differ from serialized values.
Vector3? localPosition;
Quaternion? localRotation;
Vector3? localScale;
base.ReadTransformProperties(reader, out localPosition, out localRotation, out localScale);
NetworkObject nob;
bool isGlobal = false;
if (SpawnTypeEnum.Contains(st, SpawnType.Scene))
{
ulong sceneId = reader.ReadUInt64(AutoPackType.Unpacked);
nob = base.GetSceneNetworkObject(sceneId);
if (!base.CanPredictedSpawn(nob, conn, owner, true))
return;
}
else
{
//Not used right now.
SpawnParentType spt = (SpawnParentType)reader.ReadByte();
prefabId = reader.ReadNetworkObjectId();
//Invalid prefabId.
if (prefabId == NetworkObject.UNSET_PREFABID_VALUE)
{
reader.Clear();
conn.Kick(KickReason.UnusualActivity, LoggingType.Common, $"Spawned object has an invalid prefabId of {prefabId}. Make sure all objects which are being spawned over the network are within SpawnableObjects on the NetworkManager. Connection {conn.ClientId} will be kicked immediately.");
return;
}
PrefabObjects prefabObjects = NetworkManager.GetPrefabObjects<PrefabObjects>(collectionId, false);
//PrefabObjects not found.
if (prefabObjects == null)
{
reader.Clear();
conn.Kick(KickReason.UnusualActivity, LoggingType.Common, $"PrefabObjects collection is not found for CollectionId {collectionId}. Be sure to add your addressables NetworkObject prefabs to the collection on server and client before attempting to spawn them over the network. Connection {conn.ClientId} will be kicked immediately.");
return;
}
//Check if prefab allows predicted spawning.
NetworkObject nPrefab = prefabObjects.GetObject(true, prefabId);
if (!base.CanPredictedSpawn(nPrefab, conn, owner, true))
return;
nob = NetworkManager.GetPooledInstantiated(prefabId, false);
isGlobal = SpawnTypeEnum.Contains(st, SpawnType.InstantiatedGlobal);
}
Transform t = nob.transform;
//Parenting predicted spawns is not supported yet.
t.SetParent(null, true);
base.GetTransformProperties(localPosition, localRotation, localScale, t, out Vector3 pos, out Quaternion rot, out Vector3 scale);
t.SetLocalPositionRotationAndScale(pos, rot, scale);
nob.SetIsGlobal(isGlobal);
//Initialize for prediction.
nob.InitializePredictedObject_Server(base.NetworkManager, conn);
/* Only read sync types if allowed for the object.
* If the client did happen to send synctypes while not allowed
* this will create a parse error on the server,
* resulting in the client being kicked. */
if (nob.AllowPredictedSyncTypes)
{
ArraySegment<byte> syncValues = reader.ReadArraySegmentAndSize();
PooledReader syncTypeReader = ReaderPool.GetReader(syncValues, base.NetworkManager);
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
{
//SyncVars.
int length = syncTypeReader.ReadInt32();
nb.OnSyncType(syncTypeReader, length, false, true);
//SyncObjects
length = syncTypeReader.ReadInt32();
nb.OnSyncType(syncTypeReader, length, true, true);
}
syncTypeReader.Dispose();
}
SpawnWithoutChecks(nob, owner, objectId);
//Send the spawner a new reservedId.
WriteResponse(true);
//Writes a predicted spawn result to a client.
void WriteResponse(bool success)
{
PooledWriter writer = WriterPool.GetWriter();
writer.WritePacketId(PacketId.PredictedSpawnResult);
writer.WriteNetworkObjectId(nob.ObjectId);
writer.WriteBoolean(success);
if (success)
{
Queue<int> objectIdCache = NetworkManager.ServerManager.Objects.GetObjectIdCache();
//Write next objectId to use.
int invalidId = NetworkObject.UNSET_OBJECTID_VALUE;
int nextId = (objectIdCache.Count > 0) ? objectIdCache.Dequeue() : invalidId;
writer.WriteNetworkObjectId(nextId);
//If nextId is valid then also add it to spawners local cache.
if (nextId != invalidId)
conn.PredictedObjectIds.Enqueue(nextId);
////Update RPC links.
//foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
// nb.WriteRpcLinks(writer);
}
conn.SendToClient((byte)Channel.Reliable, writer.GetArraySegment());
}
}
#endregion
#region Despawning.
/// <summary>
/// Cleans recently despawned objects.
/// </summary>
private void CleanRecentlyDespawned()
{
//Only iterate if frame ticked to save perf.
if (!base.NetworkManager.TimeManager.FrameTicked)
return;
ListCache<int> intCache = ListCaches.GetIntCache();
uint requiredTicks = _cleanRecentlyDespawnedMaxTicks;
uint currentTick = base.NetworkManager.TimeManager.LocalTick;
//Iterate 20, or 5% of the collection, whichever is higher.
int iterations = Mathf.Max(20, (int)(RecentlyDespawnedIds.Count * 0.05f));
/* Given this is a dictionary there is no gaurantee which order objects are
* added. Because of this it's possible some objects may take much longer to
* be removed. This is okay so long as a consistent chunk of objects are removed
* at a time; eventually all objects will be iterated. */
int count = 0;
foreach (KeyValuePair<int, uint> kvp in RecentlyDespawnedIds)
{
long result = (currentTick - kvp.Value);
//If enough ticks have passed to remove.
if (result > requiredTicks)
intCache.AddValue(kvp.Key);
count++;
if (count == iterations)
break;
}
//Remove cached entries.
List<int> collection = intCache.Collection;
int cCount = collection.Count;
for (int i = 0; i < cCount; i++)
RecentlyDespawnedIds.Remove(collection[i]);
ListCaches.StoreCache(intCache);
}
/// <summary>
/// Returns if an objectId was recently despawned.
/// </summary>
/// <param name="objectId">ObjectId to check.</param>
/// <param name="ticks">Passed ticks to be within to be considered recently despawned.</param>
/// <returns>True if an objectId was despawned with specified number of ticks.</returns>
public bool RecentlyDespawned(int objectId, uint ticks)
{
uint despawnTick;
if (!RecentlyDespawnedIds.TryGetValue(objectId, out despawnTick))
return false;
return ((NetworkManager.TimeManager.LocalTick - despawnTick) <= ticks);
}
/// <summary>
/// Adds to objects pending destroy due to clientHost environment.
/// </summary>
/// <param name="nob"></param>
internal void AddToPending(NetworkObject nob)
{
_pendingDestroy[nob.ObjectId] = nob;
}
/// <summary>
/// Tries to removes objectId from PendingDestroy and returns if successful.
/// </summary>
internal bool RemoveFromPending(int objectId)
{
return _pendingDestroy.Remove(objectId);
}
/// <summary>
/// Returns a NetworkObject in PendingDestroy.
/// </summary>
internal NetworkObject GetFromPending(int objectId)
{
NetworkObject nob;
_pendingDestroy.TryGetValue(objectId, out nob);
return nob;
}
/// <summary>
/// Destroys NetworkObjects pending for destruction.
/// </summary>
internal void DestroyPending()
{
foreach (NetworkObject item in _pendingDestroy.Values)
{
if (item != null)
MonoBehaviour.Destroy(item.gameObject);
}
_pendingDestroy.Clear();
}
/// <summary>
/// Despawns an object over the network.
/// </summary>
internal override void Despawn(NetworkObject networkObject, DespawnType despawnType, bool asServer)
{
//Default as false, will change if needed.
bool predictedDespawn = false;
if (networkObject == null)
{
base.NetworkManager.LogWarning($"NetworkObject cannot be despawned because it is null.");
return;
}
if (networkObject.IsDeinitializing)
{
base.NetworkManager.LogWarning($"Object {networkObject.name} cannot be despawned because it is already deinitializing.");
return;
}
if (!NetworkManager.ServerManager.Started)
{
//Neither server nor client are started.
if (!NetworkManager.ClientManager.Started)
{
base.NetworkManager.LogWarning("Cannot despawn object because server nor client are active.");
return;
}
//Server has predicted spawning disabled.
if (!NetworkManager.PredictionManager.GetAllowPredictedSpawning())
{
base.NetworkManager.LogWarning("Cannot despawn object because server is not active and predicted spawning is not enabled.");
return;
}
//Various predicted despawn checks.
if (!base.CanPredictedDespawn(networkObject, NetworkManager.ClientManager.Connection, false))
return;
predictedDespawn = true;
}
if (!networkObject.gameObject.scene.IsValid())
{
base.NetworkManager.LogError($"{networkObject.name} is a prefab. You must instantiate the prefab first, then use Spawn on the instantiated copy.");
return;
}
if (predictedDespawn)
{
base.NetworkManager.ClientManager.Objects.PredictedDespawn(networkObject);
}
else
{
FinalizeDespawn(networkObject, despawnType);
RecentlyDespawnedIds[networkObject.ObjectId] = base.NetworkManager.TimeManager.LocalTick;
base.Despawn(networkObject, despawnType, asServer);
}
}
/// <summary>
/// Called when a NetworkObject is destroyed without being deactivated first.
/// </summary>
/// <param name="nob"></param>
internal override void NetworkObjectUnexpectedlyDestroyed(NetworkObject nob, bool asServer)
{
FinalizeDespawn(nob, DespawnType.Destroy);
base.NetworkObjectUnexpectedlyDestroyed(nob, asServer);
}
/// <summary>
/// Finalizes the despawn process. By the time this is called the object is considered unaccessible.
/// </summary>
/// <param name="nob"></param>
private void FinalizeDespawn(NetworkObject nob, DespawnType despawnType)
{
if (nob != null && nob.ObjectId != NetworkObject.UNSET_OBJECTID_VALUE)
{
nob.WriteDirtySyncTypes();
WriteDespawnAndSend(nob, despawnType);
CacheObjectId(nob);
}
}
/// <summary>
/// Writes a despawn and sends it to clients.
/// </summary>
/// <param name="nob"></param>
private void WriteDespawnAndSend(NetworkObject nob, DespawnType despawnType)
{
PooledWriter everyoneWriter = WriterPool.GetWriter();
WriteDespawn(nob, despawnType, everyoneWriter);
ArraySegment<byte> despawnSegment = everyoneWriter.GetArraySegment();
//Add observers to a list cache.
ListCache<NetworkConnection> cache = ListCaches.GetNetworkConnectionCache();
cache.Reset();
cache.AddValues(nob.Observers);
int written = cache.Written;
for (int i = 0; i < written; i++)
{
//Invoke ondespawn and send despawn.
NetworkConnection conn = cache.Collection[i];
nob.InvokeOnServerDespawn(conn);
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, despawnSegment, conn);
//Remove from observers.
//nob.Observers.Remove(conn);
}
everyoneWriter.Dispose();
ListCaches.StoreCache(cache);
}
/// <summary>
/// Reads a predicted despawn.
/// </summary>
internal void ReadPredictedDespawn(Reader reader, NetworkConnection conn)
{
NetworkObject nob = reader.ReadNetworkObject();
//Maybe server destroyed the object so don't kick if null.
if (nob == null)
{
reader.Clear();
return;
}
//Does not allow predicted despawning.
if (!nob.AllowPredictedDespawning)
{
reader.Clear();
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {conn.ClientId} used predicted despawning for object {nob.name} when it does not support predicted despawning.");
}
//Despawn object.
nob.Despawn();
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5e7f3005cbc7924f99819311c58651a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,457 @@
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 FishNet.Utility.Extension;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Managing.Server
{
public sealed partial class ServerManager : MonoBehaviour
{
#region Private.
/// <summary>
/// Delegate to read received broadcasts.
/// </summary>
/// <param name="connection"></param>
/// <param name="reader"></param>
private delegate void ClientBroadcastDelegate(NetworkConnection connection, PooledReader reader);
/// <summary>
/// Delegates for each key.
/// </summary>
private readonly Dictionary<ushort, HashSet<ClientBroadcastDelegate>> _broadcastHandlers = new Dictionary<ushort, HashSet<ClientBroadcastDelegate>>();
/// <summary>
/// Delegate targets for each key.
/// </summary>
private Dictionary<ushort, HashSet<(int, ClientBroadcastDelegate)>> _handlerTargets = new Dictionary<ushort, HashSet<(int, ClientBroadcastDelegate)>>();
/// <summary>
/// Connections which can be broadcasted to after having excluded removed.
/// </summary>
private HashSet<NetworkConnection> _connectionsWithoutExclusions = new HashSet<NetworkConnection>();
#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> handler, bool requireAuthentication = true) where T : struct, IBroadcast
{
ushort key = BroadcastHelper.GetKey<T>();
/* Create delegate and add for
* handler method. */
HashSet<ClientBroadcastDelegate> handlers;
if (!_broadcastHandlers.TryGetValueIL2CPP(key, out handlers))
{
handlers = new HashSet<ClientBroadcastDelegate>();
_broadcastHandlers.Add(key, handlers);
}
ClientBroadcastDelegate del = CreateBroadcastDelegate(handler, requireAuthentication);
handlers.Add(del);
/* Add hashcode of target for handler.
* This is so we can unregister the target later. */
int handlerHashCode = handler.GetHashCode();
HashSet<(int, ClientBroadcastDelegate)> targetHashCodes;
if (!_handlerTargets.TryGetValueIL2CPP(key, out targetHashCodes))
{
targetHashCodes = new HashSet<(int, ClientBroadcastDelegate)>();
_handlerTargets.Add(key, targetHashCodes);
}
targetHashCodes.Add((handlerHashCode, del));
}
/// <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> handler) where T : struct, IBroadcast
{
ushort key = BroadcastHelper.GetKey<T>();
/* If key is found for T then look for
* the appropriate handler to remove. */
if (_broadcastHandlers.TryGetValueIL2CPP(key, out HashSet<ClientBroadcastDelegate> handlers))
{
HashSet<(int, ClientBroadcastDelegate)> targetHashCodes;
if (_handlerTargets.TryGetValueIL2CPP(key, out targetHashCodes))
{
int handlerHashCode = handler.GetHashCode();
ClientBroadcastDelegate result = null;
foreach ((int targetHashCode, ClientBroadcastDelegate del) in targetHashCodes)
{
if (targetHashCode == handlerHashCode)
{
result = del;
targetHashCodes.Remove((targetHashCode, del));
break;
}
}
//If no more in targetHashCodes then remove from handlerTarget.
if (targetHashCodes.Count == 0)
_handlerTargets.Remove(key);
if (result != null)
handlers.Remove(result);
}
//If no more in handlers then remove broadcastHandlers.
if (handlers.Count == 0)
_broadcastHandlers.Remove(key);
}
}
/// <summary>
/// Creates a ClientBroadcastDelegate.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="handler"></param>
/// <param name="requireAuthentication"></param>
/// <returns></returns>
private ClientBroadcastDelegate CreateBroadcastDelegate<T>(Action<NetworkConnection, T> handler, bool requireAuthentication)
{
void LogicContainer(NetworkConnection connection, PooledReader reader)
{
//If requires authentication and client isn't authenticated.
if (requireAuthentication && !connection.Authenticated)
{
connection.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {connection.ClientId} sent broadcast {typeof(T).Name} which requires authentication, but client was not authenticated. Client has been disconnected.");
return;
}
T broadcast = reader.Read<T>();
handler?.Invoke(connection, broadcast);
}
return LogicContainer;
}
/// <summary>
/// Parses a received broadcast.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ParseBroadcast(PooledReader reader, NetworkConnection conn, Channel channel)
{
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 HashSet<ClientBroadcastDelegate> handlers))
{
int readerStartPosition = reader.Position;
/* //muchlater resetting the position could be better by instead reading once and passing in
* the object to invoke with. */
bool rebuildHandlers = false;
//True if data is read at least once. Otherwise it's length will have to be purged.
bool dataRead = false;
foreach (ClientBroadcastDelegate handler in handlers)
{
if (handler.Target == null)
{
NetworkManager.LogWarning($"A Broadcast handler target is null. This can occur when a script is destroyed but does not unregister from a Broadcast.");
rebuildHandlers = true;
}
else
{
reader.Position = readerStartPosition;
handler.Invoke(conn, reader);
dataRead = true;
}
}
//If rebuilding handlers...
if (rebuildHandlers)
{
List<ClientBroadcastDelegate> dels = handlers.ToList();
handlers.Clear();
for (int i = 0; i < dels.Count; i++)
{
if (dels[i].Target != null)
handlers.Add(dels[i]);
}
}
//Make sure data was read as well.
if (!dataRead)
reader.Skip(dataLength);
}
else
{
reader.Skip(dataLength);
}
}
/// <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.Authenticated)
{
NetworkManager.LogWarning($"Cannot send broadcast to client because they are not authenticated.");
return;
}
using (PooledWriter writer = WriterPool.GetWriter())
{
Broadcasts.WriteBroadcast<T>(writer, message, channel);
ArraySegment<byte> segment = writer.GetArraySegment();
NetworkManager.TransportManager.SendToClient((byte)channel, segment, connection);
}
}
/// <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;
using (PooledWriter writer = WriterPool.GetWriter())
{
Broadcasts.WriteBroadcast<T>(writer, message, channel);
ArraySegment<byte> segment = writer.GetArraySegment();
foreach (NetworkConnection conn in connections)
{
if (requireAuthenticated && !conn.Authenticated)
failedAuthentication = true;
else
NetworkManager.TransportManager.SendToClient((byte)channel, segment, conn);
}
}
if (failedAuthentication)
{
NetworkManager.LogWarning($"One or more broadcast did not send to a client because they were not authenticated.");
return;
}
}
/// <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;
}
_connectionsWithoutExclusions.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)
_connectionsWithoutExclusions.Add(c);
//Remove
_connectionsWithoutExclusions.Remove(excludedConnection);
Broadcast(_connectionsWithoutExclusions, 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;
}
_connectionsWithoutExclusions.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)
_connectionsWithoutExclusions.Add(c);
//Remove
foreach (NetworkConnection c in excludedConnections)
_connectionsWithoutExclusions.Remove(c);
Broadcast(_connectionsWithoutExclusions, 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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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;
using (PooledWriter writer = WriterPool.GetWriter())
{
Broadcasts.WriteBroadcast<T>(writer, message, channel);
ArraySegment<byte> segment = writer.GetArraySegment();
foreach (NetworkConnection conn in Clients.Values)
{
//
if (requireAuthenticated && !conn.Authenticated)
failedAuthentication = true;
else
NetworkManager.TransportManager.SendToClient((byte)channel, segment, conn);
}
}
if (failedAuthentication)
{
NetworkManager.LogWarning($"One or more broadcast did not send to a client because they were not authenticated.");
return;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1cd9dcd58556e27449ce5cb0d70611cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,231 @@
using FishNet.Connection;
using FishNet.Managing.Logging;
using FishNet.Object;
using FishNet.Serializing;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Managing.Server
{
public sealed partial class ServerManager : MonoBehaviour
{
#region Private.
/// <summary>
/// Cached expected level of detail value.
/// </summary>
private uint _cachedLevelOfDetailInterval;
/// <summary>
/// Cached value of UseLod.
/// </summary>
private bool _cachedUseLod;
#endregion
/// <summary>
/// Parses a received network LOD update.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ParseNetworkLODUpdate(PooledReader reader, NetworkConnection conn)
{
if (!conn.Authenticated)
return;
if (!NetworkManager.ObserverManager.GetUseNetworkLod())
{
conn.Kick(reader, KickReason.ExploitAttempt, LoggingType.Common, $"Connection [{conn.ClientId}] sent a level of detail update when the feature is not enabled.");
return;
}
/* If local client then read out entries but do nothing.
* Local client doesn't technically have to send LOD because
* it's set on the client side but this code is kept in
* to simulate actual bandwidth. */
if (conn.IsLocalClient)
{
int w = reader.ReadInt32();
for (int i = 0; i < w; i++)
ReadLod(out _, out _);
return;
}
uint packetTick = conn.LastPacketTick;
//Check if conn can send LOD.
uint lastLod = conn.LastLevelOfDetailUpdate;
//If previously set see if client is potentially exploiting.
if (lastLod != 0)
{
if ((packetTick - lastLod) < _cachedLevelOfDetailInterval)
{
conn.Kick(reader, KickReason.ExploitAttempt, LoggingType.Common, $"Connection [{conn.ClientId}] sent excessive level of detail updates.");
return;
}
}
//Set last recv lod.
conn.LastLevelOfDetailUpdate = packetTick;
//Get server objects to save calls.
Dictionary<int, NetworkObject> serverObjects = Objects.Spawned;
//Get level of details for this connection and reset them.
Dictionary<NetworkObject, byte> levelOfDetails = conn.LevelOfDetails;
int written = reader.ReadInt32();
/* //TODO There is still an instance where client could simply say no LODs need
* updating and never update for their objects in the first place. This can be resolved
* by adding an observed object count to each connection and compare that to
* the size of the LOD collection. */
//Only process if some are written.
if (written > 0)
{
//Maximum infractions before a kick.
const int maximumInfractions = 15;
int currentInfractions = conn.LevelOfDetailInfractions;
int infractionsCounted = 0;
/* If the connection has no objects then LOD isn't capable
* of being calculated. It's possible the players object was destroyed after
* the LOD sent but we don't know for sure without adding extra checks.
* Rather than add recently destroyed player object checks if there are
* no player objects then just add an infraction. The odds of this happening regularly
* are pretty slim. */
if (conn.Objects.Count == 0)
{
if (AddInfraction(3))
{
conn.Kick(reader, KickReason.UnusualActivity, LoggingType.Common, $"Connection [{conn.ClientId}] has sent an excessive number of level of detail updates without having any player objects spawned.");
return;
}
}
/* If written is more than spawned + recently despawned then
* the client is likely trying to exploit. */
if (written > (Objects.Spawned.Count + Objects.RecentlyDespawnedIds.Count))
{
conn.Kick(reader, KickReason.UnusualActivity, LoggingType.Common, $"Connection [{conn.ClientId}] sent a level of detail update for {written} items which exceeds spawned and recently despawned count.");
return;
}
Vector3 connObjectPosition = Vector3.zero;
//Pick a random object from the player to sample.
int objectIndex = UnityEngine.Random.Range(0, conn.Objects.Count);
int connObjectIteration = 0;
foreach (NetworkObject n in conn.Objects)
{
if (connObjectIteration == objectIndex)
{
connObjectPosition = n.transform.position;
//Flag to indicate found.
objectIndex = -1;
break;
}
}
//Server somehow messed up. Should not be possible.
if (objectIndex != -1)
{
NetworkManager.LogError($"An object index of {objectIndex} could not be populated. Connection [{conn.ClientId}] object count is {conn.Objects.Count}.");
return;
}
//Sample at most x entries per update.
int samplesRemaining = 10;
//Chance to sample an update.
const float sampleChance = 0.05f;
List<float> lodDistances = NetworkManager.ObserverManager.GetLevelOfDetailDistances();
int lodDistancesCount = lodDistances.Count;
for (int i = 0; i < written; i++)
{
int objectId;
byte lod;
ReadLod(out objectId, out lod);
//Lod is not possible.
if (lod >= lodDistancesCount)
{
conn.Kick(reader, KickReason.ExploitAttempt, LoggingType.Common, $"Connection [{conn.ClientId}] provided a level of detail index which is out of bounds.");
return;
}
//Found in spawned, update lod.
if (serverObjects.TryGetValue(objectId, out NetworkObject nob))
{
//Value is unchanged.
if (levelOfDetails.TryGetValue(nob, out byte oldLod))
{
bool oldMatches = (oldLod == lod);
if (oldMatches && AddInfraction())
{
conn.Kick(reader, KickReason.UnusualActivity, LoggingType.Common, $"Connection [{conn.ClientId}] has excessively sent unchanged LOD information.");
return;
}
}
//If to sample.
if (samplesRemaining > 0 && UnityEngine.Random.Range(0f, 1f) <= sampleChance)
{
samplesRemaining--;
/* Only check if lod is less than maximum.
* If the client is hacking lods to specify maximum
* they are only doing the server a favor and hurting
* themselves with slower updates. */
if (lod < (lodDistancesCount - 1))
{
float specifiedLodDistance = lodDistances[lod];
float sqrMag = Vector3.SqrMagnitude(connObjectPosition - nob.transform.position);
/* If the found distance is actually larger than what client specified
* then it's possible client may be sending fake LODs. */
if (sqrMag > specifiedLodDistance)
{
if (AddInfraction())
{
conn.Kick(reader, KickReason.UnusualActivity, LoggingType.Common, $"Connection [{conn.ClientId}] provided an excessive number of incorrect LOD values.");
return;
}
}
}
}
levelOfDetails[nob] = lod;
}
//Not found in spawn; validate that client isn't trying to exploit.
else
{
//Too many infractions.
if (AddInfraction())
{
conn.Kick(reader, KickReason.UnusualActivity, LoggingType.Common, $"Connection [{conn.ClientId}] has accumulated excessive level of detail infractions.");
return;
}
}
}
//Adds an infraction returning if maximum infractions have been exceeded.
bool AddInfraction(int count = 1)
{
/* Only increase infractions at most 3 per iteration.
* This is to prevent a kick if the client perhaps had
* a massive lag spike. */
if (infractionsCounted < 3)
infractionsCounted += count;
bool overLimit = ((currentInfractions + infractionsCounted) >= maximumInfractions);
return overLimit;
}
}
//Reads a LOD.
void ReadLod(out int lObjectId, out byte lLod)
{
lObjectId = reader.ReadNetworkObjectId();
lLod = reader.ReadByte();
}
//Remove an infraction. This will steadily remove infractions over time.
if (conn.LevelOfDetailInfractions > 0)
conn.LevelOfDetailInfractions--;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e472255008ae291498f55f5f2a704ab2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,199 @@
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.Runtime.CompilerServices;
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>
/// Returns true if only one server is started.
/// </summary>
/// <returns></returns>
public bool OneServerStarted()
{
int startedCount = 0;
TransportManager tm = NetworkManager.TransportManager;
//If using multipass check all transports.
if (tm.Transport is Multipass mp)
{
foreach (Transport t in mp.Transports)
{
//Another transport is started, no need to load start scenes again.
if (t.GetConnectionState(true) == LocalConnectionState.Started)
startedCount++;
}
}
//Not using multipass.
else
{
if (tm.Transport.GetConnectionState(true) == LocalConnectionState.Started)
startedCount = 1;
}
return (startedCount == 1);
}
/// <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>
/// <returns></returns>
public bool AnyServerStarted(int? excludedIndex = null)
{
TransportManager tm = NetworkManager.TransportManager;
//If using multipass check all transports.
if (tm.Transport is Multipass mp)
{
//Get transport which had state changed.
Transport excludedTransport = (excludedIndex == null) ? null : mp.GetTransport(excludedIndex.Value);
foreach (Transport t in mp.Transports)
{
/* Skip t if is the transport that had it's state changed.
* We are looking for other transports already in started. */
if (t == excludedTransport)
continue;
//Another transport is started, no need to load start scenes again.
if (t.GetConnectionState(true) == LocalConnectionState.Started)
return true;
}
}
//Not using multipass.
else
{
return (tm.Transport.GetConnectionState(true) == LocalConnectionState.Started);
}
//Fall through, none started.
return false;
}
/// <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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Spawn(GameObject go, NetworkConnection ownerConnection = null)
{
if (go == null)
{
NetworkManager.LogWarning($"GameObject cannot be spawned because it is null.");
return;
}
NetworkObject nob = go.GetComponent<NetworkObject>();
Spawn(nob, ownerConnection);
}
/// <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)
{
Objects.Spawn(nob, ownerConnection);
}
/// <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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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 == null)
? networkObject.GetDefaultDespawnType()
: despawnType.Value;
Objects.Despawn(networkObject, resolvedDespawnType, 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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f2a07d9984be21648bc714ea03bd0d70
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,78 @@
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 Dictionary<ushort, RpcLink>();
/// <summary>
/// RPCLink indexes which can be used.
/// </summary>
private Queue<ushort> _availableRpcLinkIndexes = new Queue<ushort>();
#endregion
/// <summary>
/// Initializes RPC Links for NetworkBehaviours.
/// </summary>
private void InitializeRpcLinks()
{
/* Brute force enum values.
* Linq Last/Max lookup throws for IL2CPP. */
ushort highestValue = 0;
Array pidValues = Enum.GetValues(typeof(PacketId));
foreach (PacketId pid in pidValues)
highestValue = Math.Max(highestValue, (ushort)pid);
highestValue += 1;
for (ushort i = highestValue; i < ushort.MaxValue; 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.LinkIndex);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b9f0a4620d06f5c41b01f20af3f90634
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,759 @@
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.Transporting;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using FishNet.Utility.Extension;
using FishNet.Utility.Performance;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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 Dictionary<int, NetworkConnection>();
/// <summary>
/// NetworkManager for server.
/// </summary>
[HideInInspector]
public NetworkManager NetworkManager { get; private set; }
#endregion
#region Serialized.
/// <summary>
/// Authenticator for this ServerManager. May be null if not using authentication.
/// </summary>
[Obsolete("Use GetAuthenticator and SetAuthenticator.")] //Remove on 2023/06/01
public Authenticator Authenticator
{
get => GetAuthenticator();
set => SetAuthenticator(value);
}
/// <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>
/// Default send rate for SyncTypes. A value of 0f will send changed values every tick.
/// SyncTypeRate cannot yet be changed at runtime because this would require recalculating rates on SyncBase, which is not yet implemented.
/// </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 TransformPackingData()
{
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>
/// True to share the Ids of clients and the objects they own with other clients. No sensitive information is shared.
/// </summary>
internal 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;
/// <summary>
/// True to kick clients which send data larger than the MTU.
/// </summary>
internal bool LimitClientMTU => _limitClientMTU;
[Tooltip("True to kick clients which send data larger than the MTU.")]
[SerializeField]
private bool _limitClientMTU = true;
#endregion
#region Private.
/// <summary>
/// Used to read splits.
/// </summary>
private SplitReader _splitReader = new SplitReader();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
/// <summary>
/// Logs data about parser to help debug.
/// </summary>
private ParseLogger _parseLogger = new ParseLogger();
#endif
#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 ServerObjects(manager);
Objects.SubscribeToSceneLoaded(true);
InitializeRpcLinks();
//Unsubscribe first incase already subscribed.
SubscribeToTransport(false);
SubscribeToTransport(true);
NetworkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState;
NetworkManager.SceneManager.OnClientLoadedStartScenes += SceneManager_OnClientLoadedStartScenes;
if (_authenticator == null)
_authenticator = GetComponent<Authenticator>();
if (_authenticator != null)
InitializeAuthenticator();
_cachedLevelOfDetailInterval = NetworkManager.ClientManager.LevelOfDetailInterval;
_cachedUseLod = NetworkManager.ObserverManager.GetUseNetworkLod();
}
/// <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
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>
internal void SendDisconnectMessages(int[] connectionIds)
{
List<NetworkConnection> conns = new List<NetworkConnection>();
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 immediately iterates outgoing.
/// </summary>
private void SendDisconnectMessages(List<NetworkConnection> conns, bool iterate)
{
PooledWriter writer = WriterPool.GetWriter();
writer.WritePacketId(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.Dispose();
if (iterate)
NetworkManager.TransportManager.IterateOutgoing(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>
/// 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 = AnyServerStarted();
NetworkManager.ClientManager.Objects.OnServerConnectionState(args);
//If no servers are started then reset match conditions.
if (!Started)
{
MatchCondition.ClearMatchesWithoutRebuilding();
//Despawn without synchronizing network objects.
Objects.DespawnWithoutSynchronization(true);
}
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;
Debug.Log($"Local server is {state.ToString().ToLower()} for {tName}.");
}
NetworkManager.UpdateFramerate();
OnServerConnectionState?.Invoke(args);
}
/// <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;
int maxIdValue = short.MaxValue;
if (id < 0 || id > maxIdValue)
{
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 {maxIdValue}. 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 NetworkConnection(NetworkManager, id, true);
Clients.Add(args.ConnectionId, conn);
OnRemoteConnectionState?.Invoke(conn, args);
//Connection is no longer valid. This can occur if the user changes the state using the OnRemoteConnectionState event.
if (!conn.IsValid)
return;
/* If there is an authenticator
* and the transport is not a local transport. */
Authenticator auth = GetAuthenticator();
if (auth != null && !NetworkManager.TransportManager.IsLocalTransport(id))
auth.OnRemoteConnection(conn);
else
ClientAuthenticated(conn);
}
//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);
MatchCondition.RemoveFromMatchWithoutRebuild(conn, NetworkManager);
Objects.ClientDisconnected(conn);
BroadcastClientConnectionChange(false, conn);
//Return predictedObjectIds.
Queue<int> pqId = conn.PredictedObjectIds;
while (pqId.Count > 0)
Objects.CacheObjectId(pqId.Dequeue());
conn.Reset();
NetworkManager.Log($"Remote connection stopped for Id {id}.");
}
}
}
}
/// <summary>
/// Sends client their connectionId.
/// </summary>
/// <param name="connectionid"></param>
private void SendAuthenticated(NetworkConnection conn)
{
using (PooledWriter writer = WriterPool.GetWriter())
{
writer.WritePacketId(PacketId.Authenticated);
writer.WriteNetworkConnection(conn);
/* If predicted spawning is enabled then also send
* reserved objectIds. */
;
PredictionManager pm = NetworkManager.PredictionManager;
if (pm.GetAllowPredictedSpawning())
{
int count = Mathf.Min(Objects.GetObjectIdCache().Count, pm.GetReservedObjectIds());
writer.WriteByte((byte)count);
for (int i = 0; i < count; i++)
{
ushort val = (ushort)Objects.GetNextNetworkObjectId(false);
writer.WriteNetworkObjectId(val);
conn.PredictedObjectIds.Enqueue(val);
}
}
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, writer.GetArraySegment(), conn);
}
}
/// <summary>
/// Called when the server socket receives data.
/// </summary>
private void Transport_OnServerReceivedData(ServerReceivedDataArgs args)
{
args.Data = NetworkManager.TransportManager.ProcessIntermediateIncoming(args.Data, false);
ParseReceived(args);
}
/// <summary>
/// Called when the server receives data.
/// </summary>
/// <param name="args"></param>
private void ParseReceived(ServerReceivedDataArgs args)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
_parseLogger.Reset();
#endif
//Not from a valid connection.
if (args.ConnectionId < 0)
return;
ArraySegment<byte> segment = args.Data;
NetworkManager.StatisticsManager.NetworkTraffic.LocalServerReceivedData((ulong)segment.Count);
if (segment.Count <= TransportManager.TICK_BYTES)
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 && !NetworkManager.TransportManager.IsLocalTransport(args.ConnectionId))
{
ExceededMTUKick();
return;
}
PacketId packetId = PacketId.Unset;
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
try
{
#endif
using (PooledReader reader = ReaderPool.GetReader(segment, NetworkManager))
{
uint tick = reader.ReadUInt32(AutoPackType.Unpacked);
NetworkManager.TimeManager.LastPacketTick = 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)
{
//Skip packetId.
reader.ReadPacketId();
int expectedMessages;
_splitReader.GetHeader(reader, out expectedMessages);
//If here split message can be written.
_splitReader.Write(NetworkManager.TimeManager.LastPacketTick, 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. */
//Initialize reader with full message.
reader.Initialize(fullMessage, NetworkManager);
}
//Parse reader.
while (reader.Remaining > 0)
{
packetId = reader.ReadPacketId();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
_parseLogger.AddPacket(packetId);
#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 {conn.ClientId} not found within Clients. Connection will be kicked immediately.");
return;
}
conn.SetLastPacketTick(tick);
/* 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.Authenticated && packetId != PacketId.Broadcast)
{
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a Broadcast without being authenticated. Connection will be kicked immediately.");
return;
}
//Only check if not developer build because users pay pause editor.
#if !DEVELOPMENT_BUILD && !UNITY_EDITOR
/* If hasn't sent LOD recently enough. LODs are sent every half a second, so
* by multiplaying interval by 60 this gives the client a 30 second window. */
if (_cachedUseLod && conn.IsLateForLevelOfDetail(_cachedLevelOfDetailInterval * 60))
{
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} has gone too long without sending a level of detail update. Connection will be kicked immediately.");
return;
}
#endif
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 (!NetworkManager.PredictionManager.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.ReadPredictedSpawn(reader, conn);
}
else if (packetId == PacketId.ObjectDespawn)
{
if (!NetworkManager.PredictionManager.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.ReadPredictedDespawn(reader, conn);
}
else if (packetId == PacketId.NetworkLODUpdate)
{
ParseNetworkLODUpdate(reader, conn);
}
else if (packetId == PacketId.Broadcast)
{
ParseBroadcast(reader, conn, args.Channel);
}
else if (packetId == PacketId.PingPong)
{
ParsePingPong(reader, conn);
}
else
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
NetworkManager.LogError($"Server received an unhandled PacketId of {(ushort)packetId} from connectionId {args.ConnectionId}. Remaining data has been purged.");
_parseLogger.Print(NetworkManager);
#else
NetworkManager.LogError($"Server received an unhandled PacketId of {(ushort)packetId} from connectionId {args.ConnectionId}. Connection will be kicked immediately.");
NetworkManager.TransportManager.Transport.StopConnection(args.ConnectionId, true);
#endif
return;
}
}
}
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
}
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.Message}.");
}
#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)
{
/* //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.ReadUInt32(AutoPackType.Unpacked);
if (conn.CanPingPong())
NetworkManager.TimeManager.SendPong(conn, clientTick);
}
/// <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)
{
//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 ClientConnectionChangeBroadcast()
{
Connected = connected,
Id = conn.ClientId
};
Broadcast(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.
ListCache<int> lc = ListCaches.GetIntCache();
foreach (int key in Clients.Keys)
lc.AddValue(key);
ConnectedClientsBroadcast allMsg = new ConnectedClientsBroadcast()
{
ListCache = lc
};
conn.Broadcast(allMsg);
ListCaches.StoreCache(lc);
}
}
//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 ClientConnectionChangeBroadcast()
{
Connected = connected,
Id = conn.ClientId
};
Broadcast(conn, changeMsg, true, Channel.Reliable);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 68828c85278210948b9d50a8db3aab74
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant: