ObjectPool.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. //#define ANIMANCER_LOG_OBJECT_POOLING
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Text;
  6. using UnityEngine;
  7. namespace Animancer
  8. {
  9. /// <summary>Convenience methods for accessing <see cref="ObjectPool{T}"/>.</summary>
  10. /// https://kybernetik.com.au/animancer/api/Animancer/ObjectPool
  11. ///
  12. public static class ObjectPool
  13. {
  14. /************************************************************************************************************************/
  15. /// <summary>Returns a spare item if there are any, or creates a new one.</summary>
  16. /// <remarks>Remember to <see cref="Release{T}(T)"/> it when you are done.</remarks>
  17. public static T Acquire<T>()
  18. where T : class, new()
  19. => ObjectPool<T>.Acquire();
  20. /// <summary>Returns a spare `item` if there are any, or creates a new one.</summary>
  21. /// <remarks>Remember to <see cref="Release{T}(T)"/> it when you are done.</remarks>
  22. public static void Acquire<T>(out T item)
  23. where T : class, new()
  24. => item = ObjectPool<T>.Acquire();
  25. /************************************************************************************************************************/
  26. /// <summary>Adds the `item` to the list of spares so it can be reused.</summary>
  27. public static void Release<T>(T item)
  28. where T : class, new()
  29. => ObjectPool<T>.Release(item);
  30. /// <summary>Adds the `item` to the list of spares so it can be reused and sets it to <c>null</c>.</summary>
  31. public static void Release<T>(ref T item) where T : class, new()
  32. {
  33. ObjectPool<T>.Release(item);
  34. item = null;
  35. }
  36. /************************************************************************************************************************/
  37. /// <summary>An error message for when something has been modified after being released to the pool.</summary>
  38. public const string
  39. NotClearError = " They must be cleared before being released to the pool and not modified after that.";
  40. /************************************************************************************************************************/
  41. /// <summary>Returns a spare <see cref="List{T}"/> if there are any, or creates a new one.</summary>
  42. /// <remarks>Remember to <see cref="Release{T}(List{T})"/> it when you are done.</remarks>
  43. public static List<T> AcquireList<T>()
  44. {
  45. var list = ObjectPool<List<T>>.Acquire();
  46. AnimancerUtilities.Assert(list.Count == 0, "A pooled list is not empty." + NotClearError);
  47. return list;
  48. }
  49. /// <summary>Returns a spare <see cref="List{T}"/> if there are any, or creates a new one.</summary>
  50. /// <remarks>Remember to <see cref="Release{T}(List{T})"/> it when you are done.</remarks>
  51. public static void Acquire<T>(out List<T> list)
  52. => list = AcquireList<T>();
  53. /// <summary>Clears the `list` and adds it to the list of spares so it can be reused.</summary>
  54. public static void Release<T>(List<T> list)
  55. {
  56. list.Clear();
  57. ObjectPool<List<T>>.Release(list);
  58. }
  59. /// <summary>Clears the `list`, adds it to the list of spares so it can be reused, and sets it to <c>null</c>.</summary>
  60. public static void Release<T>(ref List<T> list)
  61. {
  62. Release(list);
  63. list = null;
  64. }
  65. /************************************************************************************************************************/
  66. /// <summary>Returns a spare <see cref="HashSet{T}"/> if there are any, or creates a new one.</summary>
  67. /// <remarks>Remember to <see cref="Release{T}(HashSet{T})"/> it when you are done.</remarks>
  68. public static HashSet<T> AcquireSet<T>()
  69. {
  70. var set = ObjectPool<HashSet<T>>.Acquire();
  71. AnimancerUtilities.Assert(set.Count == 0, "A pooled set is not empty." + NotClearError);
  72. return set;
  73. }
  74. /// <summary>Returns a spare <see cref="HashSet{T}"/> if there are any, or creates a new one.</summary>
  75. /// <remarks>Remember to <see cref="Release{T}(HashSet{T})"/> it when you are done.</remarks>
  76. public static void Acquire<T>(out HashSet<T> set)
  77. => set = AcquireSet<T>();
  78. /// <summary>Clears the `set` and adds it to the list of spares so it can be reused.</summary>
  79. public static void Release<T>(HashSet<T> set)
  80. {
  81. set.Clear();
  82. ObjectPool<HashSet<T>>.Release(set);
  83. }
  84. /// <summary>Clears the `set`, adds it to the list of spares so it can be reused, and sets it to <c>null</c>.</summary>
  85. public static void Release<T>(ref HashSet<T> set)
  86. {
  87. Release(set);
  88. set = null;
  89. }
  90. /************************************************************************************************************************/
  91. /// <summary>Returns a spare <see cref="StringBuilder"/> if there are any, or creates a new one.</summary>
  92. /// <remarks>Remember to <see cref="Release(StringBuilder)"/> it when you are done.</remarks>
  93. public static StringBuilder AcquireStringBuilder()
  94. {
  95. var builder = ObjectPool<StringBuilder>.Acquire();
  96. AnimancerUtilities.Assert(builder.Length == 0, $"A pooled {nameof(StringBuilder)} is not empty." + NotClearError);
  97. return builder;
  98. }
  99. /// <summary>Sets the <see cref="StringBuilder.Length"/> = 0 and adds it to the list of spares so it can be reused.</summary>
  100. public static void Release(StringBuilder builder)
  101. {
  102. builder.Length = 0;
  103. ObjectPool<StringBuilder>.Release(builder);
  104. }
  105. /// <summary>[Animancer Extension] Calls <see cref="StringBuilder.ToString()"/> and <see cref="Release(StringBuilder)"/>.</summary>
  106. public static string ReleaseToString(this StringBuilder builder)
  107. {
  108. var result = builder.ToString();
  109. Release(builder);
  110. return result;
  111. }
  112. /************************************************************************************************************************/
  113. /// <summary>Convenience wrappers for <see cref="ObjectPool{T}.Disposable"/>.</summary>
  114. public static class Disposable
  115. {
  116. /************************************************************************************************************************/
  117. /// <summary>
  118. /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
  119. /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`.
  120. /// </summary>
  121. public static ObjectPool<T>.Disposable Acquire<T>(out T item)
  122. where T : class, new()
  123. => new ObjectPool<T>.Disposable(out item);
  124. /************************************************************************************************************************/
  125. /// <summary>
  126. /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
  127. /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`.
  128. /// </summary>
  129. public static ObjectPool<List<T>>.Disposable AcquireList<T>(out List<T> list)
  130. {
  131. var disposable = new ObjectPool<List<T>>.Disposable(out list, onRelease: (l) => l.Clear());
  132. AnimancerUtilities.Assert(list.Count == 0, "A pooled list is not empty." + NotClearError);
  133. return disposable;
  134. }
  135. /************************************************************************************************************************/
  136. /// <summary>
  137. /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
  138. /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`.
  139. /// </summary>
  140. public static ObjectPool<HashSet<T>>.Disposable AcquireSet<T>(out HashSet<T> set)
  141. {
  142. var disposable = new ObjectPool<HashSet<T>>.Disposable(out set, onRelease: (s) => s.Clear());
  143. AnimancerUtilities.Assert(set.Count == 0, "A pooled set is not empty." + NotClearError);
  144. return disposable;
  145. }
  146. /************************************************************************************************************************/
  147. /// <summary>
  148. /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
  149. /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`.
  150. /// </summary>
  151. public static ObjectPool<GUIContent>.Disposable AcquireContent(out GUIContent content,
  152. string text = null, string tooltip = null, bool narrowText = true)
  153. {
  154. var disposable = new ObjectPool<GUIContent>.Disposable(out content, onRelease: (c) =>
  155. {
  156. c.text = null;
  157. c.tooltip = null;
  158. c.image = null;
  159. });
  160. #if UNITY_ASSERTIONS
  161. if (!string.IsNullOrEmpty(content.text) ||
  162. !string.IsNullOrEmpty(content.tooltip) ||
  163. content.image != null)
  164. {
  165. throw new UnityEngine.Assertions.AssertionException(
  166. $"A pooled {nameof(GUIContent)} is not cleared." + NotClearError,
  167. $"- {nameof(content.text)} = '{content.text}'" +
  168. $"\n- {nameof(content.tooltip)} = '{content.tooltip}'" +
  169. $"\n- {nameof(content.image)} = '{content.image}'");
  170. }
  171. #endif
  172. content.text = text;
  173. content.tooltip = tooltip;
  174. content.image = null;
  175. return disposable;
  176. }
  177. /************************************************************************************************************************/
  178. #if UNITY_EDITOR
  179. /// <summary>[Editor-Only]
  180. /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
  181. /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`.
  182. /// </summary>
  183. public static ObjectPool<GUIContent>.Disposable AcquireContent(out GUIContent content,
  184. UnityEditor.SerializedProperty property, bool narrowText = true)
  185. => AcquireContent(out content, property.displayName, property.tooltip, narrowText);
  186. #endif
  187. /************************************************************************************************************************/
  188. }
  189. /************************************************************************************************************************/
  190. }
  191. /************************************************************************************************************************/
  192. /// <summary>A simple object pooling system.</summary>
  193. /// <remarks><typeparamref name="T"/> must not inherit from <see cref="Component"/> or <see cref="ScriptableObject"/>.</remarks>
  194. /// https://kybernetik.com.au/animancer/api/Animancer/ObjectPool_1
  195. ///
  196. public static class ObjectPool<T> where T : class, new()
  197. {
  198. /************************************************************************************************************************/
  199. private static readonly List<T>
  200. Items = new List<T>();
  201. /************************************************************************************************************************/
  202. /// <summary>The number of spare items currently in the pool.</summary>
  203. public static int Count
  204. {
  205. get => Items.Count;
  206. set
  207. {
  208. var count = Items.Count;
  209. if (count < value)
  210. {
  211. if (Items.Capacity < value)
  212. Items.Capacity = Mathf.NextPowerOfTwo(value);
  213. do
  214. {
  215. Items.Add(new T());
  216. count++;
  217. }
  218. while (count < value);
  219. }
  220. else if (count > value)
  221. {
  222. Items.RemoveRange(value, count - value);
  223. }
  224. }
  225. }
  226. /************************************************************************************************************************/
  227. /// <summary>Increases the <see cref="Count"/> to equal the `count` if it was lower.</summary>
  228. public static void IncreaseCountTo(int count)
  229. {
  230. if (Count < count)
  231. Count = count;
  232. }
  233. /************************************************************************************************************************/
  234. /// <summary>The <see cref="List{T}.Capacity"/> of the internal list of spare items.</summary>
  235. public static int Capacity
  236. {
  237. get => Items.Capacity;
  238. set
  239. {
  240. if (Items.Count > value)
  241. Items.RemoveRange(value, Items.Count - value);
  242. Items.Capacity = value;
  243. }
  244. }
  245. /************************************************************************************************************************/
  246. /// <summary>Increases the <see cref="Capacity"/> to equal the `capacity` if it was lower.</summary>
  247. public static void IncreaseCapacityTo(int capacity)
  248. {
  249. if (Capacity < capacity)
  250. Capacity = capacity;
  251. }
  252. /************************************************************************************************************************/
  253. /// <summary>Returns a spare item if there are any, or creates a new one.</summary>
  254. /// <remarks>Remember to <see cref="Release(T)"/> it when you are done.</remarks>
  255. public static T Acquire()
  256. {
  257. var count = Items.Count;
  258. if (count == 0)
  259. {
  260. return new T();
  261. }
  262. else
  263. {
  264. count--;
  265. var item = Items[count];
  266. Items.RemoveAt(count);
  267. return item;
  268. }
  269. }
  270. /************************************************************************************************************************/
  271. /// <summary>Adds the `item` to the list of spares so it can be reused.</summary>
  272. public static void Release(T item)
  273. {
  274. AnimancerUtilities.Assert(item != null,
  275. $"Null objects must not be released into an {nameof(ObjectPool<T>)}.");
  276. Items.Add(item);
  277. }
  278. /************************************************************************************************************************/
  279. /// <summary>Returns a description of the state of this pool.</summary>
  280. public static string GetDetails()
  281. {
  282. return
  283. $"{typeof(T).Name}" +
  284. $" ({nameof(Count)} = {Items.Count}" +
  285. $", {nameof(Capacity)} = {Items.Capacity}" +
  286. ")";
  287. }
  288. /************************************************************************************************************************/
  289. /// <summary>
  290. /// An <see cref="IDisposable"/> to allow pooled objects to be acquired and released within <c>using</c>
  291. /// statements instead of needing to manually release everything.
  292. /// </summary>
  293. public readonly struct Disposable : IDisposable
  294. {
  295. /************************************************************************************************************************/
  296. /// <summary>The object acquired from the <see cref="ObjectPool{T}"/>.</summary>
  297. public readonly T Item;
  298. /// <summary>Called by <see cref="IDisposable.Dispose"/>.</summary>
  299. public readonly Action<T> OnRelease;
  300. /************************************************************************************************************************/
  301. /// <summary>
  302. /// Creates a new <see cref="Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
  303. /// <see cref="Item"/> and `item`.
  304. /// </summary>
  305. public Disposable(out T item, Action<T> onRelease = null)
  306. {
  307. Item = item = Acquire();
  308. OnRelease = onRelease;
  309. }
  310. /************************************************************************************************************************/
  311. void IDisposable.Dispose()
  312. {
  313. OnRelease?.Invoke(Item);
  314. Release(Item);
  315. }
  316. /************************************************************************************************************************/
  317. }
  318. /************************************************************************************************************************/
  319. }
  320. }