Хранение данных: поиск баланса

Начал знакомиться ближе с сетевыми протоколами, в частности с UDP. Ожидал всего, чего угодно: проблемы синхронизации, методы обеспечения надежной работы протокола, который этих гарантий не дает, бинарные форматы сериализации. Но посыпался на вопросе хранения данных. Рассказываю, в чем прикол.

Клиент способен строить детерминированную симуляцию движения всех тел в системе лишь на основе таймстемпа серверного времени. Но сервер также должен обладать "знанием" о эфемеридах, как минимум для расчета позиций кораблей/флотов игроков. Отсюда первое требование — данные о системах и телах в их составе должны быть пошарены между клиентом и сервером.

Особенность серверных приложений, обслуживающих ММО-проекты, заключается в высочайших требованиях к латентности (latency). И чем быстрее будет доступ к данным — тем лучше. Поэтому нельзя просто так взять и сложить все в БД — слишком долгое ожидание. Второе требование — скорость доступа к данным на стороне сервера.

Представьте масштаб: 1000 систем, в каждой от 500 до 5000 тел. Без процедурной генерации поддерживать такое будет настоящим безумием. Замена одного поля в базовой структуре создает лавину из ошибок компиляции в связанных файлах. Третье требование — удобство поддержки.

У меня уже есть некоторые соображения, которыми можно поделиться.

Как сейчас: все в константах. Сделал отдельный крейт для подключения на клиенте и сервере. C точки зрения безопасности типов — идеальное решение. Все ошибки всплывают сразу, практически невозможно что-то забыть или присвоить значение неправильного типа. И, конечно, скорость доступа к константам достаточно высокая. Но есть и обратная сторона: раздутый бинарь. А на больших числах поддержка превращается в ад. Вот пример для одной планеты:

// ... const EARTH_ORBIT: KeplerOrbitParams = KeplerOrbitParams::new( EARTH_ORBIT_SEMI_MAJOR_AXIS, EARTH_ORBIT_ECCENTRICITY, EARTH_ORBIT_INCLINATION, EARTH_ORBIT_LONG_ASC_NODE, EARTH_ORBIT_ARG_PERIAPSIS, EARTH_ORBIT_MEAN_ANOMALY_AT_EPOCH, );

const EARTH_PHYSICS: PhysicalProperties = PhysicalProperties { mass: EARTH_MASS, radius: EARTH_RADIUS, gm: grav_param(EARTH_MASS), };

const EARTH_ATMOSPHERE: Atmosphere = Atmosphere::new(1.225, 101_325.0, 288.15, EARTH_PHYSICS.gm, EARTH_RADIUS);

const EARTH_ID: BodyId = BodyId(1);

pub const EARTH_SPEC: StaticBodySpec = StaticBodySpec { id: EARTH_ID, name: "Земля", obj_type: CelestialObjectType::Planet { size_class: PlanetSizeClass::Terrestrial, }, physics: EARTH_PHYSICS, orbit: EARTH_ORBIT, atmosphere: Some(EARTH_ATMOSPHERE), };

Что мне захотелось сделать: запихнуть все в RON/YAML/JSON. Плюсы: гибко, структурированно, понятно. Минусы: парсинг в рантайме, риск рассинхрона клиента и сервера, потеря типобезопасности.

И кажется, что я должен сделать "судьбоносный выбор", пойти на компромисс. Но, покуривая сигаретку на балконе, пришел к интересной мысли по части разделения и хранения данных.

Итак, следите за руками:

1. Уровень протоколов. Передать полное описание системы (массы, радиусы, орбиты, ресурсы) по UDP — значит раздуть пакет до нескольких килобайт и молиться, чтобы ни один фрагмент не потерялся. А потеря одного фрагмента убивает всю датаграмму. Поэтому тяжёлую часть, по возможности, надо исключить из сетевого обмена. 2. Уровень природы данных. Физические константы — неизменны и критичны для симуляции. Они одинаковы на клиенте и сервере, их можно просто вшить в бинарь. Игровое состояние (ресурсы, строения, войска) — изменчиво и нужно только при входе в систему. Фрагменты состояния можно сделать достаточно компактными, чтобы они влезли в один UDP-пакет.

Возможно, идеального разделения не существует, и граница всегда будет плавающей — зависящей от размера мира, требований к латентности и даже от того, насколько часто я буду перекраивать галактику. Но сам процесс поиска уже расставил много точек над "i", чему я очень рад 🙂