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. /// /// 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; /// /// Used to write spawns for everyone. This writer will exclude owner only information. /// private PooledWriter _writer = new PooledWriter(); #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 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 connCache = RetrieveAuthenticatedConnections(); //Build nob cache. List nobCache = CollectionCaches.RetrieveList(); for (int i = 0; i < iterations; i++) { if (_nextTimedObserversIndex >= _timedNetworkObservers.Count) _nextTimedObserversIndex = 0; nobCache.Add(_timedNetworkObservers[_nextTimedObserversIndex++]); } 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) { _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); } /// /// Gets all NetworkConnections which are authenticated. /// /// private List RetrieveAuthenticatedConnections() { List cache = CollectionCaches.RetrieveList(); foreach (NetworkConnection item in NetworkManager.ServerManager.Clients.Values) { if (item.Authenticated) cache.Add(item); } return cache; } /// /// Gets all spawned objects with root objects first. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private List RetrieveOrderedSpawnedObjects() { List cache = CollectionCaches.RetrieveList(); bool initializationOrderChanged = false; //First order root objects. foreach (NetworkObject item in Spawned.Values) OrderRootByInitializationOrder(item, cache, ref initializationOrderChanged); OrderNestedByInitializationOrder(cache); return cache; } /// /// Orders a NetworkObject into a cache based on it's initialization order. /// Only non-nested NetworkObjects will be added. /// /// NetworkObject to check. /// Cache to sort into. /// Boolean to indicate if initialization order is specified for one or more objects. private void OrderRootByInitializationOrder(NetworkObject nob, List 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; } } } } } /// /// Orders nested NetworkObjects of cache by initialization order. /// /// Cache to sort. private void OrderNestedByInitializationOrder(List 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); } } } /// /// 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] 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); } #region Obsolete RebuildObservers. /// /// Rebuilds observers on NetworkObjects. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [Obsolete("Use RebuildObservers IList variant instead.")] public void RebuildObservers(IEnumerable nobs, bool timedOnly = false) { List conns = RetrieveAuthenticatedConnections(); RebuildObservers(nobs, conns, timedOnly); CollectionCaches.Store(conns); } /// /// Rebuilds observers on all objects for connections. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [Obsolete("Use RebuildObservers IList variant instead.")] public void RebuildObservers(IEnumerable connections, bool timedOnly = false) { List nobCache = RetrieveOrderedSpawnedObjects(); RebuildObservers(nobCache, connections, timedOnly); CollectionCaches.Store(nobCache); } /// /// Rebuilds observers on NetworkObjects for connections. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [Obsolete("Use RebuildObservers IList variant instead.")] public void RebuildObservers(IEnumerable nobs, NetworkConnection conn, bool timedOnly = false) { List connCache = CollectionCaches.RetrieveList(conn); RebuildObservers(nobs, connCache, timedOnly); CollectionCaches.Store(connCache); } /// /// Rebuilds observers for connections on NetworkObject. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [Obsolete("Use RebuildObservers IList variant instead.")] public void RebuildObservers(NetworkObject networkObject, IEnumerable connections, bool timedOnly = false) { List nobCache = CollectionCaches.RetrieveList(networkObject); RebuildObservers(nobCache, connections, timedOnly); CollectionCaches.Store(nobCache); } /// /// Rebuilds observers on NetworkObjects for connections. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [Obsolete("Use RebuildObservers IList variant instead.")] public void RebuildObservers(IEnumerable nobs, IEnumerable conns, bool timedOnly = false) { List nobCache = CollectionCaches.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.Store(nobCache); } #endregion /// /// Rebuilds observers on NetworkObjects. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RebuildObservers(IList connections, bool timedOnly = false) { List nobCache = RetrieveOrderedSpawnedObjects(); RebuildObservers(nobCache, connections, timedOnly); CollectionCaches.Store(nobCache); } /// /// Rebuilds observers on NetworkObjects for connections. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] 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++) { _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.Store(nobCache); } /// /// Rebuilds observers for a connection on NetworkObject. /// [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.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); } /// /// Rebuilds observers for a connection on NetworkObject. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] 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. */ //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.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); } } }