Недавно я занялся сериализацией ECS, которая сериализует структуру Chunk очень простым способом. По пути я узнал об этом, что я хотел бы резюмировать здесь.

В настоящее время все основано на предварительной версии 21. Приближается возможность выбора размера фрагмента, и это изменит ситуацию.

Структура фрагмента

Обратите внимание на комментарий X | Y. Это смещение до конца этого поля в зависимости от архитектуры. (64 | 32 бит)

UnsafeLinkedListNode

Каждый из них содержит 2 переменные типа указателя, следующие и предыдущие, так что именно отсюда возникла разница в смещении. Это позволяет каждому фрагменту быть членом всей сети фрагментов. Затем мы можем быстро перейти к существующим фрагментам и найти пустые фрагменты рядом с этим.

  • При сериализации: обнуляется. При десериализации мы снова связываемся с соседями.

Архетип

Известная нам переменная EntityArchetype — это всего лишь оболочка над этим типом указателя Archetype. Архетип содержит большое количество данных, но реальных данных здесь нет! Вот почему это указатель. Фактические данные хранятся в ArchetypeManager, который находится внутри EntityManager, по одному на World.

  • При сериализации: это поле заменяется целочисленным «индексом архетипа». При десериализации номер сопоставляется с эквивалентным архетипом (существующим или новым на месте) путем повторного запроса от ArchetypeManager.

ШаредКомпонентДатаValueArray

Этот указатель указывает на хвост фрагмента. Как вы знаете, ассоциированнаяSCD — это много вещей. Посмотрите на метод GetSharedComponentOffset также на картинке. Вы видите, что он смещается от конца назад на количество прикрепленного SCD. Мы можем сделать вывод, что настоящие данные находятся прямо здесь, в конце фрагмента.

  • О десериализации: он просто пытается снова найти свой хвост и восстановить указатель в новой памяти.

Количество / Емкость

Количество — это количество Entity в чанке. Это очень важно, так как все это связано с тем, как следующий будет здесь или найдет новый кусок, чтобы быть в нем.

Вместимость — это заранее рассчитанное максимальное количество объектов, которые могут находиться здесь. Он на 100% поддается предварительному расчету, потому что размер фрагмента фиксирован, а также мы знаем размер каждого компонента благодаря struct высокопроизводительному ограничению C#. Оттуда это просто некоторое умножение и деление.

  • При сериализации: эти номера сохраняются.

индекс управляемого массива

Ключ для перехода к специальному хранилищу, в котором могут храниться любые object объектов в этом чанке. Глянь сюда :



Отступ0 / Отступ2

Это трюк с размером данных, чтобы struct было хорошо выровнено. Обратите внимание, что Padding2 использует размер указателя, который будет равен 4 для 32-разрядных и 8 для 64-разрядных.

ИзменитьВерсию

Чанк может хранить версию для каждого компонента. Это похоже на SharedComponentDataValueArray в том смысле, что указывает на конец этого фрагмента. Но дальше от общих индексов.

  • О десериализации: он просто пытается снова найти свой хвост и восстановить указатель в новой памяти.


Буфер

Этот fixed буфер из 4 byte служит точкой привязки для «все, что следует здесь».

То есть сначала мы будем malloc kChunkSize (16 * 1024–256), а затем поместим начало этой памяти как Chunk* . Затем, если мы запросим chunk->Buffer , мы сможем получить свободную память для выполнения всего, что захотим, после всех заголовков чанков. Просто убедитесь, что не выходите за конечную границу.

Положение Buffer отличается для 32- и 64-битной машины из-за того, что предшествующие указатели имеют разный размер. Это означает, что 32-битная машина получила больше места для работы?

Прямо сейчас мы можем визуализировать фрагмент следующим образом.

GetChunkBufferSize вопросы

В этом методе используется фиксированный размер фрагмента 16 * 1024–256 = 16128 за вычетом заголовка (имеет смысл), а затем еще 4. Я попробовал sizeof(Chunk), и это, кажется, 88, а не 84, как ожидалось. Так вот зачем на 4 меньше? Я не знаю почему.

Но, кроме того, это не размер возвращаемого буфера. Он добавляет размер, равный количеству SCD, которое у вас есть (для общих индексов), а также количеству компонентов, которые у вас есть (для измененных версий).

Поскольку чанк может содержать много компонентов и SCD (бесконечное количество?), значение этого метода может значительно превышать 16128. Что это значит? Во-первых, «размер буфера фрагмента», как определено Unity, не является подмножеством kChunkSize, это точно.

Из поиска в источнике расчет этого метода предназначен для расчета Capacity, просто разделив его на пространство каждого объекта. Как насчет того, чтобы в случае, если у нас есть миллион компонентов, Capacity оказалось очень большим?

В этом случае занимаемое пространство каждого объекта также будет увеличиваться по отношению к добавленному компоненту. Когда и делимое, и делитель увеличиваются вместе, вполне вероятно, что Capacity в любом случае сохраняется на низком уровне.

Таким образом, даже количество возвращаемых данных этим методом значительно превышает объем памяти блока malloc, фактическая запись не может превысить его, когда емкость достигает предела.

Даже если вам удалось увеличить емкость с помощью какой-либо комбинации, именно тогда в игру вступает kMaximumEntityPerChunk. Если емкость больше 16128/8 = 2016, то это ошибка!

Не уверен насчет компонента тега, теоретически ему не понадобится место в «измененной версии», так как там нечего менять, и поэтому не расширяется пространство фрагмента.

Дополнительный

Здесь вы можете узнать больше, почему разница в архитектуре, которая изменяет структуру Chunk, проблематична в коде сериализации. Короче говоря, я работаю на своем 64-битном MacBook, а затем десериализую 64-битную память фрагмента в свой 32-битный Android. Бум SIGSEGV! (превью 21)