ObjectPool.cs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. namespace MonoGame.Extended.Collections
  6. {
  7. public enum ObjectPoolIsFullPolicy
  8. {
  9. ReturnNull,
  10. IncreaseSize,
  11. KillExisting,
  12. }
  13. public class ObjectPool<T> : IEnumerable<T>
  14. where T : class, IPoolable
  15. {
  16. private readonly Action<IPoolable> _returnToPoolDelegate;
  17. private readonly Deque<T> _freeItems; // circular buffer for O(1) operations
  18. private T _headNode; // linked list for iteration
  19. private T _tailNode;
  20. private readonly Func<T> _instantiationFunction;
  21. public ObjectPoolIsFullPolicy IsFullPolicy { get; }
  22. public int Capacity { get; private set; }
  23. public int TotalCount { get; private set; }
  24. public int AvailableCount => _freeItems.Count;
  25. public int InUseCount => TotalCount - AvailableCount;
  26. public event Action<T> ItemUsed;
  27. public event Action<T> ItemReturned;
  28. public ObjectPool(Func<T> instantiationFunc, int capacity = 16, ObjectPoolIsFullPolicy isFullPolicy = ObjectPoolIsFullPolicy.ReturnNull)
  29. {
  30. if (instantiationFunc == null)
  31. throw new ArgumentNullException(nameof(instantiationFunc));
  32. _returnToPoolDelegate = Return;
  33. _instantiationFunction = instantiationFunc;
  34. _freeItems = new Deque<T>(capacity);
  35. IsFullPolicy = isFullPolicy;
  36. Capacity = capacity;
  37. }
  38. public IEnumerator<T> GetEnumerator()
  39. {
  40. var node = _headNode;
  41. while (node != null)
  42. {
  43. yield return node;
  44. node = (T)node.NextNode;
  45. }
  46. }
  47. IEnumerator IEnumerable.GetEnumerator()
  48. {
  49. return GetEnumerator();
  50. }
  51. public T New()
  52. {
  53. if (!_freeItems.RemoveFromFront(out var poolable))
  54. {
  55. if (TotalCount <= Capacity)
  56. {
  57. poolable = CreateObject();
  58. }
  59. else
  60. {
  61. switch (IsFullPolicy)
  62. {
  63. case ObjectPoolIsFullPolicy.ReturnNull:
  64. return null;
  65. case ObjectPoolIsFullPolicy.IncreaseSize:
  66. Capacity++;
  67. poolable = CreateObject();
  68. break;
  69. case ObjectPoolIsFullPolicy.KillExisting:
  70. if (_headNode == null)
  71. return null;
  72. var newHeadNode = (T)_headNode.NextNode;
  73. _headNode.Return();
  74. _freeItems.RemoveFromBack(out poolable);
  75. _headNode = newHeadNode;
  76. break;
  77. default:
  78. throw new ArgumentOutOfRangeException();
  79. }
  80. }
  81. }
  82. Use(poolable);
  83. return poolable;
  84. }
  85. private T CreateObject()
  86. {
  87. TotalCount++;
  88. var item = _instantiationFunction();
  89. if (item == null)
  90. throw new NullReferenceException($"The created pooled object of type '{typeof(T).Name}' is null.");
  91. item.PreviousNode = _tailNode;
  92. item.NextNode = null;
  93. if (_headNode == null)
  94. _headNode = item;
  95. if (_tailNode != null)
  96. _tailNode.NextNode = item;
  97. _tailNode = item;
  98. return item;
  99. }
  100. private void Return(IPoolable item)
  101. {
  102. Debug.Assert(item != null);
  103. var poolable1 = (T) item;
  104. var previousNode = (T)item.PreviousNode;
  105. var nextNode = (T)item.NextNode;
  106. if (previousNode != null)
  107. previousNode.NextNode = nextNode;
  108. if (nextNode != null)
  109. nextNode.PreviousNode = previousNode;
  110. if (item == _headNode)
  111. _headNode = nextNode;
  112. if (item == _tailNode)
  113. _tailNode = previousNode;
  114. if (_tailNode != null)
  115. _tailNode.NextNode = null;
  116. _freeItems.AddToBack(poolable1);
  117. ItemReturned?.Invoke((T)item);
  118. }
  119. private void Use(T item)
  120. {
  121. item.Initialize(_returnToPoolDelegate);
  122. item.NextNode = null;
  123. if(_tailNode is null)
  124. {
  125. _headNode = item;
  126. _tailNode = item;
  127. item.PreviousNode = null;
  128. }
  129. else
  130. {
  131. item.PreviousNode = _tailNode;
  132. _tailNode.NextNode = item;
  133. _tailNode = item;
  134. }
  135. ItemUsed?.Invoke(item);
  136. }
  137. }
  138. }