123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
- //#define ANIMANCER_LOG_OBJECT_POOLING
- using System;
- using System.Collections.Generic;
- using System.Text;
- using UnityEngine;
- namespace Animancer
- {
- /// <summary>Convenience methods for accessing <see cref="ObjectPool{T}"/>.</summary>
- /// https://kybernetik.com.au/animancer/api/Animancer/ObjectPool
- ///
- public static class ObjectPool
- {
- /************************************************************************************************************************/
- /// <summary>Returns a spare item if there are any, or creates a new one.</summary>
- /// <remarks>Remember to <see cref="Release{T}(T)"/> it when you are done.</remarks>
- public static T Acquire<T>()
- where T : class, new()
- => ObjectPool<T>.Acquire();
- /// <summary>Returns a spare `item` if there are any, or creates a new one.</summary>
- /// <remarks>Remember to <see cref="Release{T}(T)"/> it when you are done.</remarks>
- public static void Acquire<T>(out T item)
- where T : class, new()
- => item = ObjectPool<T>.Acquire();
- /************************************************************************************************************************/
- /// <summary>Adds the `item` to the list of spares so it can be reused.</summary>
- public static void Release<T>(T item)
- where T : class, new()
- => ObjectPool<T>.Release(item);
- /// <summary>Adds the `item` to the list of spares so it can be reused and sets it to <c>null</c>.</summary>
- public static void Release<T>(ref T item) where T : class, new()
- {
- ObjectPool<T>.Release(item);
- item = null;
- }
- /************************************************************************************************************************/
- /// <summary>An error message for when something has been modified after being released to the pool.</summary>
- public const string
- NotClearError = " They must be cleared before being released to the pool and not modified after that.";
- /************************************************************************************************************************/
- /// <summary>Returns a spare <see cref="List{T}"/> if there are any, or creates a new one.</summary>
- /// <remarks>Remember to <see cref="Release{T}(List{T})"/> it when you are done.</remarks>
- public static List<T> AcquireList<T>()
- {
- var list = ObjectPool<List<T>>.Acquire();
- AnimancerUtilities.Assert(list.Count == 0, "A pooled list is not empty." + NotClearError);
- return list;
- }
- /// <summary>Returns a spare <see cref="List{T}"/> if there are any, or creates a new one.</summary>
- /// <remarks>Remember to <see cref="Release{T}(List{T})"/> it when you are done.</remarks>
- public static void Acquire<T>(out List<T> list)
- => list = AcquireList<T>();
- /// <summary>Clears the `list` and adds it to the list of spares so it can be reused.</summary>
- public static void Release<T>(List<T> list)
- {
- list.Clear();
- ObjectPool<List<T>>.Release(list);
- }
- /// <summary>Clears the `list`, adds it to the list of spares so it can be reused, and sets it to <c>null</c>.</summary>
- public static void Release<T>(ref List<T> list)
- {
- Release(list);
- list = null;
- }
- /************************************************************************************************************************/
- /// <summary>Returns a spare <see cref="HashSet{T}"/> if there are any, or creates a new one.</summary>
- /// <remarks>Remember to <see cref="Release{T}(HashSet{T})"/> it when you are done.</remarks>
- public static HashSet<T> AcquireSet<T>()
- {
- var set = ObjectPool<HashSet<T>>.Acquire();
- AnimancerUtilities.Assert(set.Count == 0, "A pooled set is not empty." + NotClearError);
- return set;
- }
- /// <summary>Returns a spare <see cref="HashSet{T}"/> if there are any, or creates a new one.</summary>
- /// <remarks>Remember to <see cref="Release{T}(HashSet{T})"/> it when you are done.</remarks>
- public static void Acquire<T>(out HashSet<T> set)
- => set = AcquireSet<T>();
- /// <summary>Clears the `set` and adds it to the list of spares so it can be reused.</summary>
- public static void Release<T>(HashSet<T> set)
- {
- set.Clear();
- ObjectPool<HashSet<T>>.Release(set);
- }
- /// <summary>Clears the `set`, adds it to the list of spares so it can be reused, and sets it to <c>null</c>.</summary>
- public static void Release<T>(ref HashSet<T> set)
- {
- Release(set);
- set = null;
- }
- /************************************************************************************************************************/
- /// <summary>Returns a spare <see cref="StringBuilder"/> if there are any, or creates a new one.</summary>
- /// <remarks>Remember to <see cref="Release(StringBuilder)"/> it when you are done.</remarks>
- public static StringBuilder AcquireStringBuilder()
- {
- var builder = ObjectPool<StringBuilder>.Acquire();
- AnimancerUtilities.Assert(builder.Length == 0, $"A pooled {nameof(StringBuilder)} is not empty." + NotClearError);
- return builder;
- }
- /// <summary>Sets the <see cref="StringBuilder.Length"/> = 0 and adds it to the list of spares so it can be reused.</summary>
- public static void Release(StringBuilder builder)
- {
- builder.Length = 0;
- ObjectPool<StringBuilder>.Release(builder);
- }
- /// <summary>[Animancer Extension] Calls <see cref="StringBuilder.ToString()"/> and <see cref="Release(StringBuilder)"/>.</summary>
- public static string ReleaseToString(this StringBuilder builder)
- {
- var result = builder.ToString();
- Release(builder);
- return result;
- }
- /************************************************************************************************************************/
- /// <summary>Convenience wrappers for <see cref="ObjectPool{T}.Disposable"/>.</summary>
- public static class Disposable
- {
- /************************************************************************************************************************/
- /// <summary>
- /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
- /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`.
- /// </summary>
- public static ObjectPool<T>.Disposable Acquire<T>(out T item)
- where T : class, new()
- => new ObjectPool<T>.Disposable(out item);
- /************************************************************************************************************************/
- /// <summary>
- /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
- /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`.
- /// </summary>
- public static ObjectPool<List<T>>.Disposable AcquireList<T>(out List<T> list)
- {
- var disposable = new ObjectPool<List<T>>.Disposable(out list, onRelease: (l) => l.Clear());
- AnimancerUtilities.Assert(list.Count == 0, "A pooled list is not empty." + NotClearError);
- return disposable;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
- /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`.
- /// </summary>
- public static ObjectPool<HashSet<T>>.Disposable AcquireSet<T>(out HashSet<T> set)
- {
- var disposable = new ObjectPool<HashSet<T>>.Disposable(out set, onRelease: (s) => s.Clear());
- AnimancerUtilities.Assert(set.Count == 0, "A pooled set is not empty." + NotClearError);
- return disposable;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
- /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`.
- /// </summary>
- public static ObjectPool<GUIContent>.Disposable AcquireContent(out GUIContent content,
- string text = null, string tooltip = null, bool narrowText = true)
- {
- var disposable = new ObjectPool<GUIContent>.Disposable(out content, onRelease: (c) =>
- {
- c.text = null;
- c.tooltip = null;
- c.image = null;
- });
- #if UNITY_ASSERTIONS
- if (!string.IsNullOrEmpty(content.text) ||
- !string.IsNullOrEmpty(content.tooltip) ||
- content.image != null)
- {
- throw new UnityEngine.Assertions.AssertionException(
- $"A pooled {nameof(GUIContent)} is not cleared." + NotClearError,
- $"- {nameof(content.text)} = '{content.text}'" +
- $"\n- {nameof(content.tooltip)} = '{content.tooltip}'" +
- $"\n- {nameof(content.image)} = '{content.image}'");
- }
- #endif
- content.text = text;
- content.tooltip = tooltip;
- content.image = null;
- return disposable;
- }
- /************************************************************************************************************************/
- #if UNITY_EDITOR
- /// <summary>[Editor-Only]
- /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
- /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`.
- /// </summary>
- public static ObjectPool<GUIContent>.Disposable AcquireContent(out GUIContent content,
- UnityEditor.SerializedProperty property, bool narrowText = true)
- => AcquireContent(out content, property.displayName, property.tooltip, narrowText);
- #endif
- /************************************************************************************************************************/
- }
- /************************************************************************************************************************/
- }
- /************************************************************************************************************************/
- /// <summary>A simple object pooling system.</summary>
- /// <remarks><typeparamref name="T"/> must not inherit from <see cref="Component"/> or <see cref="ScriptableObject"/>.</remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer/ObjectPool_1
- ///
- public static class ObjectPool<T> where T : class, new()
- {
- /************************************************************************************************************************/
- private static readonly List<T>
- Items = new List<T>();
- /************************************************************************************************************************/
- /// <summary>The number of spare items currently in the pool.</summary>
- public static int Count
- {
- get => Items.Count;
- set
- {
- var count = Items.Count;
- if (count < value)
- {
- if (Items.Capacity < value)
- Items.Capacity = Mathf.NextPowerOfTwo(value);
- do
- {
- Items.Add(new T());
- count++;
- }
- while (count < value);
- }
- else if (count > value)
- {
- Items.RemoveRange(value, count - value);
- }
- }
- }
- /************************************************************************************************************************/
- /// <summary>Increases the <see cref="Count"/> to equal the `count` if it was lower.</summary>
- public static void IncreaseCountTo(int count)
- {
- if (Count < count)
- Count = count;
- }
- /************************************************************************************************************************/
- /// <summary>The <see cref="List{T}.Capacity"/> of the internal list of spare items.</summary>
- public static int Capacity
- {
- get => Items.Capacity;
- set
- {
- if (Items.Count > value)
- Items.RemoveRange(value, Items.Count - value);
- Items.Capacity = value;
- }
- }
- /************************************************************************************************************************/
- /// <summary>Increases the <see cref="Capacity"/> to equal the `capacity` if it was lower.</summary>
- public static void IncreaseCapacityTo(int capacity)
- {
- if (Capacity < capacity)
- Capacity = capacity;
- }
- /************************************************************************************************************************/
- /// <summary>Returns a spare item if there are any, or creates a new one.</summary>
- /// <remarks>Remember to <see cref="Release(T)"/> it when you are done.</remarks>
- public static T Acquire()
- {
- var count = Items.Count;
- if (count == 0)
- {
- return new T();
- }
- else
- {
- count--;
- var item = Items[count];
- Items.RemoveAt(count);
- return item;
- }
- }
- /************************************************************************************************************************/
- /// <summary>Adds the `item` to the list of spares so it can be reused.</summary>
- public static void Release(T item)
- {
- AnimancerUtilities.Assert(item != null,
- $"Null objects must not be released into an {nameof(ObjectPool<T>)}.");
- Items.Add(item);
- }
- /************************************************************************************************************************/
- /// <summary>Returns a description of the state of this pool.</summary>
- public static string GetDetails()
- {
- return
- $"{typeof(T).Name}" +
- $" ({nameof(Count)} = {Items.Count}" +
- $", {nameof(Capacity)} = {Items.Capacity}" +
- ")";
- }
- /************************************************************************************************************************/
- /// <summary>
- /// An <see cref="IDisposable"/> to allow pooled objects to be acquired and released within <c>using</c>
- /// statements instead of needing to manually release everything.
- /// </summary>
- public readonly struct Disposable : IDisposable
- {
- /************************************************************************************************************************/
- /// <summary>The object acquired from the <see cref="ObjectPool{T}"/>.</summary>
- public readonly T Item;
- /// <summary>Called by <see cref="IDisposable.Dispose"/>.</summary>
- public readonly Action<T> OnRelease;
- /************************************************************************************************************************/
- /// <summary>
- /// Creates a new <see cref="Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the
- /// <see cref="Item"/> and `item`.
- /// </summary>
- public Disposable(out T item, Action<T> onRelease = null)
- {
- Item = item = Acquire();
- OnRelease = onRelease;
- }
- /************************************************************************************************************************/
- void IDisposable.Dispose()
- {
- OnRelease?.Invoke(Item);
- Release(Item);
- }
- /************************************************************************************************************************/
- }
- /************************************************************************************************************************/
- }
- }
|