summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/Pooling/ListPool.cs
blob: 22ff2464910fb9624767c6c400af2b948b0a508c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#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 {
	/// <summary>
	/// Lightweight List Pool.
	/// Handy class for pooling lists of type T.
	///
	/// Usage:
	/// - Claim a new list using <code> List<SomeClass> foo = ListPool<SomeClass>.Claim (); </code>
	/// - Use it and do stuff with it
	/// - Release it with <code> ListPool<SomeClass>.Release (foo); </code>
	///
	/// 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
	/// </summary>
	public static class ListPool<T> {
		/// <summary>Internal pool</summary>
		static readonly List<List<T> > pool = new List<List<T> >();

#if !ASTAR_NO_POOLING
		static readonly List<List<T> > largePool = new List<List<T> >();
		static readonly HashSet<List<T> > inPool = new HashSet<List<T> >();
#endif

		/// <summary>
		/// 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.
		/// </summary>
		const int MaxCapacitySearchLength = 8;
		const int LargeThreshold = 5000;
		const int MaxLargePoolSize = 8;

		/// <summary>
		/// 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).
		/// </summary>
		public static List<T> Claim () {
#if ASTAR_NO_POOLING
			return new List<T>();
#else
			lock (pool) {
				if (pool.Count > 0) {
					List<T> ls = pool[pool.Count-1];
					pool.RemoveAt(pool.Count-1);
					inPool.Remove(ls);
					return ls;
				}

				return new List<T>();
			}
#endif
		}

		static int FindCandidate (List<List<T> > 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<T> 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;
		}

		/// <summary>
		/// 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.
		/// </summary>
		public static List<T> Claim (int capacity) {
#if ASTAR_NO_POOLING
			return new List<T>(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<T>(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
		}

		/// <summary>
		/// 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.
		/// </summary>
		public static void Warmup (int count, int size) {
			lock (pool) {
				var tmp = new List<T>[count];
				for (int i = 0; i < count; i++) tmp[i] = Claim(size);
				for (int i = 0; i < count; i++) Release(tmp[i]);
			}
		}


		/// <summary>
		/// 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: <see cref="Claim"/>
		/// </summary>
		public static void Release (ref List<T> list) {
			Release(list);
			list = null;
		}

		/// <summary>
		/// 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: <see cref="Claim"/>
		/// </summary>
		public static void Release (List<T> 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
		}

		/// <summary>
		/// Clears the pool for lists of this type.
		/// This is an O(n) operation, where n is the number of pooled lists.
		/// </summary>
		public static void Clear () {
			lock (pool) {
#if !ASTAR_OPTIMIZE_POOLING && !ASTAR_NO_POOLING
				inPool.Clear();
#endif
				pool.Clear();
			}
		}

		/// <summary>Number of lists of this type in the pool</summary>
		public static int GetSize () {
			// No lock required since int writes are atomic
			return pool.Count;
		}
	}
}