Перейти к основному содержанию

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. Это будет делегат, который предоставит значение для чтения. Это может быть использовано, например, для повторного использования экземпляров объекта вместо выделения нового. Если это звучит как тарабарщина, не волнуйтесь, вам, скорее всего, не придётся использовать это при кодинге для нашей игры.
instanceProvider НИКОГДА не должен возвращать null. Это вызовет исключение (в отладочных сборках).

Write

Для записи вам, опять же, нужно указать:
  • Тип, либо как обобщённый аргумент типа T, либо как параметр типа в варианте с boxing’ом. Обратите внимание, что существует один вариант с boxing’ом, который не требует указания типа, так как получает его через object.GetType() Опционально вы можете указать флаг alwaysWrite, чтобы принудительно записать весь объект в yaml. В противном случае сериализатор будет опускать значения полей, равные значению по умолчанию.

Validate

Validate — это, я бы сказал, один из самых простых API, которые мы предоставляем. Здесь вы указываете:
  • Тип, либо как обобщённый аргумент типа T, либо как параметр типа
  • Node для проверки В ответ вы получите ValidationNode, предоставляющий информацию о правильности DataNode.

Copy

Наш Copy API разделён на две части: CopyTo и CreateCopy. С помощью 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]
public sealed partial class MyClass {}

[DataDefinition]
public partial struct MyStruct {}

Все наследники типа

Если у вас есть базовый тип или интерфейс, и вы хотите, чтобы все наследники автоматически стали DataDefinition, аннотируйте базовый тип или интерфейс атрибутом [ImplicitDataDefinitionForInheritors]. Все текущие аннотированные типы можно найти здесь, где вы, вероятно, найдёте много типов/интерфейсов, которые вы наследовали/реализовывали ранее.
[ImplicitDataDefinitionForInheritors]
public interface IContainer {}

[ImplicitDataDefinitionForInheritors]
public abstract class BaseType {}

// Container будет DataDefinition
public sealed partial class Container : IContainer {}

// SomeStruct будет DataDefinition
public partial struct SomeStruct : IContainer {}

// SomeType будет DataDefinition
public sealed partial class SomeType : BaseType {}

Все типы, аннотированные определённым атрибутом

Если у вас вместо этого есть атрибут, который вы будете добавлять ко всем вашим DataDefinition, добавьте атрибут [MeansDataDefinition] к вашему собственному атрибуту. Ярким примером является PrototypeAttribute, который вы, вероятно, видели ранее:
[MeansDataDefinition]
public sealed class PrototypeAttribute : Attribute {
    ...
}

// Любой класс, помеченный [Prototype], автоматически станет DataDefinition.

DataField

Типы DataField

Обычный
Любое поле или свойство DataDefinition может быть аннотировано атрибутом [DataField]. В дальнейшем свойства и поля будут называться просто «поле».
[DataField]
protected Color Color { get; set; } = Color.White;
Приведённый выше пример будет транслироваться в YAML следующим образом:
color: White
Этот атрибут принимает опциональный строковый ключ, который можно использовать для определения ключа в YAML вместо версии в camelCase имени поля. Если он не нужен, предпочтительно его не указывать.
[DataField("colorValue")]
protected Color Color { get; set; } = Color.White;
Приведённый выше пример будет транслироваться в YAML следующим образом:
colorValue: White
Include DataField
DataDefinition записывается и читается из MappingDataNode. В отличие от обычного DataField, Include DataField не будет получать значение из ключа этого MappingDataNode для чтения/записи в/из поля, а вместо этого будет использовать MappingDataNode всей DataDefinition для выполнения своей операции чтения/записи. Это имеет особые последствия для записи: IncludeDataFields сериализуются последними, и полученное отображение будет вставлено в уже созданное отображение DataDefinition. Если ключ уже существует, новое значение, полученное при сериализации IncludeDataField, будет проигнорировано.
Это поведение может стать настраиваемым в будущем.

Пользовательский Type Serializer

Пользовательский Type Serializer может быть указан, если его нет по умолчанию или требуется нестандартное поведение для сериализации определённого типа. Чтобы его использовать, передайте его через аргумент customTypeSerializer. И DataField, и IncludeDataField поддерживают пользовательские Type Interfaces, но в следующих примерах используется только DataFieldAttribute, чтобы сделать их менее громоздкими.
Этот тип НЕ обязан реализовывать ITypeSerializer. Вам нужно реализовать только те интерфейсы, которые нужны! Любое другое поведение, которое не будет отличаться от обычного, не нужно переопределять! Если интерфейс для конкретного действия не существует, будет использовано обычное поведение!
[DataField(customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
private int DrawDepth { get; set; } = DrawDepthTag.Default;
Constants
При аннотировании поля int, представляющего константу, определённую [ConstantsForAttribute], необходимо указать пользовательский Type Serializer в [DataField]:
/// <summary>
///     Tag type для определения представления глубины отрисовки (draw depth) в
///     терминах именованных констант в контенте. Чтобы узнать больше о
///     назначении этого типа, см. <see cref="ConstantsForAttribute"/>.
/// </summary>
public sealed class DrawDepth
{
    /// <summary>
    ///     Глубина отрисовки по умолчанию. Enum контента, представляющий глубину отрисовки,
    ///     должен учитывать это значение, так как оно используется в движке.
    /// </summary>
    public const int Default = 0;
}

public sealed partial class SpriteComponent
{
    [DataField(customTypeSerializer: typeof(ConstantSerializer<DrawDepthTag>))]
    private int DrawDepth { get; set; } = DrawDepthTag.Default;
}
Flags
Для определения полей int, представляющих flag enum, аннотированный [FlagsFor], процесс тот же, но используется другой сериализатор.
/// <summary>
///     Tag type для определения представления битовой маски слоя коллизий
///     в терминах читаемых имён в контенте. Чтобы узнать больше о
///     назначении этого типа, см. <see cref="FlagsForAttribute"/>.
/// </summary>
public sealed class CollisionLayer {}

public sealed partial class PhysShapeRect
{
    [DataField(customTypeSerializer: typeof(FlagSerializer<CollisionLayer>))]
    private int CollisionLayer { get; set; }
}

Поведение наследования

Два дополнительных атрибута могут быть использованы для DataField, чтобы определить, как он наследуется: [AlwaysPushInheritance] и [NeverPushInheritance]. Это применимо как к DataField, так и к IncludeDataField. [AlwaysPushInheritance] используется в тех случаях, когда данные поля должны наследоваться, даже при маппинге, например, компоненты прототипа сущности. [NeverPushInheritance] используется, чтобы указать, что значение, например, в прототипе, не должно передаваться наследуемым прототипам, например, свойство abstract.

DataRecords

TODO

Type 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-L51

Hamster

hamletheldatgunpoint.png
Последнее изменение 21 июня 2026 г.