#if !UNITY_EDITOR
// Extra optimizations when not running in the editor, but less error checking
#define ASTAR_OPTIMIZE_POOLING
#endif
using System;
using System.Collections.Generic;
namespace Pathfinding.Util {
///
/// Lightweight List Pool.
/// Handy class for pooling lists of type T.
///
/// Usage:
/// - Claim a new list using List foo = ListPool.Claim ();
/// - Use it and do stuff with it
/// - Release it with ListPool.Release (foo);
///
/// You do not need to clear the list before releasing it.
/// After you have released a list, you should never use it again, if you do use it, you will
/// mess things up quite badly in the worst case.
///
/// \since Version 3.2
/// See: Pathfinding.Util.StackPool
///
public static class ListPool {
/// Internal pool
static readonly List > pool = new List >();
#if !ASTAR_NO_POOLING
static readonly List > largePool = new List >();
static readonly HashSet > inPool = new HashSet >();
#endif
///
/// When requesting a list with a specified capacity, search max this many lists in the pool before giving up.
/// Must be greater or equal to one.
///
const int MaxCapacitySearchLength = 8;
const int LargeThreshold = 5000;
const int MaxLargePoolSize = 8;
///
/// Claim a list.
/// Returns a pooled list if any are in the pool.
/// Otherwise it creates a new one.
/// After usage, this list should be released using the Release function (though not strictly necessary).
///
public static List Claim () {
#if ASTAR_NO_POOLING
return new List();
#else
lock (pool) {
if (pool.Count > 0) {
List ls = pool[pool.Count-1];
pool.RemoveAt(pool.Count-1);
inPool.Remove(ls);
return ls;
}
return new List();
}
#endif
}
static int FindCandidate (List > pool, int capacity) {
// Loop through the last MaxCapacitySearchLength items
// and check if any item has a capacity greater or equal to the one that
// is desired. If so return it.
// Otherwise take the largest one or if there are no lists in the pool
// then allocate a new one with the desired capacity
List list = null;
int listIndex = -1;
for (int i = 0; i < pool.Count && i < MaxCapacitySearchLength; i++) {
// ith last item
var candidate = pool[pool.Count-1-i];
// Find the largest list that is not too large (arbitrary decision to try to prevent some memory bloat if the list was not just a temporary list).
if ((list == null || candidate.Capacity > list.Capacity) && candidate.Capacity < capacity*16) {
list = candidate;
listIndex = pool.Count-1-i;
if (list.Capacity >= capacity) {
return listIndex;
}
}
}
return listIndex;
}
///
/// Claim a list with minimum capacity
/// Returns a pooled list if any are in the pool.
/// Otherwise it creates a new one.
/// After usage, this list should be released using the Release function (though not strictly necessary).
/// A subset of the pool will be searched for a list with a high enough capacity and one will be returned
/// if possible, otherwise the list with the largest capacity found will be returned.
///
public static List Claim (int capacity) {
#if ASTAR_NO_POOLING
return new List(capacity);
#else
lock (pool) {
var currentPool = pool;
var listIndex = FindCandidate(pool, capacity);
if (capacity > LargeThreshold) {
var largeListIndex = FindCandidate(largePool, capacity);
if (largeListIndex != -1) {
currentPool = largePool;
listIndex = largeListIndex;
}
}
if (listIndex == -1) {
return new List(capacity);
} else {
var list = currentPool[listIndex];
// Swap current item and last item to enable a more efficient removal
inPool.Remove(list);
currentPool[listIndex] = currentPool[currentPool.Count-1];
currentPool.RemoveAt(currentPool.Count-1);
return list;
}
}
#endif
}
///
/// Makes sure the pool contains at least count pooled items with capacity size.
/// This is good if you want to do all allocations at start.
///
public static void Warmup (int count, int size) {
lock (pool) {
var tmp = new List[count];
for (int i = 0; i < count; i++) tmp[i] = Claim(size);
for (int i = 0; i < count; i++) Release(tmp[i]);
}
}
///
/// Releases a list and sets the variable to null.
/// After the list has been released it should not be used anymore.
///
/// \throws System.InvalidOperationException
/// Releasing a list when it has already been released will cause an exception to be thrown.
///
/// See:
///
public static void Release (ref List list) {
Release(list);
list = null;
}
///
/// Releases a list.
/// After the list has been released it should not be used anymore.
///
/// \throws System.InvalidOperationException
/// Releasing a list when it has already been released will cause an exception to be thrown.
///
/// See:
///
public static void Release (List list) {
#if !ASTAR_NO_POOLING
list.ClearFast();
lock (pool) {
#if !ASTAR_OPTIMIZE_POOLING
if (!inPool.Add(list)) {
throw new InvalidOperationException("You are trying to pool a list twice. Please make sure that you only pool it once.");
}
#endif
if (list.Capacity > LargeThreshold) {
largePool.Add(list);
// Remove the list which was used the longest time ago from the pool if it
// exceeds the maximum size as it probably just contributes to memory bloat
if (largePool.Count > MaxLargePoolSize) {
largePool.RemoveAt(0);
}
} else {
pool.Add(list);
}
}
#endif
}
///
/// Clears the pool for lists of this type.
/// This is an O(n) operation, where n is the number of pooled lists.
///
public static void Clear () {
lock (pool) {
#if !ASTAR_OPTIMIZE_POOLING && !ASTAR_NO_POOLING
inPool.Clear();
#endif
pool.Clear();
}
}
/// Number of lists of this type in the pool
public static int GetSize () {
// No lock required since int writes are atomic
return pool.Count;
}
}
}