// In 2021.1 this is part of Unity #if !UNITY_2021_1_OR_NEWER using System; namespace UnityEngine.Pool { internal class LinkedPool : IDisposable, IObjectPool where T : class { internal class LinkedPoolItem { internal LinkedPoolItem poolNext; internal T value; } readonly Func m_CreateFunc; readonly Action m_ActionOnGet; readonly Action m_ActionOnRelease; readonly Action m_ActionOnDestroy; readonly int m_Limit; // Used to prevent catastrophic memory retention. internal LinkedPoolItem m_PoolFirst; // The pool of available T objects internal LinkedPoolItem m_NextAvailableListItem; // When Get is called we place the node here for reuse and to prevent GC bool m_CollectionCheck; public LinkedPool(Func createFunc, Action actionOnGet = null, Action actionOnRelease = null, Action actionOnDestroy = null, bool collectionCheck = true, int maxSize = 10000) { if (createFunc == null) throw new ArgumentNullException(nameof(createFunc)); if (maxSize <= 0) throw new ArgumentException(nameof(maxSize), "Max size must be greater than 0"); m_CreateFunc = createFunc; m_ActionOnGet = actionOnGet; m_ActionOnRelease = actionOnRelease; m_ActionOnDestroy = actionOnDestroy; m_Limit = maxSize; m_CollectionCheck = collectionCheck; } public int CountInactive { get; private set; } public T Get() { T item = null; if (m_PoolFirst == null) { item = m_CreateFunc(); } else { var first = m_PoolFirst; item = first.value; m_PoolFirst = first.poolNext; // Add the empty node to our pool for reuse and to prevent GC first.poolNext = m_NextAvailableListItem; m_NextAvailableListItem = first; m_NextAvailableListItem.value = null; --CountInactive; } m_ActionOnGet?.Invoke(item); return item; } public PooledObject Get(out T v) => new PooledObject(v = Get(), this); public void Release(T item) { #if UNITY_EDITOR // keep heavy checks in editor if (m_CollectionCheck) { var listItem = m_PoolFirst; while (listItem != null) { if (ReferenceEquals(listItem.value, item)) throw new InvalidOperationException("Trying to release an object that has already been released to the pool."); listItem = listItem.poolNext; } } #endif m_ActionOnRelease?.Invoke(item); if (CountInactive < m_Limit) { LinkedPoolItem poolItem = m_NextAvailableListItem; if (poolItem == null) { poolItem = new LinkedPoolItem(); } else { m_NextAvailableListItem = poolItem.poolNext; } poolItem.value = item; poolItem.poolNext = m_PoolFirst; m_PoolFirst = poolItem; ++CountInactive; } else { m_ActionOnDestroy?.Invoke(item); } } public void Clear() { if (m_ActionOnDestroy != null) { for (var itr = m_PoolFirst; itr != null; itr = itr.poolNext) { m_ActionOnDestroy(itr.value); } } m_PoolFirst = null; m_NextAvailableListItem = null; CountInactive = 0; } public void Dispose() { // Ensure we do a clear so the destroy action can be called. Clear(); } } } #endif