tgoop.com/logofalprog/59
Last Update:
Сериализация. Часть 3: фичи
#кодище
В предыдущих постах я писал, почему мы выбрали MessagePack, и как переписали атрибуцию к нему. Пришло время закончить тему разговором о продвинутых фичах нашей сериализации.
1. Ссылки внутри файла. Не секрет, что при сериализации сложных данных легко нарваться на рекурсивные ссылки. Для ссылок внутри файла наше решение просто и элегантно: когда сериализатор начинает писать в файл новый объект, он присваивает ему порядковый номер и записывает в файл целиком; если же сериализатор повторно натыкается на этот же объект, он записывает только его порядковый номер. При чтении файла объекты попадаются в том же порядке, что позволяет легко восстановить связи. К моменту, когда десериализатор наткнётся на номер-ссылку, объект уже гарантированно будет создан.
2. Ссылки в другой файл. Иногда в сериализации требуется сослаться на объект, который хранится в другом месте. Для этого существуют GuidObject’ы, которые помимо всего прочего имеют уникальные Guid’ы. Когда GuidObject сериализуется в файл первый раз, у него есть выбор в зависимости от настроек: сериализовать объект полностью или записать только Guid, полагая, что тело будет записано в какой-то другой файл. Обратите внимание, что в обоих случаях, когда объект встретится повторно в этом же файле, Guid повторно записан не будет. Вместо него как обычно будет записан порядковый номер внутри файла.
Таким образом, один и тот же Guid никогда не пишется дважды в один файл, поэтому можно использовать довольно длинные Guid’ы и не бояться, что файлы будут пухнуть. Мы генерируем стандартные 128-битные Guid’ы, что позволяет создавать новые GuidObject’ы на разных машинах и не бояться конфликтов.
3. Склеивание. Фишка, когда при десериализации GuidObject создаётся пустым, а его поля заполняются уже позже из другого файла, делает возможным пойти дальше и разнести содержимое одного GuidObject по разным файлам.
Мы пользуемся этим, чтобы отделить статичные данные от сейва игрока. Например, у класса Location поля названия и координат на карте помечены атрибутом [StaticKey], а статус исследованности помечен атрибутом [RuntimeKey]. Таким образом состояние игрового мира хранится в двух больших бинарных файлах, а при загрузке информация из них «склеивается».
Благодаря этому мы можем спокойно менять статичные данные, такие как баланс игры, и это не повредит старые сейвы. Файлы сохранений при этом не хранят ничего лишнего, и всё это достигается простой атрибуцией без переделки самих классов.
4. Распределённая сериализация. В релизе, как уже было сказано выше, мир загружается из двух больших бинарных файлов, но это неудобно во время разработки (как минимум с точки зрения разрешения конфликтов). Поэтому у нас есть специальный режим сериализации, когда каждый GuidObject кладётся как отдельный текстовый файл в специальную папочку. Это позволяет резолвить конфликты на уровне конкретного объекта, а также добавлять/удалять объекты в этой папке с разных машин.
В этом месте, кстати, пришлось немножко подхачить MessagePack, чтобы добавить табуляцию в текстовый формат и слегка подправить парсер (так как получившийся формат имеет пару отступлений от классического JSON).
5. Прочие радости. Наша сериализация имеет ещё пару фич, но они не столь глобальны и решают мелкие нюансы именно нашей специфики, что останавливаться на них не вижу смысла.
На этом цикл постов по сериализации завершён. Ух, долго же я не мог собраться с силами закончить его. Надо постараться впредь не делать таких больших пауз. Кстати, на следующей неделе буду в Питере, собираюсь забежать на митап по С++ (можно словиться, если хотите).
Обсудить
BY Log of Alprog
Share with your friend now:
tgoop.com/logofalprog/59