A game about forced loneliness, made by TACStudios
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