com.wallstop-studios.unity-helpers 2.0.0-rc80.4 → 2.0.0-rc80.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Runtime/Core/Extension/EnumExtensions.cs +45 -120
- package/Runtime/Core/Threading/SingleThreadedThreadPool.cs +2 -0
- package/Runtime/Utils/Buffers.cs +196 -0
- package/Tests/Runtime/Core/Threading/SingleThreadedThreadPoolTests.cs +2 -0
- package/Tests/Runtime/Extensions/EnumExtensionTests.cs +25 -0
- package/Tests/Runtime/Utils/BuffersTests.cs +665 -0
- package/package.json +3 -1
|
@@ -12,142 +12,60 @@ namespace WallstopStudios.UnityHelpers.Core.Extension
|
|
|
12
12
|
public static class EnumNameCache<T>
|
|
13
13
|
where T : struct, Enum
|
|
14
14
|
{
|
|
15
|
-
private
|
|
16
|
-
|
|
17
|
-
private static readonly bool UseDensePacking;
|
|
18
|
-
private static readonly int Min;
|
|
19
|
-
private static readonly string[] DenseNames;
|
|
20
|
-
private static readonly Dictionary<int, string> SparseNames;
|
|
15
|
+
private static readonly Dictionary<T, string> Names;
|
|
21
16
|
|
|
22
17
|
static EnumNameCache()
|
|
23
18
|
{
|
|
24
|
-
T[] values = Enum.GetValues(typeof(T)).
|
|
25
|
-
|
|
26
|
-
int
|
|
27
|
-
int max = intValues.Max();
|
|
28
|
-
int range = max - min + 1;
|
|
29
|
-
|
|
30
|
-
if (range <= MaxDenseRange)
|
|
31
|
-
{
|
|
32
|
-
UseDensePacking = true;
|
|
33
|
-
Min = min;
|
|
34
|
-
DenseNames = new string[range];
|
|
35
|
-
|
|
36
|
-
for (int i = 0; i < values.Length; i++)
|
|
37
|
-
{
|
|
38
|
-
int key = intValues[i] - min;
|
|
39
|
-
T value = values[i];
|
|
40
|
-
DenseNames[key] = value.ToString("G");
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else
|
|
19
|
+
T[] values = Enum.GetValues(typeof(T)).OfType<T>().ToArray();
|
|
20
|
+
Names = new Dictionary<T, string>(values.Length);
|
|
21
|
+
for (int i = 0; i < values.Length; i++)
|
|
44
22
|
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
{
|
|
49
|
-
int key = Unsafe.As<T, int>(ref values[i]);
|
|
50
|
-
T value = values[i];
|
|
51
|
-
string name = value.ToString("G");
|
|
52
|
-
SparseNames.TryAdd(key, name);
|
|
53
|
-
}
|
|
23
|
+
T value = values[i];
|
|
24
|
+
string name = value.ToString("G");
|
|
25
|
+
Names.TryAdd(value, name);
|
|
54
26
|
}
|
|
55
27
|
}
|
|
56
28
|
|
|
57
29
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
58
30
|
public static string ToCachedName(T value)
|
|
59
31
|
{
|
|
60
|
-
|
|
61
|
-
if (UseDensePacking)
|
|
32
|
+
if (Names.TryGetValue(value, out string name))
|
|
62
33
|
{
|
|
63
|
-
|
|
64
|
-
if ((uint)idx < (uint)DenseNames!.Length)
|
|
65
|
-
{
|
|
66
|
-
return DenseNames[idx];
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
else
|
|
70
|
-
{
|
|
71
|
-
if (SparseNames!.TryGetValue(key, out string name))
|
|
72
|
-
{
|
|
73
|
-
return name;
|
|
74
|
-
}
|
|
34
|
+
return name;
|
|
75
35
|
}
|
|
76
36
|
|
|
77
|
-
return value.ToString();
|
|
37
|
+
return value.ToString("G");
|
|
78
38
|
}
|
|
79
39
|
}
|
|
80
40
|
|
|
81
41
|
public static class EnumDisplayNameCache<T>
|
|
82
42
|
where T : struct, Enum
|
|
83
43
|
{
|
|
84
|
-
private
|
|
85
|
-
|
|
86
|
-
private static readonly bool UseDensePacking;
|
|
87
|
-
private static readonly int Min;
|
|
88
|
-
private static readonly string[] DenseNames;
|
|
89
|
-
private static readonly Dictionary<int, string> SparseNames;
|
|
44
|
+
private static readonly Dictionary<T, string> Names;
|
|
90
45
|
|
|
91
46
|
static EnumDisplayNameCache()
|
|
92
47
|
{
|
|
93
48
|
Type type = typeof(T);
|
|
94
49
|
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Static);
|
|
95
|
-
|
|
96
|
-
int[] intValues = values.Select(v => Unsafe.As<T, int>(ref v)).ToArray();
|
|
97
|
-
int min = intValues.Min();
|
|
98
|
-
int max = intValues.Max();
|
|
99
|
-
int range = max - min + 1;
|
|
50
|
+
Names = new Dictionary<T, string>(fields.Length);
|
|
100
51
|
|
|
101
|
-
|
|
52
|
+
for (int i = 0; i < fields.Length; i++)
|
|
102
53
|
{
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
int key = intValues[i] - min;
|
|
110
|
-
FieldInfo field = fields[i];
|
|
111
|
-
string name = field.IsAttributeDefined(out EnumDisplayNameAttribute displayName)
|
|
112
|
-
? displayName.DisplayName
|
|
113
|
-
: field.Name;
|
|
114
|
-
DenseNames[key] = name;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else
|
|
118
|
-
{
|
|
119
|
-
UseDensePacking = false;
|
|
120
|
-
SparseNames = new Dictionary<int, string>(fields.Length);
|
|
121
|
-
for (int i = 0; i < fields.Length; i++)
|
|
122
|
-
{
|
|
123
|
-
int key = Unsafe.As<T, int>(ref values[i]);
|
|
124
|
-
FieldInfo field = fields[i];
|
|
125
|
-
string name = field.IsAttributeDefined(out EnumDisplayNameAttribute displayName)
|
|
126
|
-
? displayName.DisplayName
|
|
127
|
-
: field.Name;
|
|
128
|
-
SparseNames.TryAdd(key, name);
|
|
129
|
-
}
|
|
54
|
+
FieldInfo field = fields[i];
|
|
55
|
+
string name = field.IsAttributeDefined(out EnumDisplayNameAttribute displayName)
|
|
56
|
+
? displayName.DisplayName
|
|
57
|
+
: field.Name;
|
|
58
|
+
T value = (T)field.GetValue(null);
|
|
59
|
+
Names.TryAdd(value, name);
|
|
130
60
|
}
|
|
131
61
|
}
|
|
132
62
|
|
|
133
63
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
134
64
|
public static string ToDisplayName(T value)
|
|
135
65
|
{
|
|
136
|
-
|
|
137
|
-
if (UseDensePacking)
|
|
66
|
+
if (Names.TryGetValue(value, out string name))
|
|
138
67
|
{
|
|
139
|
-
|
|
140
|
-
if ((uint)idx < (uint)DenseNames!.Length)
|
|
141
|
-
{
|
|
142
|
-
return DenseNames[idx];
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
else
|
|
146
|
-
{
|
|
147
|
-
if (SparseNames!.TryGetValue(key, out string name))
|
|
148
|
-
{
|
|
149
|
-
return name;
|
|
150
|
-
}
|
|
68
|
+
return name;
|
|
151
69
|
}
|
|
152
70
|
|
|
153
71
|
return value.ToString();
|
|
@@ -160,9 +78,15 @@ namespace WallstopStudios.UnityHelpers.Core.Extension
|
|
|
160
78
|
public static bool HasFlagNoAlloc<T>(this T value, T flag)
|
|
161
79
|
where T : unmanaged, Enum
|
|
162
80
|
{
|
|
163
|
-
ulong valueUnderlying = GetUInt64(value);
|
|
164
|
-
ulong flagUnderlying = GetUInt64(flag);
|
|
165
|
-
|
|
81
|
+
ulong? valueUnderlying = GetUInt64(value);
|
|
82
|
+
ulong? flagUnderlying = GetUInt64(flag);
|
|
83
|
+
if (valueUnderlying == null || flagUnderlying == null)
|
|
84
|
+
{
|
|
85
|
+
// Fallback for unsupported enum sizes
|
|
86
|
+
return value.HasFlag(flag);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (valueUnderlying.Value & flagUnderlying.Value) == flagUnderlying.Value;
|
|
166
90
|
}
|
|
167
91
|
|
|
168
92
|
public static string ToDisplayName<T>(this T value)
|
|
@@ -190,23 +114,24 @@ namespace WallstopStudios.UnityHelpers.Core.Extension
|
|
|
190
114
|
}
|
|
191
115
|
|
|
192
116
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
193
|
-
private static
|
|
117
|
+
private static ulong? GetUInt64<T>(T value)
|
|
194
118
|
where T : unmanaged
|
|
195
119
|
{
|
|
196
|
-
|
|
197
|
-
Works because T is constrained to unmanaged, so it's safe to reinterpret
|
|
198
|
-
All enums are value types and have a fixed size
|
|
199
|
-
*/
|
|
200
|
-
return sizeof(T) switch
|
|
120
|
+
try
|
|
201
121
|
{
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
122
|
+
return Unsafe.SizeOf<T>() switch
|
|
123
|
+
{
|
|
124
|
+
1 => Unsafe.As<T, byte>(ref value),
|
|
125
|
+
2 => Unsafe.As<T, ushort>(ref value),
|
|
126
|
+
4 => Unsafe.As<T, uint>(ref value),
|
|
127
|
+
8 => Unsafe.As<T, ulong>(ref value),
|
|
128
|
+
_ => null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch
|
|
132
|
+
{
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
210
135
|
}
|
|
211
136
|
}
|
|
212
137
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
namespace WallstopStudios.UnityHelpers.Core.Threading
|
|
2
2
|
{
|
|
3
|
+
#if !SINGLE_THREADED
|
|
3
4
|
using System;
|
|
4
5
|
using System.Collections.Concurrent;
|
|
5
6
|
using System.Threading;
|
|
@@ -223,4 +224,5 @@ namespace WallstopStudios.UnityHelpers.Core.Threading
|
|
|
223
224
|
}
|
|
224
225
|
}
|
|
225
226
|
}
|
|
227
|
+
#endif
|
|
226
228
|
}
|
package/Runtime/Utils/Buffers.cs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
namespace WallstopStudios.UnityHelpers.Utils
|
|
2
2
|
{
|
|
3
3
|
using System;
|
|
4
|
+
using System.Collections.Concurrent;
|
|
4
5
|
using System.Collections.Generic;
|
|
5
6
|
using System.Reflection;
|
|
6
7
|
using System.Text;
|
|
8
|
+
using System.Threading;
|
|
7
9
|
using UnityEngine;
|
|
8
10
|
using WallstopStudios.UnityHelpers.Core.Helper;
|
|
9
11
|
|
|
@@ -149,6 +151,200 @@ namespace WallstopStudios.UnityHelpers.Utils
|
|
|
149
151
|
}
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
#if SINGLE_THREADED
|
|
155
|
+
public static class WallstopFastArrayPool<T>
|
|
156
|
+
{
|
|
157
|
+
private static readonly List<List<T[]>> _pool = new();
|
|
158
|
+
private static readonly Action<T[]> _onDispose = Release;
|
|
159
|
+
|
|
160
|
+
public static PooledResource<T[]> Get(int size)
|
|
161
|
+
{
|
|
162
|
+
switch (size)
|
|
163
|
+
{
|
|
164
|
+
case < 0:
|
|
165
|
+
{
|
|
166
|
+
throw new ArgumentOutOfRangeException(
|
|
167
|
+
nameof(size),
|
|
168
|
+
size,
|
|
169
|
+
"Must be non-negative."
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
case 0:
|
|
173
|
+
{
|
|
174
|
+
return new PooledResource<T[]>(Array.Empty<T>(), _ => { });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
while (_pool.Count <= size)
|
|
179
|
+
{
|
|
180
|
+
_pool.Add(null);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
List<T[]> pool = _pool[size];
|
|
184
|
+
if (pool == null)
|
|
185
|
+
{
|
|
186
|
+
pool = new List<T[]>();
|
|
187
|
+
_pool[size] = pool;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (pool.Count == 0)
|
|
191
|
+
{
|
|
192
|
+
return new PooledResource<T[]>(new T[size], _onDispose);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
int lastIndex = pool.Count - 1;
|
|
196
|
+
T[] instance = pool[lastIndex];
|
|
197
|
+
pool.RemoveAt(lastIndex);
|
|
198
|
+
return new PooledResource<T[]>(instance, _onDispose);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private static void Release(T[] resource)
|
|
202
|
+
{
|
|
203
|
+
_pool[resource.Length].Add(resource);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
#else
|
|
207
|
+
|
|
208
|
+
public static class WallstopFastArrayPool<T>
|
|
209
|
+
{
|
|
210
|
+
private static readonly ReaderWriterLockSlim _lock = new();
|
|
211
|
+
private static readonly List<ConcurrentStack<T[]>> _pool = new();
|
|
212
|
+
private static readonly Action<T[]> _onDispose = Release;
|
|
213
|
+
|
|
214
|
+
public static PooledResource<T[]> Get(int size)
|
|
215
|
+
{
|
|
216
|
+
switch (size)
|
|
217
|
+
{
|
|
218
|
+
case < 0:
|
|
219
|
+
{
|
|
220
|
+
throw new ArgumentOutOfRangeException(
|
|
221
|
+
nameof(size),
|
|
222
|
+
size,
|
|
223
|
+
"Must be non-negative."
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
case 0:
|
|
227
|
+
{
|
|
228
|
+
return new PooledResource<T[]>(Array.Empty<T>(), _ => { });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
bool withinRange;
|
|
233
|
+
ConcurrentStack<T[]> pool = null;
|
|
234
|
+
_lock.EnterReadLock();
|
|
235
|
+
try
|
|
236
|
+
{
|
|
237
|
+
withinRange = size < _pool.Count;
|
|
238
|
+
if (withinRange)
|
|
239
|
+
{
|
|
240
|
+
pool = _pool[size];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
finally
|
|
244
|
+
{
|
|
245
|
+
_lock.ExitReadLock();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (withinRange)
|
|
249
|
+
{
|
|
250
|
+
if (pool == null)
|
|
251
|
+
{
|
|
252
|
+
_lock.EnterUpgradeableReadLock();
|
|
253
|
+
try
|
|
254
|
+
{
|
|
255
|
+
pool = _pool[size];
|
|
256
|
+
if (pool == null)
|
|
257
|
+
{
|
|
258
|
+
_lock.EnterWriteLock();
|
|
259
|
+
try
|
|
260
|
+
{
|
|
261
|
+
pool = _pool[size];
|
|
262
|
+
if (pool == null)
|
|
263
|
+
{
|
|
264
|
+
pool = new ConcurrentStack<T[]>();
|
|
265
|
+
_pool[size] = pool;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
finally
|
|
269
|
+
{
|
|
270
|
+
_lock.ExitWriteLock();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
finally
|
|
275
|
+
{
|
|
276
|
+
_lock.ExitUpgradeableReadLock();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else
|
|
281
|
+
{
|
|
282
|
+
_lock.EnterUpgradeableReadLock();
|
|
283
|
+
try
|
|
284
|
+
{
|
|
285
|
+
if (size < _pool.Count)
|
|
286
|
+
{
|
|
287
|
+
pool = _pool[size];
|
|
288
|
+
if (pool == null)
|
|
289
|
+
{
|
|
290
|
+
_lock.EnterWriteLock();
|
|
291
|
+
try
|
|
292
|
+
{
|
|
293
|
+
pool = _pool[size];
|
|
294
|
+
if (pool == null)
|
|
295
|
+
{
|
|
296
|
+
pool = new ConcurrentStack<T[]>();
|
|
297
|
+
_pool[size] = pool;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
finally
|
|
301
|
+
{
|
|
302
|
+
_lock.ExitWriteLock();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else
|
|
307
|
+
{
|
|
308
|
+
_lock.EnterWriteLock();
|
|
309
|
+
try
|
|
310
|
+
{
|
|
311
|
+
while (_pool.Count <= size)
|
|
312
|
+
{
|
|
313
|
+
_pool.Add(null);
|
|
314
|
+
}
|
|
315
|
+
pool = _pool[size];
|
|
316
|
+
if (pool == null)
|
|
317
|
+
{
|
|
318
|
+
pool = new ConcurrentStack<T[]>();
|
|
319
|
+
_pool[size] = pool;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
finally
|
|
323
|
+
{
|
|
324
|
+
_lock.ExitWriteLock();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
finally
|
|
329
|
+
{
|
|
330
|
+
_lock.ExitUpgradeableReadLock();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (pool.TryPop(out T[] instance))
|
|
335
|
+
{
|
|
336
|
+
return new PooledResource<T[]>(instance, _onDispose);
|
|
337
|
+
}
|
|
338
|
+
return new PooledResource<T[]>(new T[size], _onDispose);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private static void Release(T[] resource)
|
|
342
|
+
{
|
|
343
|
+
_pool[resource.Length].Push(resource);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
#endif
|
|
347
|
+
|
|
152
348
|
public readonly struct PooledResource<T> : IDisposable
|
|
153
349
|
{
|
|
154
350
|
public readonly T resource;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
namespace WallstopStudios.UnityHelpers.Tests.Core.Threading
|
|
2
2
|
{
|
|
3
|
+
#if !SINGLE_THREADED
|
|
3
4
|
using System.Collections;
|
|
4
5
|
using System.Collections.Generic;
|
|
5
6
|
using NUnit.Framework;
|
|
@@ -51,4 +52,5 @@ namespace WallstopStudios.UnityHelpers.Tests.Core.Threading
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
}
|
|
55
|
+
#endif
|
|
54
56
|
}
|
|
@@ -2,6 +2,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Extensions
|
|
|
2
2
|
{
|
|
3
3
|
using NUnit.Framework;
|
|
4
4
|
using UnityEngine.TestTools.Constraints;
|
|
5
|
+
using WallstopStudios.UnityHelpers.Core.Attributes;
|
|
5
6
|
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
6
7
|
using Is = NUnit.Framework.Is;
|
|
7
8
|
|
|
@@ -26,8 +27,14 @@ namespace WallstopStudios.UnityHelpers.Tests.Extensions
|
|
|
26
27
|
private enum SmallTestEnum : short
|
|
27
28
|
{
|
|
28
29
|
None = 0,
|
|
30
|
+
|
|
31
|
+
[EnumDisplayName("TestFirst")]
|
|
29
32
|
First = 1 << 0,
|
|
33
|
+
|
|
34
|
+
[EnumDisplayName("TestSecond")]
|
|
30
35
|
Second = 1 << 1,
|
|
36
|
+
|
|
37
|
+
[EnumDisplayName("TestThird")]
|
|
31
38
|
Third = 1 << 2,
|
|
32
39
|
}
|
|
33
40
|
|
|
@@ -39,6 +46,24 @@ namespace WallstopStudios.UnityHelpers.Tests.Extensions
|
|
|
39
46
|
Third = 1 << 2,
|
|
40
47
|
}
|
|
41
48
|
|
|
49
|
+
[Test]
|
|
50
|
+
public void DisplayName()
|
|
51
|
+
{
|
|
52
|
+
Assert.AreEqual("None", SmallTestEnum.None.ToDisplayName());
|
|
53
|
+
Assert.AreEqual("TestFirst", SmallTestEnum.First.ToDisplayName());
|
|
54
|
+
Assert.AreEqual("TestSecond", SmallTestEnum.Second.ToDisplayName());
|
|
55
|
+
Assert.AreEqual("TestThird", SmallTestEnum.Third.ToDisplayName());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[Test]
|
|
59
|
+
public void CachedName()
|
|
60
|
+
{
|
|
61
|
+
Assert.AreEqual("None", SmallTestEnum.None.ToCachedName());
|
|
62
|
+
Assert.AreEqual("First", SmallTestEnum.First.ToCachedName());
|
|
63
|
+
Assert.AreEqual("Second", SmallTestEnum.Second.ToCachedName());
|
|
64
|
+
Assert.AreEqual("Third", SmallTestEnum.Third.ToCachedName());
|
|
65
|
+
}
|
|
66
|
+
|
|
42
67
|
[Test]
|
|
43
68
|
public void HasFlagNoAlloc()
|
|
44
69
|
{
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
namespace WallstopStudios.UnityHelpers.Tests.Utils
|
|
2
2
|
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections;
|
|
3
5
|
using System.Collections.Generic;
|
|
6
|
+
using System.Linq;
|
|
4
7
|
using NUnit.Framework;
|
|
8
|
+
using UnityEngine.TestTools;
|
|
9
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
5
10
|
using WallstopStudios.UnityHelpers.Core.Random;
|
|
6
11
|
using WallstopStudios.UnityHelpers.Utils;
|
|
12
|
+
#if !SINGLETHREADED
|
|
13
|
+
using System.Threading;
|
|
14
|
+
using System.Threading.Tasks;
|
|
15
|
+
#endif
|
|
7
16
|
|
|
8
17
|
public sealed class BuffersTests
|
|
9
18
|
{
|
|
@@ -51,5 +60,661 @@ namespace WallstopStudios.UnityHelpers.Tests.Utils
|
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
}
|
|
63
|
+
|
|
64
|
+
[Test]
|
|
65
|
+
public void WallstopFastArrayPoolGetNegativeSizeThrowsArgumentOutOfRangeException()
|
|
66
|
+
{
|
|
67
|
+
Assert.Throws<ArgumentOutOfRangeException>(() => WallstopFastArrayPool<int>.Get(-1));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
[Test]
|
|
71
|
+
public void WallstopFastArrayPoolGetZeroSizeReturnsEmptyArrayWithNoOpDispose()
|
|
72
|
+
{
|
|
73
|
+
using PooledResource<int[]> pooled = WallstopFastArrayPool<int>.Get(0);
|
|
74
|
+
Assert.NotNull(pooled.resource);
|
|
75
|
+
Assert.AreEqual(0, pooled.resource.Length);
|
|
76
|
+
Assert.AreSame(Array.Empty<int>(), pooled.resource);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
[Test]
|
|
80
|
+
public void WallstopFastArrayPoolGetPositiveSizeReturnsArrayWithCorrectLength()
|
|
81
|
+
{
|
|
82
|
+
const int size = 10;
|
|
83
|
+
using PooledResource<int[]> pooled = WallstopFastArrayPool<int>.Get(size);
|
|
84
|
+
|
|
85
|
+
Assert.NotNull(pooled.resource);
|
|
86
|
+
Assert.AreEqual(size, pooled.resource.Length);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
[Test]
|
|
90
|
+
public void WallstopFastArrayPoolGetSameSizeReusesArrayAfterDispose()
|
|
91
|
+
{
|
|
92
|
+
const int size = 5;
|
|
93
|
+
int[] firstArray;
|
|
94
|
+
|
|
95
|
+
using (PooledResource<int[]> pooled = WallstopFastArrayPool<int>.Get(size))
|
|
96
|
+
{
|
|
97
|
+
firstArray = pooled.resource;
|
|
98
|
+
firstArray[0] = 42;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
using PooledResource<int[]> pooledReused = WallstopFastArrayPool<int>.Get(size);
|
|
102
|
+
Assert.AreSame(firstArray, pooledReused.resource);
|
|
103
|
+
Assert.AreEqual(42, pooledReused.resource[0]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
[Test]
|
|
107
|
+
public void WallstopFastArrayPoolGetDifferentSizesReturnsCorrectArrays()
|
|
108
|
+
{
|
|
109
|
+
int[] sizes = { 1, 5, 10, 100, 1000 };
|
|
110
|
+
List<PooledResource<int[]>> pooledArrays = new();
|
|
111
|
+
|
|
112
|
+
try
|
|
113
|
+
{
|
|
114
|
+
foreach (int size in sizes)
|
|
115
|
+
{
|
|
116
|
+
PooledResource<int[]> pooled = WallstopFastArrayPool<int>.Get(size);
|
|
117
|
+
pooledArrays.Add(pooled);
|
|
118
|
+
Assert.AreEqual(size, pooled.resource.Length);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
finally
|
|
122
|
+
{
|
|
123
|
+
foreach (PooledResource<int[]> pooled in pooledArrays)
|
|
124
|
+
{
|
|
125
|
+
pooled.Dispose();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
[Test]
|
|
131
|
+
public void WallstopFastArrayPoolArraysNotClearedOnRelease()
|
|
132
|
+
{
|
|
133
|
+
const int size = 10;
|
|
134
|
+
|
|
135
|
+
using (PooledResource<string[]> pooled = WallstopFastArrayPool<string>.Get(size))
|
|
136
|
+
{
|
|
137
|
+
for (int i = 0; i < size; i++)
|
|
138
|
+
{
|
|
139
|
+
pooled.resource[i] = $"test{i}";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
using PooledResource<string[]> pooledReused = WallstopFastArrayPool<string>.Get(size);
|
|
144
|
+
for (int i = 0; i < size; i++)
|
|
145
|
+
{
|
|
146
|
+
Assert.AreEqual($"test{i}", pooledReused.resource[i]);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
[Test]
|
|
151
|
+
public void WallstopFastArrayPoolPoolGrowsAsNeeded()
|
|
152
|
+
{
|
|
153
|
+
const int size = 7;
|
|
154
|
+
const int count = 5;
|
|
155
|
+
List<PooledResource<int[]>> pooledArrays = new();
|
|
156
|
+
|
|
157
|
+
try
|
|
158
|
+
{
|
|
159
|
+
for (int i = 0; i < count; i++)
|
|
160
|
+
{
|
|
161
|
+
PooledResource<int[]> pooled = WallstopFastArrayPool<int>.Get(size);
|
|
162
|
+
pooledArrays.Add(pooled);
|
|
163
|
+
Assert.AreEqual(size, pooled.resource.Length);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
HashSet<int[]> distinctArrays = pooledArrays.Select(p => p.resource).ToHashSet();
|
|
167
|
+
Assert.AreEqual(count, distinctArrays.Count);
|
|
168
|
+
}
|
|
169
|
+
finally
|
|
170
|
+
{
|
|
171
|
+
foreach (PooledResource<int[]> pooled in pooledArrays)
|
|
172
|
+
{
|
|
173
|
+
pooled.Dispose();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
[Test]
|
|
179
|
+
public void WallstopFastArrayPoolDifferentTypesUseDifferentPools()
|
|
180
|
+
{
|
|
181
|
+
const int size = 10;
|
|
182
|
+
|
|
183
|
+
using PooledResource<int[]> intPooled = WallstopFastArrayPool<int>.Get(size);
|
|
184
|
+
using PooledResource<string[]> stringPooled = WallstopFastArrayPool<string>.Get(size);
|
|
185
|
+
using PooledResource<float[]> floatPooled = WallstopFastArrayPool<float>.Get(size);
|
|
186
|
+
|
|
187
|
+
Assert.AreEqual(size, intPooled.resource.Length);
|
|
188
|
+
Assert.AreEqual(size, stringPooled.resource.Length);
|
|
189
|
+
Assert.AreEqual(size, floatPooled.resource.Length);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
[Test]
|
|
193
|
+
public void WallstopFastArrayPoolLargeArraysWork()
|
|
194
|
+
{
|
|
195
|
+
const int size = 100000;
|
|
196
|
+
using PooledResource<byte[]> pooled = WallstopFastArrayPool<byte>.Get(size);
|
|
197
|
+
|
|
198
|
+
Assert.AreEqual(size, pooled.resource.Length);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
[Test]
|
|
202
|
+
public void WallstopFastArrayPoolNestedUsageWorks()
|
|
203
|
+
{
|
|
204
|
+
const int outerSize = 5;
|
|
205
|
+
const int innerSize = 3;
|
|
206
|
+
|
|
207
|
+
using PooledResource<int[]> outer = WallstopFastArrayPool<int>.Get(outerSize);
|
|
208
|
+
Assert.AreEqual(outerSize, outer.resource.Length);
|
|
209
|
+
Array.Clear(outer.resource, 0, outer.resource.Length);
|
|
210
|
+
outer.resource[0] = 1;
|
|
211
|
+
|
|
212
|
+
using (PooledResource<int[]> inner = WallstopFastArrayPool<int>.Get(innerSize))
|
|
213
|
+
{
|
|
214
|
+
inner.resource[0] = 2;
|
|
215
|
+
Assert.AreEqual(innerSize, inner.resource.Length);
|
|
216
|
+
Assert.AreEqual(1, outer.resource[0]);
|
|
217
|
+
Assert.AreEqual(2, inner.resource[0]);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
Assert.AreEqual(outerSize, outer.resource.Length);
|
|
221
|
+
Assert.AreEqual(1, outer.resource[0]);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
[UnityTest]
|
|
225
|
+
public IEnumerator WallstopFastArrayPoolStressTest()
|
|
226
|
+
{
|
|
227
|
+
const int iterations = 1000;
|
|
228
|
+
const int maxSize = 100;
|
|
229
|
+
Random random = new(42);
|
|
230
|
+
|
|
231
|
+
for (int i = 0; i < iterations; i++)
|
|
232
|
+
{
|
|
233
|
+
int size = random.Next(1, maxSize);
|
|
234
|
+
using PooledResource<int[]> pooled = WallstopFastArrayPool<int>.Get(size);
|
|
235
|
+
|
|
236
|
+
Assert.AreEqual(size, pooled.resource.Length);
|
|
237
|
+
|
|
238
|
+
for (int j = 0; j < Math.Min(10, size); j++)
|
|
239
|
+
{
|
|
240
|
+
pooled.resource[j] = random.Next();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (i % 100 == 0)
|
|
244
|
+
{
|
|
245
|
+
yield return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#if !SINGLETHREADED
|
|
251
|
+
[Test]
|
|
252
|
+
public void WallstopFastArrayPoolConcurrentAccessDifferentSizes()
|
|
253
|
+
{
|
|
254
|
+
const int threadCount = 10;
|
|
255
|
+
const int operationsPerThread = 100;
|
|
256
|
+
Task[] tasks = new Task[threadCount];
|
|
257
|
+
List<Exception> exceptions = new();
|
|
258
|
+
|
|
259
|
+
for (int t = 0; t < threadCount; t++)
|
|
260
|
+
{
|
|
261
|
+
int threadId = t;
|
|
262
|
+
tasks[t] = Task.Run(async () =>
|
|
263
|
+
{
|
|
264
|
+
try
|
|
265
|
+
{
|
|
266
|
+
PcgRandom random = new(threadId);
|
|
267
|
+
foreach (int i in Enumerable.Range(0, operationsPerThread).Shuffled(random))
|
|
268
|
+
{
|
|
269
|
+
int size = random.Next(1, 50) + threadId;
|
|
270
|
+
using PooledResource<int[]> pooled = WallstopFastArrayPool<int>.Get(
|
|
271
|
+
size
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
Assert.AreEqual(size, pooled.resource.Length);
|
|
275
|
+
|
|
276
|
+
for (int j = 0; j < Math.Min(5, size); j++)
|
|
277
|
+
{
|
|
278
|
+
pooled.resource[j] = threadId * 1000 + i * 10 + j;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
await Task.Delay(TimeSpan.FromMilliseconds(random.NextDouble()));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch (Exception ex)
|
|
285
|
+
{
|
|
286
|
+
lock (exceptions)
|
|
287
|
+
{
|
|
288
|
+
exceptions.Add(ex);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
Task.WaitAll(tasks);
|
|
295
|
+
|
|
296
|
+
if (exceptions.Count > 0)
|
|
297
|
+
{
|
|
298
|
+
throw new AggregateException(exceptions);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
[Test]
|
|
303
|
+
public void WallstopFastArrayPoolConcurrentAccessSameSize()
|
|
304
|
+
{
|
|
305
|
+
const int threadCount = 8;
|
|
306
|
+
const int operationsPerThread = 200;
|
|
307
|
+
const int arraySize = 25;
|
|
308
|
+
Task[] tasks = new Task[threadCount];
|
|
309
|
+
List<Exception> exceptions = new();
|
|
310
|
+
|
|
311
|
+
for (int t = 0; t < threadCount; t++)
|
|
312
|
+
{
|
|
313
|
+
int threadId = t;
|
|
314
|
+
tasks[t] = Task.Run(() =>
|
|
315
|
+
{
|
|
316
|
+
try
|
|
317
|
+
{
|
|
318
|
+
PcgRandom random = new(threadId);
|
|
319
|
+
foreach (int i in Enumerable.Range(0, operationsPerThread).Shuffled(random))
|
|
320
|
+
{
|
|
321
|
+
using PooledResource<int[]> pooled = WallstopFastArrayPool<int>.Get(
|
|
322
|
+
arraySize
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
Array.Clear(pooled.resource, 0, pooled.resource.Length);
|
|
326
|
+
Assert.AreEqual(arraySize, pooled.resource.Length);
|
|
327
|
+
|
|
328
|
+
for (int j = 0; j < arraySize; j++)
|
|
329
|
+
{
|
|
330
|
+
Assert.AreEqual(0, pooled.resource[j]);
|
|
331
|
+
pooled.resource[j] = threadId * 1000 + i;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch (Exception ex)
|
|
336
|
+
{
|
|
337
|
+
lock (exceptions)
|
|
338
|
+
{
|
|
339
|
+
exceptions.Add(ex);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
Task.WaitAll(tasks);
|
|
346
|
+
|
|
347
|
+
if (exceptions.Count > 0)
|
|
348
|
+
{
|
|
349
|
+
throw new AggregateException(exceptions);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
[Test]
|
|
354
|
+
public void WallstopFastArrayPoolConcurrentAccessMixedSizes()
|
|
355
|
+
{
|
|
356
|
+
const int threadCount = 6;
|
|
357
|
+
const int operationsPerThread = 150;
|
|
358
|
+
Task[] tasks = new Task[threadCount];
|
|
359
|
+
List<Exception> exceptions = new();
|
|
360
|
+
int[] sizes = { 1, 5, 10, 20, 50, 100 };
|
|
361
|
+
|
|
362
|
+
for (int t = 0; t < threadCount; t++)
|
|
363
|
+
{
|
|
364
|
+
int threadId = t;
|
|
365
|
+
tasks[t] = Task.Run(async () =>
|
|
366
|
+
{
|
|
367
|
+
try
|
|
368
|
+
{
|
|
369
|
+
PcgRandom random = new(threadId + 100);
|
|
370
|
+
foreach (int i in Enumerable.Range(0, operationsPerThread).Shuffled(random))
|
|
371
|
+
{
|
|
372
|
+
int size = random.NextOf(sizes);
|
|
373
|
+
using PooledResource<string[]> pooled =
|
|
374
|
+
WallstopFastArrayPool<string>.Get(size);
|
|
375
|
+
Array.Clear(pooled.resource, 0, pooled.resource.Length);
|
|
376
|
+
|
|
377
|
+
Assert.AreEqual(size, pooled.resource.Length);
|
|
378
|
+
|
|
379
|
+
for (int j = 0; j < size; j++)
|
|
380
|
+
{
|
|
381
|
+
Assert.IsNull(pooled.resource[j]);
|
|
382
|
+
pooled.resource[j] = $"T{threadId}-I{i}-J{j}";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (i % 50 == 0)
|
|
386
|
+
{
|
|
387
|
+
await Task.Delay(TimeSpan.FromMilliseconds(random.NextDouble()));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (Exception ex)
|
|
392
|
+
{
|
|
393
|
+
lock (exceptions)
|
|
394
|
+
{
|
|
395
|
+
exceptions.Add(ex);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
Task.WaitAll(tasks);
|
|
402
|
+
|
|
403
|
+
if (exceptions.Count > 0)
|
|
404
|
+
{
|
|
405
|
+
throw new AggregateException(exceptions);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
[Test]
|
|
410
|
+
public void WallstopFastArrayPoolConcurrentAccessRapidAllocationDeallocation()
|
|
411
|
+
{
|
|
412
|
+
const int threadCount = 12;
|
|
413
|
+
const int operationsPerThread = 500;
|
|
414
|
+
Task[] tasks = new Task[threadCount];
|
|
415
|
+
List<Exception> exceptions = new();
|
|
416
|
+
|
|
417
|
+
for (int t = 0; t < threadCount; t++)
|
|
418
|
+
{
|
|
419
|
+
int threadId = t;
|
|
420
|
+
tasks[t] = Task.Run(() =>
|
|
421
|
+
{
|
|
422
|
+
try
|
|
423
|
+
{
|
|
424
|
+
PcgRandom random = new(threadId + 100);
|
|
425
|
+
foreach (int i in Enumerable.Range(0, operationsPerThread).Shuffled(random))
|
|
426
|
+
{
|
|
427
|
+
int size = random.Next(1, 30);
|
|
428
|
+
using PooledResource<byte[]> pooled = WallstopFastArrayPool<byte>.Get(
|
|
429
|
+
size
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
Assert.AreEqual(size, pooled.resource.Length);
|
|
433
|
+
|
|
434
|
+
for (int j = 0; j < size; j++)
|
|
435
|
+
{
|
|
436
|
+
pooled.resource[j] = (byte)(threadId + i + j);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
catch (Exception ex)
|
|
441
|
+
{
|
|
442
|
+
lock (exceptions)
|
|
443
|
+
{
|
|
444
|
+
exceptions.Add(ex);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
Task.WaitAll(tasks);
|
|
451
|
+
|
|
452
|
+
if (exceptions.Count > 0)
|
|
453
|
+
{
|
|
454
|
+
throw new AggregateException(exceptions);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
[Test]
|
|
459
|
+
public void WallstopFastArrayPoolThreadSafetyPoolExpansion()
|
|
460
|
+
{
|
|
461
|
+
const int threadCount = 8;
|
|
462
|
+
const int maxPoolSize = 1000;
|
|
463
|
+
Task[] tasks = new Task[threadCount];
|
|
464
|
+
List<Exception> exceptions = new();
|
|
465
|
+
Barrier barrier = new(threadCount);
|
|
466
|
+
|
|
467
|
+
for (int t = 0; t < threadCount; t++)
|
|
468
|
+
{
|
|
469
|
+
int threadId = t;
|
|
470
|
+
tasks[t] = Task.Run(() =>
|
|
471
|
+
{
|
|
472
|
+
try
|
|
473
|
+
{
|
|
474
|
+
barrier.SignalAndWait();
|
|
475
|
+
|
|
476
|
+
for (int size = threadId + 1; size <= maxPoolSize; size += threadCount)
|
|
477
|
+
{
|
|
478
|
+
using PooledResource<int[]> pooled = WallstopFastArrayPool<int>.Get(
|
|
479
|
+
size
|
|
480
|
+
);
|
|
481
|
+
Assert.AreEqual(size, pooled.resource.Length);
|
|
482
|
+
|
|
483
|
+
pooled.resource[0] = threadId;
|
|
484
|
+
pooled.resource[size - 1] = threadId;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch (Exception ex)
|
|
488
|
+
{
|
|
489
|
+
lock (exceptions)
|
|
490
|
+
{
|
|
491
|
+
exceptions.Add(ex);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
Task.WaitAll(tasks);
|
|
498
|
+
|
|
499
|
+
if (exceptions.Count > 0)
|
|
500
|
+
{
|
|
501
|
+
throw new AggregateException(exceptions);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
[UnityTest]
|
|
506
|
+
public IEnumerator WallstopFastArrayPoolConcurrentStressTest()
|
|
507
|
+
{
|
|
508
|
+
const int threadCount = 4;
|
|
509
|
+
const int operationsPerThread = 250;
|
|
510
|
+
Task[] tasks = new Task[threadCount];
|
|
511
|
+
List<Exception> exceptions = new();
|
|
512
|
+
int completedThreads = 0;
|
|
513
|
+
|
|
514
|
+
for (int t = 0; t < threadCount; t++)
|
|
515
|
+
{
|
|
516
|
+
int threadId = t;
|
|
517
|
+
tasks[t] = Task.Run(() =>
|
|
518
|
+
{
|
|
519
|
+
try
|
|
520
|
+
{
|
|
521
|
+
PcgRandom random = new(threadId + 100);
|
|
522
|
+
foreach (int i in Enumerable.Range(0, operationsPerThread).Shuffled(random))
|
|
523
|
+
{
|
|
524
|
+
int size = random.Next(1, 100);
|
|
525
|
+
using PooledResource<float[]> pooled = WallstopFastArrayPool<float>.Get(
|
|
526
|
+
size
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
Assert.AreEqual(size, pooled.resource.Length);
|
|
530
|
+
|
|
531
|
+
for (int j = 0; j < Math.Min(10, size); j++)
|
|
532
|
+
{
|
|
533
|
+
pooled.resource[j] = threadId * 1000.0f + i + j * 0.1f;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
catch (Exception ex)
|
|
538
|
+
{
|
|
539
|
+
lock (exceptions)
|
|
540
|
+
{
|
|
541
|
+
exceptions.Add(ex);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
finally
|
|
545
|
+
{
|
|
546
|
+
Interlocked.Increment(ref completedThreads);
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
while (completedThreads < threadCount)
|
|
552
|
+
{
|
|
553
|
+
yield return null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
Task.WaitAll(tasks);
|
|
557
|
+
|
|
558
|
+
if (exceptions.Count > 0)
|
|
559
|
+
{
|
|
560
|
+
throw new AggregateException(exceptions);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
#endif
|
|
564
|
+
|
|
565
|
+
[Test]
|
|
566
|
+
public void WallstopFastArrayPoolEdgeCaseVeryLargeSize()
|
|
567
|
+
{
|
|
568
|
+
const int veryLargeSize = 1000000;
|
|
569
|
+
using PooledResource<int[]> pooled = WallstopFastArrayPool<int>.Get(veryLargeSize);
|
|
570
|
+
|
|
571
|
+
Assert.AreEqual(veryLargeSize, pooled.resource.Length);
|
|
572
|
+
Assert.AreEqual(0, pooled.resource[0]);
|
|
573
|
+
Assert.AreEqual(0, pooled.resource[veryLargeSize - 1]);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
[Test]
|
|
577
|
+
public void WallstopFastArrayPoolEdgeCaseSizeOne()
|
|
578
|
+
{
|
|
579
|
+
using PooledResource<string[]> pooled = WallstopFastArrayPool<string>.Get(1);
|
|
580
|
+
Array.Clear(pooled.resource, 0, pooled.resource.Length);
|
|
581
|
+
Assert.AreEqual(1, pooled.resource.Length);
|
|
582
|
+
Assert.IsNull(pooled.resource[0]);
|
|
583
|
+
|
|
584
|
+
pooled.resource[0] = "test";
|
|
585
|
+
Assert.AreEqual("test", pooled.resource[0]);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
[Test]
|
|
589
|
+
public void WallstopFastArrayPoolPoolingBehaviorLifo()
|
|
590
|
+
{
|
|
591
|
+
const int size = 15;
|
|
592
|
+
int[][] arrays = new int[3][];
|
|
593
|
+
{
|
|
594
|
+
using PooledResource<int[]> pooled1 = WallstopFastArrayPool<int>.Get(size);
|
|
595
|
+
arrays[0] = pooled1.resource;
|
|
596
|
+
using PooledResource<int[]> pooled2 = WallstopFastArrayPool<int>.Get(size);
|
|
597
|
+
arrays[1] = pooled2.resource;
|
|
598
|
+
using PooledResource<int[]> pooled3 = WallstopFastArrayPool<int>.Get(size);
|
|
599
|
+
arrays[2] = pooled3.resource;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
using PooledResource<int[]> pooledReuse1 = WallstopFastArrayPool<int>.Get(size);
|
|
603
|
+
Assert.AreSame(arrays[0], pooledReuse1.resource);
|
|
604
|
+
|
|
605
|
+
using PooledResource<int[]> pooledReuse2 = WallstopFastArrayPool<int>.Get(size);
|
|
606
|
+
Assert.AreSame(arrays[1], pooledReuse2.resource);
|
|
607
|
+
|
|
608
|
+
using PooledResource<int[]> pooledReuse3 = WallstopFastArrayPool<int>.Get(size);
|
|
609
|
+
Assert.AreSame(arrays[2], pooledReuse3.resource);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
[Test]
|
|
613
|
+
public void WallstopArrayPoolGetNegativeSizeThrowsArgumentOutOfRangeException()
|
|
614
|
+
{
|
|
615
|
+
Assert.Throws<ArgumentOutOfRangeException>(() => WallstopArrayPool<int>.Get(-1));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
[Test]
|
|
619
|
+
public void WallstopArrayPoolGetZeroSizeReturnsEmptyArrayWithNoOpDispose()
|
|
620
|
+
{
|
|
621
|
+
using PooledResource<int[]> pooled = WallstopArrayPool<int>.Get(0);
|
|
622
|
+
Assert.NotNull(pooled.resource);
|
|
623
|
+
Assert.AreEqual(0, pooled.resource.Length);
|
|
624
|
+
Assert.AreSame(Array.Empty<int>(), pooled.resource);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
[Test]
|
|
628
|
+
public void WallstopArrayPoolGetPositiveSizeReturnsArrayWithCorrectLength()
|
|
629
|
+
{
|
|
630
|
+
const int size = 10;
|
|
631
|
+
using PooledResource<int[]> pooled = WallstopArrayPool<int>.Get(size);
|
|
632
|
+
|
|
633
|
+
Assert.NotNull(pooled.resource);
|
|
634
|
+
Assert.AreEqual(size, pooled.resource.Length);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
[Test]
|
|
638
|
+
public void WallstopArrayPoolGetSameSizeReusesArrayAfterDispose()
|
|
639
|
+
{
|
|
640
|
+
const int size = 5;
|
|
641
|
+
int[] firstArray;
|
|
642
|
+
|
|
643
|
+
using (PooledResource<int[]> pooled = WallstopArrayPool<int>.Get(size))
|
|
644
|
+
{
|
|
645
|
+
firstArray = pooled.resource;
|
|
646
|
+
firstArray[0] = 42;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
using PooledResource<int[]> pooledReused = WallstopArrayPool<int>.Get(size);
|
|
650
|
+
Assert.AreSame(firstArray, pooledReused.resource);
|
|
651
|
+
Assert.AreEqual(0, pooledReused.resource[0]);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
[Test]
|
|
655
|
+
public void WallstopGenericPoolGetReturnsValidPooledResource()
|
|
656
|
+
{
|
|
657
|
+
using PooledResource<List<int>> pooled = WallstopGenericPool<List<int>>.Get();
|
|
658
|
+
|
|
659
|
+
Assert.NotNull(pooled.resource);
|
|
660
|
+
Assert.AreEqual(0, pooled.resource.Count);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
[Test]
|
|
664
|
+
public void WallstopGenericPoolGetReusesInstanceAfterDispose()
|
|
665
|
+
{
|
|
666
|
+
List<int> firstList;
|
|
667
|
+
|
|
668
|
+
using (PooledResource<List<int>> pooled = WallstopGenericPool<List<int>>.Get())
|
|
669
|
+
{
|
|
670
|
+
firstList = pooled.resource;
|
|
671
|
+
firstList.Add(42);
|
|
672
|
+
firstList.Add(100);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
using PooledResource<List<int>> pooledReused = WallstopGenericPool<List<int>>.Get();
|
|
676
|
+
Assert.AreSame(firstList, pooledReused.resource);
|
|
677
|
+
Assert.AreEqual(0, pooledReused.resource.Count);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
[Test]
|
|
681
|
+
public void WallstopGenericPoolClearActionWorksWithCustomType()
|
|
682
|
+
{
|
|
683
|
+
using PooledResource<HashSet<string>> pooled = WallstopGenericPool<
|
|
684
|
+
HashSet<string>
|
|
685
|
+
>.Get();
|
|
686
|
+
|
|
687
|
+
pooled.resource.Add("test1");
|
|
688
|
+
pooled.resource.Add("test2");
|
|
689
|
+
Assert.AreEqual(2, pooled.resource.Count);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
[Test]
|
|
693
|
+
public void PooledResourceDisposeCallsOnDisposeAction()
|
|
694
|
+
{
|
|
695
|
+
bool disposeCalled = false;
|
|
696
|
+
WallstopGenericPool<List<int>>.clearAction ??= list => list.Clear();
|
|
697
|
+
WallstopGenericPool<List<int>>.clearAction += Callback;
|
|
698
|
+
try
|
|
699
|
+
{
|
|
700
|
+
using (PooledResource<List<int>> pooled = WallstopGenericPool<List<int>>.Get())
|
|
701
|
+
{
|
|
702
|
+
Assert.NotNull(pooled.resource);
|
|
703
|
+
Assert.IsFalse(disposeCalled);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
Assert.IsTrue(disposeCalled);
|
|
707
|
+
}
|
|
708
|
+
finally
|
|
709
|
+
{
|
|
710
|
+
WallstopGenericPool<List<int>>.clearAction -= Callback;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return;
|
|
714
|
+
void Callback(List<int> list)
|
|
715
|
+
{
|
|
716
|
+
disposeCalled = true;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
54
719
|
}
|
|
55
720
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "com.wallstop-studios.unity-helpers",
|
|
3
|
-
"version": "2.0.0-rc80.
|
|
3
|
+
"version": "2.0.0-rc80.6",
|
|
4
4
|
"displayName": "Unity Helpers",
|
|
5
5
|
"description": "Various Unity Helper Library",
|
|
6
6
|
"dependencies": {},
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
|
|
59
|
+
|
|
58
60
|
|
|
59
61
|
|
|
60
62
|
|