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;
#if !NET_DOTS
using System.Reflection;
#endif
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.
///
[BurstCompatible]
public static class CollectionHelper
{
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
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;
}
[NotBurstCompatible /* Used only for debugging. */]
internal static void WriteLayout(Type type)
{
#if !NET_DOTS
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
);
}
#else
_ = type;
#endif
}
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)]
internal static int AssumePositive(int value)
{
return value;
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
[BurstDiscard] // Must use BurstDiscard because UnsafeUtility.IsUnmanaged is not burstable.
[NotBurstCompatible /* Used only for debugging. */]
internal static void CheckIsUnmanaged()
{
if (!UnsafeUtility.IsValidNativeContainerElementType())
{
throw new ArgumentException($"{typeof(T)} used in native collection is not blittable, not primitive, or contains a type tagged as NativeContainer");
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
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")]
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")]
internal static void CheckIndexInRange(int index, int length)
{
if (index < 0)
throw new IndexOutOfRangeException($"Index {index} must be positive.");
if (index >= 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 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.
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(AllocatorManager.AllocatorHandle) })]
public static NativeArray CreateNativeArray(int length, ref U allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
where T : struct
where U : unmanaged, AllocatorManager.IAllocator
{
NativeArray container;
if (!allocator.IsCustomAllocator)
{
container = new NativeArray(length, allocator.ToAllocator, options);
}
else
{
container = new NativeArray();
container.Initialize(length, ref allocator, options);
}
return container;
}
///
/// Create a NativeArray, using a provided AllocatorHandle.
///
/// 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.
[BurstCompatible(GenericTypeArguments = new[] { typeof(int) })]
public static NativeArray CreateNativeArray(int length, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
where T : struct
{
NativeArray container;
if(!AllocatorManager.IsCustomAllocator(allocator))
{
container = new NativeArray(length, allocator.ToAllocator, options);
}
else
{
container = new NativeArray();
container.Initialize(length, allocator, options);
}
return container;
}
///
/// Create a NativeArray from another NativeArray, using a provided AllocatorHandle.
///
/// The NativeArray to make a copy of.
/// The AllocatorHandle to use.
/// Returns the NativeArray that was created.
[BurstCompatible(GenericTypeArguments = new[] { typeof(int) })]
public static NativeArray CreateNativeArray(NativeArray array, AllocatorManager.AllocatorHandle allocator)
where T : struct
{
NativeArray container;
if (!AllocatorManager.IsCustomAllocator(allocator))
{
container = new NativeArray(array, allocator.ToAllocator);
}
else
{
container = new NativeArray();
container.Initialize(array.Length, allocator);
container.CopyFrom(array);
}
return container;
}
///
/// Create a NativeArray from a managed array, using a provided AllocatorHandle.
///
/// The managed array to make a copy of.
/// The AllocatorHandle to use.
/// Returns the NativeArray that was created.
[NotBurstCompatible]
public static NativeArray CreateNativeArray(T[] array, AllocatorManager.AllocatorHandle allocator)
where T : struct
{
NativeArray container;
if (!AllocatorManager.IsCustomAllocator(allocator))
{
container = new NativeArray(array, allocator.ToAllocator);
}
else
{
container = new NativeArray();
container.Initialize(array.Length, allocator);
container.CopyFrom(array);
}
return container;
}
///
/// Create a NativeArray from a managed array, using a provided Allocator.
///
/// The managed array to make a copy of.
/// The Allocator to use.
/// Returns the NativeArray that was created.
[NotBurstCompatible]
public static NativeArray CreateNativeArray(T[] array, ref U allocator)
where T : struct
where U : unmanaged, AllocatorManager.IAllocator
{
NativeArray container;
if (!allocator.IsCustomAllocator)
{
container = new NativeArray(array, allocator.ToAllocator);
}
else
{
container = new NativeArray();
container.Initialize(array.Length, ref allocator);
container.CopyFrom(array);
}
return container;
}
///
/// Create a NativeParallelHashMap from a managed array, using a provided Allocator.
///
/// The desired capacity of the NativeMultiHashMap.
/// The Allocator to use.
/// Returns the NativeMultiHashMap that was created.
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(int), typeof(AllocatorManager.AllocatorHandle) })]
public static NativeParallelHashMap CreateNativeParallelHashMap(int length, ref U allocator)
where TKey : struct, IEquatable
where TValue : struct
where U : unmanaged, AllocatorManager.IAllocator
{
return new NativeParallelHashMap();
}
///
/// Create a NativeMultiHashMap from a managed array, using a provided Allocator.
///
/// The desired capacity of the NativeMultiHashMap.
/// The Allocator to use.
/// Returns the NativeMultiHashMap that was created.
[Obsolete("CreateNativeMultiHashMap is renamed to CreateNativeParallelHashMap. (UnityUpgradable) -> CreateNativeParallelHashMap(*)", true)]
[BurstCompatible(GenericTypeArguments = new[] { typeof(int), typeof(int), typeof(AllocatorManager.AllocatorHandle) })]
public static NativeHashMap CreateNativeMultiHashMap(int length, ref U allocator)
where TKey : struct, IEquatable
where TValue : struct
where U : unmanaged, AllocatorManager.IAllocator
{
return new NativeHashMap { };
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
[BurstCompatible(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS")]
internal static AtomicSafetyHandle CreateSafetyHandle(AllocatorManager.AllocatorHandle allocator)
{
if (allocator.IsCustomAllocator)
{
return AtomicSafetyHandle.Create();
}
return (allocator.ToAllocator == Allocator.Temp) ? AtomicSafetyHandle.GetTempMemoryHandle() : AtomicSafetyHandle.Create();
}
[BurstCompatible(RequiredUnityDefine = "ENABLE_UNITY_COLLECTIONS_CHECKS")]
internal static void DisposeSafetyHandle(ref AtomicSafetyHandle safety)
{
AtomicSafetyHandle.CheckDeallocateAndThrow(safety);
// 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(safety))
{
int staticSafetyId = safety.staticSafetyId;
safety = AtomicSafetyHandle.Create();
safety.staticSafetyId = staticSafetyId;
}
AtomicSafetyHandle.Release(safety);
}
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());
}
///
/// Returns a static safety id which better identifies resources in safety system messages.
///
/// This is preferable to AtomicSafetyHandle.NewStaticSafetyId as it is compatible with burst.
/// The name of the resource type.
/// An int representing the static safety id for this resource.
[BurstCompatible(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 https://jira.unity3d.com/browse/DOTS-5685
CreateStaticSafetyIdInternal(ref sharedStaticId);
}
AtomicSafetyHandle.SetStaticSafetyId(ref handle, sharedStaticId);
}
///
/// Returns a static safety id which better identifies resources in safety system messages.
///
/// This is preferable to AtomicSafetyHandle.NewStaticSafetyId as it is compatible with burst.
/// The name of the resource type.
/// An int representing the static safety id for this resource.
[BurstCompatible(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
}
}