Add StickGame Assets
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Utility.Performance;
|
||||
using GameKit.Utilities;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishNet.Managing.Server
|
||||
{
|
||||
public struct ClientConnectionChangeBroadcast : IBroadcast
|
||||
{
|
||||
public bool Connected;
|
||||
public int Id;
|
||||
}
|
||||
|
||||
public struct ConnectedClientsBroadcast : IBroadcast
|
||||
{
|
||||
public List<int> Values;
|
||||
}
|
||||
|
||||
internal static class ConnectedClientsBroadcastSerializers
|
||||
{
|
||||
public static void WriteConnectedClientsBroadcast(this PooledWriter writer, ConnectedClientsBroadcast value)
|
||||
{
|
||||
writer.WriteList(value.Values);
|
||||
}
|
||||
|
||||
public static ConnectedClientsBroadcast ReadConnectedClientsBroadcast(this PooledReader reader)
|
||||
{
|
||||
List<int> cache = CollectionCaches<int>.RetrieveList();
|
||||
reader.ReadList(ref cache);
|
||||
return new ConnectedClientsBroadcast()
|
||||
{
|
||||
Values = cache
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a16b83a545be8f8488795783a0fc8648
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c150c74713eeaeb47a387a0f34529ad4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Server.Editing
|
||||
{
|
||||
|
||||
|
||||
[CustomEditor(typeof(ServerManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class ServerManagerEditor : Editor
|
||||
{
|
||||
private SerializedProperty _authenticator;
|
||||
private SerializedProperty _remoteClientTimeout;
|
||||
private SerializedProperty _remoteClientTimeoutDuration;
|
||||
private SerializedProperty _syncTypeRate;
|
||||
private SerializedProperty SpawnPacking;
|
||||
private SerializedProperty _changeFrameRate;
|
||||
private SerializedProperty _frameRate;
|
||||
private SerializedProperty _shareIds;
|
||||
private SerializedProperty _startOnHeadless;
|
||||
private SerializedProperty _limitClientMTU;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_authenticator = serializedObject.FindProperty(nameof(_authenticator));
|
||||
_remoteClientTimeout = serializedObject.FindProperty(nameof(_remoteClientTimeout));
|
||||
_remoteClientTimeoutDuration = serializedObject.FindProperty(nameof(_remoteClientTimeoutDuration));
|
||||
_syncTypeRate = serializedObject.FindProperty(nameof(_syncTypeRate));
|
||||
SpawnPacking = serializedObject.FindProperty(nameof(SpawnPacking));
|
||||
_changeFrameRate = serializedObject.FindProperty(nameof(_changeFrameRate));
|
||||
_frameRate = serializedObject.FindProperty(nameof(_frameRate));
|
||||
_shareIds = serializedObject.FindProperty(nameof(_shareIds));
|
||||
_startOnHeadless = serializedObject.FindProperty(nameof(_startOnHeadless));
|
||||
_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(_remoteClientTimeout);
|
||||
if ((RemoteTimeoutType)_remoteClientTimeout.intValue != RemoteTimeoutType.Disabled)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_remoteClientTimeoutDuration,new GUIContent("Timeout"));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da6ea97e6b868974e8ac139fe545e986
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9289a823878c5d1408e7106e6ed5d866
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 624750768480fa840b40449a42006488
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,562 @@
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Object;
|
||||
using FishNet.Observing;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility;
|
||||
using FishNet.Utility.Performance;
|
||||
using GameKit.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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;
|
||||
/// <summary>
|
||||
/// Used to write spawns for everyone. This writer will exclude owner only information.
|
||||
/// </summary>
|
||||
private PooledWriter _writer = new PooledWriter();
|
||||
#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 networkObserversCount = _timedNetworkObservers.Count;
|
||||
if (networkObserversCount == 0)
|
||||
return;
|
||||
|
||||
/* Try to iterate all timed observers every half a second.
|
||||
* This value will increase as there's more observers or timed conditions. */
|
||||
double timeMultiplier = 1d + (float)((base.NetworkManager.ServerManager.Clients.Count * 0.005d) + (_timedNetworkObservers.Count * 0.0005d));
|
||||
double completionTime = (0.5d * timeMultiplier);
|
||||
uint completionTicks = base.NetworkManager.TimeManager.TimeToTicks(completionTime, TickRounding.RoundUp);
|
||||
/* Iterations will be the number of objects
|
||||
* to iterate to be have completed all objects by
|
||||
* the end of completionTicks. */
|
||||
int iterations = Mathf.CeilToInt((float)networkObserversCount / (float)completionTicks);
|
||||
if (iterations > _timedNetworkObservers.Count)
|
||||
iterations = _timedNetworkObservers.Count;
|
||||
|
||||
List<NetworkConnection> connCache = RetrieveAuthenticatedConnections();
|
||||
//Build nob cache.
|
||||
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
if (_nextTimedObserversIndex >= _timedNetworkObservers.Count)
|
||||
_nextTimedObserversIndex = 0;
|
||||
nobCache.Add(_timedNetworkObservers[_nextTimedObserversIndex++]);
|
||||
}
|
||||
|
||||
RebuildObservers(nobCache, connCache, true);
|
||||
|
||||
CollectionCaches<NetworkConnection>.Store(connCache);
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a networkObserver component should be updated regularly. This is done automatically.
|
||||
/// </summary>
|
||||
/// <param name="networkObject">NetworkObject to be updated.</param>
|
||||
public void AddTimedNetworkObserver(NetworkObject networkObject)
|
||||
{
|
||||
_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>
|
||||
/// Gets all NetworkConnections which are authenticated.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private List<NetworkConnection> RetrieveAuthenticatedConnections()
|
||||
{
|
||||
List<NetworkConnection> cache = CollectionCaches<NetworkConnection>.RetrieveList();
|
||||
foreach (NetworkConnection item in NetworkManager.ServerManager.Clients.Values)
|
||||
{
|
||||
if (item.Authenticated)
|
||||
cache.Add(item);
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all spawned objects with root objects first.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private List<NetworkObject> RetrieveOrderedSpawnedObjects()
|
||||
{
|
||||
List<NetworkObject> cache = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
|
||||
bool initializationOrderChanged = false;
|
||||
//First order root objects.
|
||||
foreach (NetworkObject item in Spawned.Values)
|
||||
OrderRootByInitializationOrder(item, cache, ref initializationOrderChanged);
|
||||
|
||||
OrderNestedByInitializationOrder(cache);
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orders a NetworkObject into a cache based on it's initialization order.
|
||||
/// Only non-nested NetworkObjects will be added.
|
||||
/// </summary>
|
||||
/// <param name="nob">NetworkObject to check.</param>
|
||||
/// <param name="cache">Cache to sort into.</param>
|
||||
/// <param name="initializationOrderChanged">Boolean to indicate if initialization order is specified for one or more objects.</param>
|
||||
private void OrderRootByInitializationOrder(NetworkObject nob, List<NetworkObject> cache, ref bool initializationOrderChanged)
|
||||
{
|
||||
if (nob.IsNested)
|
||||
return;
|
||||
|
||||
sbyte currentItemInitOrder = nob.GetInitializeOrder();
|
||||
initializationOrderChanged |= (currentItemInitOrder != 0);
|
||||
int count = cache.Count;
|
||||
|
||||
/* If initialize order has not changed or count is
|
||||
* 0 then add normally. */
|
||||
if (!initializationOrderChanged || count == 0)
|
||||
{
|
||||
cache.Add(nob);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If current item init order is larger or equal than
|
||||
* the last entry in copy then add to the end.
|
||||
* Otherwise check where to add from the beginning. */
|
||||
if (currentItemInitOrder >= cache[count - 1].GetInitializeOrder())
|
||||
{
|
||||
cache.Add(nob);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
/* If item being sorted is lower than the one in already added.
|
||||
* then insert it before the one already added. */
|
||||
if (currentItemInitOrder <= cache[i].GetInitializeOrder())
|
||||
{
|
||||
cache.Insert(i, nob);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Orders nested NetworkObjects of cache by initialization order.
|
||||
/// </summary>
|
||||
/// <param name="cache">Cache to sort.</param>
|
||||
private void OrderNestedByInitializationOrder(List<NetworkObject> cache)
|
||||
{
|
||||
//After everything is sorted by root only insert children.
|
||||
for (int i = 0; i < cache.Count; i++)
|
||||
{
|
||||
NetworkObject nob = cache[i];
|
||||
//Skip root.
|
||||
if (nob.IsNested)
|
||||
continue;
|
||||
|
||||
int startingIndex = i;
|
||||
AddChildNetworkObjects(nob, ref startingIndex);
|
||||
}
|
||||
|
||||
void AddChildNetworkObjects(NetworkObject n, ref int index)
|
||||
{
|
||||
foreach (NetworkObject childObject in n.ChildNetworkObjects)
|
||||
{
|
||||
cache.Insert(++index, childObject);
|
||||
AddChildNetworkObjects(childObject, ref index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes a connection from observers without synchronizing changes.
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
private void RemoveFromObserversWithoutSynchronization(NetworkConnection connection)
|
||||
{
|
||||
List<NetworkObject> observerChangedObjectsCache = _observerChangedObjectsCache;
|
||||
foreach (NetworkObject nob in Spawned.Values)
|
||||
{
|
||||
if (nob.RemoveObserver(connection))
|
||||
observerChangedObjectsCache.Add(nob);
|
||||
}
|
||||
|
||||
//Invoke despawn callbacks on nobs.
|
||||
for (int i = 0; i < observerChangedObjectsCache.Count; i++)
|
||||
observerChangedObjectsCache[i].InvokeOnServerDespawn(connection);
|
||||
observerChangedObjectsCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all NetworkObjects for all connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = RetrieveOrderedSpawnedObjects();
|
||||
List<NetworkConnection> connCache = RetrieveAuthenticatedConnections();
|
||||
|
||||
RebuildObservers(nobCache, connCache, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
CollectionCaches<NetworkConnection>.Store(connCache);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for all connections for a NetworkObject.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(NetworkObject nob, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList(nob);
|
||||
List<NetworkConnection> connCache = RetrieveAuthenticatedConnections();
|
||||
|
||||
RebuildObservers(nobCache, connCache, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
CollectionCaches<NetworkConnection>.Store(connCache);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all NetworkObjects for a connection.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(NetworkConnection connection, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = RetrieveOrderedSpawnedObjects();
|
||||
List<NetworkConnection> connCache = CollectionCaches<NetworkConnection>.RetrieveList(connection);
|
||||
|
||||
RebuildObservers(nobCache, connCache, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
CollectionCaches<NetworkConnection>.Store(connCache);
|
||||
}
|
||||
|
||||
#region Obsolete RebuildObservers.
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use RebuildObservers IList variant instead.")]
|
||||
public void RebuildObservers(IEnumerable<NetworkObject> nobs, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkConnection> conns = RetrieveAuthenticatedConnections();
|
||||
|
||||
RebuildObservers(nobs, conns, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkConnection>.Store(conns);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all objects for connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use RebuildObservers IList variant instead.")]
|
||||
public void RebuildObservers(IEnumerable<NetworkConnection> connections, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = RetrieveOrderedSpawnedObjects();
|
||||
|
||||
RebuildObservers(nobCache, connections, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects for connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use RebuildObservers IList variant instead.")]
|
||||
public void RebuildObservers(IEnumerable<NetworkObject> nobs, NetworkConnection conn, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkConnection> connCache = CollectionCaches<NetworkConnection>.RetrieveList(conn);
|
||||
|
||||
RebuildObservers(nobs, connCache, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkConnection>.Store(connCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for connections on NetworkObject.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use RebuildObservers IList variant instead.")]
|
||||
public void RebuildObservers(NetworkObject networkObject, IEnumerable<NetworkConnection> connections, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList(networkObject);
|
||||
|
||||
RebuildObservers(nobCache, connections, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects for connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Obsolete("Use RebuildObservers IList variant instead.")]
|
||||
public void RebuildObservers(IEnumerable<NetworkObject> nobs, IEnumerable<NetworkConnection> conns, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
|
||||
foreach (NetworkConnection nc in conns)
|
||||
{
|
||||
_writer.Reset();
|
||||
nobCache.Clear();
|
||||
|
||||
foreach (NetworkObject nob in nobs)
|
||||
RebuildObservers(nob, nc, nobCache, timedOnly);
|
||||
|
||||
//Send if change.
|
||||
if (_writer.Length > 0)
|
||||
{
|
||||
NetworkManager.TransportManager.SendToClient(
|
||||
(byte)Channel.Reliable, _writer.GetArraySegment(), nc);
|
||||
|
||||
foreach (NetworkObject n in nobCache)
|
||||
n.OnSpawnServer(nc);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(IList<NetworkObject> nobs, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkConnection> conns = RetrieveAuthenticatedConnections();
|
||||
|
||||
RebuildObservers(nobs, conns, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkConnection>.Store(conns);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all objects for connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(IList<NetworkConnection> connections, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = RetrieveOrderedSpawnedObjects();
|
||||
|
||||
RebuildObservers(nobCache, connections, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects for connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(IList<NetworkObject> nobs, NetworkConnection conn, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkConnection> connCache = CollectionCaches<NetworkConnection>.RetrieveList(conn);
|
||||
|
||||
RebuildObservers(nobs, connCache, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkConnection>.Store(connCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for connections on NetworkObject.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(NetworkObject networkObject, IList<NetworkConnection> connections, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList(networkObject);
|
||||
|
||||
RebuildObservers(nobCache, connections, timedOnly);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects for connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(IList<NetworkObject> nobs, IList<NetworkConnection> conns, bool timedOnly = false)
|
||||
{
|
||||
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
NetworkConnection nc;
|
||||
|
||||
int connsCount = conns.Count;
|
||||
for (int i = 0; i < connsCount; i++)
|
||||
{
|
||||
_writer.Reset();
|
||||
nobCache.Clear();
|
||||
|
||||
nc = conns[i];
|
||||
int nobsCount = nobs.Count;
|
||||
for (int z = 0; z < nobsCount; z++)
|
||||
RebuildObservers(nobs[z], nc, nobCache, timedOnly);
|
||||
|
||||
//Send if change.
|
||||
if (_writer.Length > 0)
|
||||
{
|
||||
NetworkManager.TransportManager.SendToClient(
|
||||
(byte)Channel.Reliable, _writer.GetArraySegment(), nc);
|
||||
|
||||
foreach (NetworkObject n in nobCache)
|
||||
n.OnSpawnServer(nc);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobCache);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for a connection on NetworkObject.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(NetworkObject nob, NetworkConnection conn, bool timedOnly = false)
|
||||
{
|
||||
if (ApplicationState.IsQuitting())
|
||||
return;
|
||||
_writer.Reset();
|
||||
|
||||
/* When not using a timed rebuild such as this connections must have
|
||||
* hashgrid data rebuilt immediately. */
|
||||
// if (!timedOnly)
|
||||
conn.UpdateHashGridPositions(!timedOnly);
|
||||
|
||||
//If observer state changed then write changes.
|
||||
ObserverStateChange osc = nob.RebuildObservers(conn, timedOnly);
|
||||
if (osc == ObserverStateChange.Added)
|
||||
{
|
||||
base.WriteSpawn_Server(nob, conn, _writer);
|
||||
}
|
||||
else if (osc == ObserverStateChange.Removed)
|
||||
{
|
||||
if (conn.LevelOfDetails.TryGetValue(nob, out NetworkConnection.LevelOfDetailData lodData))
|
||||
ObjectCaches<NetworkConnection.LevelOfDetailData>.Store(lodData);
|
||||
conn.LevelOfDetails.Remove(nob);
|
||||
WriteDespawn(nob, nob.GetDefaultDespawnType(), _writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkManager.TransportManager.SendToClient(
|
||||
(byte)Channel.Reliable,
|
||||
_writer.GetArraySegment(), conn);
|
||||
|
||||
/* If spawning then also invoke server
|
||||
* start events, such as buffer last
|
||||
* and onspawnserver. */
|
||||
if (osc == ObserverStateChange.Added)
|
||||
nob.OnSpawnServer(conn);
|
||||
|
||||
/* If there is change then also rebuild on any runtime children.
|
||||
* This is to ensure runtime children have visibility updated
|
||||
* in relation to parent.
|
||||
*
|
||||
* If here there is change. */
|
||||
foreach (NetworkObject item in nob.RuntimeChildNetworkObjects)
|
||||
RebuildObservers(item, conn, timedOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for a connection on NetworkObject.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void RebuildObservers(NetworkObject nob, NetworkConnection conn, List<NetworkObject> addedNobs, bool timedOnly = false)
|
||||
{
|
||||
if (ApplicationState.IsQuitting())
|
||||
return;
|
||||
|
||||
/* When not using a timed rebuild such as this connections must have
|
||||
* hashgrid data rebuilt immediately. */
|
||||
//if (!timedOnly)
|
||||
//conn.UpdateHashGridPositions(true);
|
||||
conn.UpdateHashGridPositions(!timedOnly);
|
||||
|
||||
//If observer state changed then write changes.
|
||||
ObserverStateChange osc = nob.RebuildObservers(conn, timedOnly);
|
||||
if (osc == ObserverStateChange.Added)
|
||||
{
|
||||
base.WriteSpawn_Server(nob, conn, _writer);
|
||||
addedNobs.Add(nob);
|
||||
}
|
||||
else if (osc == ObserverStateChange.Removed)
|
||||
{
|
||||
if (conn.LevelOfDetails.TryGetValue(nob, out NetworkConnection.LevelOfDetailData lodData))
|
||||
ObjectCaches<NetworkConnection.LevelOfDetailData>.Store(lodData);
|
||||
conn.LevelOfDetails.Remove(nob);
|
||||
WriteDespawn(nob, nob.GetDefaultDespawnType(), _writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* If there is change then also rebuild on any runtime children.
|
||||
* This is to ensure runtime children have visibility updated
|
||||
* in relation to parent.
|
||||
*
|
||||
* If here there is change. */
|
||||
foreach (NetworkObject item in nob.RuntimeChildNetworkObjects)
|
||||
RebuildObservers(item, conn, addedNobs, timedOnly);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02d93fa4a653dd64da0bb338b82f4740
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Managing.Utility;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FishNet.Managing.Server
|
||||
{
|
||||
public partial class ServerObjects : ManagedObjects
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Parses a ServerRpc.
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cb6cef520a4ff44bb8c4814e566c5ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,918 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
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 GameKit.Utilities;
|
||||
using GameKit.Utilities.Types;
|
||||
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 List<NetworkObject> _spawnCache = new List<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_Internal.Clear();
|
||||
_objectIdCache.Clear();
|
||||
base.NetworkManager.ClearClientsCollection(base.NetworkManager.ServerManager.Clients);
|
||||
}
|
||||
//If at least one server is started then only clear for disconnecting server.
|
||||
else
|
||||
{
|
||||
//Remove connections only for transportIndex.
|
||||
base.NetworkManager.ClearClientsCollection(base.NetworkManager.ServerManager.Clients, args.TransportIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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. */
|
||||
List<NetworkObject> nobs = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
foreach (NetworkObject nob in connection.Objects)
|
||||
nobs.Add(nob);
|
||||
|
||||
int nobsCount = nobs.Count;
|
||||
for (int i = 0; i < nobsCount; 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 (!nobs[i].IsDeinitializing)
|
||||
base.NetworkManager.ServerManager.Despawn(nobs[i]);
|
||||
}
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobs);
|
||||
}
|
||||
#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 = DDOL.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)
|
||||
{
|
||||
if (!s.IsValid())
|
||||
return;
|
||||
|
||||
List<NetworkObject> sceneNobs = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
Scenes.GetSceneNetworkObjects(s, false, ref sceneNobs);
|
||||
|
||||
//Sort the nobs based on initialization order.
|
||||
bool initializationOrderChanged = false;
|
||||
List<NetworkObject> cache = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
foreach (NetworkObject item in sceneNobs)
|
||||
OrderRootByInitializationOrder(item, cache, ref initializationOrderChanged);
|
||||
OrderNestedByInitializationOrder(cache);
|
||||
//Store sceneNobs.
|
||||
CollectionCaches<NetworkObject>.Store(sceneNobs);
|
||||
|
||||
bool isHost = base.NetworkManager.IsHost;
|
||||
int nobsCount = cache.Count;
|
||||
for (int i = 0; i < nobsCount; i++)
|
||||
{
|
||||
NetworkObject nob = cache[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(cache);
|
||||
}
|
||||
|
||||
/// <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, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
//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.CurrentParentNetworkObject != null && !networkObject.CurrentParentNetworkObject.IsSpawned)
|
||||
{
|
||||
base.NetworkManager.LogError($"{networkObject.name} cannot be spawned because it has a parent NetworkObject {networkObject.CurrentParentNetworkObject} which is not spawned.");
|
||||
return;
|
||||
}
|
||||
/* If scene is specified make sure the object is root,
|
||||
* and if not move it before network spawning. */
|
||||
if (scene.IsValid())
|
||||
{
|
||||
if (networkObject.transform.parent != null)
|
||||
{
|
||||
base.NetworkManager.LogError($"{networkObject.name} cannot be moved to scene name {scene.name}, handle {scene.handle} because {networkObject.name} is not root and only root objects may be moved.");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene);
|
||||
}
|
||||
}
|
||||
|
||||
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.Add(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. */
|
||||
List<NetworkObject> spawnCacheCopy = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
spawnCacheCopy.AddRange(_spawnCache);
|
||||
_spawnCache.Clear();
|
||||
//Also rebuild observers for the object so it spawns for others.
|
||||
RebuildObservers(spawnCacheCopy);
|
||||
|
||||
int spawnCacheCopyCount = spawnCacheCopy.Count;
|
||||
/* 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 = spawnCacheCopyCount;
|
||||
for (int i = 0; i < count; i++)
|
||||
spawnCacheCopy[i].SetRenderersVisible(networkObject.Observers.Contains(NetworkManager.ClientManager.Connection));
|
||||
}
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(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);
|
||||
#if DEVELOPMENT
|
||||
string sceneName = string.Empty;
|
||||
string objectName = string.Empty;
|
||||
CheckReadSceneObjectDetails(reader, ref sceneName, ref objectName);
|
||||
nob = base.GetSceneNetworkObject(sceneId, sceneName, objectName);
|
||||
#else
|
||||
nob = base.GetSceneNetworkObject(sceneId);
|
||||
#endif
|
||||
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, collectionId, 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.Retrieve(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.Store();
|
||||
}
|
||||
|
||||
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.Retrieve();
|
||||
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;
|
||||
|
||||
List<int> intCache = CollectionCaches<int>.RetrieveList();
|
||||
|
||||
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.Add(kvp.Key);
|
||||
|
||||
count++;
|
||||
if (count == iterations)
|
||||
break;
|
||||
}
|
||||
|
||||
//Remove cached entries.
|
||||
int cCount = intCache.Count;
|
||||
for (int i = 0; i < cCount; i++)
|
||||
RecentlyDespawnedIds.Remove(intCache[i]);
|
||||
|
||||
CollectionCaches<int>.Store(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.Retrieve();
|
||||
WriteDespawn(nob, despawnType, everyoneWriter);
|
||||
|
||||
ArraySegment<byte> despawnSegment = everyoneWriter.GetArraySegment();
|
||||
|
||||
//Add observers to a list cache.
|
||||
List<NetworkConnection> cache = CollectionCaches<NetworkConnection>.RetrieveList();
|
||||
cache.AddRange(nob.Observers);
|
||||
int cacheCount = cache.Count;
|
||||
for (int i = 0; i < cacheCount; i++)
|
||||
{
|
||||
//Invoke ondespawn and send despawn.
|
||||
NetworkConnection conn = cache[i];
|
||||
nob.InvokeOnServerDespawn(conn);
|
||||
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, despawnSegment, conn);
|
||||
//Remove from observers.
|
||||
//nob.Observers.Remove(conn);
|
||||
}
|
||||
|
||||
everyoneWriter.Store();
|
||||
CollectionCaches<NetworkConnection>.Store(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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5e7f3005cbc7924f99819311c58651a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,462 @@
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Broadcast.Helping;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Managing.Utility;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using GameKit.Utilities;
|
||||
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
|
||||
{
|
||||
if (handler == null)
|
||||
{
|
||||
NetworkManager.LogError($"Broadcast cannot be registered because handler is null. This may occur when trying to register to objects which require initialization, such as events.");
|
||||
return;
|
||||
}
|
||||
|
||||
ushort key = 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;
|
||||
}
|
||||
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
Broadcasts.WriteBroadcast<T>(NetworkManager, writer, message, ref channel);
|
||||
ArraySegment<byte> segment = writer.GetArraySegment();
|
||||
NetworkManager.TransportManager.SendToClient((byte)channel, segment, connection);
|
||||
writer.Store();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to connections.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of broadcast to send.</typeparam>
|
||||
/// <param name="connections">Connections to send to.</param>
|
||||
/// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name="requireAuthenticated">True if the clients must be authenticated for this broadcast to send.</param>
|
||||
/// <param name="channel">Channel to send on.</param>
|
||||
public void Broadcast<T>(HashSet<NetworkConnection> connections, T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (!Started)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot send broadcast to client because server is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool failedAuthentication = false;
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
Broadcasts.WriteBroadcast<T>(NetworkManager, writer, message, ref 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);
|
||||
}
|
||||
writer.Store();
|
||||
|
||||
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;
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
Broadcasts.WriteBroadcast<T>(NetworkManager, writer, message, ref 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);
|
||||
}
|
||||
writer.Store();
|
||||
|
||||
if (failedAuthentication)
|
||||
{
|
||||
NetworkManager.LogWarning($"One or more broadcast did not send to a client because they were not authenticated.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cd9dcd58556e27449ce5cb0d70611cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using GameKit.Utilities;
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e472255008ae291498f55f5f2a704ab2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
if (go == null)
|
||||
{
|
||||
NetworkManager.LogWarning($"GameObject cannot be spawned because it is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkObject nob = go.GetComponent<NetworkObject>();
|
||||
Spawn(nob, ownerConnection, scene);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name="nob">MetworkObject instance to spawn.</param>
|
||||
/// <param name="ownerConnection">Connection to give ownership to.</param>
|
||||
public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
Objects.Spawn(nob, ownerConnection, scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns an object over the network. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name="go">GameObject instance to despawn.</param>
|
||||
/// <param name="cacheOnDespawnOverride">Overrides the default DisableOnDespawn value for this single despawn. Scene objects will never be destroyed.</param>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2a07d9984be21648bc714ea03bd0d70
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
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()
|
||||
{
|
||||
ushort startingLink = NetworkManager.StartingRpcLinkIndex;
|
||||
for (ushort i = ushort.MaxValue; i >= startingLink; i--)
|
||||
_availableRpcLinkIndexes.Enqueue(i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the next RPC Link to use.
|
||||
/// </summary>
|
||||
/// <returns>True if a link was available and set.</returns>
|
||||
internal bool GetRpcLink(out ushort value)
|
||||
{
|
||||
if (_availableRpcLinkIndexes.Count > 0)
|
||||
{
|
||||
value = _availableRpcLinkIndexes.Dequeue();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets data to RpcLinks for linkIndex.
|
||||
/// </summary>
|
||||
internal void SetRpcLink(ushort linkIndex, RpcLink data)
|
||||
{
|
||||
RpcLinks[linkIndex] = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns RPCLinks to availableRpcLinkIndexes.
|
||||
/// </summary>
|
||||
internal void StoreRpcLinks(Dictionary<uint, RpcLinkType> links)
|
||||
{
|
||||
foreach (RpcLinkType rlt in links.Values)
|
||||
_availableRpcLinkIndexes.Enqueue(rlt.LinkIndex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9f0a4620d06f5c41b01f20af3f90634
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,903 @@
|
||||
using FishNet.Authenticating;
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Debugging;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Predicting;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using FishNet.Utility.Performance;
|
||||
using GameKit.Utilities;
|
||||
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>
|
||||
/// What platforms to enable remote client timeout.
|
||||
/// </summary>
|
||||
[Tooltip("What platforms to enable remote client timeout.")]
|
||||
[SerializeField]
|
||||
private RemoteTimeoutType _remoteClientTimeout = RemoteTimeoutType.Development;
|
||||
/// <summary>
|
||||
/// How long in seconds client must go without sending any packets before getting disconnected. This is independent of any transport settings.
|
||||
/// </summary>
|
||||
[Tooltip("How long in seconds a client must go without sending any packets before getting disconnected. This is independent of any transport settings.")]
|
||||
[Range(1, MAXIMUM_REMOTE_CLIENT_TIMEOUT_DURATION)]
|
||||
[SerializeField]
|
||||
private ushort _remoteClientTimeoutDuration = 60;
|
||||
/// <summary>
|
||||
/// Sets timeout settings. Can be used at runtime.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void SetRemoteClientTimeout(RemoteTimeoutType timeoutType, ushort duration)
|
||||
{
|
||||
_remoteClientTimeout = timeoutType;
|
||||
duration = (ushort)Mathf.Clamp(duration, 1, MAXIMUM_REMOTE_CLIENT_TIMEOUT_DURATION);
|
||||
_remoteClientTimeoutDuration = duration;
|
||||
}
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// The last index checked to see if a client has not sent a packet in awhile.
|
||||
/// </summary>
|
||||
private int _nextClientTimeoutCheckIndex;
|
||||
/// <summary>
|
||||
/// Next time a timeout check can be performed.
|
||||
/// </summary>
|
||||
private float _nextTimeoutCheckTime;
|
||||
/// <summary>
|
||||
/// Used to read splits.
|
||||
/// </summary>
|
||||
private SplitReader _splitReader = new SplitReader();
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
/// <summary>
|
||||
/// Logs data about parser to help debug.
|
||||
/// </summary>
|
||||
private ParseLogger _parseLogger = new ParseLogger();
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Maximum value the remote client timeout can be set to.
|
||||
/// </summary>
|
||||
public const ushort MAXIMUM_REMOTE_CLIENT_TIMEOUT_DURATION = 1500;
|
||||
#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;
|
||||
NetworkManager.TimeManager.OnPostTick += TimeManager_OnPostTick;
|
||||
|
||||
if (_authenticator == null)
|
||||
_authenticator = GetComponent<Authenticator>();
|
||||
if (_authenticator != null)
|
||||
InitializeAuthenticator();
|
||||
|
||||
_cachedLevelOfDetailInterval = NetworkManager.ClientManager.LevelOfDetailInterval;
|
||||
_cachedUseLod = NetworkManager.ObserverManager.GetEnableNetworkLod();
|
||||
}
|
||||
|
||||
/// <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.Retrieve();
|
||||
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.Store();
|
||||
|
||||
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>
|
||||
/// Checks to timeout client connections.
|
||||
/// </summary>
|
||||
private void CheckClientTimeout()
|
||||
{
|
||||
if (_remoteClientTimeout == RemoteTimeoutType.Disabled)
|
||||
return;
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
//If development but not set to development return.
|
||||
else if (_remoteClientTimeout != RemoteTimeoutType.Development)
|
||||
return;
|
||||
#endif
|
||||
//Wait two timing intervals to give packets a chance to come through.
|
||||
if (NetworkManager.SceneManager.IsIteratingQueue(TimeManager.TIMING_INTERVAL * 2f))
|
||||
return;
|
||||
|
||||
float unscaledTime = Time.unscaledTime;
|
||||
if (unscaledTime < _nextTimeoutCheckTime)
|
||||
return;
|
||||
//Check for timeouts every 200ms.
|
||||
const float TIMEOUT_CHECK_FREQUENCY = 0.2f;
|
||||
_nextTimeoutCheckTime = (unscaledTime + TIMEOUT_CHECK_FREQUENCY);
|
||||
//No clients.
|
||||
int clientsCount = Clients.Count;
|
||||
if (clientsCount == 0)
|
||||
return;
|
||||
|
||||
/* If here can do checks. */
|
||||
//If to reset index.
|
||||
if (_nextClientTimeoutCheckIndex >= clientsCount)
|
||||
_nextClientTimeoutCheckIndex = 0;
|
||||
|
||||
//Number of ticks passed for client to be timed out.
|
||||
uint requiredTicks = NetworkManager.TimeManager.TimeToTicks(_remoteClientTimeoutDuration, TickRounding.RoundUp);
|
||||
|
||||
const float FULL_CHECK_TIME = 2f;
|
||||
/* Number of times this is expected to run every 2 seconds.
|
||||
* Iterations will try to complete the entire client collection
|
||||
* over these 2 seconds. */
|
||||
int checkCount = Mathf.CeilToInt(FULL_CHECK_TIME / TIMEOUT_CHECK_FREQUENCY);
|
||||
int targetIterations = Mathf.Max(clientsCount / checkCount, 1);
|
||||
|
||||
uint localTick = NetworkManager.TimeManager.LocalTick;
|
||||
//Number of connections iterated in Clients.Values.
|
||||
int connsIterated = 0;
|
||||
foreach (NetworkConnection item in Clients.Values)
|
||||
{
|
||||
//If iterations are met then we can begin checking for timeouts.
|
||||
if (connsIterated >= _nextClientTimeoutCheckIndex)
|
||||
{
|
||||
uint clientLocalTick = item.PacketTick.LocalTick;
|
||||
/* If client tick has not been set yet then use the tick
|
||||
* when they connected to the server. */
|
||||
if (clientLocalTick == 0)
|
||||
clientLocalTick = item.ServerConnectionTick;
|
||||
|
||||
uint difference = (localTick - clientLocalTick);
|
||||
//Client has timed out.
|
||||
if (difference >= requiredTicks)
|
||||
item.Kick(KickReason.UnexpectedProblem, LoggingType.Common, $"{item.ToString()} has timed out. You can modify this feature on the ServerManager component.");
|
||||
//If all iterations are complete.
|
||||
if (--targetIterations <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
//Increase iterated count.
|
||||
connsIterated++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the TimeManager calls OnPostTick.
|
||||
/// </summary>
|
||||
private void TimeManager_OnPostTick()
|
||||
{
|
||||
CheckClientTimeout();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the local client connection state changes.
|
||||
/// </summary>
|
||||
private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj)
|
||||
{
|
||||
/* If client is doing anything but started destroy pending.
|
||||
* Pending is only used for host mode. */
|
||||
if (obj.ConnectionState != LocalConnectionState.Started)
|
||||
Objects.DestroyPending();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a client loads initial scenes after connecting.
|
||||
/// </summary>
|
||||
private void SceneManager_OnClientLoadedStartScenes(NetworkConnection conn, bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
Objects.RebuildObservers(conn);
|
||||
/* If connection is host then renderers must be hidden
|
||||
* for all objects not visible to the host. The observer system
|
||||
* does handle this but only after an initial state is set.
|
||||
* If the clientHost joins without observation of an object
|
||||
* then the initial state will never be set. */
|
||||
if (conn.IsLocalClient)
|
||||
{
|
||||
foreach (NetworkObject nob in Objects.Spawned.Values)
|
||||
{
|
||||
if (!nob.Observers.Contains(conn))
|
||||
nob.SetRenderersVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes subscription status to transport.
|
||||
/// </summary>
|
||||
/// <param name="subscribe"></param>
|
||||
private void SubscribeToTransport(bool subscribe)
|
||||
{
|
||||
if (NetworkManager == null || NetworkManager.TransportManager == null || NetworkManager.TransportManager.Transport == null)
|
||||
return;
|
||||
|
||||
if (subscribe)
|
||||
{
|
||||
NetworkManager.TransportManager.Transport.OnServerReceivedData += Transport_OnServerReceivedData;
|
||||
NetworkManager.TransportManager.Transport.OnServerConnectionState += Transport_OnServerConnectionState;
|
||||
NetworkManager.TransportManager.Transport.OnRemoteConnectionState += Transport_OnRemoteConnectionState;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.TransportManager.Transport.OnServerReceivedData -= Transport_OnServerReceivedData;
|
||||
NetworkManager.TransportManager.Transport.OnServerConnectionState -= Transport_OnServerConnectionState;
|
||||
NetworkManager.TransportManager.Transport.OnRemoteConnectionState -= Transport_OnRemoteConnectionState;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when authenticator has concluded a result for a connection. Boolean is true if authentication passed, false if failed.
|
||||
/// Server listens for this event automatically.
|
||||
/// </summary>
|
||||
private void _authenticator_OnAuthenticationResult(NetworkConnection conn, bool authenticated)
|
||||
{
|
||||
if (!authenticated)
|
||||
conn.Disconnect(false);
|
||||
else
|
||||
ClientAuthenticated(conn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a connection state changes for the local server.
|
||||
/// </summary>
|
||||
private void Transport_OnServerConnectionState(ServerConnectionStateArgs args)
|
||||
{
|
||||
/* Let the client manager know the server state is changing first.
|
||||
* This gives the client an opportunity to clean-up or prepare
|
||||
* before the server completes it's actions. */
|
||||
Started = AnyServerStarted();
|
||||
NetworkManager.ClientManager.Objects.OnServerConnectionState(args);
|
||||
//If no servers are started then reset match conditions.
|
||||
if (!Started)
|
||||
{
|
||||
MatchCondition.StoreCollections(NetworkManager);
|
||||
//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;
|
||||
string socketInformation = string.Empty;
|
||||
if (state == LocalConnectionState.Starting)
|
||||
socketInformation = $" Listening on port {t.GetPort()}.";
|
||||
Debug.Log($"Local server is {state.ToString().ToLower()} for {tName}.{socketInformation}");
|
||||
}
|
||||
|
||||
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, args.TransportIndex, 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);
|
||||
Objects.ClientDisconnected(conn);
|
||||
BroadcastClientConnectionChange(false, conn);
|
||||
//Return predictedObjectIds.
|
||||
Queue<int> pqId = conn.PredictedObjectIds;
|
||||
while (pqId.Count > 0)
|
||||
Objects.CacheObjectId(pqId.Dequeue());
|
||||
|
||||
conn.Dispose();
|
||||
NetworkManager.Log($"Remote connection stopped for Id {id}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends client their connectionId.
|
||||
/// </summary>
|
||||
/// <param name="connectionid"></param>
|
||||
private void SendAuthenticated(NetworkConnection conn)
|
||||
{
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
writer.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);
|
||||
writer.Store();
|
||||
}
|
||||
/// <summary>
|
||||
/// Called when the server socket receives data.
|
||||
/// </summary>
|
||||
private void Transport_OnServerReceivedData(ServerReceivedDataArgs args)
|
||||
{
|
||||
ParseReceived(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the server receives data.
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
private void ParseReceived(ServerReceivedDataArgs args)
|
||||
{
|
||||
#if 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;
|
||||
}
|
||||
|
||||
bool hasIntermediateLayer = NetworkManager.TransportManager.HasIntermediateLayer;
|
||||
PacketId packetId = PacketId.Unset;
|
||||
PooledReader reader = null;
|
||||
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
|
||||
try
|
||||
{
|
||||
#endif
|
||||
Reader.DataSource dataSource = Reader.DataSource.Client;
|
||||
reader = ReaderPool.Retrieve(segment, NetworkManager, dataSource);
|
||||
uint tick = reader.ReadTickUnpacked();
|
||||
NetworkManager.TimeManager.SetLastPacketTick(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.
|
||||
if (hasIntermediateLayer)
|
||||
reader.Initialize(NetworkManager.TransportManager.ProcessIntermediateIncoming(fullMessage, false), NetworkManager, dataSource);
|
||||
else
|
||||
reader.Initialize(fullMessage, NetworkManager, dataSource);
|
||||
}
|
||||
//Not Split.
|
||||
else
|
||||
{
|
||||
//Override values with intermediate layer changes.
|
||||
if (hasIntermediateLayer)
|
||||
{
|
||||
ArraySegment<byte> modified = NetworkManager.TransportManager.ProcessIntermediateIncoming(reader.GetRemainingData(), false);
|
||||
reader.Initialize(modified, NetworkManager, dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
//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 {args.ConnectionId} not found within Clients. Connection will be kicked immediately.");
|
||||
return;
|
||||
}
|
||||
conn.PacketTick.Update(NetworkManager.TimeManager, tick, Timing.EstimatedTick.OldTickOption.SetLastRemoteTick);
|
||||
/* If connection isn't authenticated and isn't a broadcast
|
||||
* then disconnect client. If a broadcast then process
|
||||
* normally; client may still become disconnected if the broadcast
|
||||
* does not allow to be called while not authenticated. */
|
||||
if (!conn.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} on channel {args.Channel} from connectionId {args.ConnectionId}. Remaining data has been purged.");
|
||||
_parseLogger.Print(NetworkManager);
|
||||
#else
|
||||
NetworkManager.LogError($"Server received an unhandled PacketId of {(ushort)packetId} on channel {args.Channel} from connectionId {args.ConnectionId}. Connection will be kicked immediately.");
|
||||
NetworkManager.TransportManager.Transport.StopConnection(args.ConnectionId, true);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
#if !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}.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader?.Store();
|
||||
}
|
||||
#else
|
||||
reader?.Store();
|
||||
#endif
|
||||
|
||||
//Kicks connection for exceeding MTU.
|
||||
void ExceededMTUKick()
|
||||
{
|
||||
Kick(args.ConnectionId, KickReason.ExploitExcessiveData, LoggingType.Common, $"ConnectionId {args.ConnectionId} sent a message larger than allowed amount. Connection will be kicked immediately.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a received PingPong.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <param name="conn"></param>
|
||||
private void ParsePingPong(PooledReader reader, NetworkConnection conn)
|
||||
{
|
||||
/* //security limit how often clients can send pings.
|
||||
* have clients use a stopwatch rather than frame time
|
||||
* for checks to ensure it's not possible to send
|
||||
* excessively should their game stutter then catch back up. */
|
||||
uint clientTick = reader.ReadTickUnpacked();
|
||||
if (conn.CanPingPong())
|
||||
NetworkManager.TimeManager.SendPong(conn, clientTick);
|
||||
}
|
||||
|
||||
|
||||
/// <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
|
||||
};
|
||||
foreach (NetworkConnection c in Clients.Values)
|
||||
{
|
||||
if (c.Authenticated)
|
||||
Broadcast(c, changeMsg);
|
||||
}
|
||||
|
||||
/* If state is connected then the conn client
|
||||
* must also receive all currently connected client ids. */
|
||||
if (connected)
|
||||
{
|
||||
//Send already connected clients to the connection that just joined.
|
||||
List<int> cache = CollectionCaches<int>.RetrieveList();
|
||||
foreach (int key in Clients.Keys)
|
||||
cache.Add(key);
|
||||
|
||||
ConnectedClientsBroadcast allMsg = new ConnectedClientsBroadcast()
|
||||
{
|
||||
Values = cache
|
||||
};
|
||||
conn.Broadcast(allMsg);
|
||||
CollectionCaches<int>.Store(cache);
|
||||
}
|
||||
}
|
||||
//If not sharing Ids then only send ConnectionChange to conn.
|
||||
else
|
||||
{
|
||||
if (connected)
|
||||
{
|
||||
/* Send broadcast only to the client which just disconnected.
|
||||
* Only send if connecting. If the client is disconnected there's no reason
|
||||
* to send them a disconnect msg. */
|
||||
ClientConnectionChangeBroadcast changeMsg = new ClientConnectionChangeBroadcast()
|
||||
{
|
||||
Connected = connected,
|
||||
Id = conn.ClientId
|
||||
};
|
||||
Broadcast(conn, changeMsg, true, Channel.Reliable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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:
|
||||
Reference in New Issue
Block a user