Garry's Mod: Lua-скрипт лагает сервер — поиск и оптимизация
Сервер живёт своей жизнью: заходишь один — всё летает, набирается 20 игроков — тикрейт проседает, хитрегистрация опаздывает, а в консоли сервера потихоньку растёт server lag warning. Первая мысль — "мало CPU", но в 8 случаях из 10 в Garry's Mod виноват конкретный Lua-скрипт: чей-то криво написанный хук на Think, addon с Steam Workshop, который никто не трогал два года, или самописный геймлей-режим. Ниже — рабочий процесс поиска виновника через профайлер и конкретные паттерны, которые чаще всего сажают тикрейт.
Содержание
- Почему в Garry's Mod вообще лагает именно Lua
- Первый шаг: убедиться, что дело в Lua, а не в сети или железе
- Профилирование через встроенный lua_profiler
- Профилирование через gm_profiler / GLuaProfiler
- Частые паттерны, которые сажают тикрейт
- Как локализовать виновный аддон, если их установлено 40+
- Быстрые меры на время правки и что делать с найденным виновником
Почему в 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 lagwarning не появляется, а игроки жалуются на рывки, вероятнее дело в сети или в клиентской части, а не в серверном 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 и показывает разбивку именно по ним.
Общий подход при установке такого профайлера:
- Кладёте аддон в
garrysmod/addons/(или через Workshop, если это коллекция — см. установку аддонов из Steam Workshop). - Перезапускаете сервер, чтобы Lua-файлы подхватились.
- Запускаете сбор данных на 1-2 минуты под реальным онлайном (профилировать пустой сервер бессмысленно — лаги от Think-хуков NPC, машин, зомби-волн видны только при нагрузке).
- Смотрите топ по
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, и профайлер покажет десятки строк. Порядок действий:
- Сортируйте отчёт по суммарному времени, а не по числу вызовов — один медленный хук важнее сотни быстрых.
- Смотрите имя файла в пути (
addons/darkrp_modification_xxx/lua/...) — профайлер обычно указывает путь, откуда взят Lua-файл, это сразу называет аддон. - Бинарный поиск через отключение аддонов. Если профайлер не даёт чёткой картины (бывает с обфусцированными аддонами), временно переносите половину папок из
addons/вaddons_disabled/(создайте такую папку сами, GMod её не тронет), перезапускайте сервер и смотрите, ушёл ли лаг. Дальше делите пополам, пока не найдёте конкретный аддон — классический bisect, минут 20-30 работы, зато результат гарантированный. - Проверьте обфусцированные аддоны отдельно. Некоторые платные DarkRP-модификации шифруют Lua через
gluacили подобные инструменты — профайлер в таком случае покажет только имя функции без внятного пути. Для них бисект через отключение — единственный практичный способ, если нет доступа к исходникам.
Быстрые меры на время правки и что делать с найденным виновником
Если сервер лагает прямо сейчас, а разбор кода займёт время — временно снизьте нагрузку без переписывания логики:
| Мера | Команда/действие | Эффект |
|---|---|---|
| Снизить лимит NPC | ai_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, двери) тикрейт всё ещё плохой на пиковом онлайне — вероятно, дело в архитектуре гейммода (слишком много синхронной логики на каждый тик по дизайну), и тогда действительно может понадобиться пересмотр ключевых системных хуков, а не точечные патчи.