// 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);
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
}
}