A game about forced loneliness, made by TACStudios
at master 353 lines 13 kB view raw
1#region allocator-custom-example 2using System; 3using AOT; 4using System.Collections.Generic; 5using System.Threading.Tasks; 6using NUnit.Framework; 7using Unity.Collections; 8using Unity.Collections.LowLevel.Unsafe; 9using Unity.Burst; 10using System.Threading; 11 12// This is the example code used in 13// Packages/com.unity.collections/Documentation~/allocator/allocator-custom.md 14// Example custom allocator. The allocator is able to allocate memory from Allocator.Persistant, 15// if successful, initialize the allocated memory with a user configured value and increment an 16// allocation count. The allocator is able to deallocate the memory, if successful, decrement 17// the allocation count. 18// A custom allocator must implement AllocatorManager.IAllocator interface 19[BurstCompile(CompileSynchronously = true)] 20internal struct ExampleCustomAllocator : AllocatorManager.IAllocator 21{ 22 // A custom allocator must contain AllocatorManager.AllocatorHandle 23 AllocatorManager.AllocatorHandle m_handle; 24 25 // Implement the Function property required by IAllocator interface 26 public AllocatorManager.TryFunction Function => AllocatorFunction; 27 28 // Implement the Handle property required by IAllocator interface 29 public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } } 30 31 // Implement the ToAllocator property required by IAllocator interface 32 public Allocator ToAllocator { get { return m_handle.ToAllocator; } } 33 34 // Implement the IsCustomAllocator property required by IAllocator interface 35 public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } } 36 37 // Implement the IsAutoDispose property required by IAllocator interface 38 // Allocations made by this example allocator are not automatically disposed. 39 // This implementation can be skipped because the default implementation of 40 // this property is false. 41 public bool IsAutoDispose { get { return false; } } 42 43 // Implement the Dispose method required by IDisposable interface because 44 // AllocatorManager.IAllocator implements IDisposable 45 public void Dispose() 46 { 47 // Make sure no memory leaks 48 Assert.AreEqual(0, m_allocationCount); 49 50 m_handle.Dispose(); 51 } 52 53 #region allocator-custom-try 54 // Value to initialize the allocated memory 55 byte m_initialValue; 56 57 // Allocation count 58 int m_allocationCount; 59 60 // Implement the Try method required by IAllocator interface 61 public unsafe int Try(ref AllocatorManager.Block block) 62 { 63 // Error status 64 int error = 0; 65 66 // Allocate 67 if (block.Range.Pointer == IntPtr.Zero) 68 { 69 // Allocate memory from Allocator.Persistant and restore the original allocator 70 AllocatorManager.AllocatorHandle tempAllocator = block.Range.Allocator; 71 block.Range.Allocator = Allocator.Persistent; 72 error = AllocatorManager.Try(ref block); 73 block.Range.Allocator = tempAllocator; 74 75 // return if error occurs 76 if (error != 0) 77 return error; 78 79 // if allocation succeeds, intialize the memory with the initial value and increment the allocation count 80 if (block.Range.Pointer != IntPtr.Zero) 81 { 82 UnsafeUtility.MemSet((void*)block.Range.Pointer, m_initialValue, block.Bytes); 83 Interlocked.Increment(ref m_allocationCount); 84 85 } 86 return 0; 87 } 88 // Deallocate 89 else 90 { 91 // Deallocate memory from Allocator.Persistant and restore the original allocator 92 AllocatorManager.AllocatorHandle tempAllocator = block.Range.Allocator; 93 block.Range.Allocator = Allocator.Persistent; 94 error = AllocatorManager.Try(ref block); 95 block.Range.Allocator = tempAllocator; 96 97 // return if error occurs 98 if (error != 0) 99 return error; 100 101 // if deallocation succeeds, decrement the allocation count 102 if (block.Range.Pointer == IntPtr.Zero) 103 { 104 Interlocked.Decrement(ref m_allocationCount); 105 } 106 107 return 0; 108 } 109 } 110 111 #endregion // allocator-custom-try 112 113 #region allocator-custom-allocator-function 114 // Implement the allocator function of delegate AllocatorManager.TryFunction that is 115 // required when register the allocator on the global allocator table 116 [BurstCompile(CompileSynchronously = true)] 117 [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))] 118 public static unsafe int AllocatorFunction(IntPtr customAllocatorPtr, ref AllocatorManager.Block block) 119 { 120 return ((ExampleCustomAllocator*)customAllocatorPtr)->Try(ref block); 121 } 122 123 #endregion // allocator-custom-allocator-function 124 125 // Property to get the initial value 126 public byte InitialValue => m_initialValue; 127 128 // Property to get the allocation count 129 public int AllocationCount => m_allocationCount; 130 131 // Initialize the allocator 132 public void Initialize(byte initialValue) 133 { 134 m_initialValue = initialValue; 135 m_allocationCount = 0; 136 } 137} 138 139#endregion // allocator-custom-example 140 141#region allocator-custom-user-struct 142// Example user structure that contains the custom allocator 143internal struct ExampleCustomAllocatorStruct 144{ 145 // Use AllocatorHelper to help creating the example custom alloctor 146 AllocatorHelper<ExampleCustomAllocator> customAllocatorHelper; 147 148 // Custom allocator property for accessibility 149 public ref ExampleCustomAllocator customAllocator => ref customAllocatorHelper.Allocator; 150 151 // Create the example custom allocator 152 void CreateCustomAllocator(AllocatorManager.AllocatorHandle backgroundAllocator, byte initialValue) 153 { 154 // Allocate the custom allocator from backgroundAllocator and register the allocator 155 customAllocatorHelper = new AllocatorHelper<ExampleCustomAllocator>(backgroundAllocator); 156 157 // Set the initial value to initialize the memory 158 customAllocator.Initialize(initialValue); 159 } 160 161 #region allocator-custom-dispose 162 // Dispose the custom allocator 163 void DisposeCustomAllocator() 164 { 165 // Dispose the custom allocator 166 customAllocator.Dispose(); 167 168 // Unregister the custom allocator and dispose it 169 customAllocatorHelper.Dispose(); 170 } 171 #endregion // allocator-custom-dispose 172 173 // Constructor of user structure 174 public ExampleCustomAllocatorStruct(byte initialValue) 175 { 176 this = default; 177 CreateCustomAllocator(Allocator.Persistent, initialValue); 178 } 179 180 // Dispose the user structure 181 public void Dispose() 182 { 183 DisposeCustomAllocator(); 184 } 185 186 #region allocator-custom-use 187 // Sample code to use the custom allocator to allocate containers 188 public void UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList) 189 { 190 // Use custom allocator to allocate a native array and check initial value. 191 nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref customAllocator, NativeArrayOptions.UninitializedMemory); 192 Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); 193 nativeArray[0] = 0xFE; 194 195 // Use custom allocator to allocate a native list and check initial value. 196 nativeList = new NativeList<int>(customAllocator.Handle); 197 for (int i = 0; i < 50; i++) 198 { 199 nativeList.Add(i); 200 } 201 202 unsafe 203 { 204 // Use custom allocator to allocate a byte buffer. 205 byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); 206 Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); 207 208 // Free the byte buffer. 209 AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); 210 } 211 } 212 #endregion // allocator-custom-use 213 214 // Get allocation count from the custom allocator 215 public int AllocationCount => customAllocator.AllocationCount; 216 217 public void UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList) 218 { 219 // Use custom allocator to allocate a native array and check initial value. 220 nativeArray = CollectionHelper.CreateNativeArray<int>(100, customAllocator.ToAllocator, NativeArrayOptions.UninitializedMemory); 221 Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); 222 nativeArray[0] = 0xFE; 223 224 // Use custom allocator to allocate a native list and check initial value. 225 nativeList = new NativeList<int>(customAllocator.Handle); 226 for (int i = 0; i < 50; i++) 227 { 228 nativeList.Add(i); 229 } 230 231 unsafe 232 { 233 // Use custom allocator to allocate a byte buffer. 234 byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); 235 Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); 236 237 // Free the byte buffer. 238 AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); 239 } 240 } 241} 242 243internal class ExampleCustomAllocatorStructUsage 244{ 245 // Initial value for the custom allocator. 246 const int IntialValue = 0xAB; 247 248 // Test code. 249 [Test] 250 public void UseCustomAllocator_Works() 251 { 252 ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); 253 254 // Allocate native array and native list from the custom allocator 255 exampleStruct.UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList); 256 257 // Able to access the native array and native list 258 Assert.AreEqual(nativeArray[0], 0xFE); 259 Assert.AreEqual(nativeList[10], 10); 260 261 // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator 262 CollectionHelper.Dispose(nativeArray) ; 263 // Dispose the native list 264 nativeList.Dispose(); 265 266#if ENABLE_UNITY_COLLECTIONS_CHECKS 267 // Object disposed exception throws because nativeArray is already disposed 268 Assert.Throws<ObjectDisposedException>(() => 269 { 270 nativeArray[0] = 0xEF; 271 }); 272 273 // Object disposed exception throws because nativeList is already disposed 274 Assert.Throws<ObjectDisposedException>(() => 275 { 276 nativeList[10] = 0x10; 277 }); 278#endif 279 280 // Check allocation count after dispose the native array and native list 281 Assert.AreEqual(0, exampleStruct.AllocationCount); 282 283 // Dispose the user structure 284 exampleStruct.Dispose(); 285 } 286 287 [Test] 288 public void UseCustomAllocatorHandle_Works() 289 { 290 ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); 291 292 // Allocate native array and native list from the custom allocator handle 293 exampleStruct.UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList); 294 295 // Able to access the native array and native list 296 Assert.AreEqual(nativeArray[0], 0xFE); 297 Assert.AreEqual(nativeList[10], 10); 298 299 // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator 300 CollectionHelper.Dispose(nativeArray); 301 // Dispose the native list 302 nativeList.Dispose(); 303 304#if ENABLE_UNITY_COLLECTIONS_CHECKS 305 // Object disposed exception throws because nativeArray is already disposed 306 Assert.Throws<ObjectDisposedException>(() => 307 { 308 nativeArray[0] = 0xEF; 309 }); 310 311 // Object disposed exception throws because nativeList is already disposed 312 Assert.Throws<ObjectDisposedException>(() => 313 { 314 nativeList[10] = 0x10; 315 }); 316#endif 317 318 // Check allocation count after dispose the native array and native list 319 Assert.AreEqual(0, exampleStruct.AllocationCount); 320 321 // Dispose the user structure 322 exampleStruct.Dispose(); 323 } 324 325 [Test] 326 public void CustomAllocatorHandle_MultiThreadWorks() 327 { 328 ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); 329 330 var taskList = new List<Task>(); 331 332 // create 128 native array with another threads 333 for (var i = 0; i < 128; i++) 334 { 335 var task = Task.Run(() => 336 { 337 var nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref exampleStruct.customAllocator, 338 NativeArrayOptions.UninitializedMemory); 339 340 CollectionHelper.Dispose(nativeArray); 341 }); 342 343 taskList.Add(task); 344 } 345 346 Task.WaitAll(taskList.ToArray()); 347 348 exampleStruct.Dispose(); 349 } 350} 351#endregion // allocator-custom-user-struct 352 353