123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- using System;
- using AOT;
- using NUnit.Framework;
- using Unity.Burst;
- using Unity.Collections;
- using Unity.Collections.LowLevel.Unsafe;
- using Unity.Jobs;
- using Unity.Mathematics;
- using Unity.Jobs.LowLevel.Unsafe;
- using Unity.Collections.Tests;
-
- internal class CustomAllocatorTests : CollectionsTestCommonBase
- {
- [TestCase(1337, 1337)]
- [TestCase(0xFFFF, 0x0000)]
- [TestCase(0x0000, 0xFFFF)]
- [TestCase(0xFFFF, 0xFFFF)]
- [TestCase(0x0000, 0x0000)]
- public void AllocatorHandleToAllocatorRoundTripWorks(int i, int v)
- {
- var Index = (ushort)i;
- var Version = (ushort)v;
- AllocatorManager.AllocatorHandle srcHandle = new AllocatorManager.AllocatorHandle{ Index = Index, Version = Version };
- Allocator srcAllocator = srcHandle.ToAllocator;
- AllocatorManager.AllocatorHandle destHandle = AllocatorManager.ConvertToAllocatorHandle(srcAllocator);
- Assert.AreEqual(srcHandle.Index, destHandle.Index);
- Assert.AreEqual(srcHandle.Version, destHandle.Version);
- Allocator destAllocator = destHandle.ToAllocator;
- Assert.AreEqual(srcAllocator, destAllocator);
- }
-
- [Test]
- public void AllocatorVersioningWorks()
- {
- AllocatorManager.Initialize();
- var origin = AllocatorManager.Persistent;
- var storage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
- for(var i = 1; i <= 3; ++i)
- {
- var allocatorHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
- ref var allocator = ref allocatorHelper.Allocator;
- allocator.Initialize(storage);
- var oldIndex = allocator.Handle.Index;
- var oldVersion = allocator.Handle.Version;
- allocator.Dispose();
- #if ENABLE_UNITY_COLLECTIONS_CHECKS
- var newVersion = AllocatorManager.SharedStatics.Version.Ref.Data.ElementAt(oldIndex);
- Assert.AreEqual(oldVersion + 1, newVersion);
- #endif
- allocatorHelper.Dispose();
- }
- storage.Dispose();
- AllocatorManager.Shutdown();
- }
-
- [Test]
- public void ReleasingChildHandlesWorks()
- {
- AllocatorManager.Initialize();
- var origin = AllocatorManager.Persistent;
- var storage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
- var allocatorHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
- ref var allocator = ref allocatorHelper.Allocator;
- allocator.Initialize(storage);
- var list = NativeList<int>.New(10, ref allocator);
- list.Add(0); // put something in the list, so it'll have a size for later
- allocator.Dispose(); // ok to tear down the storage that the stack allocator used, too.
- #if ENABLE_UNITY_COLLECTIONS_CHECKS
- Assert.Throws<ObjectDisposedException>(
- () => {
- list[0] = 0; // we haven't disposed this list, but it was released automatically already. so this is an error.
- });
- #endif
- storage.Dispose();
- allocatorHelper.Dispose();
- AllocatorManager.Shutdown();
- }
-
- [Test]
- public unsafe void ReleasingChildAllocatorsWorks()
- {
- AllocatorManager.Initialize();
-
- var origin = AllocatorManager.Persistent;
- var parentStorage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
- var parentHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
- ref var parent = ref parentHelper.Allocator;
-
- parent.Initialize(parentStorage); // and make a stack allocator from it
-
- var childStorage = parent.AllocateBlock(default(byte), 10000); // allocate some space from the parent
- var childHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
- childHelper.Allocator.Initialize(childStorage); // and make a stack allocator from it
-
- parent.Dispose(); // tear down the parent allocator
-
- #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
- Assert.Throws<ArgumentException>(() =>
- {
- childHelper.Allocator.Allocate(default(byte), 1000); // try to allocate from the child - it should fail.
- });
- #endif
- parentStorage.Dispose();
- parentHelper.Dispose();
- childHelper.Dispose();
- AllocatorManager.Shutdown();
- }
-
- [Test]
- public void AllocatesAndFreesFromMono()
- {
- AllocatorManager.Initialize();
- const int kLength = 100;
-
- var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
-
- for (int i = 0; i < kLength; ++i)
- {
- var allocator = AllocatorManager.Persistent;
- var block = allocator.AllocateBlock(default(int), i);
- if(i != 0)
- Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
- Assert.AreEqual(i, block.Range.Items);
- Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
- Assert.AreEqual(expectedAlignment, block.Alignment);
- Assert.AreEqual(AllocatorManager.Persistent.Value, block.Range.Allocator.Value);
- allocator.FreeBlock(ref block);
- }
- AllocatorManager.Shutdown();
- }
-
- [BurstCompile(CompileSynchronously = true)]
- struct AllocateJob : IJobParallelFor
- {
- public NativeArray<AllocatorManager.Block> m_blocks;
- public void Execute(int index)
- {
- var allocator = AllocatorManager.Persistent;
- m_blocks[index] = allocator.AllocateBlock(default(int), index);
- }
- }
-
- [BurstCompile(CompileSynchronously = true)]
- struct FreeJob : IJobParallelFor
- {
- public NativeArray<AllocatorManager.Block> m_blocks;
- public void Execute(int index)
- {
- var temp = m_blocks[index];
- temp.Free();
- m_blocks[index] = temp;
- }
- }
-
- [Test]
- public void AllocatesAndFreesFromBurst()
- {
- AllocatorManager.Initialize();
-
- const int kLength = 100;
- var blocks = new NativeArray<AllocatorManager.Block>(kLength, Allocator.Persistent);
- var allocateJob = new AllocateJob();
- allocateJob.m_blocks = blocks;
- allocateJob.Schedule(kLength, 1).Complete();
-
- var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
-
- for (int i = 0; i < kLength; ++i)
- {
- var block = allocateJob.m_blocks[i];
- if(i != 0)
- Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
- Assert.AreEqual(i, block.Range.Items);
- Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
- Assert.AreEqual(expectedAlignment, block.Alignment);
- Assert.AreEqual(AllocatorManager.Persistent.Value, block.Range.Allocator.Value);
- }
-
- var freeJob = new FreeJob();
- freeJob.m_blocks = blocks;
- freeJob.Schedule(kLength, 1).Complete();
-
- for (int i = 0; i < kLength; ++i)
- {
- var block = allocateJob.m_blocks[i];
- Assert.AreEqual(IntPtr.Zero, block.Range.Pointer);
- Assert.AreEqual(0, block.Range.Items);
- Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
- Assert.AreEqual(expectedAlignment, block.Alignment);
- Assert.AreEqual(AllocatorManager.Persistent.Value, block.Range.Allocator.Value);
- }
- blocks.Dispose();
- AllocatorManager.Shutdown();
- }
-
- // This allocator wraps UnsafeUtility.Malloc, but also initializes memory to some constant value after allocating.
- [BurstCompile(CompileSynchronously = true)]
- struct ClearToValueAllocator : AllocatorManager.IAllocator
- {
- public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } }
-
- public Allocator ToAllocator { get { return m_handle.ToAllocator; } }
-
- public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } }
-
- internal AllocatorManager.AllocatorHandle m_handle;
- internal AllocatorManager.AllocatorHandle m_parent;
-
- public byte m_clearValue;
-
- public void Initialize<T>(byte ClearValue, ref T parent) where T : unmanaged, AllocatorManager.IAllocator
- {
- m_parent = parent.Handle;
- m_clearValue = ClearValue;
- #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
- parent.Handle.AddChildAllocator(m_handle);
- #endif
- }
-
- public unsafe int Try(ref AllocatorManager.Block block)
- {
- var temp = block.Range.Allocator;
- block.Range.Allocator = m_parent;
- var error = AllocatorManager.Try(ref block);
- block.Range.Allocator = temp;
- if (error != 0)
- return error;
- if (block.Range.Pointer != IntPtr.Zero) // if we allocated or reallocated...
- UnsafeUtility.MemSet((void*)block.Range.Pointer, m_clearValue, block.Bytes); // clear to a value.
- return 0;
- }
-
- [BurstCompile(CompileSynchronously = true)]
- [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))]
- public static unsafe int Try(IntPtr state, ref AllocatorManager.Block block)
- {
- return ((ClearToValueAllocator*)state)->Try(ref block);
- }
-
- public AllocatorManager.TryFunction Function => Try;
- public void Dispose()
- {
- m_handle.Dispose();
- }
- }
-
- [BurstCompile(CompileSynchronously = true)]
- internal struct CountingAllocator : AllocatorManager.IAllocator
- {
- public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } }
-
- public Allocator ToAllocator { get { return m_handle.ToAllocator; } }
-
- public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } }
-
- public AllocatorManager.AllocatorHandle m_handle;
- public int AllocationCount;
- public long Used;
- public bool WasUsed => AllocationCount > 0;
- public bool IsUsed => Used > 0;
- public void Initialize()
- {
- AllocationCount = 0;
- Used = 0;
- #if ENABLE_UNITY_ALLOCATIONS_CHECKS
- AllocatorManager.Persistent.Handle.AddChildAllocator(m_handle);
- #endif
- }
-
- public int Try(ref AllocatorManager.Block block)
- {
- if (block.Range.Pointer != IntPtr.Zero)
- {
- Used -= block.AllocatedBytes;
- }
-
- var temp = block.Range.Allocator;
- block.Range.Allocator = AllocatorManager.Persistent;
- var error = AllocatorManager.Try(ref block);
- block.Range.Allocator = temp;
- if (error != 0)
- return error;
- if (block.Range.Pointer != IntPtr.Zero) // if we allocated or reallocated...
- {
- ++AllocationCount;
- Used += block.AllocatedBytes;
- }
-
- return 0;
- }
-
- [BurstCompile(CompileSynchronously = true)]
- [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))]
- public static unsafe int Try(IntPtr state, ref AllocatorManager.Block block)
- {
- return ((CountingAllocator*)state)->Try(ref block);
- }
-
- public AllocatorManager.TryFunction Function => Try;
- public void Dispose()
- {
- m_handle.Dispose();
- }
- }
-
- [Test]
- public void UserDefinedAllocatorWorks()
- {
- AllocatorManager.Initialize();
- var parent = AllocatorManager.Persistent;
- var allocatorHelper = new AllocatorHelper<ClearToValueAllocator>(AllocatorManager.Persistent);
- ref var allocator = ref allocatorHelper.Allocator;
- allocator.Initialize(0, ref parent);
-
- var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
-
- for (byte ClearValue = 0; ClearValue < 0xF; ++ClearValue)
- {
- allocator.m_clearValue = ClearValue;
- const int kLength = 100;
- for (int i = 1; i < kLength; ++i)
- {
- var block = allocator.AllocateBlock(default(int), i);
- Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
- Assert.AreEqual(i, block.Range.Items);
- Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
- Assert.AreEqual(expectedAlignment, block.Alignment);
- allocator.FreeBlock(ref block);
- }
- }
- allocator.Dispose();
- allocatorHelper.Dispose();
- AllocatorManager.Shutdown();
- }
-
- // this is testing for the case where we want+ to install a stack allocator that itself allocates from a big hunk
- // of memory provided by the default Persistent allocator, and then make allocations on the stack allocator.
- [Test]
- public void StackAllocatorWorks()
- {
- AllocatorManager.Initialize();
- var origin = AllocatorManager.Persistent;
- var backingStorage = origin.AllocateBlock(default(byte), 100000); // allocate a block of bytes from Malloc.Persistent
- var allocatorHelper = new AllocatorHelper<AllocatorManager.StackAllocator>(AllocatorManager.Persistent);
- ref var allocator = ref allocatorHelper.Allocator;
- allocator.Initialize(backingStorage);
-
- var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
-
- const int kLength = 100;
- for (int i = 1; i < kLength; ++i)
- {
- var block = allocator.AllocateBlock(default(int), i);
- Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
- Assert.AreEqual(i, block.Range.Items);
- Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block.BytesPerItem);
- Assert.AreEqual(expectedAlignment, block.Alignment);
- allocator.FreeBlock(ref block);
- }
- allocator.Dispose();
- backingStorage.Dispose();
- allocatorHelper.Dispose();
- AllocatorManager.Shutdown();
- }
-
- [Test]
- public void CustomAllocatorNativeListWorksWithoutHandles()
- {
- AllocatorManager.Initialize();
- var allocator = AllocatorManager.Persistent;
- var list = NativeList<byte>.New(100, ref allocator);
- list.Dispose(ref allocator);
- AllocatorManager.Shutdown();
- }
-
- [Test]
- [TestRequiresDotsDebugOrCollectionChecks]
- public void CustomAllocatorNativeListThrowsWhenAllocatorIsWrong()
- {
- AllocatorManager.Initialize();
- var allocator0 = AllocatorManager.Persistent;
- var list = NativeList<byte>.New(100, ref allocator0);
- Assert.Throws<ArgumentOutOfRangeException>(() =>
- {
- list.Dispose(ref CommonRwdAllocatorHelper.Allocator);
- });
- list.Dispose(ref allocator0);
- AllocatorManager.Shutdown();
- }
-
- // this is testing for the case where we want to install a custom allocator that clears memory to a constant
- // byte value, and then have an UnsafeList use that custom allocator.
- [Test]
- public void CustomAllocatorUnsafeListWorks()
- {
- AllocatorManager.Initialize();
- var parent = AllocatorManager.Persistent;
- var allocatorHelper = new AllocatorHelper<ClearToValueAllocator>(AllocatorManager.Persistent);
- ref var allocator = ref allocatorHelper.Allocator;
- allocator.Initialize(0xFE, ref parent);
- for (byte ClearValue = 0; ClearValue < 0xF; ++ClearValue)
- {
- allocator.m_clearValue = ClearValue;
- var unsafelist = new UnsafeList<byte>(1, allocator.Handle);
- const int kLength = 100;
- unsafelist.Resize(kLength);
- for (int i = 0; i < kLength; ++i)
- Assert.AreEqual(ClearValue, unsafelist[i]);
- unsafelist.Dispose();
- }
- allocator.Dispose();
- allocatorHelper.Dispose();
- AllocatorManager.Shutdown();
- }
-
- [Test]
- public unsafe void SlabAllocatorWorks()
- {
- var SlabSizeInBytes = 256;
- var SlabSizeInInts = SlabSizeInBytes / sizeof(int);
- var Slabs = 256;
- AllocatorManager.Initialize();
- var origin = AllocatorManager.Persistent;
- var backingStorage = origin.AllocateBlock(default(byte), Slabs * SlabSizeInBytes); // allocate a block of bytes from Malloc.Persistent
- var allocatorHelper = new AllocatorHelper<AllocatorManager.SlabAllocator>(AllocatorManager.Persistent);
- ref var allocator = ref allocatorHelper.Allocator;
- allocator.Initialize(backingStorage, SlabSizeInBytes, Slabs * SlabSizeInBytes);
-
- var expectedAlignment = math.max(JobsUtility.CacheLineSize, UnsafeUtility.AlignOf<int>());
-
- var block0 = allocator.AllocateBlock(default(int), SlabSizeInInts);
- Assert.AreNotEqual(IntPtr.Zero, block0.Range.Pointer);
- Assert.AreEqual(SlabSizeInInts, block0.Range.Items);
- Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block0.BytesPerItem);
- Assert.AreEqual(expectedAlignment, block0.Alignment);
- Assert.AreEqual(1, allocator.Occupied[0]);
-
- var block1 = allocator.AllocateBlock(default(int), SlabSizeInInts - 1);
- Assert.AreNotEqual(IntPtr.Zero, block1.Range.Pointer);
- Assert.AreEqual(SlabSizeInInts - 1, block1.Range.Items);
- Assert.AreEqual(UnsafeUtility.SizeOf<int>(), block1.BytesPerItem);
- Assert.AreEqual(expectedAlignment, block1.Alignment);
- Assert.AreEqual(3, allocator.Occupied[0]);
-
- allocator.FreeBlock(ref block0);
- Assert.AreEqual(2, allocator.Occupied[0]);
- allocator.FreeBlock(ref block1);
- Assert.AreEqual(0, allocator.Occupied[0]);
-
- #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
- Assert.Throws<ArgumentException>(() =>
- {
- allocatorHelper.Allocator.AllocateBlock(default(int), 65);
- });
- #endif
-
- allocator.Dispose();
- backingStorage.Dispose();
- allocatorHelper.Dispose();
- AllocatorManager.Shutdown();
- }
-
- [Test]
- public unsafe void CollectionHelper_IsAligned()
- {
- #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
- Assert.Throws<ArgumentException>(() => CollectionHelper.IsAligned((void*)0x0, 0)); // value is 0
- Assert.Throws<ArgumentException>(() => CollectionHelper.IsAligned((void*)0x1000, 0)); // alignment is 0
- Assert.Throws<ArgumentException>(() => CollectionHelper.IsAligned((void*)0x1000, 3)); // alignment is not pow2
- #endif
-
- for (var i = 0; i < 31; ++i)
- {
- Assert.IsTrue(CollectionHelper.IsAligned((void*)0x80000000, 1<<i));
- }
- }
-
- [Test]
- public void AllocatorManager_AllocateBlock_UsesAlignmentArgument()
- {
- int sizeOf = 1;
- int alignOf = 4096;
- int items = 1;
- var temp = AllocatorManager.Temp;
- var block = temp.AllocateBlock(sizeOf, alignOf, items);
- Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
-
- unsafe
- {
- Assert.IsTrue(CollectionHelper.IsAligned((void*)block.Range.Pointer, alignOf));
- }
- }
-
- [Test]
- public void AllocatorManager_AllocateBlock_AlwaysCacheLineAligned()
- {
- int sizeOf = 1;
- int items = 1;
- var temp = AllocatorManager.Temp;
-
- for (int alignment = 1; alignment < 256; ++alignment)
- {
- var block = temp.AllocateBlock(sizeOf, alignment, items);
- Assert.AreNotEqual(IntPtr.Zero, block.Range.Pointer);
-
- unsafe
- {
- Assert.IsTrue(CollectionHelper.IsAligned((void*)block.Range.Pointer, CollectionHelper.CacheLineSize));
- }
- }
- }
-
- [Test]
- public void AllocatorManager_Block_DoesNotOverflow()
- {
- AllocatorManager.Block block = default;
- block.BytesPerItem = int.MaxValue;
- block.AllocatedItems = 2;
- block.Range = new AllocatorManager.Range { Items = 2 };
-
- long ExpectedAllocatedBytes = (long)block.BytesPerItem * (long)block.AllocatedItems;
- long ExpectedBytes = (long)block.BytesPerItem * (long)block.Range.Items;
- Assert.AreEqual(ExpectedAllocatedBytes, block.AllocatedBytes);
- Assert.AreEqual(ExpectedBytes, block.Bytes);
- }
- }
|