允许您创建自己的自定义原生容器。
借助原生容器,您可以创建新的容器类型,此类容器不分配任何 GC 内存,也不对分配操作进行显式控制。它们所包含的数据必须为 Blittable 类型。原生容器还可以在作业中使用,作业系统可识别 NativeContainers,作业调试器能够确保对容器的所有访问都是安全的,如果任何所用代码包含竞争条件或包含不确定性行为,该调试器会抛出异常。
原生容器必须嵌入一个 AtomicSafetyHandle,以确保作业调试系统能够检测到所有可能的竞争条件。DisposeSentinel 用于立即检测任何泄漏。
请注意,务必要仔细按照以下代码示例创建您自己的自定义容器。创建自定义容器时,尤其是向作业集成时,强烈建议为所有场景添加测试覆盖,以确保预防所有竞争条件。如果未正确实现,自定义容器很容易导致 Unity 崩溃,而不会抛出任何有用的异常。
using System.Diagnostics; using System; using Unity.Collections.LowLevel.Unsafe; using Unity.Collections;
// Marks our struct as a NativeContainer. // If ENABLE_UNITY_COLLECTIONS_CHECKS is enabled, // it is required that m_Safety & m_DisposeSentinel are declared, with exactly these names. [NativeContainer] // The [NativeContainerSupportsMinMaxWriteRestriction] enables // a common jobification pattern where an IJobParallelFor is split into ranges // And the job is only allowed to access the index range being Executed by that worker thread. // Effectively limiting access of the array to the specific index passed into the Execute(int index) method // This attribute requires m_MinIndex & m_MaxIndex to exist. // and the container is expected to perform out of bounds checks against it. // m_MinIndex & m_MaxIndex will be set by the job scheduler before Execute is called on the worker thread. [NativeContainerSupportsMinMaxWriteRestriction] // It is recommended to always implement a Debugger proxy // to visualize the contents of the array in VisualStudio and other tools. [DebuggerDisplay("Length = {Length}")] [DebuggerTypeProxy(typeof(NativeCustomArrayDebugView<>))] public struct NativeCustomArray<T> : IDisposable where T : struct { internal IntPtr m_Buffer; internal int m_Length;
#if ENABLE_UNITY_COLLECTIONS_CHECKS internal int m_MinIndex; internal int m_MaxIndex; internal AtomicSafetyHandle m_Safety; internal DisposeSentinel m_DisposeSentinel; #endif
internal Allocator m_AllocatorLabel;
public NativeCustomArray(int length, Allocator allocator) { ulong totalSize = (ulong)UnsafeUtility.SizeOf<T>() * (ulong)length;
#if ENABLE_UNITY_COLLECTIONS_CHECKS // Native allocation is only valid for Temp, Job and Persistent if (allocator <= Allocator.None) throw new ArgumentException("Allocator must be Temp, TempJob or Persistent", "allocator"); if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be >= 0"); if (!UnsafeUtility.IsBlittable<T>()) throw new ArgumentException(string.Format("{0} used in NativeCustomArray<{0}> must be blittable", typeof(T))); #endif
m_Buffer = UnsafeUtility.Malloc(totalSize, UnsafeUtility.AlignOf<T>(), allocator); UnsafeUtility.MemClear(m_Buffer , totalSize);
m_Length = length; m_AllocatorLabel = allocator;
#if ENABLE_UNITY_COLLECTIONS_CHECKS m_MinIndex = 0; m_MaxIndex = length - 1; DisposeSentinel.Create(m_Buffer, allocator, out m_Safety, out m_DisposeSentinel, 0); #endif }
public int Length { get { return m_Length; } }
public unsafe T this[int index] { get { #if ENABLE_UNITY_COLLECTIONS_CHECKS // If the container is currently not allowed to read from the buffer // then this will throw an exception. // This handles all cases, from already disposed containers // to safe multithreaded access. AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
// Perform out of range checks based on // the NativeContainerSupportsMinMaxWriteRestriction policy if (index < m_MinIndex || index > m_MaxIndex) FailOutOfRangeError(index); #endif // Read the element from the allocated native memory return UnsafeUtility.ReadArrayElement<T>(m_Buffer, index); }
set { #if ENABLE_UNITY_COLLECTIONS_CHECKS // If the container is currently not allowed to write to the buffer // then this will throw an exception. // This handles all cases, from already disposed containers // to safe multithreaded access. AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
// Perform out of range checks based on // the NativeContainerSupportsMinMaxWriteRestriction policy if (index < m_MinIndex || index > m_MaxIndex) FailOutOfRangeError(index); #endif // Writes value to the allocated native memory UnsafeUtility.WriteArrayElement(m_Buffer, index, value); } }
public T[] ToArray() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif
var array = new T[Length]; for (var i = 0; i < Length; i++) array[i] = UnsafeUtility.ReadArrayElement<T>(m_Buffer, i); return array; }
public bool IsCreated { get { return m_Buffer != IntPtr.Zero; } }
public void Dispose() { #if ENABLE_UNITY_COLLECTIONS_CHECKS DisposeSentinel.Dispose(m_Safety, ref m_DisposeSentinel); #endif
UnsafeUtility.Free(m_Buffer, m_AllocatorLabel); m_Buffer = IntPtr.Zero; m_Length = 0; }
#if ENABLE_UNITY_COLLECTIONS_CHECKS private void FailOutOfRangeError(int index) { if (index < Length && (m_MinIndex != 0 || m_MaxIndex != Length - 1)) throw new IndexOutOfRangeException(string.Format( "Index {0} is out of restricted IJobParallelFor range [{1}...{2}] in ReadWriteBuffer.\n" + "ReadWriteBuffers are restricted to only read & write the element at the job index. " + "You can use double buffering strategies to avoid race conditions due to " + "reading & writing in parallel to the same elements from a job.", index, m_MinIndex, m_MaxIndex));
throw new IndexOutOfRangeException(string.Format("Index {0} is out of range of '{1}' Length.", index, Length)); }
#endif }
// Visualizes the custom array in the C# debugger internal sealed class NativeCustomArrayDebugView<T> where T : struct { private NativeCustomArray<T> m_Array;
public NativeCustomArrayDebugView(NativeCustomArray<T> array) { m_Array = array; }
public T[] Items { get { return m_Array.ToArray(); } } }