Поскольку вы заявили, что строите игровой прототип, и в основном хотите работать с процессорами x86 и x86_64, двоичная сериализация несколько проще.
Но во-первых, есть некоторые вещи, чтобы иметь в виду:
long
значения имеют разный размер в x86 и x86_64.
- Значения
long
также имеют различные требования к выравниванию.
double
значения могут потребовать 8 или 4 байт, в зависимости от ОС и архитектуры.
- Endianness обычно является проблемой, но более новые продукты Apple не используют PowerPC, поэтому вы можете в значительной степени гарантировать, что вы работаете на малоэтажных машинах.
Последняя информация, прежде чем мы начнем: вы можете собирать информацию о системной архитектуре во время компиляции, используя макросы компилятора. В gcc __LP64__
означает, что вы компилируете 64-битный исполняемый файл. Я уверен, что есть аналогичные макросы для msvc.
Отбор другого размера переменной проблемы:
stdint.h
файл содержит определения типов для int8_t int16_t int32_t int64_t uint8_t uint16_t uint32_t uint64_t
. Каждому из них гарантировано будет такое количество бит. Вы можете безопасно использовать int64_t
вместо long
среди платформ. Если по какой-либо причине есть платформа, у которой нет этих typedefs, вы можете определить их самостоятельно с помощью typedefs, так как вы можете получить информацию о целевой системе, используя макросы препроцессора компилятора в любом случае.
Отбор другая проблема выравнивания:
Это один волосатые. Самое простое решение состоит в том, чтобы иметь структуру serializable_player
, содержащую все поля, которые содержит player
struct, но сообщите компилятору о ее упаковке (атрибут для gcc), чтобы компилятор не помещал никаких отступов. Затем при записи в файл вы создаете serializable_player
от player
и пишите прямо.
Преобразование из/в сериализуемый тип может быть накладным. Если вы можете позволить себе потратить немного памяти, вы также можете обеспечить выравнивание каждого члена структуры. Выравнивание значений double
с 8 байтами может быть хорошей идеей здесь. В gcc вы делаете это с атрибутом aligned
.
Примечание: Упакованные структуры обычно имеют штраф за исполнение, и поскольку вы собираетесь использовать player
в общей сложности, не не забудьте сделать это упакованным.
Решение проблем, различных чисел с плавающей точкой представления:
Если есть шанс, что вам может понадобиться, чтобы поддержать архитектуру, которая использует другое представление чисел с плавающей точкой, вы не будете иметь возможность хранить их в двоичной системе.
Раньше я работал в компании по разработке игр и отправлял плавающие точки по сети, вместо этого мы представляли их как целые числа. Что мы сделали, так это: вычислить минимальное разрешение, которое мы хотим r
. Затем вычислите минимальное и максимальное значение, которое оно может быть (в зависимости от того, в какие игроки играли игроки), min
и max
. В этом представлении мы могли бы представлять (max - min)/r
разные номера, и нам понадобилось log2((max - min)/r)
бит для его сохранения. Поскольку приемник также знал о min, max, r, нам не нужно было включать эту информацию в сетевой пакет.Мы сказали, что min
представлен как все 0s, max
представлен как все 1s, а остальные значения находятся между ними. Это была игра в реальном времени, и карты были не слишком большими (многопользовательская поддержка 64 игроков), у нас не было проблем даже с 32 бит, игроки не дрожали/мерцали, производительность была хорошей.
Если вы используете аналогичный подход, вам не придется сериализовать/десериализовать плавающие точки в двоичном формате.
Наконец, если вы нацелитесь на архитектуру большого конца в будущем, потребуются только небольшие шаги (если вы не используете союз). Вам нужно только иметь функции convert_to_le_*
для каждого размера. Эти функции должны быть пустыми для маленьких конечных машин, побитовая арифметика для больших конечных машин. Перед каждой сериализацией и после каждой десериализации вы должны вызывать эти функции для каждого члена, который будет отображаться по-разному в машинах большого конца. Приоритетные малоэтажные машины могут быть лучше здесь, так как у вашей основной аудитории, вероятно, будут машины x86 и x86_64.
Если вы используете объединение, ваше структурное представление также должно различаться между различными архитектурами.
Я знаю, что сохранение значений в JSON или в открытом тексте проще и оптимизация пространства на жестком диске почти всегда не нужна. Но если вы планируете делать многопользовательскую игру, подготовка к двоичной сериализации/десериализации заранее может быть полезной, так как у вас не будет роскоши отправлять данные JSON в режиме реального времени.
Редактировать: Как указано в комментариях, использование JSON представляется лучше, если вы хотите изменить свою файловую структуру, добавить дополнительные поля и т. Д. Если есть шанс сделать это, и вы по-прежнему решаете использовать структуру двоичных файлов, файлы должны содержать магическое число в первых байтах, представляющих версию. Когда вы решите добавить/удалить поле, вам следует обновить версию. Когда вы читаете из файла сохранения, вы должны сначала проверить версию и соответствующим образом обрабатывать каждую версию.
Редактировать 2: Некоторые части этого ответа относятся к процессорам x86 и x86_64. Например, использование long
вместо int64_t
имеет смысл, так как в обеих архитектурах 8 байтов. Если нужно поддерживать более широкую область, я бы рекомендовал только int * _t typedefs. Примером здесь является ядро linux, в котором s32
используется для подписанного 32-битного целого числа, если требуется принудительное исполнение размера.
«Совместимость между платформами» должна иметь _some_ широту. Формат может ограничивать весь текст до UTF8, с плавающей точкой для двоичных и целых чисел IEEE 754, например, для дополнения сети endian 2. – chux
Сериализация может быть простой или сложной, в зависимости от ваших данных. Как выглядит ваша структура? –
Я добавил код структуры. Я имею в виду, что файл, написанный в окнах, следует читать в linux. – twkmz