Skip to content

Repository files navigation

Мониторинг нагрузки на физику от тяжелых кластеров/гридов

с оповещенеим игроков-владельцев структур и переводом структуры в статику (Нужен плагин Profiler) img_1.png img.png Конфигурация:

  • Enable physics guard - вкл/выкл проверку нагрузки на физику
  • Physics checks before punish - Количество проваленных проверок до оповещения или конвертации в статику
  • Physics ms to alert - Если стуктура обсчитывается дольше указанного времени то игрок получает оповещение
  • Physics ms to punish - Если стуктура обсчитывается дольше указанного времени то структура становится статичной через некоторое количество проверок(Physics checks before punish)
  • Physics ms to punish immediately - Если стуктура обсчитывается дольше указанного времени то структура становится статичной немедленно

Заморозка

Структуры рядом с которыми нет игроков перестают обсчитываться на сервере Если на структуре есть производственные блоки то раз в Min WakeUp Interval In Sec секунд структура просыпается на несколько секунд, и заводы/сборщики компенсируют время заморозки переработывая за следующий после разморозки тик большую пачку руды или собирая большую пачку помпонентов
img_2.png

  • Freeze Physics - Если параметр включен, то динамические структуры которые не закреплены об что-то статическое, в некотором смысле становятся статикой

Мониторинг нагрузки сварки динамики в сз

img_3.png

  • Сварка/распил динамики в сз, или особенно на границе сз может вызывать ощутимые микрофризы, такие динамические структуры в сз автоматически переводятся в статику если вызывают фризы больше Safe zone Physics Threshold миллисекунд

Сварка проекций без фризов

Сварка целиком выполняется в игровом потоке: мутации блоков, конвейеры и Havok небезопасно трогать из воркеров. Параметр Async weld больше ни на что не влияет и оставлен только для совместимости конфига. Вместо переноса в потоки работа сварщиков по проекции ограничена бюджетом на кадр:

  • Projection builds per frame (по умолчанию 1, минимум 1) - сколько блоков проекции может материализовать весь сервер за один кадр симуляции. Лимит общий для всех сварщиков: право строить передаётся между ними по кругу, поэтому несколько сварщиков в одном кадре не складывают свои Projector.Build в один длинный кадр.
  • Projection checks per activation (по умолчанию 24, минимум 1) - сколько новых проверок CanBuild делает одна активация сварщика. Вместо ванильного полного скана голограммы на каждой активации проверяется небольшой кусок, курсор продолжает с того же места в следующий раз, а соседи только что построенного блока проверяются первыми.

Оба параметра меняются на лету. Множитель радиуса сварщика (плагин SentisGameplayImprovements, toolradius welder) не имеет верхнего предела, минимум 0.1.

Почему так: профиль dotTrace на 3 сварщиках показал, что время уходит не на сам Build (в среднем ~0.5 мс), а на ванильный поиск строящихся блоков: FindProjectedBlocks + CanBuild + TestPlacementAreaCube суммарно ~25 с CPU за 90 с сварки, и весь скан выполнялся в одном кадре.

Результат (стенд welder_perf из SentisTests: 3 сварщика, проекция 10×10×10 из тяжёлой брони, 1000 блоков, время работы кадра симуляции без сна до 60 Гц):

avg p99 p99.9 max кадров > 16.7 мс
прогретый сервер ~1.1 мс ~4 мс 6-8 мс 10-13 мс 0 из 7470

Самый долгий Projector.Build на прогретом сервере 2-6 мс, самые тяжёлые кадры сварки 8-10 мс (совпадают с GC). Единственный оставшийся пик от сварки - первый Build после рестарта сервера (24-36 мс, один раз): холодный путь (JIT или первая загрузка модели блока), причина не подтверждена. Подробности замеров и методика: docs/welder-perf.md в репозитории SentisTests.

Очистительные заводы без фризов

Все изменения выполняются в игровом потоке. Общий объём переработки не уменьшается, а сама переработка идёт быстрее ванили.

  • Разнесение таймеров. Ванильный таймер производственного блока срабатывает раз в 60 кадров. Блоки, созданные одновременно (загрузка мира, спавн, вставка), срабатывают в одном и том же кадре, и на большой базе это давало кадры 50-77 мс. Каждому таймеру назначается случайная фаза, и работа заводов равномерно распределяется по кадрам. Действует при создании блока (загрузка мира, спавн, вставка); касается и сборщиков. Параметров нет.
  • Добор руды до своей доли вместо ванильного 30%/60%. Ваниль тянет по 2000 кг, пока вход не заполнен на 60%, поэтому первые заводы забирают руду, а остальные простаивают. Доля завода - вся руда сети (контейнеры плюс входы всех включённых работающих заводов), делённая на число таких заводов. Завод добирает до доли; доборы меньше 500 кг пропускаются, почти пустой завод (<100 кг) всегда получает руду.
  • Бюджет доборов: не больше 30 доборов руды за кадр на весь сервер, первыми обслуживаются самые пустые заводы.
  • Пустая сеть: если в сети нет руды, завод не сканирует её каждую секунду, а ждёт 2, 4, 8... до 10 с (с разбросом по заводам). Как только руда появляется в любом отдающем инвентаре сети (контейнер, коннектор, бур) или меняется граф конвейеров, ожидание сразу отменяется.

Сборка мусора в лёгких кадрах

В .NET Framework нет GC без пауз: каждая сборка gen0/gen1 останавливает игровой поток, и чаще всего это случается в тяжёлом кадре. Плагин запоминает, насколько обычно вырастает куча до естественной сборки gen0. Когда израсходовано 60% этого бюджета, он сам вызывает сборку gen0 в конце лёгкого кадра (работа кадра не больше 4 мс). Сборок становится больше, но они короче (~2 мс) и приходятся на кадры с запасом. Действует на весь сервер, параметров нет.

Результат на стенде refinery_perf из SentisTests (64 грида, 6464 больших завода, только железная руда, 45 тыс. т), время работы кадра симуляции:

время переработки p95 p99 max кадров > 16.7 мс
ваниль 112-115 с 33-38 мс 41-43 мс 68-77 мс 960-1065
все оптимизации 99 с 6.7 мс 9.8 мс 122 мс* 4

* Одиночные паузы GC вне заводов. Такие кадры 120-190 мс были и без плагина и, по всей видимости, связаны с автосейвом большого мира. Подробности, все промежуточные замеры и найденные ловушки: docs/refinery-perf.md в репозитории SentisTests.

Сохранение мира

Сохранение (автосейв, !save) сначала собирает снимок мира в игровом потоке, и только потом пишет его на диск в фоне. Почти всё время снимка уходит на GetObjectBuilder гридов, а игроки видят это как один долгий кадр.

  • Параллельный снимок гридов. BeforeSave и object builder остальных сущностей по-прежнему создаются в игровом потоке в ванильном порядке. Object builder гридов собираются на всех ядрах, пока игровой поток ждёт завершения, поэтому мир во время снимка не меняется и сейв остаётся атомарным. Если параллельная сборка бросит исключение, снимок пересобирается последовательно, а параллельный режим отключается до рестарта сервера.
  • Сериализация компонентов блоков без лишних выделений. Ванильный MyComponentContainer.Serialize создаёт временный список на каждый тип компонента каждого блока и пустой контейнер, даже когда сохранять нечего (например, у конвейеров). Замена даёт тот же XML без этих объектов. Действует везде, где сериализуются блоки (сейв, чертежи, репликация).

Параметров нет. Результат на стенде save_perf из SentisTests (64 грида, 32 тыс. блоков):

ваниль сейчас
кадр со снимком 135-151 мс 49-70 мс
выделения на один грид (501 блок) 604 КБ 416 КБ

Остаток кадра - сборки мусора (4 gen0 и 1 gen1), которые вызывает создание object builder. Подробности: docs/save-perf.md в SentisTests.

  • Замороженные гриды собираются заранее. Если фризер включён и есть замороженные гриды, запрос на сохранение (автосейв, /save, !save) откладывается на несколько кадров. В конце каждого кадра собираются object builder замороженных гридов, не больше 3 мс работы на кадр. Потом запускается обычное сохранение, и в кадре снимка собираются только незамороженные гриды. Замороженный грид не обновляется, поэтому его заранее собранный снимок совпадает со снимком в момент сохранения (на стенде проверено сравнением XML). Заранее собираются только гриды, замороженные не меньше 5 секунд; уровни присутствия (tiers), которые меняются и у замороженных гридов, берутся с грида в момент снимка. Заранее собранный снимок отбрасывается, если грид разморозился (в том числе при пробуждении), закрылся, у него добавили или удалили блок или изменился инвентарь. Сохранение при выгрузке мира не откладывается.

Результат на стенде frozen_save_perf (64 грида, 56 заморожено, 8 держатся маяком из AntifreezeBlocksSubtypes): кадр снимка больше не выделяется, максимальный кадр за время сохранения - 17 мс (без фризера 49-70 мс), подготовка - 20-24 кадра по 3-5 мс, сохранение целиком занимает на ~0.3 с дольше. Изменения замороженного грида другими путями (смена владельца, переименование, свойства блоков через терминал или моды) за эти доли секунды не отслеживаются.

Асинхроннная отправка клиенту игры данных о структурах/вокселях

  • Параметр Fix Voxel Freeze Enabled включает механизм при котором сжатие и отправка сущностей клиентам игры происходит в отдельном потоке. В результате этого основной поток симуляции не фризит при сжатии данных для больших обёмов вокселей или больших структур

Репликация: меньше работы на кадр при многих клиентах

Изменения не настраиваются и включены всегда. Замеры - сценарий replication_perf из SentisTests (64 статичных завода, 10 летающих гридов, 64 клиента с эмуляцией сети), подробности в docs/replication-perf.md того плагина.

  • Сравнение адресов без боксинга (EndpointComparers) - EqualityComparer<T>.Default для Endpoint и EndpointId уходил в сравнение через object, то есть каждое обращение к словарям репликации выделяло память.
  • Компактные данные синхронизации свойств (PropertySyncClientData) - вместо массива на 256 пакетов для каждого клиента хранится таблица (ключ, биты), растущая с 4 записей.
  • Грязные группы состояния по индексу клиентов (StateGroupClients) - ваниль искала каждую грязную группу у каждого клиента, и при 64 клиентах один кадр уходил на это до 70 мс. Теперь у группы есть список клиентов, которым она реплицируется, а дубликаты в очереди отбрасываются.
  • Кэш билдеров гридов для стрима (GridStreamBuilders) - собранный MyObjectBuilder_CubeGrid переживает кадры, в нём обновляется только изменившееся: позиция и скорости каждый раз, блоки - по событиям, а блоки, меняющиеся сами по себе (батареи, баки, поршни, двери, таймеры), пересобираются при выдаче. Варианты со скриптами и без нужны FuckScriptThief, который отдаёт скрипт программного блока только тем, у кого есть права. Массовое подключение 64 клиентов: 140 с → 81 с.
  • Бюджет на стрим гридов (StreamingSerializeBudget) - сборка новых билдеров ограничена 3 мс на кадр, вся работа по стриму - 6 мс; отложенное возвращается в очередь клиента на следующий кадр.
  • Инвентари только тем, кто смотрит (IdleInventorySync) - игра считает приоритет группы инвентаря (1 тому, у кого блок открыт, 0 остальным), но при рассылке его не использует, поэтому инвентари всех ящиков и заводов в зоне видимости идут каждому клиенту каждые несколько кадров. Клиенту, у которого ничего не открыто, они планируются со случайной задержкой в пределах ~600 кадров, а в момент открытия терминала все его инвентари тем же кадром переставляются в начало очереди. Максимальный кадр в тесте 106 мс → 61 мс, кадров дольше 33 мс: 161 → 68.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages