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