├── .npmignore ├── CHANGELOG.md ├── CHANGELOG.md.meta ├── JacksonDunstanNativeCollections.meta ├── JacksonDunstanNativeCollections ├── IJobParallelForRanged.cs ├── IJobParallelForRanged.cs.meta ├── JacksonDunstan.NativeCollections.asmdef ├── JacksonDunstan.NativeCollections.asmdef.meta ├── NativeArray2D.cs ├── NativeArray2D.cs.meta ├── NativeChunkedList.cs ├── NativeChunkedList.cs.meta ├── NativeHashSet.cs ├── NativeHashSet.cs.meta ├── NativeIntPtr.cs ├── NativeIntPtr.cs.meta ├── NativeLinkedList.cs ├── NativeLinkedList.cs.meta ├── NativeLongPtr.cs ├── NativeLongPtr.cs.meta ├── NativePerJobThreadIntPtr.cs ├── NativePerJobThreadIntPtr.cs.meta ├── NativePerJobThreadLongPtr.cs ├── NativePerJobThreadLongPtr.cs.meta ├── SharedDisposable.cs ├── SharedDisposable.cs.meta ├── Tests.meta └── Tests │ ├── JacksonDunstan.NativeCollections.Tests.asmdef │ ├── JacksonDunstan.NativeCollections.Tests.asmdef.meta │ ├── TestNativeArray2D.cs │ ├── TestNativeArray2D.cs.meta │ ├── TestNativeChunkedList.cs │ ├── TestNativeChunkedList.cs.meta │ ├── TestNativeHashSet.cs │ ├── TestNativeHashSet.cs.meta │ ├── TestNativeIntPtr.cs │ ├── TestNativeIntPtr.cs.meta │ ├── TestNativeLinkedList.cs │ ├── TestNativeLinkedList.cs.meta │ ├── TestNativeLongPtr.cs │ ├── TestNativeLongPtr.cs.meta │ ├── TestNativePerJobThreadIntPtr.cs │ ├── TestNativePerJobThreadIntPtr.cs.meta │ ├── TestNativePerJobThreadLongPtr.cs │ ├── TestNativePerJobThreadLongPtr.cs.meta │ ├── TestSharedDisposable.cs │ └── TestSharedDisposable.cs.meta ├── LICENSE.md ├── LICENSE.md.meta ├── PerformanceTests.meta ├── PerformanceTests ├── PerformanceTest.cs ├── PerformanceTest.cs.meta ├── PerformanceTestScene.unity └── PerformanceTestScene.unity.meta ├── README.md ├── README.md.meta ├── package.json └── package.json.meta /.npmignore: -------------------------------------------------------------------------------- 1 | PerformanceTests/ 2 | PerformanceTests.meta -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.0] - 2019-08-03 4 | 5 | - Release stable version 6 | -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 061e067cc9294e24192bcf702fbc95fa 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4dfa25768880c4a06aa6470e21395173 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/IJobParallelForRanged.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Unity.Jobs.LowLevel.Unsafe; 9 | using Unity.Jobs; 10 | using Unity.Collections.LowLevel.Unsafe; 11 | 12 | namespace JacksonDunstan.NativeCollections 13 | { 14 | /// 15 | /// A ParallelFor job type that executes on ranges of indices 16 | /// 17 | [JobProducerType(typeof(IJobParallelForRangedExtensions.ParallelForJobStruct<>))] 18 | public interface IJobParallelForRanged 19 | { 20 | /// 21 | /// Execute on the given range of indices, inclusive of the start and 22 | /// exclusive of the end 23 | /// 24 | /// 25 | /// 26 | /// First index to execute on 27 | /// 28 | /// 29 | /// 30 | /// One greater than the last index to execute on 31 | /// 32 | void Execute(int startIndex, int endIndex); 33 | } 34 | 35 | /// 36 | /// Supporting functionality for 37 | /// 38 | public static class IJobParallelForRangedExtensions 39 | { 40 | /// 41 | /// Supporting functionality for 42 | /// 43 | internal struct ParallelForJobStruct 44 | where TJob : struct, IJobParallelForRanged 45 | { 46 | /// 47 | /// Cached job type reflection data 48 | /// 49 | public static IntPtr jobReflectionData; 50 | 51 | /// 52 | /// Initialize the job type 53 | /// 54 | /// 55 | /// 56 | /// Reflection data for the job type 57 | /// 58 | public static IntPtr Initialize() 59 | { 60 | if (jobReflectionData == IntPtr.Zero) 61 | { 62 | jobReflectionData = JobsUtility.CreateJobReflectionData( 63 | typeof(TJob), 64 | #if UNITY_2020_2_OR_NEWER 65 | // Parameter removed in 2020.2 66 | #else 67 | JobType.ParallelFor, 68 | #endif 69 | (ExecuteJobFunction)Execute); 70 | } 71 | return jobReflectionData; 72 | } 73 | 74 | /// 75 | /// Delegate type for 76 | /// 77 | public delegate void ExecuteJobFunction( 78 | ref TJob data, 79 | IntPtr additionalPtr, 80 | IntPtr bufferRangePatchData, 81 | ref JobRanges ranges, 82 | int jobIndex); 83 | 84 | /// 85 | /// Execute the job until there are no more work stealing ranges 86 | /// available to execute 87 | /// 88 | /// 89 | /// 90 | /// The job to execute 91 | /// 92 | /// 93 | /// 94 | /// TBD. Unused. 95 | /// 96 | /// 97 | /// 98 | /// TBD. Unused. 99 | /// 100 | /// 101 | /// 102 | /// Work stealing ranges to execute from 103 | /// 104 | /// 105 | /// 106 | /// Index of this job 107 | /// 108 | public static unsafe void Execute( 109 | ref TJob jobData, 110 | IntPtr additionalPtr, 111 | IntPtr bufferRangePatchData, 112 | ref JobRanges ranges, 113 | int jobIndex) 114 | { 115 | int startIndex; 116 | int endIndex; 117 | while (JobsUtility.GetWorkStealingRange( 118 | ref ranges, 119 | jobIndex, 120 | out startIndex, 121 | out endIndex)) 122 | { 123 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 124 | JobsUtility.PatchBufferMinMaxRanges( 125 | bufferRangePatchData, 126 | UnsafeUtility.AddressOf(ref jobData), 127 | startIndex, 128 | endIndex - startIndex); 129 | #endif 130 | jobData.Execute(startIndex, endIndex); 131 | } 132 | } 133 | } 134 | 135 | /// 136 | /// Run a job asynchronously 137 | /// 138 | /// 139 | /// 140 | /// Job to run 141 | /// 142 | /// 143 | /// 144 | /// Length of the values to execute on. 145 | /// 146 | /// 147 | /// 148 | /// Number of job executions per batch 149 | /// 150 | /// 151 | /// 152 | /// Handle of the job that must be run before this job 153 | /// 154 | /// 155 | /// 156 | /// A handle to the created job 157 | /// 158 | /// 159 | /// 160 | /// Type of job to run 161 | /// 162 | unsafe public static JobHandle ScheduleRanged( 163 | this T jobData, 164 | int valuesLength, 165 | int innerloopBatchCount, 166 | JobHandle dependsOn = new JobHandle()) 167 | where T : struct, IJobParallelForRanged 168 | { 169 | var scheduleParams = new JobsUtility.JobScheduleParameters( 170 | UnsafeUtility.AddressOf(ref jobData), 171 | ParallelForJobStruct.Initialize(), 172 | dependsOn, 173 | #if UNITY_2020_2_OR_NEWER 174 | // Parameter renamed in 2020.2 175 | ScheduleMode.Parallel 176 | #else 177 | ScheduleMode.Batched 178 | #endif 179 | ); 180 | return JobsUtility.ScheduleParallelFor( 181 | ref scheduleParams, 182 | valuesLength, 183 | innerloopBatchCount); 184 | } 185 | 186 | /// 187 | /// Run a job synchronously 188 | /// 189 | /// 190 | /// 191 | /// Job to run 192 | /// 193 | /// 194 | /// 195 | /// Length of the values to execute on. 196 | /// 197 | /// 198 | /// 199 | /// Type of job to run 200 | /// 201 | public static unsafe void RunRanged( 202 | this T jobData, 203 | int valuesLength) 204 | where T : struct, IJobParallelForRanged 205 | { 206 | var scheduleParams = new JobsUtility.JobScheduleParameters( 207 | UnsafeUtility.AddressOf(ref jobData), 208 | ParallelForJobStruct.Initialize(), 209 | new JobHandle(), 210 | ScheduleMode.Run); 211 | JobsUtility.ScheduleParallelFor( 212 | ref scheduleParams, 213 | valuesLength, 214 | valuesLength); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/IJobParallelForRanged.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e3904ef7c32334b2097c2a177c96c116 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/JacksonDunstan.NativeCollections.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JacksonDunstan.NativeCollections", 3 | "references": [], 4 | "optionalUnityReferences": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": true 8 | } -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/JacksonDunstan.NativeCollections.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0098724e988b8b14d8a97f54d663a398 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativeArray2D.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Collections; 9 | using System.Collections.Generic; 10 | using System.Diagnostics; 11 | using Unity.Collections; 12 | using Unity.Collections.LowLevel.Unsafe; 13 | using UnityEngine.Internal; 14 | 15 | namespace JacksonDunstan.NativeCollections 16 | { 17 | /// 18 | /// A two-dimensional array stored in native memory 19 | /// 20 | /// 21 | /// 22 | /// Type of elements in the array 23 | /// 24 | [DebuggerDisplay("Length0 = {Length0}, Length1 = {Length1}")] 25 | [DebuggerTypeProxy(typeof(NativeArray2DDebugView<>))] 26 | [NativeContainer] 27 | [NativeContainerSupportsDeallocateOnJobCompletion] 28 | public unsafe struct NativeArray2D 29 | : IDisposable 30 | , IEnumerable 31 | , IEquatable> 32 | #if CSHARP_7_3_OR_NEWER 33 | where T : unmanaged 34 | #else 35 | where T : struct 36 | #endif 37 | { 38 | /// 39 | /// An enumerator for this type of array. It enumerates from (0,0) to 40 | /// (Length0-1,Length1-1) in rows of the first dimension then the second 41 | /// dimension. For example, an array with Length0=2 and Length1=3 is 42 | /// enumerated as follows: 43 | /// (0, 0) 44 | /// (1, 0) 45 | /// (0, 1) 46 | /// (1, 1) 47 | /// (0, 2) 48 | /// (1, 3) 49 | /// 50 | [ExcludeFromDocs] 51 | public struct Enumerator : IEnumerator 52 | { 53 | /// 54 | /// Array to enumerate 55 | /// 56 | private NativeArray2D m_Array; 57 | 58 | /// 59 | /// Current index in the first dimension 60 | /// 61 | private int m_Index0; 62 | 63 | /// 64 | /// Current index in the second dimension 65 | /// 66 | private int m_Index1; 67 | 68 | /// 69 | /// Create the enumerator. It's initially just before the first 70 | /// element of both dimensions. 71 | /// 72 | /// 73 | /// 74 | /// Array to enumerate 75 | /// 76 | public Enumerator(ref NativeArray2D array) 77 | { 78 | m_Array = array; 79 | m_Index0 = -1; 80 | m_Index1 = 0; 81 | } 82 | 83 | /// 84 | /// Dispose of the enumerator. This is a no-op. 85 | /// 86 | public void Dispose() 87 | { 88 | } 89 | 90 | /// 91 | /// Move to the next element of the array. This moves along the 92 | /// first dimension until the end is hit, at which time the first 93 | /// dimension index is reset to zero and the second dimension index 94 | /// is incremented. 95 | /// 96 | /// 97 | /// 98 | /// If the new indices are within the bounds of the array. 99 | /// 100 | public bool MoveNext() 101 | { 102 | m_Index0++; 103 | if (m_Index0 >= m_Array.Length0) 104 | { 105 | m_Index0 = 0; 106 | m_Index1++; 107 | return m_Index1 < m_Array.Length1; 108 | } 109 | return true; 110 | } 111 | 112 | /// 113 | /// Reset to just before the first element in both dimensions 114 | /// 115 | public void Reset() 116 | { 117 | m_Index0 = -1; 118 | m_Index1 = 0; 119 | } 120 | 121 | /// 122 | /// Get the currently-enumerated element 123 | /// 124 | public T Current 125 | { 126 | get 127 | { 128 | return m_Array[m_Index0, m_Index1]; 129 | } 130 | } 131 | 132 | /// 133 | /// Get the currently-enumerated element 134 | /// 135 | object IEnumerator.Current 136 | { 137 | get 138 | { 139 | return Current; 140 | } 141 | } 142 | } 143 | 144 | /// 145 | /// Pointer to the memory the array is stored in. 146 | /// 147 | [NativeDisableUnsafePtrRestriction] 148 | private void* m_Buffer; 149 | 150 | /// 151 | /// Length of the array's first dimension 152 | /// 153 | private int m_Length0; 154 | 155 | /// 156 | /// Length of the array's second dimension 157 | /// 158 | private int m_Length1; 159 | 160 | // These fields are all required when safety checks are enabled 161 | // They must have these exact types, names, and order 162 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 163 | /// 164 | /// A handle to information about what operations can be safely 165 | /// performed on the list at any given time. 166 | /// 167 | private AtomicSafetyHandle m_Safety; 168 | 169 | /// 170 | /// A handle that can be used to tell if the list has been disposed yet 171 | /// or not, which allows for error-checking double disposal. 172 | /// 173 | [NativeSetClassTypeToNullOnSchedule] 174 | private DisposeSentinel m_DisposeSentinel; 175 | #endif 176 | 177 | /// 178 | /// Allocator used to create . 179 | /// 180 | internal Allocator m_Allocator; 181 | 182 | /// 183 | /// Create the array and optionally clear it 184 | /// 185 | /// 186 | /// 187 | /// Length of the array's first dimension. Must be positive. 188 | /// 189 | /// 190 | /// 191 | /// Length of the array's second dimension. Must be positive. 192 | /// 193 | /// 194 | /// 195 | /// Allocator to allocate native memory with. Must be valid as defined 196 | /// by . 197 | /// 198 | /// 199 | /// 200 | /// Whether the array should be cleared or not 201 | /// 202 | public NativeArray2D( 203 | int length0, 204 | int length1, 205 | Allocator allocator, 206 | NativeArrayOptions options = NativeArrayOptions.ClearMemory) 207 | { 208 | Allocate(length0, length1, allocator, out this); 209 | if ((options & NativeArrayOptions.ClearMemory) 210 | == NativeArrayOptions.ClearMemory) 211 | { 212 | UnsafeUtility.MemClear( 213 | m_Buffer, 214 | Length * (long)UnsafeUtility.SizeOf()); 215 | } 216 | } 217 | 218 | /// 219 | /// Create a copy of the given managed array 220 | /// 221 | /// 222 | /// 223 | /// Managed array to copy. Must not be null. 224 | /// 225 | /// 226 | /// 227 | /// Allocator to allocate native memory with. Must be valid as defined 228 | /// by . 229 | /// 230 | public NativeArray2D(T[,] array, Allocator allocator) 231 | { 232 | int length0 = array.GetLength(0); 233 | int length1 = array.GetLength(1); 234 | Allocate(length0, length1, allocator, out this); 235 | Copy(array, this); 236 | } 237 | 238 | /// 239 | /// Create a copy of the given native array 240 | /// 241 | /// 242 | /// 243 | /// Native array to copy 244 | /// 245 | /// 246 | /// 247 | /// Allocator to allocate native memory with. Must be valid as defined 248 | /// by . 249 | /// 250 | public NativeArray2D(NativeArray2D array, Allocator allocator) 251 | { 252 | Allocate(array.Length0, array.Length1, allocator, out this); 253 | Copy(array, this); 254 | } 255 | 256 | /// 257 | /// Get the total number of elements in the array 258 | /// 259 | public int Length 260 | { 261 | get 262 | { 263 | return m_Length0 * m_Length1; 264 | } 265 | } 266 | 267 | /// 268 | /// Get the length of the array's first dimension 269 | /// 270 | public int Length0 271 | { 272 | get 273 | { 274 | return m_Length0; 275 | } 276 | } 277 | 278 | /// 279 | /// Get the length of the array's second dimension 280 | /// 281 | public int Length1 282 | { 283 | get 284 | { 285 | return m_Length1; 286 | } 287 | } 288 | 289 | /// 290 | /// Throw an exception if the array isn't readable 291 | /// 292 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 293 | private void RequireReadAccess() 294 | { 295 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 296 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 297 | #endif 298 | } 299 | 300 | /// 301 | /// Throw an exception if the list isn't writable 302 | /// 303 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 304 | private void RequireWriteAccess() 305 | { 306 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 307 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 308 | #endif 309 | } 310 | 311 | /// 312 | /// Throw an exception if an index is out of bounds 313 | /// 314 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 315 | private void RequireIndexInBounds(int index0, int index1) 316 | { 317 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 318 | if (index0 < 0 || index0 >= m_Length0) 319 | { 320 | throw new IndexOutOfRangeException(); 321 | } 322 | if (index1 < 0 || index1 >= m_Length1) 323 | { 324 | throw new IndexOutOfRangeException(); 325 | } 326 | #endif 327 | } 328 | 329 | /// 330 | /// Throw an exception when the given allocator is invalid as defined 331 | /// by . 332 | /// 333 | /// 334 | /// 335 | /// Allocator to check. 336 | /// 337 | /// 338 | /// 339 | /// If the given allocator is invalid. 340 | /// 341 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 342 | private static void RequireValidAllocator(Allocator allocator) 343 | { 344 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 345 | if (!UnsafeUtility.IsValidAllocator(allocator)) 346 | { 347 | throw new InvalidOperationException( 348 | "The NativeArray2D can not be Disposed because it was " + 349 | "not allocated with a valid allocator."); 350 | } 351 | #endif 352 | } 353 | 354 | /// 355 | /// Index into the array to read or write an element 356 | /// 357 | /// 358 | /// 359 | /// Index of the first dimension 360 | /// 361 | /// 362 | /// 363 | /// Index of the second dimension 364 | /// 365 | public T this[int index0, int index1] 366 | { 367 | get 368 | { 369 | RequireReadAccess(); 370 | RequireIndexInBounds(index0, index1); 371 | 372 | int index = index1 * m_Length0 + index0; 373 | return UnsafeUtility.ReadArrayElement(m_Buffer, index); 374 | } 375 | 376 | [WriteAccessRequired] 377 | set 378 | { 379 | RequireWriteAccess(); 380 | RequireIndexInBounds(index0, index1); 381 | 382 | int index = index1 * m_Length0 + index0; 383 | UnsafeUtility.WriteArrayElement(m_Buffer, index, value); 384 | } 385 | } 386 | 387 | /// 388 | /// Check if the underlying unmanaged memory has been created and not 389 | /// freed via a call to . 390 | /// 391 | /// This operation has no access requirements. 392 | /// 393 | /// This operation is O(1). 394 | /// 395 | /// 396 | /// 397 | /// Initially true when a non-default constructor is called but 398 | /// initially false when the default constructor is used. After 399 | /// is called, this becomes false. Note that 400 | /// calling on one copy of this object doesn't 401 | /// result in this becoming false for all copies if it was true before. 402 | /// This property should not be used to check whether the object 403 | /// is usable, only to check whether it was ever usable. 404 | /// 405 | public bool IsCreated 406 | { 407 | get 408 | { 409 | return (IntPtr)m_Buffer != IntPtr.Zero; 410 | } 411 | } 412 | 413 | /// 414 | /// Release the object's unmanaged memory. Do not use it after this. Do 415 | /// not call on copies of the object either. 416 | /// 417 | /// This operation requires write access. 418 | /// 419 | /// This complexity of this operation is O(1) plus the allocator's 420 | /// deallocation complexity. 421 | /// 422 | [WriteAccessRequired] 423 | public void Dispose() 424 | { 425 | RequireWriteAccess(); 426 | RequireValidAllocator(m_Allocator); 427 | 428 | // Make sure we're not double-disposing 429 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 430 | #if UNITY_2018_3_OR_NEWER 431 | DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); 432 | #else 433 | DisposeSentinel.Dispose(m_Safety, ref m_DisposeSentinel); 434 | #endif 435 | #endif 436 | 437 | UnsafeUtility.Free(m_Buffer, m_Allocator); 438 | m_Buffer = null; 439 | m_Length0 = 0; 440 | m_Length1 = 0; 441 | } 442 | 443 | /// 444 | /// Copy the elements of a managed array to this array 445 | /// 446 | /// 447 | /// 448 | /// Array to copy from. Must not be null. Must have the same dimensions 449 | /// as this array. 450 | /// 451 | [WriteAccessRequired] 452 | public void CopyFrom(T[,] array) 453 | { 454 | Copy(array, this); 455 | } 456 | 457 | /// 458 | /// Copy the elements of a native array to this array 459 | /// 460 | /// 461 | /// 462 | /// Array to copy from. Must have the same dimensions as this array. 463 | /// 464 | [WriteAccessRequired] 465 | public void CopyFrom(NativeArray2D array) 466 | { 467 | Copy(array, this); 468 | } 469 | 470 | /// 471 | /// Copy the elements of this array to a managed array 472 | /// 473 | /// 474 | /// 475 | /// Array to copy to. Must not be null. Must have the same dimensions 476 | /// as this array. 477 | /// 478 | public void CopyTo(T[,] array) 479 | { 480 | Copy(this, array); 481 | } 482 | 483 | /// 484 | /// Copy the elements of this array to a native array 485 | /// 486 | /// 487 | /// 488 | /// Array to copy to. Must have the same dimensions 489 | /// as this array. 490 | /// 491 | public void CopyTo(NativeArray2D array) 492 | { 493 | Copy(this, array); 494 | } 495 | 496 | /// 497 | /// Copy the elements of this array to a newly-created managed array 498 | /// 499 | /// 500 | /// 501 | /// A newly-created managed array with the elements of this array. 502 | /// 503 | public T[,] ToArray() 504 | { 505 | T[,] dst = new T[m_Length0, m_Length1]; 506 | Copy(this, dst); 507 | return dst; 508 | } 509 | 510 | /// 511 | /// Get an enumerator for this array 512 | /// 513 | /// 514 | /// 515 | /// An enumerator for this array. 516 | /// 517 | public Enumerator GetEnumerator() 518 | { 519 | return new Enumerator(ref this); 520 | } 521 | 522 | /// 523 | /// Get an enumerator for this array 524 | /// 525 | /// 526 | /// 527 | /// An enumerator for this array. 528 | /// 529 | IEnumerator IEnumerable.GetEnumerator() 530 | { 531 | return new Enumerator(ref this); 532 | } 533 | 534 | /// 535 | /// Get an enumerator for this array 536 | /// 537 | /// 538 | /// 539 | /// An enumerator for this array. 540 | /// 541 | IEnumerator IEnumerable.GetEnumerator() 542 | { 543 | return GetEnumerator(); 544 | } 545 | 546 | /// 547 | /// Check if this array points to the same native memory as another 548 | /// array. 549 | /// 550 | /// 551 | /// 552 | /// Array to check against. 553 | /// 554 | /// 555 | /// 556 | /// If this array points to the same native memory as the given array. 557 | /// 558 | public bool Equals(NativeArray2D other) 559 | { 560 | return m_Buffer == other.m_Buffer 561 | && m_Length0 == other.m_Length0 562 | && m_Length1 == other.m_Length1; 563 | } 564 | 565 | /// 566 | /// Check if this array points to the same native memory as another 567 | /// array. 568 | /// 569 | /// 570 | /// 571 | /// Array to check against. 572 | /// 573 | /// 574 | /// 575 | /// If this array points to the same native memory as the given array. 576 | /// 577 | public override bool Equals(object other) 578 | { 579 | if (ReferenceEquals(null, other)) 580 | { 581 | return false; 582 | } 583 | return other is NativeArray2D && Equals((NativeArray2D)other); 584 | } 585 | 586 | /// 587 | /// Get a hash code for this array 588 | /// 589 | /// 590 | /// 591 | /// A hash code for this array 592 | /// 593 | public override int GetHashCode() 594 | { 595 | int result = (int)m_Buffer; 596 | result = (result * 397) ^ m_Length0; 597 | result = (result * 397) ^ m_Length1; 598 | return result; 599 | } 600 | 601 | /// 602 | /// Check if two arrays point to the same native memory. 603 | /// 604 | /// 605 | /// 606 | /// First array to check 607 | /// 608 | /// 609 | /// 610 | /// Second array to check 611 | /// 612 | /// 613 | /// 614 | /// If the given arrays point to the same native memory. 615 | /// 616 | public static bool operator ==(NativeArray2D a, NativeArray2D b) 617 | { 618 | return a.Equals(b); 619 | } 620 | 621 | /// 622 | /// Check if two arrays don't point to the same native memory. 623 | /// 624 | /// 625 | /// 626 | /// First array to check 627 | /// 628 | /// 629 | /// 630 | /// Second array to check 631 | /// 632 | /// 633 | /// 634 | /// If the given arrays don't point to the same native memory. 635 | /// 636 | public static bool operator !=(NativeArray2D a, NativeArray2D b) 637 | { 638 | return !a.Equals(b); 639 | } 640 | 641 | /// 642 | /// Allocate memory for the array 643 | /// 644 | /// 645 | /// 646 | /// Length of the array's first dimension. Must be positive. 647 | /// 648 | /// 649 | /// 650 | /// Length of the array's second dimension. Must be positive. 651 | /// 652 | /// 653 | /// 654 | /// Allocator to allocate native memory with. Must be valid as defined 655 | /// by . 656 | /// 657 | /// 658 | /// 659 | /// Array to write to once allocated 660 | /// 661 | private static void Allocate( 662 | int length0, 663 | int length1, 664 | Allocator allocator, 665 | out NativeArray2D array) 666 | { 667 | RequireValidAllocator(allocator); 668 | 669 | #if !CSHARP_7_3_OR_NEWER 670 | if (!UnsafeUtility.IsUnmanaged()) 671 | { 672 | throw new InvalidOperationException( 673 | "Only unmanaged types are supported"); 674 | } 675 | #endif 676 | 677 | int length = length0 * length1; 678 | if (length <= 0) 679 | { 680 | throw new InvalidOperationException( 681 | "Total number of elements must be greater than zero"); 682 | } 683 | 684 | array = new NativeArray2D 685 | { 686 | m_Buffer = UnsafeUtility.Malloc( 687 | length * UnsafeUtility.SizeOf(), 688 | UnsafeUtility.AlignOf(), 689 | allocator), 690 | m_Length0 = length0, 691 | m_Length1 = length1, 692 | m_Allocator = allocator 693 | }; 694 | 695 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 696 | DisposeSentinel.Create( 697 | out array.m_Safety, 698 | out array.m_DisposeSentinel, 699 | 1, 700 | allocator); 701 | #endif 702 | } 703 | 704 | /// 705 | /// Copy a native array's elements to another native array 706 | /// 707 | /// 708 | /// 709 | /// Array to copy from 710 | /// 711 | /// 712 | /// 713 | /// Array to copy to 714 | /// 715 | /// 716 | /// 717 | /// If the arrays have different sizes 718 | /// 719 | private static void Copy(NativeArray2D src, NativeArray2D dest) 720 | { 721 | src.RequireReadAccess(); 722 | dest.RequireWriteAccess(); 723 | 724 | if (src.Length0 != dest.Length0 725 | || src.Length1 != dest.Length1) 726 | { 727 | throw new ArgumentException("Arrays must have the same size"); 728 | } 729 | 730 | for (int index0 = 0; index0 < src.Length0; ++index0) 731 | { 732 | for (int index1 = 0; index1 < src.Length1; ++index1) 733 | { 734 | dest[index0, index1] = src[index0, index1]; 735 | } 736 | } 737 | } 738 | 739 | /// 740 | /// Copy a managed array's elements to a native array 741 | /// 742 | /// 743 | /// 744 | /// Array to copy from 745 | /// 746 | /// 747 | /// 748 | /// Array to copy to 749 | /// 750 | /// 751 | /// 752 | /// If the arrays have different sizes 753 | /// 754 | private static void Copy(T[,] src, NativeArray2D dest) 755 | { 756 | dest.RequireWriteAccess(); 757 | 758 | if (src.GetLength(0) != dest.Length0 759 | || src.GetLength(1) != dest.Length1) 760 | { 761 | throw new ArgumentException("Arrays must have the same size"); 762 | } 763 | 764 | for (int index0 = 0; index0 < dest.Length0; ++index0) 765 | { 766 | for (int index1 = 0; index1 < dest.Length1; ++index1) 767 | { 768 | dest[index0, index1] = src[index0, index1]; 769 | } 770 | } 771 | } 772 | 773 | /// 774 | /// Copy a native array's elements to a managed array 775 | /// 776 | /// 777 | /// 778 | /// Array to copy from 779 | /// 780 | /// 781 | /// 782 | /// Array to copy to 783 | /// 784 | /// 785 | /// 786 | /// If the arrays have different sizes 787 | /// 788 | private static void Copy(NativeArray2D src, T[,] dest) 789 | { 790 | src.RequireReadAccess(); 791 | 792 | if (src.Length0 != dest.GetLength(0) 793 | || src.Length1 != dest.GetLength(1)) 794 | { 795 | throw new ArgumentException("Arrays must have the same size"); 796 | } 797 | 798 | for (int index0 = 0; index0 < src.Length0; ++index0) 799 | { 800 | for (int index1 = 0; index1 < src.Length1; ++index1) 801 | { 802 | dest[index0, index1] = src[index0, index1]; 803 | } 804 | } 805 | } 806 | } 807 | 808 | /// 809 | /// A debugger view of the array type 810 | /// 811 | /// 812 | /// 813 | /// Type of elements in the array 814 | /// 815 | internal sealed class NativeArray2DDebugView 816 | #if CSHARP_7_3_OR_NEWER 817 | where T : unmanaged 818 | #else 819 | where T : struct 820 | #endif 821 | { 822 | /// 823 | /// The array to view 824 | /// 825 | private readonly NativeArray2D m_Array; 826 | 827 | /// 828 | /// Create the view 829 | /// 830 | /// 831 | /// 832 | /// The array to view 833 | /// 834 | public NativeArray2DDebugView(NativeArray2D array) 835 | { 836 | m_Array = array; 837 | } 838 | 839 | /// 840 | /// Get the elements of the array as a managed array 841 | /// 842 | public T[,] Items 843 | { 844 | get 845 | { 846 | return m_Array.ToArray(); 847 | } 848 | } 849 | } 850 | } 851 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativeArray2D.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 280a7cf1ff5454939ad38d4cc7c60790 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativeChunkedList.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7e997a88d82984aaf9a7113d579036c4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativeHashSet.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 361d37df9c7004784a3ef70adb39afcb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativeIntPtr.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Diagnostics; 9 | using System.Runtime.InteropServices; 10 | using System.Threading; 11 | using Unity.Collections; 12 | using Unity.Collections.LowLevel.Unsafe; 13 | 14 | namespace JacksonDunstan.NativeCollections 15 | { 16 | /// 17 | /// A pointer to an int stored in native (i.e. unmanaged) memory 18 | /// 19 | [NativeContainer] 20 | [NativeContainerSupportsDeallocateOnJobCompletion] 21 | [DebuggerTypeProxy(typeof(NativeIntPtrDebugView))] 22 | [DebuggerDisplay("Value = {Value}")] 23 | [StructLayout(LayoutKind.Sequential)] 24 | public unsafe struct NativeIntPtr : IDisposable 25 | { 26 | /// 27 | /// An atomic write-only version of the object suitable for use in a 28 | /// ParallelFor job 29 | /// 30 | [NativeContainer] 31 | [NativeContainerIsAtomicWriteOnly] 32 | public struct Parallel 33 | { 34 | /// 35 | /// Pointer to the value in native memory 36 | /// 37 | [NativeDisableUnsafePtrRestriction] 38 | internal readonly int* m_Buffer; 39 | 40 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 41 | /// 42 | /// A handle to information about what operations can be safely 43 | /// performed on the object at any given time. 44 | /// 45 | internal AtomicSafetyHandle m_Safety; 46 | 47 | /// 48 | /// Create a parallel version of the object 49 | /// 50 | /// 51 | /// 52 | /// Pointer to the value 53 | /// 54 | /// 55 | /// 56 | /// Atomic safety handle for the object 57 | /// 58 | internal Parallel(int* value, AtomicSafetyHandle safety) 59 | { 60 | m_Buffer = value; 61 | m_Safety = safety; 62 | } 63 | #else 64 | /// 65 | /// Create a parallel version of the object 66 | /// 67 | /// 68 | /// 69 | /// Pointer to the value 70 | /// 71 | internal Parallel(int* value) 72 | { 73 | m_Buffer = value; 74 | } 75 | #endif 76 | 77 | /// 78 | /// Increment the stored value 79 | /// 80 | /// 81 | /// 82 | /// This object 83 | /// 84 | [WriteAccessRequired] 85 | public void Increment() 86 | { 87 | RequireWriteAccess(); 88 | Interlocked.Increment(ref *m_Buffer); 89 | } 90 | 91 | /// 92 | /// Decrement the stored value 93 | /// 94 | /// 95 | /// 96 | /// This object 97 | /// 98 | [WriteAccessRequired] 99 | public void Decrement() 100 | { 101 | RequireWriteAccess(); 102 | Interlocked.Decrement(ref *m_Buffer); 103 | } 104 | 105 | /// 106 | /// Add to the stored value 107 | /// 108 | /// 109 | /// 110 | /// Value to add. Use negative values for subtraction. 111 | /// 112 | /// 113 | /// 114 | /// This object 115 | /// 116 | [WriteAccessRequired] 117 | public void Add(int value) 118 | { 119 | RequireWriteAccess(); 120 | Interlocked.Add(ref *m_Buffer, value); 121 | } 122 | 123 | /// 124 | /// Throw an exception if the object isn't writable 125 | /// 126 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 127 | private void RequireWriteAccess() 128 | { 129 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 130 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 131 | #endif 132 | } 133 | } 134 | 135 | /// 136 | /// Pointer to the value in native memory. Must be named exactly this 137 | /// way to allow for [NativeContainerSupportsDeallocateOnJobCompletion] 138 | /// 139 | [NativeDisableUnsafePtrRestriction] 140 | internal int* m_Buffer; 141 | 142 | /// 143 | /// Allocator used to create the backing memory 144 | /// 145 | /// This field must be named this way to comply with 146 | /// [NativeContainerSupportsDeallocateOnJobCompletion] 147 | /// 148 | internal readonly Allocator m_AllocatorLabel; 149 | 150 | // These fields are all required when safety checks are enabled 151 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 152 | /// 153 | /// A handle to information about what operations can be safely 154 | /// performed on the object at any given time. 155 | /// 156 | private AtomicSafetyHandle m_Safety; 157 | 158 | /// 159 | /// A handle that can be used to tell if the object has been disposed 160 | /// yet or not, which allows for error-checking double disposal. 161 | /// 162 | [NativeSetClassTypeToNullOnSchedule] 163 | private DisposeSentinel m_DisposeSentinel; 164 | #endif 165 | 166 | /// 167 | /// Allocate memory and set the initial value 168 | /// 169 | /// 170 | /// 171 | /// Allocator to allocate and deallocate with. Must be valid. 172 | /// 173 | /// 174 | /// 175 | /// Initial value of the allocated memory 176 | /// 177 | public NativeIntPtr(Allocator allocator, int initialValue = 0) 178 | { 179 | // Require a valid allocator 180 | if (allocator <= Allocator.None) 181 | { 182 | throw new ArgumentException( 183 | "Allocator must be Temp, TempJob or Persistent", 184 | "allocator"); 185 | } 186 | 187 | // Allocate the memory for the value 188 | m_Buffer = (int*)UnsafeUtility.Malloc( 189 | sizeof(int), 190 | UnsafeUtility.AlignOf(), 191 | allocator); 192 | 193 | // Store the allocator to use when deallocating 194 | m_AllocatorLabel = allocator; 195 | 196 | // Create the dispose sentinel 197 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 198 | #if UNITY_2018_3_OR_NEWER 199 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0, allocator); 200 | #else 201 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0); 202 | #endif 203 | #endif 204 | 205 | // Set the initial value 206 | *m_Buffer = initialValue; 207 | } 208 | 209 | /// 210 | /// Get or set the contained value 211 | /// 212 | /// This operation requires read access to the node for 'get' and write 213 | /// access to the node for 'set'. 214 | /// 215 | /// 216 | /// 217 | /// The contained value 218 | /// 219 | public int Value 220 | { 221 | get 222 | { 223 | RequireReadAccess(); 224 | return *m_Buffer; 225 | } 226 | 227 | [WriteAccessRequired] 228 | set 229 | { 230 | RequireWriteAccess(); 231 | *m_Buffer = value; 232 | } 233 | } 234 | 235 | /// 236 | /// Get a version of this object suitable for use in a ParallelFor job 237 | /// 238 | /// 239 | /// 240 | /// A version of this object suitable for use in a ParallelFor job 241 | /// 242 | public Parallel GetParallel() 243 | { 244 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 245 | Parallel parallel = new Parallel(m_Buffer, m_Safety); 246 | AtomicSafetyHandle.UseSecondaryVersion(ref parallel.m_Safety); 247 | #else 248 | Parallel parallel = new Parallel(m_Buffer); 249 | #endif 250 | return parallel; 251 | } 252 | 253 | /// 254 | /// Check if the underlying unmanaged memory has been created and not 255 | /// freed via a call to . 256 | /// 257 | /// This operation has no access requirements. 258 | /// 259 | /// This operation is O(1). 260 | /// 261 | /// 262 | /// 263 | /// Initially true when a non-default constructor is called but 264 | /// initially false when the default constructor is used. After 265 | /// is called, this becomes false. Note that 266 | /// calling on one copy of this object doesn't 267 | /// result in this becoming false for all copies if it was true before. 268 | /// This property should not be used to check whether the object 269 | /// is usable, only to check whether it was ever usable. 270 | /// 271 | public bool IsCreated 272 | { 273 | get 274 | { 275 | return m_Buffer != null; 276 | } 277 | } 278 | 279 | /// 280 | /// Release the object's unmanaged memory. Do not use it after this. Do 281 | /// not call on copies of the object either. 282 | /// 283 | /// This operation requires write access. 284 | /// 285 | /// This complexity of this operation is O(1) plus the allocator's 286 | /// deallocation complexity. 287 | /// 288 | [WriteAccessRequired] 289 | public void Dispose() 290 | { 291 | RequireWriteAccess(); 292 | 293 | // Make sure we're not double-disposing 294 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 295 | #if UNITY_2018_3_OR_NEWER 296 | DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); 297 | #else 298 | DisposeSentinel.Dispose(m_Safety, ref m_DisposeSentinel); 299 | #endif 300 | #endif 301 | 302 | UnsafeUtility.Free(m_Buffer, m_AllocatorLabel); 303 | m_Buffer = null; 304 | } 305 | 306 | /// 307 | /// Set whether both read and write access should be allowed. This is 308 | /// used for automated testing purposes only. 309 | /// 310 | /// 311 | /// 312 | /// If both read and write access should be allowed 313 | /// 314 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 315 | public void TestUseOnlySetAllowReadAndWriteAccess( 316 | bool allowReadOrWriteAccess) 317 | { 318 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 319 | AtomicSafetyHandle.SetAllowReadOrWriteAccess( 320 | m_Safety, 321 | allowReadOrWriteAccess); 322 | #endif 323 | } 324 | 325 | /// 326 | /// Throw an exception if the object isn't readable 327 | /// 328 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 329 | private void RequireReadAccess() 330 | { 331 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 332 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 333 | #endif 334 | } 335 | 336 | /// 337 | /// Throw an exception if the object isn't writable 338 | /// 339 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 340 | private void RequireWriteAccess() 341 | { 342 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 343 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 344 | #endif 345 | } 346 | } 347 | 348 | /// 349 | /// Provides a debugger view of . 350 | /// 351 | internal sealed class NativeIntPtrDebugView 352 | { 353 | /// 354 | /// The object to provide a debugger view for 355 | /// 356 | private NativeIntPtr m_Ptr; 357 | 358 | /// 359 | /// Create the debugger view 360 | /// 361 | /// 362 | /// 363 | /// The object to provide a debugger view for 364 | /// 365 | public NativeIntPtrDebugView(NativeIntPtr ptr) 366 | { 367 | m_Ptr = ptr; 368 | } 369 | 370 | /// 371 | /// Get the viewed object's value 372 | /// 373 | /// 374 | /// 375 | /// The viewed object's value 376 | /// 377 | public int Value 378 | { 379 | get 380 | { 381 | return m_Ptr.Value; 382 | } 383 | } 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativeIntPtr.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 840e81eda349647618a0cecc17f5b429 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativeLinkedList.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ac88eb2e190c4dfa9d3f36ce8a872f57 3 | timeCreated: 1533422240 -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativeLongPtr.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Diagnostics; 9 | using System.Runtime.InteropServices; 10 | using System.Threading; 11 | using Unity.Collections; 12 | using Unity.Collections.LowLevel.Unsafe; 13 | 14 | namespace JacksonDunstan.NativeCollections 15 | { 16 | /// 17 | /// A pointer to a long stored in native (i.e. unmanaged) memory 18 | /// 19 | [NativeContainer] 20 | [NativeContainerSupportsDeallocateOnJobCompletion] 21 | [DebuggerTypeProxy(typeof(NativeLongPtrDebugView))] 22 | [DebuggerDisplay("Value = {Value}")] 23 | [StructLayout(LayoutKind.Sequential)] 24 | public unsafe struct NativeLongPtr : IDisposable 25 | { 26 | /// 27 | /// An atomic write-only version of the object suitable for use in a 28 | /// ParallelFor job 29 | /// 30 | [NativeContainer] 31 | [NativeContainerIsAtomicWriteOnly] 32 | public struct Parallel 33 | { 34 | /// 35 | /// Pointer to the value in native memory 36 | /// 37 | [NativeDisableUnsafePtrRestriction] 38 | internal readonly long* m_Buffer; 39 | 40 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 41 | /// 42 | /// A handle to information about what operations can be safely 43 | /// performed on the object at any given time. 44 | /// 45 | internal AtomicSafetyHandle m_Safety; 46 | 47 | /// 48 | /// Create a parallel version of the object 49 | /// 50 | /// 51 | /// 52 | /// Pointer to the value 53 | /// 54 | /// 55 | /// 56 | /// Atomic safety handle for the object 57 | /// 58 | internal Parallel(long* value, AtomicSafetyHandle safety) 59 | { 60 | m_Buffer = value; 61 | m_Safety = safety; 62 | } 63 | #else 64 | /// 65 | /// Create a parallel version of the object 66 | /// 67 | /// 68 | /// 69 | /// Pointer to the value 70 | /// 71 | internal Parallel(long* value) 72 | { 73 | m_Buffer = value; 74 | } 75 | #endif 76 | 77 | /// 78 | /// Increment the stored value 79 | /// 80 | /// 81 | /// 82 | /// This object 83 | /// 84 | [WriteAccessRequired] 85 | public void Increment() 86 | { 87 | RequireWriteAccess(); 88 | Interlocked.Increment(ref *m_Buffer); 89 | } 90 | 91 | /// 92 | /// Decrement the stored value 93 | /// 94 | /// 95 | /// 96 | /// This object 97 | /// 98 | [WriteAccessRequired] 99 | public void Decrement() 100 | { 101 | RequireWriteAccess(); 102 | Interlocked.Decrement(ref *m_Buffer); 103 | } 104 | 105 | /// 106 | /// Add to the stored value 107 | /// 108 | /// 109 | /// 110 | /// Value to add. Use negative values for subtraction. 111 | /// 112 | /// 113 | /// 114 | /// This object 115 | /// 116 | [WriteAccessRequired] 117 | public void Add(long value) 118 | { 119 | RequireWriteAccess(); 120 | Interlocked.Add(ref *m_Buffer, value); 121 | } 122 | 123 | /// 124 | /// Throw an exception if the object isn't writable 125 | /// 126 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 127 | private void RequireWriteAccess() 128 | { 129 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 130 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 131 | #endif 132 | } 133 | } 134 | 135 | /// 136 | /// Pointer to the value in native memory. Must be named exactly this 137 | /// way to allow for [NativeContainerSupportsDeallocateOnJobCompletion] 138 | /// 139 | [NativeDisableUnsafePtrRestriction] 140 | internal long* m_Buffer; 141 | 142 | /// 143 | /// Allocator used to create the backing memory 144 | /// 145 | /// This field must be named this way to comply with 146 | /// [NativeContainerSupportsDeallocateOnJobCompletion] 147 | /// 148 | internal readonly Allocator m_AllocatorLabel; 149 | 150 | // These fields are all required when safety checks are enabled 151 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 152 | /// 153 | /// A handle to information about what operations can be safely 154 | /// performed on the object at any given time. 155 | /// 156 | private AtomicSafetyHandle m_Safety; 157 | 158 | /// 159 | /// A handle that can be used to tell if the object has been disposed 160 | /// yet or not, which allows for error-checking double disposal. 161 | /// 162 | [NativeSetClassTypeToNullOnSchedule] 163 | private DisposeSentinel m_DisposeSentinel; 164 | #endif 165 | 166 | /// 167 | /// Allocate memory and set the initial value 168 | /// 169 | /// 170 | /// 171 | /// Allocator to allocate and deallocate with. Must be valid. 172 | /// 173 | /// 174 | /// 175 | /// Initial value of the allocated memory 176 | /// 177 | public NativeLongPtr(Allocator allocator, long initialValue = 0) 178 | { 179 | // Require a valid allocator 180 | if (allocator <= Allocator.None) 181 | { 182 | throw new ArgumentException( 183 | "Allocator must be Temp, TempJob or Persistent", 184 | "allocator"); 185 | } 186 | 187 | // Allocate the memory for the value 188 | m_Buffer = (long*)UnsafeUtility.Malloc( 189 | sizeof(long), 190 | UnsafeUtility.AlignOf(), 191 | allocator); 192 | 193 | // Store the allocator to use when deallocating 194 | m_AllocatorLabel = allocator; 195 | 196 | // Create the dispose sentinel 197 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 198 | #if UNITY_2018_3_OR_NEWER 199 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0, allocator); 200 | #else 201 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0); 202 | #endif 203 | #endif 204 | 205 | // Set the initial value 206 | *m_Buffer = initialValue; 207 | } 208 | 209 | /// 210 | /// Get or set the contained value 211 | /// 212 | /// This operation requires read access to the node for 'get' and write 213 | /// access to the node for 'set'. 214 | /// 215 | /// 216 | /// 217 | /// The contained value 218 | /// 219 | public long Value 220 | { 221 | get 222 | { 223 | RequireReadAccess(); 224 | return *m_Buffer; 225 | } 226 | 227 | [WriteAccessRequired] 228 | set 229 | { 230 | RequireWriteAccess(); 231 | *m_Buffer = value; 232 | } 233 | } 234 | 235 | /// 236 | /// Get a version of this object suitable for use in a ParallelFor job 237 | /// 238 | /// 239 | /// 240 | /// A version of this object suitable for use in a ParallelFor job 241 | /// 242 | public Parallel GetParallel() 243 | { 244 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 245 | Parallel parallel = new Parallel(m_Buffer, m_Safety); 246 | AtomicSafetyHandle.UseSecondaryVersion(ref parallel.m_Safety); 247 | #else 248 | Parallel parallel = new Parallel(m_Buffer); 249 | #endif 250 | return parallel; 251 | } 252 | 253 | /// 254 | /// Check if the underlying unmanaged memory has been created and not 255 | /// freed via a call to . 256 | /// 257 | /// This operation has no access requirements. 258 | /// 259 | /// This operation is O(1). 260 | /// 261 | /// 262 | /// 263 | /// Initially true when a non-default constructor is called but 264 | /// initially false when the default constructor is used. After 265 | /// is called, this becomes false. Note that 266 | /// calling on one copy of this object doesn't 267 | /// result in this becoming false for all copies if it was true before. 268 | /// This property should not be used to check whether the object 269 | /// is usable, only to check whether it was ever usable. 270 | /// 271 | public bool IsCreated 272 | { 273 | get 274 | { 275 | return m_Buffer != null; 276 | } 277 | } 278 | 279 | /// 280 | /// Release the object's unmanaged memory. Do not use it after this. Do 281 | /// not call on copies of the object either. 282 | /// 283 | /// This operation requires write access. 284 | /// 285 | /// This complexity of this operation is O(1) plus the allocator's 286 | /// deallocation complexity. 287 | /// 288 | [WriteAccessRequired] 289 | public void Dispose() 290 | { 291 | RequireWriteAccess(); 292 | 293 | // Make sure we're not double-disposing 294 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 295 | #if UNITY_2018_3_OR_NEWER 296 | DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); 297 | #else 298 | DisposeSentinel.Dispose(m_Safety, ref m_DisposeSentinel); 299 | #endif 300 | #endif 301 | 302 | UnsafeUtility.Free(m_Buffer, m_AllocatorLabel); 303 | m_Buffer = null; 304 | } 305 | 306 | /// 307 | /// Set whether both read and write access should be allowed. This is 308 | /// used for automated testing purposes only. 309 | /// 310 | /// 311 | /// 312 | /// If both read and write access should be allowed 313 | /// 314 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 315 | public void TestUseOnlySetAllowReadAndWriteAccess( 316 | bool allowReadOrWriteAccess) 317 | { 318 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 319 | AtomicSafetyHandle.SetAllowReadOrWriteAccess( 320 | m_Safety, 321 | allowReadOrWriteAccess); 322 | #endif 323 | } 324 | 325 | /// 326 | /// Throw an exception if the object isn't readable 327 | /// 328 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 329 | private void RequireReadAccess() 330 | { 331 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 332 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 333 | #endif 334 | } 335 | 336 | /// 337 | /// Throw an exception if the object isn't writable 338 | /// 339 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 340 | private void RequireWriteAccess() 341 | { 342 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 343 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 344 | #endif 345 | } 346 | } 347 | 348 | /// 349 | /// Provides a debugger view of . 350 | /// 351 | internal sealed class NativeLongPtrDebugView 352 | { 353 | /// 354 | /// The object to provide a debugger view for 355 | /// 356 | private NativeLongPtr m_Ptr; 357 | 358 | /// 359 | /// Create the debugger view 360 | /// 361 | /// 362 | /// 363 | /// The object to provide a debugger view for 364 | /// 365 | public NativeLongPtrDebugView(NativeLongPtr ptr) 366 | { 367 | m_Ptr = ptr; 368 | } 369 | 370 | /// 371 | /// Get the viewed object's value 372 | /// 373 | /// 374 | /// 375 | /// The viewed object's value 376 | /// 377 | public long Value 378 | { 379 | get 380 | { 381 | return m_Ptr.Value; 382 | } 383 | } 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativeLongPtr.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1712150778a2348629eb002d6eacddf1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativePerJobThreadIntPtr.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Diagnostics; 9 | using System.Runtime.InteropServices; 10 | using Unity.Collections; 11 | using Unity.Collections.LowLevel.Unsafe; 12 | using Unity.Jobs.LowLevel.Unsafe; 13 | 14 | namespace JacksonDunstan.NativeCollections 15 | { 16 | /// 17 | /// A pointer to an int stored in native (i.e. unmanaged) memory. One 18 | /// integer is stored for each of the maximum number of job threads. As of 19 | /// Unity 2018.2, this results in 8 KB of memory usage. The advantage over 20 | /// is that all operations on 21 | /// are faster due to not being atomic. The resulting 22 | /// is collected with a loop. This is therefore a good 23 | /// option when most usage is via and memory usage is 24 | /// not a concern. 25 | /// 26 | [NativeContainer] 27 | [NativeContainerSupportsDeallocateOnJobCompletion] 28 | [DebuggerTypeProxy(typeof(NativePerJobThreadIntPtrDebugView))] 29 | [DebuggerDisplay("Value = {Value}")] 30 | [StructLayout(LayoutKind.Sequential)] 31 | public unsafe struct NativePerJobThreadIntPtr : IDisposable 32 | { 33 | /// 34 | /// An atomic write-only version of the object suitable for use in a 35 | /// ParallelFor job 36 | /// 37 | [NativeContainer] 38 | [NativeContainerIsAtomicWriteOnly] 39 | public struct Parallel 40 | { 41 | /// 42 | /// Pointer to the value in native memory 43 | /// 44 | [NativeDisableUnsafePtrRestriction] 45 | internal readonly int* m_Buffer; 46 | 47 | /// 48 | /// Thread index of the job using this object. This is set by Unity 49 | /// and must have this exact name and type. 50 | /// 51 | [NativeSetThreadIndex] 52 | internal readonly int m_ThreadIndex; 53 | 54 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 55 | /// 56 | /// A handle to information about what operations can be safely 57 | /// performed on the object at any given time. 58 | /// 59 | internal AtomicSafetyHandle m_Safety; 60 | 61 | /// 62 | /// Create a parallel version of the object 63 | /// 64 | /// 65 | /// 66 | /// Pointer to the value 67 | /// 68 | /// 69 | /// 70 | /// Atomic safety handle for the object 71 | /// 72 | internal Parallel(int* value, AtomicSafetyHandle safety) 73 | { 74 | m_Buffer = value; 75 | m_ThreadIndex = 0; 76 | m_Safety = safety; 77 | } 78 | #else 79 | /// 80 | /// Create a parallel version of the object 81 | /// 82 | /// 83 | /// 84 | /// Pointer to the value 85 | /// 86 | internal Parallel(int* value) 87 | { 88 | m_Buffer = value; 89 | m_ThreadIndex = 0; 90 | } 91 | #endif 92 | 93 | /// 94 | /// Increment the stored value 95 | /// 96 | /// 97 | /// 98 | /// This object 99 | /// 100 | [WriteAccessRequired] 101 | public void Increment() 102 | { 103 | RequireWriteAccess(); 104 | m_Buffer[IntsPerCacheLine * m_ThreadIndex]++; 105 | } 106 | 107 | /// 108 | /// Decrement the stored value 109 | /// 110 | /// 111 | /// 112 | /// This object 113 | /// 114 | [WriteAccessRequired] 115 | public void Decrement() 116 | { 117 | RequireWriteAccess(); 118 | m_Buffer[IntsPerCacheLine * m_ThreadIndex]--; 119 | } 120 | 121 | /// 122 | /// Add to the stored value 123 | /// 124 | /// 125 | /// 126 | /// Value to add. Use negative values for subtraction. 127 | /// 128 | /// 129 | /// 130 | /// This object 131 | /// 132 | [WriteAccessRequired] 133 | public void Add(int value) 134 | { 135 | RequireWriteAccess(); 136 | m_Buffer[IntsPerCacheLine * m_ThreadIndex] += value; 137 | } 138 | 139 | /// 140 | /// Throw an exception if the object isn't writable 141 | /// 142 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 143 | private void RequireWriteAccess() 144 | { 145 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 146 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 147 | #endif 148 | } 149 | } 150 | 151 | /// 152 | /// Pointer to the value in native memory. Must be named exactly this 153 | /// way to allow for [NativeContainerSupportsDeallocateOnJobCompletion] 154 | /// 155 | [NativeDisableUnsafePtrRestriction] 156 | internal int* m_Buffer; 157 | 158 | /// 159 | /// Allocator used to create the backing memory 160 | /// 161 | /// This field must be named this way to comply with 162 | /// [NativeContainerSupportsDeallocateOnJobCompletion] 163 | /// 164 | internal Allocator m_AllocatorLabel; 165 | 166 | // These fields are all required when safety checks are enabled 167 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 168 | /// 169 | /// A handle to information about what operations can be safely 170 | /// performed on the object at any given time. 171 | /// 172 | private AtomicSafetyHandle m_Safety; 173 | 174 | /// 175 | /// A handle that can be used to tell if the object has been disposed 176 | /// yet or not, which allows for error-checking double disposal. 177 | /// 178 | [NativeSetClassTypeToNullOnSchedule] 179 | private DisposeSentinel m_DisposeSentinel; 180 | #endif 181 | 182 | /// 183 | /// The number of integers that fit into a CPU cache line 184 | /// 185 | private const int IntsPerCacheLine = JobsUtility.CacheLineSize / sizeof(int); 186 | 187 | /// 188 | /// Allocate memory and set the initial value 189 | /// 190 | /// 191 | /// 192 | /// Allocator to allocate and deallocate with. Must be valid. 193 | /// 194 | /// 195 | /// 196 | /// Initial value of the allocated memory 197 | /// 198 | public NativePerJobThreadIntPtr(Allocator allocator, int initialValue = 0) 199 | { 200 | // Require a valid allocator 201 | if (allocator <= Allocator.None) 202 | { 203 | throw new ArgumentException( 204 | "Allocator must be Temp, TempJob or Persistent", 205 | "allocator"); 206 | } 207 | 208 | // Allocate the memory for the values 209 | m_Buffer = (int*)UnsafeUtility.Malloc( 210 | JobsUtility.CacheLineSize * JobsUtility.MaxJobThreadCount, 211 | UnsafeUtility.AlignOf(), 212 | allocator); 213 | 214 | // Store the allocator to use when deallocating 215 | m_AllocatorLabel = allocator; 216 | 217 | // Create the dispose sentinel 218 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 219 | #if UNITY_2018_3_OR_NEWER 220 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0, allocator); 221 | #else 222 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0); 223 | #endif 224 | #endif 225 | 226 | // Set the initial value 227 | Value = initialValue; 228 | } 229 | 230 | /// 231 | /// Get or set the contained value 232 | /// 233 | /// This operation requires read access to the node for 'get' and write 234 | /// access to the node for 'set'. 235 | /// 236 | /// 237 | /// 238 | /// The contained value 239 | /// 240 | public int Value 241 | { 242 | get 243 | { 244 | RequireReadAccess(); 245 | int value = 0; 246 | for (int i = 0; i < JobsUtility.MaxJobThreadCount; ++i) 247 | { 248 | value += m_Buffer[IntsPerCacheLine * i]; 249 | } 250 | return value; 251 | } 252 | 253 | [WriteAccessRequired] 254 | set 255 | { 256 | RequireWriteAccess(); 257 | *m_Buffer = value; 258 | for (int i = 1; i < JobsUtility.MaxJobThreadCount; ++i) 259 | { 260 | m_Buffer[IntsPerCacheLine * i] = 0; 261 | } 262 | } 263 | } 264 | 265 | /// 266 | /// Get a version of this object suitable for use in a ParallelFor job 267 | /// 268 | /// 269 | /// 270 | /// A version of this object suitable for use in a ParallelFor job 271 | /// 272 | public Parallel GetParallel() 273 | { 274 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 275 | Parallel parallel = new Parallel(m_Buffer, m_Safety); 276 | AtomicSafetyHandle.UseSecondaryVersion(ref parallel.m_Safety); 277 | #else 278 | Parallel parallel = new Parallel(m_Buffer); 279 | #endif 280 | return parallel; 281 | } 282 | 283 | /// 284 | /// Check if the underlying unmanaged memory has been created and not 285 | /// freed via a call to . 286 | /// 287 | /// This operation has no access requirements. 288 | /// 289 | /// This operation is O(1). 290 | /// 291 | /// 292 | /// 293 | /// Initially true when a non-default constructor is called but 294 | /// initially false when the default constructor is used. After 295 | /// is called, this becomes false. Note that 296 | /// calling on one copy of this object doesn't 297 | /// result in this becoming false for all copies if it was true before. 298 | /// This property should not be used to check whether the object 299 | /// is usable, only to check whether it was ever usable. 300 | /// 301 | public bool IsCreated 302 | { 303 | get 304 | { 305 | return m_Buffer != null; 306 | } 307 | } 308 | 309 | /// 310 | /// Release the object's unmanaged memory. Do not use it after this. Do 311 | /// not call on copies of the object either. 312 | /// 313 | /// This operation requires write access. 314 | /// 315 | /// This complexity of this operation is O(1) plus the allocator's 316 | /// deallocation complexity. 317 | /// 318 | [WriteAccessRequired] 319 | public void Dispose() 320 | { 321 | RequireWriteAccess(); 322 | 323 | // Make sure we're not double-disposing 324 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 325 | #if UNITY_2018_3_OR_NEWER 326 | DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); 327 | #else 328 | DisposeSentinel.Dispose(m_Safety, ref m_DisposeSentinel); 329 | #endif 330 | #endif 331 | 332 | UnsafeUtility.Free(m_Buffer, m_AllocatorLabel); 333 | m_Buffer = null; 334 | } 335 | 336 | /// 337 | /// Set whether both read and write access should be allowed. This is 338 | /// used for automated testing purposes only. 339 | /// 340 | /// 341 | /// 342 | /// If both read and write access should be allowed 343 | /// 344 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 345 | public void TestUseOnlySetAllowReadAndWriteAccess( 346 | bool allowReadOrWriteAccess) 347 | { 348 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 349 | AtomicSafetyHandle.SetAllowReadOrWriteAccess( 350 | m_Safety, 351 | allowReadOrWriteAccess); 352 | #endif 353 | } 354 | 355 | /// 356 | /// Throw an exception if the object isn't readable 357 | /// 358 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 359 | private void RequireReadAccess() 360 | { 361 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 362 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 363 | #endif 364 | } 365 | 366 | /// 367 | /// Throw an exception if the object isn't writable 368 | /// 369 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 370 | private void RequireWriteAccess() 371 | { 372 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 373 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 374 | #endif 375 | } 376 | } 377 | 378 | /// 379 | /// Provides a debugger view of . 380 | /// 381 | internal sealed class NativePerJobThreadIntPtrDebugView 382 | { 383 | /// 384 | /// The object to provide a debugger view for 385 | /// 386 | private NativeIntPtr m_Ptr; 387 | 388 | /// 389 | /// Create the debugger view 390 | /// 391 | /// 392 | /// 393 | /// The object to provide a debugger view for 394 | /// 395 | public NativePerJobThreadIntPtrDebugView(NativeIntPtr ptr) 396 | { 397 | m_Ptr = ptr; 398 | } 399 | 400 | /// 401 | /// Get the viewed object's value 402 | /// 403 | /// 404 | /// 405 | /// The viewed object's value 406 | /// 407 | public int Value 408 | { 409 | get 410 | { 411 | return m_Ptr.Value; 412 | } 413 | } 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativePerJobThreadIntPtr.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eea96d2e70fee4eb99acbec8d62d4c8b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativePerJobThreadLongPtr.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Diagnostics; 9 | using System.Runtime.InteropServices; 10 | using Unity.Collections; 11 | using Unity.Collections.LowLevel.Unsafe; 12 | using Unity.Jobs.LowLevel.Unsafe; 13 | 14 | namespace JacksonDunstan.NativeCollections 15 | { 16 | /// 17 | /// A pointer to an long stored in native (i.e. unmanaged) memory. One 18 | /// long is stored for each of the maximum number of job threads. As of 19 | /// Unity 2018.2, this results in 8 KB of memory usage. The advantage over 20 | /// is that all operations on 21 | /// are faster due to not being atomic. The resulting 22 | /// is collected with a loop. This is therefore a good 23 | /// option when most usage is via and memory usage is 24 | /// not a concern. 25 | /// 26 | [NativeContainer] 27 | [NativeContainerSupportsDeallocateOnJobCompletion] 28 | [DebuggerTypeProxy(typeof(NativePerJobThreadLongPtrDebugView))] 29 | [DebuggerDisplay("Value = {Value}")] 30 | [StructLayout(LayoutKind.Sequential)] 31 | public unsafe struct NativePerJobThreadLongPtr : IDisposable 32 | { 33 | /// 34 | /// An atomic write-only version of the object suitable for use in a 35 | /// ParallelFor job 36 | /// 37 | [NativeContainer] 38 | [NativeContainerIsAtomicWriteOnly] 39 | public struct Parallel 40 | { 41 | /// 42 | /// Polonger to the value in native memory 43 | /// 44 | [NativeDisableUnsafePtrRestriction] 45 | internal readonly long* m_Buffer; 46 | 47 | /// 48 | /// Thread index of the job using this object. This is set by Unity 49 | /// and must have this exact name and type. 50 | /// 51 | [NativeSetThreadIndex] 52 | internal readonly int m_ThreadIndex; 53 | 54 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 55 | /// 56 | /// A handle to information about what operations can be safely 57 | /// performed on the object at any given time. 58 | /// 59 | internal AtomicSafetyHandle m_Safety; 60 | 61 | /// 62 | /// Create a parallel version of the object 63 | /// 64 | /// 65 | /// 66 | /// Polonger to the value 67 | /// 68 | /// 69 | /// 70 | /// Atomic safety handle for the object 71 | /// 72 | internal Parallel(long* value, AtomicSafetyHandle safety) 73 | { 74 | m_Buffer = value; 75 | m_ThreadIndex = 0; 76 | m_Safety = safety; 77 | } 78 | #else 79 | /// 80 | /// Create a parallel version of the object 81 | /// 82 | /// 83 | /// 84 | /// Polonger to the value 85 | /// 86 | internal Parallel(long* value) 87 | { 88 | m_Buffer = value; 89 | m_ThreadIndex = 0; 90 | } 91 | #endif 92 | 93 | /// 94 | /// Increment the stored value 95 | /// 96 | /// 97 | /// 98 | /// This object 99 | /// 100 | [WriteAccessRequired] 101 | public void Increment() 102 | { 103 | RequireWriteAccess(); 104 | m_Buffer[LongsPerCacheLine * m_ThreadIndex]++; 105 | } 106 | 107 | /// 108 | /// Decrement the stored value 109 | /// 110 | /// 111 | /// 112 | /// This object 113 | /// 114 | [WriteAccessRequired] 115 | public void Decrement() 116 | { 117 | RequireWriteAccess(); 118 | m_Buffer[LongsPerCacheLine * m_ThreadIndex]--; 119 | } 120 | 121 | /// 122 | /// Add to the stored value 123 | /// 124 | /// 125 | /// 126 | /// Value to add. Use negative values for subtraction. 127 | /// 128 | /// 129 | /// 130 | /// This object 131 | /// 132 | [WriteAccessRequired] 133 | public void Add(long value) 134 | { 135 | RequireWriteAccess(); 136 | m_Buffer[LongsPerCacheLine * m_ThreadIndex] += value; 137 | } 138 | 139 | /// 140 | /// Throw an exception if the object isn't writable 141 | /// 142 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 143 | private void RequireWriteAccess() 144 | { 145 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 146 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 147 | #endif 148 | } 149 | } 150 | 151 | /// 152 | /// Polonger to the value in native memory. Must be named exactly this 153 | /// way to allow for [NativeContainerSupportsDeallocateOnJobCompletion] 154 | /// 155 | [NativeDisableUnsafePtrRestriction] 156 | internal long* m_Buffer; 157 | 158 | /// 159 | /// Allocator used to create the backing memory 160 | /// 161 | /// This field must be named this way to comply with 162 | /// [NativeContainerSupportsDeallocateOnJobCompletion] 163 | /// 164 | internal readonly Allocator m_AllocatorLabel; 165 | 166 | // These fields are all required when safety checks are enabled 167 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 168 | /// 169 | /// A handle to information about what operations can be safely 170 | /// performed on the object at any given time. 171 | /// 172 | private AtomicSafetyHandle m_Safety; 173 | 174 | /// 175 | /// A handle that can be used to tell if the object has been disposed 176 | /// yet or not, which allows for error-checking double disposal. 177 | /// 178 | [NativeSetClassTypeToNullOnSchedule] 179 | private DisposeSentinel m_DisposeSentinel; 180 | #endif 181 | 182 | /// 183 | /// The number of longs that fit into a CPU cache line 184 | /// 185 | private const long LongsPerCacheLine = JobsUtility.CacheLineSize / sizeof(long); 186 | 187 | /// 188 | /// Allocate memory and set the initial value 189 | /// 190 | /// 191 | /// 192 | /// Allocator to allocate and deallocate with. Must be valid. 193 | /// 194 | /// 195 | /// 196 | /// Initial value of the allocated memory 197 | /// 198 | public NativePerJobThreadLongPtr( 199 | Allocator allocator, 200 | long initialValue = 0) 201 | { 202 | // Require a valid allocator 203 | if (allocator <= Allocator.None) 204 | { 205 | throw new ArgumentException( 206 | "Allocator must be Temp, TempJob or Persistent", 207 | "allocator"); 208 | } 209 | 210 | // Allocate the memory for the values 211 | m_Buffer = (long*)UnsafeUtility.Malloc( 212 | JobsUtility.CacheLineSize * JobsUtility.MaxJobThreadCount, 213 | UnsafeUtility.AlignOf(), 214 | allocator); 215 | 216 | // Store the allocator to use when deallocating 217 | m_AllocatorLabel = allocator; 218 | 219 | // Create the dispose sentinel 220 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 221 | #if UNITY_2018_3_OR_NEWER 222 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0, allocator); 223 | #else 224 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0); 225 | #endif 226 | #endif 227 | 228 | // Set the initial value 229 | Value = initialValue; 230 | } 231 | 232 | /// 233 | /// Get or set the contained value 234 | /// 235 | /// This operation requires read access to the node for 'get' and write 236 | /// access to the node for 'set'. 237 | /// 238 | /// 239 | /// 240 | /// The contained value 241 | /// 242 | public long Value 243 | { 244 | get 245 | { 246 | RequireReadAccess(); 247 | long value = 0; 248 | for (long i = 0; i < JobsUtility.MaxJobThreadCount; ++i) 249 | { 250 | value += m_Buffer[LongsPerCacheLine * i]; 251 | } 252 | return value; 253 | } 254 | 255 | [WriteAccessRequired] 256 | set 257 | { 258 | RequireWriteAccess(); 259 | *m_Buffer = value; 260 | for (long i = 1; i < JobsUtility.MaxJobThreadCount; ++i) 261 | { 262 | m_Buffer[LongsPerCacheLine * i] = 0; 263 | } 264 | } 265 | } 266 | 267 | /// 268 | /// Get a version of this object suitable for use in a ParallelFor job 269 | /// 270 | /// 271 | /// 272 | /// A version of this object suitable for use in a ParallelFor job 273 | /// 274 | public Parallel GetParallel() 275 | { 276 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 277 | Parallel parallel = new Parallel(m_Buffer, m_Safety); 278 | AtomicSafetyHandle.UseSecondaryVersion(ref parallel.m_Safety); 279 | #else 280 | Parallel parallel = new Parallel(m_Buffer); 281 | #endif 282 | return parallel; 283 | } 284 | 285 | /// 286 | /// Check if the underlying unmanaged memory has been created and not 287 | /// freed via a call to . 288 | /// 289 | /// This operation has no access requirements. 290 | /// 291 | /// This operation is O(1). 292 | /// 293 | /// 294 | /// 295 | /// Initially true when a non-default constructor is called but 296 | /// initially false when the default constructor is used. After 297 | /// is called, this becomes false. Note that 298 | /// calling on one copy of this object doesn't 299 | /// result in this becoming false for all copies if it was true before. 300 | /// This property should not be used to check whether the object 301 | /// is usable, only to check whether it was ever usable. 302 | /// 303 | public bool IsCreated 304 | { 305 | get 306 | { 307 | return m_Buffer != null; 308 | } 309 | } 310 | 311 | /// 312 | /// Release the object's unmanaged memory. Do not use it after this. Do 313 | /// not call on copies of the object either. 314 | /// 315 | /// This operation requires write access. 316 | /// 317 | /// This complexity of this operation is O(1) plus the allocator's 318 | /// deallocation complexity. 319 | /// 320 | [WriteAccessRequired] 321 | public void Dispose() 322 | { 323 | RequireWriteAccess(); 324 | 325 | // Make sure we're not double-disposing 326 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 327 | #if UNITY_2018_3_OR_NEWER 328 | DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); 329 | #else 330 | DisposeSentinel.Dispose(m_Safety, ref m_DisposeSentinel); 331 | #endif 332 | #endif 333 | 334 | UnsafeUtility.Free(m_Buffer, m_AllocatorLabel); 335 | m_Buffer = null; 336 | } 337 | 338 | /// 339 | /// Set whether both read and write access should be allowed. This is 340 | /// used for automated testing purposes only. 341 | /// 342 | /// 343 | /// 344 | /// If both read and write access should be allowed 345 | /// 346 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 347 | public void TestUseOnlySetAllowReadAndWriteAccess( 348 | bool allowReadOrWriteAccess) 349 | { 350 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 351 | AtomicSafetyHandle.SetAllowReadOrWriteAccess( 352 | m_Safety, 353 | allowReadOrWriteAccess); 354 | #endif 355 | } 356 | 357 | /// 358 | /// Throw an exception if the object isn't readable 359 | /// 360 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 361 | private void RequireReadAccess() 362 | { 363 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 364 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 365 | #endif 366 | } 367 | 368 | /// 369 | /// Throw an exception if the object isn't writable 370 | /// 371 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 372 | private void RequireWriteAccess() 373 | { 374 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 375 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 376 | #endif 377 | } 378 | } 379 | 380 | /// 381 | /// Provides a debugger view of . 382 | /// 383 | internal sealed class NativePerJobThreadLongPtrDebugView 384 | { 385 | /// 386 | /// The object to provide a debugger view for 387 | /// 388 | private NativeLongPtr m_Ptr; 389 | 390 | /// 391 | /// Create the debugger view 392 | /// 393 | /// 394 | /// 395 | /// The object to provide a debugger view for 396 | /// 397 | public NativePerJobThreadLongPtrDebugView(NativeLongPtr ptr) 398 | { 399 | m_Ptr = ptr; 400 | } 401 | 402 | /// 403 | /// Get the viewed object's value 404 | /// 405 | /// 406 | /// 407 | /// The viewed object's value 408 | /// 409 | public long Value 410 | { 411 | get 412 | { 413 | return m_Ptr.Value; 414 | } 415 | } 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/NativePerJobThreadLongPtr.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a8e8c6b3ce2344575a9ee586d202d340 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/SharedDisposable.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Diagnostics; 9 | using System.Runtime.InteropServices; 10 | using JacksonDunstan.NativeCollections; 11 | using Unity.Collections; 12 | using Unity.Collections.LowLevel.Unsafe; 13 | 14 | namespace JacksonDunstan.NativeCollections 15 | { 16 | /// 17 | /// A reference-counted . 18 | /// 19 | /// 20 | /// 21 | /// Type of disposable that is shared. 22 | /// 23 | [NativeContainer] 24 | [NativeContainerSupportsDeallocateOnJobCompletion] 25 | [DebuggerTypeProxy(typeof(SharedDisposableDebugView<>))] 26 | [DebuggerDisplay("Disposable = {Value}")] 27 | [StructLayout(LayoutKind.Sequential)] 28 | public unsafe struct SharedDisposable : IDisposable 29 | where TDisposable : IDisposable 30 | { 31 | /// 32 | /// Pointer to the ref count in native memory. Must be named exactly 33 | /// this way to allow for 34 | /// [NativeContainerSupportsDeallocateOnJobCompletion] 35 | /// 36 | [NativeDisableUnsafePtrRestriction] 37 | internal int* m_Buffer; 38 | 39 | /// 40 | /// Allocator used to create the backing memory 41 | /// 42 | /// This field must be named this way to comply with 43 | /// [NativeContainerSupportsDeallocateOnJobCompletion] 44 | /// 45 | internal readonly Allocator m_AllocatorLabel; 46 | 47 | /// 48 | /// Disposable that is being shared 49 | /// 50 | private readonly TDisposable m_Disposable; 51 | 52 | // These fields are all required when safety checks are enabled 53 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 54 | /// 55 | /// A handle to information about what operations can be safely 56 | /// performed on the object at any given time. 57 | /// 58 | private AtomicSafetyHandle m_Safety; 59 | 60 | /// 61 | /// A handle that can be used to tell if the object has been disposed 62 | /// yet or not, which allows for error-checking double disposal. 63 | /// 64 | [NativeSetClassTypeToNullOnSchedule] 65 | private DisposeSentinel m_DisposeSentinel; 66 | #endif 67 | 68 | /// 69 | /// Allocate memory and save the disposable 70 | /// 71 | /// 72 | /// 73 | /// Disposable that is being shared 74 | /// 75 | /// 76 | /// 77 | /// Allocator to allocate and deallocate with. Must be valid. 78 | /// 79 | public SharedDisposable( 80 | #if CSHARP_7_3_OR_NEWER 81 | in 82 | #endif 83 | TDisposable disposable, 84 | Allocator allocator) 85 | { 86 | // Require a valid allocator 87 | if (!UnsafeUtility.IsValidAllocator(allocator)) 88 | { 89 | throw new ArgumentException( 90 | "Invalid allocator", 91 | "allocator"); 92 | } 93 | 94 | // Allocate the memory for the ref count and initialize to 1 95 | m_Buffer = (int*)UnsafeUtility.Malloc( 96 | sizeof(int), 97 | UnsafeUtility.AlignOf(), 98 | allocator); 99 | *m_Buffer = 1; 100 | 101 | // Store the allocator to use when deallocating 102 | m_AllocatorLabel = allocator; 103 | 104 | // Create the dispose sentinel 105 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 106 | #if UNITY_2018_3_OR_NEWER 107 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0, allocator); 108 | #else 109 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0); 110 | #endif 111 | #endif 112 | 113 | // Save the disposable 114 | m_Disposable = disposable; 115 | } 116 | 117 | /// 118 | /// Get or set the contained disposable 119 | /// 120 | /// This operation requires read access. 121 | /// 122 | /// 123 | /// 124 | /// The contained disposable 125 | /// 126 | public TDisposable Value 127 | { 128 | get 129 | { 130 | RequireReadAccess(); 131 | return m_Disposable; 132 | } 133 | } 134 | 135 | /// 136 | /// Check if the underlying unmanaged memory has been created and not 137 | /// freed via a call to . 138 | /// 139 | /// This operation has no access requirements. 140 | /// 141 | /// This operation is O(1). 142 | /// 143 | /// 144 | /// 145 | /// Initially true when a non-default constructor is called but 146 | /// initially false when the default constructor is used. After 147 | /// is called, this becomes false. Note that 148 | /// calling on one copy of this object doesn't 149 | /// result in this becoming false for all copies if it was true before. 150 | /// This property should not be used to check whether the object 151 | /// is usable, only to check whether it was ever usable. 152 | /// 153 | public bool IsCreated 154 | { 155 | get 156 | { 157 | return m_Buffer != null; 158 | } 159 | } 160 | 161 | /// 162 | /// Increment the reference count. 163 | /// 164 | /// This operation requires write access. 165 | /// 166 | /// 167 | /// 168 | /// A reference to this object. 169 | /// 170 | [WriteAccessRequired] 171 | public SharedDisposable Ref() 172 | { 173 | *m_Buffer = *m_Buffer + 1; 174 | return this; 175 | } 176 | 177 | /// 178 | /// Release the object's unmanaged memory. Do not use it after this. Do 179 | /// not call on copies of the object either. 180 | /// 181 | /// This operation requires write access. 182 | /// 183 | /// This complexity of this operation is O(1) plus the allocator's 184 | /// deallocation complexity. 185 | /// 186 | [WriteAccessRequired] 187 | public void Dispose() 188 | { 189 | RequireWriteAccess(); 190 | 191 | int newRefCount = *m_Buffer - 1; 192 | *m_Buffer = newRefCount; 193 | if (newRefCount == 0) 194 | { 195 | // Make sure we're not double-disposing 196 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 197 | #if UNITY_2018_3_OR_NEWER 198 | DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); 199 | #else 200 | DisposeSentinel.Dispose(m_Safety, ref m_DisposeSentinel); 201 | #endif 202 | #endif 203 | 204 | UnsafeUtility.Free(m_Buffer, m_AllocatorLabel); 205 | m_Buffer = null; 206 | 207 | m_Disposable.Dispose(); 208 | } 209 | } 210 | 211 | /// 212 | /// Set whether both read and write access should be allowed. This is 213 | /// used for automated testing purposes only. 214 | /// 215 | /// 216 | /// 217 | /// If both read and write access should be allowed 218 | /// 219 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 220 | public void TestUseOnlySetAllowReadAndWriteAccess( 221 | bool allowReadOrWriteAccess) 222 | { 223 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 224 | AtomicSafetyHandle.SetAllowReadOrWriteAccess( 225 | m_Safety, 226 | allowReadOrWriteAccess); 227 | #endif 228 | } 229 | 230 | /// 231 | /// Throw an exception if the object isn't readable 232 | /// 233 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 234 | private void RequireReadAccess() 235 | { 236 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 237 | AtomicSafetyHandle.CheckReadAndThrow(m_Safety); 238 | #endif 239 | } 240 | 241 | /// 242 | /// Throw an exception if the object isn't writable 243 | /// 244 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 245 | private void RequireWriteAccess() 246 | { 247 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 248 | AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); 249 | #endif 250 | } 251 | } 252 | 253 | /// 254 | /// Provides a debugger view of . 255 | /// 256 | /// 257 | /// 258 | /// Type of disposable that is shared. 259 | /// 260 | internal sealed class SharedDisposableDebugView 261 | where TDisposable : IDisposable 262 | { 263 | /// 264 | /// The object to provide a debugger view for 265 | /// 266 | private SharedDisposable m_Ptr; 267 | 268 | /// 269 | /// Create the debugger view 270 | /// 271 | /// 272 | /// 273 | /// The object to provide a debugger view for 274 | /// 275 | public SharedDisposableDebugView(SharedDisposable ptr) 276 | { 277 | m_Ptr = ptr; 278 | } 279 | 280 | /// 281 | /// Get the viewed object's disposable 282 | /// 283 | /// 284 | /// 285 | /// The viewed object's disposable 286 | /// 287 | public TDisposable Disposable 288 | { 289 | get 290 | { 291 | return m_Ptr.Value; 292 | } 293 | } 294 | } 295 | } 296 | 297 | /// 298 | /// Extensions to to support 299 | /// . 300 | /// 301 | public static class IDisposableExtensions 302 | { 303 | /// 304 | /// Allocate memory and save the disposable 305 | /// 306 | /// 307 | /// 308 | /// Disposable that is being shared 309 | /// 310 | /// 311 | /// 312 | /// Allocator to allocate and deallocate with. Must be valid. 313 | /// 314 | public static SharedDisposable Share( 315 | this TDisposable disposable, 316 | Allocator allocator) 317 | where TDisposable : IDisposable 318 | { 319 | return new SharedDisposable(disposable, allocator); 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/SharedDisposable.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e2c2313cea4814fc59c837b308a4ef3a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8b7dc29c216f74b54a311ba9ba6725f3 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/JacksonDunstan.NativeCollections.Tests.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JacksonDunstan.NativeCollections.Tests", 3 | "references": [ 4 | "JacksonDunstan.NativeCollections" 5 | ], 6 | "optionalUnityReferences": [ 7 | "TestAssemblies" 8 | ], 9 | "includePlatforms": [ 10 | "Editor" 11 | ], 12 | "excludePlatforms": [], 13 | "allowUnsafeCode": true 14 | } -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/JacksonDunstan.NativeCollections.Tests.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 016eb04efadcc124780bb372a2f0b970 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativeArray2D.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System.Collections; 8 | using NUnit.Framework; 9 | using Unity.Collections; 10 | 11 | namespace JacksonDunstan.NativeCollections.Tests 12 | { 13 | /// 14 | /// Unit tests for and 15 | /// 16 | /// 17 | public class TestNativeArray2D 18 | { 19 | private static NativeArray2D CreateArray(int width, int height) 20 | { 21 | return new NativeArray2D(width, height, Allocator.Temp); 22 | } 23 | 24 | [Test] 25 | public void ConstructorCreatesEmptyArray() 26 | { 27 | using (NativeArray2D array = CreateArray(2, 3)) 28 | { 29 | Assert.That(array[0, 0], Is.EqualTo(0)); 30 | Assert.That(array[0, 1], Is.EqualTo(0)); 31 | Assert.That(array[0, 2], Is.EqualTo(0)); 32 | Assert.That(array[1, 0], Is.EqualTo(0)); 33 | Assert.That(array[1, 1], Is.EqualTo(0)); 34 | Assert.That(array[1, 2], Is.EqualTo(0)); 35 | } 36 | } 37 | 38 | [Test] 39 | public void ConstructorThrowsForNonPositiveLength() 40 | { 41 | Assert.That( 42 | () => new NativeArray2D(-2, 3, Allocator.Temp), 43 | Throws.Exception); 44 | Assert.That( 45 | () => new NativeArray2D(2, -3, Allocator.Temp), 46 | Throws.Exception); 47 | Assert.That( 48 | () => new NativeArray2D(0, 3, Allocator.Temp), 49 | Throws.Exception); 50 | Assert.That( 51 | () => new NativeArray2D(2, 0, Allocator.Temp), 52 | Throws.Exception); 53 | } 54 | 55 | [Test] 56 | public void ConstructorThrowsForInvalidAllocator() 57 | { 58 | Assert.That( 59 | () => new NativeArray2D(1, 1, Allocator.None), 60 | Throws.Exception); 61 | } 62 | 63 | [Test] 64 | public void ConstructorCopiesManagedArray() 65 | { 66 | int[,] managed = 67 | { 68 | {100, 200, 300}, 69 | {400, 500, 600} 70 | }; 71 | using (NativeArray2D native = new NativeArray2D( 72 | managed, 73 | Allocator.Temp)) 74 | { 75 | Assert.That(managed[0, 0], Is.EqualTo(native[0, 0])); 76 | Assert.That(managed[0, 1], Is.EqualTo(native[0, 1])); 77 | Assert.That(managed[0, 2], Is.EqualTo(native[0, 2])); 78 | Assert.That(managed[1, 0], Is.EqualTo(native[1, 0])); 79 | Assert.That(managed[1, 1], Is.EqualTo(native[1, 1])); 80 | Assert.That(managed[1, 2], Is.EqualTo(native[1, 2])); 81 | } 82 | } 83 | 84 | [Test] 85 | public void ConstructorCopiesNativeArray() 86 | { 87 | using (NativeArray2D src = CreateArray(2, 3)) 88 | { 89 | NativeArray2D srcAlias = src; 90 | srcAlias[0, 0] = 100; 91 | srcAlias[0, 1] = 200; 92 | srcAlias[0, 2] = 300; 93 | srcAlias[1, 0] = 400; 94 | srcAlias[1, 1] = 500; 95 | srcAlias[1, 2] = 600; 96 | 97 | using (NativeArray2D dest = new NativeArray2D( 98 | src, 99 | Allocator.Temp)) 100 | { 101 | Assert.That(dest[0, 0], Is.EqualTo(src[0, 0])); 102 | Assert.That(dest[0, 1], Is.EqualTo(src[0, 1])); 103 | Assert.That(dest[0, 2], Is.EqualTo(src[0, 2])); 104 | Assert.That(dest[1, 0], Is.EqualTo(src[1, 0])); 105 | Assert.That(dest[1, 1], Is.EqualTo(src[1, 1])); 106 | Assert.That(dest[1, 2], Is.EqualTo(src[1, 2])); 107 | } 108 | } 109 | } 110 | 111 | [Test] 112 | public void LengthReturnsTotalNumberOfElements() 113 | { 114 | using (NativeArray2D array = CreateArray(2, 3)) 115 | { 116 | Assert.That(array.Length, Is.EqualTo(6)); 117 | } 118 | } 119 | 120 | [Test] 121 | public void Length0ReturnsTotalNumberOfElements() 122 | { 123 | using (NativeArray2D array = CreateArray(2, 3)) 124 | { 125 | Assert.That(array.Length0, Is.EqualTo(2)); 126 | } 127 | } 128 | 129 | [Test] 130 | public void Length1ReturnsTotalNumberOfElements() 131 | { 132 | using (NativeArray2D array = CreateArray(2, 3)) 133 | { 134 | Assert.That(array.Length1, Is.EqualTo(3)); 135 | } 136 | } 137 | 138 | [Test] 139 | public void IndexGetsAndSetsElementAtGivenIndex() 140 | { 141 | using (NativeArray2D array = CreateArray(2, 3)) 142 | { 143 | NativeArray2D alias = array; 144 | alias[0, 0] = 100; 145 | alias[0, 1] = 200; 146 | alias[0, 2] = 300; 147 | alias[1, 0] = 400; 148 | alias[1, 1] = 500; 149 | alias[1, 2] = 600; 150 | Assert.That(array[0, 0], Is.EqualTo(100)); 151 | Assert.That(array[0, 1], Is.EqualTo(200)); 152 | Assert.That(array[0, 2], Is.EqualTo(300)); 153 | Assert.That(array[1, 0], Is.EqualTo(400)); 154 | Assert.That(array[1, 1], Is.EqualTo(500)); 155 | Assert.That(array[1, 2], Is.EqualTo(600)); 156 | } 157 | } 158 | 159 | [Test] 160 | public void IndexOutOfBoundsThrows() 161 | { 162 | using (NativeArray2D array = CreateArray(2, 3)) 163 | { 164 | Assert.That( 165 | () => array[-1, 0], 166 | Throws.Exception); 167 | Assert.That( 168 | () => array[2, 0], 169 | Throws.Exception); 170 | Assert.That( 171 | () => array[0, 3], 172 | Throws.Exception); 173 | Assert.That( 174 | () => array[0, -1], 175 | Throws.Exception); 176 | } 177 | } 178 | 179 | [Test] 180 | public void IsCreatedReturnsTrueForDefaultStruct() 181 | { 182 | NativeArray2D array = default(NativeArray2D); 183 | Assert.That(array.IsCreated, Is.False); 184 | } 185 | 186 | [Test] 187 | public void IsCreatedReturnsTrueAfterConstructor() 188 | { 189 | using (NativeArray2D array = CreateArray(2, 3)) 190 | { 191 | Assert.That(array.IsCreated, Is.True); 192 | } 193 | } 194 | 195 | [Test] 196 | public void DisposeMakesArrayUnusable() 197 | { 198 | NativeArray2D array = CreateArray(2, 3); 199 | array.Dispose(); 200 | int val; 201 | Assert.That(() => val = array[0, 0], Throws.Exception); 202 | } 203 | 204 | [Test] 205 | public void CopyFromManagedArrayCopiesElements() 206 | { 207 | using (NativeArray2D dest = CreateArray(2, 3)) 208 | { 209 | int[,] src = 210 | { 211 | {100, 200, 300}, 212 | {400, 500, 600} 213 | }; 214 | 215 | dest.CopyFrom(src); 216 | 217 | Assert.That(src[0, 0], Is.EqualTo(dest[0, 0])); 218 | Assert.That(src[0, 1], Is.EqualTo(dest[0, 1])); 219 | Assert.That(src[0, 2], Is.EqualTo(dest[0, 2])); 220 | Assert.That(src[1, 0], Is.EqualTo(dest[1, 0])); 221 | Assert.That(src[1, 1], Is.EqualTo(dest[1, 1])); 222 | Assert.That(src[1, 2], Is.EqualTo(dest[1, 2])); 223 | } 224 | } 225 | 226 | [Test] 227 | public void CopyFromManagedArrayThrowsWhenDifferentSize() 228 | { 229 | using (NativeArray2D dest = CreateArray(2, 3)) 230 | { 231 | int[,] src = 232 | { 233 | {100, 200, 300} 234 | }; 235 | 236 | Assert.That(() => dest.CopyFrom(src), Throws.Exception); 237 | } 238 | } 239 | 240 | [Test] 241 | public void CopyFromNativeArrayCopiesElements() 242 | { 243 | using (NativeArray2D src = CreateArray(2, 3)) 244 | { 245 | using (NativeArray2D dest = CreateArray(2, 3)) 246 | { 247 | NativeArray2D srcAlias = src; 248 | srcAlias[0, 0] = 100; 249 | srcAlias[0, 1] = 200; 250 | srcAlias[0, 2] = 300; 251 | srcAlias[1, 0] = 400; 252 | srcAlias[1, 1] = 500; 253 | srcAlias[1, 2] = 600; 254 | 255 | dest.CopyFrom(src); 256 | 257 | Assert.That(dest[0, 0], Is.EqualTo(src[0, 0])); 258 | Assert.That(dest[0, 1], Is.EqualTo(src[0, 1])); 259 | Assert.That(dest[0, 2], Is.EqualTo(src[0, 2])); 260 | Assert.That(dest[1, 0], Is.EqualTo(src[1, 0])); 261 | Assert.That(dest[1, 1], Is.EqualTo(src[1, 1])); 262 | Assert.That(dest[1, 2], Is.EqualTo(src[1, 2])); 263 | } 264 | } 265 | } 266 | 267 | [Test] 268 | public void CopyFromNativeArrayThrowsWhenDifferentSize() 269 | { 270 | using (NativeArray2D src = CreateArray(2, 3)) 271 | { 272 | using (NativeArray2D dest = CreateArray(2, 4)) 273 | { 274 | Assert.That(() => dest.CopyFrom(src), Throws.Exception); 275 | } 276 | } 277 | } 278 | 279 | [Test] 280 | public void CopyToManagedArrayCopiesElements() 281 | { 282 | using (NativeArray2D src = CreateArray(2, 3)) 283 | { 284 | NativeArray2D srcAlias = src; 285 | srcAlias[0, 0] = 100; 286 | srcAlias[0, 1] = 200; 287 | srcAlias[0, 2] = 300; 288 | srcAlias[1, 0] = 400; 289 | srcAlias[1, 1] = 500; 290 | srcAlias[1, 2] = 600; 291 | 292 | int[,] dest = new int[2, 3]; 293 | 294 | src.CopyTo(dest); 295 | 296 | Assert.That(dest[0, 0], Is.EqualTo(src[0, 0])); 297 | Assert.That(dest[0, 1], Is.EqualTo(src[0, 1])); 298 | Assert.That(dest[0, 2], Is.EqualTo(src[0, 2])); 299 | Assert.That(dest[1, 0], Is.EqualTo(src[1, 0])); 300 | Assert.That(dest[1, 1], Is.EqualTo(src[1, 1])); 301 | Assert.That(dest[1, 2], Is.EqualTo(src[1, 2])); 302 | } 303 | } 304 | 305 | [Test] 306 | public void CopyToManagedArrayThrowsWhenDifferentSize() 307 | { 308 | using (NativeArray2D src = CreateArray(2, 3)) 309 | { 310 | int[,] dest = new int[2, 4]; 311 | 312 | Assert.That(() => src.CopyTo(dest), Throws.Exception); 313 | } 314 | } 315 | 316 | [Test] 317 | public void CopyToNativeArrayCopiesElements() 318 | { 319 | using (NativeArray2D src = CreateArray(2, 3)) 320 | { 321 | using (NativeArray2D dest = CreateArray(2, 3)) 322 | { 323 | NativeArray2D srcAlias = src; 324 | srcAlias[0, 0] = 100; 325 | srcAlias[0, 1] = 200; 326 | srcAlias[0, 2] = 300; 327 | srcAlias[1, 0] = 400; 328 | srcAlias[1, 1] = 500; 329 | srcAlias[1, 2] = 600; 330 | 331 | src.CopyTo(dest); 332 | 333 | Assert.That(dest[0, 0], Is.EqualTo(src[0, 0])); 334 | Assert.That(dest[0, 1], Is.EqualTo(src[0, 1])); 335 | Assert.That(dest[0, 2], Is.EqualTo(src[0, 2])); 336 | Assert.That(dest[1, 0], Is.EqualTo(src[1, 0])); 337 | Assert.That(dest[1, 1], Is.EqualTo(src[1, 1])); 338 | Assert.That(dest[1, 2], Is.EqualTo(src[1, 2])); 339 | } 340 | } 341 | } 342 | 343 | [Test] 344 | public void CopyToNativeArrayThrowsWhenDifferentSize() 345 | { 346 | using (NativeArray2D src = CreateArray(2, 3)) 347 | { 348 | using (NativeArray2D dest = CreateArray(2, 4)) 349 | { 350 | Assert.That(() => src.CopyTo(dest), Throws.Exception); 351 | } 352 | } 353 | } 354 | 355 | [Test] 356 | public void ToArrayCreatesArrayWithSameElements() 357 | { 358 | using (NativeArray2D src = CreateArray(2, 3)) 359 | { 360 | NativeArray2D srcAlias = src; 361 | srcAlias[0, 0] = 100; 362 | srcAlias[0, 1] = 200; 363 | srcAlias[0, 2] = 300; 364 | srcAlias[1, 0] = 400; 365 | srcAlias[1, 1] = 500; 366 | srcAlias[1, 2] = 600; 367 | 368 | int[,] dest = src.ToArray(); 369 | 370 | Assert.That(dest[0, 0], Is.EqualTo(src[0, 0])); 371 | Assert.That(dest[0, 1], Is.EqualTo(src[0, 1])); 372 | Assert.That(dest[0, 2], Is.EqualTo(src[0, 2])); 373 | Assert.That(dest[1, 0], Is.EqualTo(src[1, 0])); 374 | Assert.That(dest[1, 1], Is.EqualTo(src[1, 1])); 375 | Assert.That(dest[1, 2], Is.EqualTo(src[1, 2])); 376 | } 377 | } 378 | 379 | [Test] 380 | public void GetEnumeratorIteratesElementsInCorrectOrder() 381 | { 382 | using (NativeArray2D array = CreateArray(2, 3)) 383 | { 384 | NativeArray2D alias = array; 385 | alias[0, 0] = 100; 386 | alias[0, 1] = 200; 387 | alias[0, 2] = 300; 388 | alias[1, 0] = 400; 389 | alias[1, 1] = 500; 390 | alias[1, 2] = 600; 391 | 392 | using (NativeArray2D.Enumerator e = array.GetEnumerator()) 393 | { 394 | Assert.That(e.MoveNext(), Is.True); 395 | Assert.That(e.Current, Is.EqualTo(array[0, 0])); 396 | Assert.That(e.MoveNext(), Is.True); 397 | Assert.That(e.Current, Is.EqualTo(array[1, 0])); 398 | Assert.That(e.MoveNext(), Is.True); 399 | Assert.That(e.Current, Is.EqualTo(array[0, 1])); 400 | Assert.That(e.MoveNext(), Is.True); 401 | Assert.That(e.Current, Is.EqualTo(array[1, 1])); 402 | Assert.That(e.MoveNext(), Is.True); 403 | Assert.That(e.Current, Is.EqualTo(array[0, 2])); 404 | Assert.That(e.MoveNext(), Is.True); 405 | Assert.That(e.Current, Is.EqualTo(array[1, 2])); 406 | Assert.That(e.MoveNext(), Is.False); 407 | } 408 | } 409 | } 410 | 411 | [Test] 412 | public void GetEnumeratorNonGenericIteratesElementsInCorrectOrder() 413 | { 414 | using (NativeArray2D array = CreateArray(2, 3)) 415 | { 416 | NativeArray2D alias = array; 417 | alias[0, 0] = 100; 418 | alias[0, 1] = 200; 419 | alias[0, 2] = 300; 420 | alias[1, 0] = 400; 421 | alias[1, 1] = 500; 422 | alias[1, 2] = 600; 423 | 424 | IEnumerator e = ((IEnumerable)array).GetEnumerator(); 425 | Assert.That(e.MoveNext(), Is.True); 426 | Assert.That(e.Current, Is.EqualTo(array[0, 0])); 427 | Assert.That(e.MoveNext(), Is.True); 428 | Assert.That(e.Current, Is.EqualTo(array[1, 0])); 429 | Assert.That(e.MoveNext(), Is.True); 430 | Assert.That(e.Current, Is.EqualTo(array[0, 1])); 431 | Assert.That(e.MoveNext(), Is.True); 432 | Assert.That(e.Current, Is.EqualTo(array[1, 1])); 433 | Assert.That(e.MoveNext(), Is.True); 434 | Assert.That(e.Current, Is.EqualTo(array[0, 2])); 435 | Assert.That(e.MoveNext(), Is.True); 436 | Assert.That(e.Current, Is.EqualTo(array[1, 2])); 437 | Assert.That(e.MoveNext(), Is.False); 438 | } 439 | } 440 | 441 | [Test] 442 | public void EqualsReturnsTrueOnlyForSameArray() 443 | { 444 | using (NativeArray2D a1 = CreateArray(2, 3)) 445 | { 446 | Assert.That(a1.Equals(a1), Is.True); 447 | 448 | using (NativeArray2D a2 = CreateArray(2, 3)) 449 | { 450 | Assert.That(a1.Equals(a2), Is.False); 451 | } 452 | } 453 | } 454 | 455 | [Test] 456 | public void EqualsObjectReturnsTrueOnlyForSameArray() 457 | { 458 | using (NativeArray2D a1 = CreateArray(2, 3)) 459 | { 460 | Assert.That(a1.Equals((object)a1), Is.True); 461 | 462 | using (NativeArray2D a2 = CreateArray(2, 3)) 463 | { 464 | Assert.That(a1.Equals((object)a2), Is.False); 465 | Assert.That(a1.Equals("something else"), Is.False); 466 | } 467 | } 468 | } 469 | 470 | [Test] 471 | public void GetHashCodeReturnsUniqueValue() 472 | { 473 | using (NativeArray2D a1 = CreateArray(2, 3)) 474 | { 475 | using (NativeArray2D a2 = CreateArray(2, 3)) 476 | { 477 | int hash1 = a1.GetHashCode(); 478 | int hash2 = a2.GetHashCode(); 479 | Assert.That(hash1, Is.Not.EqualTo(hash2)); 480 | } 481 | } 482 | } 483 | 484 | [Test] 485 | public void EqualityOperatorReturnsTrueOnlyForSameArray() 486 | { 487 | using (NativeArray2D a1 = CreateArray(2, 3)) 488 | { 489 | // Ignore warning of comparison with self 490 | #pragma warning disable CS1718 491 | Assert.That(a1 == a1, Is.True); 492 | #pragma warning restore CS1718 493 | 494 | using (NativeArray2D a2 = CreateArray(2, 3)) 495 | { 496 | Assert.That(a1 == a2, Is.False); 497 | } 498 | } 499 | } 500 | 501 | [Test] 502 | public void InequalityOperatorReturnsTrueOnlyForDifferentArray() 503 | { 504 | using (NativeArray2D a1 = CreateArray(2, 3)) 505 | { 506 | // Ignore warning of comparison with self 507 | #pragma warning disable CS1718 508 | Assert.That(a1 != a1, Is.False); 509 | #pragma warning restore CS1718 510 | 511 | using (NativeArray2D a2 = CreateArray(2, 3)) 512 | { 513 | Assert.That(a1 != a2, Is.True); 514 | } 515 | } 516 | } 517 | 518 | [Test] 519 | public void EnumeratorResetReturnsToFirstElement() 520 | { 521 | using (NativeArray2D array = CreateArray(2, 3)) 522 | { 523 | NativeArray2D alias = array; 524 | alias[0, 0] = 123; 525 | NativeArray2D.Enumerator e = array.GetEnumerator(); 526 | e.MoveNext(); 527 | 528 | e.Reset(); 529 | 530 | Assert.That(e.MoveNext(), Is.True); 531 | Assert.That(e.Current, Is.EqualTo(123)); 532 | } 533 | } 534 | 535 | [Test] 536 | public void EnumeratorCurrentReturnsCurrentElementAsObject() 537 | { 538 | using (NativeArray2D array = CreateArray(2, 3)) 539 | { 540 | NativeArray2D alias = array; 541 | alias[0, 0] = 123; 542 | NativeArray2D.Enumerator e = array.GetEnumerator(); 543 | e.MoveNext(); 544 | 545 | Assert.That(((IEnumerator)e).Current, Is.EqualTo(123)); 546 | } 547 | } 548 | } 549 | } -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativeArray2D.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2f999fadfa7d43058f471a536f1ab709 3 | timeCreated: 1573436095 -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativeChunkedList.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1ff6184a5635346209193b8b9cad4dc9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativeHashSet.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using NUnit.Framework; 9 | using Unity.Collections; 10 | using Unity.Jobs; 11 | 12 | namespace JacksonDunstan.NativeCollections.Tests 13 | { 14 | /// 15 | /// Unit tests for and 16 | /// . 17 | /// 18 | public class TestNativeHashSet 19 | { 20 | private static NativeHashSet CreateEmptySet() 21 | { 22 | return new NativeHashSet(0, Allocator.TempJob); 23 | } 24 | 25 | private static void AssertRequiresReadOrWriteAccess( 26 | NativeHashSet set, 27 | Action action) 28 | { 29 | set.TestUseOnlySetAllowReadAndWriteAccess(false); 30 | try 31 | { 32 | Assert.That( 33 | () => action(), 34 | Throws.TypeOf()); 35 | } 36 | finally 37 | { 38 | set.TestUseOnlySetAllowReadAndWriteAccess(true); 39 | } 40 | } 41 | 42 | private static void AssertRequiresReadOrWriteAccess( 43 | NativeHashSet.ParallelWriter writer, 44 | Action action) 45 | { 46 | writer.TestUseOnlySetAllowReadAndWriteAccess(false); 47 | try 48 | { 49 | Assert.That( 50 | () => action(), 51 | Throws.TypeOf()); 52 | } 53 | finally 54 | { 55 | writer.TestUseOnlySetAllowReadAndWriteAccess(true); 56 | } 57 | } 58 | 59 | [Test] 60 | public void ConstructorCreatesEmptySet() 61 | { 62 | using (NativeHashSet set = new NativeHashSet(1, Allocator.Temp)) 63 | { 64 | Assert.That(set.Length, Is.EqualTo(0)); 65 | } 66 | } 67 | 68 | [Test] 69 | public void ConstructorClampsToMinimumCapacity() 70 | { 71 | using (NativeHashSet set = new NativeHashSet(1, Allocator.Temp)) 72 | { 73 | Assert.That(set.Capacity, Is.GreaterThan(0)); 74 | } 75 | } 76 | 77 | [Test] 78 | public void ConstructorRequiresValidAllocator() 79 | { 80 | Assert.That( 81 | () => new NativeHashSet(1, default(Allocator)), 82 | Throws.Exception); 83 | } 84 | 85 | #if !CSHARP_7_3_OR_NEWER 86 | private struct NonBlittableType 87 | { 88 | public string Str; 89 | } 90 | 91 | [Test] 92 | public void ConstructorRequiresBlittableType() 93 | { 94 | Assert.That( 95 | () => new NativeHashSet(1, Allocator.Temp), 96 | Throws.Exception); 97 | } 98 | #endif 99 | 100 | [Test] 101 | public void GetLengthRequiresReadAccess() 102 | { 103 | using (NativeHashSet set = CreateEmptySet()) 104 | { 105 | int len; 106 | AssertRequiresReadOrWriteAccess( 107 | set, 108 | () => len = set.Length); 109 | } 110 | } 111 | 112 | [Test] 113 | public void GetCapacityReturnsSetCapacity() 114 | { 115 | using (NativeHashSet set = new NativeHashSet( 116 | 100, 117 | Allocator.Temp)) 118 | { 119 | Assert.That(set.Capacity, Is.EqualTo(100)); 120 | } 121 | } 122 | 123 | [Test] 124 | public void GetCapacityRequiresReadAccess() 125 | { 126 | using (NativeHashSet set = CreateEmptySet()) 127 | { 128 | int cap; 129 | AssertRequiresReadOrWriteAccess( 130 | set, 131 | () => cap = set.Capacity); 132 | } 133 | } 134 | 135 | [Test] 136 | public void SetCapacityRequiresWriteAccess() 137 | { 138 | NativeHashSet set = CreateEmptySet(); 139 | try 140 | { 141 | AssertRequiresReadOrWriteAccess( 142 | set, 143 | () => set.Capacity = 100); 144 | } 145 | finally 146 | { 147 | set.Dispose(); 148 | } 149 | } 150 | 151 | [Test] 152 | public void SetCapacityGrowsCapacity() 153 | { 154 | NativeHashSet set = CreateEmptySet(); 155 | try 156 | { 157 | set.Capacity = 100; 158 | Assert.That(set.Capacity, Is.EqualTo(100)); 159 | } 160 | finally 161 | { 162 | set.Dispose(); 163 | } 164 | } 165 | 166 | [Test] 167 | public void SetCapacityCannotShrinkCapacity() 168 | { 169 | NativeHashSet set = new NativeHashSet(10, Allocator.Temp); 170 | try 171 | { 172 | Assert.That(() => set.Capacity = 1, Throws.Exception); 173 | } 174 | finally 175 | { 176 | set.Dispose(); 177 | } 178 | } 179 | 180 | [Test] 181 | public void TryAddAddsWhenNotPresent() 182 | { 183 | using (NativeHashSet set = CreateEmptySet()) 184 | { 185 | Assert.That(set.TryAdd(1), Is.True); 186 | Assert.That(set.Contains(1), Is.True); 187 | } 188 | } 189 | 190 | [Test] 191 | public void TryAddReturnsFalseWhenPresent() 192 | { 193 | using (NativeHashSet set = CreateEmptySet()) 194 | { 195 | set.TryAdd(1); 196 | 197 | Assert.That(set.TryAdd(1), Is.False); 198 | } 199 | } 200 | 201 | [Test] 202 | public void TryAddGrowsWhenAtCapacity() 203 | { 204 | using (NativeHashSet set = CreateEmptySet()) 205 | { 206 | int originalCapacity = set.Capacity; 207 | for (int i = 0; i < originalCapacity; ++i) 208 | { 209 | set.TryAdd(i); 210 | } 211 | 212 | Assert.That(set.TryAdd(originalCapacity), Is.True); 213 | Assert.That(set.Capacity, Is.GreaterThan(originalCapacity)); 214 | } 215 | } 216 | 217 | [Test] 218 | public void TryAddRequiresWriteAccess() 219 | { 220 | using (NativeHashSet set = CreateEmptySet()) 221 | { 222 | AssertRequiresReadOrWriteAccess( 223 | set, 224 | () => set.TryAdd(1)); 225 | } 226 | } 227 | 228 | [Test] 229 | public void ClearRemovesAllElements() 230 | { 231 | using (NativeHashSet set = CreateEmptySet()) 232 | { 233 | set.TryAdd(1); 234 | 235 | set.Clear(); 236 | 237 | Assert.That(set.Length, Is.EqualTo(0)); 238 | Assert.That(set.Contains(1), Is.False); 239 | } 240 | } 241 | 242 | [Test] 243 | public void ClearRequiresWriteAccess() 244 | { 245 | using (NativeHashSet set = CreateEmptySet()) 246 | { 247 | AssertRequiresReadOrWriteAccess( 248 | set, 249 | () => set.Clear()); 250 | } 251 | } 252 | 253 | [Test] 254 | public void RemoveRemovesContainedElement() 255 | { 256 | using (NativeHashSet set = CreateEmptySet()) 257 | { 258 | set.TryAdd(1); 259 | 260 | Assert.That(set.Remove(1), Is.True); 261 | 262 | Assert.That(set.Length, Is.EqualTo(0)); 263 | Assert.That(set.Contains(1), Is.False); 264 | } 265 | } 266 | 267 | [Test] 268 | public void RemoveReturnsFalseWhenElementIsNotContained() 269 | { 270 | using (NativeHashSet set = CreateEmptySet()) 271 | { 272 | set.TryAdd(1); 273 | 274 | Assert.That(set.Remove(2), Is.False); 275 | 276 | Assert.That(set.Length, Is.EqualTo(1)); 277 | Assert.That(set.Contains(1), Is.True); 278 | } 279 | } 280 | 281 | [Test] 282 | public void RemoveRequiresWriteAccess() 283 | { 284 | using (NativeHashSet set = CreateEmptySet()) 285 | { 286 | AssertRequiresReadOrWriteAccess( 287 | set, 288 | () => set.Remove(0)); 289 | } 290 | } 291 | 292 | [Test] 293 | public void ContainsReturnsTrueForContainedElement() 294 | { 295 | using (NativeHashSet set = CreateEmptySet()) 296 | { 297 | set.TryAdd(1); 298 | 299 | Assert.That(set.Contains(1), Is.True); 300 | } 301 | } 302 | 303 | [Test] 304 | public void ContainsReturnsFalseForNotContainedElement() 305 | { 306 | using (NativeHashSet set = CreateEmptySet()) 307 | { 308 | set.TryAdd(1); 309 | 310 | Assert.That(set.Contains(2), Is.False); 311 | } 312 | } 313 | 314 | [Test] 315 | public void ContainsRequiresReadAccess() 316 | { 317 | using (NativeHashSet set = CreateEmptySet()) 318 | { 319 | AssertRequiresReadOrWriteAccess( 320 | set, 321 | () => set.Contains(0)); 322 | } 323 | } 324 | 325 | [Test] 326 | public void IsCreatedReturnsTrueForDefaultStruct() 327 | { 328 | NativeHashSet set = default(NativeHashSet); 329 | Assert.That(set.IsCreated, Is.False); 330 | } 331 | 332 | [Test] 333 | public void IsCreatedReturnsTrueAfterConstructor() 334 | { 335 | using (NativeHashSet set = CreateEmptySet()) 336 | { 337 | Assert.That(set.IsCreated, Is.True); 338 | } 339 | } 340 | 341 | [Test] 342 | public void OperationsAfterDisposeFail() 343 | { 344 | NativeHashSet set = CreateEmptySet(); 345 | set.Dispose(); 346 | Assert.That( 347 | () => set.Contains(0), 348 | Throws.Exception); 349 | } 350 | 351 | [Test] 352 | public void IsCreatedReturnsFalseAfterDispose() 353 | { 354 | NativeHashSet set = CreateEmptySet(); 355 | set.Dispose(); 356 | Assert.That(set.IsCreated, Is.False); 357 | } 358 | 359 | [Test] 360 | public void DisposeRequiresWriteAccess() 361 | { 362 | using (NativeHashSet set = CreateEmptySet()) 363 | { 364 | AssertRequiresReadOrWriteAccess( 365 | set, 366 | () => set.Dispose()); 367 | } 368 | } 369 | 370 | private struct PreDisposeJob : IJob 371 | { 372 | [WriteOnly] public NativeArray Executed; 373 | 374 | public void Execute() 375 | { 376 | Executed[0] = 1; 377 | } 378 | } 379 | 380 | [Test] 381 | public void DisposeJobDisposesAfterGivenHandle() 382 | { 383 | using (NativeArray executed = new NativeArray( 384 | 1, 385 | Allocator.TempJob)) 386 | { 387 | NativeHashSet set = CreateEmptySet(); 388 | try 389 | { 390 | PreDisposeJob preDisposeJob = new PreDisposeJob 391 | { 392 | Executed = executed 393 | }; 394 | JobHandle preDisposeHandle = preDisposeJob.Schedule(); 395 | 396 | JobHandle disposeHandle = set.Dispose(preDisposeHandle); 397 | disposeHandle.Complete(); 398 | 399 | Assert.That(set.IsCreated, Is.False); 400 | Assert.That(executed[0], Is.EqualTo(1)); 401 | } 402 | finally 403 | { 404 | if (set.IsCreated) 405 | { 406 | set.Dispose(); 407 | } 408 | } 409 | } 410 | } 411 | 412 | [Test] 413 | public void DisposeJobRequiresWriteAccess() 414 | { 415 | using (NativeHashSet set = CreateEmptySet()) 416 | { 417 | AssertRequiresReadOrWriteAccess( 418 | set, 419 | () => set.Dispose(default(JobHandle)).Complete()); 420 | } 421 | } 422 | 423 | [Test] 424 | public void ToNativeArrayCopiesAllElementsToArrayAtGivenIndex() 425 | { 426 | using (NativeArray array = new NativeArray( 427 | 5, 428 | Allocator.TempJob)) 429 | { 430 | using (NativeHashSet set = CreateEmptySet()) 431 | { 432 | set.TryAdd(1); 433 | set.TryAdd(2); 434 | set.TryAdd(3); 435 | 436 | NativeArray toArray = set.ToNativeArray(array, 1); 437 | 438 | // Didn't overwrite out of given bounds 439 | Assert.That(array[0], Is.EqualTo(0)); 440 | Assert.That(array[4], Is.EqualTo(0)); 441 | 442 | // Written values are correct 443 | int[] managedArray = {array[1], array[2], array[3]}; 444 | Array.Sort(managedArray); 445 | Assert.That(managedArray, Is.EqualTo(new[] {1, 2, 3})); 446 | 447 | // Returned array is the same array 448 | // Check by writing to one and reading from the other 449 | toArray[0] = 4; 450 | Assert.That(array[0], Is.EqualTo(4)); 451 | } 452 | } 453 | } 454 | 455 | [Test] 456 | public void ToNativeArrayCopiesAllElementsToNewArrayWhenNotIsCreated() 457 | { 458 | using (NativeHashSet set = CreateEmptySet()) 459 | { 460 | set.TryAdd(1); 461 | set.TryAdd(2); 462 | set.TryAdd(3); 463 | 464 | using (NativeArray array = set.ToNativeArray( 465 | default(NativeArray), 466 | 1)) 467 | { 468 | // Created enough room 469 | Assert.That(array.Length, Is.EqualTo(4)); 470 | 471 | // Didn't overwrite out of given bounds 472 | Assert.That(array[0], Is.EqualTo(0)); 473 | 474 | // Written values are correct 475 | int[] managedArray = {array[1], array[2], array[3]}; 476 | Array.Sort(managedArray); 477 | Assert.That(managedArray, Is.EqualTo(new[] {1, 2, 3})); 478 | } 479 | } 480 | } 481 | 482 | [Test] 483 | public void ToNativeArrayCopiesAllElementsToNewArrayWhenNotLongEnough() 484 | { 485 | using (NativeArray shortArray = new NativeArray( 486 | 2, 487 | Allocator.TempJob)) 488 | { 489 | using (NativeHashSet set = CreateEmptySet()) 490 | { 491 | set.TryAdd(1); 492 | set.TryAdd(2); 493 | set.TryAdd(3); 494 | 495 | using (NativeArray toArray = set.ToNativeArray(shortArray, 1)) 496 | { 497 | // Created enough room 498 | Assert.That(toArray.Length, Is.EqualTo(4)); 499 | 500 | // Didn't overwrite out of given bounds 501 | Assert.That(toArray[0], Is.EqualTo(0)); 502 | 503 | // Written values are correct 504 | int[] managedArray = {toArray[1], toArray[2], toArray[3]}; 505 | Array.Sort(managedArray); 506 | Assert.That(managedArray, Is.EqualTo(new[] {1, 2, 3})); 507 | 508 | // Returned array is a different array 509 | // Check by writing to one and reading from the other 510 | NativeArray toArrayCopy = toArray; 511 | toArrayCopy[0] = 4; 512 | Assert.That(shortArray[0], Is.Not.EqualTo(4)); 513 | } 514 | } 515 | } 516 | } 517 | 518 | [Test] 519 | public void ToNativeArrayRequiresReadAccess() 520 | { 521 | using (NativeArray array = new NativeArray( 522 | 2, 523 | Allocator.TempJob)) 524 | { 525 | using (NativeHashSet set = CreateEmptySet()) 526 | { 527 | set.TryAdd(1); 528 | 529 | AssertRequiresReadOrWriteAccess( 530 | set, 531 | () => set.ToNativeArray(array, 1)); 532 | } 533 | } 534 | } 535 | 536 | [Test] 537 | public void AsParallelWriterReturnsUsableWriter() 538 | { 539 | using (NativeHashSet set = CreateEmptySet()) 540 | { 541 | NativeHashSet.ParallelWriter writer = set.AsParallelWriter(); 542 | 543 | Assert.That(writer.Capacity, Is.EqualTo(set.Capacity)); 544 | 545 | Assert.That(writer.TryAdd(1), Is.True); 546 | 547 | Assert.That(set.Contains(1), Is.True); 548 | } 549 | } 550 | 551 | [Test] 552 | public void ParallelWriterGetCapacityRequiresReadAccess() 553 | { 554 | using (NativeHashSet set = CreateEmptySet()) 555 | { 556 | NativeHashSet.ParallelWriter writer = set.AsParallelWriter(); 557 | 558 | int cap; 559 | AssertRequiresReadOrWriteAccess( 560 | writer, 561 | () => cap = writer.Capacity); 562 | } 563 | } 564 | 565 | [Test] 566 | public void ParallelWriterTryAddRequiresWriteAccess() 567 | { 568 | using (NativeHashSet set = CreateEmptySet()) 569 | { 570 | NativeHashSet.ParallelWriter writer = set.AsParallelWriter(); 571 | 572 | AssertRequiresReadOrWriteAccess( 573 | writer, 574 | () => writer.TryAdd(1)); 575 | } 576 | } 577 | 578 | struct ParallelWriterJob : IJobParallelFor 579 | { 580 | [ReadOnly] public NativeArray Array; 581 | [WriteOnly] public NativeHashSet.ParallelWriter Writer; 582 | 583 | public void Execute(int index) 584 | { 585 | Writer.TryAdd(Array[index]); 586 | } 587 | } 588 | 589 | [Test] 590 | public void AsParallelWriterReturnsUsableWriterInJob() 591 | { 592 | using (NativeHashSet set = CreateEmptySet()) 593 | { 594 | using (NativeArray array = new NativeArray( 595 | 2, 596 | Allocator.TempJob)) 597 | { 598 | NativeArray arrayRef = array; 599 | arrayRef[0] = 1; 600 | arrayRef[1] = 2; 601 | 602 | ParallelWriterJob job = new ParallelWriterJob 603 | { 604 | Array = array, 605 | Writer = set.AsParallelWriter() 606 | }; 607 | 608 | JobHandle handle = job.Schedule(array.Length, 64); 609 | handle.Complete(); 610 | 611 | Assert.That(set.Length, Is.EqualTo(2)); 612 | } 613 | } 614 | } 615 | } 616 | } -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativeHashSet.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b722688e34a6473fa86dac967f41581b 3 | timeCreated: 1566774128 -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativeIntPtr.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Unity.Collections; 9 | using Unity.Jobs; 10 | using NUnit.Framework; 11 | 12 | namespace JacksonDunstan.NativeCollections.Tests 13 | { 14 | /// 15 | /// Unit tests for 16 | /// 17 | public class TestNativeIntPtr 18 | { 19 | private static void AssertRequiresReadOrWriteAccess( 20 | NativeIntPtr intPtr, 21 | Action action) 22 | { 23 | intPtr.TestUseOnlySetAllowReadAndWriteAccess(false); 24 | try 25 | { 26 | Assert.That( 27 | () => action(), 28 | Throws.TypeOf()); 29 | } 30 | finally 31 | { 32 | intPtr.TestUseOnlySetAllowReadAndWriteAccess(true); 33 | } 34 | } 35 | 36 | [Test] 37 | public void ConstructorDefaultsValueToZero() 38 | { 39 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp)) 40 | { 41 | Assert.That(intPtr.Value, Is.EqualTo(0)); 42 | } 43 | } 44 | 45 | [Test] 46 | public void ConstructorSetsInitialValue() 47 | { 48 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp, 123)) 49 | { 50 | Assert.That(intPtr.Value, Is.EqualTo(123)); 51 | } 52 | } 53 | 54 | [Test] 55 | public void ConstructorThrowsExceptionForInvalidAllocator() 56 | { 57 | Assert.That( 58 | () => new NativeIntPtr(Allocator.None), 59 | Throws.TypeOf()); 60 | } 61 | 62 | [Test] 63 | public void GetValueReturnsWhatSetValueSets() 64 | { 65 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp)) 66 | { 67 | NativeIntPtr copy = intPtr; 68 | copy.Value = 123; 69 | 70 | Assert.That(intPtr.Value, Is.EqualTo(123)); 71 | } 72 | } 73 | 74 | [Test] 75 | public void GetValueRequiresReadAccess() 76 | { 77 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp)) 78 | { 79 | int value; 80 | AssertRequiresReadOrWriteAccess( 81 | intPtr, 82 | () => value = intPtr.Value); 83 | } 84 | } 85 | 86 | [Test] 87 | public void SetValueRequiresReadAccess() 88 | { 89 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp)) 90 | { 91 | NativeIntPtr copy = intPtr; 92 | AssertRequiresReadOrWriteAccess( 93 | intPtr, 94 | () => copy.Value = 123); 95 | } 96 | } 97 | 98 | [Test] 99 | public void ParallelIncrementIncrementsValue() 100 | { 101 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp, 123)) 102 | { 103 | NativeIntPtr.Parallel parallel = intPtr.GetParallel(); 104 | parallel.Increment(); 105 | 106 | Assert.That(intPtr.Value, Is.EqualTo(124)); 107 | } 108 | } 109 | 110 | [Test] 111 | public void ParallelIncrementRequiresReadAccess() 112 | { 113 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp)) 114 | { 115 | NativeIntPtr.Parallel parallel = intPtr.GetParallel(); 116 | AssertRequiresReadOrWriteAccess( 117 | intPtr, 118 | parallel.Increment); 119 | } 120 | } 121 | 122 | [Test] 123 | public void ParallelDecrementIncrementsValue() 124 | { 125 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp, 123)) 126 | { 127 | NativeIntPtr.Parallel parallel = intPtr.GetParallel(); 128 | parallel.Decrement(); 129 | 130 | Assert.That(intPtr.Value, Is.EqualTo(122)); 131 | } 132 | } 133 | 134 | [Test] 135 | public void ParallelDecrementRequiresReadAccess() 136 | { 137 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp)) 138 | { 139 | NativeIntPtr.Parallel parallel = intPtr.GetParallel(); 140 | AssertRequiresReadOrWriteAccess( 141 | intPtr, 142 | parallel.Decrement); 143 | } 144 | } 145 | 146 | [Test] 147 | public void ParallelAddOffsetsValue() 148 | { 149 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp, 123)) 150 | { 151 | NativeIntPtr.Parallel parallel = intPtr.GetParallel(); 152 | parallel.Add(5); 153 | 154 | Assert.That(intPtr.Value, Is.EqualTo(128)); 155 | 156 | parallel.Add(-15); 157 | 158 | Assert.That(intPtr.Value, Is.EqualTo(113)); 159 | } 160 | } 161 | 162 | [Test] 163 | public void ParallelAddRequiresReadAccess() 164 | { 165 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp)) 166 | { 167 | NativeIntPtr.Parallel parallel = intPtr.GetParallel(); 168 | AssertRequiresReadOrWriteAccess( 169 | intPtr, 170 | () => parallel.Add(10)); 171 | } 172 | } 173 | 174 | [Test] 175 | public void DisposeMakesUnusable() 176 | { 177 | NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp); 178 | intPtr.Dispose(); 179 | Assert.That( 180 | () => intPtr.Value = 10, 181 | Throws.Exception); 182 | } 183 | 184 | [Test] 185 | public void DisposeRequiresReadAccess() 186 | { 187 | using (NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp)) 188 | { 189 | AssertRequiresReadOrWriteAccess( 190 | intPtr, 191 | intPtr.Dispose); 192 | } 193 | } 194 | 195 | [Test] 196 | public void IsCreatedOnlyReturnsTrueBeforeDispose() 197 | { 198 | NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp); 199 | Assert.That(intPtr.IsCreated, Is.True); 200 | 201 | intPtr.Dispose(); 202 | 203 | Assert.That(intPtr.IsCreated, Is.False); 204 | } 205 | 206 | private struct ParallelForTestJob : IJobParallelFor 207 | { 208 | public NativeArray Array; 209 | public NativeIntPtr.Parallel Sum; 210 | 211 | public void Execute(int index) 212 | { 213 | Sum.Add(Array[index]); 214 | } 215 | } 216 | 217 | [Test] 218 | public void ParallelForJobCanUseParallelPtr() 219 | { 220 | using (NativeArray array = new NativeArray( 221 | 3, 222 | Allocator.TempJob)) 223 | { 224 | NativeArray arrayCopy = array; 225 | arrayCopy[0] = 10; 226 | arrayCopy[1] = 20; 227 | arrayCopy[2] = 30; 228 | 229 | using (NativeIntPtr sum = new NativeIntPtr( 230 | Allocator.TempJob)) 231 | { 232 | ParallelForTestJob job = new ParallelForTestJob 233 | { 234 | Array = array, 235 | Sum = sum.GetParallel() 236 | }; 237 | job.Run(array.Length); 238 | 239 | Assert.That(sum.Value, Is.EqualTo(60)); 240 | } 241 | } 242 | } 243 | 244 | private struct DeallocateOnJobCompletionJob : IJob 245 | { 246 | [DeallocateOnJobCompletion] 247 | public NativeIntPtr IntPtr; 248 | 249 | public void Execute() 250 | { 251 | } 252 | } 253 | 254 | [Test] 255 | public void CanDeallocateOnJobCompletion() 256 | { 257 | NativeIntPtr intPtr = new NativeIntPtr(Allocator.TempJob); 258 | var job = new DeallocateOnJobCompletionJob { IntPtr = intPtr }; 259 | job.Run(); 260 | 261 | Assert.That( 262 | () => intPtr.Value = 10, 263 | Throws.Exception); 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativeIntPtr.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ef57e2b4fd4614d438ee9832dd522742 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativeLinkedList.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0fa8eeb747ea1413797873e063bdd8dc 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativeLongPtr.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Unity.Collections; 9 | using Unity.Jobs; 10 | using NUnit.Framework; 11 | 12 | namespace JacksonDunstan.NativeCollections.Tests 13 | { 14 | /// 15 | /// Unit tests for 16 | /// 17 | public class TestNativeLongPtr 18 | { 19 | private static void AssertRequiresReadOrWriteAccess( 20 | NativeLongPtr intPtr, 21 | Action action) 22 | { 23 | intPtr.TestUseOnlySetAllowReadAndWriteAccess(false); 24 | try 25 | { 26 | Assert.That( 27 | () => action(), 28 | Throws.TypeOf()); 29 | } 30 | finally 31 | { 32 | intPtr.TestUseOnlySetAllowReadAndWriteAccess(true); 33 | } 34 | } 35 | 36 | [Test] 37 | public void ConstructorDefaultsValueToZero() 38 | { 39 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp)) 40 | { 41 | Assert.That(intPtr.Value, Is.EqualTo(0)); 42 | } 43 | } 44 | 45 | [Test] 46 | public void ConstructorSetsInitialValue() 47 | { 48 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp, 123)) 49 | { 50 | Assert.That(intPtr.Value, Is.EqualTo(123)); 51 | } 52 | } 53 | 54 | [Test] 55 | public void ConstructorThrowsExceptionForInvalidAllocator() 56 | { 57 | Assert.That( 58 | () => new NativeLongPtr(Allocator.None), 59 | Throws.TypeOf()); 60 | } 61 | 62 | [Test] 63 | public void GetValueReturnsWhatSetValueSets() 64 | { 65 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp)) 66 | { 67 | NativeLongPtr copy = intPtr; 68 | copy.Value = 123; 69 | 70 | Assert.That(intPtr.Value, Is.EqualTo(123)); 71 | } 72 | } 73 | 74 | [Test] 75 | public void GetValueRequiresReadAccess() 76 | { 77 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp)) 78 | { 79 | long value; 80 | AssertRequiresReadOrWriteAccess( 81 | intPtr, 82 | () => value = intPtr.Value); 83 | } 84 | } 85 | 86 | [Test] 87 | public void SetValueRequiresReadAccess() 88 | { 89 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp)) 90 | { 91 | NativeLongPtr copy = intPtr; 92 | AssertRequiresReadOrWriteAccess( 93 | intPtr, 94 | () => copy.Value = 123); 95 | } 96 | } 97 | 98 | [Test] 99 | public void ParallelIncrementIncrementsValue() 100 | { 101 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp, 123)) 102 | { 103 | NativeLongPtr.Parallel parallel = intPtr.GetParallel(); 104 | parallel.Increment(); 105 | 106 | Assert.That(intPtr.Value, Is.EqualTo(124)); 107 | } 108 | } 109 | 110 | [Test] 111 | public void ParallelIncrementRequiresReadAccess() 112 | { 113 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp)) 114 | { 115 | NativeLongPtr.Parallel parallel = intPtr.GetParallel(); 116 | AssertRequiresReadOrWriteAccess( 117 | intPtr, 118 | parallel.Increment); 119 | } 120 | } 121 | 122 | [Test] 123 | public void ParallelDecrementIncrementsValue() 124 | { 125 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp, 123)) 126 | { 127 | NativeLongPtr.Parallel parallel = intPtr.GetParallel(); 128 | parallel.Decrement(); 129 | 130 | Assert.That(intPtr.Value, Is.EqualTo(122)); 131 | } 132 | } 133 | 134 | [Test] 135 | public void ParallelDecrementRequiresReadAccess() 136 | { 137 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp)) 138 | { 139 | NativeLongPtr.Parallel parallel = intPtr.GetParallel(); 140 | AssertRequiresReadOrWriteAccess( 141 | intPtr, 142 | parallel.Decrement); 143 | } 144 | } 145 | 146 | [Test] 147 | public void ParallelAddOffsetsValue() 148 | { 149 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp, 123)) 150 | { 151 | NativeLongPtr.Parallel parallel = intPtr.GetParallel(); 152 | parallel.Add(5); 153 | 154 | Assert.That(intPtr.Value, Is.EqualTo(128)); 155 | 156 | parallel.Add(-15); 157 | 158 | Assert.That(intPtr.Value, Is.EqualTo(113)); 159 | } 160 | } 161 | 162 | [Test] 163 | public void ParallelAddRequiresReadAccess() 164 | { 165 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp)) 166 | { 167 | NativeLongPtr.Parallel parallel = intPtr.GetParallel(); 168 | AssertRequiresReadOrWriteAccess( 169 | intPtr, 170 | () => parallel.Add(10)); 171 | } 172 | } 173 | 174 | [Test] 175 | public void DisposeMakesUnusable() 176 | { 177 | NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp); 178 | intPtr.Dispose(); 179 | Assert.That( 180 | () => intPtr.Value = 10, 181 | Throws.Exception); 182 | } 183 | 184 | [Test] 185 | public void DisposeRequiresReadAccess() 186 | { 187 | using (NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp)) 188 | { 189 | AssertRequiresReadOrWriteAccess( 190 | intPtr, 191 | intPtr.Dispose); 192 | } 193 | } 194 | 195 | [Test] 196 | public void IsCreatedOnlyReturnsTrueBeforeDispose() 197 | { 198 | NativeLongPtr intPtr = new NativeLongPtr(Allocator.Temp); 199 | Assert.That(intPtr.IsCreated, Is.True); 200 | 201 | intPtr.Dispose(); 202 | 203 | Assert.That(intPtr.IsCreated, Is.False); 204 | } 205 | 206 | private struct ParallelForTestJob : IJobParallelFor 207 | { 208 | public NativeArray Array; 209 | public NativeLongPtr.Parallel Sum; 210 | 211 | public void Execute(int index) 212 | { 213 | Sum.Add(Array[index]); 214 | } 215 | } 216 | 217 | [Test] 218 | public void ParallelForJobCanUseParallelPtr() 219 | { 220 | using (NativeArray array = new NativeArray( 221 | 3, 222 | Allocator.TempJob)) 223 | { 224 | NativeArray arrayCopy = array; 225 | arrayCopy[0] = 10; 226 | arrayCopy[1] = 20; 227 | arrayCopy[2] = 30; 228 | 229 | using (NativeLongPtr sum = new NativeLongPtr( 230 | Allocator.TempJob)) 231 | { 232 | ParallelForTestJob job = new ParallelForTestJob 233 | { 234 | Array = array, 235 | Sum = sum.GetParallel() 236 | }; 237 | job.Run(array.Length); 238 | 239 | Assert.That(sum.Value, Is.EqualTo(60)); 240 | } 241 | } 242 | } 243 | 244 | private struct DeallocateOnJobCompletionJob : IJob 245 | { 246 | [DeallocateOnJobCompletion] 247 | public NativeLongPtr LongPtr; 248 | 249 | public void Execute() 250 | { 251 | } 252 | } 253 | 254 | [Test] 255 | public void CanDeallocateOnJobCompletion() 256 | { 257 | NativeLongPtr intPtr = new NativeLongPtr(Allocator.TempJob); 258 | var job = new DeallocateOnJobCompletionJob { LongPtr = intPtr }; 259 | job.Run(); 260 | 261 | Assert.That( 262 | () => intPtr.Value = 10, 263 | Throws.Exception); 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativeLongPtr.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: adcf3de91cdc1499c8ee8d446b4fd402 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativePerJobThreadIntPtr.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Unity.Collections; 9 | using Unity.Jobs; 10 | using NUnit.Framework; 11 | 12 | namespace JacksonDunstan.NativeCollections.Tests 13 | { 14 | /// 15 | /// Unit tests for 16 | /// 17 | public class TestNativePerJobThreadIntPtr 18 | { 19 | private static void AssertRequiresReadOrWriteAccess( 20 | NativePerJobThreadIntPtr intPtr, 21 | Action action) 22 | { 23 | intPtr.TestUseOnlySetAllowReadAndWriteAccess(false); 24 | try 25 | { 26 | Assert.That( 27 | () => action(), 28 | Throws.TypeOf()); 29 | } 30 | finally 31 | { 32 | intPtr.TestUseOnlySetAllowReadAndWriteAccess(true); 33 | } 34 | } 35 | 36 | [Test] 37 | public void ConstructorDefaultsValueToZero() 38 | { 39 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 40 | Allocator.Temp)) 41 | { 42 | Assert.That(intPtr.Value, Is.EqualTo(0)); 43 | } 44 | } 45 | 46 | [Test] 47 | public void ConstructorSetsInitialValue() 48 | { 49 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 50 | Allocator.Temp, 123)) 51 | { 52 | Assert.That(intPtr.Value, Is.EqualTo(123)); 53 | } 54 | } 55 | 56 | [Test] 57 | public void ConstructorThrowsExceptionForInvalidAllocator() 58 | { 59 | Assert.That( 60 | () => new NativePerJobThreadIntPtr(Allocator.None), 61 | Throws.TypeOf()); 62 | } 63 | 64 | [Test] 65 | public void GetValueReturnsWhatSetValueSets() 66 | { 67 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 68 | Allocator.Temp)) 69 | { 70 | NativePerJobThreadIntPtr copy = intPtr; 71 | copy.Value = 123; 72 | 73 | Assert.That(intPtr.Value, Is.EqualTo(123)); 74 | } 75 | } 76 | 77 | [Test] 78 | public void GetValueRequiresReadAccess() 79 | { 80 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 81 | Allocator.Temp)) 82 | { 83 | int value; 84 | AssertRequiresReadOrWriteAccess( 85 | intPtr, 86 | () => value = intPtr.Value); 87 | } 88 | } 89 | 90 | [Test] 91 | public void SetValueRequiresReadAccess() 92 | { 93 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 94 | Allocator.Temp)) 95 | { 96 | NativePerJobThreadIntPtr copy = intPtr; 97 | AssertRequiresReadOrWriteAccess( 98 | intPtr, 99 | () => copy.Value = 123); 100 | } 101 | } 102 | 103 | [Test] 104 | public void ParallelIncrementIncrementsValue() 105 | { 106 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 107 | Allocator.Temp, 108 | 123)) 109 | { 110 | NativePerJobThreadIntPtr.Parallel parallel = intPtr.GetParallel(); 111 | parallel.Increment(); 112 | 113 | Assert.That(intPtr.Value, Is.EqualTo(124)); 114 | } 115 | } 116 | 117 | [Test] 118 | public void ParallelIncrementRequiresReadAccess() 119 | { 120 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 121 | Allocator.Temp)) 122 | { 123 | NativePerJobThreadIntPtr.Parallel parallel = intPtr.GetParallel(); 124 | AssertRequiresReadOrWriteAccess( 125 | intPtr, 126 | parallel.Increment); 127 | } 128 | } 129 | 130 | [Test] 131 | public void ParallelDecrementIncrementsValue() 132 | { 133 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 134 | Allocator.Temp, 135 | 123)) 136 | { 137 | NativePerJobThreadIntPtr.Parallel parallel = intPtr.GetParallel(); 138 | parallel.Decrement(); 139 | 140 | Assert.That(intPtr.Value, Is.EqualTo(122)); 141 | } 142 | } 143 | 144 | [Test] 145 | public void ParallelDecrementRequiresReadAccess() 146 | { 147 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 148 | Allocator.Temp)) 149 | { 150 | NativePerJobThreadIntPtr.Parallel parallel = intPtr.GetParallel(); 151 | AssertRequiresReadOrWriteAccess( 152 | intPtr, 153 | parallel.Decrement); 154 | } 155 | } 156 | 157 | [Test] 158 | public void ParallelAddOffsetsValue() 159 | { 160 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 161 | Allocator.Temp, 162 | 123)) 163 | { 164 | NativePerJobThreadIntPtr.Parallel parallel = intPtr.GetParallel(); 165 | parallel.Add(5); 166 | 167 | Assert.That(intPtr.Value, Is.EqualTo(128)); 168 | 169 | parallel.Add(-15); 170 | 171 | Assert.That(intPtr.Value, Is.EqualTo(113)); 172 | } 173 | } 174 | 175 | [Test] 176 | public void ParallelAddRequiresReadAccess() 177 | { 178 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 179 | Allocator.Temp)) 180 | { 181 | NativePerJobThreadIntPtr.Parallel parallel = intPtr.GetParallel(); 182 | AssertRequiresReadOrWriteAccess( 183 | intPtr, 184 | () => parallel.Add(10)); 185 | } 186 | } 187 | 188 | [Test] 189 | public void DisposeMakesUnusable() 190 | { 191 | NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 192 | Allocator.Temp); 193 | intPtr.Dispose(); 194 | Assert.That( 195 | () => intPtr.Value = 10, 196 | Throws.Exception); 197 | } 198 | 199 | [Test] 200 | public void DisposeRequiresReadAccess() 201 | { 202 | using (NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 203 | Allocator.Temp)) 204 | { 205 | AssertRequiresReadOrWriteAccess( 206 | intPtr, 207 | intPtr.Dispose); 208 | } 209 | } 210 | 211 | [Test] 212 | public void IsCreatedOnlyReturnsTrueBeforeDispose() 213 | { 214 | NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 215 | Allocator.Temp); 216 | Assert.That(intPtr.IsCreated, Is.True); 217 | 218 | intPtr.Dispose(); 219 | 220 | Assert.That(intPtr.IsCreated, Is.False); 221 | } 222 | 223 | private struct ParallelForTestJob : IJobParallelFor 224 | { 225 | public NativeArray Array; 226 | public NativePerJobThreadIntPtr.Parallel Sum; 227 | 228 | public void Execute(int index) 229 | { 230 | Sum.Add(Array[index]); 231 | } 232 | } 233 | 234 | [Test] 235 | public void ParallelForJobCanUseParallelPtr() 236 | { 237 | using (NativeArray array = new NativeArray( 238 | 3, 239 | Allocator.TempJob)) 240 | { 241 | NativeArray arrayCopy = array; 242 | arrayCopy[0] = 10; 243 | arrayCopy[1] = 20; 244 | arrayCopy[2] = 30; 245 | 246 | using (NativePerJobThreadIntPtr sum = new NativePerJobThreadIntPtr( 247 | Allocator.TempJob)) 248 | { 249 | ParallelForTestJob job = new ParallelForTestJob 250 | { 251 | Array = array, 252 | Sum = sum.GetParallel() 253 | }; 254 | job.Run(array.Length); 255 | 256 | Assert.That(sum.Value, Is.EqualTo(60)); 257 | } 258 | } 259 | } 260 | 261 | private struct DeallocateOnJobCompletionJob : IJob 262 | { 263 | [DeallocateOnJobCompletion] 264 | public NativePerJobThreadIntPtr IntPtr; 265 | 266 | public void Execute() 267 | { 268 | } 269 | } 270 | 271 | [Test] 272 | public void CanDeallocateOnJobCompletion() 273 | { 274 | NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr( 275 | Allocator.TempJob); 276 | var job = new DeallocateOnJobCompletionJob { IntPtr = intPtr }; 277 | job.Run(); 278 | 279 | Assert.That( 280 | () => intPtr.Value = 10, 281 | Throws.Exception); 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativePerJobThreadIntPtr.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3a69b3fed952344689bb907430f8bd40 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativePerJobThreadLongPtr.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Unity.Collections; 9 | using Unity.Jobs; 10 | using NUnit.Framework; 11 | 12 | namespace JacksonDunstan.NativeCollections.Tests 13 | { 14 | /// 15 | /// Unit tests for 16 | /// 17 | public class TestNativePerJobThreadLongPtr 18 | { 19 | private static void AssertRequiresReadOrWriteAccess( 20 | NativePerJobThreadLongPtr longPtr, 21 | Action action) 22 | { 23 | longPtr.TestUseOnlySetAllowReadAndWriteAccess(false); 24 | try 25 | { 26 | Assert.That( 27 | () => action(), 28 | Throws.TypeOf()); 29 | } 30 | finally 31 | { 32 | longPtr.TestUseOnlySetAllowReadAndWriteAccess(true); 33 | } 34 | } 35 | 36 | [Test] 37 | public void ConstructorDefaultsValueToZero() 38 | { 39 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 40 | Allocator.Temp)) 41 | { 42 | Assert.That(longPtr.Value, Is.EqualTo(0)); 43 | } 44 | } 45 | 46 | [Test] 47 | public void ConstructorSetsInitialValue() 48 | { 49 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 50 | Allocator.Temp, 123)) 51 | { 52 | Assert.That(longPtr.Value, Is.EqualTo(123)); 53 | } 54 | } 55 | 56 | [Test] 57 | public void ConstructorThrowsExceptionForInvalidAllocator() 58 | { 59 | Assert.That( 60 | () => new NativePerJobThreadLongPtr(Allocator.None), 61 | Throws.TypeOf()); 62 | } 63 | 64 | [Test] 65 | public void GetValueReturnsWhatSetValueSets() 66 | { 67 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 68 | Allocator.Temp)) 69 | { 70 | NativePerJobThreadLongPtr copy = longPtr; 71 | copy.Value = 123; 72 | 73 | Assert.That(longPtr.Value, Is.EqualTo(123)); 74 | } 75 | } 76 | 77 | [Test] 78 | public void GetValueRequiresReadAccess() 79 | { 80 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 81 | Allocator.Temp)) 82 | { 83 | long value; 84 | AssertRequiresReadOrWriteAccess( 85 | longPtr, 86 | () => value = longPtr.Value); 87 | } 88 | } 89 | 90 | [Test] 91 | public void SetValueRequiresReadAccess() 92 | { 93 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 94 | Allocator.Temp)) 95 | { 96 | NativePerJobThreadLongPtr copy = longPtr; 97 | AssertRequiresReadOrWriteAccess( 98 | longPtr, 99 | () => copy.Value = 123); 100 | } 101 | } 102 | 103 | [Test] 104 | public void ParallelIncrementIncrementsValue() 105 | { 106 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 107 | Allocator.Temp, 108 | 123)) 109 | { 110 | NativePerJobThreadLongPtr.Parallel parallel = longPtr.GetParallel(); 111 | parallel.Increment(); 112 | 113 | Assert.That(longPtr.Value, Is.EqualTo(124)); 114 | } 115 | } 116 | 117 | [Test] 118 | public void ParallelIncrementRequiresReadAccess() 119 | { 120 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 121 | Allocator.Temp)) 122 | { 123 | NativePerJobThreadLongPtr.Parallel parallel = longPtr.GetParallel(); 124 | AssertRequiresReadOrWriteAccess( 125 | longPtr, 126 | parallel.Increment); 127 | } 128 | } 129 | 130 | [Test] 131 | public void ParallelDecrementIncrementsValue() 132 | { 133 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 134 | Allocator.Temp, 135 | 123)) 136 | { 137 | NativePerJobThreadLongPtr.Parallel parallel = longPtr.GetParallel(); 138 | parallel.Decrement(); 139 | 140 | Assert.That(longPtr.Value, Is.EqualTo(122)); 141 | } 142 | } 143 | 144 | [Test] 145 | public void ParallelDecrementRequiresReadAccess() 146 | { 147 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 148 | Allocator.Temp)) 149 | { 150 | NativePerJobThreadLongPtr.Parallel parallel = longPtr.GetParallel(); 151 | AssertRequiresReadOrWriteAccess( 152 | longPtr, 153 | parallel.Decrement); 154 | } 155 | } 156 | 157 | [Test] 158 | public void ParallelAddOffsetsValue() 159 | { 160 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 161 | Allocator.Temp, 162 | 123)) 163 | { 164 | NativePerJobThreadLongPtr.Parallel parallel = longPtr.GetParallel(); 165 | parallel.Add(5); 166 | 167 | Assert.That(longPtr.Value, Is.EqualTo(128)); 168 | 169 | parallel.Add(-15); 170 | 171 | Assert.That(longPtr.Value, Is.EqualTo(113)); 172 | } 173 | } 174 | 175 | [Test] 176 | public void ParallelAddRequiresReadAccess() 177 | { 178 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 179 | Allocator.Temp)) 180 | { 181 | NativePerJobThreadLongPtr.Parallel parallel = longPtr.GetParallel(); 182 | AssertRequiresReadOrWriteAccess( 183 | longPtr, 184 | () => parallel.Add(10)); 185 | } 186 | } 187 | 188 | [Test] 189 | public void DisposeMakesUnusable() 190 | { 191 | NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 192 | Allocator.Temp); 193 | longPtr.Dispose(); 194 | Assert.That( 195 | () => longPtr.Value = 10, 196 | Throws.Exception); 197 | } 198 | 199 | [Test] 200 | public void DisposeRequiresReadAccess() 201 | { 202 | using (NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 203 | Allocator.Temp)) 204 | { 205 | AssertRequiresReadOrWriteAccess( 206 | longPtr, 207 | longPtr.Dispose); 208 | } 209 | } 210 | 211 | [Test] 212 | public void IsCreatedOnlyReturnsTrueBeforeDispose() 213 | { 214 | NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 215 | Allocator.Temp); 216 | Assert.That(longPtr.IsCreated, Is.True); 217 | 218 | longPtr.Dispose(); 219 | 220 | Assert.That(longPtr.IsCreated, Is.False); 221 | } 222 | 223 | private struct ParallelForTestJob : IJobParallelFor 224 | { 225 | public NativeArray Array; 226 | public NativePerJobThreadLongPtr.Parallel Sum; 227 | 228 | public void Execute(int index) 229 | { 230 | Sum.Add(Array[index]); 231 | } 232 | } 233 | 234 | [Test] 235 | public void ParallelForJobCanUseParallelPtr() 236 | { 237 | using (NativeArray array = new NativeArray( 238 | 3, 239 | Allocator.TempJob)) 240 | { 241 | NativeArray arrayCopy = array; 242 | arrayCopy[0] = 10; 243 | arrayCopy[1] = 20; 244 | arrayCopy[2] = 30; 245 | 246 | using (NativePerJobThreadLongPtr sum = new NativePerJobThreadLongPtr( 247 | Allocator.TempJob)) 248 | { 249 | ParallelForTestJob job = new ParallelForTestJob 250 | { 251 | Array = array, 252 | Sum = sum.GetParallel() 253 | }; 254 | job.Run(array.Length); 255 | 256 | Assert.That(sum.Value, Is.EqualTo(60)); 257 | } 258 | } 259 | } 260 | 261 | private struct DeallocateOnJobCompletionJob : IJob 262 | { 263 | [DeallocateOnJobCompletion] 264 | public NativePerJobThreadLongPtr LongPtr; 265 | 266 | public void Execute() 267 | { 268 | } 269 | } 270 | 271 | [Test] 272 | public void CanDeallocateOnJobCompletion() 273 | { 274 | NativePerJobThreadLongPtr longPtr = new NativePerJobThreadLongPtr( 275 | Allocator.TempJob); 276 | var job = new DeallocateOnJobCompletionJob { LongPtr = longPtr }; 277 | job.Run(); 278 | 279 | Assert.That( 280 | () => longPtr.Value = 10, 281 | Throws.Exception); 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestNativePerJobThreadLongPtr.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5dcf55ce40caa4b3d9fae5f17a75a842 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestSharedDisposable.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (c) Jackson Dunstan. See LICENSE.md. 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Unity.Collections; 9 | using Unity.Jobs; 10 | using NUnit.Framework; 11 | 12 | namespace JacksonDunstan.NativeCollections.Tests 13 | { 14 | /// 15 | /// Unit tests for 16 | /// 17 | public class TestSharedDisposable 18 | { 19 | private struct DisposeCallCounter : IDisposable 20 | { 21 | private static int NextId = 1; 22 | public int Id; 23 | 24 | public NativeArray Num; 25 | 26 | public void AssignUniqueId() 27 | { 28 | Id = NextId++; 29 | } 30 | 31 | public void Dispose() 32 | { 33 | Num[0] = Num[0] + 1; 34 | } 35 | } 36 | 37 | private struct TestDisposable : IDisposable 38 | { 39 | public DisposeCallCounter Counter; 40 | 41 | public static TestDisposable Create() 42 | { 43 | return new TestDisposable 44 | { 45 | Counter = 46 | { 47 | Num = new NativeArray( 48 | 1, 49 | Allocator.TempJob) 50 | } 51 | }; 52 | } 53 | 54 | public void Dispose() 55 | { 56 | Counter.Num.Dispose(); 57 | } 58 | } 59 | 60 | private static void AssertRequiresReadOrWriteAccess( 61 | SharedDisposable shared, 62 | Action action) 63 | { 64 | shared.TestUseOnlySetAllowReadAndWriteAccess(false); 65 | try 66 | { 67 | Assert.That( 68 | () => action(), 69 | Throws.TypeOf()); 70 | } 71 | finally 72 | { 73 | shared.TestUseOnlySetAllowReadAndWriteAccess(true); 74 | } 75 | } 76 | 77 | [Test] 78 | public void ConstructorUsesGivenDisposable() 79 | { 80 | using (TestDisposable disposable = TestDisposable.Create()) 81 | { 82 | disposable.Counter.AssignUniqueId(); 83 | using (var shared = new SharedDisposable( 84 | disposable.Counter, 85 | Allocator.TempJob)) 86 | { 87 | Assert.That(shared.Value.Id, Is.EqualTo(disposable.Counter.Id)); 88 | } 89 | } 90 | } 91 | 92 | [Test] 93 | public void ShareExtensionUsesGivenDisposable() 94 | { 95 | using (TestDisposable disposable = TestDisposable.Create()) 96 | { 97 | disposable.Counter.AssignUniqueId(); 98 | using (var shared = disposable.Counter.Share(Allocator.TempJob)) 99 | { 100 | Assert.That(shared.Value.Id, Is.EqualTo(disposable.Counter.Id)); 101 | } 102 | } 103 | } 104 | 105 | [Test] 106 | public void ConstructorThrowsExceptionForInvalidAllocator() 107 | { 108 | using (TestDisposable disposable = TestDisposable.Create()) 109 | { 110 | Assert.That( 111 | () => new SharedDisposable( 112 | disposable.Counter, 113 | Allocator.None), 114 | Throws.TypeOf()); 115 | } 116 | } 117 | 118 | [Test] 119 | public void GetValueRequiresReadAccess() 120 | { 121 | using (TestDisposable disposable = TestDisposable.Create()) 122 | { 123 | using (var shared = disposable.Counter.Share(Allocator.TempJob)) 124 | { 125 | DisposeCallCounter val; 126 | AssertRequiresReadOrWriteAccess( 127 | shared, 128 | () => val = shared.Value); 129 | } 130 | } 131 | } 132 | 133 | [Test] 134 | public void RefIncrementsRefCountAndReturnsCopy() 135 | { 136 | using (TestDisposable disposable = TestDisposable.Create()) 137 | { 138 | disposable.Counter.AssignUniqueId(); 139 | using (var shared = disposable.Counter.Share(Allocator.TempJob)) 140 | { 141 | using (var shared2 = shared.Ref()) 142 | { 143 | Assert.That( 144 | shared2.Value.Id, 145 | Is.EqualTo(disposable.Counter.Id)); 146 | } 147 | } 148 | Assert.That(disposable.Counter.Num[0], Is.EqualTo(1)); 149 | } 150 | } 151 | 152 | [Test] 153 | public void DisposeDisposesDisposableAndMakesUnusable() 154 | { 155 | using (TestDisposable disposable = TestDisposable.Create()) 156 | { 157 | var shared = disposable.Counter.Share(Allocator.TempJob); 158 | shared.Dispose(); 159 | Assert.That(disposable.Counter.Num[0], Is.EqualTo(1)); 160 | DisposeCallCounter val; 161 | Assert.That( 162 | () => val = shared.Value, 163 | Throws.Exception); 164 | } 165 | } 166 | 167 | [Test] 168 | public void DisposeRequiresReadAccess() 169 | { 170 | using (TestDisposable disposable = TestDisposable.Create()) 171 | { 172 | using (var shared = disposable.Counter.Share(Allocator.TempJob)) 173 | { 174 | AssertRequiresReadOrWriteAccess( 175 | shared, 176 | shared.Dispose); 177 | } 178 | } 179 | } 180 | 181 | [Test] 182 | public void IsCreatedOnlyReturnsTrueBeforeDispose() 183 | { 184 | using (TestDisposable disposable = TestDisposable.Create()) 185 | { 186 | var shared = disposable.Counter.Share(Allocator.TempJob); 187 | Assert.That(shared.IsCreated, Is.True); 188 | 189 | shared.Dispose(); 190 | 191 | Assert.That(shared.IsCreated, Is.False); 192 | } 193 | } 194 | 195 | private struct DeallocateOnJobCompletionJob : IJob 196 | { 197 | [DeallocateOnJobCompletion] 198 | public SharedDisposable Shared; 199 | 200 | public void Execute() 201 | { 202 | } 203 | } 204 | 205 | [Test] 206 | public void CanDeallocateOnJobCompletion() 207 | { 208 | TestDisposable disposable = TestDisposable.Create(); 209 | var shared = disposable.Counter.Share(Allocator.TempJob); 210 | var job = new DeallocateOnJobCompletionJob { Shared = shared }; 211 | 212 | job.Run(); 213 | Assert.That( 214 | () => disposable.Counter.Num[0], 215 | Throws.Exception); 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /JacksonDunstanNativeCollections/Tests/TestSharedDisposable.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ead27e28daecf48e1a5698a40a538794 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2018 Jackson Dunstan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f6f17df7fb7b12249bc5101c9bd067c3 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /PerformanceTests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dbbb40c0150a04942bb6e3a91a9a720b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /PerformanceTests/PerformanceTest.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using Unity.Collections; 3 | using Unity.Jobs; 4 | using Unity.Burst; 5 | using JacksonDunstan.NativeCollections; 6 | using UnityEditor; 7 | 8 | public class PerformanceTest : MonoBehaviour 9 | { 10 | // Add jobs 11 | 12 | [BurstCompile] 13 | struct ListAddJob : IJob 14 | { 15 | public NativeList List; 16 | public int NumElementsToAdd; 17 | 18 | public void Execute() 19 | { 20 | for (int i = 0; i < NumElementsToAdd; ++i) 21 | { 22 | List.Add(i); 23 | } 24 | } 25 | } 26 | 27 | [BurstCompile] 28 | struct LinkedListAddJob : IJob 29 | { 30 | public NativeLinkedList List; 31 | public int NumNodesToInsert; 32 | 33 | public void Execute() 34 | { 35 | NativeLinkedList.Enumerator e = List.Tail; 36 | for (int i = 0; i < NumNodesToInsert; ++i) 37 | { 38 | e = List.InsertAfter(e, i); 39 | } 40 | } 41 | } 42 | 43 | [BurstCompile] 44 | struct ChunkedListAddJob : IJob 45 | { 46 | public NativeChunkedList List; 47 | public int NumElementsToAdd; 48 | 49 | public void Execute() 50 | { 51 | for (int i = 0; i < NumElementsToAdd; ++i) 52 | { 53 | List.Add(i); 54 | } 55 | } 56 | } 57 | 58 | [BurstCompile] 59 | struct HashSetAddJob : IJob 60 | { 61 | public JacksonDunstan.NativeCollections.NativeHashSet Set; 62 | public int NumElementsToAdd; 63 | 64 | public void Execute() 65 | { 66 | for (int i = 0; i < NumElementsToAdd; ++i) 67 | { 68 | Set.TryAdd(i); 69 | } 70 | } 71 | } 72 | 73 | // Iterate jobs 74 | 75 | [BurstCompile] 76 | struct ArrayIterateJob : IJob 77 | { 78 | public NativeArray Array; 79 | public NativeArray Sum; 80 | 81 | public void Execute() 82 | { 83 | for (int i = 0; i < Array.Length; ++i) 84 | { 85 | Sum[0] += Array[i]; 86 | } 87 | } 88 | } 89 | 90 | [BurstCompile] 91 | struct ListIterateJob : IJob 92 | { 93 | public NativeList List; 94 | public NativeArray Sum; 95 | 96 | public void Execute() 97 | { 98 | for (int i = 0; i < List.Length; ++i) 99 | { 100 | Sum[0] += List[i]; 101 | } 102 | } 103 | } 104 | 105 | [BurstCompile] 106 | struct LinkedListIterateJob : IJob 107 | { 108 | public NativeLinkedList List; 109 | public NativeArray Sum; 110 | 111 | public void Execute() 112 | { 113 | for (int i = 0; i < List.Length; ++i) 114 | { 115 | Sum[0] += List[i]; 116 | } 117 | } 118 | } 119 | 120 | [BurstCompile] 121 | struct ChunkedListIterateJob : IJob 122 | { 123 | public NativeChunkedList List; 124 | public NativeArray Sum; 125 | 126 | public void Execute() 127 | { 128 | for ( 129 | var chunks = List.Chunks.GetEnumerator(); 130 | chunks.MoveNext(); ) 131 | { 132 | for ( 133 | var chunk = chunks.Current.GetEnumerator(); 134 | chunk.MoveNext(); ) 135 | { 136 | Sum[0] += chunk.Current; 137 | } 138 | } 139 | } 140 | } 141 | 142 | // Iterate jobs (ParallelFor) 143 | 144 | [BurstCompile] 145 | struct ArrayIterateJobParallelFor : IJobParallelFor 146 | { 147 | public NativeArray Array; 148 | public NativeArray Sum; 149 | 150 | public void Execute(int index) 151 | { 152 | Sum[0] += Array[index]; 153 | } 154 | } 155 | 156 | [BurstCompile] 157 | struct LinkedListIterateJobParallelFor : IJobParallelFor 158 | { 159 | public NativeLinkedList List; 160 | public NativeArray Sum; 161 | 162 | public void Execute(int index) 163 | { 164 | Sum[0] += List[index]; 165 | } 166 | } 167 | 168 | [BurstCompile] 169 | struct ChunkedListIterateJobParallelFor : IJobParallelForRanged 170 | { 171 | public NativeChunkedList List; 172 | public NativeArray Sum; 173 | 174 | public void Execute(int startIndex, int endIndex) 175 | { 176 | for ( 177 | var chunks = List.GetChunksEnumerable(startIndex, endIndex).GetEnumerator(); 178 | chunks.MoveNext();) 179 | { 180 | for ( 181 | var chunk = chunks.Current.GetEnumerator(); 182 | chunk.MoveNext();) 183 | { 184 | Sum[0] += chunk.Current; 185 | } 186 | } 187 | } 188 | } 189 | 190 | // Insert jobs 191 | 192 | [BurstCompile] 193 | struct LinkedListInsertJob : IJob 194 | { 195 | public NativeLinkedList LinkedList; 196 | public int NumElementsToAdd; 197 | 198 | public void Execute() 199 | { 200 | for (int i = 0; i < NumElementsToAdd; ++i) 201 | { 202 | LinkedList.InsertBefore( 203 | LinkedList.GetEnumeratorAtIndex(i / 2), 204 | 1); 205 | } 206 | } 207 | } 208 | 209 | [BurstCompile] 210 | struct ChunkedListInsertJob : IJob 211 | { 212 | public NativeChunkedList List; 213 | public int NumElementsToAdd; 214 | 215 | public void Execute() 216 | { 217 | for (int i = 0; i < NumElementsToAdd; ++i) 218 | { 219 | List.Insert(i / 2, 1); 220 | } 221 | } 222 | } 223 | 224 | // Remove jobs 225 | 226 | [BurstCompile] 227 | struct LinkedListRemoveJob : IJob 228 | { 229 | public NativeLinkedList List; 230 | public int NumElementsToRemove; 231 | 232 | public void Execute() 233 | { 234 | for (int i = List.Length; i > 0; --i) 235 | { 236 | List.Remove(List.GetEnumeratorAtIndex(i / 2)); 237 | } 238 | } 239 | } 240 | 241 | [BurstCompile] 242 | struct ChunkedListRemoveJob : IJob 243 | { 244 | public NativeChunkedList List; 245 | public int NumElementsToRemove; 246 | 247 | public void Execute() 248 | { 249 | for (int i = List.Length; i > 0; --i) 250 | { 251 | List.RemoveAt(i / 2); 252 | } 253 | } 254 | } 255 | 256 | [BurstCompile] 257 | struct HashSetRemoveJob : IJob 258 | { 259 | public JacksonDunstan.NativeCollections.NativeHashSet Set; 260 | public int NumElementsToRemove; 261 | 262 | public void Execute() 263 | { 264 | for (int i = 0; i < Set.Length; ++i) 265 | { 266 | Set.Remove(i); 267 | } 268 | } 269 | } 270 | 271 | // NativeIntPtr and NativePerJobThreadIntPtr jobs 272 | 273 | [BurstCompile] 274 | struct NativeIntPtrParallelJob : IJobParallelFor 275 | { 276 | public NativeArray Array; 277 | public NativeIntPtr.Parallel Sum; 278 | 279 | public void Execute(int index) 280 | { 281 | Sum.Add(Array[index]); 282 | } 283 | } 284 | 285 | [BurstCompile] 286 | struct NativePerJobThreadIntPtrParallelJob : IJobParallelFor 287 | { 288 | public NativeArray Array; 289 | public NativePerJobThreadIntPtr.Parallel Sum; 290 | 291 | public void Execute(int index) 292 | { 293 | Sum.Add(Array[index]); 294 | } 295 | } 296 | 297 | // Warm up the job system 298 | 299 | static void WarmUpJobSystem() 300 | { 301 | // Create native collections 302 | 303 | NativeArray sum = new NativeArray(1, Allocator.TempJob); 304 | NativeArray array = new NativeArray( 305 | 4, 306 | Allocator.TempJob); 307 | NativeList list = new NativeList( 308 | 4, 309 | Allocator.TempJob); 310 | NativeLinkedList linkedList = new NativeLinkedList( 311 | 4, 312 | Allocator.TempJob); 313 | NativeChunkedList chunkedList = new NativeChunkedList( 314 | 4, 315 | 4, 316 | Allocator.TempJob); 317 | var hashSet = new JacksonDunstan.NativeCollections.NativeHashSet( 318 | 4, 319 | Allocator.TempJob); 320 | NativeIntPtr nativeIntPtr = new NativeIntPtr(Allocator.TempJob); 321 | NativePerJobThreadIntPtr nativePerJobThreadIntPtr = new NativePerJobThreadIntPtr( 322 | Allocator.TempJob); 323 | 324 | // Create jobs 325 | 326 | ListAddJob listAddJob = new ListAddJob 327 | { 328 | List = list 329 | }; 330 | LinkedListAddJob linkedListAddJob = new LinkedListAddJob 331 | { 332 | List = linkedList 333 | }; 334 | ChunkedListAddJob chunkedListAddJob = new ChunkedListAddJob 335 | { 336 | List = chunkedList 337 | }; 338 | HashSetAddJob hashSetAddJob = new HashSetAddJob 339 | { 340 | Set = hashSet 341 | }; 342 | ArrayIterateJob arrayIterateJob = new ArrayIterateJob 343 | { 344 | Array = array, 345 | Sum = sum 346 | }; 347 | ListIterateJob listIterateJob = new ListIterateJob 348 | { 349 | List = list, 350 | Sum = sum 351 | }; 352 | LinkedListIterateJob linkedListIterateJob = new LinkedListIterateJob 353 | { 354 | List = linkedList, 355 | Sum = sum 356 | }; 357 | ChunkedListIterateJob chunkedListIterateJob = new ChunkedListIterateJob 358 | { 359 | List = chunkedList, 360 | Sum = sum 361 | }; 362 | ArrayIterateJobParallelFor arrayIterateJobParallelFor = new ArrayIterateJobParallelFor 363 | { 364 | Array = array, 365 | Sum = sum 366 | }; 367 | LinkedListIterateJobParallelFor linkedListIterateJobParallelFor = new LinkedListIterateJobParallelFor 368 | { 369 | List = linkedList, 370 | Sum = sum 371 | }; 372 | ChunkedListIterateJobParallelFor chunkedListIterateJobParallelFor = new ChunkedListIterateJobParallelFor 373 | { 374 | List = chunkedList, 375 | Sum = sum 376 | }; 377 | LinkedListInsertJob linkedListInsertJob = new LinkedListInsertJob 378 | { 379 | LinkedList = linkedList 380 | }; 381 | ChunkedListInsertJob chunkedListInsertJob = new ChunkedListInsertJob 382 | { 383 | List = chunkedList 384 | }; 385 | LinkedListRemoveJob linkedListRemoveJob = new LinkedListRemoveJob 386 | { 387 | List = linkedList 388 | }; 389 | ChunkedListRemoveJob chunkedListRemoveJob = new ChunkedListRemoveJob 390 | { 391 | List = chunkedList 392 | }; 393 | HashSetRemoveJob hashSetRemoveJob = new HashSetRemoveJob 394 | { 395 | Set = hashSet 396 | }; 397 | NativeIntPtrParallelJob nativeIntPtrParallelJob = new NativeIntPtrParallelJob 398 | { 399 | Array = array, 400 | Sum = nativeIntPtr.GetParallel() 401 | }; 402 | NativePerJobThreadIntPtrParallelJob nativePerJobThreadIntPtrParallelJob = new NativePerJobThreadIntPtrParallelJob 403 | { 404 | Array = array, 405 | Sum = nativePerJobThreadIntPtr.GetParallel() 406 | }; 407 | 408 | // Run jobs 409 | 410 | listAddJob.Run(); 411 | linkedListAddJob.Run(); 412 | chunkedListAddJob.Run(); 413 | hashSetAddJob.Run(); 414 | arrayIterateJob.Run(); 415 | listIterateJob.Run(); 416 | linkedListIterateJob.Run(); 417 | chunkedListIterateJob.Run(); 418 | arrayIterateJobParallelFor.Run(array.Length); 419 | linkedListIterateJobParallelFor.Run(linkedList.Length); 420 | chunkedListIterateJobParallelFor.RunRanged(chunkedList.Length); 421 | list.Clear(); 422 | linkedList.Clear(); 423 | chunkedList.Clear(); 424 | hashSet.Clear(); 425 | linkedListInsertJob.Run(); 426 | chunkedListInsertJob.Run(); 427 | linkedListRemoveJob.Run(); 428 | chunkedListRemoveJob.Run(); 429 | hashSetRemoveJob.Run(); 430 | nativeIntPtrParallelJob.Run(array.Length); 431 | nativePerJobThreadIntPtrParallelJob.Run(array.Length); 432 | 433 | // Dispose native collections 434 | 435 | sum.Dispose(); 436 | array.Dispose(); 437 | list.Dispose(); 438 | linkedList.Dispose(); 439 | chunkedList.Dispose(); 440 | hashSet.Dispose(); 441 | nativeIntPtr.Dispose(); 442 | nativePerJobThreadIntPtr.Dispose(); 443 | } 444 | 445 | // Run the test 446 | 447 | void Start() 448 | { 449 | WarmUpJobSystem(); 450 | 451 | const int size = 452 | #if UNITY_EDITOR 453 | 1000 454 | #else 455 | 10000 456 | #endif 457 | ; 458 | 459 | const int chunkSize = 1024; 460 | const int numElementsPerChunk = chunkSize / sizeof(int); 461 | 462 | // Create native collections 463 | 464 | NativeArray sum = new NativeArray(1, Allocator.TempJob); 465 | NativeArray array = new NativeArray( 466 | size, 467 | Allocator.TempJob); 468 | NativeList list = new NativeList( 469 | 0, 470 | Allocator.TempJob); 471 | NativeLinkedList linkedList = new NativeLinkedList( 472 | 0, 473 | Allocator.TempJob); 474 | NativeChunkedList chunkedList = new NativeChunkedList( 475 | numElementsPerChunk, 476 | 0, 477 | Allocator.TempJob); 478 | var hashSet = new JacksonDunstan.NativeCollections.NativeHashSet( 479 | 0, 480 | Allocator.TempJob); 481 | NativeIntPtr nativeIntPtr = new NativeIntPtr(Allocator.TempJob); 482 | NativePerJobThreadIntPtr nativePerJobThreadIntPtr = new NativePerJobThreadIntPtr( 483 | Allocator.TempJob); 484 | 485 | // Run add jobs 486 | 487 | System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); 488 | 489 | ListAddJob listAddJob = new ListAddJob 490 | { 491 | List = list, 492 | NumElementsToAdd = size 493 | }; 494 | sw.Reset(); 495 | sw.Start(); 496 | listAddJob.Run(); 497 | long listAddTicks = sw.ElapsedTicks; 498 | 499 | LinkedListAddJob linkedListAddJob = new LinkedListAddJob 500 | { 501 | List = linkedList, 502 | NumNodesToInsert = size 503 | }; 504 | sw.Reset(); 505 | sw.Start(); 506 | linkedListAddJob.Run(); 507 | long linkedListAddTicks = sw.ElapsedTicks; 508 | 509 | ChunkedListAddJob chunkedListAddJob = new ChunkedListAddJob 510 | { 511 | List = chunkedList, 512 | NumElementsToAdd = size 513 | }; 514 | sw.Reset(); 515 | sw.Start(); 516 | chunkedListAddJob.Run(); 517 | long chunkedListAddTicks = sw.ElapsedTicks; 518 | 519 | HashSetAddJob hashSetAddJob = new HashSetAddJob 520 | { 521 | Set = hashSet, 522 | NumElementsToAdd = size 523 | }; 524 | sw.Reset(); 525 | sw.Start(); 526 | hashSetAddJob.Run(); 527 | long hashSetAddTicks = sw.ElapsedTicks; 528 | 529 | // Run iterate jobs 530 | 531 | ArrayIterateJob arrayIterateJob = new ArrayIterateJob 532 | { 533 | Array = array, 534 | Sum = sum 535 | }; 536 | sum[0] = 0; 537 | sw.Reset(); 538 | sw.Start(); 539 | arrayIterateJob.Run(); 540 | long arrayIterateTicks = sw.ElapsedTicks; 541 | 542 | ListIterateJob listIterateJob = new ListIterateJob 543 | { 544 | List = list, 545 | Sum = sum 546 | }; 547 | sum[0] = 0; 548 | sw.Reset(); 549 | sw.Start(); 550 | listIterateJob.Run(); 551 | long listIterateTicks = sw.ElapsedTicks; 552 | 553 | LinkedListIterateJob linkedListIterateJob = new LinkedListIterateJob 554 | { 555 | List = linkedList, 556 | Sum = sum 557 | }; 558 | sum[0] = 0; 559 | sw.Reset(); 560 | sw.Start(); 561 | linkedListIterateJob.Run(); 562 | long linkedListIterateTicks = sw.ElapsedTicks; 563 | 564 | ChunkedListIterateJob chunkedListIterateJob = new ChunkedListIterateJob 565 | { 566 | List = chunkedList, 567 | Sum = sum 568 | }; 569 | sum[0] = 0; 570 | sw.Reset(); 571 | sw.Start(); 572 | chunkedListIterateJob.Run(); 573 | long chunkedListIterateTicks = sw.ElapsedTicks; 574 | 575 | ArrayIterateJobParallelFor arrayIterateJobParallelFor = new ArrayIterateJobParallelFor 576 | { 577 | Array = array, 578 | Sum = sum 579 | }; 580 | sum[0] = 0; 581 | sw.Reset(); 582 | sw.Start(); 583 | arrayIterateJobParallelFor.Run(size); 584 | long arrayIterateParallelForTicks = sw.ElapsedTicks; 585 | 586 | linkedList.PrepareForParallelForJob(); 587 | LinkedListIterateJobParallelFor linkedListIterateJobParallelFor = new LinkedListIterateJobParallelFor 588 | { 589 | List = linkedList, 590 | Sum = sum 591 | }; 592 | sum[0] = 0; 593 | sw.Reset(); 594 | sw.Start(); 595 | linkedListIterateJobParallelFor.Run(size); 596 | long linkedListIterateParallelForTicks = sw.ElapsedTicks; 597 | 598 | chunkedList.PrepareForParallelForJob(); 599 | ChunkedListIterateJobParallelFor chunkedListIterateJobParallelFor = new ChunkedListIterateJobParallelFor 600 | { 601 | List = chunkedList, 602 | Sum = sum 603 | }; 604 | sum[0] = 0; 605 | sw.Reset(); 606 | sw.Start(); 607 | chunkedListIterateJobParallelFor.RunRanged(size); 608 | long chunkedListIterateParallelForTicks = sw.ElapsedTicks; 609 | 610 | // Clear native collections 611 | 612 | list.Clear(); 613 | linkedList.Clear(); 614 | chunkedList.Clear(); 615 | 616 | // Run insert jobs 617 | 618 | LinkedListInsertJob linkedListInsertJob = new LinkedListInsertJob 619 | { 620 | LinkedList = linkedList, 621 | NumElementsToAdd = size 622 | }; 623 | sw.Reset(); 624 | sw.Start(); 625 | linkedListInsertJob.Run(); 626 | long linkedListInsertTicks = sw.ElapsedTicks; 627 | 628 | ChunkedListInsertJob chunkedListInsertJob = new ChunkedListInsertJob 629 | { 630 | List = chunkedList, 631 | NumElementsToAdd = size 632 | }; 633 | sw.Reset(); 634 | sw.Start(); 635 | chunkedListInsertJob.Run(); 636 | long chunkedListInsertTicks = sw.ElapsedTicks; 637 | 638 | // Run remove jobs 639 | 640 | LinkedListRemoveJob linkedListRemoveJob = new LinkedListRemoveJob 641 | { 642 | List = linkedList, 643 | NumElementsToRemove = size 644 | }; 645 | sw.Reset(); 646 | sw.Start(); 647 | linkedListRemoveJob.Run(); 648 | long linkedListRemoveTicks = sw.ElapsedTicks; 649 | 650 | ChunkedListRemoveJob chunkedListRemoveJob = new ChunkedListRemoveJob 651 | { 652 | List = chunkedList, 653 | NumElementsToRemove = size 654 | }; 655 | sw.Reset(); 656 | sw.Start(); 657 | chunkedListRemoveJob.Run(); 658 | long chunkedListRemoveTicks = sw.ElapsedTicks; 659 | 660 | HashSetRemoveJob hashSetRemoveJob = new HashSetRemoveJob 661 | { 662 | Set = hashSet, 663 | NumElementsToRemove = size 664 | }; 665 | sw.Reset(); 666 | sw.Start(); 667 | hashSetRemoveJob.Run(); 668 | long hashSetRemoveTicks = sw.ElapsedTicks; 669 | 670 | // Run NativeIntPtr and NativePerJobThreadIntPtr jobs 671 | 672 | NativeIntPtrParallelJob nativeIntPtrParallelJob = new NativeIntPtrParallelJob 673 | { 674 | Array = array, 675 | Sum = nativeIntPtr.GetParallel() 676 | }; 677 | sw.Reset(); 678 | sw.Start(); 679 | nativeIntPtrParallelJob.Run(size); 680 | long nativeIntPtrTicks = sw.ElapsedTicks; 681 | 682 | NativePerJobThreadIntPtrParallelJob nativePerJobThreadIntPtrParallelJob = new NativePerJobThreadIntPtrParallelJob 683 | { 684 | Array = array, 685 | Sum = nativePerJobThreadIntPtr.GetParallel() 686 | }; 687 | sw.Reset(); 688 | sw.Start(); 689 | nativePerJobThreadIntPtrParallelJob.Run(size); 690 | long nativePerJobThreadIntPtrTicks = sw.ElapsedTicks; 691 | 692 | // Report results 693 | Debug.Log( 694 | "Operation,Job Type,NativeArray,NativeList,NativeLinkedList,NativeChunkedList,NativeHashSet\n" + 695 | "Add,Single," + "n/a" + "," + listAddTicks + "," + linkedListAddTicks + "," + chunkedListAddTicks + "," + hashSetAddTicks + "\n" + 696 | "Iterate,Single," + arrayIterateTicks + "," + listIterateTicks + "," + linkedListIterateTicks + "," + chunkedListIterateTicks + "," + "n/a" + "\n" + 697 | "Iterate,ParallelFor," + arrayIterateParallelForTicks + "," + "n/a" + "," + linkedListIterateParallelForTicks + "," + chunkedListIterateParallelForTicks + "," + "n/a" + "\n" + 698 | "Insert,Single," + "n/a" + "," + "n/a" + "," + linkedListInsertTicks + "," + chunkedListInsertTicks + "," + "n/a" + "\n" + 699 | "Remove,Single," + "n/a" + "," + "n/a" + "," + linkedListRemoveTicks + "," + chunkedListRemoveTicks + "," + hashSetRemoveTicks ); 700 | Debug.Log( 701 | "Operation,Job Type,NativeIntPtr,NativePerJobThreadIntPtr\n" + 702 | "Sum,ParallelFor," + nativeIntPtrTicks + "," + nativePerJobThreadIntPtrTicks); 703 | 704 | // Dispose native collections 705 | sum.Dispose(); 706 | array.Dispose(); 707 | list.Dispose(); 708 | linkedList.Dispose(); 709 | chunkedList.Dispose(); 710 | hashSet.Dispose(); 711 | nativeIntPtr.Dispose(); 712 | nativePerJobThreadIntPtr.Dispose(); 713 | 714 | // Quit 715 | #if UNITY_EDITOR 716 | EditorApplication.isPlaying = false; 717 | #else 718 | Application.Quit(); 719 | #endif 720 | } 721 | } 722 | -------------------------------------------------------------------------------- /PerformanceTests/PerformanceTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 311130592996f41ddbaa207a839f57f5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /PerformanceTests/PerformanceTestScene.unity: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!29 &1 4 | OcclusionCullingSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_OcclusionBakeSettings: 8 | smallestOccluder: 5 9 | smallestHole: 0.25 10 | backfaceThreshold: 100 11 | m_SceneGUID: 00000000000000000000000000000000 12 | m_OcclusionCullingData: {fileID: 0} 13 | --- !u!104 &2 14 | RenderSettings: 15 | m_ObjectHideFlags: 0 16 | serializedVersion: 9 17 | m_Fog: 0 18 | m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} 19 | m_FogMode: 3 20 | m_FogDensity: 0.01 21 | m_LinearFogStart: 0 22 | m_LinearFogEnd: 300 23 | m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} 24 | m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} 25 | m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} 26 | m_AmbientIntensity: 1 27 | m_AmbientMode: 0 28 | m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} 29 | m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} 30 | m_HaloStrength: 0.5 31 | m_FlareStrength: 1 32 | m_FlareFadeSpeed: 3 33 | m_HaloTexture: {fileID: 0} 34 | m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} 35 | m_DefaultReflectionMode: 0 36 | m_DefaultReflectionResolution: 128 37 | m_ReflectionBounces: 1 38 | m_ReflectionIntensity: 1 39 | m_CustomReflection: {fileID: 0} 40 | m_Sun: {fileID: 0} 41 | m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} 42 | m_UseRadianceAmbientProbe: 0 43 | --- !u!157 &3 44 | LightmapSettings: 45 | m_ObjectHideFlags: 0 46 | serializedVersion: 11 47 | m_GIWorkflowMode: 1 48 | m_GISettings: 49 | serializedVersion: 2 50 | m_BounceScale: 1 51 | m_IndirectOutputScale: 1 52 | m_AlbedoBoost: 1 53 | m_TemporalCoherenceThreshold: 1 54 | m_EnvironmentLightingMode: 0 55 | m_EnableBakedLightmaps: 1 56 | m_EnableRealtimeLightmaps: 1 57 | m_LightmapEditorSettings: 58 | serializedVersion: 10 59 | m_Resolution: 2 60 | m_BakeResolution: 40 61 | m_AtlasSize: 1024 62 | m_AO: 0 63 | m_AOMaxDistance: 1 64 | m_CompAOExponent: 1 65 | m_CompAOExponentDirect: 0 66 | m_Padding: 2 67 | m_LightmapParameters: {fileID: 0} 68 | m_LightmapsBakeMode: 1 69 | m_TextureCompression: 1 70 | m_FinalGather: 0 71 | m_FinalGatherFiltering: 1 72 | m_FinalGatherRayCount: 256 73 | m_ReflectionCompression: 2 74 | m_MixedBakeMode: 2 75 | m_BakeBackend: 1 76 | m_PVRSampling: 1 77 | m_PVRDirectSampleCount: 32 78 | m_PVRSampleCount: 500 79 | m_PVRBounces: 2 80 | m_PVRFilterTypeDirect: 0 81 | m_PVRFilterTypeIndirect: 0 82 | m_PVRFilterTypeAO: 0 83 | m_PVRFilteringMode: 1 84 | m_PVRCulling: 1 85 | m_PVRFilteringGaussRadiusDirect: 1 86 | m_PVRFilteringGaussRadiusIndirect: 5 87 | m_PVRFilteringGaussRadiusAO: 2 88 | m_PVRFilteringAtrousPositionSigmaDirect: 0.5 89 | m_PVRFilteringAtrousPositionSigmaIndirect: 2 90 | m_PVRFilteringAtrousPositionSigmaAO: 1 91 | m_ShowResolutionOverlay: 1 92 | m_LightingDataAsset: {fileID: 0} 93 | m_UseShadowmask: 1 94 | --- !u!196 &4 95 | NavMeshSettings: 96 | serializedVersion: 2 97 | m_ObjectHideFlags: 0 98 | m_BuildSettings: 99 | serializedVersion: 2 100 | agentTypeID: 0 101 | agentRadius: 0.5 102 | agentHeight: 2 103 | agentSlope: 45 104 | agentClimb: 0.4 105 | ledgeDropHeight: 0 106 | maxJumpAcrossDistance: 0 107 | minRegionArea: 2 108 | manualCellSize: 0 109 | cellSize: 0.16666667 110 | manualTileSize: 0 111 | tileSize: 256 112 | accuratePlacement: 0 113 | debug: 114 | m_Flags: 0 115 | m_NavMeshData: {fileID: 0} 116 | --- !u!1 &909671986 117 | GameObject: 118 | m_ObjectHideFlags: 0 119 | m_CorrespondingSourceObject: {fileID: 0} 120 | m_PrefabInternal: {fileID: 0} 121 | serializedVersion: 6 122 | m_Component: 123 | - component: {fileID: 909671987} 124 | - component: {fileID: 909671988} 125 | m_Layer: 0 126 | m_Name: GameObject 127 | m_TagString: Untagged 128 | m_Icon: {fileID: 0} 129 | m_NavMeshLayer: 0 130 | m_StaticEditorFlags: 0 131 | m_IsActive: 1 132 | --- !u!4 &909671987 133 | Transform: 134 | m_ObjectHideFlags: 0 135 | m_CorrespondingSourceObject: {fileID: 0} 136 | m_PrefabInternal: {fileID: 0} 137 | m_GameObject: {fileID: 909671986} 138 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 139 | m_LocalPosition: {x: 0, y: 0, z: 0} 140 | m_LocalScale: {x: 1, y: 1, z: 1} 141 | m_Children: [] 142 | m_Father: {fileID: 0} 143 | m_RootOrder: 0 144 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 145 | --- !u!114 &909671988 146 | MonoBehaviour: 147 | m_ObjectHideFlags: 0 148 | m_CorrespondingSourceObject: {fileID: 0} 149 | m_PrefabInternal: {fileID: 0} 150 | m_GameObject: {fileID: 909671986} 151 | m_Enabled: 1 152 | m_EditorHideFlags: 0 153 | m_Script: {fileID: 11500000, guid: 311130592996f41ddbaa207a839f57f5, type: 3} 154 | m_Name: 155 | m_EditorClassIdentifier: 156 | -------------------------------------------------------------------------------- /PerformanceTests/PerformanceTestScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4850c6f323d3e45959cfa82571b5e9b9 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Native Collections 2 | 3 | A small library of native collections like `NativeArray` suitable to be used with Unity 2018.1 or greater. No additional packages (e.g. ECS or Burst) are required. 4 | 5 | [![openupm](https://img.shields.io/npm/v/com.jacksondunstan.native-collections?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.jacksondunstan.native-collections/) 6 | 7 | # Getting Started 8 | 9 | Clone or download this repository and copy the `JacksonDunstanNativeCollections` directory somewhere inside your Unity project's `Assets` directory. 10 | 11 | Add this as a package to your project by adding the below as an entry to the dependencies in the `/Package/manifest.json` file: 12 | 13 | ```json 14 | "com.jacksondunstan.native-collections": "https://github.com/jacksondunstan/NativeCollections.git" 15 | ``` 16 | 17 | For more information on adding git repositories as a package see the [Git support on Package Manager](https://docs.unity3d.com/Manual/upm-git.html) in the Unity Documentation. 18 | 19 | # NativeLinkedList<T> 20 | 21 | This is a doubly-linked list backed by parallel arrays. Here's how to use it: 22 | 23 | ```csharp 24 | // Create an empty list with capacity for five nodes 25 | NativeLinkedList list = new NativeLinkedList(5, Allocator.Temp); 26 | 27 | // Add some nodes to the list 28 | list.InsertAfter(list.Tail, 10); 29 | list.InsertAfter(list.Tail, 20); 30 | list.InsertAfter(list.Tail, 30); 31 | list.InsertAfter(list.Tail, 40); 32 | list.InsertAfter(list.Tail, 50); 33 | 34 | // Iterate over the list 35 | foreach (int val in list) 36 | { 37 | Debug.Log(val); 38 | } 39 | 40 | // Remove a node from the middle 41 | list.Remove(list.GetEnumeratorAtIndex(2)); 42 | 43 | // Insert a node into the middle 44 | list.InsertBefore(list.Head.Next, 15); 45 | 46 | // Sort the nodes so they can be accessed sequentially 47 | list.SortNodeMemoryAddresses(); 48 | 49 | // Access the nodes sequentially 50 | for (int i = 0; i < list.Length; ++i) 51 | { 52 | Debug.Log(list[i]); 53 | } 54 | 55 | // Dispose the list's native memory 56 | list.Dispose(); 57 | ``` 58 | 59 | There is much more functionality available. See [the source](JacksonDunstanNativeCollections/NativeLinkedList.cs) for more. 60 | 61 | To read about the making of this type, see this [article series](https://jacksondunstan.com/articles/4865). 62 | 63 | # NativeChunkedList<T> 64 | 65 | This is a dynamically-resizable list backed by arrays that store "chunks" of elements and an array of pointers to those chunks. Here's how to use it: 66 | 67 | ```csharp 68 | // Create an empty list with capacity for 4096 elements in 1024 element chunks 69 | NativeChunkedList list = new NativeChunkedList( 70 | 1024, 71 | 4096, 72 | Allocator.Temp); 73 | 74 | // Add some elements to the list 75 | list.Add(10); 76 | list.Add(20); 77 | list.Add(30); 78 | list.Add(40); 79 | list.Add(50); 80 | 81 | // Iterate over the list 82 | foreach (int val in list) 83 | { 84 | Debug.Log(val); 85 | } 86 | 87 | // Remove an element from the middle 88 | list.RemoveAt(2); 89 | 90 | // Insert an element into the middle 91 | list.Insert(1, 15); 92 | 93 | // Access the elements sequentially 94 | foreach (var e in list.Chunks) 95 | { 96 | foreach (int val in e) 97 | { 98 | Debug.Log(val); 99 | } 100 | } 101 | 102 | // Dispose the list's native memory 103 | list.Dispose(); 104 | ``` 105 | 106 | There is much more functionality available. See [the source](JacksonDunstanNativeCollections/NativeChunkedList.cs) for more. 107 | 108 | To read about the making of this type, see this [article series](https://jacksondunstan.com/articles/4963). 109 | 110 | # NativeHashSet<T> 111 | 112 | This is a collection of unique keys that aren't mapped to values. Here's how to use it: 113 | 114 | ```csharp 115 | // Create an empty set 116 | NativeHashSet set = new NativeHashSet(100, Allocator.Persistent); 117 | 118 | // Add some keys 119 | set.TryAdd(123); 120 | set.TryAdd(456); 121 | 122 | // Check for containment 123 | set.Contains(123); // true 124 | set.Contains(1000); // false 125 | 126 | // Remove a key 127 | set.Remove(123); 128 | ``` 129 | 130 | There is much more functionality available. See [the source](JacksonDunstanNativeCollections/NativeHashSet.cs) for more. 131 | 132 | To read about the making of this type, see this [article](https://jacksondunstan.com/articles/5346). 133 | 134 | # NativeArray2D<T> 135 | 136 | This is a 2D version of `NativeArray`. Here's how to use it: 137 | 138 | ```csharp 139 | // Create a 2x3 empty array 140 | NativeArray2D array = new NativeArray2D(2, 3, Allocator.Temp); 141 | 142 | // Set elements of the array 143 | array[0, 1] = 123; 144 | array[1, 2] = 456; 145 | 146 | // Get elements of the array 147 | int val123 = array[0, 1]; 148 | int val456 = array[1, 2]; 149 | 150 | // Iterate over the array 151 | foreach (int val in array) 152 | { 153 | Debug.Log(val); 154 | } 155 | 156 | // Copy to a managed array 157 | int[,] managed = new int[2, 3]; 158 | array.CopyTo(managed); 159 | ``` 160 | 161 | There is much more functionality available. See [the source](JacksonDunstanNativeCollections/NativeArray2D.cs) for more. 162 | 163 | To read about the making of this type, see this [article](https://jacksondunstan.com/articles/5416). 164 | 165 | # NativeIntPtr and NativeLongPtr 166 | 167 | These are pointers to a single `int` or `long`, useful for counters among other purposes. Here's how to use `NativeIntPtr` (`NativeLongPtr` is identical): 168 | 169 | ```csharp 170 | // Construct with the zero value 171 | NativeIntPtr intPtr0 = new NativeIntPtr(Allocator.Temp); 172 | 173 | // Construct with a custom value 174 | NativeIntPtr intPtr = new NativeIntPtr(Allocator.Temp, 123); 175 | 176 | // Read and write the value 177 | intPtr.Value = 20; 178 | Debug.Log("Value: " + intPtr.Value); // prints "Value: 20" 179 | 180 | // Get a Parallel for use in an IJobParallelFor 181 | NativeIntPtr.Parallel parallel = intPtr.GetParallel(); 182 | 183 | // Perform atomic writes on it 184 | parallel.Increment(); 185 | parallel.Decrement(); 186 | parallel.Add(100); 187 | 188 | // Dispose the native memory 189 | intPtr0.Dispose(); 190 | intPtr.Dispose(); 191 | ``` 192 | 193 | To read about the making of this type, see this [article](https://jacksondunstan.com/articles/4940). 194 | 195 | # NativePerJobThreadIntPtr and NativePerJobThreadLongPtr 196 | 197 | These are pointers to a single `int` or `long`. Compared to `NativeIntPtr` and `NativeLongPtr`, their `Parallel` versions are much faster to use in `ParallelFor` jobs but use more memory. Here's how to use `NativePerJobThreadIntPtr` (`NativePerJobThreadLongPtr` is identical): 198 | 199 | ```csharp 200 | // Construct with the zero value 201 | NativePerJobThreadIntPtr intPtr0 = new NativePerJobThreadIntPtr(Allocator.Temp); 202 | 203 | // Construct with a custom value 204 | NativePerJobThreadIntPtr intPtr = new NativePerJobThreadIntPtr(Allocator.Temp, 123); 205 | 206 | // Read and write the value 207 | intPtr.Value = 20; 208 | Debug.Log("Value: " + intPtr.Value); // prints "Value: 20" 209 | 210 | // Get a Parallel for use in an IJobParallelFor 211 | NativePerJobThreadIntPtr.Parallel parallel = intPtr.GetParallel(); 212 | 213 | // Perform atomic writes on it 214 | parallel.Increment(); 215 | parallel.Decrement(); 216 | parallel.Add(100); 217 | 218 | // Dispose the native memory 219 | intPtr0.Dispose(); 220 | intPtr.Dispose(); 221 | ``` 222 | 223 | To read about the making of this type, see this [article](https://jacksondunstan.com/articles/4942). 224 | 225 | # IJobParallelForRanged 226 | 227 | This is a job type interface similar to `IJobParallelFor` for job types that want to operate on a range of indices from the batch rather than one index at a time. This is especially useful with `NativeChunkedList` as its enumerable and enumerator types can iterate much more efficiently than with random access via the indexer. Here's how to use this job type interface: 228 | 229 | ```csharp 230 | [BurstCompile] 231 | struct ChunkedListIterateJobParallelFor : IJobParallelForRanged 232 | { 233 | public NativeChunkedList List; 234 | public NativePerJobThreadIntPtr.Parallel Sum; 235 | 236 | public void Execute(int startIndex, int endIndex) 237 | { 238 | for ( 239 | var chunks = List.GetChunksEnumerable(startIndex, endIndex).GetEnumerator(); 240 | chunks.MoveNext();) 241 | { 242 | for ( 243 | var chunk = chunks.Current.GetEnumerator(); 244 | chunk.MoveNext();) 245 | { 246 | Sum.Add(chunk.Current); 247 | } 248 | } 249 | } 250 | } 251 | ``` 252 | 253 | To read about the making of this type, see this [article](https://jacksondunstan.com/articles/4976). 254 | 255 | # SharedDisposable<T> 256 | 257 | This allows for easy sharing of `IDisposable` objects such as the above collection types. A reference count is held internally and the object is free when its last reference is released. Here's how to use it: 258 | 259 | ```csharp 260 | // Create an IDisposable 261 | NativeArray array = new NativeArray(1, Allocator.Temp); 262 | 263 | // Prepare for sharing. Ref count = 1. 264 | SharedDisposable> shared = array.Share(Allocator.Temp); 265 | 266 | // Share. Ref count = 2. 267 | SharedDisposable> shared2 = shared.Ref(); 268 | 269 | // Use a shared reference 270 | print("Array len: " + shared2.Value.Length); 271 | 272 | // Release a reference. Ref count = 1. 273 | shared2.Dispose(); 274 | 275 | // Release the last reference. Ref count = 0. 276 | // The NativeArray is disposed. 277 | shared.Dispose(); 278 | 279 | // 'using' blocks are even more convenient 280 | // No need to call Dispose() 281 | // Exception-safe 282 | using (SharedDisposable> used = array.Share(Allocator.Temp)) 283 | { 284 | using (SharedDisposable> used2 = shared.Ref()) 285 | { 286 | } 287 | } 288 | ``` 289 | 290 | To read about the making of this type, see this [article](https://jacksondunstan.com/articles/5441). 291 | 292 | # License 293 | 294 | [MIT](LICENSE.md) 295 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7fb5c4177678d4a31b0b180815da3e61 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.jacksondunstan.native-collections", 3 | "version": "1.0.0", 4 | "displayName": "Native Collections", 5 | "description": "A small library of native collections like NativeArray suitable to be used with Unity 2018.1 or greater. No additional packages (e.g. ECS or Burst) are required.", 6 | "unity": "2019.2", 7 | "unityRelease": "0f1", 8 | "keywords": [ 9 | "Native", 10 | "Collections" 11 | ], 12 | "author": { 13 | "name": "Jackson Dunstan", 14 | "email": "jackson@jacksondunstan.com", 15 | "url": "https://jacksondunstan.com/" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/jacksondunstan/NativeCollections" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 93400fa5782de9c4b86dbec349ea979a 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------