A game about forced loneliness, made by TACStudios
at master 526 lines 20 kB view raw
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}