Перейти к основному содержимому
Модель игры

Эта страница ещё не переведена — показана английская версия.

Схема в коде

Объявите модель в коде, отправьте её на платформу и получите типы обратно. Так со схемой работает разработчик: админ-панель и код описывают одну и ту же модель, а кодогенерация замыкает круг для каждого движка.

Когда это нужно

  • Модель данных должна жить в коде и проходить ревью как код: объявили, schema diff, schema push.
  • Типы в движке не должны расходиться с развёрнутой моделью: schema codegen заново генерирует Unreal C++ и Unity C#.
  • Несовместимое изменение нужно прочитать и при необходимости отменить до того, как оно выполнится: предложить → спланировать → применить.
  • Один и тот же набор (Stat, Interactable) нужен в нескольких проектах: объявите его один раз как пресет.
  • Не нужно, если оператор только подправляет значения в админ-панели. Изменение модели, сделанное там, всё равно вернётся в код через diff.

Кто что делает

АкторЧто делает на этой странице
schema-authorобъявляет в коде сущности, части и перечисления, сравнивает их с развёрнутой моделью и отправляет
operatorпросматривает обзор в панели, предлагает и применяет миграции
ciсборочный конвейер с ключом backend-service: отправляет схему (schema) после слияния, а затем заново генерирует типы для движков

Коротко

Item со встроенной частью Stats и перечислением — всё уходит на платформу одной схемой
[Entity("item")]
public class Item
{
    public string Name = "";
    public Rarity Rarity;              // an enum declared the same way
    public Stats Stats = new();        // a part — embedded, no lifecycle of its own
}

[Part("stats")]
public class Stats { public int Power; public int Weight; }
@Entity('item')
export class Item {
  name = '';
  rarity!: Rarity;                   // an enum declared the same way
  stats = new Stats();               // a part — embedded, no lifecycle of its own
}

@Part('stats')
export class Stats { power = 0; weight = 0; }
@entity("item")
class Item:
    name: str = ""
    rarity: Rarity                     # an enum declared the same way
    stats: Stats = Stats()             # a part — embedded, no lifecycle of its own

@part("stats")
class Stats:
    power: int = 0
    weight: int = 0
Скоро — Go

Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.

USTRUCT(PSPart = "stats")
struct FItemStats
{
    GENERATED_BODY()

    UPROPERTY() int32 Power;
    UPROPERTY() int32 Weight;
};

UCLASS(PSEntity = "item")
class UItem : public UObject
{
    GENERATED_BODY()

    UPROPERTY() FString    Name;
    UPROPERTY() EPSRarity  Rarity;       // an enum declared the same way
    UPROPERTY() FItemStats Stats;        // a part — embedded, no lifecycle of its own
};
// declarations compile into the same pushed model — playserv push from the UE project or CI
Unity C# is the same C# API here — те же атрибуты компилируются в Unity на базовой версии 2021.3 — синтаксиса C# 12 здесь нет
[Entity("item")]
public class Item
{
    public string Name = "";
    public Rarity Rarity;              // an enum declared the same way
    public Stats Stats = new();        // a part — embedded, no lifecycle of its own
}

[Part("stats")]
public class Stats { public int Power; public int Weight; }
playserv schema diff       # local declarations vs deployed schema
playserv schema push       # with a revision precondition — no blind overwrites
playserv schema codegen    # regenerate Unreal C++ / Unity C# types

Отправка — отдельный шаг

Когда вы сохраняете файл, ничего не загружается. Объявление (declaration) попадает в развёрнутую модель, только когда запускается playserv push (или schema push) — на вашей машине или в CI. Вместе с ним уходит ревизия, относительно которой считался diff. Правка в панели появится в schema diff как расхождение. В какую бы сторону ни переносилось изменение, это всегда команда, которую вы запускаете сами.

Модель

Из чего состоит объявление.

СодержитЧто это
keyстабильное имя, по которому к объявлению обращаются. Переименование в коде так и остаётся переименованием, а не удалением с повторным созданием
kindсущность (entity), часть или перечисление, объявленные в коде
режим владенияseed — код создаёт запись, если её нет, а повторная отправка значения не трогает, так что дальше ими распоряжается админ-консоль. Или managed — запись всегда принадлежит коду: каждая отправка приводит значения к объявленным, а правки из админ-консоли отклоняются, чтобы они не применились и не потерялись при следующей отправке
presetнеобязательно: переиспользуемый набор — пресет сущности, например stats или world-objects, — который объявляют один раз и применяют как тип. Нового вида объявлений он не вводит

Что гарантирует отправка.

ВсегдаЧто это
сопоставлениепо ключу, а не по символу: если переименовать символ и отправить схему снова, запись останется одна, а не две
отчётпоказывает, что именно изменится, до применения, а после — что было перезаписано
происхождениеразличимо: запись, созданную отправкой из кода, можно отличить от созданной другим путём
ревизияпередаётся вместе с отправкой, а сама отправка применяется целиком или не применяется вовсе

Что гарантирует кодогенерация.

ВсегдаЧто это
перегенерацияпроисходит после каждой отправки, а сгенерированные типы вручную никогда не правят: если перегенерировать их и сравнить, изменений не будет
именаследуют объявлению, где бы его ни написали: поле Rarity в Item становится UPSItem::Rarity в Unreal и Item.Rarity в Unity
два направленияне расходятся: всё, что объявлено в коде, появляется в панели, а всё, что оператор создал в панели, чисто сравнивается с кодом. Поэтому schema diff показывает каждое различие

Что гарантирует миграция.

ВсегдаЧто это
когда она нужнакогда меняется объявление хранимых данных и это изменение переписывает уже существующие значения
что она объявляетверсию, предпросмотр, упорядоченное применение, откат при сбое и наблюдаемый результат по завершении
сосуществованиепока живы две версии данных, операции чтения и записи объявляют, какие версии они принимают. Среда выполнения никогда не выводит совместимость из имён полей

Ошибки

  • Вызывающий без нужной роли (role) получает forbidden, и развёрнутая модель остаётся нетронутой: отказ никогда не оборачивается частичной отправкой. Это не то же самое, что устаревшая ревизия. Она возвращает precondition_failed и означает, что diff считался по схеме, которая с тех пор изменилась, — пересчитайте diff и отправьте снова.
  • Значение за пределами объявленных границ при записи отклоняется, а не обрезается до границы. То же со строкой длиннее объявленной. После обрезки значение было бы допустимым, но неверным.
  • Некорректная последовательность UTF-8 при записи отклоняется, а не исправляется.
  • Несовместимое изменение хранимых данных без объявленной миграции опубликовать нельзя вообще.

Ограничения

Потолки, которые задаёт сама форма объявлений, проверяются в момент объявления — при деплое или публикации, а не при первом использовании, если во время работы превышение не выглядело бы как отказ. Конкретные числа появятся вместе с главой об ограничениях платформы.