using System; using System.Diagnostics; using System.Runtime.InteropServices; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; namespace Unity.Collections { /// /// An unmanaged single value. /// /// The functional equivalent of an array of length 1. /// When you need just one value, NativeReference can be preferable to an array because it better conveys the intent. /// The type of value. [StructLayout(LayoutKind.Sequential)] [NativeContainer] [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public unsafe struct NativeReference : INativeDisposable , IEquatable> where T : unmanaged { [NativeDisableUnsafePtrRestriction] internal void* m_Data; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; static readonly SharedStatic s_SafetyId = SharedStatic.GetOrCreate>(); #if REMOVE_DISPOSE_SENTINEL #else [NativeSetClassTypeToNullOnSchedule] DisposeSentinel m_DisposeSentinel; #endif #endif internal AllocatorManager.AllocatorHandle m_AllocatorLabel; /// /// Initializes and returns an instance of NativeReference. /// /// The allocator to use. /// Whether newly allocated bytes should be zeroed out. public NativeReference(AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory) { Allocate(allocator, out this); if (options == NativeArrayOptions.ClearMemory) { UnsafeUtility.MemClear(m_Data, UnsafeUtility.SizeOf()); } } /// /// Initializes and returns an instance of NativeReference. /// /// The allocator to use. /// The initial value. public NativeReference(T value, AllocatorManager.AllocatorHandle allocator) { Allocate(allocator, out this); *(T*)m_Data = value; } static void Allocate(AllocatorManager.AllocatorHandle allocator, out NativeReference reference) { CollectionHelper.CheckAllocator(allocator); reference = default; reference.m_Data = Memory.Unmanaged.Allocate(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), allocator); reference.m_AllocatorLabel = allocator; #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL reference.m_Safety = CollectionHelper.CreateSafetyHandle(allocator); #else if (allocator.IsCustomAllocator) { reference.m_Safety = AtomicSafetyHandle.Create(); reference.m_DisposeSentinel = null; } else { DisposeSentinel.Create(out reference.m_Safety, out reference.m_DisposeSentinel, 1, allocator.ToAllocator); } #endif CollectionHelper.SetStaticSafetyId>(ref reference.m_Safety, ref s_SafetyId.Data); #endif } /// /// The value stored in this reference. /// /// The new value to store in this reference. /// The value stored in this reference. public T Value { get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return *(T*)m_Data; } set { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif *(T*)m_Data = value; } } /// /// Whether this reference has been allocated (and not yet deallocated). /// /// True if this reference has been allocated (and not yet deallocated). public bool IsCreated => m_Data != null; /// /// Releases all resources (memory and safety handles). /// public void Dispose() { CheckNotDisposed(); if (CollectionHelper.ShouldDeallocate(m_AllocatorLabel)) { #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL CollectionHelper.DisposeSafetyHandle(ref m_Safety); #else DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel); #endif #endif Memory.Unmanaged.Free(m_Data, m_AllocatorLabel); m_AllocatorLabel = Allocator.Invalid; } m_Data = null; } /// /// Creates and schedules a job that will release all resources (memory and safety handles) of this reference. /// /// A job handle. The newly scheduled job will depend upon this handle. /// The handle of a new job that will release all resources (memory and safety handles) of this reference. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Dispose(JobHandle inputDeps) { CheckNotDisposed(); if (CollectionHelper.ShouldDeallocate(m_AllocatorLabel)) { #if ENABLE_UNITY_COLLECTIONS_CHECKS #if REMOVE_DISPOSE_SENTINEL #else // [DeallocateOnJobCompletion] is not supported, but we want the deallocation // to happen in a thread. DisposeSentinel needs to be cleared on main thread. // AtomicSafetyHandle can be destroyed after the job was scheduled (Job scheduling // will check that no jobs are writing to the container). DisposeSentinel.Clear(ref m_DisposeSentinel); #endif #endif var jobHandle = new NativeReferenceDisposeJob { Data = new NativeReferenceDispose { m_Data = m_Data, m_AllocatorLabel = m_AllocatorLabel, #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = m_Safety #endif } }.Schedule(inputDeps); #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.Release(m_Safety); #endif m_Data = null; m_AllocatorLabel = Allocator.Invalid; return jobHandle; } m_Data = null; return inputDeps; } /// /// Copy the value of another reference to this reference. /// /// The reference to copy from. public void CopyFrom(NativeReference reference) { Copy(this, reference); } /// /// Copy the value of this reference to another reference. /// /// The reference to copy to. public void CopyTo(NativeReference reference) { Copy(reference, this); } /// /// Returns true if the value stored in this reference is equal to the value stored in another reference. /// /// A reference to compare with. /// True if the value stored in this reference is equal to the value stored in another reference. [NotBurstCompatible] public bool Equals(NativeReference other) { return Value.Equals(other.Value); } /// /// Returns true if the value stored in this reference is equal to an object. /// /// Can only be equal if the object is itself a NativeReference. /// An object to compare with. /// True if the value stored in this reference is equal to the object. [NotBurstCompatible] public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } return obj is NativeReference && Equals((NativeReference)obj); } /// /// Returns the hash code of this reference. /// /// The hash code of this reference. public override int GetHashCode() { return Value.GetHashCode(); } /// /// Returns true if the values stored in two references are equal. /// /// A reference. /// Another reference. /// True if the two values are equal. public static bool operator ==(NativeReference left, NativeReference right) { return left.Equals(right); } /// /// Returns true if the values stored in two references are unequal. /// /// A reference. /// Another reference. /// True if the two values are unequal. public static bool operator !=(NativeReference left, NativeReference right) { return !left.Equals(right); } /// /// Copies the value of a reference to another reference. /// /// The destination reference. /// The source reference. public static void Copy(NativeReference dst, NativeReference src) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(src.m_Safety); AtomicSafetyHandle.CheckWriteAndThrow(dst.m_Safety); #endif UnsafeUtility.MemCpy(dst.m_Data, src.m_Data, UnsafeUtility.SizeOf()); } /// /// Returns a read-only reference aliasing the value of this reference. /// /// A read-only reference aliasing the value of this reference. public ReadOnly AsReadOnly() { #if ENABLE_UNITY_COLLECTIONS_CHECKS return new ReadOnly(m_Data, ref m_Safety); #else return new ReadOnly(m_Data); #endif } /// /// A read-only alias for the value of a NativeReference. Does not have its own allocated storage. /// [NativeContainer] [NativeContainerIsReadOnly] [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public unsafe struct ReadOnly { [NativeDisableUnsafePtrRestriction] readonly void* m_Data; #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); [BurstCompatible(CompileTarget = BurstCompatibleAttribute.BurstCompatibleCompileTarget.Editor)] internal ReadOnly(void* data, ref AtomicSafetyHandle safety) { m_Data = data; m_Safety = safety; CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data); } #else internal ReadOnly(void* data) { m_Data = data; } #endif /// /// The value aliased by this reference. /// /// The value aliased by the reference. public T Value { get { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return *(T*)m_Data; } } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckNotDisposed() { if (m_Data == null) throw new ObjectDisposedException("The NativeReference is already disposed."); } } [NativeContainer] unsafe struct NativeReferenceDispose { [NativeDisableUnsafePtrRestriction] internal void* m_Data; internal AllocatorManager.AllocatorHandle m_AllocatorLabel; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; #endif public void Dispose() { Memory.Unmanaged.Free(m_Data, m_AllocatorLabel); } } [BurstCompile] struct NativeReferenceDisposeJob : IJob { internal NativeReferenceDispose Data; public void Execute() { Data.Dispose(); } } } namespace Unity.Collections.LowLevel.Unsafe { /// /// Provides extension methods for NativeReference. /// [BurstCompatible] public static class NativeReferenceUnsafeUtility { /// /// Returns a pointer to this reference's stored value. /// /// Performs a job safety check for read-write access. /// The type of the value. /// The reference. /// A pointer to this reference's stored value. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public static unsafe void* GetUnsafePtr(this NativeReference reference) where T : unmanaged { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(reference.m_Safety); #endif return reference.m_Data; } /// /// Returns a pointer to this reference's stored value. /// /// Performs a job safety check for read-only access. /// The type of the value. /// The reference. /// A pointer to this reference's stored value. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public static unsafe void* GetUnsafeReadOnlyPtr(this NativeReference reference) where T : unmanaged { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(reference.m_Safety); #endif return reference.m_Data; } /// /// Returns a pointer to this reference's stored value. /// /// Performs no job safety checks. /// The type of the value. /// The reference. /// A pointer to this reference's stored value. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public static unsafe void* GetUnsafePtrWithoutChecks(this NativeReference reference) where T : unmanaged { return reference.m_Data; } } }