NativeContainerAttribute

class in Unity.Collections.LowLevel.Unsafe

切换到手册

描述

允许您创建自己的自定义原生容器。

借助原生容器,您可以创建新的容器类型,此类容器不分配任何 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 &amp; 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 &amp; m_MaxIndex to exist. // and the container is expected to perform out of bounds checks against it. // m_MinIndex &amp; 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 &amp;&amp; (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 &amp; write the element at the job index. " + "You can use double buffering strategies to avoid race conditions due to " + "reading &amp; 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(); } } }