using FishNet.Managing; using FishNet.Managing.Object; using FishNet.Object; using FishNet.Utility.Extension; using GameKit.Dependencies.Utilities; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; namespace FishNet.Utility.Performance { public class DefaultObjectPool : ObjectPool { #region Public. /// /// Cache for pooled NetworkObjects. /// Key: CollectionId. /// public IReadOnlyList>> Cache => _cache; private List>> _cache = new(); #endregion #region Serialized. /// /// True if to use object pooling. /// [Tooltip("True if to use object pooling.")] [SerializeField] private bool _enabled = true; #endregion #region Private. /// /// Current count of the cache collection. /// private int _cacheCount = 0; #endregion #pragma warning disable CS0672 // Member overrides obsolete member public override NetworkObject RetrieveObject(int prefabId, ushort collectionId, Transform parent = null, Vector3? nullablePosition = null, Quaternion? nullableRotation = null, Vector3? nullableScale = null, bool makeActive = true, bool asServer = true) #pragma warning restore CS0672 // Member overrides obsolete member { ObjectPoolRetrieveOption options = ObjectPoolRetrieveOption.Unset; if (makeActive) options |= ObjectPoolRetrieveOption.MakeActive; return RetrieveObject(prefabId, collectionId, options, parent, nullablePosition, nullableRotation, nullableScale, asServer); } /// /// Returns an object that has been stored. A new object will be created if no stored objects are available. /// /// PrefabId of the object to return. /// CollectionId of the object to return. /// True if being called on the server side. /// public override NetworkObject RetrieveObject(int prefabId, ushort collectionId, ObjectPoolRetrieveOption options, Transform parent = null, Vector3? nullablePosition = null, Quaternion? nullableRotation = null, Vector3? nullableScale = null, bool asServer = true) { bool makeActive = options.FastContains(ObjectPoolRetrieveOption.MakeActive); bool localSpace = options.FastContains(ObjectPoolRetrieveOption.LocalSpace); if (!_enabled) return GetFromInstantiate(); Stack cache = GetCache(collectionId, prefabId, createIfMissing: true); NetworkObject nob = null; //Iterate until nob is populated just in case cache entries have been destroyed. while (nob == null) { if (cache.TryPop(out nob)) { if (nob != null) { nob.transform.SetParent(parent); if (localSpace) nob.transform.SetLocalPositionRotationAndScale(nullablePosition, nullableRotation, nullableScale); else nob.transform.SetWorldPositionRotationAndScale(nullablePosition, nullableRotation, nullableScale); if (makeActive) nob.gameObject.SetActive(true); return nob; } } //Nothing left in cache. else { break; } } //Fall through, nothing in cache. return GetFromInstantiate(); //Returns a network object via instantation. NetworkObject GetFromInstantiate() { NetworkObject prefab = GetPrefab(prefabId, collectionId, asServer); if (prefab == null) { return null; } else { NetworkObject result; Vector3 scale; if (localSpace) { prefab.transform.OutLocalPropertyValues(nullablePosition, nullableRotation, nullableScale, out Vector3 pos, out Quaternion rot, out scale); if (parent != null) { //Convert pos and rot to world values for the instantiate. pos = parent.TransformPoint(pos); rot = (parent.rotation * rot); } result = Instantiate(prefab, pos, rot, parent); } else { prefab.transform.OutWorldPropertyValues(nullablePosition, nullableRotation, nullableScale, out Vector3 pos, out Quaternion rot, out scale); result = Instantiate(prefab, pos, rot, parent); } result.transform.localScale = scale; if (makeActive) result.gameObject.SetActive(true); return result; } } } /// /// Returns a prefab for prefab and collectionId. /// public override NetworkObject GetPrefab(int prefabId, ushort collectionId, bool asServer) { PrefabObjects po = base.NetworkManager.GetPrefabObjects(collectionId, false); return po.GetObject(asServer, prefabId); } /// /// Stores an object into the pool. /// /// Object to store. /// True if being called on the server side. /// public override void StoreObject(NetworkObject instantiated, bool asServer) { //Pooling is not enabled. if (!_enabled) { Destroy(instantiated.gameObject); return; } //Get all children as well and reset state on them. List nestedNobs = instantiated.GetNetworkObjects(GetNetworkObjectOption.All); foreach (NetworkObject nob in nestedNobs) nob.ResetState(asServer); CollectionCaches.Store(nestedNobs); //Set root inactive. instantiated.gameObject.SetActive(false); Stack cache = GetCache(instantiated.SpawnableCollectionId, instantiated.PrefabId, createIfMissing: true); cache.Push(instantiated); } /// /// Instantiates a number of objects and adds them to the pool. /// /// Prefab to cache. /// Quantity to spawn. /// True if storing prefabs for the server collection. This is only applicable when using DualPrefabObjects. #pragma warning disable CS0672 // Member overrides obsolete member public override void CacheObjects(NetworkObject prefab, int count, bool asServer) => StorePrefabObjects(prefab, count, asServer); #pragma warning restore CS0672 // Member overrides obsolete member /// /// Instantiates a number of objects and adds them to the pool. /// /// Prefab to cache. /// Quantity to spawn. /// True if storing prefabs for the server collection. This is only applicable when using DualPrefabObjects. /// Prefabs instantiated and added to cache. public override List StorePrefabObjects(NetworkObject prefab, int count, bool asServer) { if (!_enabled) return null; if (count <= 0) return null; if (prefab == null) return null; if (prefab.PrefabId == NetworkObject.UNSET_PREFABID_VALUE) { NetworkManagerExtensions.LogError($"Pefab {prefab.name} has an invalid prefabId and cannot be cached."); return null; } List added = new(); Stack cache = GetCache(prefab.SpawnableCollectionId, prefab.PrefabId, createIfMissing: true); for (int i = 0; i < count; i++) { NetworkObject nob = Instantiate(prefab); nob.gameObject.SetActive(false); cache.Push(nob); added.Add(nob); } return added; } /// /// Clears pooled objects for a specific NetworkObject. /// /// Prefab or Instantiated NetworkObject to clear pool for. /// This will clear the entire pool for the specified object. public void ClearPool(NetworkObject nob) { if (!_enabled) return; if (nob == null) return; int spawnableCollectionId = nob.SpawnableCollectionId; Stack stack = GetCache(spawnableCollectionId, nob.PrefabId, createIfMissing: false); if (stack == null) return; DestroyStackNetworkObjectsAndClear(stack); _cache[spawnableCollectionId].Clear(); } /// /// Clears all pooled objects. /// public void ClearPool() { int count = _cache.Count; for (int i = 0; i < count; i++) ClearPool(i); } /// /// Clears a pool destroying objects for a SpawnableCollectionId. /// /// CollectionId to clear for. public void ClearPool(int spawnableCollectionId) { if (spawnableCollectionId >= _cacheCount) return; Dictionary> dict = _cache[spawnableCollectionId]; foreach (Stack item in dict.Values) DestroyStackNetworkObjectsAndClear(item); dict.Clear(); } /// /// Gets a cache for an id or creates one if does not exist. /// /// public Stack GetCache(int collectionId, int prefabId, bool createIfMissing) { if (collectionId >= _cacheCount) { //Do not create if missing. if (!createIfMissing) return null; //Add more to the cache. while (_cache.Count <= collectionId) { Dictionary> dict = new(); _cache.Add(dict); } _cacheCount = _cache.Count; } Dictionary> dictionary = _cache[collectionId]; //No cache for prefabId yet, make one. if (!dictionary.TryGetValueIL2CPP(prefabId, out Stack cache)) { if (createIfMissing) { cache = new(); dictionary[prefabId] = cache; } } return cache; } [Obsolete("Use GetCache(int, int, bool)")] public Stack GetOrCreateCache(int collectionId, int prefabId) => GetCache(collectionId, prefabId, createIfMissing: true); /// /// Destroys all NetworkObjects within a stack and clears the stack. /// private void DestroyStackNetworkObjectsAndClear(Stack stack) { foreach (NetworkObject networkObject in stack) { if (networkObject != null) Destroy(networkObject.gameObject); } stack.Clear(); } } }