К содержимому
Версии и миграции
На этой странице

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

Какое число версионирует что, как мигрирует старый файл и почему файл из более новой сборки не читается

Три версии

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

Текущие версии - gv 0.16.2, sv 0.16.2, mg 1

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

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

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

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

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

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

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

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

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

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

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

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

КонстантаЗначениеСмысл
ModelGenerations.Invalid-1поколения нет вообще
ModelGenerations.Test0заготовка Versions/V0, которая проверяет путь миграции
ModelGenerations.Release1то, что игра пишет сегодня, на нём все домены
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