using FishNet.Connection;
using FishNet.Managing.Object;
using FishNet.Managing.Timing;
using FishNet.Object;
using FishNet.Observing;
using FishNet.Serializing;
using FishNet.Transporting;
using GameKit.Dependencies.Utilities;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.Managing.Server
{
public partial class ServerObjects : ManagedObjects
{
#region Private.
///
/// 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.
///
private List _observerChangedObjectsCache = new(100);
///
/// NetworkObservers which require regularly iteration.
///
private List _timedNetworkObservers = new();
///
/// Index in TimedNetworkObservers to start on next cycle.
///
private int _nextTimedObserversIndex;
///
/// Used to write spawns for everyone. This writer will exclude owner only information.
///
private PooledWriter _writer = new();
///
/// Indexes within TimedNetworkObservers which are unset.
///
private Queue _emptiedTimedIndexes = new();
#endregion
///
/// Called when MonoBehaviours call Update.
///
private void Observers_OnUpdate()
{
UpdateTimedObservers();
}
///
/// Progressively updates NetworkObservers with timed conditions.
///
private void UpdateTimedObservers()
{
if (!base.NetworkManager.IsServerStarted)
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. */
float timeMultiplier = 1f + ((base.NetworkManager.ServerManager.Clients.Count * 0.005f) + (_timedNetworkObservers.Count * 0.0005f));
//Check cap this way for readability.
float completionTime = Mathf.Min((0.5f * timeMultiplier), base.NetworkManager.ObserverManager.MaximumTimedObserversDuration);
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 / completionTicks);
if (iterations > _timedNetworkObservers.Count)
iterations = _timedNetworkObservers.Count;
List connCache = RetrieveAuthenticatedConnections();
//Build nob cache.
List nobCache = CollectionCaches.RetrieveList();
for (int i = 0; i < iterations; i++)
{
if (_nextTimedObserversIndex >= _timedNetworkObservers.Count)
_nextTimedObserversIndex = 0;
NetworkObject nob = _timedNetworkObservers[_nextTimedObserversIndex++];
if (nob != null)
nobCache.Add(nob);
}
RebuildObservers(nobCache, connCache, true);
CollectionCaches.Store(connCache);
CollectionCaches.Store(nobCache);
}
///
/// Indicates that a networkObserver component should be updated regularly. This is done automatically.
///
/// NetworkObject to be updated.
public void AddTimedNetworkObserver(NetworkObject networkObject)
{
if (_emptiedTimedIndexes.TryDequeue(out int index))
_timedNetworkObservers[index] = networkObject;
else
_timedNetworkObservers.Add(networkObject);
}
///
/// Indicates that a networkObserver component no longer needs to be updated regularly. This is done automatically.
///
/// NetworkObject to be updated.
public void RemoveTimedNetworkObserver(NetworkObject networkObject)
{
int index = _timedNetworkObservers.IndexOf(networkObject);
if (index == -1)
return;
_emptiedTimedIndexes.Enqueue(index);
_timedNetworkObservers[index] = null;
//If there's a decent amount missing then rebuild the collection.
if (_emptiedTimedIndexes.Count > 20)
{
List newLst = CollectionCaches.RetrieveList();
foreach (NetworkObject nob in _timedNetworkObservers)
{
if (nob == null)
continue;
newLst.Add(nob);
}
CollectionCaches.Store(_timedNetworkObservers);
_timedNetworkObservers = newLst;
_emptiedTimedIndexes.Clear();
}
}
///
/// Gets all NetworkConnections which are authenticated.
///
///
private List RetrieveAuthenticatedConnections()
{
List cache = CollectionCaches.RetrieveList();
foreach (NetworkConnection item in NetworkManager.ServerManager.Clients.Values)
{
if (item.IsAuthenticated)
cache.Add(item);
}
return cache;
}
///
/// Gets all spawned objects with root objects first.
///
///
private List RetrieveOrderedSpawnedObjects()
{
List spawnedCache = GetSpawnedNetworkObjects();
List sortedCache = SortRootAndNestedByInitializeOrder(spawnedCache);
CollectionCaches.Store(spawnedCache);
return sortedCache;
}
///
/// Returns spawned NetworkObjects as a list.
/// Collection returned is a new cache and should be disposed of properly.
///
///
private List GetSpawnedNetworkObjects()
{
List cache = CollectionCaches.RetrieveList();
Spawned.ValuesToList(ref cache);
return cache;
}
///
/// Sorts a collection of NetworkObjects root and nested by initialize order.
/// Collection returned is a new cache and should be disposed of properly.
///
internal List SortRootAndNestedByInitializeOrder(List nobs)
{
List sortedRootCache = CollectionCaches.RetrieveList();
//First order root objects.
foreach (NetworkObject item in nobs)
{
if (item.IsNested)
continue;
sortedRootCache.AddOrdered(item);
}
/* After all root are ordered check
* their nested. Order nested in segments
* of each root then insert after the root.
* This must be performed after all roots are ordered. */
//This holds the results of all values.
List sortedRootAndNestedCache = CollectionCaches.RetrieveList();
//Cache for sorting nested.
List sortedNestedCache = CollectionCaches.RetrieveList();
foreach (NetworkObject item in sortedRootCache)
{
/* Remove recursive and only check Initialized and Runtime. Once iterated
* check each added entry again using Initialized and Recursive. */
List nested = item.GetNetworkObjects(GetNetworkObjectOption.AllNestedRecursive);
foreach (NetworkObject nestedItem in nested)
{
if (sortedNestedCache.Contains(nestedItem))
Debug.LogError("Already contains " + nestedItem.name);
else
sortedNestedCache.AddOrdered(nestedItem);
}
CollectionCaches.Store(nested);
/* Once all nested are sorted then can be added to the
* sorted root and nested cache. */
sortedRootAndNestedCache.Add(item);
sortedRootAndNestedCache.AddRange(sortedNestedCache);
//Reset cache.
sortedNestedCache.Clear();
}
//Store temp caches.
CollectionCaches.Store(sortedRootCache);
CollectionCaches.Store(sortedNestedCache);
return sortedRootAndNestedCache;
}
///
/// Removes a connection from observers without synchronizing changes.
///
///
private void RemoveFromObserversWithoutSynchronization(NetworkConnection connection)
{
List 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();
}
///
/// Rebuilds observers on all NetworkObjects for all connections.
///
public void RebuildObservers(bool timedOnly = false)
{
List nobCache = RetrieveOrderedSpawnedObjects();
List connCache = RetrieveAuthenticatedConnections();
RebuildObservers(nobCache, connCache, timedOnly);
CollectionCaches.Store(nobCache);
CollectionCaches.Store(connCache);
}
///
/// Rebuilds observers for all connections for a NetworkObject.
///
public void RebuildObservers(NetworkObject nob, bool timedOnly = false)
{
List nobCache = CollectionCaches.RetrieveList(nob);
List connCache = RetrieveAuthenticatedConnections();
RebuildObservers(nobCache, connCache, timedOnly);
CollectionCaches.Store(nobCache);
CollectionCaches.Store(connCache);
}
///
/// Rebuilds observers on all NetworkObjects for a connection.
///
public void RebuildObservers(NetworkConnection connection, bool timedOnly = false)
{
List nobCache = RetrieveOrderedSpawnedObjects();
List connCache = CollectionCaches.RetrieveList(connection);
RebuildObservers(nobCache, connCache, timedOnly);
CollectionCaches.Store(nobCache);
CollectionCaches.Store(connCache);
}
///
/// Rebuilds observers on NetworkObjects.
///
public void RebuildObservers(IList nobs, bool timedOnly = false)
{
List conns = RetrieveAuthenticatedConnections();
RebuildObservers(nobs, conns, timedOnly);
CollectionCaches.Store(conns);
}
///
/// Rebuilds observers on all objects for connections.
///
public void RebuildObservers(IList connections, bool timedOnly = false)
{
List nobCache = RetrieveOrderedSpawnedObjects();
RebuildObservers(nobCache, connections, timedOnly);
CollectionCaches.Store(nobCache);
}
///
/// Rebuilds observers on NetworkObjects for connections.
///
public void RebuildObservers(IList nobs, NetworkConnection conn, bool timedOnly = false)
{
List connCache = CollectionCaches.RetrieveList(conn);
RebuildObservers(nobs, connCache, timedOnly);
CollectionCaches.Store(connCache);
}
///
/// Rebuilds observers for connections on NetworkObject.
///
public void RebuildObservers(NetworkObject networkObject, IList connections, bool timedOnly = false)
{
List nobCache = CollectionCaches.RetrieveList(networkObject);
RebuildObservers(nobCache, connections, timedOnly);
CollectionCaches.Store(nobCache);
}
///
/// Rebuilds observers on NetworkObjects for connections.
///
public void RebuildObservers(IList nobs, IList conns, bool timedOnly = false)
{
List nobCache = CollectionCaches.RetrieveList();
NetworkConnection nc;
int connsCount = conns.Count;
for (int i = 0; i < connsCount; i++)
{
nobCache.Clear();
nc = conns[i];
int nobsCount = nobs.Count;
for (int z = 0; z < nobsCount; z++)
RebuildObservers(nobs[z], nc, nobCache, timedOnly);
//Send if change.
if (_writer.Length > 0)
{
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, _writer.GetArraySegment(), nc);
_writer.Clear();
foreach (NetworkObject n in nobCache)
n.OnSpawnServer(nc);
}
}
CollectionCaches.Store(nobCache);
}
///
/// Rebuilds observers for a connection on NetworkObject.
///
public void RebuildObservers(NetworkObject nob, NetworkConnection conn, bool timedOnly = false)
{
if (ApplicationState.IsQuitting())
return;
_writer.Clear();
conn.UpdateHashGridPositions(!timedOnly);
//If observer state changed then write changes.
ObserverStateChange osc = nob.RebuildObservers(conn, timedOnly);
if (osc == ObserverStateChange.Added)
{
WriteSpawn(nob, _writer, conn);
}
else if (osc == ObserverStateChange.Removed)
{
nob.InvokeOnServerDespawn(conn);
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);
_writer.Clear();
/* If there is change then also rebuild recursive networkObjects. */
foreach (NetworkBehaviour item in nob.RuntimeChildNetworkBehaviours)
RebuildObservers(item.NetworkObject, conn, timedOnly);
}
///
/// Rebuilds observers for a connection on NetworkObject.
///
internal void RebuildObservers(NetworkObject nob, NetworkConnection conn, List addedNobs, bool timedOnly = false)
{
if (ApplicationState.IsQuitting())
return;
/* When not using a timed rebuild such as this connections must have
* hashgrid data rebuilt immediately. */
conn.UpdateHashGridPositions(!timedOnly);
//If observer state changed then write changes.
ObserverStateChange osc = nob.RebuildObservers(conn, timedOnly);
if (osc == ObserverStateChange.Added)
{
WriteSpawn(nob, _writer, conn);
addedNobs.Add(nob);
}
else if (osc == ObserverStateChange.Removed)
{
nob.InvokeOnServerDespawn(conn);
WriteDespawn(nob, nob.GetDefaultDespawnType(), _writer);
}
else
{
return;
}
/* If there is change then also rebuild on any runtime children.
* This is to ensure runtime children have visibility updated
* in relation to parent.
*
* If here there is change. */
foreach (NetworkBehaviour item in nob.RuntimeChildNetworkBehaviours)
RebuildObservers(item.NetworkObject, conn, addedNobs, timedOnly);
}
}
}