using System; using System.Diagnostics; using System.Runtime.InteropServices; using Unity.Collections.LowLevel.Unsafe; using Unity.Burst; using Unity.Burst.CompilerServices; using Unity.Jobs; using Unity.Jobs.LowLevel.Unsafe; using Unity.Mathematics; using System.Reflection; using System.Runtime.CompilerServices; namespace Unity.Collections { /// /// For scheduling release of unmanaged resources. /// public interface INativeDisposable : IDisposable { /// /// Creates and schedules a job that will release all resources (memory and safety handles) of this collection. /// /// A job handle which the newly scheduled job will depend upon. /// The handle of a new job that will release all resources (memory and safety handles) of this collection. JobHandle Dispose(JobHandle inputDeps); } /// /// Provides helper methods for collections. /// [GenerateTestsForBurstCompatibility] public static class CollectionHelper { [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] internal static void CheckAllocator(AllocatorManager.AllocatorHandle allocator) { if (!ShouldDeallocate(allocator)) throw new ArgumentException($"Allocator {allocator} must not be None or Invalid"); } /// /// The size in bytes of the current platform's L1 cache lines. /// /// The size in bytes of the current platform's L1 cache lines. public const int CacheLineSize = JobsUtility.CacheLineSize; [StructLayout(LayoutKind.Explicit)] internal struct LongDoubleUnion { [FieldOffset(0)] internal long longValue; [FieldOffset(0)] internal double doubleValue; } /// /// Returns the binary logarithm of the `value`, but the result is rounded down to the nearest integer. /// /// The value. /// The binary logarithm of the `value`, but the result is rounded down to the nearest integer. public static int Log2Floor(int value) { return 31 - math.lzcnt((uint)value); } /// /// Returns the binary logarithm of the `value`, but the result is rounded up to the nearest integer. /// /// The value. /// The binary logarithm of the `value`, but the result is rounded up to the nearest integer. public static int Log2Ceil(int value) { return 32 - math.lzcnt((uint)value - 1); } /// /// Returns an allocation size in bytes that factors in alignment. /// /// /// // 55 aligned to 16 is 64. /// int size = CollectionHelper.Align(55, 16); /// /// The size to align. /// A non-zero, positive power of two. /// The smallest integer that is greater than or equal to `size` and is a multiple of `alignmentPowerOfTwo`. /// Thrown if `alignmentPowerOfTwo` is not a non-zero, positive power of two. public static int Align(int size, int alignmentPowerOfTwo) { if (alignmentPowerOfTwo == 0) return size; CheckIntPositivePowerOfTwo(alignmentPowerOfTwo); return (size + alignmentPowerOfTwo - 1) & ~(alignmentPowerOfTwo - 1); } /// /// Returns an allocation size in bytes that factors in alignment. /// /// /// // 55 aligned to 16 is 64. /// ulong size = CollectionHelper.Align(55, 16); /// /// The size to align. /// A non-zero, positive power of two. /// The smallest integer that is greater than or equal to `size` and is a multiple of `alignmentPowerOfTwo`. /// Thrown if `alignmentPowerOfTwo` is not a non-zero, positive power of two. public static ulong Align(ulong size, ulong alignmentPowerOfTwo) { if (alignmentPowerOfTwo == 0) return size; CheckUlongPositivePowerOfTwo(alignmentPowerOfTwo); return (size + alignmentPowerOfTwo - 1) & ~(alignmentPowerOfTwo - 1); } /// /// Returns true if the address represented by the pointer has a given alignment. /// /// The pointer. /// A non-zero, positive power of two. /// True if the address is a multiple of `alignmentPowerOfTwo`. /// Thrown if `alignmentPowerOfTwo` is not a non-zero, positive power of two. public static unsafe bool IsAligned(void* p, int alignmentPowerOfTwo) { CheckIntPositivePowerOfTwo(alignmentPowerOfTwo); return ((ulong)p & ((ulong)alignmentPowerOfTwo - 1)) == 0; } /// /// Returns true if an offset has a given alignment. /// /// An offset /// A non-zero, positive power of two. /// True if the offset is a multiple of `alignmentPowerOfTwo`. /// Thrown if `alignmentPowerOfTwo` is not a non-zero, positive power of two. public static bool IsAligned(ulong offset, int alignmentPowerOfTwo) { CheckIntPositivePowerOfTwo(alignmentPowerOfTwo); return (offset & ((ulong)alignmentPowerOfTwo - 1)) == 0; } /// /// Returns true if a positive value is a non-zero power of two. /// /// Result is invalid if `value < 0`. /// A positive value. /// True if the value is a non-zero, positive power of two. public static bool IsPowerOfTwo(int value) { return (value & (value - 1)) == 0; } /// /// Returns a (non-cryptographic) hash of a memory block. /// /// The hash function used is [djb2](http://web.archive.org/web/20190508211657/http://www.cse.yorku.ca/~oz/hash.html). /// A buffer. /// The number of bytes to hash. /// A hash of the bytes. public static unsafe uint Hash(void* ptr, int bytes) { // djb2 - Dan Bernstein hash function // http://web.archive.org/web/20190508211657/http://www.cse.yorku.ca/~oz/hash.html byte* str = (byte*)ptr; ulong hash = 5381; while (bytes > 0) { ulong c = str[--bytes]; hash = ((hash << 5) + hash) + c; } return (uint)hash; } [ExcludeFromBurstCompatTesting("Used only for debugging, and uses managed strings")] internal static void WriteLayout(Type type) { Console.WriteLine($" Offset | Bytes | Name Layout: {0}", type.Name); var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (var field in fields) { Console.WriteLine(" {0, 6} | {1, 6} | {2}" , Marshal.OffsetOf(type, field.Name) , Marshal.SizeOf(field.FieldType) , field.Name ); } } internal static bool ShouldDeallocate(AllocatorManager.AllocatorHandle allocator) { // Allocator.Invalid == container is not initialized. // Allocator.None == container is initialized, but container doesn't own data. return allocator.ToAllocator > Allocator.None; } /// /// Tell Burst that an integer can be assumed to map to an always positive value. /// /// The integer that is always positive. /// Returns `x`, but allows the compiler to assume it is always positive. [return: AssumeRange(0, int.MaxValue)] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static int AssumePositive(int value) { return value; } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] [GenerateTestsForBurstCompatibility(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS", GenericTypeArguments = new[] { typeof(NativeArray) })] internal static void CheckIsUnmanaged() { if (!UnsafeUtility.IsUnmanaged()) { throw new ArgumentException($"{typeof(T)} used in native collection is not blittable or not primitive"); } } #if ENABLE_UNITY_COLLECTIONS_CHECKS [GenerateTestsForBurstCompatibility(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS", GenericTypeArguments = new[] { typeof(NativeArray) })] internal static void InitNativeContainer(AtomicSafetyHandle handle) { if (UnsafeUtility.IsNativeContainerType()) AtomicSafetyHandle.SetNestedContainer(handle, true); } #endif [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] internal static void CheckIntPositivePowerOfTwo(int value) { var valid = (value > 0) && ((value & (value - 1)) == 0); if (!valid) { throw new ArgumentException($"Alignment requested: {value} is not a non-zero, positive power of two."); } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] internal static void CheckUlongPositivePowerOfTwo(ulong value) { var valid = (value > 0) && ((value & (value - 1)) == 0); if (!valid) { throw new ArgumentException($"Alignment requested: {value} is not a non-zero, positive power of two."); } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void CheckIndexInRange(int index, int length) { // This checks both < 0 and >= Length with one comparison if ((uint)index >= (uint)length) throw new IndexOutOfRangeException($"Index {index} is out of range in container of '{length}' Length."); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] internal static void CheckCapacityInRange(int capacity, int length) { if (capacity < 0) throw new ArgumentOutOfRangeException($"Capacity {capacity} must be positive."); if (capacity < length) throw new ArgumentOutOfRangeException($"Capacity {capacity} is out of range in container of '{length}' Length."); } /// /// Create a NativeArray, using a provided allocator that implements IAllocator. /// /// The type of the elements. /// The type of allocator. /// The number of elements to allocate. /// The allocator to use. /// Options for allocation, such as whether to clear the memory. /// Returns the NativeArray that was created. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int), typeof(AllocatorManager.AllocatorHandle) })] public static NativeArray CreateNativeArray(int length, ref U allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory) where T : unmanaged where U : unmanaged, AllocatorManager.IAllocator { NativeArray nativeArray; if (!allocator.IsCustomAllocator) { nativeArray = new NativeArray(length, allocator.ToAllocator, options); } else { nativeArray = new NativeArray(); nativeArray.Initialize(length, ref allocator, options); } return nativeArray; } /// /// Create a NativeArray, using a provided AllocatorHandle. /// /// The type of the elements. /// The number of elements to allocate. /// The AllocatorHandle to use. /// Options for allocation, such as whether to clear the memory. /// Returns the NativeArray that was created. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public static NativeArray CreateNativeArray(int length, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory) where T : unmanaged { NativeArray nativeArray; if(!AllocatorManager.IsCustomAllocator(allocator)) { nativeArray = new NativeArray(length, allocator.ToAllocator, options); } else { nativeArray = new NativeArray(); nativeArray.Initialize(length, allocator, options); } return nativeArray; } /// /// Create a NativeArray from another NativeArray, using a provided AllocatorHandle. /// /// The type of the elements. /// The NativeArray to make a copy of. /// The AllocatorHandle to use. /// Returns the NativeArray that was created. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public static NativeArray CreateNativeArray(NativeArray array, AllocatorManager.AllocatorHandle allocator) where T : unmanaged { NativeArray nativeArray; if (!AllocatorManager.IsCustomAllocator(allocator)) { nativeArray = new NativeArray(array, allocator.ToAllocator); } else { nativeArray = new NativeArray(); nativeArray.Initialize(array.Length, allocator); nativeArray.CopyFrom(array); } return nativeArray; } /// /// Create a NativeArray from a managed array, using a provided AllocatorHandle. /// /// The type of the elements. /// The managed array to make a copy of. /// The AllocatorHandle to use. /// Returns the NativeArray that was created. [ExcludeFromBurstCompatTesting("Managed array")] public static NativeArray CreateNativeArray(T[] array, AllocatorManager.AllocatorHandle allocator) where T : unmanaged { NativeArray nativeArray; if (!AllocatorManager.IsCustomAllocator(allocator)) { nativeArray = new NativeArray(array, allocator.ToAllocator); } else { nativeArray = new NativeArray(); nativeArray.Initialize(array.Length, allocator); nativeArray.CopyFrom(array); } return nativeArray; } /// /// Create a NativeArray from a managed array, using a provided Allocator. /// /// The type of the elements. /// The type of allocator. /// The managed array to make a copy of. /// The Allocator to use. /// Returns the NativeArray that was created. [ExcludeFromBurstCompatTesting("Managed array")] public static NativeArray CreateNativeArray(T[] array, ref U allocator) where T : unmanaged where U : unmanaged, AllocatorManager.IAllocator { NativeArray nativeArray; if (!allocator.IsCustomAllocator) { nativeArray = new NativeArray(array, allocator.ToAllocator); } else { nativeArray = new NativeArray(); nativeArray.Initialize(array.Length, ref allocator); nativeArray.CopyFrom(array); } return nativeArray; } /// /// Dispose a NativeArray from an AllocatorHandle where it is allocated. /// /// The type of the elements. /// The NativeArray to make a copy of. /// The AllocatorHandle used to allocate the NativeArray. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public static void DisposeNativeArray(NativeArray nativeArray, AllocatorManager.AllocatorHandle allocator) where T : unmanaged { nativeArray.DisposeCheckAllocator(); } /// /// Dispose a NativeArray from an AllocatorHandle where it is allocated. /// /// The type of the elements. /// The NativeArray to be disposed. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public static void Dispose(NativeArray nativeArray) where T : unmanaged { nativeArray.DisposeCheckAllocator(); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] static void CheckConvertArguments(int length) where T : unmanaged { if (length < 0) throw new ArgumentOutOfRangeException(nameof(length), "Length must be >= 0"); if (!UnsafeUtility.IsUnmanaged()) { throw new InvalidOperationException( $"{typeof(T)} used in NativeArray<{typeof(T)}> must be unmanaged (contain no managed types)."); } } #if ENABLE_UNITY_COLLECTIONS_CHECKS [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] static void InitNestedNativeContainer(AtomicSafetyHandle handle) where T : unmanaged { if (UnsafeUtility.IsNativeContainerType()) { AtomicSafetyHandle.SetNestedContainer(handle, true); } } #endif /// /// Convert existing data into a NativeArray. /// /// The type of the elements. /// Pointer to the data to be converted. /// The count of elements. /// The Allocator to use. /// Use temporary memory atomic safety handle. /// Returns the NativeArray that was created. /// The caller is still the owner of the data. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public static unsafe NativeArray ConvertExistingDataToNativeArray(void* dataPointer, int length, AllocatorManager.AllocatorHandle allocator, bool setTempMemoryHandle = false) where T : unmanaged { #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG CheckConvertArguments(length); #endif NativeArray nativeArray = default; nativeArray.m_Buffer = dataPointer; nativeArray.m_Length = length; if (!allocator.IsCustomAllocator) { nativeArray.m_AllocatorLabel = allocator.ToAllocator; } else { nativeArray.m_AllocatorLabel = Allocator.None; } #if ENABLE_UNITY_COLLECTIONS_CHECKS nativeArray.m_MinIndex = 0; nativeArray.m_MaxIndex = length - 1; if (setTempMemoryHandle) { NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, AtomicSafetyHandle.GetTempMemoryHandle()); } #endif return nativeArray; } /// /// Convert NativeList into a NativeArray. /// /// The type of the elements. /// NativeList to be converted. /// The count of elements. /// The Allocator to use. /// Returns the NativeArray that was created. /// There is a caveat if users would like to transfer memory ownership from the NativeList to the converted NativeArray. /// NativeList implementation includes two memory allocations, one holds its header, another holds the list data. /// After convertion, the converted NativeArray holds the list data and dispose the array only free the list data. /// Users need to manually free the list header to avoid memory leaks, for example after convertion call, /// AllocatorManager.Free(allocator, nativeList.m_ListData); [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public static unsafe NativeArray ConvertExistingNativeListToNativeArray(ref NativeList nativeList, int length, AllocatorManager.AllocatorHandle allocator) where T : unmanaged { NativeArray nativeArray = ConvertExistingDataToNativeArray(nativeList.GetUnsafePtr(), length, allocator); #if ENABLE_UNITY_COLLECTIONS_CHECKS var safetyHandle = NativeListUnsafeUtility.GetAtomicSafetyHandle(ref nativeList); NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeArray, safetyHandle); InitNestedNativeContainer(nativeArray.m_Safety); #endif return nativeArray; } #if ENABLE_UNITY_COLLECTIONS_CHECKS [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) }, RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS", CompileTarget = GenerateTestsForBurstCompatibilityAttribute.BurstCompatibleCompileTarget.Editor)] internal static AtomicSafetyHandle GetNativeArraySafetyHandle(ref NativeArray nativeArray) where T : unmanaged { return nativeArray.m_Safety; } #endif /// /// Create a NativeParallelMultiHashMap from a managed array, using a provided Allocator. /// /// The type of the keys. /// The type of the values. /// The type of allocator. /// The desired capacity of the NativeParallelMultiHashMap. /// The Allocator to use. /// Returns the NativeParallelMultiHashMap that was created. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int), typeof(int), typeof(AllocatorManager.AllocatorHandle) })] public static NativeParallelMultiHashMap CreateNativeParallelMultiHashMap(int length, ref U allocator) where TKey : unmanaged, IEquatable where TValue : unmanaged where U : unmanaged, AllocatorManager.IAllocator { var container = new NativeParallelMultiHashMap(); container.Initialize(length, ref allocator); return container; } /// /// Empty job type used for Burst compilation testing /// [BurstCompile] public struct DummyJob : IJob { /// /// Empty job execute function used for Burst compilation testing /// public void Execute() { } } /// /// Checks that reflection data was properly registered for a job. /// /// This should be called before instantiating JobsUtility.JobScheduleParameters in order to report to the user if they need to take action. /// The reflection data pointer. /// Job type [GenerateTestsForBurstCompatibility(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS", GenericTypeArguments = new[] { typeof(DummyJob) })] [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] public static void CheckReflectionDataCorrect(IntPtr reflectionData) { #if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG bool burstCompiled = true; CheckReflectionDataCorrectInternal(reflectionData, ref burstCompiled); if (burstCompiled && reflectionData == IntPtr.Zero) throw new InvalidOperationException("Reflection data was not set up by an Initialize() call. For generic job types, please include [assembly: RegisterGenericJobType(typeof(MyJob))] in your source file."); #endif } #if ENABLE_UNITY_COLLECTIONS_CHECKS /// /// Creates a new AtomicSafetyHandle that is valid until [[CollectionHelper.DisposeSafetyHandle]] is called. /// /// The AllocatorHandle to use. /// Safety handle. [GenerateTestsForBurstCompatibility(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS")] public static AtomicSafetyHandle CreateSafetyHandle(AllocatorManager.AllocatorHandle allocator) { if (allocator.IsCustomAllocator) { return AtomicSafetyHandle.Create(); } return (allocator.ToAllocator == Allocator.Temp) ? AtomicSafetyHandle.GetTempMemoryHandle() : AtomicSafetyHandle.Create(); } /// /// Disposes a previously created AtomicSafetyHandle. /// /// Safety handle. [GenerateTestsForBurstCompatibility(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS")] public static void DisposeSafetyHandle(ref AtomicSafetyHandle handle) { AtomicSafetyHandle.CheckDeallocateAndThrow(handle); // If the safety handle is for a temp allocation, create a new safety handle for this instance which can be marked as invalid // Setting it to new AtomicSafetyHandle is not enough since the handle needs a valid node pointer in order to give the correct errors if (AtomicSafetyHandle.IsTempMemoryHandle(handle)) { int staticSafetyId = handle.staticSafetyId; handle = AtomicSafetyHandle.Create(); handle.staticSafetyId = staticSafetyId; } AtomicSafetyHandle.Release(handle); } static unsafe void CreateStaticSafetyIdInternal(ref int id, in FixedString512Bytes name) { id = AtomicSafetyHandle.NewStaticSafetyId(name.GetUnsafePtr(), name.Length); } [BurstDiscard] static void CreateStaticSafetyIdInternal(ref int id) { CreateStaticSafetyIdInternal(ref id, typeof(T).ToString()); } /// /// Assigns the provided static safety ID to an [[AtomicSafetyHandle]]. The ID's owner type name and any custom error messages are used by the job debugger when reporting errors involving the target handle. /// /// This is preferable to AtomicSafetyHandle.NewStaticSafetyId as it is compatible with burst. /// Type of container safety handle refers to. /// Safety handle. /// The static safety ID to associate with the provided handle. This ID must have been allocated with ::ref::NewStaticSafetyId. [GenerateTestsForBurstCompatibility(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS", GenericTypeArguments = new[] { typeof(NativeArray) })] public static void SetStaticSafetyId(ref AtomicSafetyHandle handle, ref int sharedStaticId) { if (sharedStaticId == 0) { // This will eventually either work with burst supporting a subset of typeof() // or something similar to Burst.BurstRuntime.GetTypeName() will be implemented // JIRA issue DOTS-5685 CreateStaticSafetyIdInternal(ref sharedStaticId); } AtomicSafetyHandle.SetStaticSafetyId(ref handle, sharedStaticId); } /// /// Assigns the provided static safety ID to an [[AtomicSafetyHandle]]. The ID's owner type name and any custom error messages are used by the job debugger when reporting errors involving the target handle. /// /// This is preferable to AtomicSafetyHandle.NewStaticSafetyId as it is compatible with burst. /// Safety handle. /// The static safety ID to associate with the provided handle. This ID must have been allocated with ::ref::NewStaticSafetyId. /// The name of the resource type. [GenerateTestsForBurstCompatibility(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS")] public static unsafe void SetStaticSafetyId(ref AtomicSafetyHandle handle, ref int sharedStaticId, FixedString512Bytes name) { if (sharedStaticId == 0) { CreateStaticSafetyIdInternal(ref sharedStaticId, name); } AtomicSafetyHandle.SetStaticSafetyId(ref handle, sharedStaticId); } #endif [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] [BurstDiscard] static void CheckReflectionDataCorrectInternal(IntPtr reflectionData, ref bool burstCompiled) { if (reflectionData == IntPtr.Zero) throw new InvalidOperationException($"Reflection data was not set up by an Initialize() call. For generic job types, please include [assembly: RegisterGenericJobType(typeof({typeof(T)}))] in your source file."); burstCompiled = false; } } }