【特性】
Unity特性:
- [NativeContainer]表明其是一个NativeContainer
- [ReadOnly]表示只读
- [WriteAccessRequired]表示要有写操作
- [NativeDisableUnsafePtrRestriction] 允许使用Unsafe代码,当数据量大时,copy可能费时,需要用指针,写unsafe代码会用到
- [NativeDisableParallelForRestrictionAttribute] 允许多线程写入,在并行Job中常用,自己维护好,不同索引的Index。
- [NativeDisableContainerSafetyRestriction]禁用job的 safety system,让你对NativeArray拥有完全的控制权,同时系统也就不会帮你定位race condition等情况,所以在使用的时候,需要自己确保安全性
- [NativeContainerSupportsMinMaxWriteRestriction]用于限制NativeContainer中的元素在一定范围内进行写操作。通过设置最小值和最大值,可以确保在修改NativeContainer中的元素时不会超出指定范围,从而避免出现意外的错误或问题。
C#特性:
- [MethodImpl(MethodImplOptions.AggressiveInlining)]指定编译器对该函数进行内联
- [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]编译器指令,条件为真时才会编译输出
【源码】
定义
public struct NativeArray<T> : IDisposable, IEnumerable<T>, IEnumerable, IEquatable<NativeArray<T>> where T : struct
继承三个接口(一般自定义底层数据结构都会继承这三个),约束泛型是结构体
可以参考C# Array的实现,需要支持的功能有:构造函数、析构函数、取值赋值
构造函数
//构造函数中主要逻辑时做内存分配,和数据Copypublic NativeArray(T[] array, Allocator allocator){if (array == null){throw new ArgumentNullException("array");}Allocate(array.Length, allocator, out this);Copy(array, this);}//数据在Buffer指针//[NativeDisableUnsafePtrRestriction]//internal unsafe void* m_Buffer;//内存分配:Unity自己会管理内存,根据allocator做不同类型的内存分配,每块内存与SafetyId一一对应private unsafe static void Allocate(int length, Allocator allocator, out NativeArray<T> array){long size = (long)UnsafeUtility.SizeOf<T>() * (long)length; //计算结构体占用内存大小,实际调用System.Runtime.CompilerServices.Unsafe.SizeOf,其获取对象占用内存大小时可以绕过C#语言的类型安全检查CheckAllocateArguments(length, allocator);array = default(NativeArray<T>);IsUnmanagedAndThrow();array.m_Buffer = UnsafeUtility.MallocTracked(size, UnsafeUtility.AlignOf<T>(), allocator, 0);//注意分配内存时,传的时size,而不是long,写C++应该很明白,C#写多了,容易糊涂array.m_Length = length; //length就和C# Array一样array.m_AllocatorLabel = allocator;array.m_MinIndex = 0;array.m_MaxIndex = length - 1;AtomicSafetyHandle.CreateHandle(out array.m_Safety, allocator);//创建一个原子操作安全的HandleInitStaticSafetyId(ref array.m_Safety);//用TypeNameBytes和bytesCount算了一个SafetyId,一般也就那几种Hash算法来算InitNestedNativeContainer(array.m_Safety);}private unsafe static void CopySafe(T[] src, int srcIndex, NativeArray<T> dst, int dstIndex, int length){AtomicSafetyHandle.CheckWriteAndThrow(dst.m_Safety);CheckCopyPtr(src);//关闭ENABLE_UNITY_COLLECTIONS_CHECKS,这些Check都能去掉,对性能提升有益CheckCopyArguments(src.Length, srcIndex, dst.Length, dstIndex, length);GCHandle gCHandle = GCHandle.Alloc(src, GCHandleType.Pinned);IntPtr intPtr = gCHandle.AddrOfPinnedObject();//用GCHandle拿到托管对象的指针UnsafeUtility.MemCpy((byte*)dst.m_Buffer + dstIndex * UnsafeUtility.SizeOf<T>(), (byte*)(void*)intPtr + srcIndex * UnsafeUtility.SizeOf<T>(), length * UnsafeUtility.SizeOf<T>());//拷贝,需要传入目的地址指针、源地址指针,拷贝数据大小,底层实际调用的是C++的memcpygCHandle.Free();//释放GCHandle}
其他重载的构造函数、Allocate和CopySafe大同小异
取值赋值
// 取值赋值public unsafe T this[int index]{[MethodImpl(MethodImplOptions.AggressiveInlining)]get{CheckElementReadAccess(index);return UnsafeUtility.ReadArrayElement<T>(m_Buffer, index);//调用System.Runtime.CompilerServices.Unsafe.Read<T>((byte*)source + (long)index * (long)System.Runtime.CompilerServices.Unsafe.SizeOf<T>());//这里的意思是找到要读的数据的开始的地址,为buffer的地址加上索引乘以T的大小,读取的长度为T的大小。T只是数据的标识方式}[MethodImpl(MethodImplOptions.AggressiveInlining)][WriteAccessRequired]set{CheckElementWriteAccess(index);UnsafeUtility.WriteArrayElement(m_Buffer, index, value);调用 System.Runtime.CompilerServices.Unsafe.Write((byte*)destination + (long)index * (long)System.Runtime.CompilerServices.Unsafe.SizeOf<T>(), value);}}
析构函数
[WriteAccessRequired]public unsafe void Dispose(){if (m_AllocatorLabel != Allocator.None && !AtomicSafetyHandle.IsDefaultValue(in m_Safety)){AtomicSafetyHandle.CheckExistsAndThrow(in m_Safety);}if (IsCreated){if (m_AllocatorLabel == Allocator.Invalid){throw new InvalidOperationException("The NativeArray can not be Disposed because it was not allocated with a valid allocator.");}if (m_AllocatorLabel > Allocator.None){AtomicSafetyHandle.DisposeHandle(ref m_Safety);//释放AtomicSafetyHandleUnsafeUtility.FreeTracked(m_Buffer, m_AllocatorLabel);//释放内存,由于是Native内存,必须显式调用释放接口m_AllocatorLabel = Allocator.Invalid;}m_Buffer = null;//指针置空}}
IEquatable接口实现
自定义数据结构实现IEquatable时一般要实现Equals方法,并重写判等操作符
// buffer地址和长度相等,才相等public unsafe bool Equals(NativeArray<T> other){return m_Buffer == other.m_Buffer && m_Length == other.m_Length;}// 重载操作符public static bool operator ==(NativeArray<T> left, NativeArray<T> right){return left.Equals(right);}public static bool operator !=(NativeArray<T> left, NativeArray<T> right){return !left.Equals(right);}
迭代器太常见了不说了
NativeArray.Dispose(JobHandle)
当某个Job依赖NativeArray时,该NativeArray需要在该Job完成后释放,可以使用该接口。
其为该NativeArray创建一个依赖传入的JobHandle的NativeArrayDisposeJob
if (m_AllocatorLabel > Allocator.None){NativeArrayDisposeJob jobData = default(NativeArrayDisposeJob);jobData.Data = new NativeArrayDispose{m_Buffer = m_Buffer,m_AllocatorLabel = m_AllocatorLabel,m_Safety = m_Safety};JobHandle result = jobData.Schedule(inputDeps);AtomicSafetyHandle.Release(m_Safety);m_Buffer = null;m_AllocatorLabel = Allocator.Invalid;return result;}