You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

267 lines
5.8 KiB
C#

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<CacheObject> _precacheObjects;
private readonly Dictionary<GameObject, Stack<GameObject>> _cached = new();
private readonly Dictionary<GameObject, GameObject> _borrowed = new();
private readonly List<DeferredReturn> _deferred = new();
private readonly Stack<DeferredReturn> _pool = new();
private readonly List<GameObject> _all = new();
// PUBLIC METHODS
public T Get<T>(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>(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<T>() : null;
}
public GameObject Get(GameObject prefab, Transform parent, bool activate = true, bool createIfEmpty = true)
{
if (_cached.TryGetValue(prefab, out Stack<GameObject> stack) == false)
{
stack = new Stack<GameObject>();
_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<GameObject> 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<GameObject> stack) == false)
{
stack = new Stack<GameObject>();
_cached[prefab] = stack;
}
while (stack.Count < desiredCount)
{
CreateInstance(prefab);
}
}
// MONOBEHAVIOUR
private void OnEnable()
{
foreach (CacheObject cacheObject in _precacheObjects)
{
_cached[cacheObject.GameObject] = new Stack<GameObject>();
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;
}
}
}
}