Привет!
Мы пишем свой мультиплеер для GTA V, называется CloudV:MP. Сервер авторитетный, весь мир держится на нём, клиент только рисует. Основную ставку делаем на плотность, то есть чтобы реально много народу собиралось в одном месте и синхра не рассыпалась, как на рейдже и т.д. Видео с тестов и презу с нагрузочными замерами приложил.
Сервер целиком на Rust, с нуля. Мир построен на ECS, взяли bevy_ecs. ECS это когда сущности не объекты с методами, а наборы компонентов, лежащие плотными массивами, и системы гоняют по ним пачками. За счёт этого оно кэш-френдли и нормально параллелится, обычная ООП-модель на такой плотности просто легла бы. Тысячи сущностей в одном такте тянутся спокойно.
Сам такт распараллелен по всем ядрам через rayon, состояние мира для каждого игрока считается конкурентно, а не в один поток. На горячих путях конкурентные структуры, dashmap и parking_lot.
Сеть своя, бинарный протокол поверх UDP, есть и QUIC (quinn). Асинхронка на tokio, на проводе сериализуем через postcard, он компактный. На сессию два канала: надёжный для важного и потоковый для позиций.
С шифрованием чуть заморочились. Ключи меняются через X25519 (ECDH), сервер подписывается Ed25519, весь трафик под AES-GCM. Хеши и MAC на BLAKE3, SHA-2, HMAC. Коннект прикрыт от спуф-флуда: stateless SYN-cookie плюс рейт-лимит по IP ещё до того, как вообще выделяется сессия и крипта, так что задёшево заддосить хендшейк не выйдет.
Вся векторная математика на glam (SIMD), с детерминированным режимом, это важно чтобы позиции и физика сходились одинаково у всех.
Про репликацию: есть Area of Interest, он же AOI (сетка релевантности, кому кого вообще видно), клапан плотности, дельты с кейфреймами.
Метрики в Prometheus, распределённый трейсинг через OpenTelemetry, краши и паники летят в Sentry/GlitchTip. В реалтайме видим частоту такта, задержки p50/p95/p99, потери пакетов, CPU и RAM, ширину канала на игрока. На этом и строятся замеры из презы.
Нагрузку тестируем не абстрактно. Гоняем флот ботов, которые дают настоящую нагрузку игрока: тот же путь ingest, авторинг, раздача, тот же трафик и стоимость такта, а не пустышки. Конкретные цифры по ёмкости, задержкам и структуре такта в презентации.
Инфра обычная, Linux, systemd, nginx. Деплой самопроверяющийся, катим целый коммит, собираем на боксе, рестарт, и сервер сам подтверждает что поднялся именно нужный билд, иначе автооткат.
Из интересного:
Интерес-менеджмент по приоритетам. Не каждой сущности в зоне видимости нужен апдейт каждый такт. Далёкая машина спокойно обновляется пару раз в секунду, а пешеход рядом идёт на полной частоте. Каждая сущность копит приоритет (важность на обратный квадрат расстояния), в такт уходят самые накопившие в рамках бюджета, остальные продолжают копить и рано или поздно тоже отправятся. Ничего не голодает и не мигает, а в тот же байтовый бюджет влезает в разы больше сущностей, чем при тупом "ближние N каждый такт".
Лаг-компенсация. Попадания считаются на сервере с перемоткой. Сервер оценивает смещение часов каждого клиента по обычному трафику и, когда игрок стреляет, откатывает мир к тому моменту, который стрелок реально видел у себя, и только тогда проверяет попадание. То есть стрельба честная и при пинге, а не "я же попал, а урона нет"
Оверлоад-ладдер. Сервер меряет, сколько занимает сборка такта (со сглаживанием), и если начинает не влезать в бюджет, мягко деградирует по ступеням: сначала режет частоту обновления дальних, потом ужимает радиус видимости, потом включает жёсткий кап на видимое. Когда нагрузка отпускает, так же ступенчато возвращается, с гистерезисом чтобы не дёргаться туда-сюда. Смысл в том, что под пиком сервер сам подстраивает качество, а не падает.
Скриптинг. Геймод пишется на C# или на JavaScript, через наш мост. То есть логику сервера можно писать на нормальных языках, а не только на условном Lua.
Видео
Жду вашего фидбека! Спасибо)
Мы пишем свой мультиплеер для GTA V, называется CloudV:MP. Сервер авторитетный, весь мир держится на нём, клиент только рисует. Основную ставку делаем на плотность, то есть чтобы реально много народу собиралось в одном месте и синхра не рассыпалась, как на рейдже и т.д. Видео с тестов и презу с нагрузочными замерами приложил.
Сервер целиком на Rust, с нуля. Мир построен на ECS, взяли bevy_ecs. ECS это когда сущности не объекты с методами, а наборы компонентов, лежащие плотными массивами, и системы гоняют по ним пачками. За счёт этого оно кэш-френдли и нормально параллелится, обычная ООП-модель на такой плотности просто легла бы. Тысячи сущностей в одном такте тянутся спокойно.
Сам такт распараллелен по всем ядрам через rayon, состояние мира для каждого игрока считается конкурентно, а не в один поток. На горячих путях конкурентные структуры, dashmap и parking_lot.
Сеть своя, бинарный протокол поверх UDP, есть и QUIC (quinn). Асинхронка на tokio, на проводе сериализуем через postcard, он компактный. На сессию два канала: надёжный для важного и потоковый для позиций.
С шифрованием чуть заморочились. Ключи меняются через X25519 (ECDH), сервер подписывается Ed25519, весь трафик под AES-GCM. Хеши и MAC на BLAKE3, SHA-2, HMAC. Коннект прикрыт от спуф-флуда: stateless SYN-cookie плюс рейт-лимит по IP ещё до того, как вообще выделяется сессия и крипта, так что задёшево заддосить хендшейк не выйдет.
Вся векторная математика на glam (SIMD), с детерминированным режимом, это важно чтобы позиции и физика сходились одинаково у всех.
Про репликацию: есть Area of Interest, он же AOI (сетка релевантности, кому кого вообще видно), клапан плотности, дельты с кейфреймами.
Метрики в Prometheus, распределённый трейсинг через OpenTelemetry, краши и паники летят в Sentry/GlitchTip. В реалтайме видим частоту такта, задержки p50/p95/p99, потери пакетов, CPU и RAM, ширину канала на игрока. На этом и строятся замеры из презы.
Нагрузку тестируем не абстрактно. Гоняем флот ботов, которые дают настоящую нагрузку игрока: тот же путь ingest, авторинг, раздача, тот же трафик и стоимость такта, а не пустышки. Конкретные цифры по ёмкости, задержкам и структуре такта в презентации.
Инфра обычная, Linux, systemd, nginx. Деплой самопроверяющийся, катим целый коммит, собираем на боксе, рестарт, и сервер сам подтверждает что поднялся именно нужный билд, иначе автооткат.
Из интересного:
Интерес-менеджмент по приоритетам. Не каждой сущности в зоне видимости нужен апдейт каждый такт. Далёкая машина спокойно обновляется пару раз в секунду, а пешеход рядом идёт на полной частоте. Каждая сущность копит приоритет (важность на обратный квадрат расстояния), в такт уходят самые накопившие в рамках бюджета, остальные продолжают копить и рано или поздно тоже отправятся. Ничего не голодает и не мигает, а в тот же байтовый бюджет влезает в разы больше сущностей, чем при тупом "ближние N каждый такт".
Лаг-компенсация. Попадания считаются на сервере с перемоткой. Сервер оценивает смещение часов каждого клиента по обычному трафику и, когда игрок стреляет, откатывает мир к тому моменту, который стрелок реально видел у себя, и только тогда проверяет попадание. То есть стрельба честная и при пинге, а не "я же попал, а урона нет"
Оверлоад-ладдер. Сервер меряет, сколько занимает сборка такта (со сглаживанием), и если начинает не влезать в бюджет, мягко деградирует по ступеням: сначала режет частоту обновления дальних, потом ужимает радиус видимости, потом включает жёсткий кап на видимое. Когда нагрузка отпускает, так же ступенчато возвращается, с гистерезисом чтобы не дёргаться туда-сюда. Смысл в том, что под пиком сервер сам подстраивает качество, а не падает.
Скриптинг. Геймод пишется на C# или на JavaScript, через наш мост. То есть логику сервера можно писать на нормальных языках, а не только на условном Lua.
Видео
Жду вашего фидбека! Спасибо)
Вложения
Последнее редактирование:
