【特性】
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的实现,需要支持的功能有:构造函数、析构函数、取值赋值
构造函数
//构造函数中主要逻辑时做内存分配,和数据Copy
public 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);//创建一个原子操作安全的Handle
InitStaticSafetyId(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++的memcpy
gCHandle.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);//释放AtomicSafetyHandle
UnsafeUtility.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;
}