using System; using System.Collections.Generic; using UnityEngine; namespace Projectiles { public sealed class ObjectCache : MonoBehaviour { // PUBLIC MEMBERS public int CachedCount { get { return _all.Count; } } public int BorrowedCount { get { return _borrowed.Count; } } // PRIVATE MEMBERS [SerializeField] private bool _hideCachedObjectsInHierarchy = true; [SerializeField] private List _precacheObjects; private readonly Dictionary> _cached = new(); private readonly Dictionary _borrowed = new(); private readonly List _deferred = new(); private readonly Stack _pool = new(); private readonly List _all = new(); // PUBLIC METHODS public T Get(T prefab, bool activate = true, bool createIfEmpty = true) where T : UnityEngine.Component { return Get(prefab, null, activate, createIfEmpty); } public GameObject Get(GameObject prefab, bool activate = true, bool createIfEmpty = true) { return Get(prefab, null, activate, createIfEmpty); } public T Get(T prefab, Transform parent, bool activate = true, bool createIfEmpty = true) where T : UnityEngine.Component { GameObject instance = Get(prefab.gameObject, parent, activate, createIfEmpty); return instance != null ? instance.GetComponent() : null; } public GameObject Get(GameObject prefab, Transform parent, bool activate = true, bool createIfEmpty = true) { if (_cached.TryGetValue(prefab, out Stack stack) == false) { stack = new Stack(); _cached[prefab] = stack; } if (stack.Count == 0) { if (createIfEmpty == true) { CreateInstance(prefab); } else { Debug.LogWarningFormat("Prefab {0} not available in cache, returning NULL", prefab.name); return null; } } GameObject instance = stack.Pop(); _borrowed[instance] = prefab; Transform instanceTransform = instance.transform; if (parent != null) { instanceTransform.SetParent(parent, false); } instanceTransform.localPosition = Vector3.zero; instanceTransform.localRotation = Quaternion.identity; instanceTransform.localScale = Vector3.one; if (activate == true) { instance.SetActive(true); } #if UNITY_EDITOR if (_hideCachedObjectsInHierarchy == true) { instance.hideFlags &= ~HideFlags.HideInHierarchy; } else { instance.name = prefab.name; } #endif return instance; } public void Return(UnityEngine.Component component, bool deactivate = true) { Return(component.gameObject, deactivate); } public void Return(GameObject instance, bool deactivate = true) { if (deactivate == true) { instance.SetActive(false); } instance.transform.SetParent(null, false); _cached[_borrowed[instance]].Push(instance); _borrowed.Remove(instance); #if UNITY_EDITOR if (_hideCachedObjectsInHierarchy == true) { instance.hideFlags |= HideFlags.HideInHierarchy; } else { instance.name = $"(Cached) {instance.name}"; } #endif } public void ReturnRange(List instances, bool deactivate = true) { for (int i = 0; i < instances.Count; i++) { Return(instances[i], deactivate); } } public void ReturnDeferred(GameObject instance, float delay) { DeferredReturn toReturn = _pool.Count > 0 ? _pool.Pop() : new DeferredReturn(); toReturn.GameObject = instance; toReturn.Delay = delay; _deferred.Add(toReturn); } public void Prepare(GameObject prefab, int desiredCount) { if (_cached.TryGetValue(prefab, out Stack stack) == false) { stack = new Stack(); _cached[prefab] = stack; } while (stack.Count < desiredCount) { CreateInstance(prefab); } } // MONOBEHAVIOUR private void OnEnable() { foreach (CacheObject cacheObject in _precacheObjects) { _cached[cacheObject.GameObject] = new Stack(); for (int i = 0; i < cacheObject.Count; ++i) { CreateInstance(cacheObject.GameObject); } } } private void OnDisable() { foreach (var item in _borrowed) { GameObject go = item.Key; bool shouldReturn = go != null; foreach (var deferredItem in _deferred) { if (go == deferredItem.GameObject) { shouldReturn = false; break; } } if (shouldReturn == true) { Debug.LogWarning($"Object {go.name} from cache was not returned and will be destroyed"); } } _deferred.Clear(); _borrowed.Clear(); _cached.Clear(); foreach (GameObject instance in _all) { Destroy(instance); } _all.Clear(); } private void Update() { for (int i = _deferred.Count; i --> 0;) { DeferredReturn deferred = _deferred[i]; deferred.Delay -= Time.deltaTime; if (deferred.Delay > 0.0f) continue; _deferred.RemoveBySwap(i); Return(deferred.GameObject, true); deferred.Reset(); _pool.Push(deferred); } } // PRIVATE METHODS private void CreateInstance(GameObject prefab) { GameObject instance = Instantiate(prefab, null, false); instance.name = prefab.name; instance.SetActive(false); _cached[prefab].Push(instance); _all.Add(instance); #if UNITY_EDITOR if (_hideCachedObjectsInHierarchy == true) { instance.hideFlags |= HideFlags.HideInHierarchy; } #endif } // HELPERS [Serializable] private sealed class CacheObject { public int Count; public GameObject GameObject; } private sealed class DeferredReturn { public GameObject GameObject; public float Delay; public void Reset() { GameObject = null; Delay = 0.0f; } } } }