欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > Unity Job System详解(3)——NativeArray源码分析

Unity Job System详解(3)——NativeArray源码分析

2025/2/22 2:23:06 来源:https://blog.csdn.net/enternalstar/article/details/143299952  浏览:    关键词:Unity Job System详解(3)——NativeArray源码分析

【特性】

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;}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词