Physgun Wiki

Производительность

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 уровня файла:

local MATERIAL = Material('models/openrp/ui_kit.png', 'noclamp smooth') local GHOST_COLOR = Color(255, 255, 255, 100)

Color, Vector и Angle детектор ловит только с постоянными аргументами. Color(r, g, b) от переменных ошибкой не считается, но объект лучше не создавать вовсе: у векторов есть изменяющие методы, и vec:Add(offset) дешевле, чем vec + Vector(0, 0, 4).

Обход игроков

Итераторы не создают таблицу на каждый вызов:

for _, ply in player.Iterator() do for _, ent in ents.Iterator() do

Для счёта есть player.GetCount() — считать #player.GetAll() не надо.

Разовый обход в таймере — например, уборщик пропов раз в 300 секунд — нормально: это не горячий путь.

Профессии не читают через player_manager

player_manager.GetPlayerClassTable() и GetPlayerClasses() делают table.Copy на каждый вызов. У профессии вроде гражданского это копия списка из 130 моделей, а Job() вызывается в том числе из HUD, каждый кадр.

Поэтому профессия берётся из собственного реестра:

ply:Job() -- профессия игрока roleplay.Jobs[id] -- любая профессия по идентификатору

Реестр отдаёт ту же таблицу, что зарегистрирована в player_manager, а не копию. Читать из неё можно свободно, менять нельзя: правка ply:Job().Salary изменит профессию сразу для всех игроков. Состояние конкретного игрока хранится на самом игроке.

Лукап вместо поиска

Поиск по массиву растёт линейно, обращение по ключу — почти постоянное:

local doorClasses = { ["func_door"] = true, ["prop_door_rotating"] = true } function ENTITY:IsDoor() return doorClasses[self:GetClass()] == true end

Так же устроены roleplay.HUD.Hidden и roleplay.Chat.HiddenTypes — списки-фильтры, по которым хук делает лукап.

Сеть

Слать нужно изменения, а не состояние по таймеру, и точными типами: net.WriteUInt(count, 8) вместо net.WriteTable. Постоянное состояние сущностей — это сетевые переменные, а не ручная рассылка, см. сетевые переменные.