440 lines
15 KiB
C#
440 lines
15 KiB
C#
|
using System;
|
||
|
using System.Diagnostics;
|
||
|
using System.Runtime.InteropServices;
|
||
|
using Unity.Burst;
|
||
|
using Unity.Collections.LowLevel.Unsafe;
|
||
|
using Unity.Jobs;
|
||
|
|
||
|
namespace Unity.Collections
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// An unmanaged single value.
|
||
|
/// </summary>
|
||
|
/// <remarks>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.</remarks>
|
||
|
/// <typeparam name="T">The type of value.</typeparam>
|
||
|
[StructLayout(LayoutKind.Sequential)]
|
||
|
[NativeContainer]
|
||
|
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
|
||
|
public unsafe struct NativeReference<T>
|
||
|
: INativeDisposable
|
||
|
, IEquatable<NativeReference<T>>
|
||
|
where T : unmanaged
|
||
|
{
|
||
|
[NativeDisableUnsafePtrRestriction]
|
||
|
internal void* m_Data;
|
||
|
|
||
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||
|
internal AtomicSafetyHandle m_Safety;
|
||
|
|
||
|
static readonly SharedStatic<int> s_SafetyId = SharedStatic<int>.GetOrCreate<NativeReference<T>>();
|
||
|
|
||
|
#if REMOVE_DISPOSE_SENTINEL
|
||
|
#else
|
||
|
[NativeSetClassTypeToNullOnSchedule]
|
||
|
DisposeSentinel m_DisposeSentinel;
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
internal AllocatorManager.AllocatorHandle m_AllocatorLabel;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initializes and returns an instance of NativeReference.
|
||
|
/// </summary>
|
||
|
/// <param name="allocator">The allocator to use.</param>
|
||
|
/// <param name="options">Whether newly allocated bytes should be zeroed out.</param>
|
||
|
public NativeReference(AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
|
||
|
{
|
||
|
Allocate(allocator, out this);
|
||
|
if (options == NativeArrayOptions.ClearMemory)
|
||
|
{
|
||
|
UnsafeUtility.MemClear(m_Data, UnsafeUtility.SizeOf<T>());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initializes and returns an instance of NativeReference.
|
||
|
/// </summary>
|
||
|
/// <param name="allocator">The allocator to use.</param>
|
||
|
/// <param name="value">The initial value.</param>
|
||
|
public NativeReference(T value, AllocatorManager.AllocatorHandle allocator)
|
||
|
{
|
||
|
Allocate(allocator, out this);
|
||
|
*(T*)m_Data = value;
|
||
|
}
|
||
|
|
||
|
static void Allocate(AllocatorManager.AllocatorHandle allocator, out NativeReference<T> reference)
|
||
|
{
|
||
|
CollectionHelper.CheckAllocator(allocator);
|
||
|
|
||
|
reference = default;
|
||
|
reference.m_Data = Memory.Unmanaged.Allocate(UnsafeUtility.SizeOf<T>(), UnsafeUtility.AlignOf<T>(), 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<NativeQueue<T>>(ref reference.m_Safety, ref s_SafetyId.Data);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The value stored in this reference.
|
||
|
/// </summary>
|
||
|
/// <param name="value">The new value to store in this reference.</param>
|
||
|
/// <value>The value stored in this reference.</value>
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Whether this reference has been allocated (and not yet deallocated).
|
||
|
/// </summary>
|
||
|
/// <value>True if this reference has been allocated (and not yet deallocated).</value>
|
||
|
public bool IsCreated => m_Data != null;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Releases all resources (memory and safety handles).
|
||
|
/// </summary>
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates and schedules a job that will release all resources (memory and safety handles) of this reference.
|
||
|
/// </summary>
|
||
|
/// <param name="inputDeps">A job handle. The newly scheduled job will depend upon this handle.</param>
|
||
|
/// <returns>The handle of a new job that will release all resources (memory and safety handles) of this reference.</returns>
|
||
|
[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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Copy the value of another reference to this reference.
|
||
|
/// </summary>
|
||
|
/// <param name="reference">The reference to copy from.</param>
|
||
|
public void CopyFrom(NativeReference<T> reference)
|
||
|
{
|
||
|
Copy(this, reference);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Copy the value of this reference to another reference.
|
||
|
/// </summary>
|
||
|
/// <param name="reference">The reference to copy to.</param>
|
||
|
public void CopyTo(NativeReference<T> reference)
|
||
|
{
|
||
|
Copy(reference, this);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns true if the value stored in this reference is equal to the value stored in another reference.
|
||
|
/// </summary>
|
||
|
/// <param name="other">A reference to compare with.</param>
|
||
|
/// <returns>True if the value stored in this reference is equal to the value stored in another reference.</returns>
|
||
|
[NotBurstCompatible]
|
||
|
public bool Equals(NativeReference<T> other)
|
||
|
{
|
||
|
return Value.Equals(other.Value);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns true if the value stored in this reference is equal to an object.
|
||
|
/// </summary>
|
||
|
/// <remarks>Can only be equal if the object is itself a NativeReference.</remarks>
|
||
|
/// <param name="obj">An object to compare with.</param>
|
||
|
/// <returns>True if the value stored in this reference is equal to the object.</returns>
|
||
|
[NotBurstCompatible]
|
||
|
public override bool Equals(object obj)
|
||
|
{
|
||
|
if (ReferenceEquals(null, obj))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return obj is NativeReference<T> && Equals((NativeReference<T>)obj);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the hash code of this reference.
|
||
|
/// </summary>
|
||
|
/// <returns>The hash code of this reference.</returns>
|
||
|
public override int GetHashCode()
|
||
|
{
|
||
|
return Value.GetHashCode();
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns true if the values stored in two references are equal.
|
||
|
/// </summary>
|
||
|
/// <param name="left">A reference.</param>
|
||
|
/// <param name="right">Another reference.</param>
|
||
|
/// <returns>True if the two values are equal.</returns>
|
||
|
public static bool operator ==(NativeReference<T> left, NativeReference<T> right)
|
||
|
{
|
||
|
return left.Equals(right);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns true if the values stored in two references are unequal.
|
||
|
/// </summary>
|
||
|
/// <param name="left">A reference.</param>
|
||
|
/// <param name="right">Another reference.</param>
|
||
|
/// <returns>True if the two values are unequal.</returns>
|
||
|
public static bool operator !=(NativeReference<T> left, NativeReference<T> right)
|
||
|
{
|
||
|
return !left.Equals(right);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Copies the value of a reference to another reference.
|
||
|
/// </summary>
|
||
|
/// <param name="dst">The destination reference.</param>
|
||
|
/// <param name="src">The source reference.</param>
|
||
|
public static void Copy(NativeReference<T> dst, NativeReference<T> 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<T>());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a read-only reference aliasing the value of this reference.
|
||
|
/// </summary>
|
||
|
/// <returns>A read-only reference aliasing the value of this reference.</returns>
|
||
|
public ReadOnly AsReadOnly()
|
||
|
{
|
||
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||
|
return new ReadOnly(m_Data, ref m_Safety);
|
||
|
#else
|
||
|
return new ReadOnly(m_Data);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// A read-only alias for the value of a NativeReference. Does not have its own allocated storage.
|
||
|
/// </summary>
|
||
|
[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<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<ReadOnly>();
|
||
|
|
||
|
[BurstCompatible(CompileTarget = BurstCompatibleAttribute.BurstCompatibleCompileTarget.Editor)]
|
||
|
internal ReadOnly(void* data, ref AtomicSafetyHandle safety)
|
||
|
{
|
||
|
m_Data = data;
|
||
|
m_Safety = safety;
|
||
|
CollectionHelper.SetStaticSafetyId<ReadOnly>(ref m_Safety, ref s_staticSafetyId.Data);
|
||
|
}
|
||
|
#else
|
||
|
internal ReadOnly(void* data)
|
||
|
{
|
||
|
m_Data = data;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// The value aliased by this reference.
|
||
|
/// </summary>
|
||
|
/// <value>The value aliased by the reference.</value>
|
||
|
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
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Provides extension methods for NativeReference.
|
||
|
/// </summary>
|
||
|
[BurstCompatible]
|
||
|
public static class NativeReferenceUnsafeUtility
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Returns a pointer to this reference's stored value.
|
||
|
/// </summary>
|
||
|
/// <remarks>Performs a job safety check for read-write access.</remarks>
|
||
|
/// <typeparam name="T">The type of the value.</typeparam>
|
||
|
/// <param name="reference">The reference.</param>
|
||
|
/// <returns>A pointer to this reference's stored value.</returns>
|
||
|
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
|
||
|
public static unsafe void* GetUnsafePtr<T>(this NativeReference<T> reference)
|
||
|
where T : unmanaged
|
||
|
{
|
||
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||
|
AtomicSafetyHandle.CheckWriteAndThrow(reference.m_Safety);
|
||
|
#endif
|
||
|
return reference.m_Data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a pointer to this reference's stored value.
|
||
|
/// </summary>
|
||
|
/// <remarks>Performs a job safety check for read-only access.</remarks>
|
||
|
/// <typeparam name="T">The type of the value.</typeparam>
|
||
|
/// <param name="reference">The reference.</param>
|
||
|
/// <returns>A pointer to this reference's stored value.</returns>
|
||
|
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
|
||
|
public static unsafe void* GetUnsafeReadOnlyPtr<T>(this NativeReference<T> reference)
|
||
|
where T : unmanaged
|
||
|
{
|
||
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||
|
AtomicSafetyHandle.CheckReadAndThrow(reference.m_Safety);
|
||
|
#endif
|
||
|
return reference.m_Data;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a pointer to this reference's stored value.
|
||
|
/// </summary>
|
||
|
/// <remarks>Performs no job safety checks.</remarks>
|
||
|
/// <typeparam name="T">The type of the value.</typeparam>
|
||
|
/// <param name="reference">The reference.</param>
|
||
|
/// <returns>A pointer to this reference's stored value.</returns>
|
||
|
[BurstCompatible(GenericTypeArguments = new [] { typeof(int) })]
|
||
|
public static unsafe void* GetUnsafePtrWithoutChecks<T>(this NativeReference<T> reference)
|
||
|
where T : unmanaged
|
||
|
{
|
||
|
return reference.m_Data;
|
||
|
}
|
||
|
}
|
||
|
}
|