Лучшие практики

Dragokas

Добрая душа
Сообщения
229
Реакции
213
9. Лучшие практики:

  • Используйте RemoveEntity(entity); (в SM 1.10+), которая является быстрой и безопасной версией AcceptEntityInput(entity, "Kill"); (в SM <= 1.9) по сравнению с RemoveEdict(entity).
    - RemoveEdict в некоторых случаях может быть безопасной и хорошей для создания + удаления сущности в одной и той же функции, когда эта сущность вам едва ли потребуется в дальнейшем, например point_hurt и env_shake.

  • Перед использованием или убийством сущности убедитесь, что она действительна с помощью IsValidEntity();
    - Обязательно проверяйте на ноль if(entity != 0) или просто if(entity), потому что 0 - это сущность worldspawn и является действительной, таким образом вы можете мгновенно обвалить сервер.

  • Не имеет смысла в использовании if IsClientConnected(client) && IsClientInGame(client) одновременно, поскольку IsClientInGame уже включает в себя проверку IsClientConnected.

  • Используйте единый стиль именования переменных. Хорошее начало - "Венгерская нотация"
    - например, предваряя имя переменной префиксом g_ в случае, когда она объявляется как глобальная.

  • Используйте TIMER_FLAG_NO_MAPCHANGE для закрытия хендлов таймера автоматически по завершению карты. Детальнее с примерами см. дополнение "Как остановить (удалить) глобальный таймер".

  • !!iValue - для преобразования "int" в "bool"

  • Лично я объявляю переменные типа "char" как "static char", если они большие и вызываются несколько раз в дорогостоящих циклах или в Think* функциях.
    • Это оптимизирует скорость, потому что для такой переменной не требуется повторное выделение памяти. НО: Эта память никогда не уничтожается и навсегда остаётся там.
    • Используйте это с умом, т.е. только, когда вы действительно понимаете, что делаете. Это на самом деле даёт выигрыш для дорогостоящих в плане производительности функций и серверах с высоким тиком и 50-100+ сумасшедшими плагинами, где оптимизация становится просто необходима.
    • Однако: "использование static для локальных массивов - не является хорошим советом, это приводит к множеству багов, в тоже время выделение памяти в стеке - дешёвая операция." - asherkin.

  • Различия между глобально объявленным int и static int (см. тему - спасибо Bacardi). Вкратце: если сделать включение #include xx.sp, в котором глобально объявлена переменная static int a1, то эта переменная a1 будет недоступна основному sp, то есть её область видимости ограничивается sp-файлом, где она объявлена.

  • Если вы желаете присвоить одно и тоже значение всем элементам массива, вы можете это сделать прямо во время инициализации, а не через цикл позднее: int test[32] = {1, ...};
    - В противном случае int test[32] заполняется значениями по умолчанию, в данном случае 0.

  • TeleportEntity должен идти перед DispatchSpawn. Если сделать в обратном порядке, это может привести к крашу сервера. Такое случается с некоторыми типами моделей/сущностей, которые спавнятся в координатах 0,0,0.

  • switch вместо множества условий if else. Это вероятно ещё и быстрее. Больше информации здесь.
    - case секция использует константные значения. Она также поддерживает множественные значения, например case 1,2:

  • c[0] == 'a' вместо полного сравнения строки, к примеру, когда вы проверяете список известных моделей игроков. Найдите индекс в строке, где для каждой из них знак будет уникальным. Можно использовать совместно со switch().
    - Хороший пример от Lux можно найти здесь.

  • strcmp вместо StrEqual. Последнее - просто обёртка от первого и будет работать медленнее из-за создания дополнительного вызова.

  • FormatEx вместо Format. Используйте только, когда входящий и выходной буферы НЕ одинаковые.

  • FormatEx(blah, sizeof blah, "%d", i); - слегка быстрее, чем IntToString(i, blah, sizeof blah); но в целом, сравнения не делал, так что используйте любую.
    - Однако, обе они в 3 - 10x раз быстрее, чем Format(blah, sizeof blah, "%d", i);

  • StringMap.GetValue слегка быстрее, чем ArrayList.FindString и вероятно значительно быстрее на огромных массивах.

  • Предпочитайте SDKHook_Think и похожие функции вместо использования OnGameFrame.

  • Создавайте один таймер для всех игроков вместо отдельного таймера для каждого из них (например: плагины для регенерации здоровья).

  • delete и null являются предпочтительней CloseHandle и INVALID_HANDLE. delete также устанавливает хендл равным null, таким образом вам не нужно заботится об этом.

  • Слежение за кварами, передавая их хендл в HookConVarChange и хранение возвращаемого значения в глобальные переменные - является более оптимальным по скорости, чем постоянный вызов GetConvar*.
    - Посмотрите на любой из моих плагинов для примера.

  • Используйте %N вместо GetClientName() где это возможно, например, при использовании Format.

  • Используйте 8-ричные числа (0oN) для указания режима работы с файлами/папками. Пример:
    C-подобный:
    CreateDirectory(path, 0o755); // правильно
    CreateDirectory(path, 755); // ошибка

  • Учтите, что событие "round_end" не вызывается при смене карты. Подстраховуйте событием "map_transition". Это может оказаться критично, если вам требуется очистить некие переменные перед стартом следующего раунда. Кроме того, ни одно из этих событий не вызывается при смене карты через ForceChangeLevel, так что необходима ещё и дополнительная страховка через форвард OnMapEnd(). Альтернативой может служить событие "round_start", которое вызывается всегда после смены карты / окончания раунда.

  • Некоторые события, например "round_start" могут вызываться сдвоенно, т.е. дважды в течении нескольких фреймов. Учтите, это при создании критически важных объектов (например, таймеров, которые регенерируют здоровье).

  • Поздняя загрузка - Больше инфы здесь.
    • Поздняя загрузка (Late loading) - это когда плагин был вероятно загружен вручную во время игры, или перезагружен.
    • Из-за этого, плагин может начать работать не так, как было предусмотрено, потому что сработают не все события.
    • Вы можете определить, что плагин загружен как поздний и соответственно настроить всё, как положено при нормальной загрузке.
    PHP:
    bool g_bLateLoad;
    public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
    {
        g_bLateLoad = late;
        return APLRes_Success;
    }
    
    public void OnPluginStart()
    {
        if( g_bLateLoad )
        {
            for( int i = 1; i <= MaxClients; i++; )
            {
                if( IsClientInGame(i) )
                {
                    // Делаем что-либо специфическое для клиентов, которые уже находятся в игре, если они подключились до того, как плагин был загружен.
                    OnClientPutInServer(i);
                }
            }
        }
    }
    
    public void OnClientPutInServer(int client)
    {
        // Действия
    }

  • Выгрузка плагинов
    • Я советую также поддерживать выгрузку. Это значит удалять сущности, которые ваш плагин создавал или восстанавливать пропатченную память и т.д.
    • Это особенно полезно при тестировании вашего плагина в процессе написания, когда вы постоянно его перезагружаете, например, через плагин AutoReload.
    • Это также предотвратит случаи, когда прикреплённые модели застревают на игроках, потому что они больше не отслеживаются, но и не удаляются.

  • Поддержка Listen серверов - Listen сервера - это когда игровой клиент одновременно является сервером (хостом).
    • Выделенные сервера ВСЕГДА советуют использовать вместо Listen.
    • Индекс 0 на выделенном сервере - это сущность "World". На Listen серверах клиентский индекс через консоль также 0, что создаёт проблемы с командами, используемыми через консоль.
    • Выполнение команд через консоль, которые ожидают получения индекса клиента, часто приводит к ошибкам.
    • Некоторые функции SourceMod не поддерживают listen сервера и ожидают, что индекс клиента будет > 0. Ещё одна причина использовать Выделенные сервера.
    • Плагин Console Cmd As Host может помочь решить проблему с индексом 0 при команд чата.
    • Некоторый функционал SourceMod также не работает на Listen серверах, к примеру SendConVarValue когда я её тестировал.
    - Просто намотайте на ус: Не используйте Listen сервера!
 
Последнее редактирование:

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #2
StringMap.GetValue слегка быстрее, чем ArrayList.FindString и вероятно значительно быстрее на огромных массивах.
Поскольку под капотом используется хеш-таблица. Вместо того, чтобы хранить оригинальную строку-"ключ", хранится хеш от неё.
Отсюда следует понимать так же, что возможны коллизии, и в каких-то сверх-специфических кейсах можно словить ситуацию, когда записал в мапу значение с одним ключом, и возможно это самое значение получить по другому ключу.

delete и null являются предпочтительней CloseHandle и INVALID_HANDLE. delete также устанавливает хендл равным null, таким образом вам не нужно заботится об этом.
ИМХО, вот это - полная вкусовщина.
Я любитель "старой школы", и не люблю неявное поведение. Для меня очень долгое время был не очевидным тот момент, что delete так же выполняет приравнивание переменной к null.

Потому по возможности предпочитаю использовать связку handle.Close() (скрытый метод у методмапы Handle, реализован на уровне компилятора и является тупо алиасом к CloseHandle()) и явное присванивание переменной значения null.
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
Я любитель "старой школы", и не люблю неявное поведение. Для меня очень долгое время был не очевидным тот момент, что delete так же выполняет приравнивание переменной к null.

Потому по возможности предпочитаю использовать связку handle.Close() (скрытый метод у методмапы Handle, реализован на уровне компилятора и является тупо алиасом к CloseHandle()) и явное присванивание переменной значения null.
Я когда-то давно (после того как встретил delete) даже написал тестовый плагин, чтобы сравнить способы закрытия хэндлов.
Тогда и узнал, что delete приравнивает значение хэндла к null.
 

MAGNAT2645

Участник
Сообщения
63
Реакции
7
Потому по возможности предпочитаю использовать связку handle.Close() (скрытый метод у методмапы Handle, реализован на уровне компилятора и является тупо алиасом к CloseHandle()) и явное присванивание переменной значения null.
Тоже использую Handle.Close(), но только для локальных переменных т.к. не уверен: обнуляются только глобальные переменные или все?
Например, в MenuAction_End не использую delete просто потому, что menu после удаления, возможно, присваивается null (это надо проверять на практике), а в данном случае это лишняя операция.

В общем, использую delete только для глобальных дескрипторов.
 

Похожие темы

Ответы
0
Просмотры
210
Ответы
5
Просмотры
Ответы
1
Просмотры
245
Ответы
1
Просмотры
277
Сверху Снизу