MAATRIX GAMES / Блог / Garry's Mod: Lua-скрипт лагает сервер — поиск и оптимизация

Garry's Mod: Lua-скрипт лагает сервер — поиск и оптимизация

MAATRIX GAMES

Сервер живёт своей жизнью: заходишь один — всё летает, набирается 20 игроков — тикрейт проседает, хитрегистрация опаздывает, а в консоли сервера потихоньку растёт server lag warning. Первая мысль — "мало CPU", но в 8 случаях из 10 в Garry's Mod виноват конкретный Lua-скрипт: чей-то криво написанный хук на Think, addon с Steam Workshop, который никто не трогал два года, или самописный геймлей-режим. Ниже — рабочий процесс поиска виновника через профайлер и конкретные паттерны, которые чаще всего сажают тикрейт.

Почему в Garry's Mod вообще лагает именно Lua

GMod построен на движке Source, а вся игровая логика — сервер, клиент и часть UI — крутится в Lua поверх него. Движок сам по себе тянет довольно много одновременно активных серверов без проблем, но Lua-интерпретатор в GMod однопоточный: весь код аддонов, гейммода и плагинов выполняется последовательно в одном потоке на каждый тик сервера. Если какой-то хук отрабатывает 15 мс вместо 0.5 мс — это не "фоновая задержка", это прямая потеря тикрейта, потому что следующий тик просто не может начаться, пока не закончился текущий.

Отсюда правило: в GMod почти никогда не "не хватает железа" в буквальном смысле — сервер на 4 ядрах с гигантским запасом RAM всё равно будет лагать, если один хук на 200 NPC в DarkRP-карте пересчитывает пути каждый тик. Поднять сервер с большим количеством CPU и памяти, конечно, не помешает (сравнение тарифов — в статье про оптимизацию под высокую нагрузку), но без поиска конкретного виновника в коде это лечит симптом, а не причину.

Первый шаг: убедиться, что дело в Lua, а не в сети или железе

Прежде чем лезть в профайлер, отсеките банальные причины:

  • Хостовый лаг vs сетевой лаг. В консоли сервера включите host_timer_spew 1 — если Server lag warning не появляется, а игроки жалуются на рывки, вероятнее дело в сети или в клиентской части, а не в серверном Lua.
  • CPU sv_hibernate. Проверьте, что sv_hibernate_think и sv_hibernate_ms не выставлены криво — иногда сервер "спит" между тиками дольше, чем нужно, из-за конфига, а не из-за скриптов.
  • Общий мониторинг TPS/лагов. Если у вас уже настроен мониторинг вроде описанного в статье про мониторинг TPS и лагов, сверьтесь с графиком — лаг-спайки коррелируют с конкретными событиями (заход волны NPC, спавн машины, вайп базы) или размазаны равномерно? Событийные спайки почти всегда указывают на конкретный хук.

Если хостовый лаг подтверждён и не объясняется сетью — переходим к профилированию.

Поднять сервер Garry's Mod за пару минут

Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.

Создать сервер

Профилирование через встроенный lua_profiler

У Source Engine есть встроенный профайлер Lua, который не требует установки аддонов — он уже в движке.

// в консоли сервера (не клиента!)
lua_profiler_enable 1
lua_profiler 1
// подождать 30-60 секунд под реальной нагрузкой
lua_profiler_report

Отчёт lua_profiler_report выводит список функций, отсортированных по суммарному времени выполнения, с указанием файла и строки. Смотрите не на "среднее время вызова" — оно часто маленькое, а на суммарное время за интервал и на количество вызовов. Функция, которая весит 0.1 мс, но вызывается 500 раз за тик (например, повешенная на Think без троттлинга), в сумме съедает 50 мс — а бюджет всего тика на дефолтном тикрейте 66 — около 15 мс.

Важно: не забудьте выключить профайлер после диагностики (lua_profiler_enable 0), он сам по себе создаёт небольшую накладную нагрузку и не должен постоянно висеть на продакшн-сервере.

Профилирование через gm_profiler / GLuaProfiler

Встроенный lua_profiler даёт сырые числа, но неудобен для отслеживания конкретно хуков по имени и по владельцу-аддону. Для этого сообщество использует GLuaProfiler (модуль вроде gm_profiler или Lua-реализации типа GLuaTest/Profiler от известных разработчиков DarkRP-сцены) — он вешается на все зарегистрированные хуки и timers и показывает разбивку именно по ним.

Общий подход при установке такого профайлера:

  1. Кладёте аддон в garrysmod/addons/ (или через Workshop, если это коллекция — см. установку аддонов из Steam Workshop).
  2. Перезапускаете сервер, чтобы Lua-файлы подхватились.
  3. Запускаете сбор данных на 1-2 минуты под реальным онлайном (профилировать пустой сервер бессмысленно — лаги от Think-хуков NPC, машин, зомби-волн видны только при нагрузке).
  4. Смотрите топ по hook.Add — там почти всегда видно название аддона в имени идентификатора хука ("MyGamemode_Think", "DarkRP_CheckMoney" и т.п.).

Если у вас DarkRP или похожий тяжёлый гейммод — обычно топ занимают хуки экономики (проверка зарплаты по таймеру на каждого игрока), хуки дверей/собственности и NPC AI. Если это чистый sandbox с аддонами — чаще всего виноват один конкретный тяжёлый SWEP или scripted vehicle с плохо написанным Think.

Частые паттерны, которые сажают тикрейт

1. Think-хук без троттлинга. Самая частая причина. Кто-то вешает логику на GM:Think или hook.Add("Think", ...), которая должна выполняться раз в секунду, но вызывается на каждом тике (то есть 66 раз в секунду на дефолтном тикрейте).

Плохо:

hook.Add("Think", "CheckAllPlayersMoney", function()
    for _, ply in ipairs(player.GetAll()) do
        ply:GiveMoney(CalculateSalary(ply)) -- тяжёлый расчёт каждый тик
    end
end)

Хорошо — троттлинг через таймер, а не через Think:

timer.Create("CheckAllPlayersMoney", 60, 0, function()
    for _, ply in ipairs(player.GetAll()) do
        if IsValid(ply) then
            ply:GiveMoney(CalculateSalary(ply))
        end
    end
end)

timer.Create с интервалом сам управляет частотой вызова и не насилует движок на каждый тик — используйте его для всего, что не обязано реагировать мгновенно (зарплата, автосохранение, проверка АФК, спавн волн NPC).

2. Тяжёлые циклы внутри GM:Tick / think у Entity. У каждой entity с ENT.Think (SWEP, NPC, транспорт) есть собственный Think, который вызывается движком отдельно. Если таких сущностей на карте 200 (толпа NPC в DarkRP или зомби-волна), а каждая делает traceline или обход всех игроков внутри своего Think — это уже 200 тяжёлых операций каждый тик. Решение — разносить проверки по кадрам (например, проверять только каждую 5-ю или 10-ю сущность на тик по счётчику) или увеличивать self:NextThink(CurTime() + 0.2) вместо дефолтных значений.

3. player.GetAll() и подобные обходы внутри вложенных циклов. Вызов player.GetAll() не бесплатный — он создаёт таблицу заново. Если он вызывается внутри цикла по NPC, которые сами в цикле по игрокам — получается вложенность O(n×m) на каждый тик. Кешируйте результат в переменную один раз за тик/таймер, а не вызывайте функцию заново на каждой итерации.

4. net/util.TableToJSON и сериализация на каждый чих. Отправка больших net-сообщений или сериализация крупных таблиц в JSON внутри часто вызываемого хука (например, синхронизация инвентаря при каждом изменении, а не батчем) создаёт лаги и по CPU, и по сети одновременно.

5. SQL-запросы синхронно в основном потоке. Если аддон дёргает MySQL/SQLite синхронно внутри хука обработки урона или чата — каждый запрос блокирует весь Lua-поток до ответа базы. Правильно — асинхронные запросы (mysqloo, tmysql4) с колбэком, а не блокирующий вызов. Если у вас плагины завязаны на MySQL, стоит свериться с общими принципами настройки в статье про MySQL для игровых плагинов — многие грабли там пересекаются с GMod-специфичными.

Как локализовать виновный аддон, если их установлено 40+

На типичном DarkRP или TTT сервере легко набирается 30-50 аддонов из Workshop, и профайлер покажет десятки строк. Порядок действий:

  1. Сортируйте отчёт по суммарному времени, а не по числу вызовов — один медленный хук важнее сотни быстрых.
  2. Смотрите имя файла в пути (addons/darkrp_modification_xxx/lua/...) — профайлер обычно указывает путь, откуда взят Lua-файл, это сразу называет аддон.
  3. Бинарный поиск через отключение аддонов. Если профайлер не даёт чёткой картины (бывает с обфусцированными аддонами), временно переносите половину папок из addons/ в addons_disabled/ (создайте такую папку сами, GMod её не тронет), перезапускайте сервер и смотрите, ушёл ли лаг. Дальше делите пополам, пока не найдёте конкретный аддон — классический bisect, минут 20-30 работы, зато результат гарантированный.
  4. Проверьте обфусцированные аддоны отдельно. Некоторые платные DarkRP-модификации шифруют Lua через gluac или подобные инструменты — профайлер в таком случае покажет только имя функции без внятного пути. Для них бисект через отключение — единственный практичный способ, если нет доступа к исходникам.

Быстрые меры на время правки и что делать с найденным виновником

Если сервер лагает прямо сейчас, а разбор кода займёт время — временно снизьте нагрузку без переписывания логики:

МераКоманда/действиеЭффект
Снизить лимит NPCai_disabled 1 (тест) или лимит в конфиге гейммодаУбирает Think-нагрузку от AI сразу
Ограничить сущностиent_create лимиты в гейммоде / вайп лишних propsМеньше объектов = меньше суммарного Think
Временно отключить подозрительный аддонПереместить папку в addons_disabledПроверка гипотезы без даунтайма всего сервера
Снизить maxplayers на пиковые часыmaxplayers в конфиге запускаМеньше циклов по игрокам в хуках экономики/HUD
Увеличить sv_maxrate/сетевые тикиОсторожно, тестовоИногда путают сетевой лаг с тикрейтом — стоит проверить отдельно

Это костыли, не решение — но они держат сервер играбельным, пока вы бисектите аддоны и правите хуки.

Дальше — что делать с найденным виновником. Если это ваш собственный код или доработка — правьте паттерны выше (Think → timer, троттлинг, кеш player.GetAll(), асинхронный SQL). Если это чужой Workshop-аддон:

  • Проверьте, обновлялся ли он за последний год — заброшенные аддоны часто написаны под старую версию GMod и не учитывают более новый оптимизированный API.
  • Поищите форк или альтернативу с тем же функционалом — на DarkRP-сцене почти у каждого популярного аддона есть 2-3 форка, из которых один обычно менее прожорливый.
  • Если аддон критичен и без исходников — напишите автору или, если у вас есть Lua-опыт, декомпилируйте обфусцированную часть через gluac - и точечно исправьте самый тяжёлый хук, не трогая остальное.

Держите под рукой лог падений и предупреждений — если после правок лаги ушли, но сервер начал падать по другой причине, разбор в статье про логи и краш-репорты поможет не спутать новую проблему со старой.

Поднять сервер Garry's Mod за пару минут

Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.

Создать сервер

Частые вопросы

Профайлер сам создаёт лаги — можно ли профилировать продакшн-сервер с игроками?

Да, lua_profiler и большинство сторонних профайлеров рассчитаны именно на живой сервер под нагрузкой — накладные расходы заметны, но не критичны на 1-2 минуты сбора данных. Просто не держите его включённым постоянно и предупредите игроков, что возможны кратковременные микрофризы во время замера.

После оптимизации хуков тикрейт всё равно скачет — в чём дело?

Проверьте, не совпадают ли скачки с конкретными событиями — открытие крупной коллекции карты, спавн волны NPC, массовый respawn после вайпа. Иногда проблема не в постоянной нагрузке, а в разовом тяжёлом событии, которое профайлер, снятый в "спокойный" момент, просто не поймал — переснимайте профиль именно во время события.

Есть ли смысл поднимать тикрейт сервера (66 вместо дефолтных значений), если лагает Lua?

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

Можно ли автоматически отслеживать деградацию тикрейта, чтобы не ждать жалоб игроков?

Да, стоит держать постоянный лёгкий мониторинг (без постоянно включённого профайлера) — общие подходы к этому описаны в статье про мониторинг TPS и лагов на игровом сервере, принципы применимы и к GMod с поправкой на его собственные метрики хостового лага.

Проблема в геймплейном режиме целиком, а не в одном аддоне — стоит ли переписывать гейммод?

Если после бисекта и оптимизации ключевых хуков (экономика, NPC AI, двери) тикрейт всё ещё плохой на пиковом онлайне — вероятно, дело в архитектуре гейммода (слишком много синхронной логики на каждый тик по дизайну), и тогда действительно может понадобиться пересмотр ключевых системных хуков, а не точечные патчи.