Мы используем BinaryFormatter в игре C #, чтобы сохранить прогресс пользовательской игры, игровые уровни и т. Д. Мы сталкиваемся с проблемой обратной совместимости.
Цели:
- Дизайнер уровней создает кампанию (уровни и правила), мы меняем код, кампания все равно должна работать нормально.Это может происходить каждый день во время разработки перед выпуском.
- Пользователь сохраняет игру, мы выпускаем игровой патч, пользователь все равно должен иметь возможность загрузить игру
- Невидимый процесс преобразования данных должен работать независимо от того, какдалекие две версии.Например, пользователь может пропустить наши первые 5 небольших обновлений и получить 6-е непосредственно.Тем не менее, его сохраненные игры по-прежнему должны нормально загружаться.
Решение должно быть полностью невидимым для пользователей и дизайнеров уровней и минимально обременять программистов, которые хотят что-то изменить (например, переименовать поле, потому что они думаютлучшее имя).
Некоторые графы объектов, которые мы сериализуем, коренятся в одном классе, некоторые в других.Прямая совместимость не требуется.
Потенциально прерывание изменений (и что происходит, когда мы сериализуем старую версию и десериализуем в новую):
- add field (получает инициализацию по умолчанию)
- изменить тип поля (ошибка)
- переименовать поле (эквивалентно его удалению и добавлению нового)
- изменить свойство на поле и обратно (эквивалентно переименованию)
- изменить свойство автоопределения для использования вспомогательного поля (эквивалентно переименованию)
- добавить суперкласс (эквивалентно добавлению его полей в текущий класс)
- интерпретировать поле по-другому (например, было вградусы, теперь в радианах)
- для типов, реализующих ISerializable, мы можем изменить нашу реализацию методов ISerializable (например, начать использовать сжатие в реализации ISerializable для некоторого действительно большого типа)
- Переименовать класс,переименуйте значение enum
Я читал о:
Мое текущее решение :
- Мы вносим как можно больше изменений, не нарушая,используя такие вещи, как обратный вызов OnDeserializing.
- Мы планируем критические изменения раз в две недели, поэтому кода совместимости остается меньше.
- Каждый раз перед внесением критических изменений мы копируем все классы [Serializable], которые мы используем, в пространство имен / папку с именем OldClassVersions.VersionX (где X - следующий порядковый номер после последнего).Мы делаем это, даже если не собираемся выпускать релиз в ближайшее время.
- При записи в файл мы сериализуем экземпляр этого класса: class SaveFileData {int version;данные объекта;}
- При чтении из файла мы десериализуем SaveFileData и передаем его в итеративную процедуру «обновления», которая выполняет что-то вроде этого:
.
for(int i = loadedData.version; i < CurrentVersion; i++)
{
// Update() takes an instance of OldVersions.VersionX.TheClass
// and returns an instance of OldVersions.VersionXPlus1.TheClass
loadedData.data = Update(loadedData.data, i);
}
- Для удобства функция Update () в своей реализации может использовать функцию CopyOverlappingPart (), которая использует отражение для копирования как можно большего количества данных из старой версии в новую версию.Таким образом, функция Update () может обрабатывать только те вещи, которые действительно изменились.
Некоторые проблемы с этим:
- десериализатор десериализуется в класс Foo, а не в класс OldClassVersions.Version5.Foo - потому что класс Foo - это то, что было сериализовано.
- почти невозможно протестировать или отладить
- требует хранения старых копий множества классов, которые подвержены ошибкам и хрупкии раздражает
- Я не знаю, что делать, когда мы хотим переименовать класс
Это должно быть действительно распространенной проблемой.Как люди обычно решают это?