Производительность
Lua в Garry's Mod однопоточен и делит такт с игрой. За горячими путями следят всерьёз: Think, Move, отрисовка — всё, что выполняется каждый кадр или на каждого игрока.
За их пределами приоритет у читаемости. Локализовать каждый глобал, разворачивать циклы и кэшировать то, что считается раз за игру, не нужно: оптимизация без замера и без горячего пути — это просто менее понятный код. Выполняется часто — оптимизируем, выполняется по действию игрока — пишем понятно.
Правила BadCoderz
Аддон BadCoderz ловит тяжёлые вызовы внутри рендер- и тик-хуков. Список ниже взят из его исходников.
Опасными считаются Think, Tick, PlayerTick, PlayerPostThink, PlayerDeathThink, Move, HUDPaint, HUDPaintBackground, Paint панелей, ENT:Draw, ENT:DrawTranslucent, CalcView, CalcMainActivity, а также все Pre*Draw* и Post*Draw*.
Внутри них не должно быть:
| Вызов | Почему |
|---|---|
Material, surface.CreateFont, surface.GetTextureID |
читают с диска |
Color, Vector, Angle с константными аргументами |
мусор для сборщика каждый кадр |
vgui.Create |
панель создаётся по действию игрока, не в отрисовке |
player.GetAll, ents.GetAll, table.HasValue |
линейный обход каждый кадр |
любой file.*, CompileString, RunString |
работа с диском и компиляция на лету |
Материалы и цвета выносят в local уровня файла:
Color, Vector и Angle детектор ловит только с постоянными аргументами. Color(r, g, b) от переменных ошибкой не считается, но объект лучше не создавать вовсе: у векторов есть изменяющие методы, и vec:Add(offset) дешевле, чем vec + Vector(0, 0, 4).
Обход игроков
Итераторы не создают таблицу на каждый вызов:
Для счёта есть player.GetCount() — считать #player.GetAll() не надо.
Разовый обход в таймере — например, уборщик пропов раз в 300 секунд — нормально: это не горячий путь.
Профессии не читают через player_manager
player_manager.GetPlayerClassTable() и GetPlayerClasses() делают table.Copy на каждый вызов. У профессии вроде гражданского это копия списка из 130 моделей, а Job() вызывается в том числе из HUD, каждый кадр.
Поэтому профессия берётся из собственного реестра:
Реестр отдаёт ту же таблицу, что зарегистрирована в player_manager, а не копию. Читать из неё можно свободно, менять нельзя: правка ply:Job().Salary изменит профессию сразу для всех игроков. Состояние конкретного игрока хранится на самом игроке.
Лукап вместо поиска
Поиск по массиву растёт линейно, обращение по ключу — почти постоянное:
Так же устроены roleplay.HUD.Hidden и roleplay.Chat.HiddenTypes — списки-фильтры, по которым хук делает лукап.
Сеть
Слать нужно изменения, а не состояние по таймеру, и точными типами: net.WriteUInt(count, 8) вместо net.WriteTable. Постоянное состояние сущностей — это сетевые переменные, а не ручная рассылка, см. сетевые переменные.
