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); } } }