Копирование структур NativeContainer
Основные контейнеры являются типами значений, что означает, что при их назначении переменной Unity копирует структуру NativeContainer, которая содержит указатели на место хранения данных основного контейнера, включая его AtomicSafetyHandle. Она не копирует все содержимое NativeContainer.
Этот сценарий означает, что может существовать несколько копий структуры NativeContainer, которые все ссылаются на один и тот же регион памяти, и все содержат объекты AtomicSafetyHandle, которые ссылаются на одну и ту же центральную запись.
Вышеприведенная диаграмма показывает три различные копии структуры NativeArray, которые все представляют один и тот же фактический контейнер. Каждая копия указывает на те же самые хранящиеся данные и те же самые данные безопасности, что и оригинальная NativeArray. Однако каждая копия NativeArray имеет разные флаги, указывающие, что задание может делать с этой копией. Указатель на данные безопасности, в сочетании с флагами, образует AtomicSafetyHandle.
Номера версий
Если NativeContainer уничтожен, все копии структуры NativeContainer должны признать, что оригинальный NativeContainer недействителен. Уничтожение оригинального NativeContainer означает, что блок памяти, который использовался для хранения данных для NativeContainer, был выделен. В этой ситуации указатель на данные, хранящиеся в каждой копии NativeContainer, является недействительным и может вызвать нарушения доступа, если вы его используете.
AtomicSafetyHandle также указывает на центральную запись, которая становится недействительной для экземпляров NativeContainer. Однако система безопасности никогда не перераспределяет память для центральной записи, поэтому она избегает риска нарушения доступа.
Вместо этого, каждая запись содержит номер версии. Копия номера версии хранится внутри каждого экземпляра AtomicSafetyHandle, который ссылается на эту запись. Когда NativeContainer удаляется, Unity вызывает Release(), который увеличивает номер версии в центральной записи. После этого запись может быть повторно использована для других экземпляров NativeContainer.
Каждый оставшийся AtomicSafetyHandleсравнивает свой сохраненный номер версии с номером версии в центральной записи, чтобы проверить, удален ли NativeContainer. Unity выполняет этот тест автоматически как часть вызовов методов, таких как CheckReadAndThrowи CheckWriteAndThrow.
Статические представления динамических контейнеров
Динамический основной контейнер — это контейнер с переменным размером, в который можно продолжать добавлять элементы, например NativeList<T> (он доступен в пакете Collections). Это противоположность статическому нативному контейнеру, такому как NativeArray<T>, который имеет фиксированный размер, который вы не можете изменить.
При использовании динамического нативного контейнера к его данным можно обращаться и напрямую — через другой интерфейс, называемый представлением. Представление позволяет создать алиас для NativeContainer Примеры представлений включают объекты перечислителя, которые можно использовать для доступа к данным в основном контейнере элемент по элементу, и методы, такие как NativeList<T>.AsArray, который вы можете использовать для лечения NativeList как будто это было NativeArray.
Представления, как правило, не являются потоково безопасными, если изменяется размер динамического основного контейнера. Это происходит потому, что при изменении размера основного контейнера Unity перемещается туда, где данные хранятся в памяти, что приводит к тому, что любые указатели, хранящиеся в представлении, становятся недействительными.
Вторичные номера версий
Для поддержки ситуаций, когда размер динамического основного контейнера изменяется, система безопасности включает вторичный номер версии в AtomicSafetyHandle. Этот механизм похож на механизм версионирования, но использует второй номер версии, хранящийся в центральной записи, который может быть увеличен независимо от первого номера версии.
Чтобы использовать номер вторичной версии, вы можете использовать UseSecondaryVersion для настройки представлений в данные, хранящиеся в NativeContainer. Для операций, которые изменяют размер основного контейнера или иным образом делают существующие представления недействительными, используйте CheckWriteAndBumpSecondaryVersion вместо CheckWriteAndThrow. Также необходимо установить SetBumpSecondaryVersionOnScheduleWrite на NativeContainer для автоматического недействительного представления при каждом запланированном задании, которое записывает в основной контейнер.
При создании представления и копировании в него AtomicSafetyHandle используйте CheckGetSecondaryDataPointerAndThrow для подтверждения того, что копирование указателя на память основного контейнера в представление безопасно.
Специальные ручки
Существует два специальных манипулятора, которые можно использовать при работе с временными оригинальными контейнерами:
GetTempMemoryHandle: ВозвращаетAtomicSafetyHandle, который можно использовать в собственных контейнерах, выделенных сAllocator.Temp. Unity автоматически отменяет этот handler, когда текущая область временной памяти заканчивается, так что вам не нужно освобождать его самостоятельно. Чтобы проверить, является ли конкретныйAtomicSafetyHandlehandlerом, возвращеннымGetTempMemoryHandle, используйтеIsTempMemoryHandle.GetTempUnsafePtrSliceHandle: Возвращает глобальный handler, который можно использовать для временных собственных контейнеров, которые поддерживаются небезопасной памятью. Например,NativeSlice, построенный из памяти стека. Вы не можете передавать контейнеры, использующие этот handler, в задания.