要实现自定义原生容器,您必须使用 NativeContainer 属性对您的类型进行注释。您还应该了解原生容器如何与 安全系统 集成。
实现自定义原生容器主要包括两个部分:
NativeContainer 实例的已计划作业,以便检测并防止潜在的冲突,例如两个作业同时写入同一个原生容器。NativeContainer 是否未正确释放。在这种情况下,分配给 NativeContainer 的内存将无法在程序的剩余生命周期中使用,从而导致内存泄漏。要在代码中访问使用情况跟踪,请使用 AtomicSafetyHandle 类。AtomicSafetyHandle 保存对安全系统为给定原生容器存储的中心信息的引用,并且是 NativeContainer 的方法与安全系统交互的主要方式。因此,每个 NativeContainer 实例都必须包含一个名为 m_Safety 的 AtomicSafetyHandle 字段。
每个 AtomicSafetyHandle 存储一组标志,这些标志指示在当前上下文中可以在原生容器上执行哪些类型的操作。当作业包含 NativeContainer 实例时,作业系统会自动配置 AtomicSafetyHandle 中的标志,以反映该作业中可以使用原生容器的方式。
当作业尝试从 NativeContainer 实例读取时,作业系统会在读取之前调用 CheckReadAndThrow 方法,以确认该作业是否具有对原生容器的读取访问权限。类似地,当作业尝试写入原生容器时,作业系统会在写入之前调用 CheckWriteAndThrow 方法,以检查该作业是否具有对原生容器的写入访问权限。两个已分配相同 NativeContainer 实例的作业对该原生容器具有单独的 AtomicSafetyHandle 对象,因此,尽管它们都引用同一组中心信息,但它们各自可以保存单独的标志,指示每个作业对原生容器的读取和写入访问权限。
Unity 的原生代码主要实现泄漏跟踪。它使用 UnsafeUtility.MallocTracked 方法分配存储 NativeContainer 数据所需的内存,然后使用 UnsafeUtility.FreeTracked 方法释放它。
在早期版本的 Unity 中,DisposeSentinel 类提供泄漏跟踪。当 垃圾回收器 收集 DisposeSentinel 对象时,Unity 会报告内存泄漏。要创建 DisposeSentinel,请使用 Create 方法,该方法还会同时初始化 AtomicSafetyHandle。当您使用此方法时,无需初始化 AtomicSafetyHandle。当 NativeContainer 被释放时,Dispose 方法会通过单个调用释放 DisposeSentinel 和 AtomicSafetyHandle。
要识别泄漏的 NativeContainer 在哪里创建,您可以捕获最初分配内存的堆栈跟踪。为此,请使用 NativeLeakDetection.Mode 属性。您也可以在编辑器中访问此属性。为此,请转到 **首选项** > **作业** > **泄漏检测级别** 并选择所需的泄漏检测级别。
安全系统不支持作业中的嵌套原生容器,因为作业系统无法正确配置较大 NativeContainer 实例中每个单独 NativeContainer 的 AtomicSafetyHandle。
要防止调度使用嵌套原生容器的作业,请使用 SetNestedContainer,当它们包含其他 NativeContainer 实例时,此方法会将 NativeContainer 标记为嵌套。
安全系统提供错误消息,指示代码何时不符合安全约束。为了使错误消息更清晰,您可以将 NativeContainer 对象的名称注册到安全系统。
要注册名称,请使用 NewStaticSafetyId,它返回一个安全 ID,您可以将其传递给 SetStaticSafetyId。创建安全 ID 后,您可以将其重复用于 NativeContainer 的所有实例,因此常见的模式是将其存储在容器类的静态成员中。
您还可以使用 SetCustomErrorMessage 覆盖特定安全约束违规的错误消息。