A game about forced loneliness, made by TACStudios
1using System;
2using AOT;
3using NUnit.Framework;
4using Unity.Burst;
5using Unity.Collections;
6using Unity.Collections.LowLevel.Unsafe;
7using Unity.Jobs;
8using Unity.Mathematics;
9using Unity.Jobs.LowLevel.Unsafe;
10using Unity.Collections.Tests;
11
12internal class CustomAllocatorTests : CollectionsTestCommonBase
13{
14 [TestCase(1337, 1337)]
15 [TestCase(0xFFFF, 0x0000)]
16 [TestCase(0x0000, 0xFFFF)]
17 [TestCase(0xFFFF, 0xFFFF)]
18 [TestCase(0x0000, 0x0000)]
19 public void AllocatorHandleToAllocatorRoundTripWorks(int i, int v)
20 {
21 var Index = (ushort)i;
22 var Version = (ushort)v;
23 AllocatorManager.AllocatorHandle srcHandle = new AllocatorManager.AllocatorHandle{ Index = Index, Version = Version };
24 Allocator srcAllocator = srcHandle.ToAllocator;
25 AllocatorManager.AllocatorHandle destHandle = AllocatorManager.ConvertToAllocatorHandle(srcAllocator);
26 Assert.AreEqual(srcHandle.Index, destHandle.Index);
27 Assert.AreEqual(srcHandle.Version, destHandle.Version);
28 Allocator destAllocator = destHandle.ToAllocator;
29 Assert.AreEqual(srcAllocator, destAllocator);
30 }
31
32 [Test]
33 public void AllocatorVersioningWorks()
34 {
35 AllocatorManager.Initialize();
36 var origin = AllocatorManager.Persistent;
37 var storage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
38 for(var i = 1; i <= 3; ++i)
39 {
40 var allocatorHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
41 ref var allocator = ref allocatorHelper.Allocator;
42 allocator.Initialize(storage);
43 var oldIndex = allocator.Handle.Index;
44 var oldVersion = allocator.Handle.Version;
45 allocator.Dispose();
46#if ENABLE_UNITY_COLLECTIONS_CHECKS
47 var newVersion = AllocatorManager.SharedStatics.Version.Ref.Data.ElementAt(oldIndex);
48 Assert.AreEqual(oldVersion + 1, newVersion);
49#endif
50 allocatorHelper.Dispose();
51 }
52 storage.Dispose();
53 AllocatorManager.Shutdown();
54 }
55
56 [Test]
57 public void ReleasingChildHandlesWorks()
58 {
59 AllocatorManager.Initialize();
60 var origin = AllocatorManager.Persistent;
61 var storage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
62 var allocatorHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
63 ref var allocator = ref allocatorHelper.Allocator;
64 allocator.Initialize(storage);
65 var list = NativeList<int>.New(10, ref allocator);
66 list.Add(0); // put something in the list, so it'll have a size for later
67 allocator.Dispose(); // ok to tear down the storage that the stack allocator used, too.
68#if ENABLE_UNITY_COLLECTIONS_CHECKS
69 Assert.Throws<ObjectDisposedException>(
70 () => {
71 list[0] = 0; // we haven't disposed this list, but it was released automatically already. so this is an error.
72 });
73#endif
74 storage.Dispose();
75 allocatorHelper.Dispose();
76 AllocatorManager.Shutdown();
77 }
78
79 [Test]
80 public unsafe void ReleasingChildAllocatorsWorks()
81 {
82 AllocatorManager.Initialize();
83
84 var origin = AllocatorManager.Persistent;
85 var parentStorage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
86 var parentHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
87 ref var parent = ref parentHelper.Allocator;
88
89 parent.Initialize(parentStorage); // and make a stack allocator from it
90
91 var childStorage = parent.AllocateBlock(default(byte), 10000); // allocate some space from the parent
92 var childHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
93 childHelper.Allocator.Initialize(childStorage); // and make a stack allocator from it
94
95 parent.Dispose(); // tear down the parent allocator
96
97#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
98 Assert.Throws<ArgumentException>(() =>
99 {
100 childHelper.Allocator.Allocate(default(byte), 1000); // try to allocate from the child - it should fail.
101 });
102#endif
103 parentStorage.Dispose();
104 parentHelper.Dispose();
105 childHelper.Dispose();
106 AllocatorManager.Shutdown();
107 }
108
109 [Test]
110 public void AllocatesAndFreesFromMono()
111 {
112 AllocatorManager.Initialize();
113 const int kLength = 100;
114
115 var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
116
117 for (int i = 0; i < kLength; ++i)
118 {
119 var allocator = AllocatorManager.Persistent;
120 var block = allocator.AllocateBlock(default(int), i);
121 if(i != 0)
122 Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
123 Assert.AreEqual(i, block.Range.Items);
124 Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
125 Assert.AreEqual(expectedAlignment, block.Alignment);
126 Assert.AreEqual(AllocatorManager.Persistent.Value, block.Range.Allocator.Value);
127 allocator.FreeBlock(ref block);
128 }
129 AllocatorManager.Shutdown();
130 }
131
132 [BurstCompile(CompileSynchronously = true)]
133 struct AllocateJob : IJobParallelFor
134 {
135 public NativeArray<AllocatorManager.Block> m_blocks;
136 public void Execute(int index)
137 {
138 var allocator = AllocatorManager.Persistent;
139 m_blocks[index] = allocator.AllocateBlock(default(int), index);
140 }
141 }
142
143 [BurstCompile(CompileSynchronously = true)]
144 struct FreeJob : IJobParallelFor
145 {
146 public NativeArray<AllocatorManager.Block> m_blocks;
147 public void Execute(int index)
148 {
149 var temp = m_blocks[index];
150 temp.Free();
151 m_blocks[index] = temp;
152 }
153 }
154
155 [Test]
156 public void AllocatesAndFreesFromBurst()
157 {
158 AllocatorManager.Initialize();
159
160 const int kLength = 100;
161 var blocks = new NativeArray<AllocatorManager.Block>(kLength, Allocator.Persistent);
162 var allocateJob = new AllocateJob();
163 allocateJob.m_blocks = blocks;
164 allocateJob.Schedule(kLength, 1).Complete();
165
166 var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
167
168 for (int i = 0; i < kLength; ++i)
169 {
170 var block = allocateJob.m_blocks[i];
171 if(i != 0)
172 Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
173 Assert.AreEqual(i, block.Range.Items);
174 Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
175 Assert.AreEqual(expectedAlignment, block.Alignment);
176 Assert.AreEqual(AllocatorManager.Persistent.Value, block.Range.Allocator.Value);
177 }
178
179 var freeJob = new FreeJob();
180 freeJob.m_blocks = blocks;
181 freeJob.Schedule(kLength, 1).Complete();
182
183 for (int i = 0; i < kLength; ++i)
184 {
185 var block = allocateJob.m_blocks[i];
186 Assert.AreEqual(IntPtr.Zero, block.Range.Pointer);
187 Assert.AreEqual(0, block.Range.Items);
188 Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
189 Assert.AreEqual(expectedAlignment, block.Alignment);
190 Assert.AreEqual(AllocatorManager.Persistent.Value, block.Range.Allocator.Value);
191 }
192 blocks.Dispose();
193 AllocatorManager.Shutdown();
194 }
195
196 // This allocator wraps UnsafeUtility.Malloc, but also initializes memory to some constant value after allocating.
197 [BurstCompile(CompileSynchronously = true)]
198 struct ClearToValueAllocator : AllocatorManager.IAllocator
199 {
200 public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } }
201
202 public Allocator ToAllocator { get { return m_handle.ToAllocator; } }
203
204 public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } }
205
206 internal AllocatorManager.AllocatorHandle m_handle;
207 internal AllocatorManager.AllocatorHandle m_parent;
208
209 public byte m_clearValue;
210
211 public void Initialize<T>(byte ClearValue, ref T parent) where T : unmanaged, AllocatorManager.IAllocator
212 {
213 m_parent = parent.Handle;
214 m_clearValue = ClearValue;
215#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
216 parent.Handle.AddChildAllocator(m_handle);
217#endif
218 }
219
220 public unsafe int Try(ref AllocatorManager.Block block)
221 {
222 var temp = block.Range.Allocator;
223 block.Range.Allocator = m_parent;
224 var error = AllocatorManager.Try(ref block);
225 block.Range.Allocator = temp;
226 if (error != 0)
227 return error;
228 if (block.Range.Pointer != IntPtr.Zero) // if we allocated or reallocated...
229 UnsafeUtility.MemSet((void*)block.Range.Pointer, m_clearValue, block.Bytes); // clear to a value.
230 return 0;
231 }
232
233 [BurstCompile(CompileSynchronously = true)]
234 [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))]
235 public static unsafe int Try(IntPtr state, ref AllocatorManager.Block block)
236 {
237 return ((ClearToValueAllocator*)state)->Try(ref block);
238 }
239
240 public AllocatorManager.TryFunction Function => Try;
241 public void Dispose()
242 {
243 m_handle.Dispose();
244 }
245 }
246
247 [BurstCompile(CompileSynchronously = true)]
248 internal struct CountingAllocator : AllocatorManager.IAllocator
249 {
250 public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } }
251
252 public Allocator ToAllocator { get { return m_handle.ToAllocator; } }
253
254 public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } }
255
256 public AllocatorManager.AllocatorHandle m_handle;
257 public int AllocationCount;
258 public long Used;
259 public bool WasUsed => AllocationCount > 0;
260 public bool IsUsed => Used > 0;
261 public void Initialize()
262 {
263 AllocationCount = 0;
264 Used = 0;
265#if ENABLE_UNITY_ALLOCATIONS_CHECKS
266 AllocatorManager.Persistent.Handle.AddChildAllocator(m_handle);
267#endif
268 }
269
270 public int Try(ref AllocatorManager.Block block)
271 {
272 if (block.Range.Pointer != IntPtr.Zero)
273 {
274 Used -= block.AllocatedBytes;
275 }
276
277 var temp = block.Range.Allocator;
278 block.Range.Allocator = AllocatorManager.Persistent;
279 var error = AllocatorManager.Try(ref block);
280 block.Range.Allocator = temp;
281 if (error != 0)
282 return error;
283 if (block.Range.Pointer != IntPtr.Zero) // if we allocated or reallocated...
284 {
285 ++AllocationCount;
286 Used += block.AllocatedBytes;
287 }
288
289 return 0;
290 }
291
292 [BurstCompile(CompileSynchronously = true)]
293 [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))]
294 public static unsafe int Try(IntPtr state, ref AllocatorManager.Block block)
295 {
296 return ((CountingAllocator*)state)->Try(ref block);
297 }
298
299 public AllocatorManager.TryFunction Function => Try;
300 public void Dispose()
301 {
302 m_handle.Dispose();
303 }
304 }
305
306 [Test]
307 public void UserDefinedAllocatorWorks()
308 {
309 AllocatorManager.Initialize();
310 var parent = AllocatorManager.Persistent;
311 var allocatorHelper = new AllocatorHelper<ClearToValueAllocator>(AllocatorManager.Persistent);
312 ref var allocator = ref allocatorHelper.Allocator;
313 allocator.Initialize(0, ref parent);
314
315 var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
316
317 for (byte ClearValue = 0; ClearValue < 0xF; ++ClearValue)
318 {
319 allocator.m_clearValue = ClearValue;
320 const int kLength = 100;
321 for (int i = 1; i < kLength; ++i)
322 {
323 var block = allocator.AllocateBlock(default(int), i);
324 Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
325 Assert.AreEqual(i, block.Range.Items);
326 Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
327 Assert.AreEqual(expectedAlignment, block.Alignment);
328 allocator.FreeBlock(ref block);
329 }
330 }
331 allocator.Dispose();
332 allocatorHelper.Dispose();
333 AllocatorManager.Shutdown();
334 }
335
336 // this is testing for the case where we want+ to install a stack allocator that itself allocates from a big hunk
337 // of memory provided by the default Persistent allocator, and then make allocations on the stack allocator.
338 [Test]
339 public void StackAllocatorWorks()
340 {
341 AllocatorManager.Initialize();
342 var origin = AllocatorManager.Persistent;
343 var backingStorage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
344 var allocatorHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
345 ref var allocator = ref allocatorHelper.Allocator;
346 allocator.Initialize(backingStorage);
347
348 var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
349
350 const int kLength = 100;
351 for (int i = 1; i < kLength; ++i)
352 {
353 var block = allocator.AllocateBlock(default(int), i);
354 Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
355 Assert.AreEqual(i, block.Range.Items);
356 Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
357 Assert.AreEqual(expectedAlignment, block.Alignment);
358 allocator.FreeBlock(ref block);
359 }
360 allocator.Dispose();
361 backingStorage.Dispose();
362 allocatorHelper.Dispose();
363 AllocatorManager.Shutdown();
364 }
365
366 [Test]
367 public void CustomAllocatorNativeListWorksWithoutHandles()
368 {
369 AllocatorManager.Initialize();
370 var allocator = AllocatorManager.Persistent;
371 var list = NativeList<byte>.New(100, ref allocator);
372 list.Dispose(ref allocator);
373 AllocatorManager.Shutdown();
374 }
375
376 [Test]
377 [TestRequiresDotsDebugOrCollectionChecks]
378 public void CustomAllocatorNativeListThrowsWhenAllocatorIsWrong()
379 {
380 AllocatorManager.Initialize();
381 var allocator0 = AllocatorManager.Persistent;
382 var list = NativeList<byte>.New(100, ref allocator0);
383 Assert.Throws<ArgumentOutOfRangeException>(() =>
384 {
385 list.Dispose(ref CommonRwdAllocatorHelper.Allocator);
386 });
387 list.Dispose(ref allocator0);
388 AllocatorManager.Shutdown();
389 }
390
391 // this is testing for the case where we want to install a custom allocator that clears memory to a constant
392 // byte value, and then have an UnsafeList use that custom allocator.
393 [Test]
394 public void CustomAllocatorUnsafeListWorks()
395 {
396 AllocatorManager.Initialize();
397 var parent = AllocatorManager.Persistent;
398 var allocatorHelper = new AllocatorHelper<ClearToValueAllocator>(AllocatorManager.Persistent);
399 ref var allocator = ref allocatorHelper.Allocator;
400 allocator.Initialize(0xFE, ref parent);
401 for (byte ClearValue = 0; ClearValue < 0xF; ++ClearValue)
402 {
403 allocator.m_clearValue = ClearValue;
404 var unsafelist = new UnsafeList<byte>(1, allocator.Handle);
405 const int kLength = 100;
406 unsafelist.Resize(kLength);
407 for (int i = 0; i < kLength; ++i)
408 Assert.AreEqual(ClearValue, unsafelist[i]);
409 unsafelist.Dispose();
410 }
411 allocator.Dispose();
412 allocatorHelper.Dispose();
413 AllocatorManager.Shutdown();
414 }
415
416 [Test]
417 public unsafe void SlabAllocatorWorks()
418 {
419 var SlabSizeInBytes = 256;
420 var SlabSizeInInts = SlabSizeInBytes / sizeof(int);
421 var Slabs = 256;
422 AllocatorManager.Initialize();
423 var origin = AllocatorManager.Persistent;
424 var backingStorage = origin.AllocateBlock(default(byte), Slabs * SlabSizeInBytes); // allocate a block of bytes from Malloc.Persistent
425 var allocatorHelper = new AllocatorHelper<AllocatorManager.SlabAllocator>(AllocatorManager.Persistent);
426 ref var allocator = ref allocatorHelper.Allocator;
427 allocator.Initialize(backingStorage, SlabSizeInBytes, Slabs * SlabSizeInBytes);
428
429 var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
430
431 var block0 = allocator.AllocateBlock(default(int), SlabSizeInInts);
432 Assert.AreNotEqual(IntPtr.Zero, block0.Range.Pointer);
433 Assert.AreEqual(SlabSizeInInts, block0.Range.Items);
434 Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block0.BytesPerItem);
435 Assert.AreEqual(expectedAlignment, block0.Alignment);
436 Assert.AreEqual(1, allocator.Occupied[0]);
437
438 var block1 = allocator.AllocateBlock(default(int), SlabSizeInInts - 1);
439 Assert.AreNotEqual(IntPtr.Zero, block1.Range.Pointer);
440 Assert.AreEqual(SlabSizeInInts - 1, block1.Range.Items);
441 Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block1.BytesPerItem);
442 Assert.AreEqual(expectedAlignment, block1.Alignment);
443 Assert.AreEqual(3, allocator.Occupied[0]);
444
445 allocator.FreeBlock(ref block0);
446 Assert.AreEqual(2, allocator.Occupied[0]);
447 allocator.FreeBlock(ref block1);
448 Assert.AreEqual(0, allocator.Occupied[0]);
449
450#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
451 Assert.Throws<ArgumentException>(() =>
452 {
453 allocatorHelper.Allocator.AllocateBlock(default(int), 65);
454 });
455#endif
456
457 allocator.Dispose();
458 backingStorage.Dispose();
459 allocatorHelper.Dispose();
460 AllocatorManager.Shutdown();
461 }
462
463 [Test]
464 public unsafe void CollectionHelper_IsAligned()
465 {
466#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
467 Assert.Throws<ArgumentException>(() => CollectionHelper.IsAligned((void*)0x0, 0)); // value is 0
468 Assert.Throws<ArgumentException>(() => CollectionHelper.IsAligned((void*)0x1000, 0)); // alignment is 0
469 Assert.Throws<ArgumentException>(() => CollectionHelper.IsAligned((void*)0x1000, 3)); // alignment is not pow2
470#endif
471
472 for (var i = 0; i < 31; ++i)
473 {
474 Assert.IsTrue(CollectionHelper.IsAligned((void*)0x80000000, 1<<i));
475 }
476 }
477
478 [Test]
479 public void AllocatorManager_AllocateBlock_UsesAlignmentArgument()
480 {
481 int sizeOf = 1;
482 int alignOf = 4096;
483 int items = 1;
484 var temp = AllocatorManager.Temp;
485 var block = temp.AllocateBlock(sizeOf, alignOf, items);
486 Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
487
488 unsafe
489 {
490 Assert.IsTrue(CollectionHelper.IsAligned((void*)block.Range.Pointer, alignOf));
491 }
492 }
493
494 [Test]
495 public void AllocatorManager_AllocateBlock_AlwaysCacheLineAligned()
496 {
497 int sizeOf = 1;
498 int items = 1;
499 var temp = AllocatorManager.Temp;
500
501 for (int alignment = 1; alignment < 256; ++alignment)
502 {
503 var block = temp.AllocateBlock(sizeOf, alignment, items);
504 Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
505
506 unsafe
507 {
508 Assert.IsTrue(CollectionHelper.IsAligned((void*)block.Range.Pointer, CollectionHelper.CacheLineSize));
509 }
510 }
511 }
512
513 [Test]
514 public void AllocatorManager_Block_DoesNotOverflow()
515 {
516 AllocatorManager.Block block = default;
517 block.BytesPerItem = int.MaxValue;
518 block.AllocatedItems = 2;
519 block.Range = new AllocatorManager.Range { Items = 2 };
520
521 long ExpectedAllocatedBytes = (long)block.BytesPerItem * (long)block.AllocatedItems;
522 long ExpectedBytes = (long)block.BytesPerItem * (long)block.Range.Items;
523 Assert.AreEqual(ExpectedAllocatedBytes, block.AllocatedBytes);
524 Assert.AreEqual(ExpectedBytes, block.Bytes);
525 }
526}