К содержимому
Документация
На этой странице

Версии и миграции

Версий три: gv - игра, sv - SDK, mg - поколение формата моделей. По mg SDK мигрирует старый файл, а файл с поколением новее сборки отказывается читать

Три версии

МеткаРасшифровкаЧто версионируетГде живёт
gvgame versionигровой клиентbundleVersion Unity-проекта
svsdk versionSDK как библиотеку, semver по публичному APISdkVersion.Value
mgmodel generationформат моделей, одно поколение на домен[ModelGeneration] на каждом корне, ModelGenerations.Current

Текущие версии - gv 1.3.0, sv 1.3.0, mg 3

Номера gv и sv не обязаны следовать друг за другом, но в большинстве случаев они совпадают. Версии поднимаются вместе, и общее обновление несёт одну и ту же версию

Что значит каждая часть:

  • gv: major - глобальное обновление (сюжет, мультиплеер), minor - обычные новые функции, patch - хотфикс
  • sv, когда SDK начнёт обещать стабильность API: major - ломающее изменение API (тип удалён, сигнатура или смысл члена изменились), minor - добавление, на которое никому не нужно реагировать, patch - исправление, не меняющее сигнатур

mg важнее остальных. По нему SDK решает, мигрировать файл или отказаться его читать

Экран настроек показывает все три в этом порядке и с метками: gv 1.3.0, sv 1.3.0, mg 3. Без меток два одинаковых числа в баг-репорте не отличить

Версия с b и номером на конце, например gv 1.1.0b1, - это бета: сборка следующей версии для тестирования, обычно в бета-ветке Steam. Релиз после неё выходит без буквы (gv 1.1.0). Файл, сохранённый бетой, может не открыться в предыдущем релизе

Предупреждение

Пока SDK не скажет иначе, sv следует за игрой, и sv 1.0.0 не обещает стабильности API. Мажорная sv пока ничего не говорит о совместимости для кода, собранного против DLL

Числа, которые не версии

Ещё два числа похожи на "версию", но ей не являются:

  • LevelMeta.LevelVersion (ключ vrs) - версия уровня от автора, строка вроде "1.0"
  • BlobFormat.Generation - раскладка байтов .blob. Подробнее - Формат Blob

Поколение: одно целое число на домен

Домен - это корень, который мигрирует как единое целое. Их 21: Level, LevelMeta, UserSettings, Prefab, ThemeData, PublishProfile и другие. Сюда входят и части внутри Level: LevelSettings, GameLevel, AudioLevel, LevelResources, LevelHints. Каждый домен пишет своё поколение в ключ g своего конверта

КонстантаЗначениеСмысл
ModelGenerations.Invalid-1поколения нет вообще
ModelGenerations.Test0заготовка Versions/V0, которая проверяет путь миграции
ModelGenerations.V1_AlphaRelease1то, с чем вышла gv 1.0.0, на нём большинство доменов
ModelGenerations.V2_SimplifyEntrance2UserSettings, LevelMeta, LevelStatistics, GameStatistics и новый Collection
ModelGenerations.Currentсамое новоето, что показывают интерфейс и отчёты

Поколение - одно число, а не major.minor. Изменение формы файла либо требует миграции, либо нет. Промежуточной степени не бывает

Номера берутся из одного глобального счётчика. Изменившийся домен получает ModelGenerations.Current + 1, а не следующий свободный номер для себя. Именно это сохраняет смысл единственного mg в строке версий

Новое отвергается

Каждое изменение модели - это новое поколение. Изменение формы, добавленное поле и новое значение перечисления считаются одинаково. Значит, поколение выше, чем знает сборка, - это форма, которую она заведомо не видела

Исключение одно: совсем новая модель, которую не упоминает ни одна существующая, например скины аватара в отдельном файле. Это новый домен, мигрировать ему не из чего, поэтому поколение не растёт. Растёт поколение той существующей модели, которая начнёт на него ссылаться

Такой файл не читается вовсе: никаких значений по умолчанию, никаких пропущенных частей. NewerGenerationException несёт Domain, FileGeneration и BuildGeneration, а хост просит игрока обновиться

refused: 'Level' is at generation 2, newer than this build's 1 (domain Level, file 2, this SDK 1) - update the SDK

Старое мигрирует

Домен, который меняет форму, получает:

  1. новое поколение из глобального счётчика
  2. замороженный снимок старой формы в Versions/V<старое>/: класс с [GenerateModel] и [ModelGeneration(домен, <старое>)]
  3. ModelMigration<TFrom, TTo> в Versions/V<старое>/Migrations/
public class GameEventsV0ToV1 : ModelMigration<GameEventsV0, GameEvents>
{
    public override GameEvents Migrate(GameEventsV0 from) => new();
}

VersionedTypeRegistry находит снимки и миграторы через рефлексию и проходит цепочку до сегодняшней формы. Каждая подмена при чтении с потерями попадает в SerializationReport, а не в тишину

Отказ до чтения

LevelMeta.MinGeneration (ключ min_generation) - самое высокое поколение среди доменов уровня. Оно вычисляется при сохранении через LevelGenerations.Required() и никогда не вводится вручную

Клиент сравнивает его до открытия level.json. Так уровень из будущего отвергается без чтения мегабайтов содержимого. Файл, который ничего не заявляет, хранит -1

Полная запись решений - VERSIONING.md в репозитории SDK