Code/ObjectPool.cs
using System;
using System.Collections.Generic;

namespace ObjectPools;

public class ObjectPool<T> : IDisposable where T : class
{
	///<summary>How many objects have been created.</summary>
	public int ObjectCountAll => _objectCountAll;

	///<summary>How many objects are currently in the pool.</summary>
	public int PoolCount => _pool.Count;

	private readonly List<T> _pool;

	private readonly Func<T> _onCreate;
	private readonly Action<T> _onGet;
	private readonly Action<T> _onRelease;
	private readonly Action<T> _onDestroy;

	private readonly bool _collectionCheck;

	private readonly int _maxAmount;
	private int _objectCountAll;

	/// <param name="defaultCapacity">The default capacity of the pool.</param>
	/// <param name="maxAmount">The max amount of items that the pool can hold before it starts to destroy them.</param>
	/// <param name="collectionCheck">A check to make sure an item isn't released to the pool more than once.</param>
	public ObjectPool(Func<T> onCreate, Action<T> onGet = null, Action<T> onRelease = null, Action<T> onDestroy = null, int defaultCapacity = 10, int maxAmount = 10000, bool collectionCheck = true)
	{
		if (onCreate == null)
		{
			Log.Error(nameof(onCreate) + " can't be null!");
			return;
		}
		if (maxAmount <= 0)
		{
			Log.Error(nameof(maxAmount) + " can't be 0 or less!");
			return;
		}

		_pool = new(defaultCapacity);

		_onCreate = onCreate;
		_onGet = onGet;
		_onRelease = onRelease;
		_onDestroy = onDestroy;

		_collectionCheck = collectionCheck;

		_maxAmount = maxAmount;
	}
	
	/// <summary>Creates items and adds them to the pool.</summary>
	/// <param name="amount">How many items to add to the pool.</param>
	/// <param name="useOnRelease">If the created item should go through the OnRelease function after being created.</param>
	public void Prewarm(int amount, bool useOnRelease = true)
	{
		if (_pool.Count < _maxAmount)
		{
			for (int i = 0; i < amount; i++)
			{
				T item = _onCreate();

				if (useOnRelease)
				{
					_onRelease?.Invoke(item);
				}

				_pool.Add(item);
				_objectCountAll++;
			}
		}
	}

	///<summary>Get an item from the pool.</summary>
	public T Get()
	{
		T item = null;

		if (_pool.Count > 0)
		{
			item = _pool[^1];
			_pool.RemoveAt(_pool.Count - 1);
		}
		else
		{
			item = _onCreate();
			_objectCountAll++;
		}
		
		_onGet?.Invoke(item);

		return item;
	}

	///<summary>Give back an item to the pool</summary>
	public void Release(T item)
	{
		if (_collectionCheck && _pool.Count > 0 && _pool.Contains(item))
		{
			Log.Error("Trying to release an item that's already in the pool!");
			return;
		}

		_onRelease?.Invoke(item);

		if (PoolCount < _maxAmount)
		{
			_pool.Add(item);
		}
		else
		{
			_objectCountAll--;
			_onDestroy?.Invoke(item);
		}
	}

	public void Dispose()
	{
		if (_onDestroy != null)
		{
			for (int i = _pool.Count - 1; i >= 0; i--)
			{
				_onDestroy(_pool[i]);
			}
		}

		_pool.Clear();
		_objectCountAll = 0;
	}
}