Версии и миграции
Какое число версионирует что, как мигрирует старый файл и почему файл из более новой сборки не читается
Три версии
| Метка | Расшифровка | Что версионирует | Где живёт |
|---|---|---|---|
gv | game version | игровой клиент | bundleVersion Unity-проекта |
sv | sdk version | SDK как библиотеку, semver по публичному API | SdkVersion.Value |
mg | model 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.Test | 0 | заготовка Versions/V0, которая проверяет путь миграции |
ModelGenerations.Release | 1 | то, что игра пишет сегодня, на нём все домены |
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
Старое мигрирует
Домен, который меняет форму, получает:
- новое поколение из глобального счётчика
- замороженный снимок старой формы в
Versions/V<старое>/: класс с[GenerateModel]и[ModelGeneration(домен, <старое>)] 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