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