API
Каждый API-поверхность (за исключением Composition) SerializationManager имеет обобщённый вариант, а также вариант с boxing’ом. Кроме того, есть ещё два обобщённых метода для прямого вызова TypeInterface: один, где вы можете указать используемый экземпляр, и другой, где вам нужно указать только тип, и менеджер сам позаботится о получении экземпляра.Общие параметры
В этом разделе мы рассмотрим параметры, присутствующие во всех API. Поэтому мы не будем упоминать их снова при обсуждении конкретных API.NotNullableOverride
Из-за того, что ссылочные типы допускают нулевой указатель, но текущие API C#/CIL не позволяют определить, был ли аргумент обобщённого ссылочного типа аннотирован как (не)nullable, мы добавили флаг переопределения под названиемnotNullableOverride в виде параметра bool. Установите этот параметр в true, если вы не хотите, чтобы метод возвращал null!
Правильность использования этого параметра теперь контролируется анализатором. Если вам нужно его использовать или вы используете его неправильно, ваша IDE должна вам сообщить.
SerializationContext
Может быть использован для предоставления экземпляра контекста, если вы этого хотите. См. SerializationContext для получения информации о том, как создать контекст.SkipHook
Все API также предоставляют параметрbool под названием skipHook, который можно использовать для пропуска вызова методов, реализованных с помощью интерфейса ISerializationHook. Обратите внимание, однако, что этот параметр планируется к удалению. Этот параметр недоступен в API Write, Validate и Composition!
Read
При чтении вам необходимо указать:- Тип, либо как обобщённый аргумент типа T, либо как параметр типа в варианте с boxing’ом
- DataNode для чтения (разумеется)
Опционально у вас также есть возможность указать
instanceProvider. Это будет делегат, который предоставит значение для чтения. Это может быть использовано, например, для повторного использования экземпляров объекта вместо выделения нового. Если это звучит как тарабарщина, не волнуйтесь, вам, скорее всего, не придётся использовать это при кодинге для нашей игры.
Write
Для записи вам, опять же, нужно указать:- Тип, либо как обобщённый аргумент типа T, либо как параметр типа в варианте с boxing’ом. Обратите внимание, что существует один вариант с boxing’ом, который не требует указания типа, так как получает его через object.GetType()
Опционально вы можете указать флаг
alwaysWrite, чтобы принудительно записать весь объект в yaml. В противном случае сериализатор будет опускать значения полей, равные значению по умолчанию.
Validate
Validate — это, я бы сказал, один из самых простых API, которые мы предоставляем. Здесь вы указываете:- Тип, либо как обобщённый аргумент типа T, либо как параметр типа
- Node для проверки В ответ вы получите ValidationNode, предоставляющий информацию о правильности DataNode.
Copy
Наш Copy API разделён на две части: CopyTo и CreateCopy. С помощью CopyTo вы можете копировать значения из одного объекта в другой. С помощью CreateCopy вы создаёте копию объекта, который передаёте.Composition
Здесь composition выполняется через узлы с использованием определений, связанных с переданным типом. Это означает, что переданный тип определяет, как предоставленные data-узлы будут объединены. В настоящее время существует лишь очень ограниченное количество методов для настройки этого поведения, особенно для DataField. Однако мы работаем над этим!Data Definition
DataDefinition — это структуры или классы с полями/свойствами, аннотированными как DataField. Эти DataField записываются и читаются в/из yaml, но также используются для операций копирования, валидации и композиции. В дальнейшем я буду называть структуры и классы просто «тип». Data definitions должны иметь конструктор без параметров, чтобы быть валидными. (За исключением DataRecords)Объявление DataDefinition
Нет никакого риска в объявлении DataDefinition с несколькими из этих опций одновременно. Дублирующиеся регистрации будут просто сокращены до одной.
DataDefinition должны быть объявлены как partial, чтобы работать с нашим source generator’ом для копирования.
Напрямую
Чтобы сделать класс DataDefinition, вы можете добавить атрибут [DataDefinition] к типу, как показано ниже.Все наследники типа
Если у вас есть базовый тип или интерфейс, и вы хотите, чтобы все наследники автоматически стали DataDefinition, аннотируйте базовый тип или интерфейс атрибутом [ImplicitDataDefinitionForInheritors]. Все текущие аннотированные типы можно найти здесь, где вы, вероятно, найдёте много типов/интерфейсов, которые вы наследовали/реализовывали ранее.Все типы, аннотированные определённым атрибутом
Если у вас вместо этого есть атрибут, который вы будете добавлять ко всем вашим DataDefinition, добавьте атрибут [MeansDataDefinition] к вашему собственному атрибуту. Ярким примером является PrototypeAttribute, который вы, вероятно, видели ранее:DataField
Типы DataField
Обычный
Любое поле или свойство DataDefinition может быть аннотировано атрибутом [DataField]. В дальнейшем свойства и поля будут называться просто «поле».Include DataField
DataDefinition записывается и читается из MappingDataNode. В отличие от обычного DataField, Include DataField не будет получать значение из ключа этого MappingDataNode для чтения/записи в/из поля, а вместо этого будет использовать MappingDataNode всей DataDefinition для выполнения своей операции чтения/записи. Это имеет особые последствия для записи: IncludeDataFields сериализуются последними, и полученное отображение будет вставлено в уже созданное отображение DataDefinition. Если ключ уже существует, новое значение, полученное при сериализации IncludeDataField, будет проигнорировано.Это поведение может стать настраиваемым в будущем.
Пользовательский Type Serializer
Пользовательский Type Serializer может быть указан, если его нет по умолчанию или требуется нестандартное поведение для сериализации определённого типа. Чтобы его использовать, передайте его через аргумент customTypeSerializer. И DataField, и IncludeDataField поддерживают пользовательские Type Interfaces, но в следующих примерах используется только DataFieldAttribute, чтобы сделать их менее громоздкими.Constants
При аннотировании поля int, представляющего константу, определённую [ConstantsForAttribute], необходимо указать пользовательский Type Serializer в [DataField]:Flags
Для определения полей int, представляющих flag enum, аннотированный [FlagsFor], процесс тот же, но используется другой сериализатор.Поведение наследования
Два дополнительных атрибута могут быть использованы для DataField, чтобы определить, как он наследуется: [AlwaysPushInheritance] и [NeverPushInheritance]. Это применимо как к DataField, так и к IncludeDataField. [AlwaysPushInheritance] используется в тех случаях, когда данные поля должны наследоваться, даже при маппинге, например, компоненты прототипа сущности. [NeverPushInheritance] используется, чтобы указать, что значение, например, в прототипе, не должно передаваться наследуемым прототипам, например, свойство abstract.DataRecords
TODOType Serializer
Интерфейсы Type Serializer — это набор интерфейсов для определения пользовательской логики для действий над определёнными типами. Иногда ожидаемый тип узла также может быть указан. Класс, реализующий хотя бы один из этих интерфейсов Type Serializer, называется Type Serializer. Если вы хотите, чтобы ваш Type Serializer всегда использовался, вы можете аннотировать его атрибутом[TypeSerializer]. В противном случае тип можно использовать как пользовательский Type Serializer.
Статический IoCManager.Resolve не должен использоваться, так как сериализатор может работать в отдельном потоке без инициализированного контекста IoC.
Serialization Context
Вы можете создать SerializationContext, реализовав интерфейс ISerializationContext на типе. Тип предоставит SerializationProvider, который он может использовать для регистрации TypeSerializer’ов. В настоящее время используется MapContext во время загрузки карты: https://github.com/space-wizards-federation/RobustToolbox/blob/025fa958549b4d63e4888a810f780c53e6fb89a9/Robust.Shared/Map/MapSerializationContext.cs#L17-L51Hamster
