#if UNITY_EDITOR || DEVELOPMENT_BUILD
#define DEVELOPMENT
#endif
#if UNITY_EDITOR
using FishNet.Editing.PrefabCollectionGenerator;
using UnityEditor;
#endif
using FishNet.Connection;
using FishNet.Managing.Client;
using FishNet.Managing.Server;
using FishNet.Managing.Timing;
using FishNet.Managing.Transporting;
using UnityEngine;
using FishNet.Managing.Scened;
using FishNet.Object;
using FishNet.Documenting;
using System.Collections.Generic;
using System;
using FishNet.Managing.Observing;
using System.Linq;
using FishNet.Managing.Debugging;
using FishNet.Managing.Object;
using FishNet.Transporting;
using FishNet.Managing.Statistic;
using FishNet.Utility.Performance;
using FishNet.Component.ColliderRollback;
using FishNet.Managing.Predicting;
using GameKit.Dependencies.Utilities;
namespace FishNet.Managing
{
///
/// Acts as a container for all things related to your networking session.
///
[DefaultExecutionOrder(short.MinValue)]
[DisallowMultipleComponent]
[AddComponentMenu("FishNet/Manager/NetworkManager")]
public sealed partial class NetworkManager : MonoBehaviour
{
#region Types.
///
/// How to persist with multiple NetworkManagers.
///
public enum PersistenceType
{
///
/// Destroy any new NetworkManagers.
///
DestroyNewest,
///
/// Destroy previous NetworkManager when a new NetworkManager occurs.
///
DestroyOldest,
///
/// Allow multiple NetworkManagers, do not destroy any automatically.
///
AllowMultiple
}
#endregion
#region Public.
///
/// True if this instance of the NetworkManager is initialized.
///
public bool Initialized { get; private set; }
///
///
///
private static List _instances = new();
///
/// Currently initialized NetworkManagers.
///
public static IReadOnlyList Instances
{
get
{
/* Remove null instances of NetworkManager.
* This shouldn't happen because instances are removed
* OnDestroy but none the less something is causing
* it. */
for (int i = 0; i < _instances.Count; i++)
{
if (_instances[i] == null)
{
_instances.RemoveAt(i);
i--;
}
}
return _instances;
}
}
///
/// PredictionManager for this NetworkManager.
///
internal PredictionManager PredictionManager { get; private set; }
///
/// ServerManager for this NetworkManager.
///
public ServerManager ServerManager { get; private set; }
///
/// ClientManager for this NetworkManager.
///
public ClientManager ClientManager { get; private set; }
///
/// TransportManager for this NetworkManager.
///
public TransportManager TransportManager { get; private set; }
///
/// TimeManager for this NetworkManager.
///
public TimeManager TimeManager { get; private set; }
///
/// SceneManager for this NetworkManager.
///
public SceneManager SceneManager { get; private set; }
///
/// ObserverManager for this NetworkManager.
///
public ObserverManager ObserverManager { get; private set; }
///
/// DebugManager for this NetworkManager.
///
public DebugManager DebugManager { get; private set; }
///
/// StatisticsManager for this NetworkManager.
///
public StatisticsManager StatisticsManager { get; private set; }
///
/// An empty connection reference. Used when a connection cannot be found to prevent object creation.
///
[APIExclude]
public static NetworkConnection EmptyConnection { get; private set; } = new();
#endregion
#region Internal.
///
/// Starting index for RpcLinks.
///
internal static ushort StartingRpcLinkIndex;
#if DEVELOPMENT
///
/// Logs data about parser to help debug.
///
internal PacketIdHistory PacketIdHistory = new();
#endif
#endregion
#region Serialized.
#if UNITY_EDITOR
///
/// True to refresh the DefaultPrefabObjects collection whenever the editor enters play mode. This is an attempt to alleviate the DefaultPrefabObjects scriptable object not refreshing when using multiple editor applications such as ParrelSync.
///
[Tooltip("True to refresh the DefaultPrefabObjects collection whenever the editor enters play mode. This is an attempt to alleviate the DefaultPrefabObjects scriptable object not refreshing when using multiple editor applications such as ParrelSync.")]
[SerializeField]
private bool _refreshDefaultPrefabs = false;
#endif
///
/// True to have your application run while in the background.
///
[Tooltip("True to have your application run while in the background.")]
[SerializeField]
private bool _runInBackground = true;
///
/// True to make this instance DontDestroyOnLoad. This is typical if you only want one NetworkManager.
///
[Tooltip("True to make this instance DontDestroyOnLoad. This is typical if you only want one NetworkManager.")]
[SerializeField]
private bool _dontDestroyOnLoad = true;
///
/// Object pool to use for this NetworkManager. Value may be null.
///
public ObjectPool ObjectPool => _objectPool;
[Tooltip("Object pool to use for this NetworkManager. Value may be null.")]
[SerializeField]
private ObjectPool _objectPool;
///
/// How to persist when other NetworkManagers are introduced.
///
[Tooltip("How to persist when other NetworkManagers are introduced.")]
[SerializeField]
private PersistenceType _persistence = PersistenceType.DestroyNewest;
#endregion
#region Private.
///
/// True if this NetworkManager can persist after Awake checks.
///
private bool _canPersist;
#endregion
#region Const.
///
/// Version of this release.
///
public const string FISHNET_VERSION = "4.6.9";
///
/// Maximum framerate allowed.
///
internal const ushort MAXIMUM_FRAMERATE = 500;
#endregion
private void Awake()
{
InitializeLogging();
if (!ValidateSpawnablePrefabs(true))
return;
if (StartingRpcLinkIndex == 0)
StartingRpcLinkIndex = (ushort)(Enums.GetHighestValue() + 1);
bool isDefaultPrefabs = (SpawnablePrefabs != null && SpawnablePrefabs is DefaultPrefabObjects);
#if UNITY_EDITOR
/* If first instance then force
* default prefabs to repopulate.
* This is only done in editor because
* cloning tools sometimes don't synchronize
* scriptable object changes, which is what
* the default prefabs is. */
if (_refreshDefaultPrefabs && _instances.Count == 0 && isDefaultPrefabs)
{
Generator.IgnorePostProcess = true;
Debug.Log("DefaultPrefabCollection is being refreshed.");
Generator.GenerateFull(initializeAdded: false);
Generator.IgnorePostProcess = false;
}
#endif
//If default prefabs then also make a new instance and sort them.
if (isDefaultPrefabs)
{
DefaultPrefabObjects originalDpo = (DefaultPrefabObjects)SpawnablePrefabs;
//If not editor then a new instance must be made and sorted.
DefaultPrefabObjects instancedDpo = ScriptableObject.CreateInstance();
instancedDpo.AddObjects(originalDpo.Prefabs.ToList(), checkForDuplicates: false, initializeAdded: false);
instancedDpo.Sort();
SpawnablePrefabs = instancedDpo;
}
_canPersist = CanInitialize();
if (!_canPersist)
return;
if (TryGetComponent(out _))
InternalLogError($"NetworkObject component found on the NetworkManager object {gameObject.name}. This is not allowed and will cause problems. Remove the NetworkObject component from this object.");
SpawnablePrefabs.InitializePrefabRange(0);
SpawnablePrefabs.SetCollectionId(0);
SetDontDestroyOnLoad();
SetRunInBackground();
DebugManager = GetOrCreateComponent();
TransportManager = GetOrCreateComponent();
ServerManager = GetOrCreateComponent();
ClientManager = GetOrCreateComponent();
TimeManager = GetOrCreateComponent();
SceneManager = GetOrCreateComponent();
ObserverManager = GetOrCreateComponent();
RollbackManager = GetOrCreateComponent();
PredictionManager = GetOrCreateComponent();
StatisticsManager = GetOrCreateComponent();
if (_objectPool == null)
_objectPool = GetOrCreateComponent();
InitializeComponents();
_instances.Add(this);
Initialized = true;
}
private void Start()
{
ServerManager.StartForHeadless();
}
private void OnDestroy()
{
_instances.Remove(this);
}
///
/// Initializes components. To be called after all components are added.
///
private void InitializeComponents()
{
TimeManager.InitializeOnce_Internal(this);
TimeManager.OnLateUpdate += TimeManager_OnLateUpdate;
SceneManager.InitializeOnce_Internal(this);
TransportManager.InitializeOnce_Internal(this);
ClientManager.InitializeOnce_Internal(this);
ServerManager.InitializeOnce_Internal(this);
ObserverManager.InitializeOnce_Internal(this);
RollbackManager.InitializeOnce_Internal(this);
PredictionManager.InitializeOnce(this);
StatisticsManager.InitializeOnce_Internal(this);
_objectPool.InitializeOnce(this);
}
///
/// Updates the frame rate based on server and client status.
///
internal void UpdateFramerate()
{
bool clientStarted = ClientManager.Started;
bool serverStarted = ServerManager.Started;
int frameRate = 0;
//If both client and server are started then use whichever framerate is higher.
if (clientStarted && serverStarted)
frameRate = Math.Max(ServerManager.FrameRate, ClientManager.FrameRate);
else if (clientStarted)
frameRate = ClientManager.FrameRate;
else if (serverStarted)
frameRate = ServerManager.FrameRate;
/* Make sure framerate isn't set to max on server.
* If it is then default to tick rate. If framerate is
* less than tickrate then also set to tickrate. */
#if UNITY_SERVER && !UNITY_EDITOR
ushort minimumServerFramerate = (ushort)(TimeManager.TickRate + 15);
if (frameRate == MAXIMUM_FRAMERATE)
frameRate = minimumServerFramerate;
else if (frameRate < TimeManager.TickRate)
frameRate = minimumServerFramerate;
#endif
//If there is a framerate to set.
if (frameRate > 0)
Application.targetFrameRate = frameRate;
}
///
/// Called when MonoBehaviours call LateUpdate.
///
private void TimeManager_OnLateUpdate()
{
/* Some reason runinbackground becomes unset
* or the setting goes ignored some times when it's set
* in awake. Rather than try to fix or care why Unity
* does this just set it in LateUpdate(or Update). */
SetRunInBackground();
//Let's object pooler do regular work.
_objectPool.LateUpdate();
}
///
/// Returns if this NetworkManager can initialize.
///
///
private bool CanInitialize()
{
/* If allow multiple then any number of
* NetworkManagers are allowed. Don't
* automatically destroy any. */
if (_persistence == PersistenceType.AllowMultiple)
return true;
List instances = Instances.ToList();
//This is the first instance, it may initialize.
if (instances.Count == 0)
return true;
//First instance of NM.
NetworkManager firstInstance = instances[0];
//If to destroy the newest.
if (_persistence == PersistenceType.DestroyNewest)
{
InternalLog($"NetworkManager on object {gameObject.name} is being destroyed due to persistence type {_persistence}. A NetworkManager instance already exist on {firstInstance.name}.");
DestroyImmediate(gameObject);
//This one is being destroyed because its the newest.
return false;
}
//If to destroy the oldest.
else if (_persistence == PersistenceType.DestroyOldest)
{
InternalLog($"NetworkManager on object {firstInstance.name} is being destroyed due to persistence type {_persistence}. A NetworkManager instance has been created on {gameObject.name}.");
DestroyImmediate(firstInstance.gameObject);
//This being the new one will persist, allow initialization.
return true;
}
//Unhandled.
else
{
InternalLog($"Persistance type of {_persistence} is unhandled on {gameObject.name}. Initialization will not proceed.");
return false;
}
}
///
/// Validates SpawnablePrefabs field and returns if validated successfully.
///
///
private bool ValidateSpawnablePrefabs(bool print)
{
//If null and object is in a scene.
if (SpawnablePrefabs == null && !string.IsNullOrEmpty(gameObject.scene.name))
{
//First try to fetch the file, only if editor and not in play mode.
#if UNITY_EDITOR
if (!ApplicationState.IsPlaying())
{
SpawnablePrefabs = Generator.GetDefaultPrefabObjects();
if (SpawnablePrefabs != null)
{
Debug.Log($"SpawnablePrefabs was set to DefaultPrefabObjects automatically on object {gameObject.name} in scene {gameObject.scene.name}.");
EditorUtility.SetDirty(this);
return true;
}
}
#endif
//Always throw an error as this would cause failure.
if (print)
Debug.LogError($"SpawnablePrefabs is null on {gameObject.name}. Select the NetworkManager in scene {gameObject.scene.name} and choose a prefabs file. Choosing DefaultPrefabObjects will automatically populate prefabs for you.");
return false;
}
return true;
}
///
/// Sets DontDestroyOnLoad if configured to.
///
private void SetDontDestroyOnLoad()
{
if (_dontDestroyOnLoad)
DontDestroyOnLoad(this);
}
///
/// Sets Application.runInBackground to runInBackground.
///
private void SetRunInBackground()
{
Application.runInBackground = _runInBackground;
}
///
/// Gets a component, creating and adding it if it does not exist.
///
/// Value which may already be set. When not null this is returned instead.
private T GetOrCreateComponent(T presetValue = null) where T : UnityEngine.Component
{
//If already set then return set value.
if (presetValue != null)
return presetValue;
if (gameObject.TryGetComponent(out T result))
return result;
else
return gameObject.AddComponent();
}
///
/// Clears a client collection after disposing of the NetworkConnections.
///
///
internal void ClearClientsCollection(Dictionary clients, int transportIndex = -1)
{
//True to dispose all connections.
bool disposeAll = (transportIndex < 0);
List cache = CollectionCaches.RetrieveList();
foreach (KeyValuePair kvp in clients)
{
NetworkConnection value = kvp.Value;
//If to check transport index.
if (!disposeAll)
{
if (value.TransportIndex == transportIndex)
{
cache.Add(kvp.Key);
value.ResetState();
}
}
//Not using transport index, no check required.
else
{
value.ResetState();
}
}
//If all are being disposed the collection can be cleared.
if (disposeAll)
{
clients.Clear();
}
//Otherwise, only remove those which were disposed.
else
{
foreach (int item in cache)
clients.Remove(item);
}
CollectionCaches.Store(cache);
}
#region Editor.
#if UNITY_EDITOR
private void OnValidate()
{
if (SpawnablePrefabs == null)
Reset();
}
private void Reset()
{
ValidateSpawnablePrefabs(true);
}
#endif
#endregion
}
}