using System; using System.Collections.Generic; using System.Linq; namespace Boxfriend.Utils { /// /// Pools a specific number of objects, will reuse oldest active objects when all objects are in use. /// public class ObjectPoolCircular where T : class { private Queue _activeQueue, _inactiveQueue; private readonly Func _objectCreator; private readonly Action _returnObjectToPool, _getObjectFromPool, _destroyObject; private readonly int _size; /// /// Total number of objects in the pool. /// public int Count => _size; /// /// Total number of currently active pooled objects /// public int ActiveCount => _activeQueue.Count; /// /// Total number of currently inactive pooled objects /// public int InactiveCount => _inactiveQueue.Count; /// Creates and returns an object of the specified type. /// Action called on object when pulled from the pool or created. /// Action called on object when returned to pool. /// Action called on object when it is to be destroyed. Can be null /// Total number of objects in the pool /// Size must be greater than zero /// public ObjectPoolCircular (Func createObject, Action getObjectFromPool, Action returnObjectToPool, Action onDestroyObject = null, int size = 100) { if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "size must be greater than zero"); _returnObjectToPool = returnObjectToPool ?? throw new ArgumentNullException(nameof(returnObjectToPool)); _getObjectFromPool = getObjectFromPool ?? throw new ArgumentNullException(nameof(getObjectFromPool)); _objectCreator = createObject ?? throw new ArgumentNullException(nameof(createObject)); _destroyObject = onDestroyObject; _size = size; _inactiveQueue = new(size); _activeQueue = new(size); for (var i = 0; i < size; i++) { var obj = _objectCreator(); _returnObjectToPool(obj); _inactiveQueue.Enqueue(obj); } } /// /// Gets an object from the pool or reuses the oldest active object if all pooled objects are in use. Calls on the object /// Will call if reusing an active object. /// public T FromPool () { if(_inactiveQueue.Count + _activeQueue.Count == 0) throw new InvalidOperationException("Object pool has been cleared, there is nothing left to get"); T poolObject; if(_inactiveQueue.Count == 0) { poolObject = _activeQueue.Dequeue(); _returnObjectToPool(poolObject); } else { poolObject = _inactiveQueue.Dequeue(); } _getObjectFromPool(poolObject); _activeQueue.Enqueue(poolObject); return poolObject; } /// /// Adds an item to the pool and calls on it /// Generates garbage if item is not the oldest object pulled from the pool /// /// Item to be added public void ToPool (T item) { if (item == null) throw new ArgumentNullException(nameof(item)); if(_activeQueue.Peek() == item) _activeQueue.Dequeue(); else _activeQueue = new Queue(_activeQueue.Where(x => x != item)); _returnObjectToPool(item); _inactiveQueue.Enqueue(item); } /// /// Returns all active items to the inactive queue /// public void ReturnAllToPool() { while(ActiveCount > 0) { var obj = _activeQueue.Dequeue(); _returnObjectToPool(obj); _inactiveQueue.Enqueue(obj); } } /// /// Removes all items from the pool, calling on it if not null. /// Does not call . /// public void EmptyPool() { if(_destroyObject is null) { _activeQueue.Clear(); _inactiveQueue.Clear(); return; } while(_activeQueue.Count > 0) { var obj = _activeQueue.Dequeue(); _destroyObject(obj); } while(_inactiveQueue.Count > 0) { var obj = _inactiveQueue.Dequeue(); _destroyObject(obj); } } } }